C++类和对象3

news2024/11/26 13:37:45

一.初始化列表

我们之前的构造函数都是在函数体内对数据成员进行赋值

Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

然而我们的构造函数还有另一种初始化的方式:初始化列表 ——初始化列表是以参数表后冒号开始,用数据成员后面括号中的变量或者表达式来进行初始化。

Date(int year, int month, int day): 
	_year(year), _month(month), _day(day) {}

 初始化列表也叫做初始值列表,其的特征为:

1.每个数据成员只能在初始化列表中出现一次,初始化列表的作用是初始化成员变量,多次出现重复初始化有什么意义?

2.引用成员变量,const成员变量,以及没有默认构造函数的类对象成员必须使用初始化列表进行初始化。

引用类型在定义的时候就必须得初始化,否则会报错

const成员变量只能初始化,不能赋值,因为其值是不可改变的,所以也必须得在初始化列表中初始化。

那为什么普通的构造函数不可以呢?

因为普通的构造函数进行的是赋值操作,而并非初始化操作。 

3.成员的初始化顺序与类中生命的顺序是一致的,与初始化列表中的顺序无关。

如上图所示,初始化成员的顺序并不是按照_day,_month,_year的顺序进行初始化的,而是按照数据成员声明的顺序进行初始化的,先初始_year,_month,_day。 

例:

class A
{
public:
	A(int ii):
		_a1(ii),_a2(_a1){}

private:
	int _a2;
	int _a1;
};

当输入ii == 1时,cout<<_a1<<_a2<<的结果是? 

结果是:1 随机值

分析:成员的初始化顺序与类中声明的顺序一直,类中先声明_a2,再声明_a1,所以在构造函数中,先对_a2进行初始化,在对_a1进行初始化,由于_a1还没有被初始化所以_a2就是随机值,随后_a1被ii初始化为1.

 4.C++11支持在成员变量声明的位置给出缺省值,这个缺省值会给没有显示在初始化列表中初始化的成员进行初始化。(注意,这个缺省值要和函数形参的缺省值区分开。)

在声明成员的时候给出缺省值,并不是给成员变量初始化。 

当我们没有在初始化表中显式写出_a2,此时就会使用_a2的缺省值对_a2进行初始化:

5.使用初始化列表的方式对数据成员进行初始化可以提升效率 

初始化列表的方式是直接进行初始化操作,而普通的构造函数是先初始化然后再进行赋值操作。

 这里给出总结:能用初始化列表就用初始化列表,尽量使初始化列表中的顺序和成员声明的顺序保持一致,另外就是避免用一个成员变量去初始化另一个成员变量

二.类型转换

  • C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的的构造函数
  • 构造函数前面加上explicit就不再支持隐式类型转换
  • 类类型的对象之间也可以进行隐式转换,需要相应的构造函数支持
class A
{
public:
	A(int a) :
		_a(a)
	{}

	A(int a,int b):
		_a(a),_b(b)
	{}

private:
	int _a;
	int _b;
};

int main()
{
	A a1(1);

	//1会先隐式类型转换为A类型的临时变量,然后该临时变量在拷贝构造给a2
	A a2 = 1;

	//C++11之后支持多参数的转化
	//但是必须用花括号括起来
	A a3(1, 2);

	A a4 = { 1,2 };
	A a5 = 1, 2;//错误

	return 0;
}

三.static成员

1.用static修饰的成员变量,称为静态成员变量,静态成员变量必须在类外进行初始化

class A
{
private:
	static int _a;
};

int _a = 1;

2.静态成员变量是所有该类对象所公有的,不是属于某一个对象的,不存在对象中,存放在静态区

3.用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针,所以静态成员函数不可以访问非静态成员变量,只能访问静态成员变量

static void Show()
{
	std::cout << _a << std::endl;//正确
	std::cout << _b << std::endl;//错误,静态成员函数不能访问非静态成员变量
}

 

 4.非静态成员函数可以访问任意的静态成员变量和静态成员函数

void Print()
{
	std::cout << _a << std::endl;//正确,非静态成员函数可以访问静态成员变量
	std::cout << _b << std::endl;//正确
}

5.突破类域的限制,就可以访问类中的静态成员,有两种访问方式:

类名::静态成员

对象.静态成员 

//静态成员变量
std::cout << A::_a << std::endl;

A a(10);
std::cout << a._a << std::endl;

//静态成员函数
A::Show();

