初始化列表 / 隐式转换 / 静态

news2024/11/17 11:23:09

目录

  • 初始化列表
  • 隐式转换
    • 单参数的隐式类型转换
    • 多参数的隐式类型转换
    • explicit关键字
  • static

初始化列表

大部分时候成员变量在对象实例化的时候调用构造函数就整体定义了,注意此时只有定义,不算初始化。而定义后的值的值是在构造函数里面给的。我们知道构造函数可以执行很多语句,可以给成员变量多次赋值。此时就不能说在构造函数内部初始化了。
但是有些成员必须在定义的时候初始化。如果定义一个const成员变量,但是const成员变量规定不能在构造函数里面给他初始化而且const就只有一次初始化的机会。此时就可以在初始化列表给他初始化。
初始化列表的写法:

class Date
{
public:
	Date(int y)
		:_year(1)
		,_month(2)
		,_day(3)//初始化列表初始化的值
	{
		_year = y;
		//_day = 100;const无法在构造函数中改变
		cout << "Date(int y)" << endl;
	}
	Date(const Date& d)//拷贝构造也可以有初始化列表
		:_year(1)
		, _month(2)
		, _day(3)
	{
		cout << "Date(Date& d)" << endl;
	}
private:
	int _year;
	int _month;
	const int _day;
};

在构造函数下面写初始化列表,来拷贝构造和构造函数都可以有初始化列表

祖师爷为了区分初始化和定义就设计了初始化列表,在初始化列表中初始化我们的成员变量。我们每一次定义对象执行构造函数时,会先走初始化列表,即使我们没写也会走初始化列表。 初始化列表给了初始值,如果没有初始值,在C++11中的打了补丁,给了缺省值,即在类声明的时候给的值,其实就是初始化时候要给的值,如果没有缺省值,那初始化就给默认值。

private:
	int _year = 10;
	int _month = 20;
	const int _day = 30;

如上,如果没有给初始化列表,初始值就是这些缺省值

建议能用初始化列表就用初始化列表,因为初始化列表是必走的。

注意:

  1. const必须走初始化列表,因为const属性必须给予初始值, 必须使用初始化列表,这里说使用初始化列表是,必须给一个缺省值或者在初始化列表给一个值
  2. 引用必须走初始化列表,因为引用只能是一个对象的引用,一个对象可以有多个引用,但一个引用只能对应一个引用对象。从这个角度看,引用就像是一个常量一样。
    引用既可以给缺省值引用也可以在初始化列表进行引用
private:
	int _year;
	int _month;
	const int _day;
	int& yy = _month;

自定义类型也会走初始化列表,然后去调用自己的默认构造,如果他没有默认构造就会报错,这一点在之前写构造函数时提到过。但是有了初始化列表,我们在初始化列表里面给自定义类型参数,此时就不会报错。此时相当于直接定义然后调用了构造。

如下,如果我们不写初始化列表里面的a(1,2),编译器就会报错,因为类A没有默认构造函数。但是如果在初始化列表里面写了的话,就相当于定义的对象,并且给了参数。然后走他的构造函数。

class A
{
public:
	A(int a, int b)
		:x(a+10)
	{
		cout << "A(int a, int b)" << endl;
		cout << x << endl;
	}
private:
	int x;
};

class Date
{
public:
	Date(int y)
		:_year(1)
		,_month(2)
		,_day(3)
		, yy(_month)
		,a(1,2)
	{
		_year = y;
		cout << "Date(int y)" << endl;
		cout << _day << endl;
	}
	Date(const Date& d)//拷贝构造也可以有初始化列表
		:_year(1)
		, _month(2)
		, _day(3)
		, yy(_month)
		, a(1, 2)
	{
		cout << "Date(Date& d)" << endl;
	}
private:
	int _year;
	int _month;
	const int _day;
	int& yy = _month;
	A a;
};

int main()
{
	Date d1(1);
	return 0;
}

上述代码执行结果如下:
在这里插入图片描述
初始化列表主要解决的就是这三类问题:
引用成员变量,const成员变量,自定义类型成员(且没有默认构造函数)。就是那些不能在函数体内定义的成员变量。
其他的成员,可以在初始化列表,也可以在构造函数内初始化处理,但是建议在初始化列表里面处理。

另一个注意的点是:
初始化列表按照声明顺序初始化

class Date
{
public:
	Date(int y)
		:_month(2) 
		,_year(_month)
	{
		cout << _year << endl;
		cout << _month << endl;
	}
private:
	int _year;
	int _month;
};

int main()
{
	Date d1(1);
	return 0;
}

