侯捷c++面向对象 笔记(二)

Class with pointer members

拷贝构造与拷贝赋值

不写的话编译器会自己生成一份
对于包含带指针的成员的类,如果不重写这两个函数,那么去拷贝一个对象时,对于这个对象中的某个带指针的成员,则会完全拷贝原指针,新的对象和老的对象中的这一个指针指向同一个老对象中的成员的地址,这并不是真正的拷贝

深拷贝和浅拷贝
浅拷贝:指针拷贝
深拷贝:内容拷贝

big three,三个特殊函数:拷贝构造函数、拷贝赋值函数、析构函数

当写包含带指针的成员的类时,一定要写这三个函数

Class with pointer members 必须有拷贝构造和拷贝赋值

拷贝构造函数

拷贝构造的过程,是直接开辟新的内存空间,将新的内容填进去
对象a中有成员s1,在对象a的内存区域存放的是指向s1的指针,并不包括s1的内存区域
同理对象b也存了指向s2的指针
这时候如果没有拷贝构造函数,在做b=a的赋值时,会出现浅拷贝,寄指针拷贝,s1的指针被赋值给了s2的指针,所以他们同时指向s1
这是后s2的内存区域就发生了泄露

拷贝赋值函数

区别于拷贝构造,拷贝赋值的过程,是先将原有的内容清除,再将新的内容复制进来,多了一个清除的过程
注意,拷贝赋值,一定要检测是否是自我赋值

1
2
3
4
5
6
7
8
inline String &String::operator=(const String &str) {//这里注意 隐藏入参this 指向调用者 因为是成员函数,指向调用的对象  
if (this == &str)//检测是否是自我赋值,没有这一步,下面第1步被删了之后,第二步就没法取值了
return *this;
delete[] m_data;//1
m_data = new char [strlen(str.m_data) + 1];//2
strcpy(m_data, str.m_data);//3
return *this;
}

几种对象的生命周期

stack obj 栈对象,作用域结束后被自动释放
static local obj 静态对象,作用域结束后仍存在,程序结束后调用析构函数释放
global obj 任何大括号之外,全局作用域,程序结束后调用析构函数释放
heap obj 堆对象 注意new之后要delete

new:先分配内存,再调用构造函数

编译器转化后,new的过程:

  1. operator new,内部调用malloc,分配内存
  2. 转型,将第一步创建的void指针转为正确的类型
  3. 调用构造函数,指针指向开辟的空间

delete:先调用析构函数,再释放内存

编译器转化后,delete的过程:

  1. 调用析构函数
  2. 释放内存,内部调用free

动态分配所得的内存块(VC下)

内存对齐 来了!!!!,方便回收,不满足16倍数的会补齐
红色:上下cookie,记录整块内存的大小,回收的时候用,cookie十六进制最后一位0/1代表操作系统给出去(1)或收回(0)
绿色:复数数据实际占用的
灰色:调试模式下占用
深绿色:内存对齐补上去的

动态分配所得的数组(VC下)

有一个点,new一个数组时,对应的构造函数使用 new[],在析构时必须对应使用 delete[],不然会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
inline String::String(const char* cstr) {
if (cstr) {
m_data = new char[strlen(cstr)+1]; //new char[]
strcpy(m_data, cstr);
} else {
m_data = new char[1];
*m_data = '\0';
}
}

inline String::~String() {
delete[] m_data; //delete[]
}

注意,当分配内存给数组时,图中橙色调试区和灰色数据区之间这里多了4bit的空间,记录数组的大小

如上图,因为记录了数组大小3,会有三个对象,则delete会调用三次对象的析构函数,以完成释放
如果没写中括号,仅调用一次析构函数,则数组中剩余的两个对象的指针指向的内存区域就得不到释放了
注意,发生内存泄漏的位置不是array本身,而是array中存放的没有被释放的对象的内存区域

文章作者: ゴウサク
文章链接: http://dapaner.top/2022/04/10/侯捷c-面向对象-笔记(二)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Corner&Coder
微信赞赏码
支付宝赞赏码