目录

一、经典的string类问题

1.出现的问题

2.浅拷贝

3.深拷贝

二、string类的模拟实现

1.传统版的string类

2.现代版的string类(采用移动语义)

3.相关习题*

习题一

习题二

4.写时拷贝

5.完整版string类的模拟实现[注意重定义]

MyString.h

MyString.c

test.c


一、经典的string类问题

1.出现的问题

模拟实现string容器的 构造、拷贝构造、赋值运算符重载以及析构函数

出错代码如下:

// 类的定义namespace imitate{class string{public: //string()//无参构造函数//:_str(nullptr)//{}//string(char* str)//有参构造函数//:_str(str)//{}string():_str(new char[1]){_str[0] = '\0';}string(char* str) //构造函数在堆上开辟一段strlen+1的空间+1是c_str:_str(new char[strlen(str)+1]){strcpy(_str, str); //strcpy会拷贝\0过去}//string(char* str="") //构造函数在堆上开辟一段strlen+1的空间+1是c_str//:_str(new char[strlen(str) + 1])//{//strcpy(_str, str); //strcpy会拷贝\0过去//}size_t size() const{return strlen(_str);}bool empty(){return _str == nullptr;}char& operator[](size_t i)//用引用返回不仅可以读字符,还可以修改字符{return _str[i];}~string()//析构函数{if (_str){delete[] _str;_str = nullptr;}}const char* c_str() //返回C的格式字符串{return _str;}private:char* _str;};}
// 测试using namespace imitate;void TestString1(){string s1("hello");string s2;for (size_t i = 0; i < s1.size(); i++){s1[i] += 1;std::cout << s1[i] << " ";}std::cout << std::endl;for (size_t i = 0; i < s2.size(); i++){s2[i] += 1;std::cout << s2[i] << " ";}std::cout << std::endl;}void TestString2(){string s1("hello");//采用默认的构造函数string s2(s1); // 出错点 1std::cout << s1.c_str() << std::endl;std::cout << s2.c_str() << std::endl;string s3("world");//采用s1 = s3; // 出错点 2std::cout << s1.c_str() << std::endl;std::cout << s3.c_str() << std::endl;}int main(){TestString1();TestString2();return 0;}

出错点1:

string s2(s1); // 出错点 1

采用默认的拷贝构造函数,导致s2._str拷贝了s1._str所指向的地址,导致s1,s2的_str都指向同一块空间。测试函数2结束后,s2、s1指向相同的地址会被释放两次,会导致程序崩溃。

出错点2:

s1 = s3; // 出错点 2

采用默认赋值运算符重载,s3._str被赋予了s1._str所指向的地址,导致s1,s2的_str都指向同一块空间。测试函数2结束后,s3、s1指向相同的地址会被释放两次,会导致程序崩溃。

总结:采用 默认拷贝构造函数默认赋值运算符重载 会导致一块空间会被多个类对象指向,导致多次释放同一空间,致使程序崩溃。这种拷贝方式,称为浅拷贝。

2.浅拷贝

浅拷贝指创建了一个新的对象,其中包含了原始对象的所有属性和值,但是对于原始对象中引用的其他对象,浅拷贝只是复制了其引用,而不是复制其实际值

在浅拷贝中,新对象和原始对象之间共享同一个内存地址,所以如果修改其中一个对象的属性,则另一个对象的属性也会随之改变,因为它们引用同一个对象。浅拷贝通常比深拷贝更快,因为它只需要复制对象的引用,而不需要递归地复制对象的所有子对象。

3.深拷贝

深拷贝指创建一个新对象,该对象是原始对象的完全副本,包括原始对象的所有属性和嵌套对象的属性深拷贝会递归地复制所有嵌套的对象,而不仅仅是复制其引用。因此,原始对象和副本对象之间没有共享任何内存地址。

深拷贝可以防止副本对象的修改影响原始对象,因为它们是完全独立的。但是,深拷贝可能比浅拷贝更耗时,因为它需要递归地复制所有嵌套的对象。

如果类中有指向堆内存的指针或者使用了动态内存分配函数(如new、malloc等),则需要手动编写拷贝构造函数或赋值操作符来实现深拷贝。

二、string类的模拟实现

1.传统版的string类

C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。
具体来说:能像 int 类型那样定义变量,并且支持赋值、复制。能用作函数的参数类型及返回类型。能用作标准库容器的元素类型,即 vector / list / deque 的 value_type。(用作 std::map 的 key_type 是更进一步的要求,本文从略)。换言之,你的 String 能让以下代码编译运行通过,并且没有内存方面的错误。

添加了自定义的拷贝的构造函数赋值运算符重载

代码如下:

// 传统版的string类的模拟————类的定义// ...namespace imitate{class string{public:string():_str(new char[1]){_str[0] = '\0';}string(const char* str):_str(new char[strlen(str) + 1]){strcpy(_str, str);}string(const string& s):_str(new char[s.size() + 1]){strcpy(_str, s._str);}size_t size() const{return strlen(_str);}char& operator[](size_t i){return _str[i];}~string(){if (_str){delete[] _str;_str = nullptr;}}const char* c_str(){return _str;}// const 是因为对 str 不需要修改,安全性更高// 参数 & 是因为不需要传值拷贝、效率高// 返回值 & 是为了连续赋值(效率高)string& operator=(const string& str){// 检查是否自己给自己赋值if (this != &str){// 因为str为const修饰,所以要调用size()函数的话应给函数加上const修饰char* tmp = new char[str.size() + 1];strcpy(tmp, str._str);delete[] _str;_str = tmp;}return *this;}private:char* _str;};}
// 测试代码// ...using namespace imitate;void TestString(){string s1;string s2("hello");for (size_t i = 0; i < s2.size(); i++){std::cout << s2[i] << " ";}std::cout << std::endl;s1 = s2;for (size_t i = 0; i < s1.size(); i++){std::cout << s1[i] << " ";}}int main(){TestString();return 0;}

2.现代版的string类(采用移动语义)

现代版的string类采用swap函数的原因:实现C++11中的移动语义

C++的移动语义

可以在不进行任何拷贝操作的情况下,将一个对象的所有权从一个对象转移到另一个对象。这个过程使用右值引用(rvalue references)实现。

移动语义的主要目的是优化C++代码的性能,减少不必要的内存分配拷贝操作,提高程序的效率。当一个对象被移动而不是拷贝时,可以避免拷贝构造函数和析构函数的调用,从而提高程序的性能。

代码如下:

// 现代版的string类模拟————类的定义// ...namespace imitate{class string{public:string():_str(new char[1]){_str[0] = '\0';}string(const char* str):_str(new char[strlen(str) + 1]){strcpy(_str, str);}string(const string& s) // 使用swap函数,进行直接把所有权交予另一个对象:_str(nullptr){string tmp(s._str);std::swap(_str, tmp._str);}size_t size() const{return strlen(_str);}char& operator[](size_t i){return _str[i];}~string(){if (_str){delete[] _str;_str = nullptr;}}const char* c_str(){return _str;}string& operator=(const string& str){if (this != &str){//tmp 会在函数结束时自动调用其析构函数进行资源的释放string tmp(str._str);std::swap(_str, tmp._str);}return *this;}private:char* _str;};}
// 测试代码// ...void TestString1(){string s1;string s2("hello");string s3(s2);s1 = s3;}int main(){TestString1();return 0;}

3.相关习题*

习题一

习题二

注意:用一个对象初始化另一个对象时,例如 MyClass obj1; MyClass obj2 = obj1;

采用的是 拷贝构造函数 而非使用 重载运算符

4.写时拷贝

写时拷贝(Copy-On-Write,简称COW)是一种优化技术,它可以避免在数据复制时不必要的内存开销。写时拷贝的实现方式是,在数据需要被修改时,才会复制数据,而在数据未被修改时,共享同一份数据。

写时拷贝通常是通过使用智能指针实现的。智能指针可以跟踪指向的对象的引用计数,并在需要时进行复制。具体来说,当一个智能指针被拷贝时,它的引用计数会增加,而指向的对象不会被复制。当一个指向对象的智能指针需要修改对象时,它会先检查引用计数是否为1,如果是,说明这个对象没有被其他智能指针共享,可以直接修改,否则,它会先复制一份对象,并把引用计数减1,然后对复制后的对象进行修改。

5.完整版string类的模拟实现[注意重定义]

注意:

1.size_t imitate::string::npos = -1;不能放到头文件中,避免重定义问题。

2.将函数标记为”inline”可以消除重定义错误。

3.在头文件中定义了这些函数需要将它们标记为 inline,否则每个包含该头文件的源文件都会生成该函数的定义,从而导致链接错误。

MyString.h

