C++基础学习笔记----第十课(初始化列表、析构函数)

主要讲解初始化列表的使用方法和注意事项以及析构函数的使用方法,最后解释了在类中直接调用构造函数产生的后果。

可以在以一个类中定义其他的类的对象,但是这个对象不能够调用其他类的成员。因为在另一个类中声明前一个类的时候,内存已经已经分配给了这个类所有内容,这个时候我们再调用前一个类的函数就相当于从一块大的内存中再调用一块小的内存,所以编译出错。

class A
{
public:
	A()
	{
		i = 0;
	}
	int print ()
	{
		printf ("%d",i);
		return 0;
	}
};

class B
{
	A a;

	//a.print(); 编译出错
};
如果我们想要使用a类中的成员函数,那么可以在其他的函数中定义B的对象,然后进行调用。

int main()
{
	B b;
	b.a.print();
	return 0;
}
在上面的程序中,在B类中定义A类的对象编译器不同和其他的约束条件不同有的时候可以定义成功有的时候能够定义成功。

初始化列表

基本使用方法

初始化列表主要作用是对成员变量和成员函数进行初始化。语法规则如下:

Constructor::Constructor:m1(v1),m2(v2,v3),m3(v4,v5,v6)
注: 对成员函数初始化采用的是函数名+(),括号中是传递给构造函数的参数。对成员变量的初始化采用的是变量名+()的形式。

初始化列表的初始化顺序和时机

成员变量的初始化顺序与声明的顺序有关,与在初始化列表中的顺序无关。初始化列表先于构造函数的函数体执行。

#include <stdio.h>
class A
{
private:
	int ai;
public:
	
	A(int i)
	{
		aprintf ("A(int i) %d\n",i);
		ai = i;
	}
	int geti()
	{
		return ai;
	}
};


class B
{
private:
	A a1;
	A a2;
public:
	B():a1(1),a2(2)
	{
		printf("B()\n");
	}
	void print()
	{
		/*在子函数中可以通过A类的对象来调用它的成员函数了,因为这就是又一片其他的内存空间了*/
		printf("a1.ai = %d, a2.ai = %d\n",a1.geti(),a2.geti());
	}

};

void gog()
{
	B b1;
	b1.print();
}

int main()
{
	gog();
	return 0;
}

程序打印如下图:

程序的执行顺序分析如下:程序从main函数开始执行,调用gog函数,在gog函数中程序创建了B类的对象b1,在类B中创建了两个类A的对象,这个时候调用初始参数列表,a1(1),a2(2)。这里开始调用两次类A的构造函数,所以打印两次A(int i)。然后调用类B的构造函数,打印B(),最后在gog()函数 调用b1对象的成员函数b1.print(),打印出来来a1.ai和a2.ai。通过上面的分析可以得出,函数初始化列表的作用时机是在A类的对象在调用的时候,初始化列表向A类的构造函数传递参数。
最后,初始化列表的作用域只是一个类中,不能够初始化其他类中的成员,同时初始化列表的位置一般位于构造函数之后。

类中的const成员变量

在一个类中,不能够给类中的成员变量进行赋值,只能通过构造函数和初始化列表方式进行。所以在类中的const成员也同样不能够被赋初值,编译器无法直接得到const成员变量的初始值,因此无法进入符号表成为真正意义上的常量。根据const的标准,这个时候类中的const成员变量是一个只读变量,编译器会为这个const的只读变量分配空间。

主要的初始化形式如下所示:

private:
    const int c;
    M m1;
    M m2;
public:
    Test() : c(1), m2(3), m1(2)
    {
        printf("Test()\n");
    }
    

初始化与赋值

初始化是用已存在的对象或值对正在创建的对象进行初值设置,赋值是用已存在的对象或值对已经存在的对象进行值设置。在初始化列表里为类的成员提供值叫做初始化,构造函数中为类的成员提供值叫做赋值。初始化:被初始化的对象正在创建。赋值:被赋值的对象已经存在。

析构函数

基本概念

析构函数是一个特殊的成员函数,这个成员函数用来清理对象。定义格式:~ClassName(),析构函数没有参数也没有任何函数返回值类型说明析构函数在对象销毁的时候被自动调用析构函数在一个类中只允许存在一个,析构函数的调用主要取决于对象的销毁。

基本使用方法如下所示:

class A
{
private:
	int ai;
public:
	
	A(int i):ai(i)
	{
		printf ("A(int i) %d\n",i);
	}

	~A()
	{
		printf ("~A()");
	}
};

构造函数和析构函数的调用顺序

当类中有成员变量是其他类的对象时,首先调用成员变量的构造函数(调用顺序与声明顺序相同),之后调用自身类的构造函数(可见带有截图结果的程序)。 析构函数的调用顺序与构造函数的调用顺序相反。

#include <stdio.h>
class A
{
private:
	int ai;
public:
	
	A(int i)
	{
		ai = i;
		printf (" A(int i) %d\n",ai);
	}
	A()
	{
		ai = -1;
		printf (" A()\n %d",ai);
	}
	A(const A& aai)
	{
		printf (" A(const)\n)",aai.ai);
		ai = aai.ai;
	}


	~A()
	{
		printf (" ~A()%d \n",ai);
	}
};

void func(A a)
{
	A u(2);
}

void run()
{
	A t(0);
	func(t);
}

int main()
{
	run();
	return 0;
}

程序打印结果如下图所示:

首先使用A类的对象t,调用类A的构造函数,同时初始化i = 0,所以打印A(int i) 0,然后调用func(A a),这里的函数参数由实参转换为形参进行了一次复制,形式相当于A a = t(0),这个时候调用类A中的拷贝构造函数,打印A(const),最后使用类A中的对象u,调用构造函数A(int i),所以打印A(int i) 2。调用析构函数,这个调用顺序类似于函数栈的返回,我们最后调用的构造函数是A(int i) 2,然后依次调用上两个析构函数。这里一共调用了三次构造函数,所以这里调用三次析构函数。

课后习题

在一个类中是否可以直接调用构造函数,直接调用构造函数会发生什么?

答:在一个类中可以直接调用构造函数,直接调用构造函数将会得到一个临时对象。

#include <stdio.h>

class Test
{
private:
    int mI;
    int mJ;
    const char* mS;
public:
    Test()
    {
        printf("Test()\n");
        
        mI = 0;
        mJ = 0;
    }
    
    Test(const char* s)
    {
        printf("Test(const char* s)\n");
        
        Test();
        
        mS = s;
    }
    
    ~Test()
    {
        printf("~Test()\n");
    }
    
    void print()
    {
        printf("mI = %d, mJ = %d, mS = %s\n", mI, mJ, mS);
    }
};

void run()
{
    Test t = Test("aa"); 
    
    t.print();
}

int main()
{
    run();
  
    return 0;
}
程序打印结果如下所示:


字符指针参数的构造函数中调用了一次构造函数Test(),这个构造函数Test既不是被自动的调用也不是手动调用,所以这个时候将会产生一个临时对象,产生的这个临时对象在调用结束之后马上将被销毁,所以马上调用析构函数~Test(),所以这里的mI和mJ没有被赋值,是垃圾值。在编写程序的时候一定要避免在类中调用构造函数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值