A b(10);
b.Show();

 需要注意的是,静态成员也是类的成员,依旧受到访问限定符(private,public,protected)的限定作用。如果静态成员是被private修饰的,那么通过上面的访问方式不能访问。

 6.因为静态数据成员不属于某一个对象,而是属于该类的,所以它并不是在创建对象时被定义的。这意味着它们不是由类的构造函数初始化的。而成员变量在初始化时都要走初始化表,所以静态成员变量是不走初始化列表。它们要在类外部进行定义和初始化。

7.静态成员和普通成员的另外一个区别是我们可以使用静态成员作为默认参数(缺省值)。

非静态数据成员不能作为默认参数,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。

四.友元

一个类中的私有成员只能由该类的成员函数来访问,但是除此之外,C++还可以让外部函数访问一个类的私有成员变量或者私有成员函数,方法是——声明该函数为这个类的友元(friend)函数。

1.友元提供了一种突破访问限定符限制的访问方式,友元分为:友元函数和友元类,其声明方式是在函数声明或者类声明的前面加上friend关键字

当我们直接用Print访问A的私有成员时,会发生报错

我们只需要在类中对Print进行友元声明,此时Print就可以访问A的私有成员 

2.友元函数只能出现在类定义的内部,但是在类内出现的具体位置不限,即友元函数可以在类中的任何地方进行声明,不受访问限定符(private,public,protected)的限制。 

这三个位置的效果是相同的,Print依旧是A类的成员函数,可以访问A类中的私有成员。 

一般来说,最好在类定义开始或者结束前的位置集中声明友元。

 3.友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对该函数进行一次声明。

Note:许多编译器并未强制限定友元函数必须在使用之前在类的外部声明。 

4.当一个类成为另一个类的友元类时,该类的所有成员函数都可以访问另一个类的私有成员。

注意:虽然我们已经在类A中声明类B为A的友元类,但是因为B是在A的后面定义的,A并不认识B,所以还得对B进行声明,这是前置声明。

//前置声明
class B;

class A
{
	friend B;
public:
	A(int a, int b) :
		_a(a), _b(b)
	{}

private:
	int _a;
	int _b;
};

class B
{
public:
	void Print(const A& a)
	{
		std::cout << a._a << " " << a._b << std::endl;
	}
};

5.还可以将一个类的成员函数声明为另一个类的友元 

但是这种声明有条件要求:

  1. 要先定义内部有函数要作为友元的类,在定义另一种类
  2. 该成员函数只能在类中声明,且定义在这两个类的后面

只有满足上面的条件,才能使友元函数成立。

现分析下面代码:

 B类中的Print函数要作为A类的友元函数,所以要将B类定义到A类的前面。且该Print函数只能在B类中声明,而不能定义。定义要放在这两个类的后面。否则Print就无法访问类A的私有成员。

class B
{
public:
	void Print(const A& a);
};

class A
{
	friend void B::Print(const A& a);
public:
	A(int a, int b) :
		_a(a), _b(b)
	{}

private:
	int _a;
	int _b;
};


void B::Print(const A& a)
{
	std::cout << a._a << " " << a._b << std::endl;
}

当我们交换上面类A和类B的顺序时,友元函数无法成立: 

当Print在类内定义或者没有定义在这两个类的后面时,友元无法成立: 

6.一个函数可以是多个类的友元函数,一个类也可以有多个友元函数 

因为A先定义,而在A中声明友元的时候出现了B,所以为了避免编译器不认识B,所以要对B进行前置声明

//前置声明
class B;

class A
{
	friend void Print(const A& a, const B& b);
private:
	int _a;
	int _b;
};

class B
{
	friend void Print(const A& a, const B& b);
private:
	char _a;
	char _b;
};

void Print(const A& a, const B& b)
{
	std::cout << a._a << " " << a._b << std::endl;
	std::cout << b._a << " " << b._b << std::endl;
}

7.友元类的关系是单向的 ,比如:A类是B类的友元,但是B不是A的友元(A类对象可以访问B类对象的私有成员,但B对象不可以访问A类对象的私有成员)

8.友元关系不具有传递性,A是B的友元,B是C的友元,但A不是C的友元 

五.内部类

如果A类定义在了B类的内部,那么A类就叫做B类的内部类。

1.内部类是一个独立的类,它跟定义在全局的类的区别只在于收到了类域和访问限定符的限制,所以外部类定义的对象,并不包括内部类。

class B
{
public:
	class A
	{
	private:
		int _a;
		int _b;
	};
};

我们可以验证一下B类的大小,看看是否包含类A。 

