【C++初阶】--类和对象(下)

目录

一.const成员

 1.权限放大问题

2.权限的缩小 

二.再谈构造函数 

1.构造函数体赋值 

2.初始化列表 

(1)概念 

(2)使用 

①在对象实例化过程中,成员变量先依次进行初始化

②再进行函数体内二次赋值 

3.explicit关键字 

(1)C++为什么要存在自动隐式类型转换这样的东西呢?

 (2)explicit关键字

​编辑

(3)知识补充:多参数的函数进行隐式类型转换

(4)总结:缺省值的各种写法

三.static成员 

1.概念 

2.使用 

①面试题:A类型创建了多少个对象? (统计构造了多少次) 

四.友元 

1.友元函数 

2.友元类 

五.内部类 

1.概念 

2.特性 

六.匿名对象 


一.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

 1.权限放大问题

class Date
{
public:Date()//构造函数{_year = 1;_month = 1;_day = 1;}Date(int year=1,int month=1,int day=1)//带参构造函数(函数重载){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{const Date d1(2024, 1, 31);d1.Print();return 0;
}

这里存在一个权限被放大的问题: 

  • 优化 
//权限的平移
void Print() const
{cout << _year << "-" << _month << "-" << _day << endl;
}

2.权限的缩小 

class Date
{
public:Date()//构造函数{_year = 1;_month = 1;_day = 1;}Date(int year=1,int month=1,int day=1)//带参构造函数(函数重载){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d2(2024, 3, 31);d2.Print();return 0;
}

  • 注意 

权限的放大只有指针和引用才存在,而拷贝是不影响的。

	//可以运行const int i = 0;int j = i;//报错const int* p1 = &i;int* p2 = p1;

二.再谈构造函数 

1.构造函数体赋值 

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。  

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

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

2.初始化列表 

(1)概念 

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。  

class Date
{
public://初始化列表Date(int year, int month, int day): _year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};

(2)使用 

  • 注意事项

类中包含以下成员,必须放在初始化列表位置进行初始化:

  1. 引用成员变量
  2. const成员变量
  3. 自定义类型成员(且该类没有默认构造函数时)  
 class A
{
public://A(int a = 0, int b = 1)A(int a, int b):_a(a){cout << "A(int a = 0)" << endl;}private:int _a;
};
class Date
{
public:// 初始化列表是每个成员变量定义初始化的位置// 能用初始化列表就建议用初始化列表Date(int year, int month, int day, int& x):_year(year),_month(month),_day(day),_n(1),_ref(x),_aa(1, 2),_p((int*)malloc(sizeof(4) * 10)){if (_p == nullptr){perror("malloc fail");}for (size_t i = 0; i < 10; i++){_p[i] = 0;}}private:// 声明int _year;int _month;int _day;// 必须走初始化const int _n;int& _ref;A _aa;int* _p;
};

①在对象实例化过程中,成员变量先依次进行初始化
  • 图示解析 

②再进行函数体内二次赋值 

总结:尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。  

3.explicit关键字 

  • 知识引入 

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。 

class C
{
public:C(int x = 0):_x(x){}C(const C& cc){cout << "C(const C& cc)" << endl;}private:int _x;
};int main()
{C cc1(1);// 单参数构造函数支持隐式类型的转换C cc2 = 2;//2 用来构造一个临时对象,再进行拷贝构造-> 编译器优化了,同一个表达式连续步骤的构造,一般会被合二为一return 0;
}

  • 回顾:一个临时变量不能引用 

(1)C++为什么要存在自动隐式类型转换这样的东西呢?

  • 传统进行栈的插入 
class Stack
{
public:void Push(const C& c){//}
};
int main()
{Stack st;C cc4(3);st.Push(cc4);return 0;
}

在插入该类时,我们还需要在创建一个该类型的对象,非常麻烦。 

  • 有了自动隐式类型转换后的栈插入
class Stack
{
public:void Push(const C& c){//}
};
int main()
{Stack st;C cc4(3);st.Push(4);return 0;
}

这里我们将4直接传给了st.Push(),而该成员函数的参数类型是自定义类型,但由于存在隐式类型的自动转换,我们可以直接传过去了!非常爽!

