1. 构造函数
1.1 概念
在我们学习c语言版的数据结构的时候,我们经常会调用初始化函数来初始化我们自定义的数据结构,这未免有点麻烦,而且有时候会忘记调用,造成未知的后果,那能否在对象创建的时候就把信息设置进去呢?
构造函数就应运而生,构造函数是一个特殊的成员函数,名字和类名相同,创建类对象时有编译器自动调用,以保证类的成员有合适的初始值,并且这个在整个对象的生命周期只调用一次。
1.2 特性
可能大家会被这个函数的名字误导成是用来创造对象的,但该函数的作用是用来初始化变量的。
该函数的特性:
- 函数名和类名相同。
- 没有返回值(不是void就是字面意思的没有)。
- 和普通函数一样支持重载
- 由编译器自动调用
- 在整个对象的生命周期只调用一次。
- 可以缺省
class Date
{
public:
//无参构造
Date()
{
_year = 2023;
_month = 10;
_day = 23;
}
//带参函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//缺省
Date(int year, int month = 10, int day = 23)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
注:全缺省和无参构造函数不同同时存在,调用的时候会产生歧义,不知道调用哪个
好,先在我们知道如何定义一个构造函数,那我们如何调用呢?
class Date
{
public:
//无参构造
Date()
{
_year = 2023;
_month = 10;
_day = 23;
}
//带参函数
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 date;//调用无参构造
Date date1(2024,10,23);//调用有参构造
date.Print();
date1.Print();
return 0;
}
注:大家是不是认为调用无参构造的时候应该写成Date date(),看起来很有道理,但是大家注意这是不是和函数的声明矛盾了?所以这样写是不合理的。
- 如果没有显示的定义构造函数,编译器会默认自动生成一个不带参数的构造函数,但如果自己写了,则不会生成。
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date;
date.Print();
return 0;
}
现在显示定义一个构造函数
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 date;
date.Print();
return 0;
}
看完这点以后大家可能会疑惑,编译器调用默认的构造函数之后,我们看打印的数据还是随机值,这是否意味这这个默认的构造函数没什么用?首先说答案,肯定是有用的。
在解释之前我们先了解一个概念:C++把数据分为两类,一类是内置类型,例如int、char、指针…,还有一类就是自定义类型,例如class、struct、union…,默认的构造函数对内置类型是没什么用的,但是对于自定义类型,默认的构造函数会使自定义类型调用它默认的构造函数。
class Time
{
public:
Time()
{
_hour = 22;
_minute = 55;
_second = 30;
}
void Print()
{
cout << _hour << "-" << _minute << "-" << _second << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time time;
};
int main()
{
Date date;
date.time.Print();
return 0;
}
注:这里我们嘴中说的默认的构造函数,不仅仅指的是编译器自动生成的构造函数,我们写的无参构造函数和全缺省参数构造函数,也是默认的构造函数,也就是可以不传参数就调用构造,都可以叫默认构造。
2. 析构函数
2.1 概念
析构函数的作用和构造函数相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
2.2 特性
- 析构函数是在类名前面加上~
- 没返回类型,和构造函数一样,而且还没有参数
- 一个类只能有一个析构函数,不能重载
- 和构造函数一样,如果你不显示的定义析构函数,编译器会默认的生成一个析构函数。
- 在对象的生命周期结束时,编译器会自动的调用析构函数完成资源回收
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack、Queue类
7.如果在一个自定义类型A里面,还有其他的自定类型B,A自定义类型生命周期结束的时候,会默认的调用B的析构函数。
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
void Print()
{
cout << _hour << "-" << _minute << "-" << _second << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
~Date()
{
cout << "~Date()" << endl;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
int _year;
int _month;
int _day;
Time time;
};
int main()
{
Date date;
return 0;
}
3. 拷贝构造函数
3.1 概念
身为资深CV工程师,我们在创造一个对象的时候,能否创造一个和已存在的对象一样的对象呢?
拷贝构造函数闻着味就来了,它只有一个形参,而且必须是对本类对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
3.2 特性
- 拷贝构造函数也是构造函数的一个重载。
- 拷贝构造函数,只有一个形参,而且必须是本类对象的引用形式,如果你使用传值,编译器会直接报错,因为会引发无穷的递归。
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 2023;
_month = 10;
_day = 24;
}
/*错误写法,因为值传递,也会调用构造拷贝,所以这里会无穷递归
Date(Date d)
{
cout << "Date(Date&)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
*/
//正确写法
Date(Date& d)
{
cout << "Date(Date&)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date1;
Date date2(date1);
date2.Print();
return 0;
}
- 如果没有显示的写拷贝构造函数,编译器会默认的生成一个,默认生成的拷贝构造按内存存储的字节来拷贝,也就是浅拷贝或者叫值拷贝。
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 2023;
_month = 10;
_day = 24;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date1;
Date date2(date1);
date2.Print();
return 0;
}
注:如果类的成员里边有自定义类型,编译器会默认调用它的拷贝构造函数,内置类型则是按照字节直接拷贝而成的。
- 如果类中有资源申请(例如malloc),我们必须显示的定义拷贝,不能使用编译器默认的拷贝函数,如果没有,就可以不用写拷贝构造函数。
/*错误写法*/
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 2023;
_month = 10;
_day = 24;
arr = (int*)malloc(sizeof(int) * 4);
}
void Print()
{
cout << _year << "-" << _month << "-" << _day;
}
~Date()
{
cout << "~Date()" << endl;
free(arr);
}
private:
int _year;
int _month;
int _day;
int* arr;
};
int main()
{
Date date1;
Date date2(date1);
return 0;
}
上面这段代码是错误的示范,因为上面的代码使用的是编译器默认生成的拷贝构造函数,完成的是浅拷贝,也就是两个对象的指针成员指向的是一块空间,根据上面我们所学的析构函数可知,当对象的生命周期结束,编译器会自动调用析构函数,完成资源回收,所以上面这段代码对一块空间free了两次(根据运行结果也能看出),所以错了。
/*正确写法*/
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 2023;
_month = 10;
_day = 24;
_arr = (int*)malloc(sizeof(int) * 4);
}
Date(Date& d)
{
_arr = (int*)malloc(sizeof(int) * 4);
memcpy(_arr, d._arr, sizeof(int) * 4);
}
void Print()
{
cout << _year << "-" << _month << "-" << _day;
}
~Date()
{
cout << "~Date()" << endl;
free(_arr);
}
private:
int _year;
int _month;
int _day;
int* _arr;
};
int main()
{
Date date1;
Date date2(date1);
return 0;
}
- 拷贝构造函数的典型应用
(1)使用已存在对象创建新对象
(2)函数参数类型为类类型对象,因为如果传值的话会发生拷贝,导致效率下降
(3)函数返回值类型为类类型对象,因为如果返回值的话,也会发生拷贝,导致效率下降。
总结:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。