类的构造函数和析构函数
对于大多数的类,都应该为类提供构造函数和析构函数,问题是为什么需要这种函数:C++的目标之一就是让使用类对象类似于使用内置的标准类型一样。然后到现在为止我们还无法直接在声明的时候直接向初始化结构或者数组一样初始化类。
int a = 1;
struct car {
std::string name;
int years;
};
car c{"Moon",3200};
class pen {
std::string brand;
int price;
};
pen p{"Hero",1999}; // 不被允许因为brand和price都是私有成员! 如果声明为公共成员就可以但是违反了OOP的数据隐藏原则。
上述例子因为程序无法直接访问类私有成员,必须要通过成员函数才能访问,那么要如何解决这种问题呢,C++提供了一个特殊的函数叫做构造函数,专门用于构造新对象,将值赋给他们的数据成员,更准确的说C++为这些成员函数提供了名称和使用语法,而程序员需要为其提供定义,名称与类名相同,构造函数的原型和函数头有一个有趣的特征,虽然没有返回值,但没有被声明为void,实际上构造函数没有被声明类型(实际上构造函数只能在创建类对象时自动被程序调用,程序员无法调用,所以返回值没有任何意义,即没有人可以接受他的返回值)
声明和定义构造函数
来看car类的一个允许的构造函数
car(const std::string& carname,int price); // 需要注意的是第一没有返回值,第二名称与类名一致
car(const std::string& carname,int price){
name = carname;
this->name = name;
}
成员名和参数名
不熟悉的构造函数的您会使用类成员名称作为构造函数的参数名,如下所示
car(const std::string& carname,int price);
如果你最终在定义中写出如下代码就是错误的,根据局部变量覆盖全局变量的原则,如下语句就是把函数形式参数price赋值给自己
price = price;
有两种办法解决这个问题
- 更改成员函数名称 加上下划线或者跟上m_前缀 如
_price;
_name;
//或者
m_price;
m_name;
- 或者如上述构造函数所示,通过this指针显示指定类成员
this->name. = name; // 前面name被显示指定为类成员,而后面的根据局部变量覆盖全局变量是函数形式参数
在现代IDE中(如图为2022版本的Clion)往往类成员被颜色显示标出。
使用构造函数
C++提供两种办法来使用构造函数初始化对象的方式
显示调用构造函数
car c = car("Mango",1900);
car c("Mango",1900);
// 或者使用new分配内存
car * c = new car("Mango",1900);
但是无法显示调用的构造函数,因为在构造对象前,对象是不存在的,所以构造函数只被创建对象,而不能通过对用来调用。
默认构造函数
**默认构造函数是在未提供显式初始值的时候,用来创建的对象的函数,**也就是说他用来创建下面声明的类对象。
car c;
这种语句的管用的地方在于如果没有提供任何构造函数C++将自动提供默认的构造函数,他是默认的构造函数的隐式版本,不做任何工作,显示版本一般为
car(){ };
如同创建int都是不提供初始值一样。
int x;
奇怪的是,当且仅当没有定义任何构造函数时,C++编译器才会提供默认的构造函数,一旦为类定义了构造函数后,如果还想使用上述声明就必须手动提供默认构造函数,如果提供了非默认的构造函数,但是没有提供默认构造函数一下声明将会出错。
car c // 不被允许
这样做的原因无非就是C++编译器认为一旦程序员提供了构造函数就应该完全符合程序员的构造函数来构造,从而不提供默认构造函数,来防止不被初始化的对象。
定义默认构造函数的方式有两种,一种是为构造函数的所有参数设置默认值,第二种方式是通过函数重载提供一个没有参数的构造函数,如下
// 为所有参数设置默认值
car(const std::string& carname = "undefined",int price = 0){
name = carname;
this->price = price;
}
// 没有参数构造函数
car(){ };
// 没有参数的构造函数
car(){
name = "undefined";
this->price = price;
}
提供了上述任何一种构造函数都可以直接声明对象,而不必初始化。
一般来说在设计类的时候,我们都会提供默认构造函数,不然你将无法按以下方式创建数组等容器,除非你为所有元素全部初始化
car c[1000]; // 如果没有默认构造函数将不被允许
car c;// 正确 自动调用了默认构造函数
car c(); // 错误你将声明一个返回值为 car 函数名为 c 没有任何参数的函数,而不是创建对象
隐式的调用默认构造函数时不要用括号,用括号必须要有显示的参数。
构造函数
用构造函数创建对象时,程序负责跟踪该对象,直到其过期为止,对象过期时程序会自动调用一个特殊的成员函数—析构函数,析构函数完成清理工作,如果构造函数或者其他类成员函数使用new来分配内存,那么析构函数负责释放这种内存,也就是说如果构造函数或者其他函数没有使用new分配内存,那么析构函数实际上没有事情可做。只需要编译器生成什么都不做的隐式析构函数即可。
析构函数用于释放类中用new分配的内存,自动变量会自动释放,不需要也无法使析构函数介入
和构造函数一样,析构函数的名称也很特别,在类名加~,同样没有返回值也没有声明类型,但是与构造函数稍作区别的,析构函数没有参数,下面就是car类的析构函数的声明.
~car();
什么时候调用析构函数呢,这通常由编译器决定,通常不应该在代码中显式调用的析构函数(可以但一般不)。如果创建的是静态存储对象,则析构函数会在程序结束被调用,如果是自动存储类对象,则其析构函数会在程序执行完代码(该变量被声明)块后被调用。在这种情况下程序会自动调用析构函数。。
由于在类对象过期后析构函数会被编译器自动调用,所以必须要存在一个析构函数,如果程序员没有显示的提供析构函数,那么编译器将隐式的声明一个默认构造函数后(就是啥事都不敢的构造函数)并在发现对象被删除的代码后生成该定义。
在默认情况(没有重载赋值运算符)将一个对象赋值给同类型的对象,C++将原对象的内容复制到目标对象的相应数据成员中。
来看两条语句
car car1 = car("Mango",1900);
car2 = car("Mango",1900);
第一句话是初始化,他可能会创建一个临时对象(这通常取决于编译器实现),第二个是一定会创建一个临时对象。
C++11 列表初始化
在C++11中可以将列表初始化语法用于类吗,可以,只要有匹配的构造函数,就可以实现,看下面实例。
car car1("Mango",1999);
car car1 = car1("Mango",1999);
car car1{"Mango",1999};
如上三条语句完全等价。
另外C++11还提供了名为 s t d : : i n i t i a l i z e l i s t std::initialize_list std::initializelist的类,可将其用作函数参数或者方法参数的类型,这个表示任意长度的列表,只要类型都相同,或者可以表示为同样长度的类标。
c o n s t const const 成员函数
// 看下面的car类的成员函数 show
void car::show(){
cout << this->name << " " << this->price << endl;
}
// 函数调用
const car car1{"Mango",1999};
car1.show();
编译器将拒绝调用 s h o w show show函数因为该函数无法保证不修改 c a r 1 car1 car1里面的内容,在一般的函数中解决的问题的方法往往是将参数列表的参数声明为常量,但是这个函数没有参数,相反它的参数是通过方法调用隐式提供的。需要一种的新的语法保证函数调用不会修改对象,C++的解决方法是将 c o n s t const const放在括号后面。看如下新的 s h o w show show函数声明
void car::show() const{
cout << this->name << " " << this->price << endl;
}
以这种方法声明和声明的函数被称为 c o n s t const const成员函数,就像就可能将 c o n s cons const引用和指针作为函数参数一样,只要类方法不修改调用对象就应该声明他为 c o n s t const const.
构造函数和析构函数小结
构造函数是一种特殊的成员函数,在创建类的对象的时候被调用,构造函数名称与类名相同,可以通过函数重载创建多个构造函数,构造函数没有声明类型。通常构造函数用于初始化类对象里面的成员,确保所有成员都能被合理的初始化。
car(int brand){
this->brand = brand;
}
car car1{1000};
car car1 = car(1);
car car1(1000);
car car1 = 1000;
实际上第四种实例是新类型,之后将介绍如何关闭这种类型,因为他可能带来令人不愉悦的以外,接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值。
默认构造函数没有参数,因此如果创建对象时没有显示的初始化,则将调用默认构造函数,如果程序中没有显式的提供构造函数,那么编译器将自动提供一个隐式的构造函数,否则必须自己提供默认构造函数,默认构造函数要么没有任何参数,或者为所有参数提供默认值。
当对象被删除的时候将会自动调用析构函数,每个类只能有一个析构函数(因为没有参数,所以无法重载),析构函数没有返回类型也没有参数,其名称为类名在前面加上一个~。如果构造函数使用new分配内存,那就必须使用delete释放内存。