C++类成员冒号初始化以及构造函数内赋值

2023-05-16

通常我们对类成员进行“初始化”有两种方式:

1. 构造函数后面跟冒号;

2. 构造函数里面对成员进行赋值。

有些人不太注意这个小细节,或者根本不知道他们的区别,认为两种方式是一样的。这个误解有时可能会对程序带来影响,这里我来介绍一下这两种方式。

首先我们看这么一段代码:

class A
{
public:
	A(int& c)
	{
		_a = 1;
	}
protected:
	int _a;
	const int _b;
	int& _c;
};

这段代码正确吗?答案是否定,这段代码无法通过编译。我们会看到下面的编译错误

1>d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_b' : must be initialized in constructor base/member initializer list
1>        d:\study\myconsole\myconsole\myconsole.cpp(20) : see declaration of 'A::_b'
1>d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_c' : must be initialized in constructor base/member initializer list
1>        d:\study\myconsole\myconsole\myconsole.cpp(21) : see declaration of 'A::_c'
意思是说成员_b和_c必须在构造函数的成员初始化列表里面初始化。那么_a为什么没有报错呢?看看成员的声明,我们看到_a是一个int类型,_b是一个const int类型,_c是一个int&类型。根据C++的规则,const类型和引用不可以被赋值,只能被初始化。这里我们先花一点点时间来看一下const类型和引用。

大家看看下面的这段代码是否正确:

int _tmain(int argc, _TCHAR* argv[])
{
	int a;
	const int b;
	int& c;

	return 0;
}

编译一下就会看到这2个错误:
1>d:\study\myconsole\myconsole\myconsole.cpp(30) : error C2734: 'b' : const object must be initialized if not extern
1>d:\study\myconsole\myconsole\myconsole.cpp(31) : error C2530: 'c' : references must be initialized

 哦,原来const和引用必须在声明的时候就初始化(其实就是因为const和引用不可以在变量创建完成后再被赋值,所以编译器做了这个限制)。ok,把代码改一下:

int _tmain(int argc, _TCHAR* argv[])
{
	int a;
	const int b=5;
	int& c = a;

	return 0;
}


现在 编译就没有问题了。细心的朋友会发现我这里在b和c声明代码那里使用的=号而不是(),其实我们也可以这么做:

int _tmain(int argc, _TCHAR* argv[])
{
	int a;
	const int b(5);
	int& c(a);

	return 0;
}

在这种情况下用括号和等于号初始化,效果是一样的。具体就不细讲了。

OK,我们举这个小例子的目的就是想加深大家对const和引用的印象:const和引用必须在声明的时候就初始化,换句话说就是在给const和引用类型变量分配内存的时候就初始化。

好了,现在我们回到class A的问题,类A里面有const成员和引用成员,当系统要给类A的对象分配内存的时候,系统需要给A的对象的3个成员_a, _b, _c分配内存。_a没有问题,系统直接给它一块内存。_b和_c就出问题了,分配内存的时候没有初始化。所以编译就出问题了。其实C++给类成员初始化的唯一方式就是成员初始化列表,也就是构造函数后面跟冒号的那种形式。将class A的代码调整一下:

class A
{
public:
	A(int& c): _b(2), _c(c)
	{
		_a = 1;
	}
protected:
	int _a;
	const int _b;
	int& _c;
};




int _tmain(int argc, _TCHAR* argv[])
{
	int number = 3;
	A a(number);

	return 0;
}

我们在A的构造函数的后面用冒号来初始化_b和_c。现在可以通过编译了。因为系统可以在给_b和_c分配内存的时候就初始化了。那么假如我们把代码改成下面的形式:

class A
{
public:
	A(int& c)
	{
		_a = 1;
		_b = 2;
		_c = c;
	}
protected:
	int _a;
	const int _b;
	int& _c;

};


这样 能行吗?编译一下就得到下面的错误:

1>d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_b' : must be initialized in constructor base/member initializer list
1>        d:\study\myconsole\myconsole\myconsole.cpp(22) : see declaration of 'A::_b'
1>d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_c' : must be initialized in constructor base/member initializer list
1>        d:\study\myconsole\myconsole\myconsole.cpp(23) : see declaration of 'A::_c'
1>d:\study\myconsole\myconsole\myconsole.cpp(17) : error C2166: l-value specifies const object

这3个错误包含2个意思:

1. const和引用变量没有初始化;