执行结果是
在这里插入图片描述
可以发现初始化列表先写了month,再写的year,但是year并没有使用month的值初始化自己。调试的时候也是执行的year行再执行的month行。

隐式转换

单参数的隐式类型转换

看下面代码理解隐式转换:

class Date
{
public:
	Date(int y)
	{
		cout << y << endl;
	}
private:
	int _year;
	int _month;
};

int main()
{
	Date d1(11);
	Date d2 = 12;
	return 0;
}

执行结果为
在这里插入图片描述
可以发现代码中Date d2 = 12;,是将一个整形赋值给了一个类类型。这样的写法就是隐式类型转换。但是前提是单参数构造函数,只有单参数构造函数才支持隐式类型转换

转换原理: 整型12作为参数构造了一个临时对象,然后d2再通过临时对象进行拷贝构造,但是编译器会进行优化,同一个表达式的连续步骤的构造,一般会合二为一,此时不会去执行拷贝构造,如果上述代码写了拷贝构造也不会执行的。
但是当我真正去使用的时候会发现,编译器直接调用了构造函数,可以直接理解为将等号右边直接进行了传参。
比如改为下面代码

class Date
{
public:
	Date(int y)
	{
		cout << y << endl;
		cout << this << endl;
	}
	Date(Date& d)
	{
		d._month = 100;
	}
	void operator=(Date d)
	{
		cout << "void operator=(Date d)" << endl;
	}
	Date* Print()
	{
		cout << "Date* Print()" << endl;
		cout << this << endl;
		return this;
	}
private:
	int _year;
	int _month;
};

int main()
{
	Date d1(11);
	Date d2 = 12;
	cout << d2.Print() << endl;
	const Date& d3 = 13;
	int i = 10;
	double j = i;
	return 0;
}

执行结果就是
在这里插入图片描述

可以发现全部调用的都是构造函数

需要注意的地方有两点:

  1. 并不是只有刚创建的是才会进行隐式类型转换。如果创建过后在进行赋值,此时执行的就是赋值运算符重载,如果没有写就会进行浅拷贝,若此时成员变量有const限制的变量时,编译器就会报错。在VS2022中会报1>D:\Desktop\cpp\Project1\Project1\main.cpp(432,5): error C2280: “Date &Date::operator =(const Date &)”: 尝试引用已删除的函数
    这是因为如果没有定义自定义的复制构造函数或复制赋值运算符,编译器会默认生成这些函数。但当这些函数尝试访问 const 成员时,由于它们不能被改变,因此编译器会将复制赋值运算符标记为“已删除”。 此时就没有默认生成拷贝构造,如果我们写了赋值运算符的重载,并且不牵扯const限制的变量,就不会在报错。
    比如下面代码,如果把注释去掉就不会报错了
class Date
{
public:
	Date(int y)
	{
		cout << "Date(int y)" << endl;
	}
	//void operator=(Date d)
	//{

	//}
private:
	int _year;
	int _month;
	const int _day = 1;
};

int main()
{
	Date d1(1);
	d1 = 3;
	return 0;
}

但是如果没有const限制的成员变量,就不会出现上述报错,此时还是像刚创建一样,进行隐式类型转换,但是接着执行的就是赋值运算符重载,并且不会像刚赋值一样被优化不去执行,是真的执行了赋值运算符重载。

  1. 类型转换或者传值返回会产生临时变量,临时变量具有常性,所以引用不加const会报错
int main()
{
	Date d1(11);
	Date d2 = 12;
	const Date& d3 = 13;
	int i = 10;//这种赋值不产生临时变量
	const double j = i;
	return 0;
}

上述代码中d3是13临时对象的引用,13产生的临时对象具有常性,不能被改变,所以要加const,但是此时临时的,所以可能会产生野引用。但是const会延长临时对象的生命周期,临时对象在其引用的作用域结束后才会被销毁。 这里要注意,此外这句话不像Date d2 = 12; Date d2 = 12;会直接调用构造函数,传参然后创建d2对象。但是const Date& d3 = 13;是会产生临时变量的。这条语句不是创建对象,只要创建对象才会被优化。

i 赋值给 j ,i 会进行类型转换产生一个临时的具有常性的变量,然后赋值给 j ,此时 j 应该被const限制。不然是不正确的,但是有的编译器会优化,不会报错,VS2022就没有报错。
但是Date d2 = 12;按理来说也有一个具有临时对象,此时的拷贝构造的参数应该是const限制的,但是上面说了会优化为直接执行构造函数传参,所以这里也不存在这个错误了。

