C++构造和析构
构造函数
- 名字和类名相同
- 没有返回值
- 构造函数是用来构造对象,构造对象时候必定调用构造函数
- 不写构造函数,存在一个默认的构造函数,默认的构造函数是无参,所以可以构造无参对象
- 默认的构造函数可以删掉,通过delete删除默认的构造函数
- 显示使用默认的构造函数, 通过default做显示调用
- 通常情况构造函数是public属性
- 自己写了构造函数,默认的构造函数就不存在了
- 构造函数决定对象的长相(构造函数无参,对象无参,构造有一个,对象必须也要一个参数)
- 构造函数通常做的事情,就是给数据成员初始化
- 构造函数也是函数,所以也可以重载,也可以缺省
- 通过重载和缺省,实现构造不同长相对象
#include <iostream> #include <string> using namespace std; class MM { public: //MM() = default; //显式使用默认的构造函数 //MM() = delete; //删掉默认的构造函数 MM(string name, int age) { m_name = name; m_age = age; cout << "两个参数的构造函数" << endl; } MM() = default; //默认无参构造函数,据说速度更快 void printMM() { cout << m_name << "\t" << m_age << endl; } protected: string m_name; int m_age; }; //MM::MM() //{ // cout << "调用无参构造函数" << endl; //} struct Boy { string name; int age; int num; //一旦C++结构体中写了构造函数,必须当做类去操作,不能用C语言的那种方式使用 Boy() {} Boy(string bname, int bage) { name = bname; age = bage; } }; void testStruct() { //Boy boy = { "string",18,1001}; //错误 //这个地方也是创建对象过程,所以数据也需要和构造函数 Boy boy = { "string",18}; //这里数据必须和构造函数的一致 Boy boy2; Boy array[3]; } int main() { //MM mm; //因为构造函数有两个参数,对象也必须带有两参数 MM mm("对象", 18); //这步创建对象的过程就是调用构造函数构造函数的过程 mm.printMM(); MM empty; //调用无参构造函数 //new一个对象 MM* p = new MM; //调用无参的构造函数 MM* p2 = new MM("对象", 29); //调用有参的构造函数 return 0; }
析构函数
- ~类名 当做析构函数名字
- 没有参数
- 释放数据成员new的内存
- 在对象死亡前自动调用
- 通常如果数据成员没有做new操作,就可以不写析构函数
- 不写析构函数,存在一个默认的析构函数
#include <iostream> #include <cstring> using namespace std; class MM { public: MM(const char* str="ILoveyou") //缺省 相当于存在两个构造函数,一个是无参的,一个是有参 { int length = strlen(str) + 1; name = new char[length]; strcpy_s(name, length, str); } ~MM(); protected: char* name; }; MM::~MM() { if (name != nullptr) { delete[] name; name = nullptr; } cout << "析构函数....\n" << endl; } int main() { { MM mm; MM* p = new MM; cout << "1......" << endl; delete p; //立刻调用析构函数 p = nullptr; cout << "2......." << endl; } { MM beauty("Continue"); } cout << "对象死亡" << endl; return 0; }
拷贝构造函数
- 拷贝构造函数也是构造函数
- 拷贝构造函数参数是固定的:对对象的引用
- 拷贝构造函数不写会存在一个默认的拷贝构造函数
- 拷贝构造函数作用: 通过一个对象产生另一个对象
- 关键点:一定是有一个新的对象产生
#include <iostream> #include <string> using namespace std; class MM { public: MM() {} MM(string name, int age) { m_name = name; m_age = age; } MM(MM& object); void printMM() { cout << m_name << "\t" << m_age << endl; } protected: string m_name; int m_age; }; //拷贝构造函数 //通过传入对象属性确定创建对象的属性 MM::MM(MM& object) { m_name = object.m_name; m_age = object.m_age; cout << "调用自己写的拷贝构造函数" << endl; } //函数传参也可以隐式调用 void print(MM object) //MM object=girl { object.printMM(); } //C++中传参能用引用就用,效率搞 void printData(MM& object) //引用就是别名,没有产生新的对象 { object.printMM(); } int main() { MM mm("C神", 18); //产生一个对象 MM beauty = mm; //隐式调用拷贝构造函数 MM girl(beauty); //显式调用拷贝构造函数 print(girl); //调用拷贝构造函数 printData(girl); MM boy; //对象先有了,才赋值,不调用拷贝 boy = girl; //不调用拷贝构造函数-->默认重载=运算符(后续会讲) return 0; }
深浅拷贝问题
浅拷贝
- 没有在拷贝构造函数中给数据成员做new操作
- 默认拷贝构造函数都是浅拷贝
深拷贝
- 在拷贝构造函数中做了new操作
浅拷贝导致内存释放问题
浅拷贝会导致同一段内存重复释放问题
#include <iostream> #include <cstring> using namespace std; class MM { public: MM() {} MM(const char* str) { int length = strlen(str) + 1; name = new char[length]; strcpy_s(name, length, str); } ~MM() { if (name != nullptr) { delete[] name; name = nullptr; } } private: char* name; }; int main() { { MM girl("girl"); MM mm = girl; //调用拷贝构造函数 } return 0; }
深拷贝解决方案
#include <iostream> #include <cstring> using namespace std; class MM { public: MM() {} MM(const char* str) { int length = strlen(str) + 1; name = new char[length]; strcpy_s(name, length, str); } MM(MM& object) { //name = object.name; int length = strlen(object.name) + 1; name = new char[length]; strcpy_s(name, length, object.name); } ~MM() { if (name != nullptr) { delete[] name; name = nullptr; } } private: char* name; }; int main() { { MM girl("girl"); MM mm = girl; //调用拷贝构造函数 } return 0; }
综上: 一旦类中有指针,做了内存申请,并且要对对象做拷贝操作,就必须使用深拷贝
匿名对象
匿名对象就是无名对象,匿名对象只能充当右值,所以匿名对象拷贝必须存在移动拷贝,或者准备一个ecosnt限定普通拷贝构造函数。
#include <iostream> #include <string> using namespace std; class MM { public: MM() {}; MM(string name, int age) { m_name = name; m_age = age; } void printMM() { cout << m_name << "\t" << m_age << endl; } MM(MM& object) { m_name = object.m_name; m_age = object.m_age; } //移动构造--->新标准的 MM(MM&& object) { m_name = object.m_name; m_age = object.m_age; cout << "调用移动构造" << endl; } ~MM() { cout << "析构打印name:" << m_name << endl; } private: string m_name; int m_age; }; MM returnMM(string name, int age) { return MM(name, age); //构造一个匿名对象当做函数返回值 } int main() { { MM girl("girl", 19); //匿名对象是一个右值 MM mm=MM("mm", 29); //匿名对象 --->匿名对象转正 ,mm接管所有权 cout << "匿名对象已死亡" << endl; } MM beauty = returnMM("返回值", 18); beauty.printMM(); return 0; }
构造和析构顺序问题
- 一般情况构造顺序和析构是相反的
- 静态的和全局的是最后释放的
- delete 立刻调用析构函数
#include <iostream> #include <string> using namespace std; class Test { public: Test(string data="A") { m_data = data; cout << m_data; } ~Test() { cout << m_data; } protected: string m_data; }; int main() { { Test t1; //A static Test t2("B"); //B Test array[3]; //AAA //数组就是多个无参 Test* p = new Test("C"); //C delete p; //C p = nullptr; } //ABAAACCAAAAB return 0; }
小试牛刀
//用构造函数的方式实现单链表 //构造函数创建节点 //构造函数做表头法插入 //加个遍历链表的操作即可 struct Node { int data; struct Node* next; Node(int data); Node(int data,Node* next); }; class List { public: List(); void insertData(int data); void printList(); protected: Node* headNode; int curSize; };