2. 不可以对const变量_b进行赋值,也可以说const变量不可以当作左值(error C2166: l-value specifies const object)。

现在我们就可以知道了,其实在构造函数里面调用等于号并不是真正意义上的“初始化”。这个过程相当于:

1. 系统创建成员变量;

2. 创建完后再进行赋值操作。

而在构造函数后面跟冒号,就相当于:

1. 系统创建成员变量并且初始化。也就是系统为成员变量分配了一块内存并且把相应的数据给填了进去。而构造函数里面调用等于号的方式是分配好后再进行赋值,多了一个步骤。

 下面我们再来做一个实验:

class A
{
public:
	A(int& c): _b(2), _c(c)
	{
		_a = 1;
	}
protected:
	int _a;
	const int _b;
	int& _c;

};

class B
{
public:
	B(int& c):_objA(c)
	{
		printf("B constructor\n");
	}

protected:
	A _objA;
};



int _tmain(int argc, _TCHAR* argv[])
{
	int number = 3;
	B obj2(number);

	return 0;
}

类B里面有个一个类A的对象,在类B的构造函数里面用冒号来初始化成员_objA。那么_objA是什么时候被初始化的呢?有图有真相:


 

 从callstack里面可以清楚的看到:

1. 进入B的构造函数;

2. 进入A的构造函数。

也就是说冒号后面的代码是在一进入构造函数的时候就被调用了。

然后从左下角的Watch里面也可以看到,在系统调用构造函数括号里面的第一行代码之前,_a,_b, _c就已经分配好了。我们可以看到_a是个没有初始化过的值(系统自己生成了一个),_b和_c都是我们初始化的。那么我可以得出一个结论:

构造函数后面跟的冒号代码是在进入构造函数并且在括号里面的第一行代码之前被执行。

 

假如在B的构造函数里面不显式初始化_objA,会发生什么事呢?用代码模拟一下就知道了,系统会调用A的默认构造函数来初始化_objA。

 

好了,讲完了。通俗的讲,构造函数后面的冒号就是初始化,而括号里面的等于号并不是初始化,而是变量生成以后的赋值而已(永远都是2个步骤)。

 

附:

本文前面我提到一句话:const和引用不可以被赋值,只能被初始化。可能会有些朋友对这句话有意见,看下面的代码,这段代码是正确的,没有问题。那么怎么说不能被赋值呢?其实b=12只是把a的内容给改掉了(a和b的值都是12),而不是把引用b指向另外一个变量。换句话说:引用b初始化完成后,就永远指向初始化时候的那个变量,无法再改变了。我这里的“引用不可以被赋值”是指不能给引用本身赋值来改变它的指向,并不是说不可以改变引用指向的内存的内容。可能言语上面会有不同的理解,但是只要知道是这么回事情就可以了。

int _tmain(int argc, _TCHAR* argv[])
{
	int a = 1;
	int& b = a;
	b = 12;

	return 0;
}


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++类成员冒号初始化以及构造函数内赋值 的相关文章