 (2)explicit关键字

有些地方我们并不想发生隐式类型的自动转换,这是添加explicit关键字就可以解决这个问题了。

class C
{
public:explicit C(int x = 0):_x(x){}C(const C& cc){cout << "C(const C& cc)" << endl;}private:int _x;
};int main()
{C cc1(1);C cc2 = 2;return 0;
}

(3)知识补充:多参数的函数进行隐式类型转换

class A
{
public://explicit A(int a1, int a2)A(int a1, int a2):_a1(a1),_a2(a2){}private:int _a1;int _a2;
};int main()
{//隐式类型转换A aa1 = { 1, 2 };const A& aa2 = { 1, 2 };//构造的话就正常构造A aa2(2,3)return 0;
}

(4)总结:缺省值的各种写法

class B
{
private:// 缺省值int _a = 1;int* _p = (int*)malloc(4);A aa1 = { 1,2 };
};
  • 小题试炼 

以下程序的最终结果是什么? 

A.输出1 1
B.程序崩溃 

C.编译不通过
D.输出1 随机值

class A
{
public:A(int a):_a1(a), _a2(2){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:// 声明顺序int _a2;int _a1;
};int main() {A aa(1);aa.Print();
}

答案:D 

  • 解析

初始列表初始的顺序不是该列表出现的顺序(也就是先_a1再_a2),而是声明的顺序(先_a2再_a1)。

三.static成员 

1.概念 

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

2.使用 

①面试题:A类型创建了多少个对象? (统计构造了多少次) 

  • 写法一:直接统计出构造和拷贝构造的总次数
int n = 0;
class A
{
public:A(){++n;}A(const A& aa){++n;}
};
A Func()
{A aa;return aa;
}int main()
{A aa1;A aa2;Func();cout << n << endl;return 0;
}

由于各个编译器的优化程度不一样,所以导致最后调用的次数可能会不同,因此这种写法具有一定的风险性。 

  • 写法2:设置成静态成员,并突破类域和访问限定符 
class A
{
public:A(){++n;}A(const A& aa){++n;}//不是属于某一个对象,属于所有对象,属于整个类static int n ;//在类内声明
};
int A::n = 0;//在类外定义
A Func()
{A aa;return aa;
}int main()
{A aa1;A aa2;Func();//三种访问形式cout << aa1.n << endl;cout << aa2.n << endl;cout << A::n << endl;return 0;
}
  • 写法3:存在访问限定符 
class A
{
public:A(){++n;}A(const A& aa){++n;}int GetN(){return n;}
private://不是属于某一个对象,属于所有对象,属于整个类static int n ;//在类内声明
};
int A::n = 0;//在类外定义
A Func()
{A aa;return aa;
}int main()
{A aa1;A aa2;Func();cout << aa1.GetN() << endl;return 0;
}
  • 写法4:静态成员函数写法 
class A
{
public:A(){++n;}A(const A& aa){++n;}//静态成员函数没有this指针static int GetN(){return n;}
private://不是属于某一个对象,属于所有对象,属于整个类static int n ;//在类内声明
};int A::n = 0;//在类外定义
A Func()
{A aa;return aa;
}int main()
{A aa1;A aa2;Func();cout << A::GetN() << endl;return 0;
}

四.友元 

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。

友元分为:友元函数和友元类。

1.友元函数 

问题:现在尝试去重载operator,然后发现没办法将operator重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。 

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}// d1 << cout  ---->  d1.operator<<(&d1, cout); 不符合常规调用// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧ostream& operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}
private:int _year;int _month;int _day;
};

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。  

class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{Date d;cin >> d;cout << d << endl;return 0;
}
  • 注意事项 
  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同  

2.友元类 

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  2. 友元关系是单向的,不具有交换性。 (比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)
  3. 友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  4. 友元关系不能继承。  
class Time
{friend class Date;// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};

五.内部类 

1.概念 

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。

