0 引入
在C/C++中,变量(对象)在定义的时候,可以被初始化的,如果不初始化,变量的值是什么呢?
- 未初始化的全局变量和静态变量--------> 0
- 未初始化的局部变量-------->随机值(不可预测的)
对于C++中的对象来说,对象和基本变量一样,定义的时候也可以进行初始化。但是,一个对象的内部结构可能比较复杂,在定义的时候,如果不进行初始化,在使用的时候就可以能会产生一些错误。
如:有一些以指针为成员变量的类实例化的时候,如果没有初始化,成员指针就是野指针,我们的屏幕对象,在实例化之后,必须经过"初始化"工作,确定内部的(状态)。所以,对象的初始化往往不仅仅是对成员变量的赋值那么简单,也可能还需要进行一些动态内存分配,打开文件等复杂的操作,这种情况下,我们就不能以初始化基本类型的方式来对对象进行初始化了。
虽然可以为类设计一个"初始化"函数,对象在实例化之后,就立即调用这个函数,但是这样做的话,初始化函数就不具有强制性,很难确保程序每一次实例化之后都调用它。
面向对象的程序设计语言倾向于对象(变量)在实例化之后,一定要有一个确定的状态(值),使用起来才会比较安全。
因此,C++引入了构造函数(constructor)的概念,用于对对象进行自动初始化工作。
1 构造函数(constructor)
构造函数:在创建一个新对象的时候一定会调用。
- 形式:
类名(){}
- 功能:专门用于对象的初始化工作,在类的对象创建时定义初始状态。构造函数的作用只是给成员变量赋值,初始化资源。不给对象分配空间!
- 特点:
1.构造函数的名字和类名相同!
2.构造函数没有返回值类型,也不能写void,可以有参数(可以重载,可以有很多个构造函数),参数可以设置默认值。
3.在创建对象的时候,自动调用,而且一定会调用,且只调用一次,不能通过已有对象 手动调用构造函数!
4.如果一个类中没有显示的声明构造函数,编译器会自动的生成一个构造函数,自动生成的构造函数函数体为空且没有参数。但你自己显示的写了任何构造函数时, 编译器就不会自动生成了!
5.编译器在调用构造函数的时候,会根据构造函数参数自动匹配相应的构造函数。
6.在创建对象的时候,没有提供实际参数,编译器会自动查找Test::Test()
#include<iostream>
using namespace std;
class Test
{
private:
int m_a = 1024; //C++11新标准
int m_b = 1024;
int *m_p;
public:
//构造函数(自己写了构造函数,编译器就不会自动生成了)
Test(int a,int b=2)
{
cout<< "有参-构造函数" <<endl;
m_a = a;
m_b = b;
m_p = (int *)malloc(4);
*m_p = 1024;
}
//重载的构造函数
Test()
{
cout<< "无参-构造函数" <<endl;
m_a = 100;
m_b = 100;
m_p = (int *)malloc(4);
*m_p = 1024;
}
void show()
{
cout << "m_a:" << m_a <<endl;
cout << "m_b:" << m_b << endl;
cout << "m_p:" << *m_p <<endl;
}
};
int main()
{
//编译器在调用构造函数的时候,会根据构造函数参数自动匹配相应的构造函数
//在创建对象的时候,没有提供实际参数,编译器会自动查找Test::Test()
Test t; //调用无参构造函数,实例化一个Test对象
t.show();
//t.Test(10,11); 对象一旦创建,就不能手动调用构造函数
//创建对象的时候,同时指定初始化参数(调用有参构造函数),形式:
//类名 对象名(实际参数列表)
Test t1(12,13); //调用有参构造函数(Test::Test(int,int)),实例化一个Test对象
t1.show();
//Test t2(); //这不是实例化对象,而是声明了一个函数
//t2.show();
//为了解决这个问题,C++11新标准中引入了一种新的初始化方式,就是在初始化的时候统一使用大括号
//如:
Test t3{}; //调用无参构造函数
Test t4{100}; //调用Test::Test(int)
Test t5{100,101}; //调用Test::Test(int,int)
//Test t6{100,101,102};//调用Test::Test(int,int,int)
return 0;
}
Test t2();
//这不是实例化对象,而是声明了一个函数
t2.show();
为了解决这个问题,C++11新标准中引入了一种新的初始化方式,就是在初始化的时候统一使用大括号,如:
Test t3{};
//调用无参构造函数
Test t5{100,101};
//调用Test::Test(int,int)
不要因为构造函数的名称而认为构造函数负责为对象分配空间,构造函数在执行的时候,对象的内存空间就已经分配好了,构造函数的作用只是给成员变量赋值,初始化资源。
2 析构函数(de-structor)
C++中如何清理需要销毁的对象呢?一般而言,需要销毁的对象都需要进行清理工作(释放资源),如:关闭文件,释放动态内存分配的空间…
解决方案:为每一个类提供一个公有的free函数,对象不需要的时候,立即调用free函数释放资源。
class Test
{
private:
int *m_p;
public:
Test()
{
m_p = (int *)malloc(4)
};
void free()
{
free(m_p);
}
};
存在一个问题,free只是一个普通的函数,必须要显示的调用,对象在销毁前没有调用这个函数,很有可能会造成资源浪费。C++编译器在对象销毁的时候能够自动调用一个特殊的函数进行对象的清理工作。
这个特殊的清理函数就是析构函数,析构函数的作用和构造函数的作用是相反的,析构函数用来释放对象占用的资源。
-
形式: ~类名(){}
-
功能:专门用于对象的清理工作
-
特点:
1.析构函数的名字和类名相似 ~类名
2.析构函数没有返回值类型,也不能写void,也没有参数(不能设置默认值,不能重载,只能有一个析构函数)。
3.在对象销毁的时候自动调用(不提倡使用类对象手动调用)。
4.如果一个类中没有显示的声明析构函数,编译器会自动的生成一个析构函数,自动生成的析构函数函数体为空。
3 构造函数初始化列表
构造函数初始化列表是对构造函数的一种增强,它能够初始化一些特殊成员。
如:常量,引用,调用成员对象指定的构造函数…
在构造函数中,仅仅是对成员变量的赋值,这里严格意义上来说,不是初始化,仅仅是一个赋值计算的过程,当成员变量是常量的时候,常量是不能赋值的,就会报错
- 形式:
构造函数名(参数列表):成员变量名{初始值},成员变量名2{初始值},....
{
构造函数的函数体!
}
-
必须使用构造函数初始化列表的场合:
1)成员变量是常量
2)成员变量是引用(后面讲,在定义的时候必须初始化)
3)成员对象没有无参构造函数,指定成员对象特定的构造函数(指定成员对象的初始化方法)
4)初始化基类成员(指定基类的构造函数)
-
建议:成员变量的初始化尽量都使用构造函数初始化列表的方式
1)代码简洁
2)初始化列表的这种方式效率比普通构造函数高
3)构造函数的声明和定义分开的时候,构造函数初始化列表必须写在定义的地方
class Test
{
public:
Test(int x,int y):m_y(y),m_x(m_y)
{
}
private:
int m_x;
int m_y;
}
Test t{100,101};
t.m_x: 随机值
t.m_y: 101
在构造函数初始化列表中,初始化成员的顺序是按照类中的声明顺序来的,而不是按照出现在初始化列表中的顺序,在上面,虽然m_y出现在前面,但是m_x先初始化。