1.构造函数的引出
一个实实在在的对象,应该有合法的属性,而不是在对象出来之后,再去设置属性,要实现这个过程,必须在对象出来的这一时刻初始化合法的值,而且不能由程序员调用,要给属性合法的值,必须通过成员函数初始化,那么这个成员函数就应该比较特殊,特殊在能在对象出来的时刻自动调用,这个成员函数的作用是不但要给当前的对象开辟空间,而且还要给这个对象一个合法的值,它的功能就是把这个对象构造出来,所以我们把这样的函数称为构造函数。
【注意】
如果一个类中没有写构造函数,那么这个类中也有构造函数,因为如果没有构造函数的话数据成员就没有空间,只是这个构造函数没有合法的值,只分配空间。
2.构造函数的定义
构造函数是特殊的公有成员函数(在特殊用途中构造函数的访问限定可以定义成私有保护)。
数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次,称为构造函数。
3.默认构造函数
如果程序员没有写构造函数,则系统会提供一个默认的构造函数,默认构造函数的函数名和类名相同,无参,只是给里面的数据成员分配空间;如果程序员写了构造函数,则系统不会再提供默认构造函数。
4.构造函数的基本特点
函数名和类名相同,无返回类型,可以带参(可以重载),在对象出来的那一时刻自动调用。
主要代码:
class Table //定义一个桌子类
{
public:
// Table() { cout << "Table" << endl; }
Table(int l = 120, int w = 50, int h = 80) //带默认值的构造函数,省去了写好几个构造函数
{
m_length = l; //桌子长度
m_width = w; //桌子宽度
m_height = h; //桌子宽度
}
void Show()
{
cout << m_length << " " << m_width << " " << m_height << endl;
}
private:
int m_length;
int m_width;
int m_height;
};
void main()
{
Table t; //定义对象t,要自动调用构造函数,如果无参,不能写()
//Table b(); 错误,定义对象时如果无参不能写(),因为如果写了(),就相当于声明了一个函数叫b,返回值为Table
t.Show();
Table t1(60, 40);
t1.Show();
Table t2(120, 60, 40);
t2.Show();
}
运行结果:
5.构造函数的作用
(1)创建对象
(2)初始化对象中的数据成员
(3)类型转换
6.构造函数作为类型转换
构造函数除了能构造对象,还能作为类型转换,将一个基本数据类型转换为对象,直接赋值给对象。但是当前构造函数必须带有基本数据类型的参数,才能做相应的转换,也就是说把一个基本数据类型的数据赋给一个类型,相应构造函数必须要有相应的基本数据类型作为参数。
class A
{
public:
A() { cout << "A" << endl; } //一个无参的构造函数,写了无参的构造函数,其他构造函数就不可以带默认值,也就是说不能再有带默认值的构造函数
explicit A(int i) //explicit是明确的,确定的意思,加上它之后说明当前的构造函数只能作为构造函数去创建对象
{
m_i = i;
cout << "A(int)" << endl;
}
private:
int m_i;
};
void main()
{
//A a = 10; //要这么写的话,就不能写explicit关键字
/*把一个整型赋给一个类型,相应构造函数必须要有整型作为参数,
它相当于A b = A(10);其实是生成一个无名对象A ,用无名对象去构造当前的a,
编译器会自动优化就可以直接写为A b = 10;*/
A a; //调用无参的构造函数
A b(10); //调用带参数的构造函数
}
【注意】
①如果写了一个无参的构造函数,那么不能在写带有默认值的构造函数。
②explicit是明确的,确定的意思,在构造函数的前面加上它,就说明当前的构造函数只能作为构造函数去创建对象。
③把一个整型赋给一个类型,相应构造函数必须要有整型作为参数
7.构造函数的特征
(1)函数名与类名相同
(2)构造函数无函数返回类型说明。注意是没有函数返回类型而不是void,即什么也不写,不可以写void。实际上构造函数有返回值,返回的就是构造函数所创建的对象。
(3)在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。
(4)构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。
(5)构造函数可以在类中定义,也可以在类中声明,在类外定义。
(6)如果类说明中,没有给出构造函数,则C++编译器自动给出一个缺省的构造函数:类名(void){ }
但是只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个。
8.构造函数的执行顺序
(1)遇到对象,自动调用当前类的构造函数
(2)传参
(3)根据数据成员在类里面的声明顺序,用冒号语法后面的值进行初始化
冒号语法就是成员初始化列表,成员初始化列表只能在构造函数里面写。
(4)进入构造函数函数体,执行其他操作
class A
{
public:
A(int i = 0):m_i(i) { cout << "A" << m_i << endl; }
private:
int m_i;
};
class B
{
public:
B(int j = 10):a(j)
{
cout << "B" << j << endl;
}
private:
A a;
};
void main()
{
B b(20);
}
【运行结果】
【说明】
从当前的主函数开始,第一步遇到一个对象b,要自动调用当前B类的构造函数;第二步传参,把20传给了j;第三步,根据数据成员在类里面的声明顺序,数据成员只有a,用冒号语法后面的值进行初始化,用j去初始化当前的数据成员a,发现数据成员a是一个对象,然后调用A类的构造函数,把j传给了i,再根据数据成员在类里面的声明顺序,数据成员只有m_i,用冒号语法后面i的值去给m_i去初始化,m_i就初始化完了,m_i初始化为20;第四步,进入到A类的构造函数函数体执行代码,输出A20,此时给A的构造函数就结束了,就去执行B的构造函数函数体,执行代码,输出B20。
class A
{
public:
A(int i = 0, int j = 0) :m_j(j), m_i(m_j)
{
cout << m_i << " " << m_j << endl;
}
private:
int m_i;
int m_j;
};
void main()
{
A a(10, 30);
}
【运行结果】
【说明】
从当前的主函数开始,第一步遇到一个对象a,自动调用当前构造函数A;第二步传参,把10传给了i,把30传给了j,第三步,根据数据成员在类里面的声明顺序m_i,m_j,依次给m_i,m_j初始化,用冒号语法后面的m_j给m_i初始化,因为m_j没有值是随机值,所以m_i初始化为随机值,再用冒号语法后面的j给m_j初始化,m_j初始化为30;第四步,进入到构造函数A的函数体执行代码,输出m_i,m_j的值。
9.析构函数的引出
我们在构造类的时候要给类里面的数据成员开辟内存空间,当对象的生命周期结束之后要消失,这个空间就需要被释放掉,在释放内存空间的时候就要用到析构函数。
10.析构函数的定义
当定义一个对象时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象进行善后工作,这个特殊的成员函数就叫做析构函数。
11.析构函数的特征
(1)析构函数名与类名相同,但在前面加上字符’~',如~CGoods();
。
(2)析构函数无函数返回类型,在这方面与构造函数是一样的。但是析构函数不带任何参数。
(3)一个类有一个且只有一个析构函数,这与构造函数不同。
(4)对象注销时,系统自动调用析构函数。
(5)如果类说明中没有给出析构函数,则C++编译器会自动给出一个缺省的析构函数。
class A
{
public:
A(int i = 0) :m_i(i) { cout << "A" << m_i << endl; }
~A() { cout << "~A" << m_i << endl; }
private:
int m_i;
};
void fn()
{
A c(40);
}
void test()
{
static A d(50);
}
void main()
{
A a(20);
A b(30);
fn();
test();
test();
test();
}
【运行结果】
【分析】
(1)先构造的后析构。
(2)在主函数结束之前,当作用域即将要消失的时候调用析构函数。
(3)fn()函数中的对象c,c的作用域是在进入fn()函数的时候构造,出了fn函数c的作用域就要消失,就要调用析构函数。
(4)test()函数中的d是一个静态变量,在第一次遇到静态变量的时候给它开辟空间,后面不再开辟空间,出了test()函数之后它的空间不消失,但是它的空间外界不能用,它空间里面的值就一直保存着。所以主函数中第二次遇到test()函数的时候就不再构造了,因为它的空间并没有消失,不需要重新开辟空间。静态空间的作用域在程序都执行完之后才消失。