1.类的6个默认成员函数
一个类中什么都不写,就是空类。而空类实际上有成员,当一个类中什么都不写时,编译器会生成六个对应默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
本文先介绍前三个:构造函数,析构函数,拷贝构造
2. 构造函数
2.1 概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,并且在对象整个生命周期内只调用一次。
如有以下Date类:
class Date
{
public:
Date()
{
cout << "调用了构造函数" << endl;
_year = 1;
_month = 1;
_day = 1;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
Date test;
cout << test._day << " " << test._month << " " << test._year << endl;
return 0;
}
Date的对象实例化时,会自动且必须调用构造函数,这里Date的构造函数是使类的属性初始化。
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象
特征!
:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载,可以多写几个,定义多种初始化方式
//构造函数重载
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{
cout << "调用了无参构造函数" << endl;
}
Date(int x)
{
cout << "调用了有参构造函数" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date test;//调用了无参构造函数
Date test1(10);//调用了有参构造函数
return 0;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,这个自动生成的默认构造函数一般不会做任何事(不同的编译器不太一样),一旦用户显式定义编译器将不再生成。
class Date
{
public:
private:
int _year;
int _month;
int _day;
};
int main()
{
//程序正常运行,因为编译器自动生成无参的构造函数,示例化会使用这个构造函数
Date d1;
return 0;
}
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 1. 用户显式定义了构造函数,编译器不会生成默认构造函数
// 2. 程序不能通过编译,实例化没有适合的构造函数调用
Date d1;
return 0;
}
- C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
class Date
{
private:
int _year = 1;
int _month = 1;
int _day = 1 ;
};
int main()
{
Date d1;
return 0;
}
- 如果类中有自定义类型,那么在实例化时会调用自定义类型的构造函数
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数全缺省构造函数编译器默认生成的构造函数都可以认为是默认构造函数。
总结:不传参数就可以调用的就是默认构造
3.析构函数
3.1 概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 特性
- 析构函数名是在类名前加上字符 ~
- 无参数无返回值类型
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。并且在同一作用域下,如果有多个类实例化,那么析构函数的调用顺序与构造函数完全相反,这句话用以下代码具体说明:
class A
{
public:
A()
{
cout << "调用了A的析构函数" << endl;
};
~A()
{
cout << "调用了A的析构函数" << endl;
}
};
class B
{
public:
B()
{
cout << "调用了B的析构函数" << endl;
};
~B()
{
cout << "调用了B的析构函数" << endl;
}
};
class C
{
public:
C()
{
cout << "调用了C的析构函数" << endl;
};
~C()
{
cout << "调用了C的析构函数" << endl;
}
};
int main()
{
A test1;
cout << "----------------" << endl;
B test2;
cout << "----------------" << endl;
C test3;
cout << "----------------" << endl;
return 0;
}
可以看到程序运行到最后时,A B C 类开始调用他们的析构函数,调用顺序与构造函数完全相反。
- 与构造函数类似,默认析构函数对于内置类型不做处理,对于自定义类型会去调用其的析构函数。
- 实践中总结:有资源要清理,就写析构。如Stack类。没有资源要清理(如Date类),或者没有内置类型只剩下自定义类型(如MyQueue),用默认生成的析构就可以了。
4.拷贝构造
拷贝构造也是一种构造函数,但其构造的形式是以拷贝的方式进行。
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是该类型对象的引用(Date类举个例子)
class Date
{
public:
//默认构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//拷贝构造
Date(Date& test)
{
_year = test._year;
_month = test._month;
_day = test._day;
}
private:
int _year;
int _month;
int _day;
};
为什么拷贝构造的参数是引用而不是直接传参呢?我们需要了解传自定义类型参数的实质,如果直接传值:
//Date类内
...
Date(Date test)
{
_year = test._year;
_month = test._month;
_day = test._day;
}
...
//main
Date a1;
Date a2(a1);
同时说明:传入自定义类型作为参数(参数非引用)时,是又创建了个临时类,再调用这个临时类拷贝构造,以你传入的参数作为拷贝构造的参数的。结论:以类为参数的函数,在执行函数时,会调用一次类的拷贝构造
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Date
{
public:
//默认构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//不显示定义拷贝构造
private:
int _year;
int _month;
int _day;
};
int main()
{
Date t1;
Date t2(t1);//使用默认拷贝构造可以实现浅拷贝
}
- 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
class Date
{
public:
Date()
{
arr = (int*)malloc(20);
_year = 1;
_month = 1;
_day = 1;
}
~Date()
{
free(arr);
}
private:
int _year;
int _month;
int _day;
int* arr;
};
int main()
{
Date t1;
//使用默认的拷贝构造,是浅拷贝,t1与t2的arr指针指向同一片空间
Date t2(t1);
//t1与t2最后都调用析构函数,程序崩溃 同一片空间free了两次
return 0;
}
5. 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类的类型对象
- 函数返回值类型为类的类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022, 1, 13);
Test(d1);
return 0;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。