ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

列表初始化和赋值初始化的使用注意事项

2021-10-20 18:35:14  阅读:200  来源: 互联网

标签:初始化 num1 num2 int 基类 注意事项 赋值 构造函数


成员变量初始化方式有两种:列表初始化和赋值初始化。如下代码。但是这两种初始化表面上看着相同,但是用法和原理却并不相同。本篇博客主要讨论这两种初始化的使用方法和基本原理。

class Test
{
public:
	Test(int a, int b, int c):_a(a)//初始化列表初始化
	{
		_b = b;//赋值初始化
		_c = c;//赋值初始化
	}
private:
	int _a;
	int _b;
	int _c;
};

首先,必须是本类的本身的成员变量或者是基类才允许用列表初始化。也就是说从基类继承的成员变量不允许利用初始化列表进行初始化。

一、类本身包含的变量(非继承而来的变量)和基类才可以使用初始化列表

1.类本身的变量(非继承而来的变量)用赋值初始化或者初始化列表都行

类本身的变量用列表初始化或者赋值初始化都可以,可以掺杂着用,也可以都用赋值初始化也可以都用列表初始化。如下代码:

class Test
{
public:
	Test(int a, int b, int c):_a(a)//初始化列表初始化
	{
		_b = b;//赋值初始化
		_c = c;//赋值初始化
	}
private:
	int _a;
	int _b;
	int _c;
};

二、从基类继承而来的变量的初始化

首先直接向类本身的变量一样利用列表初始化是错误的,如下代码:

class Base 
{
public:
	int _num1;
	int _num2;
};

class Son1 :public Base
{
public:
	Son1(int num1, int num2):_num1(num1),_num2(num2){}
};

编译器会报错,提醒说_num1和_num2既不是类的(本身的)非静态成员变量也不是基类,无法使用列表初始化。
下面看看以赋值初始化的方式对继承而来的变量进行初始化。
(1)用赋值初始化

class Base 
{
public:
	int _num1;
	int _num2;
};

class Son1 :public Base
{
public:
	Son1(int num1, int num2)
	{
		//从基类继承而来的成员变量在函数体内用赋值的方式初始化
		_num1 = num1;
		_num2 = num2;
	}
};

上面的代码是正确的,类本身的成员变量和继承而来的成员变量都可以使用赋值初始化的方式进行初始化。

(2)继承而来的成员变量利用列表初始化和基类的构造函数进行初始化
前面说了,只有本类的本身的非静态成员变量和基类才可以使用列表初始化。本身的非静态成员变量好理解,即在本类的非静态成员变量,非继承而来的非静态成员变量。那么基类用列表初始化怎么理解呢?即当子类继承基类,基类的成员变量也被继承,可以通过基类的构造函数对子类从基类继承而来的变量进行初始化。如下代码:

class Base 
{
public:
	Base(int num1, int num2) 
	{
		_num1 = num1;
		_num2 = num2;
		cout << "Base 的非缺省构造函数" << endl;
	}
	Base()
	{
		cout << "Base 的缺省构造函数" << endl;
	}
public:
	int _num1;
	int _num2;
};

class Son1 :public Base
{
public:
	Son1(int num1, int num2) :Base(num1, num2)
	{
		cout << "Son1的构造函数" << endl;
	}
};

int main()
{
	Son1 so(1, 2);
	cout << so._num1 << "   " << so._num2 << endl;
	return 0;
}

运行结果:
在这里插入图片描述

发现Son1从Base继承而来的成员变量通过列表初始化的Base(num1,num2)调用了一次构造函数,将Son1继承而来的成员变量进行了初始化。
注意: Base的非缺省构造函数在Son1构造函数之前执行。

那么如果将Base(num1,num2)这句代码放到函数体中呢?如下代码:

class Base 
{
public:
	Base(int num1, int num2) 
	{
		_num1 = num1;
		_num2 = num2;
		cout << "Base 的非缺省构造函数" << endl;
	}
	Base()
	{
		cout << "Base 的缺省构造函数" << endl;
	}
public:
	int _num1;
	int _num2;
};

class Son1 :public Base
{
public:
	Son1(int num1, int num2) 
	{
		Base(num1, num2);
		cout << "Son1的构造函数" << endl;
	}
};

int main()
{
	Son1 so(1, 2);
	cout << so._num1 << "   " << so._num2 << endl;
	return 0;
}

运行结果:
在这里插入图片描述

发现Son1创建一个so对象经过了一下三个步骤:
①调用基类的缺省构造函数
②调用Son1类的构造函数
③调用基类的非缺省构造函数
第三步其实是由于在Son1函数体内我们留下了这样一句代码:Base(num1,num2);这句代码就是简单的执行Base的构造函数,没有特别意义,只是创造了一个匿名对象。这也是为什么_num1和_num2依然为随机值的缘故。

将以上两段代码合起来分析一下:
在创建子类对象so之前,一定会先调用基类的构造函数,因为有继承关系,子类对象需要包含从基类继承下来的成员变量,而编译器会先调用基类的构造函数构造一个无名对象,这样基类的成员变量才可以被子类的对象给包含。这一段总结就是说,子类创建对象之前,先创建个基类的无名对象,然后子类再将基类的无名对象的成员变量包含在其中。
当我们在子类后面用列表初始化写这一句代码Base(num1,num2)的时候,子类创建对象时,在进入到子类的构造函数体之前,就先执行了这句代码,创建一个无名对象,且用num1和num2分别初始化了无名对象的成员变量_num1和_num2(注意,子类继承的_num1和_num2这两个变量就是这个时候被定义并且用num1和num2初始化的),然后进入到子类的构造函数的函数体内,创建了子类对象,包含了在这之前调用父类的构造函数创建的无名对象的内容,即继承了基类的这些成员变量。
那么再来看第二段代码,由于在创建子类对象之前,得先创建一个基类的对象,然后子类的对象包含了基类对象的内容,即继承基类的成员变量。由于我们没有写调用基类的哪一个构造函数,那么编译器就会调用基类的默认构造函数,构造出来一个无名对象,之后,子类对象才创建出来,包含这个基类对象,即继承基类的成员变量。
那么对于第二段代码,如果我们将默认构造函数注释掉或者将默认构造函数设置成私有的话,那么编译器将无法调用基类的默认构造函数,那么子类的子对象将无法创建。如下代码:

