当前位置: 首页 > news >正文

【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路

文章目录

  • C++ `string` 类的模拟实现:从构造到高级操作
    • 前言
    • 第一章:为什么要手写 C++ `string` 类?
      • 1.1 理由与价值
    • 第二章:实现一个简单的 `string` 类
      • 2.1 基本构造与析构
        • 2.1.1 示例代码:基础的 `string` 类实现
        • 2.1.2 解读代码
      • 2.2 浅拷贝与其缺陷
        • 2.2.1 示例代码:浅拷贝问题
      • 2.3 深拷贝的解决方案
        • 2.3.1 示例代码:实现深拷贝
    • 第三章:赋值运算符重载与深拷贝
      • 3.1 为什么需要重载赋值运算符?
      • 3.2 实现赋值运算符重载
        • 3.2.1 示例代码:赋值运算符重载
        • 3.2.2 解读代码
    • 第四章:迭代器与字符串操作
      • 4.1 迭代器的实现
        • 4.1.1 示例代码:实现 `string` 类的迭代器
    • 第五章:字符串的常见操作
      • 5.1 查找操作
        • 5.1.1 示例代码:实现字符和子字符串查找
      • 5.1.2 静态 `const` 成员变量初始化规则详解
        • 5.1.2.1 静态成员变量属于类,而不属于对象
        • 5.1.2.2 `const` 修饰符的作用
        • 5.1.2.3 整型和枚举类型的特殊处理
        • 5.1.2.4 复杂类型为什么不能在类内初始化?
        • 5.1.2.5 为什么 `static const size_t npos = -1` 可以在类内初始化?
        • 5.1.2.6 总结:为什么静态 `const` 的复杂类型不能在类内初始化
      • 5.2 插入操作
        • 5.2.1 示例代码:实现字符串插入
      • 5.3 删除操作
        • 5.3.1 示例代码:实现字符串删除
    • 读者须知与结语

C++ string 类的模拟实现:从构造到高级操作

💬 欢迎讨论:在实现 string 类的过程中,如果有任何疑问,欢迎在评论区交流!我们一起探讨如何一步步实现它。

👍 支持一下:如果这篇文章对你有所帮助,记得点赞、收藏并分享给更多对 C++ 感兴趣的小伙伴吧!你们的支持是我创作的动力!


前言

在 C++ 标准库中,string 类是用于字符串操作的一个非常常见和重要的类,它极大地简化了开发者处理字符串的过程。然而,为了深入理解 C++ 的核心机制,特别是内存管理、深拷贝与浅拷贝的差异、运算符重载等底层细节,自己实现一个简易的 string 类是一个很好的练习。

通过本篇博客,我们将一步步实现一个简单的 string 类,并且深入探讨与之相关的现代 C++ 特性,包括内存管理、深拷贝与浅拷贝、移动语义等。我们会从最基础的构造函数开始,逐步扩展功能。


第一章:为什么要手写 C++ string 类?

1.1 理由与价值

在面试或者一些学习场景中,手写 string 类不仅仅是对字符串操作的考察,更多的是考察程序员对 C++ 内存管理的理解。例如,深拷贝与浅拷贝的实现,如何正确重载赋值运算符,如何避免内存泄漏,这些都是需要掌握的核心技能。

实现一个简易的 string 类可以帮助我们更好地理解:

  1. C++ 中动态内存管理:如何正确地分配与释放内存。
  2. 深拷贝与浅拷贝的区别:当对象之间共享资源时,如何避免潜在问题。
  3. 运算符重载的实现:尤其是赋值运算符和输出运算符的重载。
  4. 现代 C++ 特性:包括移动语义、右值引用等。

接下来,我们会从一个简单的 string 类开始,逐步扩展。


第二章:实现一个简单的 string

2.1 基本构造与析构

我们先实现 string 类的基础部分,包括构造函数、析构函数、字符串存储、内存管理等基础操作。在最初的实现中,我们将模拟 C++ 标准库 string 类的基本行为,让其能够存储字符串,并在析构时正确释放内存。

