视频与课件来源:https://www.bilibili.com/video/BV1et411b73Z?spm_id_from=333.337.search-card.all.click
目录
前言
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现。(析构和构造函数是空的,里面一行代码没有)
一、 构造函数
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次(每创建一个对象就调用一次构造函数)
构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
二、 析构函数
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次(每销毁一个对象就调用一次析构函数)
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
示例:
#include<iostream>
using namespace std;
#include<string>
// 对象的初始化和清理
// 1.构造函数 进行初始化操作
class Person {
// 1.构造函数
// 没有返回值 也不用写void
// 函数名与类名相同
// 构造函数可以有参数
// 创建对象的时候,构造函数会自动调用,而且只调用一次
public:
Person() {
cout << "Person构造函数的调用" << endl;
}
// 2.析构函数
// 没有返回值 不写void
// 函数名和类名相同 在名称前加~
// 析构函数不可以有参数,不可以发生重载
// 对象在销毁前自动调用,而且只调用一次
~Person() {
cout << "Person的析构函数被调用" << endl;
}
};
void test01() {
Person p;
Person p2;
}
int main() {
test01();
system("pause");
return 0;
}
三、 构造函数的分类及调用
两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
三种调用方式:
括号法(推荐使用)
显示法
隐式转换法
3.1 有参构造和无参构造
构造函数可以发生重载!
// 无参构造(或者叫默认构造函数)
Person() {
cout << "构造函数之无参构造" << endl;
}
// 有参构造
Person(int a) {
age = a;
cout << "构造函数之有参构造" << endl;
}
3.2 普通构造和拷贝构造
// 除了拷贝构造,其他都是普通构造
// 普通无参构造
Person() {
cout << "构造函数之无参构造" << endl;
}
// 普通有参构造
Person(int a) {
age = a;
cout << "构造函数之有参构造" << endl;
}
// 拷贝构造函数
Person(const Person &p) {
//将传入的类身上的所有属性拷贝过来
age = p.age;
cout << "构造函数之拷贝构造" << endl;
}
注意拷贝构造参数传的是类的引用,而且是常引用!
拷贝拷的是什么?拷的是一个类
3.3 括号法调用构造函数
// 1.括号法
Person p1;// 默认构造函数调用
Person p2(10);//调用有参构造函数
cout << "经过此次有参构造后,p2.age=" << p2.age << endl << endl;
Person p3(p2);//调用拷贝构造函数
cout << "经过此次拷贝构造后,p3.age=" << p3.age << endl << endl;
注意:括号法调用默认构造函数(也就是无参构造)时不要加()
不要写成Person p();
要写成 Person p;
因为编译器会把Person p();当成是函数声明
3.4 显示法调用构造函数
1.
// 2.显示法
Person p;//调用默认构造
Person p1 = Person(10);//调用有参构造
Person p2 = Person(p2);//调用拷贝构造
2.
class Person{
public:
string m_name;
int m_age;
// 有参构造
Person(int a,string name) {
age = a;
m_name = name;
cout << "构造函数之有参构造" << endl;
}
// 拷贝构造函数
Person(const Person &p) {
//将传入的类身上的所有属性拷贝过来
age = p.age;
m_name = p.m_name;
cout << "构造函数之拷贝构造" << endl;
}
// 当类中有多个属性时,显示法得这么写
Person p2 = Person(10,"waeaweawe");//显示法调用有参构造
cout << p2.age << " " << endl;cout<< p2.m_name << endl;
Person p3 = Person(p2);//显示法调用有参构造
cout << p3.age << " " << endl;cout<< p3.m_name << endl;
在显示法调用构造函数中,等号右边的那部分叫匿名对象
在 { Person p1 = Person(10);//调用有参构造
Person p2 = Person(p2);//调用拷贝构造 } 中
Person(10);
Person(p2);
如果把上面两个单独拿出来的话,他们叫匿名对象(没有名的对象)
匿名对象的特点:当前行执行结束后,系统会立即回收掉匿名对象。
因为他没有名,后面根本使用不了,所以当前行结束后就被回收。
举个例子:
// 我们用匿名对象创建了一个对象
// 下一行紧跟着一行打印代码
// 如果匿名对象在当前行执行结束后就被回收
// 那么系统打印的顺序是:构造函数→析构函数→dada
// 如果匿名函数不是在当前行结束后就被回收
// 那么打印顺序是:构造函数→dada→析构函数
Person(10);
cout << "dada" << endl;
如果把显示法调用构造函数中的等号右边那一部分单独拿出来的话就是匿名对象,匿名对象的特点是:当前行执行结束后,系统会立即回收掉匿名对象。
关于匿名对象的注意事项
不要利用拷贝构造函数来初始化匿名对象!!!
Person p2=Person(10);//显示法调用有参构造
Person(p2);//这是错误的!!
// 因为编译器会认为Person(p2)等价为Person p2,这是对象声明
// Person(p2)==Person p2;
3.5 隐式转换法调用构造函数
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
class Person{
public:
string m_name;
int m_age;
// 有参构造
Person(int a,string name) {
age = a;
m_name = name;
cout << "构造函数之有参构造" << endl;
}
// 拷贝构造函数
Person(const Person &p) {
//将传入的类身上的所有属性拷贝过来
age = p.age;
m_name = p.m_name;
cout << "构造函数之拷贝构造" << endl;
}
// 当类中有多个属性时,隐式转换法得这么写
Person p4 = { 10,"weaewa" }; // 注意用{}括起来,而不是()
cout << p4.age << " " << p4.m_name << endl;
Person p5 = p4;
cout << p5.age << " " << p5.m_name << endl;
综合
#include<iostream>
using namespace std;
#include<string>
// 构造函数的分类和调用
// 1.分类
class Person {
// 构造函数
// 按照参数分类 ,无参构造和有参构造
public:
// 无参构造
Person() {
cout << "构造函数之无参构造" << endl;
}
// 有参构造
Person(int a) {
age = a;
cout << "构造函数之有参构造" << endl;
}
// 拷贝构造函数
Person(const Person &p) {
//将传入的类身上的所有属性拷贝过来
age = p.age;
cout << "构造函数之拷贝构造" << endl;
}
~Person() {
cout << "析构函数" << endl;
}
int age;
};
// 调用
void test01() {
// 1.括号法
Person p1();// 默认构造函数调用
Person p2(10);//调用有参构造函数
cout << "经过此次有参构造后,p2.age=" << p2.age << endl << endl;
Person p3(p2);//调用拷贝构造函数
cout << "经过此次拷贝构造后,p3.age=" << p3.age << endl << endl;
// 2.显示法
Person p4;//调用默认构造
Person p5 = Person(10);//调用有参构造
cout << "经过此次有参构造后,p5.age=" << p5.age << endl << endl;
Person p6 = Person(p5);//调用拷贝构造
cout << "经过此次拷贝构造后,p6.age=" << p6.age << endl << endl;
//3.隐身转换法
Person p7 = 10 ;
cout << p7.age << endl;
Person p8 = p7;
cout << p8.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
四、 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
4.1 使用一个已经创建完毕的对象来初始化一个新对象
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //括号法调用拷贝构造函数
Person newman2 = man; //隐式转换法调用拷贝构造
cout << "man.age=" << man.age << endl;
cout << "newman.age=" << newman.age << endl;
cout << "newman2.age=" << newman2.age << endl;
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,这是赋值操作
}
4.2 值传递的方式给函数参数传值
值传递的本质会拷贝一个临时的副本
//2. 值传递的方式给函数参数传值
void test02(Person p){
p.age=25;
cout<<"在test02函数中p.age="<<p.age<<endl;
}
void test03() {
Person p; //调用默认构造函数
test02(p);// 实参传给形参时会调用拷贝构造函数
// 拷贝构造函数的参数列表为:const Person &p,形参为只读,改变不了实参。也是值传递
// 值传递参数列表:Person p或const Person &p,形参不可以改变实参
// 引用传递参数列表:Person &p,形参可以改变实参
cout<<"在test03函数中p.age="<<p.age<<endl;
}
4.3 以值方式返回局部对象
局部对象的特点是函数执行完后就会被释放掉
// 3.以值方式返回局部对象
Person test04() {
Person p;
p.age = 55;
cout << "&p=" << int(&p) << endl;
cout << "p.age=" << p.age << endl;
return p; //我们知道局部对象的特点是函数执行完后就会被释放掉,那么此时返回的是p本身吗?
// 此时返回的并不是p本身,因为他已经被释放掉了
//此时返回的是根据p创建出的新对象
//这个新对象和p数据上是一样的(虽然他们两个是不同的对象),因为是拷贝构造来的
// return p会调用拷贝构造函数!!!!
}
void test05() {
Person p1 = test04();
cout << "&1=" << int(&p1) << endl;
cout << "p1.age=" << p1.age << endl;
}
两者地址不一样,说明不是同一个对象。但两者数据是一样的,说明P1是由P拷贝阔来的
以值方式返回局部对象的时候会调用拷贝构造
扩展
当我们的函数返回的是一个局部变量时,该函数的return语句会自动拷贝出一个跟返回的局部变量数据上一样的新变量出来
#include<iostream>
using namespace std;
int test01() {
int a = 25;
int b = 33;
int c = a + b;
cout << "&c" << int(&c) << endl;
return c;
}
int main() {
int d = test01();
cout << "d=" << d << endl;
cout << "&d=" << int(&d) << endl;
return 0;
}
五、 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
验证1:
我们先自己写个构造函数看看效果
#include<iostream>
using namespace std;
#include<string>
// 构造函数调用规则
//默认情况下,c++编译器至少给一个类添加3个函数
//1.默认构造函数(无参,函数体为空)
//2.默认析构函数(无参,函数体为空)
//3.默认拷贝构造函数,对属性进行值拷贝
class Person {
public:
int age;
Person() {
cout << "Person的默认构造函数被调用" << endl;
}
// 我们先写上构造函数看看效果
Person(const Person& p) {
age = p.age;
cout << "Person的拷贝构造函数被调用" << endl;
}
~Person() {
cout << "Person的析构函数被调用" << endl;
}
};
void test01() {
Person p;
p.age = 25;
Person p2(p);
cout << "p.age=" << p.age << endl;
cout << "p2.age=" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
如果编译器默认给类添加了默认构造函数、默认析构函数、默认拷贝构造函数,那么我把自定义的默认拷贝构造函数注释掉,p2还能成功创建吗?试试看
#include<iostream>
using namespace std;
#include<string>
// 构造函数调用规则
//默认情况下,c++编译器至少给一个类添加3个函数
//1.默认构造函数(无参,函数体为空)
//2.默认析构函数(无参,函数体为空)
//3.默认拷贝构造函数,对属性进行值拷贝
class Person {
public:
int age;
Person() {
cout << "Person的默认构造函数被调用" << endl;
}
/*Person(int a) {
age = a;
cout << "有参构造" << endl;
}*/
/* Person(const Person& p) {
age = p.age;
cout << "Person的拷贝构造函数被调用" << endl;
}*/
~Person() {
cout << "Person的析构函数被调用" << endl;
}
};
void test01() {
Person p;
p.age = 25;
Person p2(p);
cout << "p.age=" << p.age << endl;
cout << "p2.age=" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
验证2:如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
#include<iostream>
using namespace std;
#include<string>
// 构造函数调用规则
//默认情况下,c++编译器至少给一个类添加3个函数
//1.默认构造函数(无参,函数体为空)
//2.默认析构函数(无参,函数体为空)
//3.默认拷贝构造函数,对属性进行值拷贝
// 4.如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
class Person {
public:
int age;
/*Person() {
cout << "Person的默认构造函数被调用" << endl;
}*/
Person(int a) {
age = a;
cout << "Person的有参构造函数被调用" << endl;
}
/* Person(const Person& p) {
age = p.age;
cout << "Person的拷贝构造函数被调用" << endl;
}*/
~Person() {
cout << "Person的析构函数被调用" << endl;
}
};
void test01() {
//Person p;
Person p1(25);
Person p2(p1);
cout << "p1.age=" << p1.age << endl;
cout << "p2.age=" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
我们发现,当我们注释掉默认构造函数和默认拷贝构造函数,并自定义一个有参构造后,无法创建对象p,但仍能通过有参构造创建p1后拷贝构造创建p2。所以说明当用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
验证3:如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include<iostream>
using namespace std;
#include<string>
// 构造函数调用规则
//默认情况下,c++编译器至少给一个类添加3个函数
//1.默认构造函数(无参,函数体为空)
//2.默认析构函数(无参,函数体为空)
//3.默认拷贝构造函数,对属性进行值拷贝
// 4.如果用户提供拷贝构造,编译器不会提供其他构造函数
class Person {
public:
int age;
/*Person() {
cout << "Person的默认构造函数被调用" << endl;
}*/
/*Person(int a) {
age = a;
cout << "Person的有参构造函数被调用" << endl;
}*/
Person(const Person& p) {
age = p.age;
cout << "Person的拷贝构造函数被调用" << endl;
}
~Person() {
cout << "Person的析构函数被调用" << endl;
}
};
void test01() {
Person p;
Person p1(25);
Person p2(p1);
cout << "p1.age=" << p1.age << endl;
cout << "p2.age=" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
我们发现,当我们注释掉默认构造函数和有参构造函数并只留下自定义的拷贝构造函数后,无法通过默认构造函数创建p也无法通过有参构造函数创建p1