这种隐式类型转换使用的情况一般是下面这种:

void StackPush(Date d)
{
	//
}
StackPush(1);

这样写可以很方便的进行Push,而不需要创建一个对象再Push。

多参数的隐式类型转换

上面写的多参数的构造函数不能进行隐式类型转换,其实并不,只是要加限制,不能直接加个等号就进行赋值了。

class Date
{
public:
	Date(int y, int j)
	{
		cout << y << endl;
		cout << j << endl;
	}
	Date(Date& d)
	{
		d._month = 100;
	}
	void operator=(Date d)
	{
		cout << "void operator=(Date d)" << endl;
	}
private:
	int _year;
	int _month;
};

int main()
{
	Date d1(11, 111);
	Date d2 = { 12, 122 };
	const Date& d3 = { 13, 133 };
	int i = 10;
	double j = i;
	return 0;
}

要像上述代码,写一个大括号才能进行隐式类型转换,此时执行的流程和上面单参数的的执行过程是一样的。
如果类里含有类类型的成员变量,我们也可以使用上述方法给缺省值。

explicit关键字

explicit是C++中的一个关键字,它用来修饰只有一个参数的类构造函数,以表明该构造函数是显式的,而非隐式的。当使用explicit修饰构造函数时,它将禁止类对象之间的隐式转换,以及禁止隐式调用拷贝构造函数。
例如:

class Date
{
public:
	explicit Date(int y)
	{
		cout << y << endl;
		cout << this << endl;
	}
private:
	int _year;
	int _month;
};

此时就不能进行隐式类型转换了,多参数的也一样。

static

通过一个场景引出static
怎样统计我们调用了几次构造函数?
可以定义一个全局变量,在构造和拷贝构造里面进行加 1 操作。
如下:

int n = 0;
class Date
{
public:
	Date()
	{
		++n;
	}
	Date(const Date& d)
	{
		++n;
	}
private:
};

Date Func()
{
	Date a;
	return a;
}
int main()
{
	Date d1;
	Date d2;
	Func();
	cout << n << endl;
	return 0;
}

定义一个全局的n,每次进入构造就执行一次++n操作。但是执行后输出结果却是3次,其实应该是4次,在Func()函数的return时是传值返回,会执行一次拷贝构造,但是编译器认为没有人使用,返回了也没用,就不执行拷贝构造了,其实试的时候发现就算有对象承接,也不会执行,应该是优化的原因。并且定义为全局的可能会在不经意间被修改。总之,这种方式不靠谱。

于是可以使用static关键字,封装成静态的。static修饰的仍然是全局的,封装在类中,可以在全局使用,而且不会出现上述的被优化或者被修改的情况。
写法如下:

class Date
{
public:
	Date()
	{
		++n;
	}
	Date(const Date& d)
	{
		++n;
	}
//private:
	static int n;
};
int Date::n = 0;
Date Func()
{
	Date a;
	return a;
}
int main()
{
	Date d1;
	Date d2;
	Func();
	cout << Date::n << endl;
	return 0;
}

需要注意的是:
static修饰的变量不能给缺省值,因为它此时不是属于某一个对象,而是属于所有对象,属于整个类。 所以也不能在初始化列表初始化,初始化列表是初始化某一个对象的。但是static不是属于某一个对象的。而且要在类外面定义。类里面是声明。
可以理解为此时还是全局的一个静态变量,但是受到了类域和访问限定符的限制。
此时若要访问这个变量,就要加访问限定符,如下几种情况都是可以的:

int main()
{
	Date d1;
	Date d2;
	Date* ptr = nullptr;
	Func();
	cout << d1.n << endl;
	cout << d2.n << endl;
	cout << ptr->n << endl;
	cout << Date::n << endl;
	return 0;
}

此时访问的都是n,只要确定是哪个类就好,指定类确定或者通过对象确定都可以。像指向空的Date类型指针都可以访问的,因为n并不在ptr指向的地址,而是在静态区,这样访问知识为了让他突破类域,找到n在哪。
但是上述的访问也要在公有的情况下才可以,其实和全局的区别不大。所以没关系如果要是私有,就要用公有的函数才可以访问了。
如下:

class Date
{
public:
	Date()
	{
		++n;
	}
	Date(const Date& d)
	{
		++n;
	}
	int Getn()
	{
		return n;
	}
private:
	static int n;
};

int Date::n = 0;

Date Func()
{
	Date a;
	return a;
}
int main()
{
	Date d1;
	Date d2;
	Date* ptr = nullptr;
	Func();
	cout << d1.Getn() << endl;
	return 0;
}