因为外部类B除了类A外并没有任何成员变量,所以它的大小就是1.从这里也可以看出内部类是不算在外部类中的。 

2.内部类受到访问限定符的限制,当内部类被public修饰时,此时该内部类除了外部类可以访问外,其他的类也可以访问。但如果被private或者protected修饰时,就成了外部类的专属内部类,只有该外部类可以访问。

class B
{
public:
	class A
	{
		void Print(const B& b)
		{
			std::cout << b._a << " " << b._b << std::endl;
		}
	};
private:
	int _a;
	int _b;
};

//上面相当于下面
class A
{
	void Print(const B& b)
	{
		std::cout << b._a << " " << b._b << std::endl;
	}
};

class B
{
public:
	friend A;
private:
	int _a;
	int _b;
};

从这里也可以看出外部类B的大小不包含内部类A

3.内部类默认是外部类的友元,即内部类的成员函数可以访问外部类的私有成员函数或者成员变量。

class B
{
public:
	class A
	{
		void Print(const B& b)
		{
			std::cout << b._a << " " << b._b << std::endl;
		}
	};
private:
	int _a;
	int _b;
};

六.匿名对象

在C++中,匿名对象是指在没有分配给任何变量的情况下创建的临时对象。它们通常用于在需要临时的对象时使用,而不需要将其分配给变量。

其语法格式为:

类名();

 与匿名对象相反的就是有名对象,我们可以对这两个对象进行对比,以此来理解匿名对象:

class A
{
public:
	A(int a,int b):
		_a(a),_b(b)
	{}

private:
	int _a;
	int _b;
};

int main()
{
	A a(1,2);//有名对象
	A(3,4);//无名对象

	return 0;
}

我们从上面就可以看出有名对象和无名对象的最主要区别就在于没有名字。

还有一个区别就是 匿名对象的声明周期只在当前行,当程序运行到这一行时,匿名对象创建,到下一行时,匿名对象就已经销毁了。而有名对象的声明周期是在创建该对象的作用域中。

匿名对象也可以简化我们的调用方式,以往我们调用一个成员函数是必须得是:

对象.成员函数() 

而使用匿名对象就可以简化调用方式:

class Solution
{
public:
	int sum_Solution()
	{
		std::cout << "sum_Solution" << std::endl;
	}
private:
	int _i;
};

int main()
{
	Solution s;
	s.sum_Solution();//有名方式调用成员函数

	Solution().sum_Solution();//匿名对象调用

	return 0;
}

这种调用方式在对象名比较长非常方便。 

需要注意的是匿名对象和临时对象一样,也具有常性。所以匿名对象在使用期间就有可能造成权限放大的问题。

A& a = A(1, 2);

上面这段代码在编译时就会报错, 因为A(1,2)会创建匿名对象,而匿名对象具有常性,不可修改,而我们将其赋值给了一个该类的引用,其值可以修改,放大了其权限。

 为了解决这个问题,我们只需加上const即可,使该引用也变成不可修改。

const A& a = A(1, 2);

 但是用const修饰以后,该匿名对象的声明周期就得到了延长,从当前行延长到了程序结束。

七.对象拷贝时的编译器优化

  • 现代编译器为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。
  • 如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续表达式步骤中的连续拷贝会进行合并优化,有些更新更“激进”的编译器还会进行跨行表达式的合并优化。

注意:接下来的分析全都借助于VS2022来分析,不同版本的编译器优化程度不同

1.传值传参

当我们以下面的方式调用f1时,是不会发生编译器优化的,因为调用f1函数时仅仅发生了拷贝构造,编译器只有遇到【拷贝+拷贝构造】时,才会进行合并优化为拷贝。

void f1(A a)
{}

int main()
{
	A aa(1,1);

	f1(aa);

}

当我们直接传入一个1时,此时会发生优化么? 

f1(1);

会!

因为此时1会发生隐式类型转换,转换为该类的临时对象,这里会调用构造,然后传值传参时又会调用拷贝构造,此时构造和拷贝构造连续发生,编译器会优化,直接变为一个构造

当我们传入A(3)时会不会优化呢?

f1(A(3)); 

会!

 A(3)此时会先调用构造函数,然后传值传参时又会调用拷贝构造,两者连续出现会发生优化,直接构造。

 2.传引用传参

传引用传参可以减少拷贝,提高运行效率。

void f2(const A& aa)
{}

当我们先创建对象然后调用f2

A a(1);

f2(a);

