C++(在Mystring类中碰到的构造函数和析构函数以及深拷贝和浅拷贝的问题)

张开发
2026/6/7 14:08:39 15 分钟阅读
C++(在Mystring类中碰到的构造函数和析构函数以及深拷贝和浅拷贝的问题)
以实例开始从我的视角看我对这个代码中涉及知识点的疑惑吧首先上代码是不是觉得很长没关系慢慢看会有收获的想知道是不是比我聪明就看看你问的问题是不是比我少吧class Mystring { private: char* str; public: //构造函数 Mystring(const char* ps nullptr):str(nullptr) { if (nullptr ! ps *ps ! \0) { int len strlen(ps); str (char*)calloc(len1, sizeof(char)); assert(str ! NULL); strcpy(str, ps); } } //析构函数 ~Mystring(){ free(str); str NULL; } //深拷贝 Mystring(const Mystring it):str (nullptr) { if (it.str ! nullptr *(it.str) ! \0) { str(char*)calloc(strlen(it.str)1,sizeof(char)); strcpy(str, it.str); } } //运算符重载 Mystring operator(const Mystring it) { if (this ! it) { free(str); str nullptr; if (it.str ! nullptr *it.str ! \0) { str (char*)calloc(strlen(it.str) 1, sizeof(char)); assert(str ! nullptr); strcpy(str, it.str); } } return *this; } //打印输出 void PrintInfo() const{ if (nullptr ! str) { cout str:str endl; } else { cout Empty endl; } return; } }; struct Node { int value; }; int main() { int* p nullptr; int Node::* s nullptr; s Node::value; Mystring s1; Mystring s4; Mystring s2(Happy); s1.PrintInfo(); s2.PrintInfo(); Mystring s3(s2); s3.PrintInfo(); s4 s2; s4.PrintInfo(); return 0; }1Mystring类和其他类有什么区别吗普通类成员变量是值类型比如存Int类型的类数据直接存在自己的对象里Mystring类成员变量是指针类型比如存char*类型的类真实字符串存在堆内存里对象只存地址。2为什么要区分 Private 和 Publicclass 类的默认权限是 Private struct 默认权限是 PublicPrivate是封装的核心目的是为保证数据内容的合法性不让外部乱改类内部的重要数据通过 public 函数间接修改 private 变量就可以做校验。private把变量锁起来不让外面乱碰public 函数给你一个合法的入口在函数里加判断保证数据永远是合法的这就是封装的意义保护数据防止被乱改让类更安全、更健壮。3构造函数构造函数是做什么的对象一创建他就自动跑用来初始化对象构造函数的特点是什么函数名和类名一模一样没有返回值连 void 都不用写创建对象时构造函数自动调用可以有参数重载结合本代码例子讲一讲构造函数//构造函数 Mystring(const char* ps nullptr):str(nullptr) { if (nullptr ! ps *ps ! \0) {//指针不为空和不为空串 int len strlen(ps); str (char*)calloc(len1, sizeof(char));//calloc(个数类型字节) //char* str (char*)calloc(len1, sizeof(char));//错误的 assert(str ! NULL); strcpy(str, ps); } }作用创建对象时根据传入的字符串在堆上开辟空间并拷贝内容为什么函数的参数是const char* ps nullptr1为什么是 const char* 类型——传入的是字符串常量加const是为了防止在构造函数里不小心修改字符串内容能接收const和非const的字符串符合 C 常量正确性规范不写const有些编译器会报警 / 报错2为什么要有 nullptr——默认参数调用这个构造函数时可以不传参总之定义了一个构造函数可以接收一个字符串指针有参数传入参数没有参数默认nullptr 作为参数为什么后面要写str(nullptr)把成员变量 str 初始化为空指针 nullptr“:”后面这一堆是在构造函数执行前给成员变量赋初值的——成员初始化列表为什么必须显示初始化——指针如果不初始化里面是垃圾随机值为什么char* str (char*)calloc(len1, sizeof(char));是错误的char* str 定义了一个局部变量和类成员 str 重名在{}里是str是局部变量不是类成员给char* str扩容是给局部变量扩容局部变量出了 {} 就销毁了类成员str还是nullptr正确写法str (char*)calloc(len1, sizeof(char));这样才是给类成员扩容更加标准的写法this-str (char*)calloc(len1, sizeof(char));更加明确是给此类成员扩容几种扩容函数的格式malloc类型* 指针类型*malloc(元素个数*sizeof(元素类型));eg.char* p(char*)malloc(10*sizeof(char));calloc类型* 指针类型*calloc(元素个数,sizeof(元素类型));eg.char* p(char*)calloc(10,sizeof(char));realloc类型* 指针类型*realloc(旧指针元素个数*sizeof(元素类型));eg.char* p(char*)realloc(s,10*sizeof(char));new指针new 类型 [元素个数*sizeof(元素类型)];eg.pnew(char)[10*sizeof(char)];4析构函数析构函数是做什么的对象要死的时候离开作用域、程序结束自动跑用来清理内存。析构函数的特点是什么析构函数与类名相同但在前面加上字符‘~’析构函数无函数返回类型不带有参数一个类只有一个析构函数为什么要析构对象销毁时释放堆上的字符串内存避免内存泄漏如果不释放程序一直占着内存不用5为什么在Mystring类中区分深拷贝和浅拷贝浅拷贝浅拷贝做了什么C默认生成的拷贝构造函数和运算符重载函数只会做一件事情逐字节拷贝成员变量——就是把指针地址复制过去不会复制堆里字符串的内容最后浅拷贝的后果是什么Mystring s2(Happy); Mystring s3(s2);s2.str 和 s3.str 指向同一块堆内存一个改全改一个析构释放空间另一个变成野指针最后两个对象析构同一个内存释放两次程序崩溃深拷贝深拷贝做了什么自己写拷贝构造函数和赋值重载函数开辟新的内存空间、将堆空间中的内容复制过去、让新对象拥有独立的内存、两个对象互不干扰深拷贝的效果s3 和 s2 有各自的的堆空间给 s3.str 新开辟一块内存把 s2 的字符串内容复制过去两个指针指向不同内存完全独立析构时各自释放自己的内存不会造成内存崩溃结合本代码例子讲一讲深拷贝Mystring(const Mystring it):str(nullptr) { if (it.str ! nullptr *(it.str) ! \0) { // 1. 开新空间 str (char*)calloc(strlen(it.str)1, sizeof(char)); // 2. 把内容复制过去 strcpy(str, it.str); } }为什么是 Mystring因为这是Mystring 类的拷贝构造函数函数名必须和类名一样。为什么参数 const Mystring it 要加引用 引用不能用值传递Mystring it因为值传递会触发拷贝构造那就会无限递归 → 直接崩溃所以必须用引用 it是被拷贝的对象这句it.str ! nullptr *(it.str) ! \0的作用是什么用来判断被拷贝的对象 it也就是 s2有有效字符串吗6运算符重载函数运算符重载函数是做什么的就是给、、、[]、这些符号重新定义它在你的类里怎么干活在Mystring中其他几种常用的几种运算符重载格式加号 字符串拼接//字符串拼接 Mystring operator(const Mystring it) const { Mystring temp; int len1 (str ! nullptr) ? strlen(str) : 0; int len2 (it.str ! nullptr) ? strlen(it.str) : 0; temp.str (char*)calloc(len1 len2 1, sizeof(char)); if (str ! nullptr) strcat(temp.str, str); if (it.str ! nullptr) strcat(temp.str, it.str); return temp; }字符串比较// 字符串比较 bool operator(const Mystring it) const { if (str nullptr it.str nullptr) return true; if (str nullptr || it.str nullptr) return false; return strcmp(str, it.str) 0; }下标返回// 下标访问 [] char operator[](int index) { assert(str ! nullptr); assert(index 0 index (int)strlen(str)); return str[index]; }结合本代码的例子讲讲运算符重载函数Mystring operator(const Mystring it) { //1.检查是否是自己给自己赋值 if (this ! it) { //2.将当前空间free掉 /*delete[]str;*/ free(this-str); //3.str置空 this-str nullptr; //4.it.str分配空间 if (it.str ! nullptr *it.str ! \0) { /*char* str new(it.str)char[sizeof(strlen(it.str)1)];*/ this-str (char*)calloc(strlen(it.str) 1, sizeof(char)); //assert(str ! nullptr); //5.拷贝 strcpy(this-str, it.str); } } return *this; }operator的作用赋值运算符重载在主函数中可以写s3s2const Mystring it的作用作用让 it 等于 右边的对象 用引用避免拷贝浪费const 只读取不修改原变量Mystring 返回值的作用作用返回当前对象的引用 *this目的支持链式赋值 eg.s3s2s1先释放旧内存那里面的东西放在哪里(以s3s2为例子)首先free(this-str);释放的是指针指向的旧字符串内存不是释放指针本身指针还在只是不指向变成了野指针然后this-strnullptr;将指针指向空安全其次this-str (char*)calloc(strlen(it.str) 1, sizeof(char));申请新空间最后strcpy(this-str, it.str);改变指针指向让指针指向新空间你真棒看到这里啦给自己送个花花吧

更多文章