什么是构造函数
构造函数就是一个特殊的类成员函数,函数名和类名相同,它在创建类类型对象时会由编译器自动调用
函数名()
{
}
函数名(参数1,参数2)
{
}
构造函数有什么作用
构造函数主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 (即构造函数的重载)。
构造函数的特点
① 函数名和类名相同
② 没有返回值
③ 如果不写构造函数,任何类中都存在一个默认的构造函数
默认的构造函数是无参的。 当我们自己写了构造函数,默认的构造函数就不存在
④ 构造函数在构造对象的时候调用
⑤ delete可以用来删掉默认的函数
⑥ 指定使用默认的无参构造函数,用default说明
⑦ 允许构造函数调用另一个构造函数,只是要用初始化参数列表的写法
⑧ 初始化参数列表 : 只有构造函数有
构造函数名(参数1,参数2,…):成员1(参数1),成员2(参数2),…{}
⑨避免形参名和数据成员名相同的导致问题
构造函数代码分析
#include#include using namespace std; class Student { public: Student() { _name = "莫九梦"; _age = 20; _id = 111; } Student(string name, int age, int id) { _name = name; _age = age; _id = id; } void print() { cout << _name << " " << _age << " " << _id << endl; } protected: string _name; int _age; int _id; }; int main() { Student student;//触发构造,调用无参构造函数 student.print(); Student student1("梦", 21, 2020);//触发构造,调用有参构造函数 student1.print(); return 0; }
运行结果
默认构造函数分三种:
①我们不写,编译器就自己生成的构造函数
② 无参的构造函数
③ 全缺省的构造函数(效果和无参构造函数一样)
注意:
构造函数是特殊的类成员函数,不能用student.Student();的方式调用
Student student; student.Student(); //错误的调用方法
通过无参构造函数创建对象,对象后面不能跟括号,否则就成了函数声明。
#include#include using namespace std; class Student { public: Student() {} Student(string name) { _name = name; } void print() { cout << _name << endl; } protected: string _name; }; int main() { Student student(); // 这个对象实际上没有被定义出来,这里会报错。 //调用无参构造函数不能带括号,就用不带括号的来定义就行了 //Student student; return 0; }
如果你没有定义构造函数(类中未显式定义),C++ 编译器就会自动生成一个默认的无参构造函数。当然,如果你自己定义了,编译器就不会帮你生成。
#includeusing namespace std; class Data { public: void print() { cout << data << endl; } protected: int data; }; int main() { //没有定义构造函数,对象也可以创建成功 Data data;//这里调用的是默认的无参构造函数 data.print(); return 0; }
定义了无参构造函数不能同时构造全缺省构造函数
//错误代码 #include析构函数#include using namespace std; class Student { public: Student() { _name="莫九梦"; } Student(string name="莫九梦") { _name = name; } void print() { cout << _name << endl; } protected: string _name; }; int main() { Student student; return 0; }
既然知道了怎么初始化对象,那么如何清理对象呢?这里就要用到析构函数了
先来讲讲析构函数的概念
析构函数和构造函数一样,也是特殊的类成员函数,作用吗?和构造函数相反,析构函数主要是用来清理对象的
这里要注意了!!!
构造函数主要任务是初始化对象,而不是创建对象
析构函数主要是清理对象,而不是销毁对象
析构函数的特点
① 析构函数名是在类名前面加个~
② 没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)
③ 释放数据成员new的内存
在对象死亡前自动调用 通常如果数据成员没有做new操作,就可以不写析构函数
④ 不写析构函数,存在一个默认的析构函数
⑤ 一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)
这里我们写个代码看看析构函数的自动调用
#include拷贝构造函数#include using namespace std; class Student { public: Student(string name = "莫九梦"); ~Student(); void print() { cout << _name << endl; } private: string _name; }; Student::Student(string name) { _name = name; } Student::~Student() { cout << "调用了析构函数" << endl; } int main() { Student student; Student student1("九梦"); return 0; }
① 拷贝构造函数也是构造函数
② 拷贝构造函数参数是固定的:对对象的引用
③ 拷贝构造函数不写会存在一个默认的拷贝构造函数
④ 拷贝构造函数作用: 通过一个对象产生另一个对象
关键点:一定是有一个新的对象产生
拷贝构造函数的概念
拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。(它是一个特殊的成员函数)
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用 const 修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数的特点
① 拷贝构造函数是构造函数的一个重载形式。函数名和类名相同,没有返回值。
② 拷贝构造函数的参数只有一个,并且必须要使用引用传参
代码分析
#include深浅拷贝问题#include using namespace std; class Student { public: Student() {} Student(string name, int age) { _name = name; _age = age; } //拷贝构造函数 //通过传入对象属性确定创建对象的属性 Student(Student& object) { _name = object._name; _age = object._age; cout << "调用自己写的拷贝构造函数" << endl; } void print() { cout << _name << "t" << _age << endl; } protected: string _name; int _age{}; }; //函数传参也可以隐式调用 void print(Student object) //Student object=girl,产生了新的对象object { object.print(); } //C++中传参能用引用就用,效率搞 void printData(Student& object) //引用就是别名,没有产生新的对象 { object.print(); } int main() { Student student("莫九梦", 20); //产生一个对象 Student beauty = student; //隐式调用拷贝构造函数 Student girl(beauty); //显式调用拷贝构造函数 print(girl); //调用拷贝构造函数 printData(girl); Student boy; //对象先有了,才赋值,就不调用拷贝 boy = girl; //不调用拷贝构造函数-->默认重载=运算符(后续会讲) return 0; }
⛅浅拷贝
- 没有在拷贝构造函数中给数据成员做new操作
- 默认拷贝构造函数都是浅拷贝
深拷贝
- 在拷贝构造函数中做了new操作
代码分析
#include#include 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#include 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; }
综上: 一旦类中有指针,做了内存申请,并且要对对象做拷贝操作,就必须使用深拷贝
构造和析构顺序问题- 一般情况构造顺序和析构是相反的
- 静态的和全局的是最后释放的
- delete 立刻调用析构函数
#include#include 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; } //先构造函数输出ABAAAC,再析构函数输出CAAAAB //说明构造函数和析构函数的调用顺序是相反的 return 0; }