创建对象时会调用构造函数,在调用f2时不会发生拷贝构造,因为这里aa直接作为a的别名就传给了函数,不需要创建临时变量,也就不需要拷贝构造了。

直接传3,发生隐式类型转换

f2(3);

 先发生类型转换调用构造函数生成临时变量,然后aa直接作为该临时变量的别名,不需要再调用拷贝构造。

传A(3)的结果和直接传3的结果是一样的

f2(A(3));

A(3)会先调用构造函数,然后与前面相同,aa直接作为该匿名对象的别名,也不再需要拷贝构造。 

总结:在传参时,如果可以传引用的话,最好就传引用,这样可以减少拷贝,增加运行效率 

3.传值返回 

A f3()
{
	A aa(1);
	return aa;
}

当我们直接调用f3()时

f3();

 在f3函数中,会先调用构造函数,创建对象aa,在返回时会调用拷贝构造生成一个临时对象,此时编译器会直接优化为一个构造

当我们用另一个对象接收f3()的返回值时

A aa = f3(); 

此时会先发生一个构造生成待返回的对象,然后拷贝构造生成临时对象,在用该临时对象拷贝构造aa,此时发生的过程为【构造+拷贝构造+拷贝构造】,此时编译器会直接优化为一个构造

需要注意的是,连续的拷贝构造和赋值运算符重载也会优化

A aa;

aa = f3();

在函数体内创建aa变量之后,应该先拷贝构造生成临时对象,然后用该临时对象通过赋值运算符重载给aa,但是在VS2022环境下,编译器对次做出了优化,优化掉了拷贝构造部分。 

但是VS2019的debug环境下,拷贝构造和赋值运算符重载是不会进行优化的: 

在VS2019release版本下,优化结果与VS2022相同。

总结:编译器对对象拷贝时的构造优化是不同的,根据编译器的不同,或者同种编译器的不同版本,会进行不同的优化结果。 

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

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

相关文章

数学建模笔记—— 多目标规划

数学建模笔记—— 多目标规划 多目标规划1. 模型原理1.1 多目标规划的一般形式1.2 多目标规划的解1.3 多目标规划的求解 2. 典型例题3. matlab代码实现 多目标规划 多目标规划是数学规划的一个分支。研究多于一个的目标函数在给定区域上的最优化。又称多目标最优化。通常记为 …

VCS(Video Cloud Storage)解决方案研究报告

1.背景 控视频是重要的数据资产和证据链&#xff0c;在银行、交通、司法等行业对视频数据有很高的安全等级。随着监控的重要性不断提升&#xff0c;在能源、电力、校园、厂矿、高星酒店等多场景中对监控存储也有更高要求&#xff0c;体现为海量存储、超长时间和数据安全。为了充…

得物APP助力释放首发经济新活力,解锁年轻潮流密码

在消费升级与高质量发展的时代背景下&#xff0c;我国首发经济正以前所未有的活力蓬勃发展&#xff0c;成为推动市场繁荣、满足个性化消费需求的重要力量。首发&#xff0c;即产品首次在市场亮相&#xff0c;往往代表着最新的设计理念、最尖端的科技应用以及最前沿的潮流趋势。…

C++入门知识(1)

一、namespace 1、用处 可以解决程序里面定义重名变量的问题 namespace是一个命名空间。 定义变量可以在4个域下面定义&#xff0c;全局域&#xff0c;局部域&#xff0c;命名空间域&#xff0c;类域。各个域之间是相互不影响的。命名空间里面的变量可以和外面的变量重名 2…

Stable Diffusion4.9一键安装教程SD(AI绘画软件)

**无套路&#xff01;**文末提供下载方式 Stable Diffusion 是一款革命性的 AI 绘画生成工具&#xff0c;它通过潜在空间扩散模型&#xff0c;将图像生成过程转化为一个逐步去噪的“扩散”过程。 与传统的高维图像空间操作不同&#xff0c;Stable Diffusion 首先将图像压缩到…

样品管理的重要性与实操解决方案,外贸软件一键搞定

在外贸过程中&#xff0c;样品管理是一个重要的环节&#xff0c;它不仅涉及到产品的质量和细节确认&#xff0c;还是与客户沟通的重要桥梁。在选择客户时&#xff0c;通常会优先考虑那些目的明确、意向较强的客户&#xff0c;因为这些客户成交的可能性较大。无论是纺织品、服装…

基于SpringBoot+Vue的学生成绩管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的学生成绩…

Python语法,从入门到精通,一步到位!