同样可以把函数封装为静态的,如下:

class Date
{
public:
	Date()
	{
		++n;
	}
	Date(const Date& d)
	{
		++n;
	}
	static int Getn()
	{
		return n;
	}
private:
	static int n;
};
int Date::n = 0;
Date Func()
{
	Date a;
	return a;
}
int main()
{
	Date d1;
	Date d2;
	Date* ptr = nullptr;
	Func();
	cout << d1.Getn() << endl;
	cout << Date::Getn() << endl;
	return 0;
}

此时可以通过类进行访问这个函数,但是要注意,静态的成员函数没有this指针,此时要做的事情如果牵扯到this指针就无法执行。

其实由于VS2022编译器优化的过于厉害,用上述的static其实也无法正确统计调用的构造的次数,也可能是真的就调用了编译器返回的那么多次,那么多次可能对于编译器来说够了。编译器会有各种优化,并且会修改我们的代码,所以被优化导致不符合预期也是正常的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChaCha20:高效且安全的流密码算法

随着互联网的普及和数据安全意识的提高&#xff0c;加密算法在保护个人隐私和商业秘密方面发挥着越来越重要的作用。ChaCha20是一种流密码算法&#xff0c;由丹伯恩斯坦在2008年提出&#xff0c;后被广泛应用于网络通信和数据加密场景。本文将探讨ChaCha20的原理和特点。 ChaCh…

「ComfyUI」增强图像细节只需要一个节点,SD1.5、SDXL、FLUX.1 全支持,简单好用!

前言 ‍‍‍‍‍前 言 今天听雨给小伙伴们介绍一个非常简单&#xff0c;但又相当好使的一个插件。 功能很简单&#xff0c;就是增加或者减少图像的细节&#xff0c;节点也很简单&#xff0c;就一个节点&#xff0c;只需要嵌入我们的 ComfyUI 的基础工作流中就可以了&#xf…

高频变压器无功补偿怎么做

高频变压器的无功补偿主要是为了提高功率因数、减小无功损耗、提高电源利用率。在高频电路中&#xff0c;由于频率较高&#xff0c;传统的无功补偿方法需要进行一定的调整和优化。以下是高频变压器无功补偿的一些方法和建议&#xff1a; 1、无功补偿电容器 高频电容器选择&…

阿里云OSS跨账号迁移过程

阿里云OSS跨账号迁移过程 关于OSS在线迁移服务的更新说明 旧版在线迁移已停止服务,用户需切换至新版在线迁移。与旧版相比,新版在线迁移的主要区别在于身份验证方式的调整。新版不再使用AK/AS(AccessKey ID和AccessKey Secret)进行认证,而是采用了角色授权机制。这一变化旨…

【CTF Web】CTFShow 版本控制泄露源码 Writeup(目录扫描+.git泄漏)

版本控制泄露源码 10 版本控制很重要&#xff0c;但不要部署到生产环境更重要。 解法 用 dirsearch 扫描。 dirsearch -u https://a21fb823-c708-47ea-91c8-945c25d2ddb1.challenge.ctf.show/找到 .git 仓库。 访问&#xff1a; https://a21fb823-c708-47ea-91c8-945c25d2dd…

2024年【甘肃省安全员C证】考试内容及甘肃省安全员C证免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年甘肃省安全员C证考试内容为正在备考甘肃省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的甘肃省安全员C证免费试题祝您顺利通过甘肃省安全员C证考试。 1、【多选题】听证程序是指行政机关作…

聚鼎装饰画:装饰画店铺未来5年到底有没有前景

随着人们生活水平的提高和审美需求的多样化&#xff0c;装饰画作为家居装饰的重要组成部分&#xff0c;其市场需求持续增长。然而&#xff0c;面对快速变化的市场环境和消费者偏好&#xff0c;装饰画店铺在未来5年的发展前景如何? 一方面&#xff0c;科技进步将为装饰画行业带…

鸿蒙(API 12 Beta3版)【使用ImagePacker完成图片编码】图片开发指导

图片编码指将PixelMap编码成不同格式的存档图片&#xff08;当前仅支持打包为JPEG、WebP 和 png 格式&#xff09;&#xff0c;用于后续处理&#xff0c;如保存、传输等。 开发步骤 图片编码进文件流 创建图像编码ImagePacker对象。 // 导入相关模块包 import { image } fr…

IOC/DI注解开发

