• STL库:string


    STL库:string


    1.STL库对于string类的介绍

    官方介绍:

    1. 字符串是表示字符序列的对象
    2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性
    3. string 类是使用 char (即作为它的字符类型,使用它的默认 char_traits 和分配器类型(关于模板的更多信息,请参阅 basic_string)
    4. string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traits 和 allocator 作为 basic_string 的默认参数(关于更多的模板信息请参考 basic_string)
    5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

    简言之:

    1. string 是表示字符串的字符串类
    2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作
    3. string 在底层实际是 basic_string 模板类的别名 typedef basic_string string;
    4. 不能操作多字节或者变长字符的序列

    2.string常用接口的掌握

    2.1 string的构造接口

    string类的构造函数接口很多,记住以下常用的就可以:

    1. string(); // 默认构造,构造空的string类对象,即空字符串""

    2. string (const string& str); // 拷贝构造,用已有的string类对象去构造string类对象

    3. string (const char* s); // 用c-string来构造string类对象

    4. string (const char* s, size_t n); // 用c-string前n个字符来构造string类对象

    5. string (size_t n, char c); // 用n个c字符来构造string对象

    6. template // 用迭代器[first,last)范围内的字符序列构造string类对象

      string (InputIterator first, InputIterator last);

    接口使用举例:

    // string constructor
    #include 
    #include 
    
    int main()
    {
        std::string s0 ("Initial string");				   //string(const char* s);
    
        std::string s1;                                    // string();---空串里放了一个字符\0
        std::string s2 (s0);                               // string(cosnt string& str);
        std::string s4 ("A character sequence");           // string(const char* s);
        std::string s5 ("Another character sequence", 12); // string(const char* s,size_t n);
        std::string s6 (10, 'x');                          // string(size_t n,char c);
        std::string s7 (s0.begin(), s0.begin() + 7);       // string(InputIterator first,InputIterator last);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意:空串并不是没有,而是第一个字符放的\0,存储空间不为0

    请添加图片描述


    2.2 string的容量操作接口

    1. size:返回字符串有效长度(为了统一设计,所有容器都是用size表示有效元素个数,而抛弃了使用length)
    2. length:返回字符串有效字符长度(这是早期提供的接口)
    3. resize:将字符串大小调整为 n 个有效字符的长度
    4. capacity:返回有效字符的最大容量(即已分配 size 的大小)
    5. reverse:更改容量(capacity)的大小
    6. clear:清空字符串的内容,变为空字符串(size 变为 0,不改变 capacity 的大小)
    7. empty:检测字符串是否为空串,是返回 true,否则返回 false

    对于容量操作我们主要看resize、reverse:

    1.resize函数的两种重载形式:

    //resize的两种重载形式
    void resize (size_t n);
    void resize (size_t n, char c);
    
    //举例代码
    void Test1()
    {
    	string s("hello");
    
    	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
    	// "helloaaaaa"
    	s.resize(10, 'a');
    	cout << s.size() << endl;     // 10
    	cout << s.capacity() << endl; // 15
    	cout << s << endl;            // s: "helloaaaaa"
    
    	// 将s中有效字符个数增加到20个,多出位置用缺省值'\0'进行填充
    	// 如果resize参数大于原有 capacity 大小,会进行增容
    	// "helloaaaaa\0\0\0\0\0\0\0\0\0\0"
    	s.resize(20);
    	cout << s.size() << endl;     // 15
    	cout << s.capacity() << endl; // 31
    	cout << s << endl;            // s: "helloaaaaa"
    
        // 如果resize参数x原有 capacity 大小
    	// 将s中有效字符个数缩小到2个
    	// "he"
    	s.resize(2);
    	cout << s.size() << endl;     // 2
    	cout << s.capacity() << endl; // 31
    	cout << s << endl;            // s: "he"
    
    	// 将s的内容清空,注意清空时只是将size置0,不改变capacity的大小
    	s.clear();
    	cout << s.size() << endl;     // 0
    	cout << s.capacity() << endl; // 31
    	cout << s << endl;            // s: ""
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2.reserve函数的形式:

    //reserve函数的形式
    void reserve (size_t n = 0);
    
    //举例代码
    void Test2()
    {
    	string s("hellohellohello");
    
    	// 如果reserve参数大于原有 capacity 大小,会进行增容
    	s.reserve(20);
    	cout << s.size() << endl;     // 15
    	cout << s.capacity() << endl; // 31
    
    	// 测试reserve参数小于原有 capacity 大小,是否会将空间缩小呢?
    	// VS2019下,如果字符串有效长度size大于参数10,不会缩小,如果字符串长度小于参数10,会缩小
    	// 当然,这个也和编译器平台有关系
    	s.reserve(10);
    	cout << s.size() << endl;     // 15
    	cout << s.capacity() << endl; // 31(VS2019)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.思考:reverse扩容的大小:

    void Test3()
    {
    	string s;
    
    	cout << "initial value: " << s.capacity() << endl;
    
    	size_t sz = s.capacity();
    	for (size_t i = 0; i < 500; i++)
    	{
    		s.push_back('a');
    		if (s.capacity() != sz)
    		{
    			sz = s.capacity();
    			cout << "capacity changed: " << sz << endl;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请添加图片描述

    结论:VS下是1.5倍扩容,Linux下是2倍扩容


    2.3 string的访问操作接口

    string的访问操作通常使用[]运算重载来实现,就行数组下标一样

    operator[]:返回对字符串中 pos 位置处的字符的引用(string 类对象支持随机访问)(一般物理地址是连续的才支持)

    //operator[]函数的两种重载形式
    char& operator[] (size_t pos);
    const char& operator[] (size_t pos) const;
    
    //注意:operator[] 函数会自动检查越界(pos 必须小于 size),不像下标一样随机检查
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4 string的迭代器遍历操作接口

    1. begin(iterator):返回指向第一个有效字符的迭代器
    2. rbegin(reverse_iterator):反向迭代器(可以反向遍历对象)
    3. end:返回指向字符串末尾字符的迭代器(即最后一个有效字符的下一个位置)
    4. rend:反向迭代器
    5. 范围for:C++11支持更简洁的范围 for 的新遍历方式(底层其实是被替换成迭代器,所以支持迭代器就支持范围 for)

    请添加图片描述

    1.string迭代器的使用举例:

    //普通迭代器和const迭代器
    iterator begin();             // 可读可写
    const_iterator begin() const; // 只读
    
    //举例代码
    void test(const std::string& s) 
    {
        // const对象必须要用const迭代器
    	std::string::const_iterator it = s.begin();
    	while (it != s.end()) 
        {
    		std::cout << *it;
            it++;
    	}
    }
    
    int main()
    {
    	std::string s1;
    	std::string s2("hello");
        
    	// for+operator[]遍历
        for (size_t i = 0; i < s.size(); ++i)
    		cout << s2[i] << endl;
        
        // 正向迭代器遍历
        // 注意:这里不建议写成it < s2.end(),比如链式结构的容器,就没法用了
        // 所以统一写成 it != s2.end()
    	std::string::iterator it = s2.begin();
    	while (it != s2.end()) 
        {
    		std::cout << *it;
    		it++;
    	}
    	// 反向迭代器遍历
        for (std::string::reverse_iterator rit = s2.rbegin(); rit != s2.rend(); ++rit)
    		std::cout << *rit;
        for (auto rit = s2.rbegin(); rit != s2.rend(); ++rit) // 用auto简化代码
    		std::cout << *rit;
        
        // 范围for遍历,支持迭代器的容器就支持范围for
    	for (auto& e : s2) 
        {
    		std::cout << e;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    2.5 string的修改操作接口

    1. operator+=:在当前字符串末尾追加字符串(追加 string / char* / char 类型的都可以)
    2. append:在当前字符串末尾追加字符串
    3. push_back:将一个字符附加到字符串的末尾(尾插)(void push_back (char c);
    4. swap:交换两个字符串的内容(注意:还存在一个具有相同名称的非成员函数 swap)
    5. c_str:返回指向 C 格式字符串的数组的指针(const char* c_str() const;
    6. find:从 pos 位置开始往后找字符,返回该字符在字符串中的位置,如果未找到返回 npos
    7. rfind:从 pos 位置开始往前找字符,返回该字符在字符串中的位置,如果未找到返回 npos
    8. npos:作为返回值,通常用于表示不匹配(npos是一个静态成员变量 static const size_t npos = -1;
    9. substr:在字符串中从 pos 位置开始,截取 len 个字符,然后将其作为新的 string 类对象返回

    insert、erase不介绍,因为要挪动字符,时间效率太低

    追加内容举例代码:

    void Test4()
    {
    	string s1("hello");
    	string s2("world");
    
    	s1 += ' ';   // 追加字符
    	s1 += s2;    // 追加string类对象
    	s1 += "!!!"; // 追加字符串
    }
    
    //c_str举例代码
    void Test7()
    {
        string s("hello");
    	printf("%s\n", s.c_str()); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    find函数介绍:

    // 从pos位置开始往后找,默认从第一个字符的位置(即pos = 0)开始找
    size_t find (const string& str, size_t pos = 0) const;
    size_t find (const char* s, size_t pos = 0) const;
    size_t find (const char* s, size_t pos, size_t n) const; // 从pos位置往后匹配n个字符
    size_t find (char c, size_t pos = 0) const;
    
    void Test6()
    {
    	// 取出url中的协议、域名、uri
    	string url("http://www.cplusplus.com/reference/");
    
    	size_t pos1 = url.find("://");
    	if (pos1 != string::npos) 
    	{
    		cout << url.substr(0, pos1 - 0) << endl;
    	}
    
    	size_t pos2 = pos1 + 3;            // 'w'的位置
    	size_t pos3 = url.find('/', pos2); // '/'的位置,从pos2开始查找
    	if (pos3 != string::npos)
    	{
    		cout << url.substr(pos2, pos3 - pos2) << endl;
    	}
    	
    	cout << url.substr(pos3) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    substr函数介绍:

    // len: 从pos位置开始,要截取字符的个数
    string substr (size_t pos = 0, size_t len = npos) const; // 若len缺省,默认截取到字符串最后
    
    void Test5()
    {
    	// 取出文件1的后缀
    	string file1("test.txt");
    
    	size_t pos = file1.find(".");
    	if (pos != string::npos)
    	{
    		cout << file1.substr(pos) << endl; // .txt
    		// cout << file.substr(pos, file1.size() - pos) << endl;
    	}
    
    	// 取出文件2的后缀
    	string file2("test.txt.zip");
    
    	size_t rpos = file2.rfind(".");
    	if (rpos != string::npos)
    	{
    		cout << file2.substr(rpos) << endl; // .zip
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.6 string的非成员函数重载接口

    1. std::operator>>:流提取运算符重载
    2. std::operator<<:流插入运算符重载
    3. std::getline:获取一行字符串,直到遇到换行符 ‘\n’
    4. relational operators:关系运算符,进行大小比较
    5. std::operator+:尽量少用,因为是传值返回,导致深拷贝效率低
    6. std::swap:交换两个字符串的值
    //getline的介绍
    // istream& getline (istream& is, string& str);
    string s;
    getline(cin, s);
    
    • 1
    • 2
    • 3
    • 4

    2.7 string的特殊判断接口

    C语言头文件中处理字符的接口:

    1.字符串处理函数:

    • int isalpha(int c):检查字符是否为字母,是返回非零(true),不是则返回0(false)
    • int isdigit(int c):检查字符是否为十进制数字,是返回非零(true),不是则返回0(false)

    2.字符转换函数:

    • int tolower(int c):把字母转换成小写字母,返回转换之后的字符
    • int toupper(int c):把字母转换成大写字母,返回转换之后的字符

    C++头文件中处理字符的接口:

    1.函数 std::to_string(C++11):将数值转换为字符串,返回 string 类对象

    2.函数 std::stoi(C++11):将字符串转换为整数,返回 int 整数

    C++头文件中反转排序接口:

    1.函数 std::reverse:反转范围 [first,last) 中元素的顺序

    // 传一段迭代器区间 [first, last)
    template <class BidirectionalIterator> // 双向迭代器
    void reverse (BidirectionalIterator first, BidirectionalIterator last);
    
    • 1
    • 2
    • 3

    2.函数 std::sort:将 [first,last) 范围内的元素按升序排序

    // 传一段迭代器区间 [first, last),默认排升序,若要排降序,需要传仿函数
    template <class RandomAccessIterator, class Compare> // 随机访问迭代器
    void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
    
    • 1
    • 2
    • 3

    3.string的模拟实现

    3.1 string类的结构实现

    请添加图片描述

    #include
    #include
    #include
    using namespace std;
    
    namespace winter
    {
        class string
        {
    	private:
            char* _str;       // 指向字符数组
            size_t _size;     // 有效字符数
            size_t _capacity; // 有效字符容量,不包含最后作标识的'\0'
    
            static const size_t npos;
    
    	public:
            /*******************************************************/
            // 迭代器
            typedef char* iterator;
            typedef const char* const_iterator;
            iterator begin() { return _str; } // 返回指向第一个字符的迭代器
            iterator end() { return _str + _size; } // 返回指向最后一个字符下一个字符的迭代器
            const_iterator begin() const { return _str; } // 返回指向第一个字符的迭代器
            const_iterator end() const { return _str + _size; } // 返回指向最后一个字符下一个字符的迭代器
    
            /*******************************************************/
            // 默认成员函数:
    
            string(const char* str = ""); // 默认构造函数
            void swap(string& s); // 交换两个对象的内容
            string(const string& s); // 拷贝构造函数(深拷贝)
            string& operator=(string s); // 赋值运算符重载(深拷贝)
            ~string(); // 析构函数
    
            /*******************************************************/
            // 访问元素,[]运算符重载
            
            char& operator[](size_t pos);             // 可读可写
            const char& operator[](size_t pos) const; // 只读不能写
    
            /*******************************************************/
    		// 容量操作:
            
            // 获取字符串有效元素个数
            size_t size() const { return _size; } 
            // 获取字符串容量(有效字符的最大容量)
            size_t capacity() const { return _capacity; } 
            // 清空有效字符
            void clear() 
            {
                _str[0] = '\0';
                _size = 0;
            }
            // 更改容量(capacity)的大小
            void reserve(size_t n); 
            // 调整字符串有效字符的长度
            void resize(size_t n, char ch = '\0'); 
    
    		/*******************************************************/
            // 修改操作:
            
            string& insert(size_t pos, const char ch); // 在pos位置插入一个字符
            
            string& insert(size_t pos, const char* str); // 在pos位置插入一个字符串
            
            void push_back(const char ch); // 尾插一个字符
            
            void append(const char* str); // 在当前字符串末尾追加一个字符串
            
            string& operator+=(const char ch); // 当前字符串末尾追加一个字符/字符串
            string& operator+=(const char* str);
            string& operator+=(const string& s);        
            
            string& erase(size_t pos = 0, size_t len = npos); // 删除从pos位置开始的len个字符
            
            /*******************************************************/
            // String operations
            
            // 从pos位置开始查找字符,若找到,则返回该字符的下标,若没找到,则返回npos
            size_t find(char ch, size_t pos = 0) const;
    
            // 从pos位置开始查找子串,若找到,则返回该子串首字符的下标,若没找到,则返回npos
            size_t find(const char* str, size_t pos = 0) const;
    		
            // 返回指向 C 格式字符串的数组的指针
            char* c_str() const { return _str; }
        };
    
        const size_t string::npos = -1;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    3.2 string的默认成员函数模拟实现

    1.默认构造函数

    // 默认构造函数
    string(const char* str = "")  // 空串并不是什么都没有,第一个字符为'\0'
        :_size(strlen(str))
    	,_capacity(_size)
    {
    	_str = new char[_capacity + 1]; // 多开一个空间是存放'\0'的
    	strcpy(_str, str);              // 拷贝数据
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.交换容器内容swap函数

    // s1.swap(s2);
    void swap(string& s)
    {
        // 函数名冲突,指定去调用全局域里面的::swap
        ::swap(_str, s._str);
        ::swap(_size, s._size);
        ::swap(_capacity, s._capacity);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.拷贝构造函数

    // 拷贝构造函数(深拷贝)
    // s2(s1)
    string(const string& s)
        :_str(nullptr) // 当前对象是一个正在构造的对象,成员变量还未初始化,是一个随机值,所以先置空
    	,_size(0)
    	,_capacity(0)
    {
    	string tmp(s._str); // 拿s的内容,调用构造函数构造临时对象tmp
    	this->swap(tmp);    // 将临时对象tmp和当前对象的成员变量分别进行交换
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.赋值运算符重载

    // 赋值运算符重载(深拷贝)
    // s1 = s2
    string& operator=(string s)
    {
        this->swap(s); // 将拷贝构造的对象s和当前对象的成员变量分别进行交换
        return *this;  // 返回当前对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.析构函数

    // 析构函数
    ~string()
    {
        delete[] _str;
        _str = nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.3 string的浅拷贝、深拷贝问题

    需要「深拷贝」的类,其内部往往是很复杂的,是需要用户显式定义拷贝构造函数来完成「深拷贝」的

    浅拷贝举例代码:

    namespace winter
    {
    	class string
    	{
    	private:
    		char* _str;
            
    	public:
            // 构造函数
    		string(const char* s)
    			:_str(new char[strlen(s) + 1])
    		{
    			strcpy(_str, s);
    		}
    		
            // 析构函数
    		~string()
    		{
    			delete[] _str;
    			_str = nullptr;
    		}
    	};
    
    	void test()
    	{
    		string s1("hello"); // 用一个常量字符串去构造string类对象s1
    		string s2(s1); // s2调用编译器默认生成的拷贝构造函数
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    通过调试可以看到:编译器默认生成的拷贝构造函数是「浅拷贝」,会导致两个 string 对象中的字符指针 _str 指向的是同一个字符数组。(因为浅拷贝只拷贝了 _str 数组指针的 4 个字节的内容)

    请添加图片描述

    所以在上述类中必须要显式定义拷贝构造函数,否则编译器默认生成的拷贝构造函数无法正常完成拷贝

    • 总结:上述 string 类没有显式定义其拷贝构造函数与赋值运算符重载函数,此时编译器会默认生成一个,当用 s1 构造 s2 时,编译器会调用默认生成的拷贝构造函数。最终导致:s1 和 s2 共用同一块内存空间,在调用析构函数清理对象资源时,同一块空间被释放多次,引起程序崩溃,这种拷贝方式是浅拷贝,而我们实际需要的是深拷贝
    • 浅拷贝:也称位拷贝,编译器只是将对象中的数据「按字节序」拷贝过来。如果对象中管理的有其它资源(比如堆上的资源),那就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而另一些对象不知道该资源已经被释放,以为还有效,就继续对资源进行操作(比如增删查改),此时就会发生违规访问
    • 深拷贝:给每个对象独立分配资源,保证多个对象之间不会因为共享资源问题而造成多次释放资源,导致程序崩溃
    • 浅拷贝引发的问题:同一块空间会被析构多次,一个对象修改会影响另外一个对象

    3.4 string的拷贝构造的深拷贝实现

    string的拷贝构造的深拷贝实现有两种写法:传统写法、现代写法

    1.拷贝构造深拷贝传统写法:

    //浅拷贝
    string(const char* s)
    	:_str(new char[strlen(s) + 1])
    {
    	strcpy(_str, s);
    }
    
    // 显式定义拷贝构造函数(深拷贝)
    string(const string& s) // 保护形参不被改变,加引用防止无穷递归
        :_str(new char[strlen(s._str) + 1])  // 给新对象申请一段和原对象一样大小的空间
    {
    	strcpy(_str, s._str); // 把原对象的数据一一拷贝给新对象
    }
    
    //测试代码
    void test()
    {
    	string s1("hello"); // 用一个常量字符串去构造string类对象s1
    	string s2(s1); // s2调用编译器默认生成的拷贝构造函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    请添加图片描述

    2.深拷贝的现代写法:

    // 拷贝构造函数(深拷贝)
    string(const string& s)
        :_str(nullptr) // 当前对象是一个正在构造的对象,成员变量还未初始化,是一个随机值,所以先置空
    {
    	string tmp(s._str);   // 拿s的内容,调用构造函数构造临时对象tmp
    	swap(_str, tmp._str); // 将临时对象tmp和当前对象的成员变量_str进行交换
    }
    
    //测试代码
    void test()
    {
    	string s1("hello"); // 用一个常量字符串去构造string类对象s1
    	string s2(s1); // s2调用编译器默认生成的拷贝构造函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    请添加图片描述


    3.5 string的赋值运算符重载函数的深拷贝实现

    string的赋值运算符重载函数的深拷贝实现分为:传统写法、现代写法

    需要「深拷贝」的类,其内部往往是很复杂的,是需要用户显式定义赋值运算符重载函数来完成「深拷贝」的

    1.赋值运算符重载函数的深拷贝传统写法:

    // 显式定义赋值运算符重载(深拷贝)
    string& operator=(const string& s)
    {
        if (this != &s) // 防止自己给自己赋值
        {
            char* tmp = new char[strlen(s._str) + 1]; // 重新开辟一块和s一样大小的空间
            delete[] _str;                            // 释放自己的空间
            _str = tmp;
            strcpy(_str, s._str);                     // 把s的数据拷贝过来
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    请添加图片描述

    2.赋值运算符重载函数的深拷贝现代写法:

    通过参数间接调用拷贝构造函数,将「拷贝构造出来的 string 类对象 s」和「当前对象」的成员变量分别进行交换即可,这样当前对象就拿到了自己想要的内容,当函数调用结束后,拷贝构造出来的对象 s 出了作用域会被自动析构

    // 赋值运算符重载(深拷贝)
    // s1 = s2
    // 写法一:
    string& operator=(const string& s) // 传引用
    {
        if (this != &s) // 防止自己给自己赋值
        {
            string tmp(s._str);   // 拿s的内容,调用构造函数构造临时对象tmp
            swap(_str, tmp._str); // 将临时对象tmp和当前对象的成员变量_str进行交换
        }
        return *this;
    }
    
    // 写法二:
    string& operator=(string s) // 重点:传值
    {
        // 传参时,调用拷贝构造函数,拷贝构造了一个string类对象s
    
        // 将拷贝构造出来的string类对象s和当前对象的成员变量_str进行交换
        swap(_str, s._str);
        
        return *this; // 返回当前对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    请添加图片描述


    3.6 string的迭代器模拟实现

    class string
    {
    private:
        char* _str;       // 指向字符数组
        size_t _size;     // 有效字符数
        size_t _capacity; // 有效字符容量,不包含最后作标识的'\0'
    
        static const size_t npos;
    
    public:
        /*******************************************************/
        // 迭代器
        // iterator是内嵌类型,在stirng类域里面定义的类型
        
        typedef char* iterator;
        typedef const char* const_iterator;
        
        iterator begin() { return _str; } // 返回指向第一个字符的迭代器
        iterator end() { return _str + _size; } // 返回指向最后一个字符下一个字符的迭代器
        
        const_iterator begin() const { return _str; } // 返回指向第一个字符的迭代器
        const_iterator end() const { return _str + _size; } // 返回指向最后一个字符下一个字符的迭代器
        // ...
        // ...
    };
    const size_t string::npos = -1;
    
    //测试代码
    void test3()
    {
        string s1("hello world");
    
        // iterator是内嵌类型,在stirng类域里面定义的类型
        // 告诉编译器,要到string类域里面去找
        string::iterator it = s1.begin();
        while (it != s1.end())
        {
            cout << *it;
            it++;
        }
        cout << endl;
    
        // 范围for的原理就是被替换成迭代器
        for (auto e : s1)
        {
            cout << e;
        }
        cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    3.7 string的访问操作运算符重载模拟实现

    //对于普通数组来说:越界读一般是检查不出来的,越界写会抽查,可能会检查出来
    //对于string类来说:越界读写都会被检查出来,因为[]中进行了严格的检查
    
    // 普通版本
    char& operator[](size_t pos) // 可读可写
    {
        assert(pos < _size);
        return _str[pos]; // *(_str + pos)
    }
    
    //const版本
    const char& operator[](size_t pos) const // 只读不能写
    {
        assert(pos < _size);
        return _str[pos]; // *(_str + pos)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.8 string的容量操作函数模拟实现

    1.resize函数:调整字符串有效字符的长度

    // 调整字符串有效字符的长度
    void resize(size_t n, char ch = '\0')
    {
        // 要调整的有效字符的长度小于原有 _size 大小
        if (n < _size)
        {
            _size = n;          // 更新有效字符个数
            _str[_size] = '\0'; // 补上字符串结束标志'\0'
        }
        // 要调整的有效字符的长度大于原有 _size 大小
        else if (n > _size)
        {
            // 要调整的有效字符的长度大于原有 _capacity 大小,先进行增容
            if (n > _capacity) reserve(n);
    
            // 多出的位置用字符 ch(缺省值'\0')进行填充
            for (size_t i = _size; i < n; i++)
            {
                _str[i] = ch;
            }
            _size = n;          // 更新有效字符个数
            _str[_size] = '\0'; // 补上字符串结束标志'\0'
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.reverse函数:更改容量(capacity)的大小

    // 更改容量(capacity)的大小
    void reserve(size_t n)
    {
        if (n > _capacity)
        {
            // 开辟新空间
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);  // 旧空间数据拷贝到新空间
    
            // 释放旧空间,使用新空间
            delete[] _str;
            _str = tmp;    // 指向新空间
            _capacity = n; // 更新容量
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.9 string的修改操作函数模拟实现

    1.insert函数:在pos位置插入一个字符
    请添加图片描述

    // 在pos位置插入一个字符串
    string& insert(size_t pos, const char* str)
    {
        assert(pos >= 0 && pos <= _size);
    
        // 先检查是否需要扩容
        size_t len = strlen(str);
        if (_size + len >= _capacity)
        {
            reserve(_size + len);
        }
    
        // 挪动字符
        for (size_t i = _size + len; i >= pos + len; i--)
        {
            _str[i] = _str[i - len];
        }
        // 插入字符
        for (size_t i = 0; i < len; i++)
        {
            _str[pos++] = str[i];
        }
        _size += len; // 更新有效字符个数
    
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.push_back函数:尾插一个字符

    void push_back(const char ch)
    {
    	//方法一:
        // 先检查是否需要扩容
        if (_size >= _capacity)
        {
        	// 防止是空串"",容量为0,扩容失败
            size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
    
            reserve(newcapacity); // 扩2倍容
        }
    
        _str[_size] = ch;   // 尾插字符
        _size++;            // 有效字符个数+1
        _str[_size] = '\0'; // 补上字符串结束标志'\0'
    
      
        //方法二: 复用 insert 函数的代码 
        insert(_size, ch);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.append函数:在当前字符串末尾追加一个字符串

    void append(const char* str)
    {
    	//方法一:
        // 先检查是否需要扩容
        size_t len = strlen(str);
        if (_size + len > _capacity)
        {
            reserve(_size + len); // 扩容
        }
    
        strcpy(_str + _size, str); // 尾插字符串(strcpy会拷贝'\0',并在该点停止)
        _size += len;              // 有效字符个数+len
    	
    
        //方法二:复用 insert 函数的代码 
    
        insert(_size, str);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.+=运算符重载函数:在当前字符串末尾追加一个字符或字符串

    string& operator+=(const char ch)
    {
        push_back(ch);
        return *this;
    }
    string& operator+=(const char* str)
    {
        append(str);
        return *this;
    }
    string& operator+=(const string& s)
    {
        append(s._str);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.erase函数:删除从 pos 位置(包含 pos 位置)开始的 len 个字符

    erase删除分为两种情况

    1.情况一:

    • 如果 len 缺省,表示从 pos 位置开始,后面的字符删除完
    • 如果 pos + len >= _size,表示删除 [ pos, _size - 1 ] 区间的字符

    2.情况二:

    请添加图片描述

    string& erase(size_t pos = 0, size_t len = npos)
    {
        assert(pos >= 0 && pos < _size);
    
        // 1. 从pos位置开始,后面的字符删除完,这是一个O(1)的操作
        if (len == npos || pos + len >= _size)
        {
            _str[pos] = '\0';
            _size = pos;
        }
        // 2. 从pos位置开始,后面的字符删除一部分,这是一个O(n)的操作
        else
        {
            strcpy(_str + pos, _str + pos + len);
            _size -= len;
        }
    
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.10 string的字符串操作函数模拟实现

    1.find函数:从 pos 位置开始查找字符,若找到,返回该字符第一次出现的下标;若没找到,返回npos

    size_t find(char ch, size_t pos = 0) const
    {
        assert(pos >= 0 && pos < _size);
    
        for (size_t i = pos; i < _size; i++)
        {
            if (_str[i] == ch) return i;
        }
        return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.find函数:从 pos 位置开始查找子串,若找到,返回该字符第一次出现的下标;若没找到,返回npos

    size_t find(const char* str, size_t pos = 0) const
    {
        assert(pos >= 0 && pos < _size);
    
        // 在原串中去匹配子串str
        // 匹配成功,返回子串str首字符的地址
        // 匹配失败,返回空指针
        const char* p = strstr(_str + pos, str);
    
        if (p) return p - _str; // 通过子串str首字符的地址,计算出首字符的下标
        else return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.11 string的特殊函数模拟实现

    1.流插入<<运算符重载

    // 流插入运算符重载
    ostream& operator<<(ostream& out, const string& s)
    {
        // 遍历string类对象 s ,一个一个字符的插入
        for (size_t i = 0; i < s.size(); i++)
        {
            out << s[i];
        }
    
        return out;
    }
    
    void test6()
    {
        // 一般场景下,以下两种输出没有差别
        string s1("hello");
        cout << s1 << endl;         // 编译器当成自定义类型对象处理,匹配该类型重载的<<运算符
        cout << s1.c_str() << endl; // C_str()返回char*,编译器当成字符数组处理(内置类型)
    
        // 但是这种场景下有区别
        string s2("hello");
        s2.resize(10);
        s2[9] = 'x'; // 最后一个字符设为'x'
    
        // s2 = "hello\0\0\0\0x"
    
        cout << s2 << endl;         // 输出所有有效字符,"hello\0\0\0\0x"
        cout << s2.c_str() << endl; // 遇到'\0'终止,"hello"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.流提取>>运算符重载

    // 流提取运算符重载
    istream& operator>>(istream& in, string& s)
    {
        // 一个一个字符输入
        // 遇到空格或者换行符终止输入
        char ch;
        in >> ch; // 从流中获取一个字符
        while (ch != ' ' && ch != '\n')
        {
            s += ch;  // 把提取的字符追加到sting类对象 s 中去
            in >> ch; // 继续从流中获取下一个字符
        }
    
        return in;
    }
    
    void test7()
    {
        string s1;
        cin >> s1; // operator>>(cin, s1);
        cout << s1 << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    请添加图片描述

    流提取运算符重载函数中,改成用 get() 函数获取字符:但还是存在一个小问题

    // 流提取运算符重载
    istream& operator>>(istream& in, string& s)
    {
        // 一个一个字符输入
        // 遇到空格或者换行符终止输入
        char ch;
        ch = in.get(); // 从流中获取一个字符
        while (ch != ' ' && ch != '\n')
        {
            s += ch;       // 把提取的字符追加到sting类对象 s 中去
            ch = in.get(); // 继续从流中获取下一个字符
        }
    
        return in;
    }
    
    void test6()
    {
        string s1;
        cin >> s1; // operator>>(cin, s1);
        cout << s1 << endl;
    }
    void test7()
    {
        string s1("hello");
        cin >> s1; // operator>>(cin, s1);
        cout << s1 << endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    请添加图片描述

    正确写法:

    class string
    {
    private:
        // ...
    public:
        // ...
        void clear() // 清空所有有效字符
        {
            _str[0] = '\0';
            _size = 0;
        }
        // ...
    };
    
    // 流提取运算符重载
    istream& operator>>(istream& in, string& s)
    {
        // 先清空所有有效字符
        s.clear();
    
        // 一个一个字符输入
        // 遇到空格或者换行符终止输入
        char ch;
        ch = in.get(); // 从流中获取一个字符
        while (ch != ' ' && ch != '\n')
        {
            s += ch;       // 把提取的字符追加到sting类对象 s 中去
            ch = in.get(); // 继续从流中获取下一个字符
        }
    
        return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    3.getline函数:获取一行字符串,直到遇到换行符 ‘\n’

    istream& getline(istream& in, string& s)
    {
        // 先清空所有有效字符
        s.clear();
    
        // 一个一个字符的输入
        // 遇到换行符终止输入
        char ch;
        ch = in.get(); // 从流中获取一个字符
        while (ch != '\n')
        {
            s += ch;       // 把获取的字符追加到string类对象 s 中去
            ch = in.get(); // 继续从流中获取下一个字符
        }
        return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.关系运算符>重载:关系运算符,进行大小比较

    // 比较两个对象的大小(按字符ascii码比较)
    // s1 > s2
    bool operator>(const string& s1, const string& s2)
    {
        // 指针 i 和 j 分别指向两个字符串的第一个字符
        size_t i = 0, j = 0;
        for (; i < s1.size() && j < s2.size(); i++, j++) // 同时遍历两个字符串
        {
            if (s1[i] != s2[j]) return s1[i] > s2[j];
        }
    
        if (i == s1.size() && j == s2.size()) return false; // 同时被遍历完,说明 s1 = s2
        else if (i == s1.size()) return false; // s1先被遍历完,说明 s1 < s2
        else if (j == s2.size()) return true;  // s2先被遍历完,说明 s1 > s2
    }
    
    // s1 == s2
    bool operator==(const string& s1, const string& s2)
    {
        // 指针 i 和 j 分别指向两个字符串的第一个字符
        size_t i = 0, j = 0;
        for (; i < s1.size() && j < s2.size(); i++, j++) // 同时遍历两个字符串
        {
            if (s1[i] != s2[j]) return false;
        }
    
        if (i == s1.size() && j == s2.size()) return true; // 同时被遍历完,说明 s1 = s2
        else return false; // 有一个字符串没被遍历完
    }
    
    // 下面的关系运算符重载,全都可以复用上面的代码
    
    // s1 != s2
    bool operator!=(const string& s1, const string& s2)
    {
        return !(s1 == s2);
    }
    
    // s1 >= s2
    bool operator>=(const string& s1, const string& s2)
    {
        return s1 > s2 || s1 == s2;
    }
    
    // s1 < s2
    bool operator<(const string& s1, const string& s2)
    {
        return !(s1 >= s2);
    }
    
    // s1 <= s2
    bool operator<=(const string& s1, const string& s2)
    {
        return !(s1 > s2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    4.string类对象的大小计算

    举例:

    int main()
    {
    	string s1("xxx");              // 3个有效字符
    	string s2("xxxxxxxxxxxxxxxx"); // 16个有效字符
    
        //我的VS2019得出都是28
    	cout << sizeof(s1) << endl; // 输出:28
    	cout << sizeof(s2) << endl; // 输出:28
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    思考:VS2019 下测试,输出28,按照我们上面模拟实现的版本应该是12,为什么会是28呢?

    这其实和 VS 下 PJ 版本的 STL string 的实现有关:

    1. 如果有效字符个数 < 16,不会去堆上开空间,而是存到一个名叫 _buf 的数组空间上,即存到对象中
    2. 如果有效字符个数 >= 16,则会存到 _str 指向的堆空间上
    3. 这样做可以减少内存碎片,提高效率
    // VS下string的实现,大概是这样样子
    class string
    {
    private:
        char _buf[16];
        char* _str;
        size_t _size;
        size_t _capacity;
    // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    请添加图片描述

    补充:Linux下输出8,所以当我们计算 string 类对象大小时,不要总不同而觉得奇怪,因为各家实现的版本可能会有所差异


    5.写时拷贝的了解

    首先回顾一下浅拷贝引发的问题:

    • 同一块空间会被析构(释放)多次
    • 一个对象修改会影响另外一个对象

    为了解决这两个问题:

    1. 为了应对同一块空间会被析构多次这个问题,提出了引用计数
      • 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源,如果计数为 1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源
    2. 为了应对一个对象修改会影响另外一个对象这个问题,提出了写时拷贝计数
      • 写时拷贝就是一种拖延症,是在「浅拷贝」的基础之上增加了引用计数的方式来实现的
      • 多个对象共用同一块内存空间,哪个对象去写数据,哪个对象就再进行深拷贝,本质是一种延迟深拷贝。当然,如果不进行写数据,那就不用进行深拷贝,提高了效率
      • 但这种方案也是有副作用的,现在基本上也被放弃了

    写时拷贝的验证:

    #include
    #include
    int main()
    {
    	std::string s1("hello world");
    	std::string s2(s1);              // 拷贝构造
    	printf("%p\n", s1.c_str()); // c_str()函数返回其指向字符数组的地址
    	printf("%p\n", s2.c_str());
    	// 修改
        s2[0] = 'x';
        printf("%p\n", s1.c_str());
    	printf("%p\n", s2.c_str());
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行结果(VS2019 下 PJ 版本的 STL):没有用写时拷贝技术,直接深拷贝

    请添加图片描述

    运行结果(Linux 下 SGI 版本的 STL):这个早期15年的版本用了写时拷贝技术,加上了引用计数
    请添加图片描述

    
    
    
    
    • 1
    • 2
    • 3
  • 相关阅读:
    多维时序 | MATLAB实现TCN-selfAttention自注意力机制结合时间卷积神经网络多变量时间序列预测
    jvm调优-内存泄漏导致cpu飙升
    Vue3组件通信全解析:利用props、emit、provide/inject跨层级传递数据,expose与ref实现父子组件方法调用
    openstack wallaby 对应 需要的python版本 3.8.18
    Java面试之SpringBoot篇
    Shellcode——绕过31
    【无标题】
    宝宝为什么会出现乳糖不耐受?
    2.6 动态规划—lc炒股系列
    80C51单片机指令寻址方式
  • 原文地址:https://blog.csdn.net/qq_29678157/article/details/127842904