Python语法及入门涵盖了多个方面&#xff0c;包括基本语法、数据类型、控制流、函数、模块等。以下是一个超全超详细的介绍&#xff1a; 一、Python基本语法 注释&#xff1a;Python中使用井号&#xff08;#&#xff09;表示注释&#xff0c;从井号开始到行尾的内容都会被Pytho…

一节课教你学会【预处理详解】

谢谢观看&#xff01;希望以下内容帮助到了你&#xff0c;对你起到作用的话&#xff0c;可以一键三连加关注&#xff01;你们的支持是我更新地动力。 因作者水平有限&#xff0c;有错误还请指出&#xff0c;多多包涵&#xff0c;谢谢&#xff01; 预处理详解 一、预定义符号二、…

红米K60U/K50/Note11TPro澎湃OS无法绑定账号解锁BL-不能激活小米账号

小米澎湃OS对于解锁BL&#xff0c;新增了各种限制&#xff0c;早前我们还能使用bypass脚本来实现澎湃OS上绑 定账号成功&#xff0c;但随着澎湃OS七月系统上的推送&#xff0c;旧版的bypass已经彻底失效&#xff0c;并且无法安装 旧版的设置APK来解决问题。此次涉及的机型有红米…

SpringSecurity剖析

1、SpringSecurity 入门 1.1、简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。Spring Security是一个框架&#xff0c;致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样&#xff0c;Sp…

【PX4-AutoPilot教程-TIPS】PX4中MAVLink话题频率修改

PX4中MAVLink话题频率修改 方法一&#xff1a;使用QGC地面站通过命令行解释器MAVLink Shell修改话题频率方法二&#xff1a;使用SD卡中的命令脚本文件修改话题频率方法三&#xff1a;通过修改PX4飞控固件源码修改话题频率 环境&#xff1a; PX4 &#xff1a;1.13.0 方法一&am…

SOP流程制定:vioovi ECRS工时分析软件的智慧引领

在现代制造业中&#xff0c;标准化操作流程&#xff08;SOP&#xff09;已成为提升生产效率、确保产品质量、降低运营成本的关键要素。SOP不仅为生产活动提供了明确的指导&#xff0c;还促进了企业管理的规范化和精细化。然而&#xff0c;如何科学、高效地制定SOP流程&#xff…

CISC 和 RISC 架构的对比

研究 RISC 架构优缺点的最简单方法是将其与其前身进行对比&#xff1a; CISC&#xff08;复杂指令集计算机&#xff09;架构。 内存中的两个数字相乘 右图表示一台普通计算机的存储方案。 主存储器被划分为编号从&#xff08;行&#xff09;1&#xff1a;&#xff08;列&…

RAG系统的7个检索指标:信息检索任务准确性评估指南

大型语言模型&#xff08;LLMs&#xff09;作为一种生成式AI技术&#xff0c;在近两年内获得了显著的关注和应用。但是在实际部署中&#xff0c;LLMs的知识局限性和幻觉问题仍然是一个挑战。检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09…

好网站包含哪些方面

好网站通常在多个方面都表现出色&#xff0c;包括但不限于设计、内容、导航、性能和互动性。下面将详细介绍这些方面。 首先&#xff0c;设计是一个网站吸引用户的第一印象。一个好的网站设计应该是清晰、直观、美观&#xff0c;并且符合用户体验原则。页面布局应该合理&#x…

Spire.PDF for .NET【文档操作】演示:创比较 PDF 文档

PDF 已成为跨不同平台共享和保存文档的标准格式&#xff0c;在专业和个人环境中都发挥着无处不在的作用。但是&#xff0c;创建高质量的 PDF 文档需要多次检查和修订。在这种情况下&#xff0c;了解如何有效地比较 PDF 文件并找出它们的差异变得至关重要&#xff0c;这使文档编…

【Go】Go语言基本语法--注释、变量、常量

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Memfire Cloud使用技巧:让开发更简单,更高效

在软件开发的世界里&#xff0c;Memfire Cloud就像是一位隐形的助手&#xff0c;它悄无声息地帮助开发者们解决了一个又一个难题。如果你还在为搭建服务、开发API接口而头疼&#xff0c;那么Memfire Cloud无疑是你的救星。今天&#xff0c;我们就来聊聊如何使用Memfire Cloud&a…

面试官:v-if和v-for的优先级是什么?

一、作用 v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法&#xff0c;其中 items 是源数据数组或者对象&#xff0c;而 item 则是被迭代的…