随机推荐

  • 关于单应性矩阵的理解:Homography matrix for dummies

    尽量写的通俗一点 xff0c 因为从某种程度上讲 xff0c 本人也是dummy 1 先说homogeneous coordinate xff0c 齐次坐标 一幅2D图像上的非齐次坐标为 x y xff0c 而齐次坐标为 x y 1 xff
  • 关于RANSAC的理解

    先说最小二乘 ok xff0c 你手头有一堆数据 xff0c 比如这些蓝点 xff1a 那么我们假设它符合一个直线模型 xff1a y 61 ax 43 b xff0c 用最小二乘就可以很容易求解出未知参数a和b 最小二乘大法确实好哇 xf
  • Visual Studio中监视数组

    比如有一个double h 9 xff0c 如果选择监视 xff0c 那么就只会监视h 0 xff0c 如果想监视其他元素 xff0c 难道只能h 1 h 2 一个个的添加吗 xff1f 当然不需要 xff0c 在监视中输入h 9就可以了
  • 在编译PX4之前,你需要知道的几件事

    1 在git上clone代码 xff0c 必须是clone xff0c 因为编译时需要有 git文件夹 如果你看Makefile就会发现有这么一行 xff1a Enforce the presence of the GIT reposito
  • 马氏距离与卡方分布

    最近在看 Fundamentals of object tracking xff0c 看到最近邻滤波时 xff0c 碰到了题中的两个概念 以下内容基本来自wiki xff0c 读者有不懂的地方看wiki更清晰明了 1 马氏距离 Mahala
  • PX4中的mavlink

    简介 px4与地面站的通信协议是mavlink xff0c 对于其消息格式的介绍看这里和这里 需要注意几点 xff1a 不光是px4与qgroundcontrol通信通过mavlink xff0c 有一些sensor也支持mavlink m
  • STM32F1Debug,定时器时基初始化参数

    STM32F1 xff0c 定时器时基初始化参数 错误代码 xff1a 在初始化时基时 xff0c 没有给TIM ClockDivision和TIM RepetitionCounter赋值 错误代码 xff1a span class tok
  • dockerfile详解

    前言 各位想必应该记得 xff0c 我们此前如果安装一个nginx的话 xff0c 安装完以后 xff0c 我们说过很多次了 xff0c 通常不会运行在默认配置下 xff0c 那因此 xff0c 我们通常需要去改一改它的配置文件或者定义模块
  • docker hub + github action x持续集成CI/CD

    docker 43 github 持续集成CI CD docker 持续集成 参考官网 xff1a https docs docker com ci cd best practices 的大部分内容 2020 Jetbrains devel
  • 利用爬虫获取免费IP代理

    项目目标 通过爬虫获取 西拉代理 xff08 http www xiladaili com xff09 上的高匿代理 xff0c 并储存至一个列表 项目分析 首先对网页进行观察 xff0c 主体内容如下图所示 不但指明了代理IP 协议类型
  • DOCKER windows 7 详细安装教程

    Edit DOCKER windows安装 编者 xff1a xiaym 日期 xff1a 2015年1月20日 排版工具 xff1a 马克飞象 QQ 252536711 DOCKER windows安装 1 下载程序包2 设置环境变量3
  • 职场里不能与之结为团队的十种人

    俗话说 xff1a 女怕嫁错郎 xff0c 男怕入错行 同样 xff0c 一个人进入职场最怕的就是遇上了自己无法与其默契的某些团队成员 xff0c 这会影响到自己的事业进取 xff0c 影响到自己努力奋斗的成果收获 xff0c 影响到自己做
  • python读取大疆P1相机POS

    大疆P1相机读取POS xff0c 算法不是很好 xff0c 但是可以用 未来有好的算法再贡献 import os import os path import exifread workspace 61 r 39 G 20210727 39
  • Java数据结构之Lambda表达式

    目录 1 背景1 1 Lambda表达式的语法1 2 函数式接口 2 Lambda表达式的基本使用3 变量捕获3 1 匿名内部类的变量捕获3 2 Lambda的变量捕获 4 Lambda在集合当中的使用4 1 Collection接口4 2
  • docker学习笔记

    一 docker简介 xff1a 1 是什么 xff1a xff08 1 xff09 为什么会有docker出现 xff0c 将解决什么样的问题 xff1a 当我们在开发一个项目的时候 xff0c 假如您自己的电脑有您自己的开发环境 xff
  • iserver配置https加密通信

    1 升级iserver为https访问 xff1a iserver是部署在tomcat中 xff0c 所以只要配置tomcat的相关配置就可以 xff1a xff08 1 xff09 https访问需要用到证书 xff0c 因此需要准备相关
  • ZeroMQ发布订阅模式之多进程实现

    ZeroMQ的发布订阅模式是单向的数据发布 xff0c 服务器 xff08 即消息发布方 xff09 将更新的消息 事件推送到一组客户端 xff08 即订阅方 xff09 消息发布者创建ZMQ PUB类型的socket并将消息发送到消息队列
  • java面试清单和书籍推荐 五颗星五颗星

    前言 面试必备技能清单 xff0c 这里不会详细论述 xff0c 更多的是清单列举 xff0c 罗列一些关键字和链接注释 数据结构与算法 排序算法 选择排序冒泡排序插入排序快速排序快速排序 xff08 普通 xff0c 二路 xff0c 三
  • ssh Key exchange was not finished sshd

    报错 xff1a ssh Key exchange was not finished 则需修改sshd文件 链接 xff1a Key exchange was not finished connection is closed近期遇到这个错
  • C++类成员冒号初始化以及构造函数内赋值

    通常我们对类成员进行 初始化 有两种方式 xff1a 1 构造函数后面跟冒号 xff1b 2 构造函数里面对成员进行赋值 有些人不太注意这个小细节 xff0c 或者根本不知道他们的区别 xff0c 认为两种方式是一样的 这个误解有时可能会对