【C++】类和对象(三)
目录
一、构造函数补充
1、初始化列表
1.1、初始化列表概念
1.2、初始化列表性质
2、explicit关键字
二、static成员
1、概念及使用
2、性质总结
三、友元
1、友元函数
2、友元类
四、内部类
五、拷贝对象时的一些编译器优化
一、构造函数补充
在《类和对象(二)》中,我们已经学习了关于构造函数的大部分内容,然而这些内容还不足以解决全部的问题。
例如,当我们写下如下代码时,运行程序:
class Test
{
public:private:int _a;int _b;
};int main()
{Test t;return 0;
}
程序运行成功:
但是我们再在成员变量中增加一个 const 类型变量时:
程序报错了,这是因为我们在定义 const 类型变量时,必须要进行初始化。但是由于默认生成的构造函数对于内置类型不做处理,也就无法对 const 类型变量进行初始化,因此而报错。
虽然这里的问题可以通过 C++11 新增的针对内置类型成员打的补丁来解决,即:内置类型成员变量在类中声明时可以给默认值。但是在 C++11 之前这个问题是如何解决的呢?
为了解决这个问题,我们引入一个新的概念:初始化列表。
1、初始化列表
1.1、初始化列表概念
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 "成员变量" 后面跟
一个放在括号中的初始值或表达式。
形如:
class Test
{
public:Test():_a(1) //初始化列表, _b(2), _c(3){//构造函数的函数体}
private:int _a;int _b;const int _c;
};int main()
{Test t;return 0;
}
初始化列表是成员变量定义的地方。不管我们写没写初始化列表,编译器都会自己过一遍初始化列表。编译器会把我们在初始化列表中写了的成员变量按照我们写的来进行初始化,把我们没在初始化列表中写的成员变量初始化成默认值。
对于普通的内置类型成员变量是可以初始化成默认值的,因为之后还可以随意修改。但是对于 const 类型的成员变量因为无法再修改,所以必须要被初始化为一个有意义的初始值。
与之同理的还有引用类型的成员变量:
除了以上两类成员变量,还有一类成员变量也必须要放在初始化列表位置进行初始化,那就是自定义类型成员(且该类没有默认构造函数时)。
因为该类没有默认构造函数,所以在初始化时必须要传参,否则会报错。这里可以结合《类和对象(二)》中默认构造函数对于自定义类型成员变量会调用它的默认构造函数来理解,原因就是编译器会自动走一遍初始化列表,遇到自定义类型成员变量会做出对应的处理。
总结:类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
对于其他类型的成员变量,则就算不在初始化列表中写出来也不会报错。
1.2、初始化列表性质
我们推荐尽量使用初始化列表进行初始化。因为不管你是否使用初始化列表,编译器对于所有成员变量都一定会走一遍初始化列表,并使用初始化列表进行初始化。
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
我们来看如下代码:
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 。又因为在初始化列表中,_a2 是使用 _a1 来初始化的,而此时 _a1 尚且还是一个随机值,所以就导致 _d2 被初始化成了随机值。
2、explicit关键字
我们来看如下代码:
class A
{
public:A(int a):_a1(a){}void Print(){cout << _a1 << endl;}
private:int _a1;
};int main()
{A aa1(1);A aa2 = 1;aa1.Print();aa2.Print();
}
在编译器中运行:
可以发现对象 aa1 、 aa2 的成员变量都被初始化为了 1 。
这是不是说明这两种写法的意义是一样的呢?其实不是的,第一种写法是调用了构造函数来初始化的。而第二种写法实际上是一个隐式类型转换,把整型数字 1 进行类型转换,转换成类类型存储到类类型临时变量中,再把该临时变量赋值给 aa2 。
写一行代码来证明一下:
A& ref = 10;
代码报错,显示 int 无法转换为 A& 类型,这是因为临时变量具有常性,不可更改,所以我们要把 ref 改为 const 类型:
const A& ref = 10;
程序运行成功。
如果我们不希望发生这种隐式类型转换,则可以使用关键字: explicit 。
此时,第二种写法就已经不被允许了。用 explicit 修饰构造函数,将会禁止构造函数的隐式转换。
补充说明: 类型转换针对的是单参数构造函数,C++98 不支持多参数构造函数。但是在C++11 中对此进行了拓展,使多参数构造函数也支持隐式类型装换了。形如:
A aa3 = {2, 2};
二、static成员
1、概念及使用
声明为 static 的类成员称为类的静态成员。用 static 修饰的成员变量,称之为静态成员变量。用
static 修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
静态成员不属于某个对象,而是属于所有对象,属于整个类。
例如,我们实现了一个类,现在想要计算程序中创建出了多少个类对象,就可以使用静态成员变量来计算:
class A
{
public:A(int a = 0){++count;}A(const A& aa){++count;}//读取私有的成员变量 count int GetCount(){return count;}
private:static int count; //声明
};int A::count = 0; //定义初始化int main()
{A aa1;A aa2(aa1);return 0;
}
静态变量是被存放在静态区的,被所有类对象共用。又因为静态变量 count 是在类域中声明的,所以变量名也不会与外界的变量名相互冲突。
需要注意的是,静态变量的初始化不能在类内进行,只能放在类外。这是因为 count 作为静态变量被所有对象共用,不应该在初始化列表中被初始化,在初始化列表中进行初始化的变量是单独属于某个对象的。
所以静态变量的声明放在类内,而定义是放在全局的。在全局定义的时候要加上 域名: : 。
因为我们实例化了两个对象 aa1 、aa2 ,所以 count 的值为 2 ,所有对象都共用一个 count 。
因为静态变量 count 是存放在静态区的,而不是对象内,所以蓝色方框框起的 "->" 符号没有访问到对象内部的数据,只起到了提示域名的作用,不属于解引用。具体相关知识可以参考《类和对象(一)》。
但是如果当前函数作用域内没有对象的话,使用起来就会有些麻烦,像下面这样:
由于 main 函数中没有对象,也就无法通过对象调用 GetCount 函数来读取 count 的值。于是只能专门实例化出一个对象来读取,同时还要把读取到的 count 减去一,去掉这个我们新定义出来的没有其他实际意义的对象。
补充内容:因为我们实例化出对象 aa ,仅仅只是为了在这个地方使用一次,一次过后就不会再去使用它。所以这里可以使用一个特殊的对象:匿名对象来简化代码。
在《类和对象(二)》中,我提到过,在实例化对象时,不可以写成这种形式:
A aa();
因为编译器无法区分这段代码是一个函数的声明,还是调用默认构造函数。但是下面这种写法是可以的:
A();
意为实例化了一个匿名对象,他的特点是生命周期只存在于这一行,刚好符合我们只调用一次的需求,所以读取 count 的值时,我们也可以这样写:
但是这样写起来的话还是太过于麻烦,也不够优雅。为了解决这个问题,我们再来学习一个东西:静态成员函数。
静态成员函数是在成员函数前面使用 static 修饰。他没有 this 指针,于是我们可以直接调用该函数:
同时,由于静态成员函数没有 this 指针,也就没有办法访问非静态成员。可以说静态成员函数就是为了静态成员变量而生的。
有了上面的知识,我们来看一下下面这段代码创建了多少了对象:
void func()
{A aa1;A aa2(aa1);A aa3[10];
}
int main()
{func();cout << A::GetCount() << endl; return 0;
}
答案是 12 个,因为 aa3[10] 是一个容量为 10 的自定义类型数组,也就调用了 10 次构造函数。
2、性质总结
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
三、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以
友元不宜多用。友元分为:友元函数和友元类。
1、友元函数
问题:现在尝试去重载 operator<< ,然后发现没办法将 operator<< 重载成成员函数。因为 cout 的输出流对象和隐含的 this 指针在抢占第一个参数的位置。 this 指针默认是第一个参数也就是左操作数了。但是实际使用中 cout 需要是第一个形参对象,才能正常使用。所以要将 operator<< 重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。 operator>> 同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加 friend 关键字。
关于友元函数的说明:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
2、友元类
除了函数可以是类的友元之外,类也可以是类的友元。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
关于友元类的说明:
- 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。 - 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。 - 友元关系不能继承,在继承位置再给大家详细介绍
四、内部类
内部类的概念:如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。
我们写一个内部类来观察一下:
B类定义在A类内部,但是A类对象 aa 的大小为 4 个字节,只占了一个整型的空间。这是因为A类里面只有 a ,而没有 b 。
其实内部类仅仅只是定义在了另一个类的里面而已,和定义在全局并没有什么区别,只是内部类受到了外面这个类的类域的限制。
如果想要使用内部类,则需要在前面说明内部类的域:
A::B bb;
如果内部类是外部类的私有类型,则无法直接使用内部类:
需要注意的是:内部类是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元类。
五、拷贝对象时的一些编译器优化
首先是我们上面介绍过的隐式类型转换:
按照正常的逻辑,编译器会首先调用构造函数来创建一个类类型的临时变量,然后再调用拷贝构造函数使 aa1 变为类类型临时变量的拷贝。为了简化过程,编译器把拷贝构造 + 构造优化为了直接构造。
需要注意的是,这种优化只能存在于同一表达式,构造完成之后直接把获得的临时变量用于拷贝构造的情况。
适用这种情况的除了赋值、连续赋值之外,还有一些其他的情况,如传值传参:
这两行代码中,传值传参时所进行的构造 + 拷贝构造被优化为直接构造。
传引用传参则因为无需拷贝而无需优化。
当拷贝构造用于返回值时,例如以下场景:
class A
{
public:A(int a = 1):_a(a){}
private:int _a;
};A func()
{A aa;return aa;
}int main()
{func();A aa1 = func();return 0;
}
按照正常逻辑,这里共需要一次构造,两次拷贝构造:
而编译器进行优化时,会把这个过程优化为一个构造加一个拷贝构造,去除同一表达式中的冗余部分。
如果我们直接返回匿名对象时,例如:
编译器同样会把同一表达式中多余的步骤优化掉。
从以上各种例子中,我们可以知道在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
同学们需要注意的是,这里有一个容易弄混的地方:
这两种方式是不同的,第一个方框框起的代码属于拷贝构造,而第二个方框框起的属于赋值重载。编译器可以优化第一种而没有办法优化第二种。
了解了以上知识,我们日后写代码时就可以有意识的遵守三点规则:
- 接收返回值对象,尽量用拷贝构造方式接收,而不要赋值重载方式接收。
- 函数中返回对象尽量返回匿名对象。
- 传参时尽量使用传引用传参。
关于类和对象的相关知识就讲到这里,希望同学们多多支持,如果有不对的地方欢迎大佬指正,谢谢!
相关文章:

【C++】类和对象(三)
目录 一、构造函数补充 1、初始化列表 1.1、初始化列表概念 1.2、初始化列表性质 2、explicit关键字 二、static成员 1、概念及使用 2、性质总结 三、友元 1、友元函数 2、友元类 四、内部类 五、拷贝对象时的一些编译器优化 一、构造函数补充 在《类和对象&#x…...

vTESTstudio - VT System CAPL Functions - General/Trigger Function
前面文章中我们已经介绍了常用的几种板卡的基本信息,那这些板卡该如何去通过软件调用呢?带着这个问题我们开始新的一块内容 - VT系统相关的自动化控制函数介绍,我会按照不同的板卡来分类,对其可控制的函数进行介绍,方便…...
IDEA 快捷键
ctrlD :复制当前行到下一行 ctrlO : 重写当前类的方法 ctrlshiftu : 大小写转化 Alt 上/下 :跳到上一个、下一个函数 Alt 左/右 : 回到上一个、下一个文件 Alt 回车 : 代码修正 Alt Insert : 插入代码 Ctrl Alt L …...
2023新华为OD机试题 - 入栈出栈(JavaScript) | 刷完必过
入栈出栈 题目 向一个空栈中依次存入正整数 假设入栈元素N(1 <= N <= 2^31-1) 按顺序依次为Nx ... N4、N3、N2、N1, 当元素入栈时,如果N1=N2+...Ny (y的范围[2,x],1 <= x <= 1000) 则N1到Ny全部元素出栈,重新入栈新元素M(M=2*N1) 如依次向栈存储6、1、2、3,当存…...

