【C++精华铺】6.C++类和对象(下)类与对象的知识补充及编译器优化

目录

1. 再谈构造

1.1 成员变量的初始化(初始化列表)

1.2 初始化列表的行为

1.3 explicit关键字

 2. 类中的static成员

2.1 静态成员变量

2.2 静态成员函数

3. 友元

3.1 友元函数

3.1 友元类

4. 内部类

 5. 匿名对象

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

 

1. 再谈构造

1.1 成员变量的初始化(初始化列表)

        为什么还要去看初始化的问题呢,因为这里有一个比较大的误区,我们都知道创建对象的时候会调用构造函数对成员进行初始化,所以我们会把下面的代码看作初始化,但其实下面的构造函数代码只能叫做赋值。

class Date
{
public:
	Date(int year,int month,int day)
	{
		_year = year;   //因为初始化只有一次,
		_month = month; //而在函数里面可以多次赋值,不能叫做初始化
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

        而真正的初始化是在初始化列表中进行的,初始化列表的位置是在构造函数”{}“的前面,是由一个冒号后面跟着多个用逗号隔开的成员变量,每个成员变量后面都有括号,括号里面就是初始值,初始化的次序与成员列表次序无关,是按照成员的声明顺序进行初始化,如下:

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

        而且像下面三种成员必须使用初始化列表进行初始化:

  1. const 成员变量:因为const成员具有常性不能进行赋值,而函数体是进行赋值的,所以只能在初始化列表进行初始化(具体与初始化列表的行为有关,待会叙述
  2. 引用成员变量:因为引用在定义的时候必须初始化,并且引用的实体不能更改,如果在函数体里进行,本质上是一个赋值重载。
  3. 没有默认构造的自定义类型:因为自定义类型会调用默认构造进行初始化,如果没有构造就必须显示的初始化。

1.2 初始化列表的行为

        我想大家应该都很困惑,为什么在初始化列表里面是初始化而在函数体里面就是赋值?初始化列表的执行时间是在函数体之前的,当我们调用构造函数的时候,编译器会首先去按照声明顺序进行默认的初始化,如果在初始化列表中有构造初始值则不会执行默认初始化,而去执行显式的初始化,所以无论我们有没有去写初始化列表,都是会在函数体执行之前初始化,这也就解释了为什么有些成员只能在初始化列表里面进行初始化和初始化列表的顺序与初始化顺序无关。而初始化列表对于编译器只是一个参考,可以认为编译器在执行的时候如果你(列表)有,我就按你的来,如果没有我就按照我自己的来。而const成员一旦执行了初始化就不能进行赋值,所以在语法上规定了const必须人为的给构造初始值,这也就是为什么const必须在初始化列表进行初始化了。

        而当我们执行到函数体的时候所有成员其实早就执行默认初始化初始化完成了,这个时候去进行所谓的”初始化“其实都是赋值(拷贝或者进行赋值重载)。

        无论什么时候我们都建议使用初始化列表进行初始化,首先就是不容易出错,其次就是规则上的统一。

1.3 explicit关键字

        如果构造函数只有单个参数或者除第一个参数无默认值其余均有默认值,在一些情况下会存在隐式类型转换。比如

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
//
class B
{
public:
	B(int a, int b = 1, int c = 1)
		:_a(a)
        ,_b(b)
        ,_c(c)
	{}
private:
	int _a;
	int _b;
	int _c;
};

int main()
{
	A a1 = 1;  //这俩种情况都会发生隐式类型转换 转换成一个无名类型的对象进行拷贝
	B b1 = 1;
    //C++11
    B b2 = { 1, 2, 3}; //c++11支持的括号初始化,将数组进行类型转换再构造,            
                       //想了解可以看我的c++11专栏


}

23c9ecf132be4e57b0a78ea0b99255ae.png

         对于代码中提到的括号初始化可以看我的这一篇文章:【C++11】 统一的列表初始化( {}初始化 )_子亦半截诗的博客-CSDN博客

        回归正题,上述的这种类型转换有的时候我们并不希望发生,所以就有了explicit关键字,用这个关键字修饰构造函数,将不会发生这种类型转换,同时C++11的括号初始化特性也会被禁用。

class A
{
public:
	explicit A(int a)
		:_a(a)
	{}
private:
	int _a;
};
//
class B
{
public:
	explicit B(int a, int b = 1, int c = 1)
		:_a(a)
		, _b(b)
		, _c(c)
	{}
private:
	int _a;
	int _b;
	int _c;
};
int main()
{
	A a1 = 1;  //报错:E0415	不存在从 "int" 转换到 "A" 的适当构造函数
	B b1 = 1;  //报错:E0415	不存在从 "int" 转换到 "B" 的适当构造函数
	//C++11
	B b2 = { 1, 2, 3 };   //报错:E2334	复制列表初始化不能使用标记为“显式”的构造函数
}

 报错:c5bc0a3024c047f48e01265fc3cd5e8f.png

 

 2. 类中的static成员

        类中的静态成员和类中其他的成员有很大的区别,非静态成员变量的初始化在初始化列表里面进行,但是静态成员变量不能在初始化列表中初始化,因为static成员并不属于某个对象,static成员被存放在静态区,被同类型的所有对象共享

2.1 静态成员变量

        静态成员变量属于同类型的所有对象,它的定义和初始化在类外进行。在定义的时候需要在变量名前面加上 类名:: 。

int A::_a = 0;
class A
{
public:
private:
	static int _a;
};

2.2 静态成员函数

        由于静态函数属于每一个类对象,而不属于某个对象,所以静态成员函数也就没有this指针,所以也就不能访问类的非静态成员。非静态成员函数可以调用静态成员函数,但是静态成员函数不能调用非静态函数。可能有人要疑问了:非静态成员函数不也是被共享的吗,为什么不能被静态成员函数访问呢?这是因为非静态成员函数需要传入this指针,而静态成员函数是没有this指针的。

int A::_a = 0;
class A
{
public:
	static void test() {  }
	void test1() { test(); }
private:
	static int _a;
};

3. 友元

        友元是一种突破封装的方式,让外部成员访问自己私有成员的一种方式,但过度使用会破坏封装。

3.1 友元函数

        友元函数的声明就是在类中声明函数时在前面加上friend关键字,声明成友元函数这个函数就能访问对象的私有成员。在一些特殊情况下需要使用,就比如”<<“流写入符号重载,由于"<<"的左操作数必须是ostream对象,所以第一个参数就必须是ostream类型,但是如果定义成成员函数,第一个参数就变成this指针了,这会导致我们使用”<<“的时候就变成” 对象<<cout  “,这与我们的习惯就不符,所以流写入的符号重载必须定义成全局函数,而且还必须要能访问类的私有成员,这个时候友元函数就可以发挥作用了。如下:

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << ' ' << d._month << ' ' << d._day << endl;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
  •  友元函数可以访问类的所有成员,但不是类的成员
  • 友元函数不可以用const修饰
  • 友元函数不受访问限定符限制

3.1 友元类

        如果一个类A声明了一个友元类B,那么B类中的所有成员都可以访问A类的所有成员,但A类不能访问B类的成员。友元关系不能传递和继承(继承以后再说),比如C是D的友元,D是E的友元,但C不是E的友元。

class A
{
	friend class B;
private:
	int _aa;
	int _ab;
	int _ac;
};
class B
{
	void print()
	{
		cout << _atest._aa << ' ' << _atest._ab << ' ' << _atest._ac << endl;
	}

private:
	A _atest;
};

4. 内部类

        当一个类定义在另一个类的内部,这个类就被称之为内部类。内部类相当于外部类的友元类,可以通过对象参数访问内部类中的所有成员,访问静态成员的时候不需要对象参数就可以直接访问,但是外部类不能访问内部类的成员。内部类是一个独立的类,对外部类的大小没有任何影响。外部成员对内部类的访问会受到访问限定修饰符的限制。

class A
{
public:
	class B
	{
		void print(const A& a)
		{
			cout << a._a;
		}
	};
private:
	int _a;
};

 5. 匿名对象

        匿名对象的定义就是在对象后面加上一个括号,匿名对象不用取名字,声明周期也只有一行,使用完后就自动调用析构函数销毁了。

class A
{
public:
	static int _a;
};
int A::_a = 0;

int main()
{
	A();   //匿名对象
	cout << A()._a;
}

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

        在我们传参或者传值返回的时候可能会进行频繁的构造和拷贝构造,如果对象较大,会造成极大的资源消耗,所以为此编译器做出一些优化。(只针对构造,赋值重载不会进行优化)

class A
{
public:
	A(int a = 0)
	{
		cout << "A(int a = 0)" << endl;
	}
	A(const A& aa)
	{
		cout << "A(const A & aa)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
void f1(A a)
{}
A f2()
{
	A a;
	return a;
}
int main()
{
	A a;
	f1(1);  //连续构造+拷贝构造->直接构造
	f1(A(2)); //连续构造+拷贝构造->直接构造
	A a1 = f2(); //拷贝构造+拷贝构造->优化为一个拷贝构造
}

a385f1784f714926b72221a51c924dcd.png

 

 

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

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

相关文章

设计模式之策略模式

目录 背景步骤关于策略模式&#xff0c;他的业务需求是什么样的场景&#xff1f;什么是策略模式&#xff1f;当然策略模式的实现不仅仅是策略模式&#xff0c;他和简单工厂的设计模式有什么样的关系&#xff1f;方法是什么 总结 背景 问题是最好的老师 关于策略模式&#xff0…

springboot文件上传和下载接口的简单思路

springboot文件上传和下载的简单思路 文件上传文件下载 文件上传 在springboot中&#xff0c;上传文件只需要在接口中通过 MultipartFile 对象来获取前端传递的数据&#xff0c;然后将数据存储&#xff0c;并且返回一个对外访问路径即可。一般对于上传文件的文件名&#xff0c…

在 Android 上使用机器学习套件检测人脸

须知事项 此 API 需要 Android API 级别 19 或更高级别。确保应用的 build 文件使用的 minSdkVersion 值不小于 19。 请务必在您的项目级 build.gradle 文件中的 buildscript 和 allprojects 部分添加 Google 的 Maven 代码库。 将 Android 版机器学习套件库的依赖项添加到模…

【MATLAB第68期】基于MATLAB的LSTM长短期记忆网络多变量时间序列数据多步预测含预测未来(非单步预测)

【MATLAB第68期】基于MATLAB的LSTM长短期记忆网络多变量时间序列数据多步预测含预测未来&#xff08;非单步预测&#xff09; 输入前25个时间&#xff0c;输出后5个时间 一、数据转换 1、原始数据 5列时间序列数据&#xff0c;70行样本 705 数据矩阵结构 2、数据转换 将…

【论文阅读】NoDoze:使用自动来源分类对抗威胁警报疲劳(NDSS-2019)

NODOZE: Combatting Threat Alert Fatigue with Automated Provenance Triage 伊利诺伊大学芝加哥分校 Hassan W U, Guo S, Li D, et al. Nodoze: Combatting threat alert fatigue with automated provenance triage[C]//network and distributed systems security symposium.…

2023/8/11题解

时间限制: 1000MS 内存限制: 65536KB 解题思路 建树 模拟 &#xff0c;复杂在于建树&#xff0c;此处从题目需求可知需要按层建树&#xff0c;所以需要队列模拟&#xff0c;查找比较容易就是普通的深搜 参考代码 #include<bits/stdc.h> using namespace std; vector<…

Windows 环境下 Python3 离线安装 cryptography 失败

发布Flask Web项目时&#xff0c;报错缺少Cryptography&#xff0c;于是尝试重新安装该库&#xff0c;但本机没有网络&#xff0c;只支持手动离线安装&#xff0c;尝试了pip、setup.py两种方式安装&#xff0c;结果都报错。。最后使用将安装包拷贝至本机(在其他电脑上安装的sit…

【算法挨揍日记】day01——双指针算法_移动零、 复写零

283.移动零 283. 移动零https://leetcode.cn/problems/move-zeroes/ 题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 …

gitee分支合并

合并dev分支到master&#xff08;合并到主分支&#xff09; git checkout master git merge dev //这里的dev表示你的分支名称 git push //推送到远程仓库 效果如下图 不报错就表示推送成功了&#xff0c;希望能帮助各位小伙伴

DevOps最佳实践和工具在本地环境中的概述

引言 最近&#xff0c;我进行了一次网上搜索&#xff0c;以寻找DevOps的概述&#xff0c;尽管有大量的DevOps工具和实践&#xff0c;但我无法找到一个综合的概述。因此&#xff0c;我开始了对DevOps生态系统和最佳实践的梳理&#xff0c;以创建一个整体视图,方便后续研究实践 C…

5.1 web浏览安全

数据参考&#xff1a;CISP官方 目录 Web应用基础浏览器所面临的安全威胁养成良好的Web浏览安全意识如何安全使用浏览器 一、Web应用基础 1、Web应用的基本概念 Web ( World wide Web) 也称为万维网 脱离单机Web应用在互联网上占据了及其重要的地位Web应用的发展&#xf…

K8s环境下监控告警平台搭建及配置

Promethues是可以单机搭建的&#xff0c;参考prometheus入门[1] 本文是就PromethuesGrafana在K8s环境下的搭建及配置 Prometheus度量指标监控平台简介 启动minikube minikube start 安装helm 使用Helm Chart 安装 Prometheus Operator: helm install prometheus-operator stabl…

AI:01-基于机器学习的深度学习的玫瑰花种类的识别

文章目录 一、数据集介绍二、数据预处理三、模型构建四、模型训练五、模型评估六、模型训练七、模型评估八、总结深度学习技术在图像识别领域有着广泛的应用,其中一种应用就是玫瑰花种类的识别。在本文中,我们将介绍如何使用机器学习和深度学习技术来实现玫瑰花种类的识别,并…

备忘录模式(C++)

定义 在不破坏封装性的前提下&#xff0c;捕获一-个对象的内部状态&#xff0c;并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。 应用场景 ➢在软件构建过程中&#xff0c;某些对象的状态在转换过程中&#xff0c;可能由于某种需要&#xff0c;要…

c++遍历当前windows目录

前言 设置vs的高级属性为使用多字节字符集&#xff0c;不然会报char类型的实参与LPCWSTR类型的形参类型不兼容的错误 代码 #include <iostream> #include <cstring> #include <windows.h>void listFiles(const char* dir);int main() {using namespace st…

【服务平台】Rancher运行和管理Docker和Kubernetes,提供管理生产中的容器所需的整个软件堆栈

Rancher是一个开源软件平台&#xff0c;使组织能够在生产中运行和管理Docker和Kubernetes。使用Rancher&#xff0c;组织不再需要使用一套独特的开源技术从头开始构建容器服务平台。Rancher提供了管理生产中的容器所需的整个软件堆栈。  完整软件堆栈 Rancher是供采用容器的团…

SpringBoot案例-部门管理-删除

目录 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文档 思路分析 功能接口开发 控制层&#xff08;Controllre类&#xff09; 业务层&#xff08;Service类&#xff09; 持久层&#xff08;Mapper类&#xff09; 接口测试 前后端联调 查看页面原型&a…

NIDS网络威胁检测系统-Golang

使用技术&#xff1a; Golang Gin框架 前端三件套 演示画面&#xff1a; 可以部署在linux和window上 目前已在Kali2021和Window10上进行测试成功

【瑞吉外卖】Linux学习

Linux常用命令 Linux命令初体验 Linux的命令都是由一个或几个单词的缩写构成的 命令对应英文作用lslist查看当前目录下的内容pwdprint work directory查看当前所在目录cd [目录名]change directory切换目录touch [文件名]touch如果文件不存在&#xff0c;新建文件mkdir [目录…

Redis_哨兵模式

9. 哨兵模式 9.1 简介 当主库宕机&#xff0c;在从库中选择一个&#xff0c;切换为主库。 问题: 主库是否真正宕机?哪一个从库可以作为主库使用?如何实现将新的主库的信息通过给从库和客户端&#xff1f; 9.2 基本流程 哨兵主要任务&#xff1a; 监控选择主库通知 会有…