【C++入门】类和对象(完)
前言
在谈论C++时,常常会涉及到一些高级特性和概念,比如初始化列表、static成员、友元、内部类、匿名对象等。这些概念在C++编程中起着非常重要的作用,对于想要深入了解C++语言的开发者来说,掌握这些知识是至关重要的。本文,我们将深入探讨这些概念,探讨它们的作用以及如何在实际中的应用。
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;
};
这是最开始所说的构造函数初始化的一种方法,这种方法实质上不能被成为初始化,构造函数体中的语句只能将其称为赋初值,初始化只能初始化一次,而构造函数体内可以多次赋值。
回到初始化列表。
初始化列表:以一个冒号开始,逗号分割,每个" 成员变量 "后面跟一个放在括号中的初始值或表达式。
比如:
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
定义一个类的对象时是这样的:
Date d1(2023,11,20);
这是对象的整体定义,成员变量的定义在哪儿?就是在初始化列表。
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
对于一些特殊情况,就必须要使用初始化列表进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
初始化列表也可以与构造函数函数体内初始化结合使用:
这个是一个测试样例:
class A
{
public:A(int a):_a(a){}
private:int _a;
};Date(int year, int month, int day)
{_year = year;_month = month;_day = day;_n = 1; //报错:error C2530: “Date::_ref”: 必须初始化引用_ref = year; //报错:error C2789: “Date::_n”: 必须初始化常量限定类型的对象
}
private:int _year;int _month;int _day;int& _ref;const int _n;};
_ref和_n如果在函数体内初始化就会报错,const修饰会让其具备常属性,也就是说该变量只能在定义时初始化,引用类型也是,在定义时必须要初始化。
正确的构造函数写法应该是:
Date(int year, int month, int day): _n(1), _ref(_year)
{_year = year;_month = month;_day = day;
}
接下来就是它在C++语法设计时的细节问题:
class Date
{
public:Date(int year, int month, int day): _n(1) //类成员定义, _ref(year){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;int& _ref;const int _n;
};int main()
{Date d1(2023, 11, 20);// 对象整体定义return 0;
}
这里C++语法设计的有点过于复杂,构造函数执行完初始化列表内容之后,函数体内的成员变量也已经被定义
在没有执行赋值初始化操作之前,内置类型默认的随机值(但此时的全部成员变量都是已经被定义了的)。
其次就是自定义类型:
测试代码,比如:
class A
{
public:A(int a = 0) //注意这里给了缺省值:_a(a){}
private:int _a;
};class Date
{
public:Date(int year, int month, int day): _n(1), _ref(year){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;int& _ref;const int _n;A _obj;
};int main()
{Date d1(2023, 11, 20);return 0;
}
注意观察以下调试过程:
在初始化列表进行初始化时,自定义类型会自动调用默认构造函数。
注意:自定义类型A在构造函数中给了缺省值!!!
如果不给缺省值,就会直接报错(没有默认构造函数): error C2512: “A”: 没有合适的默认构造函数可用
解决方法有两种:
- 在A的构造函数中给缺省值
- 使用初始化列表
Date(int year, int month, int day): _n(1), _ref(year), _obj(10)
{_year = year;_month = month;_day = day;
}
之前我们提到C++打补丁支持声明时给缺省值:
class Date
{
public:Date(int year, int month, int day): _n(1), _ref(year), _obj(10){_year = year;_month = month;_day = day;}
private:int _year = 1; //在声明中给缺省值int _month;int _day;int& _ref;const int _n;A _obj;
};
这个缺省值其实就是给初始化列表的,在初始化列表中如果没有显示要求初始化就会使用这里的缺省值。
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
当然也有初始化列表无法完成的情况:
有些初始化或检查的任务,初始化列表无法完成,比如:
class Stack
{
public:Stack(int n = 2):_a((int*)malloc(sizeof(int)*n)),_top(0),_capacity(n){if (_a == nullptr){perror("malloc fail");exit(-1);}memset(_a, 0, sizeof(int) * n);}
private:int* _a;int _top;int _capacity;
};
使用函数初始化数组的值,或者是开空间失败后的异常处理。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
看以下这个代码:
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};int main()
{A aa(1);aa.Print();
}
它的输出结果是什么?
答案是_a1 = 1和_a2 = 随机值,声明次序就是其在初始化列表中的初始化顺序。所以它先初始化的是_a2,_a2使用_a1的值进行初始化,但此时_a1还未执行初始化,此时的_a1为随机值。
1.2 explicit关键字
看下面这段代码:
class A
{
public:A(int a):_a(a){}int _a = 0;};
void Test()
{A a1(1);A a2 = 2;// 内置类型隐式转换成自定义类型}
a2在进行初始化时发生了隐式转换:实际编译器背后会用2构造一个无名对象,最后用无名对象给a2对象进行赋值。
能支持整形2转换,是有A的int 单参数构造函数支持,如果使用int* 类型就不支持隐式类型转换了。
想要支持可以添加一个含int* 的单参数构造函数:
class A
{
public:A(int a):_a(a){}A(int* p){}int _a = 0;};
如果不想让这个隐式类型转换(强制类型转换还是可以的),就可以使用explicit关键字来修饰构造函数。
class A
{
public:explicit A(int a):_a(a){}int _a = 0;};
前边的示例是单参数,那多参数呢?
class Date
{
public:Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};
如上,也是可以的,支持一个传参的半缺省(全缺省也可以)也是可以隐式类型转换。那可不可以支持多个内置类型隐式转换,C++11是可以的:
Date d1 = { 2023, 11, 10 };
但是只能如上这种写法。
多参数也是一样,如果不想要隐式类型转换,可以给构造函数加explicit。
2. static成员
static修饰有什么作用?
static修饰的成员变量或成员函数在类加载时执行,并且只会执行一次。
问题:实现一个类,计算程序中创建出了多少个类对象
使用全局变量计算:
int n = 0;class A
{
public:A() { ++n; }A(const A& t) { ++n; }~A() {}private:};A func()
{A aa;return aa;
}int main()
{n++;A aa;func();n++;cout << n << endl;return 0;
}
当前边存在修改n的情况,结果就不准确了,其次就是每次使用时都要将n置为0(其他类也可能使用过n);那有没有专属这个类的计数变量呢?使用static就可以很好的解决这个问题。
2.1 概念
用static修饰的成员变量,称之为静态成员变量;
用static修饰的成员函数,称之为静态成员函数。
静态成员变量一定要在类外进行初始化
class A
{
public:A() { ++count; }A(const A& t) { ++count; }~A() {}static int GetACount() { return count; }
private:static int count;
};int A::count = 0;
A func()
{A aa;return aa;
}int main()
{A aa;func();cout << A::GetACount() << endl;return 0;
}
2.2 特点
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
总结:静态成员变量和静态成员函数,可以理解为受限制的全局变量和全局函数。专属于某个类,受类域和访问限定符的限制。
3. 匿名对象
匿名对象其实很简单我们看下面这个代码:
class Date
{
public:Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};int main()
{vector<Date> v;Date d1(2023, 11, 1);v.push_back(d1);return 0;
}
在C++中的STL容器中我们要存储日期类的对象。每次存储还要创建一个对象,然后再存储,这样很麻烦,这里我们就可以使用匿名函数:
v.push_back(Date(2023, 10, 31));
匿名对象在这样场景下就很好用。匿名对象的生命周期只有这一行,执行完之后自动调用析构函数。
4. 友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以
友元不宜多用
友元分为:友元函数和友元类
4.1 友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字
前边在实现Date类的流插入、流提取中提到过。
友元函数的特点:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
4.2 友元类
友元类(friend class)是指一个类可以访问另一个类的私有成员和保护成员。
比如:
class A {
private:int privateData;
protected:int protectedData;friend class B; // B是A的友元类
};class B {
public:void accessA(A& a) {// 友元类B可以访问A的私有成员和保护成员a.privateData = 10;a.protectedData = 20;}
};
注意:
- 友元关系是单向的
比如:类A声明类B为友元类,那么类B可以访问类A的私有成员和保护成员,但类A不能访问类B的私有成员和保护成员。
- 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元
- 友元关系不能继承
5. 内部类
什么是内部类?
一个类定义在另一个类的内部
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限
比如:
class A
{
private:int h;
public:class B // B天生就是A的友元{public:void fun(const A& a){cout << a.h << endl;//OK}};
};
A于B的关系:
可以理解为B就是一个普通类,它只受A的类域和访问限定符限制,
int main()
{A aa;//B bb //error C2065: “B”: 未声明的标识符A::B bb;cout << sizeof(A) << endl; // 4return 0;
}
内部类天生就是外部类的友元
*6. 拷贝构造时的一些优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝。
比如:
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A aa1 = 1; return 0;
}
A aa1 = 1;
它的过程是什么样的?它的过程其实分为两步
1、先用1构造一个临时对象 2、再用临时对象拷贝构造aa1
但我们在调试时会发现最终编译器显示的是一次构造。这其实就是由于编译器的优化。
同一个表达式中,连续构造+构造 / 构造+拷贝构造 / 拷贝构造+拷贝构造 会合二为一
合并规则如下:
- 构造+构造->构造
- 构造+拷贝构造->构造
- 拷贝构造+拷贝构造->拷贝构造
编译器不同优化效果也不同,老的编译器不会怎么优化,但新的编译器基本都会做这样的优化,甚至优化的比这个更厉害。
总结
在C++编程中,掌握初始化列表、static成员、友元和内部类等相关知识是非常重要的。这些概念不仅能够帮助我们更好地理解C++语言的特性,还能够提高我们的编程效率和代码质量,以上便是本文全部内容,希望可以对你有所帮助,感谢阅读!
相关文章:

【C++入门】类和对象(完)
前言 在谈论C时,常常会涉及到一些高级特性和概念,比如初始化列表、static成员、友元、内部类、匿名对象等。这些概念在C编程中起着非常重要的作用,对于想要深入了解C语言的开发者来说,掌握这些知识是至关重要的。本文,…...

webshell检测方式深度剖析 --- Pixy系列二(数据流分析)
开篇 书接上文,这次我们来聊聊数据流分析,数据流分析的内容非常广泛,我们力求深入浅出通俗易懂,在简短的篇幅内将这一概念描述清楚。 简单来说,数据流分析是一种用来获取相关数据沿着程序执行路径流动的信息分析技术…...

[DAU-FI Net开源 | Dual Attention UNet+特征融合+Sobel和Canny等算子解决语义分割痛点]
文章目录 概要I Introduction小结 概要 提出的架构,双注意力U-Net与特征融合(DAU-FI Net),解决了语义分割中的挑战,特别是在多类不平衡数据集上,这些数据集具有有限的样本。DAU-FI Net 整合了多尺度空间-通…...

使用Triton部署ONNX模型
介绍 适用于各种 AI 工作负载的推理:借助 NVIDIA Triton™,在任何处理器(GPU、CPU 或其他)上,对使用基于任何框架的,经过训练的机器学习模型或深度学习模型,进行推理部署。Triton 是 NVIDIA AI…...
Python访问ElasticSearch
ElasticSearch是广受欢迎的NoSQL数据库,其分布式架构提供了极佳的数据空间的水平扩展能力,同时保障了数据的可靠性;反向索引技术使得数据检索和查询速度非常快。更多功能参见官网介绍 https://www.elastic.co/cn/elasticsearch/ 下面简单罗列…...

Flutter 混合开发 - 动态下发 libflutter.so libapp.so
背景 最近在做包体积优化,在完成代码混淆、压缩,裁剪ndk支持架构,以及资源压缩(如图片转webp、mp3压缩等)后发现安装包的中占比较大的仍是 so 动态库依赖。 具体查看发现 libflutter.so 和 libapp.so 的体积是最大的&…...

Peter算法小课堂—动态规划
Peter推荐算法书:《算法导论》 图示: 目录 钢条切割 打字怪人 钢条切割 算法导论(第四版)第十四章第一节:钢条切割 题目描述: 给定一根长度为 n 英寸的钢条和一个价格表 ,其中 i1,2,…,n …...

2022–2023学年2021级计算机科学与技术专业数据库原理 (A)卷
一、单项选择题(每小题1.5分,共30分) 1、构成E—R模型的三个基本要素是( B )。 A.实体、属性值、关系 B.实体、属性、联系 C.实体、实体集、联系 D.实体、实体…...

Clojure 实战(4):编写 Hadoop MapReduce 脚本
Hadoop简介 众所周知,我们已经进入了大数据时代,每天都有PB级的数据需要处理、分析,从中提取出有用的信息。Hadoop就是这一时代背景下的产物。它是Apache基金会下的开源项目,受Google两篇论文的启发,采用分布式的文件…...

Django 分页(表单)
目录 一、手动分页二、分页器分页 一、手动分页 1、概念 页码:很容易理解,就是一本书的页码每页数量:就是一本书中某一页中的内容(数据量,比如第二页有15行内容),这 15 就是该页的数据量 每一…...

socket实现视频通话-WebRTC
最近喜欢研究视频流,所以思考了双向通信socket,接下来我们就一起来看看本地如何实现双向视频通讯的功能吧~ 客户端获取视频流 首先思考如何获取视频流呢? 其实跟录音的功能差不多,都是查询电脑上是否有媒体设备,如果…...

simulink代码生成(九)—— 串口显示数据(纸飞机联合调试)
纸飞机里面的协议是固定的,必须按照协议配置; (1)使用EasyHEX协议,测试int16数据类型 测试串口发出的数据是否符合? 串口接收数据为: 打开纸飞机绘图侧: (1)…...
Mysql数据库(中)——增删改查的学习(全面,详细)
上一篇主要对查询操作进行了详细的总结,本篇主要对增删改操作以及一些常用的函数进行总结,包括流程控制等;以下的代码可以直接复制到数据库可视化软件中,便于理解和练习; 常用的操作: #函数: S…...

test dbtest-03-对比 Liquibase、flyway、dbDeploy、dbsetup
详细对比 Liquibase、flyway、dbDeploy、dbsetup,给出对比表格 下面是一个简要的对比表格,涵盖了 Liquibase、Flyway、dbDeploy 和 DbSetup 这四个数据库变更管理工具的一些主要特点。 特点/工具LiquibaseFlywaydbDeployDbSetup开发语言Java࿰…...
力导向图与矩阵排序
Graph-layout force directed(力导向图布局)是一种用于可视化网络图的布局算法。它基于物理模型,模拟了图中节点之间的相互排斥和连接弹性,以生成具有良好可读性和美观性的图形布局。 在力导向图布局中,每个节点被视为…...

word 常用功能记录
word手册 多行文字对齐标题调整文字间距打钩方框插入三线表插入参考文献自动生成目录 多行文字对齐 标题调整文字间距 打钩方框 插入三线表 插入一个最基本的表格把整个表格设置为无框线设置上框线【实线1.5磅】设置下框线【实线1.5磅】选中第一行,设置下框线【实线…...

C#线程基础(线程启动和停止)
目录 一、关于线程 二、示例 三、生成效果 一、关于线程 在使用多线程前要先引用命名空间System.Threading,引用命名空间后就可以在需要的地方方便地创建并使用线程。 创建线程对象的构造方法中使用了ThreadStart()委托,当线程开始执行时,…...
如何利用ChatGPT来提高编程效率
如何利用ChatGPT来提高编程效率 在当今这个信息爆炸和技术快速发展的时代,程序员们面临着巨大的压力,既要保证代码的质量,又要提高工作效率。幸运的是,人工智能(AI)正在改变我们编写和维护代码的方式,而OpenAI的ChatGPT是其中的佼佼者。本文将讨论如何利用ChatGPT以及结合…...

java智慧工地源码,互联网+建筑工地,实现对工程项目内人员、车辆、安全、设备、材料等的智能化管理
智慧工地全套源码,微服务JavaSpring Cloud UniApp MySql;支持多端展示(大屏端、PC端、手机端、平板端)演示自主版权。 智慧工地概念: 智慧工地就是互联网建筑工地,是将互联网的理念和技术引入建筑工地&…...
创建并使用自己的C++模块(Windows10+MSVC)
module是C20种新引入的特性,关于module的介绍和好处,网上已有大量的文章,此处也不再赘述,本文仅记录在个人的环境上创建一个简单的module并使用这个module。 环境同上一篇文章( windows10,MSVC C工具链&am…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...