类的默认成员函数
类中共有6个默认成员函数,即自己不实现,编译器会帮助我们实现出来
构造函数、析构函数、拷贝构造、赋值运算符重载、const成员、取地址及const取地址操作符重载
1. 构造函数
1. 概念
- 在对象构造时调用的函数,这个函数完成初始化工作
2. 无参时主函数中的写法
#include<iostream> using namespace std; class date { public: //没有返回值 date(int year, int month, int day)//构造函数名与类名相同 { _year = year; _month = month; _day = day; } date()//构造函数可以重载 { _year = 0; _month = 0; _day = 0; } void print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { //构造函数无参时 //date d();错误写法会报错 date d;//正确写法 d.print(); return 0; }
- 构造函数无参是一种特殊情况,不能像普通成员函数一样写括号,只写类的实例化即可
- 编译器无法区分 date d(); 是函数的声明还是定义对象
3. 特性
特性1-3
1.没有返回值
2.函数名跟类名相同
3.对象实例化时编译器自动调用对应的构造函数
#include<iostream> using namespace std; class date { public: //没有返回值 date(int year,in tmonth,int day)//构造函数名与类名相同 { _year=year; _month=month; _day=day; } private: int _year; int _month; int _day; int main() { date d(2023,2,7);//类的对象实例化自动调用构造函数 return 0; }
特性 4
4.构造函数可以重载(一个类有多个构造函数)
class date { public: //没有返回值 date(int year,in tmonth,int day)//构造函数名与类名相同 { _year=year; _month=month; _day=day; } date ()//构造函数可以重载 { .... } private: int _year; int _month; int _day; int main() { date d(2023,2,7);//类的对象实例化调用构造函数 return 0; }
当使用构造函数不传参数时,若写成date d2(); ,则会报错
特性 5
如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
内置类型(int char double)
#include<iostream> using namespace std; class date { public: //编译器自动生成默认构造函数 void print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { date d; d.print();//-858993460--858993460--858993460 return 0; }
若输出结果,则会发现为随机值
对于编译器自动生成的默认构造函数, 针对内置类型的成员变量没有做处理
自定义类型
#include<iostream> using namespace std; class Time { public: Time()//默认构造函数 { _hours = 0; _minute = 0; _seconds = 0; } private: int _hours; int _minute; int _seconds; }; class date { public: //没有构造函数,则编译器会自动生成一个无参的默认构造函数 void print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; Time _t;//调用自定义类型 }; int main() { date d;//无参数 d.print(); return 0; }
在date类中又定义了一个自定义类型time
自定义类型成员会调用它的默认构造函数(不用传参数的构造)
特性 6
- 无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个
- 对于无参的,两者都可以使用,就没必要共同存在
- 默认构造函数:(不用传参数)
1.自己实现的无参的构造函数
2.自己实现的全缺省构造函数
3.自己没写编译器自动生成的
- 既想要带参数,又想要不带参数的 如何使用一个构造函数完成?
全缺省
若参数没有传过去,则使用缺省参数
若有参数,则直接进入函数中
#include<iostream> using namespace std; 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; } private: int _year; int _month; int _day; }; int main() { date d;//无参数 d.print();//1-1-1 date d2(2022, 12, 20);//带参数 d2.print();//2022-12-20 return 0; }
2. 析构函数
1. 概念
- 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
2. 特性
1. 析构函数名是在类名前加上字符~ (~在c语言表示按位取反)
2. 无参数无返回值类型
3. 一个类只能由一个析构函数,若未显式定义,系统会自动生成默认的析构函数(析构函数不能重载)
4. 对象生命周期结束,C++编译系统自动调用析构函数
#include<iostream> using namespace std; class date { public: date() { } //没有返回值并且无参 ~date()//析构函数,若我们自己不写,系统会自动生成一个析构函数 { } int main() { date d; return 0;//调试时,走到return 0这步 F11会进入析构函数 }
3.先构造后析构
#include<iostream> using namespace std; class stack { public: stack(int n=10)//构造函数 { _a = (int*)malloc(sizeof(int) * n); _size = 0; _capity = n; } ~stack()//析构函数 { free(_a); _a = nullptr; _size = _capity = 0; } private: int* _a; int _size; int _capity; }; int main() { stack s1; stack s2; return 0; }
若使用构造函数malloc开辟一块空间,则使用析构函数free销毁空间
先通过 构造s1,再构造s2
由于在栈中,满足先进后出,所以 先析构s2,再析构s1
4. 对于成员变量
#include<iostream> using namespace std; class Time { public: ~Time()//析构函数 { cout << "~Time()" << endl;//输出~Time() } private: int _hours; int _minute; int _seconds; }; class date { public: //没有构造函数,则编译器会自动生成一个无参的默认构造函数 void print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; Time _t;//调用自定义类型 }; int main() { date d;//无参数 d.print(); return 0; }
- 默认生成析构函数,对内置类型成员不处理
- 默认生成析构函数,对自定义类型的成员,调用它的析构函数
3. 拷贝构造函数
1.值传递
#include<iostream> using namespace std; class date { public: date(int year = 1, int month = 1, int day = 1)//全缺省构造 { _year = year; _month = month; _day = day; } date(date d)//值传递 date d 会报错造成无限循环 { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { date d1(2022,12,21); date d2(d1);//拷贝构造 return 0; }
- 这里为什么会报错?
- 存在递归拷贝
类的对象实例化需要先调用构造函数,所以需要先传参数,把d1传给d这个过程属于传值调用,即将d1拷贝给d,正常来说,若两者为内置类型(int ,char ,double)会直接进行拷贝,但是由于d1和d都是自定义类型date,所以会发生拷贝构造
date d(d1)又是一个拷贝构造,又会发生重复上述过程,即先调用构造函数,调用之前先传参数,d1传给d这个过程中再次发生拷贝构造,从而导致无线循环下去
2. 引用传递
由于d为d1的别名,d相当于d1本身,所以 参数d1传给 d的过程中, 不会发生拷贝构造
#include<iostream> using namespace std; class date { public: date(int year = 1, int month = 1, int day = 1)//全缺省构造 { _year = year; _month = month; _day = day; } date(const date& d)//引用传递 { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { date d1(2022,12,21); date d2(d1);//拷贝构造 return 0; }
加入const,是为了防止由于操作失误导改变d本身
如:假设 d._year =_year , _year代表d2._year ,将d2中的年赋值给d1的年,就会导致报错
3.内置类型(int char double)
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; } //编译器自动生成拷贝构造 private: int _year; int _month; int _day; }; int main() { date d1(2023,2,7);//无参数 date d2(d1); d1.print();//2023-2-7 d2.print();//2023-2-7 return 0; }
虽然我们没有自己写拷贝构造,但是使用编译器自动生成的拷贝构造依旧可以正常运行,
说明对于内置类型会进行处理
4. 浅拷贝问题
#include<iostream> using namespace std; class stack { public: stack(int n)//构造函数 { _a = (int*)malloc(sizeof(int) * n); _size = 0; _capity = n; } ~stack()//析构函数 { free(_a); _a = nullptr; _size = _capity = 0; } private: int * _a; int _size; int _capity; }; int main() { stack s1(10); stack s2(s1);//拷贝构造 return 0;//空间会被释放两次,程序崩溃 }
以上代码为什么一运行就会报错?
-
s1._a 指针指向 开辟的10个字节的空间,由于是拷贝构造,所以将s1._a指针的值 赋值给了 s2._a指针, 使s2._a指针同样指向与s1._a相同的位置,
又由于是先构造的后析构,所以理应先析构 s2 ,s2free这块空间后,由于s1._a与s2._a指向同一个位置,s1还会对这块空间再次free
同一块空间释放两次,会导致崩溃
4. 赋值运算符重载
1.运算符重载
1. 自定义类型为什么要使用运算符重载
#include<iostream> using namespace std; 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; } private: int _year; int _month; int _day; }; int main() { date d1(2022,12,21); date d2(2022,12,22); return 0; }
date 实例化出的对象 d1 与d2 的大小可以直接比较嘛?
不可以,内置类型是编译器自己定义的类型,它知道要怎么比
自定义类型是自己定义的,怎么比较大小由自己规定
C++为了增强代码的可读性引入运算符重载,运算符重载是具有特殊函数名的函数 ,运算符重载就是为了给自定义类型进行比较等提供的函数
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)