  • 注意

内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

class A
{
public://内部类天生就是外部类的友元类class B{public:void func(A* p){p->_a1++;}};
private:int _a1;int _a2;
};int main()
{cout << sizeof(A) << endl;A::B bb;
}

2.特性 

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3.  sizeof(外部类)=外部类,和内部类没有任何关系。  
  • 内部类的大小 
class A
{
public:class B{private:int _b1;};
private:int _a1;int _a2;
};int main()
{cout << sizeof(A) << endl;//8
}
  1. 类B受类A的类域限制,所以类A里其实是没有类B的,类B可以理解为和全局变量没有区别。
  2. 类B在创建对象时受到类A封装的限制。
int main()
{A::B bb;//这样创建对象
}
如果将类B设置成private,则无法访问

六.匿名对象 

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{// 有名对象A aa1;// 匿名对象// 特点:生命周期只在当前一行A();A(10);return 0;
}

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

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

相关文章

Linux内核中自旋锁驱动代码举例二

一. 简介 前面学习了不考虑中断的自旋锁的代码举例,文章地址: Linux内核自旋锁驱动代码举例一-CSDN博客 但是在 Linux系统中,中断时存在的。所以,这里学习使用带保存中断状态的自旋锁API函数,实现对Led设备的互斥访问。 二. 带保存中断状态的自旋锁函数使用 1. 准备…

Linux第64步_编译移植好的虚拟机文件

最好还是认真了解linux系统移植的整个过程&#xff0c;否则&#xff0c;可能会让你误入歧途。 1、编译移植好的tf-a 1)、编译生成“tf-a-stm32mp157d-atk-trusted.stm32” 输入“cd /home/zgq/linux/atk-mp1/tfa/my-tfa/tf-a-stm32mp-2.2.r1/回车”&#xff0c;切换到“/hom…

算法打卡day1|数组篇|Leetcode 704.二分查找、27.移除元素

数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;可以方便的通过下标索引的方式获取到下标下对应的数据。 1.数组下标都是从0开始的。 2.数组内存空间的地址是连续的。 正是因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添…

算法题--华为od机试考试(分苹果、字符串统计及重排、高矮个子排队)

目录 分苹果 题目描述 输入描述 输出描述 示例1 输入 输出 备注 示例2 输入 输出 解析 答案 字符统计及重排 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 说明 解析 答案 高矮个子排队 题目描述 输入描述 输出描述 备注…

【深度学习笔记】3_5 图像分类数据集fashion-mnist

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.5 图像分类数据集&#xff08;Fashion-MNIST&#xff09; 在介绍softmax回归的实现前我们先引入一个多类图像分类数据集。它将在后面的章节中被多次使用&#xff0c…

STM32使用PB3, PB4引脚的注意事项

STM32的PB3, PB4引脚作为GPIO引脚需要注意&#xff0c;因为他们默认分别是JTDO和NJTRST引脚。 笔者在设计可调增益增益放大器&#xff08;VGA&#xff09;的时候&#xff0c;使用4个GPIO读取外部控制电压&#xff0c;根据约定的编码格式设定DAC的输出电压&#xff0c;从而设置V…

《Docker 简易速速上手小册》第1章 Docker 基础入门(2024 最新版)

文章目录 1.1 Docker 简介与历史1.1.1 Docker 基础知识1.1.2 重点案例&#xff1a;Python Web 应用的 Docker 化1.1.3 拓展案例 1&#xff1a;使用 Docker 进行 Python 数据分析1.1.4 拓展案例 2&#xff1a;Docker 中的 Python 机器学习环境 1.2 安装与配置 Docker1.2.1 重点基…

消息队列-RabbitMQ:发布确认—发布确认逻辑和发布确认的策略

九、发布确认 1、发布确认逻辑 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的消息都将会被指派一个唯一的 ID (从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发送一个确认给…

Python基础教程——17个工作必备的Python自动化代码

您是否厌倦了在日常工作中做那些重复性的任务&#xff1f;简单但多功能的Python脚本可以解决您的问题。 引言 Python是一种流行的编程语言&#xff0c;以其简单性和可读性而闻名。因其能够提供大量的库和模块&#xff0c;它成为了自动化各种任务的绝佳选择。让我们进入自动化…

K8s环境搭建

一、基础环境准备 VMware虚拟机&#xff0c;安装三台CentOS&#xff0c;网络环境选择NAT模式&#xff0c;推荐配置如下&#xff08;具体安装步骤省略&#xff0c;网上很多虚拟机安装CentOS7的教程&#xff09; 二、网络环境说明 使用NAT模式&#xff0c;我的IP分别是&#xf…

Promise相关理解记录

一、Promise基础定义相关 Promise是一个构造函数&#xff0c;调用时需要使用new关键字 Promise是解决回调地狱的一种异步解决方式 Promise有三个状态&#xff1a;pending(进行中)、fulfilled(成功)、rejected(失败) Promise的状态只会从 pending→fulfilled 或者 pending→…

SQL创建数据库

SQL&#xff0c;全称结构化查询语言(Structured Query Language)&#xff0c;是一种用于管理关系型数据库的标准语言。通过 SQL&#xff0c;我们可以创建、查询、更新和删除数据库中的数据。今天&#xff0c;我们将学习使用SQL创建数据库。本文的目标是让读者了解如何使用SQL创…

300分钟吃透分布式缓存-13讲:如何完整学习MC协议及优化client访问?

协议分析 异常错误响应 接下来&#xff0c;我们来完整学习 Mc 协议。在学习 Mc 协议之前&#xff0c;首先来看看 Mc 处理协议指令&#xff0c;如果发现异常&#xff0c;如何进行异常错误响应的。Mc 在处理所有 client 端指令时&#xff0c;如果遇到错误&#xff0c;就会返回 …

信号系统之线性图像处理

1 卷积 图像卷积的工作原理与一维卷积相同。例如&#xff0c;图像可以被视为脉冲的总和&#xff0c;即缩放和移位的delta函数。同样&#xff0c;线性系统的特征在于它们如何响应脉冲。也就是说&#xff0c;通过它们的脉冲响应。系统的输出图像等于输入图像与系统脉冲响应的卷积…

pclpy 半径滤波实现

pclpy 半径滤波实现 一、算法原理背景 二、代码1.pclpy 官方给与RadiusOutlierRemoval2.手写的半径滤波&#xff08;速度太慢了&#xff0c;用官方的吧&#xff09; 三、结果1.左边为原始点云&#xff0c;右边为半径滤波后点云 四、相关数据 一、算法原理 背景 RadiusOutlier…

Linux——进程概念

目录 冯诺依曼体系结构 操作系统 管理 系统调用和库函数 进程的概念 进程控制块——PCB 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程 进程状态 运行状态-R ​编辑 浅度睡眠状态-S 深度睡眠状态-D 暂停状态-T 死亡状态-X 僵尸状态-Z 僵尸进程…

AD24-PCB的DRC电气性能检查

1、 2、如果报错器件选中&#xff0c;不能跳转时&#xff0c;按下图设置 3、开始出现以下提示时处理 4、到后期&#xff0c;错误改得差不多的时候&#xff1b;出现以下的处理步骤 ①将顶层和底层铜皮选中&#xff0c;移动200mm ②执行以下操作 ③将铜皮在移动回来&#xff0c;进…

STM32_IIC_AT24C02_1_芯片简介即管脚配置

STM32的IIC总线是存在bug&#xff0c;感兴趣的可以上网搜一搜。我们可以使用两个I/O口和软件的方式来模拟stm32的iic总线的控制&#xff0c;所以就不需要使用stm32的硬件控制器了&#xff0c;同理数据手册中的I2C库函数也没有用了。 ROM&#xff08;只读存储器&#xff09;和…

黄仁勋最新专访:机器人基础模型可能即将出现,新一代GPU性能超乎想象

最近&#xff0c;《连线》的记者采访了英伟达CEO黄仁勋。 记者表示&#xff0c;与Jensen Huang交流应该带有警告标签&#xff0c;因为这位Nvidia首席执行官对人工智能的发展方向如此投入&#xff0c;以至于在经过近 90 分钟的热烈交谈后&#xff0c;我&#xff08;指代本采访的…

blender快捷键记录

一、移动界面方式 1、shift 拖动界面 2、滚轮wheel 以世界原点为中心旋转 二、基本操作 1、s 方位键 放大缩小 2、g 方位键 移动方位 3、r 方位键 渲染方位 4、tab 切换编辑模式 5、衰减模式 g wheel 可以调整衰减圈范围大小 6、编辑模式 1、2、3 切换点 线 面 的…