C++基础(3)-类的6个默认成员函数
C++的类中,有6个默认成员函数,即使用户不实现,编译器会帮助用户实现。
类的6个默认成员函数:
1、构造函数
2、析构函数
3、拷贝构造函数
4、赋值重载函数
5、const成员函数
6、取地址以及const取地址操作符重载
构造函数
当我们实现一个类后,往往创建出这个类的对象后,首先要对其进行初始化,这一点无论放在哪个类上面,都是要进行的。所以,C++在类中引入构造函数,当类的对象创建出来后,C++编译器会自动调用其构造函数进行初始化工作,这样,用户在创建出对象后,就可以直接使用这个对象,不需要初始化了。
构造函数的职责不是开空间创建对象,是对类实例化的对象进行初始化。
在类中定义了构造函数后,编译器会通过用户实际传递的参数进行匹配适合的构造函数,这个适合的构造函数只能有一个,进行初始化对象。
class Date
{
public:Date(){_year = 2000;_month = 12;_day = 31;}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 d1;d1.Print();Date d2(2025,1,1);d2.Print();return 0;
}
如果要调用无参的构造函数时,不可以Date d1()这样写,因为这会让编译器分不清这一行代码是函数声明还是创建类对象。
class Date
{
public:Date(){_year = 2000;_month = 12;_day = 31;}
private:int _year;int _month;int _day;
};int main()
{Date d1(); //ErrorDate d1; //OKreturn 0;
}
构造函数可以支持重载,这样写语法层面上没问题,但是,在创建Date对象时,如果是无参数创建时(Date d1;),此时,编译器就不知道要调用哪个构造函数了,因为这俩构造函数都满足条件,编译器就会报错。
Date()
{_year = 2000;_month = 12;_day = 31;
}
Date(int year = 2020, int month = 1, int day = 1)
{ _year = year;_month = month;_day = day;
}//调用 Date d1;出现Error
即使我们在类中不写构造函数,此时,编译器也会默认生成一个构造函数。默认生成的构造函数:对于内置类型的成员变量不做处理、对于自定义类型的成员变量会去调用其构造函数。
内置类型就是C语言内部自带的类型,比如:int、double、float、指向任何数据类型的指针类型等。自定义类型就是用户自己定义的类型:比如:struct结构体、class类、union联合体类型等。
对内置类型不做处理。
对自定义类型会去调用其自定义类型的构造函数。
这个设计不是很完美,所以在C++11及其以后,支持给类中的内置类型的成员变量赋缺省值。类似于这样:
class AA
{
private:int x=10;int y=20;
};
这样,在进行初始化对象时,调用编译器生成的默认构造函数时,就会将内置类型处理为缺省值,即x初始化为10,y初始化为20。
但是,注意,这里的x和y成员变量还是声明,这个10、20只是给的缺省值,不是定义!!!
默认构造函数分为:
1、用户不写,编译器默认生成的构造函数是默认构造函数。
2、用户写的无参数的构造函数是默认构造函数。
3、用户写的全缺省的构造函数是默认构造函数。
只要不传参数的构造函数,都是默认构造函数。这三种默认构造函数不能同时存在,那样会存在歧义,导致编译器不知道调用哪个,所以只能存在一个。
构造函数的特征:
1、函数名和类名相同
2、无返回值
3、对象实例化时编译器自动调用对应的构造函数进行初始化
4、构造函数可以重载
5、当用户不写构造函数时,编译器会默认生成一个构造函数,在类的对象实例化出来后进行调用,默认成员函数对内置类型可以用其缺省值进行初始化,如果没有给缺省值,就用随机值初始化、对自定义类型去调用其自定义类型的构造函数。
6、当用户写了构造函数时,编译器就不会生成默认构造函数了。
析构函数
构造函数的作用是进行初始化,析构函数的作用是进行销毁、完成资源的清理工作,这里的资源指的是用户自己申请的内存空间,new、malloc的空间,内置类型不需要清理,比如:int、double类型的成员变量,因为这些内置类型是存储在这个类对象所处的函数栈帧中的,当出了作用域后,编译器就会对这块栈帧进行销毁,这里的内置类型的成员变量也会被销毁,而用户手动申请的空间存储在堆上,堆内部开辟的空间只有程序退出时才会被系统进行销毁,如果用户不主动销毁,那么只有等到程序退出的时候,才会被销毁,这里就会出现内存泄漏的问题,所以析构函数的存在是为了防止内存泄露,从而在合适的时机将资源其进行销毁。
class Stack
{
public:Stack(int capacity = 5){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){perror("malloc error");exit(-1);}_capacity = capacity;_top = 0;}~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;int _top=0;int _capacity;
};
int main()
{Stack s;return 0;
}
调用析构函数之前:
调用析构函数之后:
析构函数特征:
1、函数名是在类名前面加上按位取反操作符~
2、无参数、无返回值
3、一个类只能有一个析构函数。若用户未显示定义,系统会自动生成默认的析构函数。
4、不支持重载。
5、类对象生命周期结束后,C++编译器会自动调用其析构函数。
6、如果用户不写析构函数,编译器会默认生成析构函数,默认生成的析构函数:对内置类型成员变量不做处理,对自定义类型的成员变量会去调用其自定义类型的析构函数。
对于上面Stack的例子,如果不写析构函数,那么_a指向的空间是不会被销毁的,编译器也不知道要销毁多少,多长的空间资源,所以,对于析构函数来说,如果类中手动申请了空间,那么析构函数就很有必要实现。