C++面向对象(五)组合类与构造函数的初始化列表、指向成员的指针和函数指针调用的效率

一、本文基础概念与结论:
面向对象:
程序 = 对象+对象+对象+…+对象之间消息通信
是什么样的对象之间可以通信呢:
对象A和对象B他们所属的类和类之间要有关系。
类之间关系基本分类:
组合(聚合):类A是类B的一部分,类A定义的对象作为了类B的成员变量;
继承:类A是类B的一种,类A继承自类B。
构造一个对象编译器做了哪些事情:
1.根据对象的类型,系统给对象分配内存;
2.构造当前对象的成员对象;
3.根据对象的定义方式,调用相应的构造函数。
初始化列表用在哪里:
构造函数中,包括构造函数和拷贝构造函数中。

二、聚合关系与构造函数的初始化列表:

//这里实现一个date日期类 和 stu学生类,其中date实例的对象作为stu的成员变量
//成员对象的初始化 在 stu对象的初始化 之前,所以我们不能在stu构造函数指定date对象初始化方式
//所以我们在 stu 的初始化列表中进行初始化

//日期类
class date
{
public:
	date(int y,int m,int d)
	{
		year = y;
		month = m;
		day = d;
	}
	void show()
	{
		cout << year << " " << month << " " << day << " " << endl;
	}
private:
	int year;
	int month;
	int day;
};

//学生类
class stu{

public:
	stu(char *n,  int a,  float s ,int y, int m, int d) 
		:age(a), score(s), date(y,m,d)    //构造函数的初始化列表: 在这里进行成员对象 的 初始化
	{
		strcpy(name,n);
		//age = a;
		//score = s;
		//date(y,m,n); 不能在这里初始化,因为成员对象的初始化在stu对象初始化之前。
	}
	void show()
	{
		cout << "姓名:" << name;
		cout << "年龄:" << age;
		cout << "成绩:" << score<<" ";
		date.show();      //调用成员对象 date 的show()方法
	}
private:
	char name[20];
	int age;
	float score;
	date date;   //成员对象
};

简单的测试一下:

int main()
{
	stu s("king",10,100,1997,9,9);
	s.show();
	return 0;
}

执行结果:
姓名:king年龄:10成绩:100 1997 9 9
请按任意键继续. . .

三、构造函数的初始列表使用的注意事项:
1.只能用在构造函数中,当然包括拷贝构造函数;
2.初始化列表的先后顺序与成员变量的定义先后顺序有关,与在初始化列表出现的先后顺序无关。

//ex: 一个错误理解
class test
{
public:
	test(int data = 100) 
	:mb(data), ma(mb)  //依然是先用mb初始ma,再用data初始化mb,因为ma先定义
	{
		//
	}
	void show()
	{
		cout << "ma: " << ma << endl;
		cout << "mb: " << mb << endl;
	}
private:
	int ma;
	int mb;
};

1.ma先定义,所以初始化列表中肯定会先初始化ma,
此时mb还没右初始化,所以mb的值可能是0xcccccccc,一些无效值;
2.再用data初始化mb

验证一下:

int main()
{
	test t1 = test();
	t1.show();
	return 0;
}
执行结果:
ma: -858993460
mb: 100
请按任意键继续. . .

3.在初始化列表中和构造函数区别:
构造函数中属于赋值操作;
初始化列表中属于初始化的过程,对于成员对象来说,用初始化的方式构造成员变量和赋值的方式得到的我们想要的对象,效率上初始化的效率更高。
并且如果我们进行赋值的方式,前提是要有默认的构造函数,否则会出错。

4.常量和引用必须放在构造函数的初始化列表中:
c++中常量必须初始化;
引用必须初始化;

//情景1 : 成员变量  有  引用
class test
{
public:
	test(int data = 100) :mb(data), ma(data)
	{
		//
	}
	void show()
	{
		cout << "ma: " << ma << endl;
		cout << "mb: " << mb << endl;

	}
private:
	int &ma;   
	//ma保存的是构造函数data的值,是构造函数栈帧上的数值,构造函数执行完成栈帧将被回退
	int mb;
};
int main()
{
	test t1 = test();
	t1.show();
	return 0;
}

执行结果:
ma: 18893946      
mb: 100
请按任意键继续. . .

引用ma保存的内存data  对应的函数栈帧被回退,
所以在show函数中访问ma内存会访问无效值,可以认为show函数将构造函数的栈帧覆盖了。

使用引用注意的第二个点:成员变量有引用,将不产生默认的赋值运算符重载函数

int main()
{
	test t1 = test();
	t1.show();
	test t2;
	t2 = t1;     //赋值
	t2.show();
	return 0;
}
执行结果:
error C2582: “operator =”函数在“test”中不可用
说明:
在这种情况下,编译器不能产生默认的赋值运算符重载函数;
原因:
因为引用一经使用就不能被其他引用
	test t2;   //t2对象已经引用了一块内存
	t2 = t1;  //这里相当于用t2的ma引用再去 引用其他内存的引用 ,
	            //引用一旦引用一块内存,就不能去引用别的内存,所以会出错

既然编译器不能给我们默认生成赋值运算符重载函数,我自定义实现一个发现编译通过了:
	void operator=(const test &src)
	{
		ma = src.ma;
		mb = src.mb;
	}
这是为什么:
这是因为我们在使用引用的时候,访问的是对应地址中的数据,而不是地址。所以实际上是内存中的值进行了赋值。
执行结果:
ma: 13132090
mb: 100
ma: 13132120
mb: 100
请按任意键继续. . .

回顾下引用:引用保存的是数据的地址,但是访问引用时自动解引用使用内存中的值。

成员变量中有常量的情况:

class test
{
public:
	test(int data = 100) :mb(data), ma(data)
	{
		//我们不能在这里对ma赋值,一定要在初始化列表进行初始化
		//因为ma是常量
	}
	void show()
	{
		cout << "ma: " << ma << endl;
		cout << "mb: " << mb << endl;

	}
private:
	const int ma;
	int mb;
};

四、指向成员的指针

class test
{
public:
	test(int data = 100) : ma(data)
	{
		//
	}
	void show()
	{
		cout << "test::show(): " << ma << endl;
	}

	int ma;
};

void _cdecl show()
{
	cout << "global::show() " << endl;
}
int main()
{
	test t1;
	test *t2 = new test;
	//定义全局函数的函数指针
	void(*pf)() = show;
	pf();


	//定义test类中的函数指针
	void(test::*ppf)() = &test::show;
	//调用的时候依赖对象调用
	//1.依赖对象调用      .*
	(t1.*ppf)();
	//2.依赖指向对象的指针  ->*
	(t2->*ppf)(); 
	delete t2;
	return 0;
}

指针调用函数和对象调用的函数的区别:
1.类中实现的函数被编译器定义为内联函数,而内联函数在编译的时候会在函数调用点自动展开,通过对象调用的函数效率高;
2.使用指针调用函数,编译的时候不知道调用哪个函数,因为指针保存的是函数的入口地址,不能被编译器的处理成内联函数,效率较低。

猜你喜欢

转载自blog.csdn.net/KingOfMyHeart/article/details/89915258