微信公众号扫码授权登录思路
引言 上学期研究了一下微信登录相关内容,也写了两三篇笔记,但是最后实际登录流程没有写,主要因为感觉功能完成有所欠缺,一直也没有好的思路;这两天我又看了看官方文档,重新构思了一下微信公众号登录相关的…...
数据结构与算法基础-学习-10-线性表之顺序栈的清理、销毁、压栈、弹栈
一、函数实现顺序栈的其他函数实现,请看之前的博客链接《数据结构与算法基础-学习-09-线性表之栈的理解、初始化顺序栈、判断顺序栈空、获取顺序栈长度的实现》。1、ClearSqStack(1)用途清理栈的空间。只需要栈顶指针和栈底指针相等ÿ…...

Hazel游戏引擎(005)
本人菜鸟,文中若有代码、术语等错误,欢迎指正 我写的项目地址:https://github.com/liujianjie/GameEngineLightWeight(中文的注释适合中国人的你) 文章目录前言关键操作代码文件关键代码代码流程代码文件关键代码exter…...

牛客网Python篇数据分析习题(四)
1.现有一个Nowcoder.csv文件,它记录了牛客网的部分用户数据,包含如下字段(字段与字段之间以逗号间隔): Nowcoder_ID:用户ID Level:等级 Achievement_value:成就值 Num_of_exercise&a…...
盲盒如何创业?
所谓的“盲盒”,受众群体大部分是那些爱碰运气的人,顾客买的是那种在打开盲盒时一刹那的惊喜感和神秘感,在打开盲盒之前,谁也不知道自己会得到什么,这也是为什么消费者更愿意购买的原因。网上的盲盒,主要是…...
第1集丨Java中面向对象相关概念汇总
目录一、基本概念1.1 类1.2 属性1.3 方法1.4 静态1.5 包1.6 import二、高级概念2.1 构造方法2.2 继承2.3 super & this2.4 多态2.5 方法重载2.6 方法重写2.7 访问权限2.8 内部类2.9 final2.10 抽象2.11 接口2.12 匿名类面向对象的编程思想力图使计算机语言中对事物的描述与…...

