类的六个默认函数
如果一个类当中没有成员的话,那叫空类,实际上空类有6个编译器默认生成的函数成员
默认成员函数:没有显示实现,编译器生成的成员函数称为默认成员函数
1,构造函数与构析函数
1.1构造函数的概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以
保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
1.2构造函数的特性
构造函数主要任务是初始化对象,并不是开辟空间
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
class Data {public://无参构造函数Data(){_year = 1;_month = 1;_day = 1;}//含参构造函数Data(int year, int month, int day){_year = year;_month = month;_day = day;}//全缺省函数Data(int year = 2, int month = 2, int day = 2){}void printf(){cout << _year << _month << _day << endl;//崩溃,cout << "printf()" << endl;//正常}private:int _year;int _month;int _day;};int main(){Data d1;d1.printf();Data d2(2024, 2, 2);d2.printf();Data d3;d3.printf();return 0;}
4.1无参构造函数与含参构造函数的使用,无参函数直接Data d1;不加括号为了
避免重复声明新函数
Data d1;d1.printf();Data d2(2024, 2, 2);d2.printf();
4.2全缺省函数与无参构造函数,d1与d3构成重载函数,但下面会构成重载失误
Data d1;d1.printf();Data d3;d3.printf();//d3与d1构成重载,
会出现下面错误
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
6. C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。
class Time {public:Time(){cout << "time()" << endl;_hour = 0;_minute = 0;}private:int _hour;int _minute;};class Data{public:private:int _year; int _month; int _day; Time _t;};int main(){Data d;return 0;}
上面的代码的处理结果是内置类型不一会被处理(具体看编译器vs不会处理),而自定义类型会被处理,Time是自定义类型所以会初始化
但只有是无参构造函数才会成功,若有参数会报错Time(int a){}
1.3析构函数的概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
1.4析构函数的特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
typedef int DataType;class Stack{public: Stack(size_t capacity = 3) { _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } _capacity = capacity; _size = 0; } void Push(DataType data) { // CheckCapacity(); _array[_size] = data; _size++; } // 其他方法... ~Stack() { if (_array) { free(_array); _array = NULL; _capacity = 0; _size = 0; } }private: DataType* _array; int _capacity; int _size;};void TestStack(){ Stack s; s.Push(1); s.Push(2);}
如果像这一类,申请了空间,没有调用显示析构函数,会有内存泄漏的风险,所以如果有类调用堆上的空间,一定要调用析构函数
2,拷贝构造函数
2,1概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
2.2特性
1, 拷贝构造函数的参数只有一个
2参数必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
class Date{public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // Date(const Date& d) // 正确写法 Date(const Date& d) // 错误写法:编译报错,会引发无穷递归 { _year = d._year; _month = d._month; _day = d._day; }private: int _year; int _month; int _day;};int main(){ Date d1; Date d2(d1); return 0;}
因为传值会调用拷贝构造函数,造成无限递归,所以用引用
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
4类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
typedef int DataType;class Stack{public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } }private: DataType *_array; size_t _size; size_t _capacity;};int main(){ Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2(s1); return 0;}
这是为什么,是因为浅拷贝是在同一块空间,如果创建两个对象,s1,s2,都指向同一内存,调用析构函数时,会将同一块空间free两次,造成错误
5. 拷贝构造函数典型调用场景:
使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象
class Date {public:Date(int year,int month,int day){cout << "Data()"<<endl;_year = year;_month = month;_day = day;}~Date(){cout << "~Data()"<<endl;}Date(const Date& d){cout << "Date(const Date&d)"<<endl;_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};Date test(Date d1){Date tem(d1);第二次拷贝构造return tem;//本来是第三次拷贝构造,但被编译被优化了因为第二次拷贝构造的值可以做返回值}int main(){Date d1(2024, 2, 1);test(d1);//第一次拷贝构造函数return 0;}
下面是结果
6.传值与引用的细分
1传值会多拷贝一次,比较麻烦,但是可以避免一些错误
2而引用虽然方便但是会有一些情况出问题;
class Date {public:Date(int year,int month,int day){cout << "Data()"<<endl;_year = year;_month = month;_day = day;}~Date(){cout << "~Data()"<<endl;_year = -1;_month = -1;_day = -1;}Date(const Date& d){cout << "Date(const Date&d)"<<endl;_year = d._year;_month = d._month;_day = d._day;}Date operator=(const Date& d){_year = d._year;_month = d._month;_day =d._day;//this是d2的形参,d是_d4//然后d1是this的位置,d2是d4 return *this;}void printf(){cout << _year <<" " << _month <<" " << _day << endl;}private:int _year;int _month;int _day;}; Date& fun() { Date d(1, 2, 2); return d; }void func() { } int main() { Date d1(2024, 4, 14); Date d2(d1); Date d3 = d1;//拷贝构造,把存在的拷贝给不存在的 Date d4(2024, 5, 1);d1= d4;//赋值重载,不会调用拷贝构造函数 //连续赋值 d1 = d2 = d4; //到这一行总共5个调用拷贝,但是把上面的变成引用返回,只有两个return 0;}
下面是结果
但是如果是引用的话
下面是一些错误的发生
Date& fun() { Date d(1, 2, 2); return d; } int main() { Date& ret = fun(); ret.printf(); return 0; }
因为return d前先析构,再返回,本来是调试看到析构后this是随机值是野空间,如果下面在调用一个函数,就会可能会变野空间会 变化,随机值被改变
总 结 返回对象是临时对象,或局部变量不用引用返回,有风险
1如果返回对象生命周期到了,不会析构,可以用传引用返回uobuyi
创作不易,希望点赞!!!
目录
类的六个默认函数
1,构造函数与构析函数
2,拷贝构造函数