初识C++ · 类和对象(下)

目录

1 再谈构造函数

2 类中的隐式类型转换

3 Static成员

4 友元和内部类

5 匿名对象

6 编译器的一些优化


1 再谈构造函数

先看一段代码:

class Date
{
public :
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

当我们生成解决方案的时候,系统会报错:没有默认的构造函数,因为我们显式调用了构造函数,也没有默认构造,我们没有给缺省值,参数也没有缺省值,调用的时候就会报错。

C++引入了一个概念叫做初始化列表,以冒号开始,逗号分割,括号给值:

Date()
	:_year(2024)
	,_month(4)
	,_day(30)
{}	

也就是我们将构造函数写成这样,这样我们不传参也是可以成功的。

可能看起来没什么用?

class Stack
{
public:
Stack(int n)
	:_size(10)
	, _capacity(n)
	, arr(nullptr)
{
	//……
}
private:
	int _size;
	int _capacity;
	int* arr;
};
class MyQueue
{
public:
	MyQueue(int n)
		:s1(n)
		,s2(n)
	{
		_size = n;
	}
private:
	Stack s1;
	Stack s2;
	int _size;
};
int main()
{
	MyQueue q1(10);
	return 0;
}

对于类中有自定义类型的,我们原本的想法是给MyQueue一个值,然后初始化,并且stack调用自己的默认构造,如果没有初始化列表,Stack就完不成自己的初始化,那么MyQueue也就完不成自己的默认构造。

初始化列表赋值的时候都是用括号赋值,如果不想用括号,那么进入花括号里面进行赋值也是可以的,一般来说的话能直接括号就直接括号了。

赋值的括号里面可以是变量也可以是加减法一类的,也可以是常量。

有意思的是括号里面还可以进行运算。

初始化的本质可以理解为声明和定义,private那里是声明,初始化列表就是定义,定义的时候我们给缺省值也是没有问题的。

那么,初始化列表有那么几个需要注意的地方。

有三种成员必须要在初始化列表初始化:
第一种是const成员:

int main()
{
	const int a;
	a = 10;
	return 0;
}

这种代码就是错误的,因为const定义的变量只有一次初始化的机会,就是定义的时候,定义好了之后就不能改值的,所以const成员变量必须要在初始化列表初始化。

第二种是引用类型:

int main()
{
	int x = 10;
	int& xx;
	xx = x;
	return 0;
}

引用类型和const类型是一样的,不可能说先给一个外号,看谁像就给谁,所以引用类型也是要在初始化列表的时候给值。

第三种类型是没有默认构造的自定义类型的成员:

class Stack
{
public:
	Stack(int n)
		:_size(n)
		, _capacity(n)
		, arr(nullptr)
	{
		//……
	}

private:
	int _size;
	int _capacity;
	int* arr;
};
class MyQueue
{
public:
	MyQueue(int n = 10)
		:s1(n)
		,s2(n)
	{
		_size = n;
	}
private:
	Stack s1;
	Stack s2;
	int _size;
};

像这种,stack类必须要传参才是初始化的,没有默认构造函数,那么为了让他能顺利初始化,就在初始化列表里面初始化了。

对于初始化列表来说,三类成员必须在初始化列表初始化,其他类型的可以在初始化列表进行初始化,也可以进入函数体内初始化。

看个有意思的:
 

class Stack
{
public:
	Stack(int n = 4)
		:_size(n)
		, _capacity(n)
		, arr(nullptr)
	{}
private:
	int _size;
	int _capacity;
	int* arr;
};
class MyQueue
{
public:
	MyQueue(int n = 10)
	{
		_size = n;
	}
private:
	Stack s1;
	Stack s2;
	int _size;
};
int main()
{
	MyQueue q1;
	return 0;
}

我们给stack默认构造函数,使用MyQueue的初始化列表的时候没有Stack的初始化,那么stack会不会初始化呢?

stack类也是初始化了的,那么这就意味着,初始化列表不管你写不写编译器都是要走一遍的,所以C++打的补丁缺省值,实际上给的是初始化列表。即便我初始化列表什么都不写,仍然会走一遍初始化列表。无非就是调用它自己的默认构造函数而已。

一般的顺序都是先走一遍初始化列表,再走函数体,比如初始化一个指针,我们可以这样初始化:

	Stack(int n = 4)
		:_size(n)
		, _capacity(n)
		, arr((int*)malloc(sizeof(int) * 10))
	{
		memset(arr, 1, 40);
	}

函数体更多的是用来进行其他参数,初始化一般在初始化列表就可以了。

接下来看一个有意思的:

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main()
{
	A a(1);
	a.Print();
	return 0;
}

问最后结果是什么?

答案可能出乎你的意料:

打印出来了一个随机值,这是因为初始化列表的一个特点:
成员变量的声明次序就是初始化列表中的初始化顺序

我们先声明的_a2,所以_a2先给值,是_a1给的,_a1还没开始初始化,所以给的是随机值,然后初始化_a1,这时候_a1初始化为了1,所以打印出来有一个是1,有一个是随机值。

如果我们声明次序换一下,就是Ok的:


2 类中的隐式类型转换

先来看一个很有用的小代码:

class A
{
public:
	A(int n)
		:_a(n)
	{}
private:
	int _a;
};
int main()
{
	A a1();
	A a2 = 2;
	return 0;
}

我们创建对象的时候,可以用构造函数创建,也可以利用隐式类型转换创建,内置类型被转换为自定义类型,这里是2构建了一个A的临时对象,然后临时对象拷贝复制给a2。

当然了如果我们要引用一个的话,就得加一个const了,因为const具有常性。