#pragma once#define _CRT_SECURE_NO_WARNINGS#include#includenamespace imitate{class string{public:typedef char* iterator;static size_t npos; //insert用的位置public:string(const char* str = " "){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s): _size(0), _capacity(0), _str(nullptr){string tmp(s._str);this->swap(tmp);}~string(){if (_str){delete[] _str;_size = 0;_capacity = 0;_str = nullptr;}}string& operator=(string& s);void swap(string& s);void push_back(char c);void append(const char* str);void append(const string& s);string& operator+=(char c);string& operator+=(const char* str);string& operator+=(const string& s);string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);string& insert(size_t pos, const string& s);string& erase(size_t pos, size_t len = npos);size_t find(char c, size_t pos = 0);size_t find(const char* str, size_t pos = 0);size_t find(const string& s, size_t pos = 0);void resize(size_t newsize, char c = '\0');iterator begin()//iterator迭代器的原理{return _str;}iterator end(){return (_str + _size);}const char* c_str(){return _str;}char& operator[](size_t i) const{return _str[i];}bool operator<(const string& s){return strcmp(_str, s._str);}bool operator(const string& s){return strcmp(_str, s._str);}bool operator>=(const string& s){return (strcmp(_str, s._str) == 1) || (strcmp(_str, s._str) == 0);}bool operator=(const string& s){return strcmp(_str, s._str) == 0;}bool operator!=(const string& s){return strcmp(_str, s._str);}bool empty(){return _size == 0;}size_t size() const{return _size;}size_t capacity(){return _capacity;}private:char* _str;size_t _size; //已经有多少个有效字符个数size_t _capacity; //能存多少个有效字符个数 \0不是有效字符,\0是标识结束的字符void CheckFull(){if (_size == _capacity){size_t newcapacity = _capacity == 0 " />>(std::istream& _in, string& s){while (1){char c;c = _in.get();if (c == ' ' || c == '\n'){break;}else{s += c;}}return _in;}}

MyString.c

#include"MyString.h"size_t imitate::string::npos = -1;imitate::string& imitate::string::operator=(string& s){string tmp(s);string::swap(tmp);return *this;}void imitate::string::swap(string& s){std::swap(_size,s._size);std::swap(_capacity ,s._capacity);std::swap(_str, s._str);}void imitate::string::push_back(char c){this->CheckFull();_str[_size] = c;_size++;_str[_size] = '\0';}void imitate::string::append(const char* str){size_t len = strlen(str);if ((_size + len) > _capacity){size_t newcapacity = _size + len;char* tmp = new char[newcapacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}strcpy(_str + _size, str);_size += len;}void imitate::string::append(const string& s){if ((_size + s._size) > _capacity){size_t newcapacity = _size + s._size;char* tmp = new char[newcapacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}strcpy(_str + _size, s._str);_size += s._size;}imitate::string& imitate::string::operator+=(char c){this->push_back(c);return *this;}imitate::string& imitate::string::operator+=(const char* str){this->append(str);return *this;}imitate::string& imitate::string::operator+=(const string& s){this->append(s);return *this;}imitate::string& imitate::string::insert(size_t pos, char c){assert(pos CheckFull();size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;return *this;}imitate::string& imitate::string::insert(size_t pos, const char* str){assert(pos  _capacity){size_t newcapacity = _size + len;char* tmp = new char[newcapacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}size_t end = _size;while (end >= pos){_str[end + len] = _str[end];end--;}for (size_t i = 0; i < len; ++i, ++pos){_str[pos] = str[i];}_size += len;return *this;}imitate::string& imitate::string::insert(size_t pos, const string& s){assert(pos  _capacity){size_t newcapacity = _size + len;char* tmp = new char[newcapacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}size_t end = _size;while (end >= pos){_str[end + len] = _str[end];end--;}for (size_t i = 0; i < len; ++i, ++pos){_str[pos] = s._str[i];}_size += len;return *this;}imitate::string& imitate::string::erase(size_t pos, size_t len){assert(pos = _size-pos){_str[pos] = '\0';_size = pos;}else{for (size_t i = pos + len; i <= _size; ++i){_str[i - len] = _str[i];}_size = _size - len;}return *this;}size_t imitate::string::find(char c, size_t pos){iterator it = begin();while (it != end()){if (*it == c){return it - begin();}it++;}return npos;}size_t imitate::string::find(const char* str, size_t pos){char* p = strstr(_str + pos, str);if (!p){return npos;}return p - _str;}size_t imitate::string::find(const string& s, size_t pos){char* p = strstr(_str + pos, s._str);if (!p){return npos;}return p - _str;}void imitate::string::resize(size_t newsize, char c){if (_size  _capacity){size_t newcapacity = newsize;char* tmp = new char[newcapacity + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}for (size_t i = _size; i < newsize; ++i){_str[i] = c;}_size = newsize;_str[_size] = '\0';}}

test.c

#include"MyString.h"using namespace imitate;void test1(){string s1;string s2("Hello");string s3(s2);string s4(" World!");s2.push_back('!');s2.append(" World!");s3.append(s4);s4 += s2;s2 += "Hi! Hi! Hi!";}void test2(){string s2("Hello");string s3(s2);string s4(" World!");s2.insert(2, s4);}void test3(){string s1("Hello");string s2(" World!");s1.append(s2);s1.find('a', 2);s1.find("World!", 7);s1.find(s2);std::cin >> s1;std::cout << s1;}int main(){test3();std::cout << string::npos;return 0;}