class Base 
{
public:
	Base(int num1, int num2) 
	{
		_num1 = num1;
		_num2 = num2;
		cout << "Base 的非缺省构造函数" << endl;
	}
	
public:
	int _num1;
	int _num2;
private:
	Base()
	{
		cout << "Base 的缺省构造函数" << endl;
	}
};

class Son1 :public Base
{
public:
	Son1(int num1, int num2) 
	{
		cout << "Son1的构造函数" << endl;
	}
};

int main()
{
	Son1 so(1, 2);
	cout << so._num1 << "   " << so._num2 << endl;
	return 0;
}

运行结果:
在这里插入图片描述
发现编译器报错,说Base::Base无法访问,就是说无法访问Base的默认构造函数,运行到第28行停止的,原因就是子类创建子对象之前,“一定”(注意,是相对于我们这个代码来说是一定)要调用基类的默认构造函数,基类的默认构造函数无法访问,就报错。
上面的“一定”打了引号,是因为可以不让编译器调用基类的默认构造函数,做法就是我们告诉编译器调用基类的哪个构造函数创建基类的对象给子类的对象继承。如下代码:

class Son1 :public Base
{
public:
	Son1(int num1, int num2) :Base(num1,num2)
	{
		cout << "Son1的构造函数" << endl;
	}
};

在这里插入图片描述
红线部分就是列表初始化,可以让编译器知道,我们让它调用基类的Base(int num1,int num2)构造函数来创建无名对象给子类创建的对象继承。这个构造函数里面对_num1和_num2初始化了,所以子类对象将基类创建的无名对象继承过来以后,继承过来的成员变量已经被调用的Base(int,int)构造函数用num1和num2初始化过了。

至于是用列表初始化效率高还是赋值初始化效率高,应该答案已经出来了。用列表初始化,我们可以让基类在构建无名对象产生变量的时候就已经对成员变量进行初始化。而如果用赋值初始化,编译器得先调用基类的默认构造函数,然后再在子类构造函数体内对基类的成员变量进行赋值初始化。
再来看以下代码,类中的东西都只是声明,包括_a,_b,_c,_d这四个变量。由于_b和_c和_d都是常量和引用,由于常量和引用在定义的时候必须要有初始值,而当进入到子类的构造函数内部,这些成员变量都被编译器自动定义了,但是没初始值,编译器会报错,所以需要我们在编译器执行构造函数之前就对这些常量和引用进行定义,那么,就只能使用列表初始化,因为在执行构造函数之前,编译器会先执行初始化列表,而列表初始化就可以看成是定义(当然,只能是对以在类内声明的成员变量的定义),()里面的是给的初始值。

class Test
{
public:
	Test(int num1, int num2, int num3) : _b(num1), _c(num1),_d(_a)
	{
		_a = 10;//可列表初始化,可赋值初始化
	}
public:
	int _a;
	const int _b;//定义的时候必须有初始值,所以必须使用列表初始化
	int& _c;       //定义的时候必须有初始值,所以必须使用列表初始化
	const int& _d;
};

int main()
{
	Test so(1, 2,3);
	cout << so._a << "   " << so._b << "   " << so._c << "   " << so._d << endl;
	return 0;
}

以上代码,列表初始化可以看成这些代码:
const int _b = num1;
int& _c = num1;
const int& _d = _a;
注意这个列表初始化是有问题的,首先是进入到构造函数之前(这时函数参数是已经绑定过确定的)执行的,_b没问题,就是保存num1的值,_c有问题,它是num1的引用,num1是形参,构造函数结束后形参生存期就到了,num1的空间里就是随机值了,所以主函数打印的时候_c是随机值,_d没问题,执行const int& _d = _a;的时候,_a还是随机值,但是执行完构造函数,_a就是10,所以主函数里打印的_d就是10。
运行结果:
在这里插入图片描述

总结:
不同成员变量初始化使用:
(1)类本身的成员变量(非继承而来的成员变量)既可以使用列表初始化又可以使用赋值初始化。
(2)继承而来的成员变量,要么用基类的构造函数和列表初始化告诉编译器调用基类的哪个构造函数,可以在基类的构造函数中对将要被创建的子类对象继承的成员变量进行初始化,要么就编译器默认的调用基类的默认构造函数,在子类的函数体中以赋值初始化的方式对继承的成员变量进行初始化。
必须使用列表初始化的三个情况:
由于列表初始化就是相当于定义,所以那些定义的时候必须需要初始值的,就用列表初始化。
(1)成员变量是const类型
(2)成员变量是引用类型
(3)如果基类没有缺省构造函数(必须使用列表初始化,告诉编译器调用其他基类构造函数)。或者说类本身有一个自定义类型变量,但是没有缺省构造函数(因为自定义类型的变量定义的时候,就是类的实例化,编译器默认调用这个类型的默认构造函数创建这个对象,但是没有缺省构造啊函数的话,就得用列表初始化告诉编译器用其他的构造函数创建这个自定义类型的变量)。

标签:初始化,num1,num2,int,基类,注意事项,赋值,构造函数
来源: https://blog.csdn.net/yi_chengyu/article/details/120869929

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有