高性能(二)
三、读写分离和分库分表 1.读写分离 1.1 概述 将数据库的读写操作分散到不同的数据库节点上 通常一主多从一台主数据库负责写,多台从数据库负责读。 主库和从库之间会进行数据同步,以保证从库中数据的准确性。 1.2 问题及解决 1.2.1 问题 主从同…...

Allegro如何实现同一个屏幕界面分屏显示操作指导
Allegro如何实现同一个屏幕界面分屏显示操作指导 在做PCB设计的时候,会需要分屏显示,比如一边是放大的视图,另外一边是缩小的视图,Allegro支持同一个屏幕界面下进行分屏显示,如下图 而且会实时同步起来 如何分屏,具体操作如下 点击View...

前后端一些下载与配置(第二篇 第10天过后)nuxt banner redis 短信服务
NUXT 应该是不用怎么装? 有现成的 axios 还需要在npm吗 好像已经有现成的了 banner banner 笔记汇总P396 Redis Linux安装redis tar -xzvf redis-6.2.6.tar.gz cd redis-6.2.6 照着他做 然后 cd /usr/local/redis/bin ./redis-server /usr/local/redis…...
OSG三维渲染引擎编程学习之四十八:“第五章:OSG场景渲染” 之 “5.6 多重纹理映射”
目录 第五章 OSG场景渲染 5.6 多重纹理映射 5.6.1 多重纹理映射介绍 5.6.2 多重纹理映射示例...

对Node.js 的理解?优缺点?应用场景?
一、是什么 Node.js 是一个开源与跨平台的 JavaScript 运行时环境 在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能 可以理解为 Node.js 就是一个服务器端的、非阻塞式I/…...
Bean的生命周期
所谓的生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期~~ Bean的生命周期分为以下五大部分: 实例化(为 Bean 分配内存空间) 设置属性(Bean对象注入/装配) 初…...
Python学习-----函数2.0(函数对象,名称空间,作用域-->全局变量与局部变量)
目录 前言: 1.函数对象 (1)函数对象的引用 (2)函数可以放到序列里面 (3)函数可以作为参数 , 传递给另一个函数 2.名称空间 3.作用域 (1)作用域的理解 …...
Java中Json字符串和Java对象的互转
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。诞生于 2002 年。易于人阅读和编写。同时也易于机器解析和生成。JSON 是目前主流的前后端数据传输方式。 JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的…...
代码随想录NO42 | 动态规划_Leetcode70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数
动态规划_Leetcode70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数70. 爬楼梯 (进阶) 在原题基础上,改为:一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶…...

【C++从入门到放弃】初识C++(基础知识入门详解)
🧑💻作者: 情话0.0 📝专栏:《C从入门到放弃》 👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢! C基础…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...