	const A& aa = 1;

按道理来说,2构造了一个临时对象,发生了一次构造,然后临时对象拷贝构造给a2,所以一共是两次函数调用,但是在编译认为连续的构造 + 拷贝构造不如优化为构造,测试一下:

class A
{
public:
	A(int n)
		:_a(n)
	{
		cout << "int n" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "const A& aa" << endl;
	}
private:
	int _a;
};
int main()
{
	A a1(1);//构造
	A a2 = 2;//构造+拷贝构造 = 直接构造
	return 0;
}

这个隐式类型转换应用的场景比如:


class A
{
public:
	A(int n = 1)
		:_a(n)
	{
		cout << "int n" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "const A& aa" << endl;
	}
private:
	int _a;
};

class Stack
{
public:
	void push(const A& aa)
	{
		//...
	}
private:
	int _size;
};

int main()
{
	A a1;
	Stack s1;
	s1.push(a1);
	s1.push(2);
	return 0;
}

我往栈里面插入一个自定义类型,如果没有隐式类型转换,我就需要先创建一个,再插进去,这多麻烦,有了隐式类型转换直接就插入进去了。

但是有没有发现一个问题就是,隐式类型转换是内置类型给给自定义类型,如果是多个参数,又怎么办呢?

先不急,还有一个关键字explicit,它的用法很简单,就是防止隐式类型转换的发生的:

当多参数的时候,万能的花括号就派上用场了:

class A
{
public:
	A(int n,int m)
		:_a(n)
		,_b(m + 1)
		,_c(n + 2)
	{
		cout << "int n" << endl;
	}
	 A(const A& aa)
		:_a(aa._a)
	{
		cout << "const A& aa" << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};
int main()
{
	A a1 = { 1,2};
	A a2{ 1,3 };
	const A& aa{ 2,2 };
	return 0;
}

对于多参数的初始化,用花括号即可,并且在新的标准中可以不用等好,直接就花括号就可以了,


3 Static成员

class A
{
public:
	A()
	{
		_count++;
	}
	A(const A& aa)
	{
		_count++;
	}
	~A()
	{
		_count--;
	}
private:
	int _a;
	int _b;
	static int _count;
};

都知道static是用来修饰静态成员变量,那么在类里面如上,请问该类的大小是多大呢?

sizeof计算出来是8,也就是说_count是不在类里面的,因为它在静态区里面,那么结合初始化列表的知识,我们能给缺省值吗?

当然是不行的,因为缺省值最后都是要走初始化列表的,static的成员变量都不在类里面,怎么能走呢?

因为static的成员是静态的,我们只能在定义的时候给初始值,我们就只能在全局给一个初始值:

int A::_count = 1;

既然它是静态的,所以我们可以用来计数,比如实时观察有几个对象:

class A
{
public:
	A(int n = 1)
		:_a(n)
		,_b(n)
	{
		_count++;
	}
	A(const A& aa)
	{
		_count--;
	}
	~A()
	{
		_count++;
	}
//private:
	int _a;
	int _b;
	static int _count;
};

int A::_count = 0;

A Func()
{
	A a1;
	return a1;
}

int main()
{
	A a1;//1
	A a2 = a1;//2
	A a3 = 3;//3
	Func();//4
	//拷贝构造一个5
	cout << a1._count << endl;
	return 0;
}

函数里面有一次初始化,一次拷贝,加上主函数的三次,一共就是5个。

但是!

以上的所有操作都是基于count是公有的解决的,但是成员变量一般都是私有的,所以解决方法是用static修饰的函数:

static int Getcount()
{
	return _count;
}

因为函数也是静态的,所以没有this指针,那么访问的只能是静态成员,比如_count,其他成员变量都是不能访问的。


4 友元和内部类

友元前面已经简单提过,这里也介绍一下:

class A
{
	friend class B;
	//A是B的友元
public:
	//...
private:
	int _a1;
	int _a2;
};
class B
{
public:
	//...
private:
	int _b1;
	int _b2;
};

A是B的友元,友元的位置声明放在任意位置都是可以的,既然A是B的友元,也就是说A是B的朋友,那么B就可以访问A中的成员,如:

class A
{
	friend class B;
	//A是B的友元
public:
	//...
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
public:
	//...
	void BPrint()
	{
		cout << a1._a1 << endl;
	}
private:
	int _b1;
	int _b2;
	A a1;
};
int main()
{
	B bb;
	bb.BPrint();
	return 0;
}

但是反过来就不行了,A是B的朋友没错,但是B不是A的朋友,所以A不能使用B的成员,这个世界的情感很多都是单向的~

但是呢友元关系不能继承,之后介绍。

内部类,和友元关系挺大的:

class A
{
public:
	class B
	{
	public:

	private:
		int _b1 = 1;
		int _b2 = 2;
	};
private:
	int _a1 = 1;
	int _a2 = 2;
};

B是A的内部类,那么他们天生就有B是A的友元的关系,所以A可以直接访问B的成员变量,但是sizeof(外部类)的结果就是外部类:

内部类还可以直接访问外部类的static变量,不需要类名等:

class A
{
public:
	class B
	{
	public:
		void PirntK()
		{
			cout << _k << endl;
		}
	private:
	};
private:
	static int _k;
};
int A::_k = 1;
int main()
{
	A::B b1;
	b1.PirntK();
	return 0;
}

5 匿名对象

不少人看到匿名对象可能会联想到匿名结构体,不同的是匿名对象是对象实例化的时候不给名字,如:

class A
{
public:
	A(int num = 1)
		:_a(num)
	{
		cout << "int A" << endl;
	}
	~A()
	{
		_a = -1;
		cout << "~A" << endl;
	}
private:
	int _a;
};
int main()
{
	A a1;//有名对象
	A(1);//匿名对象
	return 0;
}

与匿名结构体不同的是,匿名i对象的声明周期只在这一行,没错,就是只有一行,我们可以通过析构函数调用实验一下:

int main()
{
	A(1);
	cout << "666" << endl;
	return 0;
}

如果是有名对象,那么析构函数的调用会在主函数结束的时候调用,那么666的打印就会在~A之前打印,但是这是匿名对象,创建即是销毁。

那么有用没呢?

存在即合理,比如我们调用函数:

class S
{
public:
	void P()
	{
		cout << " aaa " << endl;
	}
private:

};
int main()
{
	S s1;
	s1.P();
	S().P();
	return 0;
}

这是两种调用方法,两行代码的是有名对象的调用,一行代码的是匿名对象的调用,所以嘛,存在即合理。


6 编译器的一些优化

编译器的一些优化在2022是不太好观察的,因为2022的优化是比较大的,这里推荐的是Vs2019或者使用Linux机器观察,这里使用Vs2019观察:

先来看一下传值传参热热身:

class A
{
public:
	A(int num = 1)
		:_a(num)
	{
		cout << "int A" << endl;
	}
	A(const A& aa)
	{
		cout << "const A& aa" << endl;
	}
	~A()
	{
		cout << "~A" << endl;
	}
private:
	int _a;
};

//测试代码
void Func(A aa)
{}
int main()
{
	A a;
    Func(a);
	cout << endl;
	return 0;
}

顺序是a的构造->aa的拷贝构造->aa的析构(因为出了函数的作用域)->a的析构:

打印出来的换行也可以说明。

这里可能有人要问了,为什么拷贝构造函数要用个const修饰,因为有了匿名对象,呼应上了这就:
匿名对象发生的是临时变量的拷贝,具有常性,所以我们应该用const进行修饰