IOC/DI&#xff08;Inversion of Control/Dependency Injection&#xff09;是一种依赖注入的软件设计模式&#xff0c;它的核心思想是将对象的创建和依赖关系的管理从代码中抽离出来&#xff0c;交给外部容器来管理。这种设计模式可以提高代码的可维护性、可测试性和可扩展性。…

【包教包会】怎么购买海外域名?Namecheap+虚拟卡购买步骤详解

前言 为什么要用海外域名&#xff1f; 首先&#xff0c;胖虎要介绍一下为什么要使用海外域名&#xff0c;使用国内的域名不好吗&#xff1f;主要是出于以下几个原因供大家参考&#xff0c;主要是涵盖了品牌保护、市场拓展、灵活性和创新性等多个方面&#xff1a; 品牌保护&a…

油耳朵用什么耳勺?揭露行业的五大隐患套路!

油耳清洁是很多人的一个困惑&#xff0c;由于外耳道分泌油性耵聍过多而导致。油耳使用传统耳勺和棉签很容易掏不干净&#xff0c;而可视挖耳勺采用了柔软且耐用的材料制成&#xff0c;可以避免因材料过硬对耳道造成损伤。并且用户可以通过摄像头清晰地看到耳道内的情况&#xf…

Java导出DBF文件(附带工具类)

导出DBF文件 先看效果 JavaDBF 使用JavaDBF库 数据类型映射 写入支持的类型 类型XBase类型XBase 符号JavaDBF 中使用的 Java 类型字符CharacterCjava.lang.String数值NumericNjava.math.BigDecimal浮点Floating PointFjava.math.BigDecimal布尔LogicalLjava.lang.Boolea…

公司如何监控员工电脑,怎么监控电脑进程

在现代企业管理中&#xff0c;监控员工电脑的行为已成为确保工作效率、数据安全和合规性的重要措施。通过合理的监控手段&#xff0c;企业可以预防潜在的安全威胁&#xff0c;优化工作流程&#xff0c;并确保员工遵循公司的政策和规定。 公司监控员工电脑的常见方法 屏幕监控&…

什么是pk答题软件源码

答题软件的源码是开发答题软件的基础程序代码。由于答题软件的功能和复杂程度不同&#xff0c;其源码也会有很大差异。 一般来说&#xff0c;答题软件的源码可能包含以下几个主要部分&#xff1a; 一、用户界面部分 登录和注册界面&#xff1a; 允许用户输入用户名、密码等信…

C++资料电子书资源PDF免费分享

C电子书 这里写目录标题 C电子书目录资源获取 目录 《数据结构(C语言版)》(严蔚敏 吴伟明编著).pdf 7.6MB 《C程序设计题解与上机指导》(第二版).谭浩强.pdf 7.0MB 《C程序设计(第四版)学习辅导》.谭浩强.扫描版.pdf 13.1MB 《C程序设计》第一版&#xff08;谭浩强&#xff09…

个人笔记--python画图(一维,二维,三维)

1. 一维 1. plot import numpy as np import matplotlib.pyplot as plt# linspace(): 创建等间距的数值序列 x np.linspace(0, 2 * np.pi, 100)u np.sin(x)# 绘制一维图形 plt.figure() plt.plot(x, u) plt.title(Plot of sin(x)) plt.xlabel(x) plt.ylabel(sin(x)) plt.sh…

监控摄像头能看到电脑屏幕吗?公司监控摄像头拍员工电脑屏幕!

监控摄像头已经成为许多企业和公共场所不可或缺的安全管理工具。 它们不仅守护着物理空间的安全&#xff0c;也在一定程度上影响着企业的运营管理和员工的行为规范。 然而&#xff0c;当“公司监控摄像头拍员工电脑屏幕”这一话题被提出时&#xff0c;不禁引发了广泛的讨论与…

Sketch for mac(专业矢量绘图设计软件100.3版) 中文激活版 一键快速安装!

Sketch 是一款专为 macOS 设计的专业矢量图形编辑软件&#xff0c;自发布以来便成为 UI/UX 设计师首选的工具之一。其简洁高效的用户界面、强大的设计功能&#xff0c;以及与 macOS 系统的深度集成&#xff0c;使得 Sketch 在设计领域享有很高的声誉。无论是移动应用设计、网页…

PT:如何获取net的Delta delay信息

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 来自星球提问: crosstalk的Delta delay可以从三个渠道获取: report_timing

监控用户登录信息,执行事务码情况

CMOD->SUSR0001 *&---------------------------------------------------------------------* *& 包含 ZXUSRU01 *&---------------------------------------------------------------------*DATA:lv_sblm_obj TYPE sblm_obj.CALL FUNCTION ZFMB…