目录
三.拷贝构造函数(浅拷贝)
一.构造函数
在认识构造函数之前我们先看一段代码,
class Date
{
public:
void SetDate(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main() {
Date s1;
s1.SetDate(2001,1,8);
s1.Print();
Date s2;
s2.SetDate(2000, 2, 8);
s2.Print();
}
对于Date类来说,可以通过SetDate公有的方法给对象设置内容,,但是每次创建对象的时候都要调用一次未免麻烦,那能否在对象创建时,就将信息设置进去呢?这个时候就要用到构造函数了。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
1.1特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
这时候我们尝试改一下代码
class Date
{
public:
//函数名与类名相同,无返回值,
//无参构造函数
/*Date(){
}*/
//二者都可以,都是无参
//Date() {
// _year = 2000;
// _month = 1;
// _day = 8;
//}
//
//有参构造函数
//也就是上一个函数的重载
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main() {
//Date s1;
//s1.Print();
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
//Date s1();这是错误的
Date s2(2004,5,8);
s2.Print();
}
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:
//自己没有写构造函数,编译器会自动生成一个无参的默认构造函数
// 如果用户显式定义了构造函数,编译器将不再生成
//Date(int year, int month, int day) {
// _year = year;
// _month = month;
// _day = day;
// }
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main() {
// 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
Date s1;
s1.Print();
}
6,默认构造函数可以分为三种:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数,注意:默认构造函数只能有一个。
class Date
{
public:
//无参
Date() {
_year = 2000;
_month = 1;
_day = 8;
}
//全缺省函数,半缺省函数也是,只不过没有全缺省实用
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//还要系统默认的
//三者只能存一
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
但是默认构造函数对内置类型和基本类型:int/char/double/指针...不做处理,对于自定义类型成员变量才会处理:class/struct去定义类型对象。
这也就为啥上面的编译器默认构造函数初始化_year,_month,_day,为随机值的原因。
这时候就会很多人有疑问:在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?s1对象调用了编译器生成的默认构造函数,但是s1对象 year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵用??
其实不然,先看下面的代码:
class Stack
{
public:
//默认构造函数
//这里不写默认构造函数,会调用不了,会出错的
Stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int)*capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue {
public:
// 默认生成构造函数就可以用了
void push(int x) {
}
int pop() {
}
private:
Stack _st1;
Stack _st2;
};
int main() {
//大致代码意思就是用两个栈实现队列,具体就不写了
MyQueue q;
}
总结:一般情况一个C++类,都要自己写构造函数。一般只有少数情况可以让编译器默认生成。
1、类里面成员都是自定义类型成员,并且这些成员都提供了默认构造函数
2、如果还有内置类型成员,声明时给了缺省值
二.析构函数
2.1概念
前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
2.2特性
1. 析构函数名是在类名前加上字符~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.内置类型不做处理,自定义类型会调用他的析构函数
//像这种只有内置类型,调用他的析构函数,没有什么意义了,他对内置类型不做处理
class Date
{
public:
void SetDate(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
//没有啥用处
cout << "~Date():" << this << endl;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main() {
Date s1;
s1.SetDate(2001, 1, 8);
s1.Print();
}
析构函数的用处:
class Stack
{
public:
//默认构造函数
Stack(int capacity =10)
{
_a = (int*)malloc(sizeof(int)*capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
~Stack()
{
//这种对资源的管理,需要自己定义
//无返回值
cout << "~Stack():" << this << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue {
public:
// 默认生成构造函数就可以用了
// 默认生成析构函数就可以用了
void push(int x) {
}
int pop() {
}
private:
Stack _st1;
Stack _st2;
};
int main() {
//大致代码意思就是用两个栈实现队列,具体就不写了
MyQueue q;
}
总结:析构函数跟构造函数有相似之处:类里面成员都是自定义类型成员,并且这些成员都提供了默认析构函数。
三.拷贝构造函数
3.1概念
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
通俗的来说就是用存在的对象,去初始化另一个对象。
3.2特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
注意: 我们这里讲的是浅拷贝
class Date
{
public:
//默认构造函数
Date(int year=1,int month=1,int day=1) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
//我们不写,编译器会自动调用
//这里一定要用引用传参
Date(const Date& s1) {
cout << "hello" << endl;
_year = s1._year;
_month = s1._month;
_day = s1._day;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main() {
Date s1(2004,5,6);
s1.Print();
//用s1去初始化s2;
Date s2(s1);
s2.Print();
}
为什么我们非要用引用传参呢?
那么在自定类型浅拷贝会做什么呢?
class Stack
{
public:
//默认构造函数
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
//默认拷贝构造函数
//自己不写编译器会自动调用
Stack(const Stack& st) {
cout << "Stack(const Stack& st)" << endl;
}
~Stack()
{
//默认析构函数
//无返回值
cout << "~Stack():" << this << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main() {
Stack st;
Stack st2(st);
}
这段代码是有问题的,会崩溃,不是拷贝构造出现了问题,而是结束的析构函数出现了问题
那么自定类型与内置类型同时存在的情况会做什么?
class Stack
{
public:
//默认构造函数
Stack(int capacity =4)
{
_a = (int*)malloc(sizeof(int)*capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
//默认拷贝构造函数
//自己不写编译器会自动调用
Stack(const Stack& st) {
cout << "Stack(const Stack& st)" << endl;
}
~Stack()
{
//默认析构函数
//无返回值
cout << "~Stack():" << this << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue {
public:
// 默认生成构造函数就可以用了
// 默认生成析构函数就可以用了
//默认拷贝构造函数就可以用了
void push(int x) {
}
int pop() {
}
private:
int size = 0;
Stack _st1;
Stack _st2;
};
int main() {
//大致代码意思就是用两个栈实现队列,具体就不写了
MyQueue q;
MyQueue mq(q);
}
总结:一般的类,自己生成拷贝构造就够用了,只有像栈结构的类,自己管理资源,这样就需要用到之后学到的深拷贝。