	Func(A(1));

1 连续的构造 + 拷贝构造 = 直接构造(不绝对)

如下三个场景:

int main()
{
	Func(2);

	Func(A(2));

	A aa = 3;
	return 0;
}

比如最后一个,给一个3,那么3会构造一个临时对象,临时变量拷贝给aa,整个过程就是连续的构造 + 拷贝构造,编译器会直接优化为构造。

但是为什么说不绝对呢?这和内联函数都是一样的,取决于编译器的实现,优化,内联函数对编译器来说都只是个建议,具体看的是编译器。

2 连续的拷贝构造 + 拷贝构造 = 一个拷贝构造

A Func()
{
	A aa;
	return aa;
}
int main()
{
	A ret = Func();
	return 0;
}

代码执行的顺序是aa的构造 -> aa返回临时变量进行拷贝 -> ret拷贝构造一个临时对象

这里是连续的拷贝构造即被编译器优化为一个拷贝构造:

但是……

int main()
{
	A ret;
	ret= Func();
	return 0;
}

这里是连续的拷贝构造吗?

并不是,ret  = Fun()这里是一个赋值重载,所以就不会有编译器的优化。

即拷贝 + 赋值重载 = 无法优化。

这是debug版本下的优化,release版本下的优化简直可以吓死人:


void operator=(const A& aa)
{
    cout << "operator=" << endl;
}
A Func()
{
	A aa;
	return aa;
}
int main()
{
	A ret;
	ret= Func();
	return 0;
}

原来是构造 + 构造 + 拷贝 +  赋值重载,这直接:​​​​​

拷贝直接优化掉了,直接赋值重载,这还不是最吓人的。

A Func()
{
	A aa;
	return aa;
}
int main()
{
	A ret= Func();
	return 0;
}

按道理来说,有构造 + 拷贝 + 拷贝,编译器直接三合一:

厉害吧?所以有时候观察麻烦就是因为编译器给优化掉了。

以上就是类和对象下的内容。


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis线程模型

文章目录 &#x1f496; Redis 单线程模型⭐ 单线程监听大量的客户端连接⭐ Redis 6.0 之前为什么不用多线程&#xff1f; &#x1f496; Redis多线程⭐ Redis 后台线程⭐ Redis 网络IO多线程 对于读写命令来说&#xff0c;Redis 一直是单线程模型。不过&#xff0c;在 Redis 4…

TinyXML-2介绍

1.简介 TinyXML-2 是一个简单、小巧的 C XML 解析库&#xff0c;它是 TinyXML 的一个改进版本&#xff0c;专注于易用性和性能。TinyXML-2 用于读取、修改和创建 XML 文档。它不依赖于外部库&#xff0c;并且可以很容易地集成到项目中。 tinyXML-2 的主要特点包括&#xff1a…

华为:三层交换机与路由器连通上网实验

三层交换机是一种网络交换机&#xff0c;可以实现基于IP地址的高效数据转发和路由功能&#xff0c;通常用于大型企业、数据中心和校园网络等场景。此外&#xff0c;三层交换机还支持多种路由协议&#xff08;如OSPF、BGP等&#xff09;&#xff0c;以实现更为复杂的网络拓扑结构…

automa警惕通过点击元素打开新的标签页,因为你可能会被他蒙蔽!

大家好&#xff0c;我是大胡子&#xff0c;专注于研究RPA实战与解决方案。 我们经常用到automa里面的【点击元素】组件&#xff0c;但要警惕通过点击元素打开新的标签页&#xff0c;例如下面这个场景&#xff0c;点击公众号的图文消息&#xff0c;之后&#xff0c;要自动输入标…

python环境下labelImg图片标注工具的使用

labelimg GitHub地址 python环境下labelImg图片标注工具的使用 1. 写在开头2. 如何使用2.1安装2.2 启动2.2.1 先启动后设置标注的目录2.2.2 指定标注的目录和预设置的标签 2.3 设置自动保存和显示类别。2.4 保存文件类型2.5 [快捷键](https://github.com/HumanSignal/labelImg…

【数据结构】C/C++ 带头双向循环链表保姆级教程(图例详解!!)

目录 一、前言 二、链表的分类 &#x1f95d;单链表 &#x1f95d;双链表 &#x1f95d;循环链表 &#x1f95d;带头双向循环链表 &#x1f34d;头节点&#xff08;哨兵位&#xff09;的作用 ✨定义&#xff1a; ✨作用&#xff1a; &#x1f347;总结 三、带头双向循环链表 …

技术速递|使用 .NET 为 Microsoft AI 构建可扩展网关

作者&#xff1a;Kara Saucerman 排版&#xff1a;Alan Wang Microsoft AI 团队构建了全面的内容、服务、平台和技术&#xff0c;以便消费者在任何设备上、任何地方获取他们想要的信息&#xff0c;并为企业改善客户和员工的体验。我们的团队支持多种体验&#xff0c;包括 Bing、…

通过氧气退火增强β-Ga₂O₃二极管.中国科技大学和河北半导体研究所的研究人员在这一特定领域取得了最新重大进展

上图所示&#xff1a;&#xff08;a&#xff09;增加台面有助于提高β-Ga2O3肖特基势垒二极管的阻断电压&#xff08;b&#xff09;。 氧气退火和自对准台面终端使β-Ga2O3二极管进一步走向商业化。 虽然β-Ga2O3电力电子技术已经取得了长足的进步&#xff0c;但仍然存在挑战&…

.双链表.

题目&#xff1a; 实现一个双链表&#xff0c;双链表初始为空&#xff0c;支持 55 种操作&#xff1a; 在最左侧插入一个数&#xff1b;在最右侧插入一个数&#xff1b;将第 k&#x1d458; 个插入的数删除&#xff1b;在第 k&#x1d458; 个插入的数左侧插入一个数&#xf…

Redis(Redis配置和订阅发布)

文章目录 1.Redis配置1.网络配置1.配置文件位置 /etc/redis.conf2.bind&#xff08;注销支持远程访问&#xff09;1.默认情况bind 127.0.0.1 只能接受本机的访问2.首先编辑配置文件3.进入命令模式输入/bind定位&#xff0c;输入n查找下一个&#xff0c;shift n查找上一个&…

书生·浦语大模型实战营之XTuner多模态训练与测试

书生浦语大模型实战营之XTuner多模态训练与测试 目录 XTuner多模态训练与测试给LLM装上电子眼&#xff1a;多模态LLM原理简介文本单模态文本图像多模态 电子眼&#xff1a;LLaVA方案简介LLaVA训练阶段示意图LLaVA测试阶段示意图 项目实践环境准备XTuner安装概述Pretrain阶段Fi…

NVIDIA_SMI has failed because it couldn’t communicate with the NVIDIA driver

参考&#xff1a;https://www.zhihu.com/question/474222642/answer/3127013936 https://blog.csdn.net/ZhouDevin/article/details/128265656 nvidia-smi查看报错&#xff0c;nvcc正常 1&#xff09;查看nvidia版本 ls /usr/src | grep nvidia nvidia-550.78 2&#xff09;…

无线通信基础

这里写目录标题 通信概述什么是无线通信无线通信电磁波 通信概述 什么是无线通信 无线通信 : 是指利用电磁波信号可以在自由空间中传播的特性进行信息交换的一种通信方式 无线通信的关键技术包括调制技术、解调技术、信道编码技术、信号处理技术、天线技术等。这些技术的不断…

【mobx-入门与思考】

介绍 mobx 是 nodejs生态中的框架&#xff0c; 主要用于做状态管理&#xff0c;可以监控变量状态的变化。 nodejs中除了mobx&#xff0c;还有个redux&#xff0c;也是做状态管理的&#xff0c;都是比较成熟的框架&#xff0c;二者的选择可以参考 【nodejs状态管理: Redux VS M…

太原理工大学Python数据分析原理与应用(课外考题:8~11章)

这部分大概只考10分&#xff0c;且大部分出在选择题&#xff0c;填空最多一两个 (仅供参考) 第十章 (理解概念为主&#xff0c;无需看推导过程) 第十一章

1-1ARM开发环境搭建(GD32)

1:安装MDK最好是5.27以及以上版本&#xff0c;避免后续学习中出现相关错误 2&#xff1a;安装芯片支持包 双击安装即可&#xff0c;也可以是默认路径&#xff0c;也可以自己更改路径 3&#xff1a;安装jlink下载器驱动&#xff08;下载调试器&#xff09; 具体安装步骤如下所示…

Java 线程池 ( Thread Pool )的简单介绍

想象一下&#xff0c;你正指挥着一支超级英雄团队&#xff0c;面对蜂拥而至的敌人&#xff08;任务&#xff09;&#xff0c;不是每次都召唤新英雄&#xff08;创建线程&#xff09;&#xff0c;而是精心调配现有成员&#xff0c;高效应对。这就是Java线程池的魔力&#xff0c;…

重装win11系统后找不到WiFi

由于电脑崩溃重装了系统&#xff0c;win11,装完之后WiFi图标不见了且网络适配器根本没有无线网络选项。 右键电脑》管理》网络适配器。 在刚装好系统时候并没有前两项&#xff0c;查了很多资料&#xff0c;比如 关机14s 重启&#xff0c;还有通过服务配置 WLAN AutoConfig 都…

从0到1提审苹果商店(appstore)上线一款新APP

本篇主要复盘和介绍一款APP如何从0到1上线到苹果商店,将我自己项目遇到的坑跟大家分享,希望能为同样做开发或者运营的你提供经验,少走弯路。 如果你是24年1月1日之后开始首次提审APP,还需要先将自己的APP在工信部备案,苹果后台增加了工信部备案号的填写,备案方法和经验如…

如何去官网下载windows10操作系统iso镜像

文章目录 一、先从微软中国官网https://www.microsoft.com/zh-cn/进去二、然后按图示一步步点进去三、点击下载工具这个工具会帮你生成windows操作系统iso文件四、下载好后一步步按图示要求成功操作一、先从微软中国官网https://www.microsoft.com/zh-cn/进去 二、然后按图示一…