2.1.1 示例代码:基础的 string 类实现
#include <iostream>
#include <cstring>   // 包含 strlen 和 strcpy 函数
#include <cassert>   // 包含 assert 函数namespace W
{class string{public:// 默认构造函数string(const char* str = "") {_size = strlen(str);  // 计算字符串长度_capacity = _size;_str = new char[_capacity + 1];  // 动态分配内存strcpy(_str, str);  // 复制字符串内容}// 析构函数~string() {if (_str) {delete[] _str;  // 释放动态分配的内存_str = nullptr;}}private:char* _str;  // 存储字符串的字符数组size_t _capacity;  // 分配的内存容量size_t _size;  // 当前字符串的有效长度};
}int main() {W::string s("Hello, World!");return 0;  // 程序结束时,析构函数自动释放内存
}
2.1.2 解读代码

在这个简单的 string 类中,我们实现了两个重要的函数:

  • 构造函数:为字符串动态分配内存,并将传入的字符串内容复制到新分配的空间中。
  • 析构函数:使用 delete[] 释放动态分配的内存,以避免内存泄漏。

接下来,我们将讨论拷贝构造函数以及浅拷贝带来的潜在问题。


2.2 浅拷贝与其缺陷

当前版本的 string 类只支持基本的构造和析构操作。如果我们通过另一个 string 对象来构造新的对象,默认情况下会发生浅拷贝,即对象共享同一块内存。这会带来潜在的内存管理问题,特别是当对象被销毁时,会导致多个对象同时试图释放同一块内存,进而导致程序崩溃。

2.2.1 示例代码:浅拷贝问题
void TestString() {W::string s1("Hello C++");W::string s2(s1);  // 浅拷贝,s1 和 s2 共享同一块内存// 当程序结束时,析构函数会尝试两次释放同一块内存,导致程序崩溃
}

问题分析:浅拷贝的默认行为只复制指针的值,即 s1s2 都指向同一个内存区域。因此,当程序执行析构函数时,会尝试两次释放同一块内存,导致程序崩溃。


2.3 深拷贝的解决方案

为了避免浅拷贝带来的问题,我们需要在拷贝构造函数中实现深拷贝。深拷贝确保每个对象都有自己独立的内存空间,不会与其他对象共享内存。

2.3.1 示例代码:实现深拷贝
namespace W
{class string{public:// 构造函数string(const char* str = "") {_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}// 深拷贝构造函数string(const string& s) {_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];  // 分配新的内存strcpy(_str, s._str);  // 复制字符串内容}// 析构函数~string() {delete[] _str;}private:char* _str;size_t _capacity;size_t _size;};
}void TestString() {W::string s1("Hello C++");W::string s2(s1);  // 深拷贝,s1 和 s2 拥有独立的内存
}

第三章:赋值运算符重载与深拷贝

3.1 为什么需要重载赋值运算符?

在C++中,当我们将一个对象赋值给另一个对象时,默认情况下,编译器会为我们生成一个浅拷贝的赋值运算符。这意味着赋值后的对象和原对象会共享同一个内存空间,这会导致和浅拷贝相同的潜在问题,特别是在一个对象被销毁时,另一个对象继续使用该内存区域会引发错误。

为了解决这个问题,我们需要手动重载赋值运算符,确保每个对象都拥有自己独立的内存空间。

3.2 实现赋值运算符重载

在赋值运算符重载中,我们需要考虑以下几点:

  1. 自我赋值:对象是否会被赋值给自己,避免不必要的内存释放和分配。
  2. 释放原有资源:在赋值前,我们需要释放被赋值对象原有的内存资源,避免内存泄漏。
  3. 深拷贝:为目标对象分配新的内存,并复制内容。
3.2.1 示例代码:赋值运算符重载
namespace W
{class string{public:// 构造函数string(const char* str = "") {_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}// 深拷贝构造函数string(const string& s) {_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];strcpy(_str, s._str);}// 赋值运算符重载string& operator=(const string& s) {if (this != &s) {  // 避免自我赋值delete[] _str;  // 释放原有内存_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];  // 分配新内存strcpy(_str, s._str);  // 复制内容}return *this;}// 析构函数~string() {delete[] _str;}private:char* _str;size_t _capacity;size_t _size;};
}void TestString() {W::string s1("Hello");W::string s2("World");s2 = s1;  // 调用赋值运算符重载
}
3.2.2 解读代码
  1. 自我赋值检查:自我赋值是指对象在赋值时被赋值给自己,例如 s1 = s1。在这种情况下,如果我们没有进行检查,就会先删除对象的内存,然后再试图复制同一个对象的内容,这样会导致程序崩溃。因此,重载赋值运算符时,自我赋值检查是非常必要的。

  2. 释放原有内存:在分配新内存之前,我们必须先释放旧的内存,以防止内存泄漏。

  3. 深拷贝:通过分配新的内存,确保目标对象不会与源对象共享内存,避免浅拷贝带来的问题。


第四章:迭代器与字符串操作

4.1 迭代器的实现

迭代器是一种用于遍历容器(如数组、string 等)的工具,它允许我们在不直接访问容器内部数据结构的情况下遍历容器。通过迭代器,可以使用范围 for 循环等简便的方式遍历 string 对象中的字符。

在我们的 string 类中,迭代器一般会被实现为指向字符数组的指针

4.1.1 示例代码:实现 string 类的迭代器
namespace W
{class string{public:// 非const迭代器typedef char* iterator;// const迭代器typedef const char* const_iterator;// 构造函数与析构函数等...// 非const迭代器接口iterator begin() { return _str; }iterator end() { return _str + _size; }// const迭代器接口(针对const对象)const_iterator begin() const { return _str; }const_iterator end() const { return _str + _size; }private:char* _str;size_t _capacity;size_t _size;};}void TestIterator() {W::string s("Hello World!");// 非const对象使用迭代器for (W::string::iterator it = s.begin(); it != s.end(); ++it) {*it = toupper(*it);  // 转换为大写}std::cout << s << std::endl;  // 输出:HELLO WORLD!// const对象使用const迭代器const W::string cs("Const String!");for (W::string::const_iterator it = cs.begin(); it != cs.end(); ++it) {std::cout << *it;  // 只能读取,不能修改}std::cout << std::endl;for (auto& ch : s) {ch = tolower(ch);  // 转换为小写}std::cout << s << std::endl;  // 输出:hello world!// 范围for循环遍历const对象for (const auto& ch : cs) {std::cout << ch;  // 只能读取,不能修改}std::cout << std::endl;
}

第五章:字符串的常见操作

在 C++ 标准库 string 类中,提供了很多方便的字符串操作接口,如查找字符或子字符串、插入字符、删除字符等。我们也需要在自定义的 string 类中实现这些操作。接下来,我们将逐步实现这些功能,并进行测试。


5.1 查找操作

C++ 中 string 类的 find() 函数用于查找字符串或字符在当前字符串中的位置。如果找到了字符或子字符串,find() 会返回其位置;如果找不到,则返回 string::npos

我们将在自定义的 string 类中实现类似的功能。

5.1.1 示例代码:实现字符和子字符串查找
namespace W
{class string{public:// 构造函数与析构函数等...// 查找字符在字符串中的第一次出现位置size_t find(char c, size_t pos = 0) const {assert(pos < _size);for (size_t i = pos; i < _size; ++i) {if (_str[i] == c) {return i;}}return npos;  // 如果没有找到,返回 npos}// 查找子字符串在字符串中的第一次出现位置size_t find(const char* str, size_t pos = 0) const {assert(pos < _size);const char* p = strstr(_str + pos, str);if (p) {return p - _str;  // 计算子字符串的位置}return npos;  // 如果没有找到,返回 npos}public:static const size_t npos = -1;  // 定义 npos 为 -1,表示未找到private:char* _str;size_t _capacity;size_t _size;};
}void TestFind() {W::string s("Hello, World!");// 查找字符size_t pos = s.find('W');if (pos != W::string::npos) {std::cout << "'W' found at position: " << pos << std::endl;} else {std::cout << "'W' not found." << std::endl;}// 查找子字符串size_t subPos = s.find("World");if (subPos != W::string::npos) {std::cout << "'World' found at position: " << subPos << std::endl;} else {std::cout << "'World' not found." << std::endl;}
}

看到这里细心的小伙伴可能发现了,我们在声明npos的时候直接给了初始值,但是之前我们在【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解里明确说过静态成员变量只能在类外初始化,以及const修饰的变量只能在初始化列表初始化,但这里却可以

这是为什么呢?不得不承认这是一看到就令人困惑的语法😂让我们来梳理一下

5.1.2 静态 const 成员变量初始化规则详解

5.1.2.1 静态成员变量属于类,而不属于对象

静态成员变量是在类层次上定义的,而不是在对象层次上。换句话说,静态成员变量是所有对象共享的,且只会有一份实例存在。因此,静态成员变量的内存是在程序的全局区域分配的,而不是在每个对象的内存中分配。

  • 静态变量需要在全局范围内被初始化,以确保在所有对象中共享的唯一实例具有一致的值。

5.1.2.2 const 修饰符的作用
  • const 表示变量的值在其生命周期内不能被修改。因此,const 静态成员变量的值必须在类加载时确定,并且在整个程序运行过程中保持不变。
  • 但是 const 静态成员的值不能在对象实例化时通过构造函数来提供,必须直接在类级别初始化。

5.1.2.3 整型和枚举类型的特殊处理

C++ 允许整型(如 intchar)和枚举类型const 静态成员变量在类内部进行初始化。这是因为这些类型的值可以在编译时完全确定,编译器不需要等待运行时计算或分配内存。

class MyClass {
public:static const int value = 42;  // 可以直接在类内初始化
};
  • 编译器可以将 value 当作编译时常量,它可以直接内联到使用它的代码中,不需要单独的存储空间。这种优化适用于常量表达式。

5.1.2.4 复杂类型为什么不能在类内初始化?

对于复杂类型(如 doublefloat 或自定义类等),这些类型的初始化可能涉及到运行时的计算或需要分配更多的内存。C++ 的设计者为了避免复杂类型的静态成员在类内初始化时增加不必要的复杂性,要求这些变量必须在类外进行初始化。

class MyClass {
public:static const double pi;  // 在类内声明,但不能直接初始化
};// 在类外初始化
const double MyClass::pi = 3.14159;

5.1.2.5 为什么 static const size_t npos = -1 可以在类内初始化?

size_t 是一种整型类型,尽管其大小和符号位取决于平台,但它仍然是整型常量的一种。因此,npos 的初始化类似于前面提到的整型静态成员变量。由于 -1 可以表示为 size_t 的最大值,这个值在编译时就可以确定,因此它符合类内初始化的条件。

class String {
public:static const size_t npos = -1;  // 可以在类内初始化
};
  • 总结:因为 npos 是整型常量,并且编译器可以在编译时确定其值,符合在类内部初始化的条件。

5.1.2.6 总结:为什么静态 const 的复杂类型不能在类内初始化
  • 整型和枚举类型const 静态成员变量可以在类内初始化,因为它们是编译时常量,编译器可以直接替换为常量值。
  • 复杂类型(如 double 或对象类型)需要在类外初始化,因为这些类型的初始化可能依赖运行时的条件或动态内存分配。
  • 这是 C++ 设计者在保证效率和复杂性之间做出的权衡,允许简单类型进行编译时优化,但要求复杂类型在类外显式初始化,以确保其初始化的灵活性和正确性。

没啥好说的,人家设计的,记住就行了🥲🥲


5.2 插入操作

C++ 中的 string 类允许我们在字符串的任意位置插入字符或子字符串。接下来,我们将在自定义的 string 类中实现类似的插入功能。

5.2.1 示例代码:实现字符串插入

其他没啥,注意下面这个问题:

无符号整型的易错问题👍

//注意:下面这个写法当pos==0时会出现死循环问题哦/*for (size_t i = _size; i >= pos; --i) {_str[i + len] = _str[i];}*/
namespace W
{class string{public:// 构造函数与析构函数等...// 在指定位置插入字符string& insert(size_t pos, char c) {assert(pos <= _size);  // 确保插入位置合法if (_size == _capacity) {reserve(_capacity * 2);  // 如果容量不够,扩展容量}// 将 pos 位置后的字符后移一位for (size_t i = _size; i > pos; --i) {_str[i] = _str[i - 1];}_str[pos] = c;++_size;_str[_size] = '\0';return *this;}// 在指定位置插入字符串string& insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);  // 如果容量不够,扩展容量}// 将 pos 位置后的字符后移 len 位//注意:下面这个写法当pos==0时会出现死循环问题哦/*for (size_t i = _size; i >= pos; --i) {_str[i + len] = _str[i];}*///采用这种for (size_t i = _size; i + 1 > pos; --i) {_str[i + len] = _str[i];}// 复制要插入的字符串memcpy(_str + pos, str, len);_size += len;_str[_size] = '\0';return *this;}private:char* _str;size_t _capacity;size_t _size;};
}void TestInsert() {W::string s("Hello World!");// 在第 5 位置插入逗号s.insert(5, ',');std::cout << s << std::endl;// 在第 6 位置插入字符串s.insert(6, " Beautiful");std::cout << s << std::endl;
}

5.3 删除操作

string 类允许我们删除指定位置的字符或子字符串。接下来,我们实现字符串的删除功能。

5.3.1 示例代码:实现字符串删除
namespace W
{class string{public:// 在指定位置删除若干字符string& erase(size_t pos, size_t len = npos) {assert(pos <= _size);  // 确保删除的位置合法if (len == 0 || pos == _size) {// 如果 len 为 0 或 pos 已经到达字符串末尾,无需执行任何操作return *this;}if (len == npos || pos + len > _size) {len = _size - pos;  // 确保不越界删除}// 将 pos 后 len 位字符前移for (size_t i = pos; i + len < _size; ++i) {_str[i] = _str[i + len];}_size -= len;_str[_size] = '\0';  // 更新字符串末尾return *this;}private:char* _str;size_t _capacity;size_t _size;public:static const size_t npos = -1;  // 定义 npos 为 -1 表示无效位置};
}void TestErase() {W::string s("Hello, Beautiful World!");// 删除第 5 位置后的 9 个字符s.erase(5, 9);std::cout << s << std::endl;  // 输出:Hello World!
}

读者须知与结语

在本文中,我们手写了一个简易版的 string 类,实现了诸如字符串插入、删除、查找等基本功能。然而,这个实现仍然是非常简陋的,使用了大量 C 风格的字符串函数,如 strlenstrcpy。这些函数都假设字符串是以 '\0' 结尾的字符数组,这意味着如果字符串中间出现 '\0',程序的行为将不可预期——它会错误地认为字符串已经结束。
此外,这个简易 string 类在面对一些复杂的情况时也会显得捉襟见肘。例如,我们并没有考虑多线程安全性异常处理等高级特性,而标准库的 std::string 早已针对这些问题进行了优化。标准库中的 string 类还支持更多的操作,并且在效率和内存管理上做了大量优化,因此我们的实现和真正的 std::string 相比可谓天差万别
但这并不是我们这篇文章的初衷。我们的目的是通过手写一个 string 类,让你深入理解底层的内存管理、拷贝控制、动态分配等核心概念。这些基础知识对于深入学习 C++ 编程和理解 STL 容器的实现原理至关重要。通过这个简化版的实现,希望你能更加透彻地理解 std::string 背后的机制。


如果你有任何问题或者更好的想法,欢迎在评论区分享你的观点。你们的反馈和支持是我创作的最大动力!

💬 欢迎讨论:如果你在学习过程中遇到问题,欢迎在评论区交流!

👍 支持一下:如果这篇文章对你有所帮助,请点赞、收藏并分享!你们的支持是我创作的动力!


以上就是关于【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

相关文章:

【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路

文章目录 C string 类的模拟实现&#xff1a;从构造到高级操作前言第一章&#xff1a;为什么要手写 C string 类&#xff1f;1.1 理由与价值 第二章&#xff1a;实现一个简单的 string 类2.1 基本构造与析构2.1.1 示例代码&#xff1a;基础的 string 类实现2.1.2 解读代码 2.2 …...

毕业设计选题:基于ssm+vue+uniapp的校园失物招领小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…...

[系统设计总结] - Proximity Service算法介绍

问题描述 Proximity Service广泛应用于各种地图相关的服务中比如外卖&#xff0c;大众点评&#xff0c;Uber打车&#xff0c;Google地图中&#xff0c;其中比较关键的是我们根据用户的位置来快速找到附近的餐厅&#xff0c;司机&#xff0c;外卖员也就是就近查询算法。 主流的…...

变压吸附制氧机的应用范围

变压吸附制氧机是一种利用变压吸附技术从空气中分离出氧气的设备。该技术通过吸附剂在不同压力下的吸附与解吸性能&#xff0c;实现了氧气的有效分离和纯化。 工业领域 在工业领域&#xff0c;变压吸附制氧机同样具有广泛的应用。首先&#xff0c;钢铁企业在生产过程中需要大量…...

MATLAB绘图基础8:双变量图形绘制

参考书&#xff1a;《 M A T L A B {\rm MATLAB} MATLAB与学术图表绘制》(关东升)。 8.双变量图形绘制 8.1 散点图 散点图用于显示两个变量间的关系&#xff0c;每个数据点在图上表示为一个点&#xff0c;一个变量在 X {\rm X} X轴&#xff0c;一个变量在 Y {\rm Y} Y轴&#…...

Appium高级话题:混合应用与原生应用测试策略

Appium高级话题&#xff1a;混合应用与原生应用测试策略 在移动应用开发领域&#xff0c;混合应用与原生应用各有千秋&#xff0c;但它们的测试策略却大相径庭。本文旨在深入探讨这两种应用类型的测试挑战&#xff0c;并介绍如何利用自动化测试软件ItBuilder高效解决这些问题&…...

windows源码安装protobuf,opencv,ncnn

安装笔记 cmake 在windows可以使用-G"MinGW Makefiles" 搭配make使用&#xff0c;install出来的lib文件时.a结尾的&#xff0c;适合linux下面使用。所以在windows上若无需求使用-G"NMake Makefiles" 搭配nmake。 但是windows上使用-G"NMake Makefil…...

MicroPython 怎么搭建工程代码

在MicroPython中搭建工程代码可以遵循以下步骤&#xff1a; 1. 准备工作 安装MicroPython固件&#xff1a;确保已经将MicroPython烧录到ESP32开发板中。准备开发环境&#xff1a; 可以使用文本编辑器&#xff08;如VS Code、Thonny、uPyCraft等&#xff09;来编写代码。 2.…...

Android studio安装问题及解决方案

Android studio安装问题及解决方案 gradle已经安装好了&#xff0c;但是每次就是找不到gradle的位置&#xff0c;每次要重新下载&#xff0c;很慢&#xff0c;每次都不成功 我尝试用安装android studio时自带的卸载程序&#xff0c;卸载android studio&#xff0c;然后重新下…...

前端面试题(二)

6. 深入 JavaScript this 关键字的指向是什么&#xff1f; this 的指向是在函数执行时决定的。默认情况下&#xff0c;非严格模式下 this 指向全局对象&#xff08;浏览器中为 window&#xff09;&#xff0c;严格模式下 this 为 undefined。在对象方法中&#xff0c;this 通常…...

【C++】stack和queue的使用及模拟实现

stack就是栈的意思&#xff0c;这个结构遵循后进先出(LIFO)的原则&#xff0c;可以将栈想象为一个子弹夹&#xff0c;先进去的子弹后出来。 queue就是队列的意思&#xff0c;这个结构遵循先进先出(FIFO)的原则&#xff0c;可以将对列想象成我们排队买饭的场景&#xff0c;先排…...

MongoDB解说

MongoDB 是一个流行的开源 NoSQL 数据库&#xff0c;它使用了一种被称为文档存储的数据库模型。 与传统的关系型数据库管理系统&#xff08;RDBMS&#xff09;不同&#xff0c;MongoDB 不使用表格来存储数据&#xff0c;而是使用了一种更为灵活的格式——JSON 样式的文档。 这…...

问:JAVA中唤醒阻塞的线程有哪些?

在Java中&#xff0c;唤醒阻塞线程的方法有多种&#xff0c;以下是常见的线程唤醒方法。 唤醒方法 使用notify()和notifyAll()方法 synchronized (obj) {obj.notify(); // 唤醒单个等待线程// obj.notifyAll(); // 唤醒所有等待线程 }使用interrupt()方法 Thread thread n…...

Github Webhook触发Jenkins自动构建

1.功能说明 Github Webhook可以触发Jenkins自动构建&#xff0c;通过配置Github Webhook&#xff0c;每次代码变更之后&#xff08;例如push操作&#xff09;&#xff0c;Webhook会自动通知Jenkins服务器&#xff0c;Jenkins会自动执行预定义的构建任务&#xff08;如Jenkins …...

ESP32-WROOM-32 [创建AP站点-客户端-TCP透传]

简介 基于ESP32-WROOM-32 开篇(刚买)&#xff0c; 本篇讲的是基于固件 ESP32-WROOM-32-AT-V3.4.0.0&#xff08;内含用户指南, 有AT指令说明&#xff09;的TCP透传设置与使用 设备连接 TTL转USB线, 接ESP32 板 的 GND&#xff0c;RX2&#xff0c; TX2 指令介绍 注意,下面指…...

新闻文本分类识别系统Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+TensorFlow+Django网页界面

一、介绍 文本分类识别系统。本系统使用Python作为主要开发语言&#xff0c;首先收集了10种中文文本数据集&#xff08;“体育类”, “财经类”, “房产类”, “家居类”, “教育类”, “科技类”, “时尚类”, “时政类”, “游戏类”, “娱乐类”&#xff09;&#xff0c;然…...

Java使用Map数据结构配合函数式接口存储方法引用

Java使用Map数据结构配合函数式接口存储方法引用 背景 需求中存在这样一直情况 一个国家下面有很多的州 每个州对应的计算日期方法是不同的 这个时候 就面临 可能会有很多if else 为了后期维护尽量还是不想采用这个方式&#xff0c;那么就可以使用策略模式 但是 使用策略带来的…...

LeetCode:2207. 字符串中最多数目的子序列(Java)

目录 2207. 字符串中最多数目的子序列 题目描述&#xff1a; 实现代码与解析&#xff1a; 遍历&#xff1a; 原理思路&#xff1a; 2207. 字符串中最多数目的子序列 题目描述&#xff1a; 给你一个下标从 0 开始的字符串 text 和另一个下标从 0 开始且长度为 2 的字符串 p…...

win10开机自启动方案总汇

win10开机自启动方案总汇 一、开始文件目录添加二、添加注册表启动程序三、服务启动3.1. 将程序注册为服务使用命令行创建服务设置服务启动类型启动服务 3.2. 使用 Windows 服务管理器配置服务3.3. 删除服务 四、定时任务或程序4.1 设置程序自启动&#xff08;使用任务计划程序…...

【自动驾驶】基于车辆几何模型的横向控制算法 | Stanley 算法详解与编程实现

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…...

微服务--初识MQ

在微服务架构中&#xff0c;MQ&#xff08;Message Queue&#xff0c;消息队列&#xff09;作为一种重要的通信机制&#xff0c;扮演着至关重要的角色。 MQ&#xff0c;即消息队列&#xff0c;是一种在不同服务或系统之间传递消息的中间件。它允许消息的发送者&#xff08;生产…...

车辆识别数据集,图片数量20500,模型已训练200轮

车辆识别数据集&#xff08;Vehicle Recognition Dataset, VDRD&#xff09; 摘要 VDRD 是一个专为车辆识别设计的大规模数据集&#xff0c;它包含了20500张不同类型的汽车、货车、公交车以及其他类型车辆的图像。数据集提供了四种车辆类别&#xff1a;汽车、货车、其他车辆和…...

MES系统如何提升制造企业的运营效率和灵活性

参考拓展&#xff1a;苏州稳联-西门子MES系统-赋能智能制造的核心引擎 制造执行系统(MES)在提升制造企业运营效率和灵活性方面发挥着关键作用。 一、MES系统的基本概念和功能 MES系统是连接企业管理层与生产现场的重要桥梁。它主要负责生产调度、资源管理、质量控制等多个方…...

Nexpose 6.6.270 发布下载,新增功能概览

Nexpose 6.6.270 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Sep 18, 2024 请访问原文链接&#xff1a;https://sysin.org/blog/nexpose-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.or…...

【数据库】sqlite

文章目录 1. 基本概述2. 主要特点3. 应用场景4. 优缺点5. 基本使用示例6. 在编程语言中的使用连接到 SQLite 数据库&#xff08;如果文件不存在会自动创建&#xff09;创建表插入数据提交事务查询数据关闭连接 7. 总结 SQLite 是一个轻量级的关系型数据库管理系统&#xff08;R…...

详解 C++中的模板

目录 前言 一、函数模板 1.定义 2.函数模板的实现 3.模板函数的实例化 4.模板参数的省略 1.函数模板的实参推导 2.类模板的实参推导 3.默认模板参数 4.特殊情况:无法推导的模板 5.推导失败的情况 二、类模板 1.概念和定义 2.类模板定义 3.类模板的使用 4.类模板…...

基于DAMODEL——Faster-RCNN 训练与测试指南

Faster-RCNN 训练与测试指南 前言 今天我们要来实现一个经典的目标检测模型&#xff1a;Faster-Rcnn。我们使用DAMODEL云平台来实现&#xff0c;这是个很强大的云端平台&#xff0c;功能众多&#xff0c;你可以投你所好去进行你想做的事情。 1. 环境与工具准备 1.1 远程连接…...

考研数据结构——C语言实现冒泡排序

冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的列表&#xff0c;比较每对相邻元素&#xff0c;并在顺序错误的情况下交换它们。这个过程重复进行&#xff0c;直到没有需要交换的元素&#xff0c;这意味着列表已经排序完成。冒泡排序的名字来源于较小的元素会逐…...

labview更换操作系统后打开原VI闪退

labview更换操作系统后打开原VI闪退 问题描述&#xff1a; Windows11由家庭版更换为专业版后&#xff0c;重新安装labview2021&#xff0c;打开原来的项目&#xff0c;项目管理器可以正常打开&#xff0c;但是打开VI却闪退&#xff0c;并报错如下 出现这种原因主要是labview在…...

什么是CAPTCHA?有什么用途?

一、CAPTCHA 的工作原理 CAPTCHA的核心目的是通过呈现人类可以轻松理解但计算机程序难以解决的任务&#xff0c;来阻止恶意的自动化工具。传统的CAPTCHA通过展示扭曲或模糊的文字、图片或者点击操作等&#xff0c;要求用户完成验证任务。这些任务通常需要视觉、听觉或简单的逻辑…...