当前位置: 首页 > news >正文

【C++】类和对象(下)

【C++】类和对象(下)

前言:虽说类和对象都很抽象,但是相较于类和对象中,下没有那么难,接下来再次深入了解一下呢~

一、再探构造函数

  • 在之前,实现构造函数时,初始化成员变量最主要使用函数体内赋值,在这里我们探讨另一个方式:初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

  • 每个成员变量在初始化列表中只能出现一次 ,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方

  • 引用成员变量,const成员变量,没有默认构造的类类型成员变量,必须放在初始化列表位置进行初始化的成员使用

  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表的成员使用的

class Time
{public:Time(int hour):_hour(hour){cout << "Time()" << endl;}
private:int _hour;}
class Data
{public://Data后面的括号的内容就是初始化的表达式Data(int& x,int year=1,int month=1,int day=1):_year=year(year),_month=month(month),_day=day(day),_t(12),_ref(x),_n(1){}private://声明的位置int _year;int _month;int _day;Time _t;//没有默认构造int& _ref;//引用const int _n;//const成员变量
}
  • 尽量使用初始化列表初始化,因为即使不在初始化列表的成员也会走初始化列表,若这个成员在声明处给了缺省值,初始化列表会用这个缺省值初始化。若没有缺省值,对没有显示在初始化列表的内置类型成员是否初始化取决于编译器;对没有显示在初始化列表的自定义类型成员会调用这个成员类型的默认构造,若没有默认构造就会编译错误。
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的先后顺序没有关系。(因此建议初始化列表和声明顺序一致
class Data 
{public:Data(int year=2,int month=2,int day=2):_year(year),_month(month),_day(day){}private://声明给缺省值->初始化列表//这里不是初始化列表,这里是给缺省值,这个缺省值是给初始化列表的//若初始化列表没有显示初始化值,默认就会用这个缺省值初始化int _year=1;int _month=1;int _day=1;}

在这里插入图片描述

成员变量走初始化列表的逻辑图

用初始化列表的优点:

  • 效率方面:避免不必要的默认构造和赋值操作;对于引用成员变量,const成员变量,没有默认构造的类类型成员变量必须初始化时赋值,不能先默认构造再赋值
  • 代码简洁性和可读性:简洁表达初始化意图,清晰的看到所有成员变量的初始化情况;遵循类的设计语义,明确成员变量的初始状态。

二、类型转换

  • C++支持内置类型隐式类型转换为类类型对象,需要有相关内类型为参数的构造函数
  • 构造函数前面加explicit就不再支持隐式类型转换。

explicit关键字拓展用于修饰类的构造函数,以防止隐式类型转换。在没有explicit关键字时,单参数的构造函数(或除第一个参数外其余参数都有默认值的多参数构造函数)可以用来隐式转换

class Data
{
public://单参数构造函数,没有使用explicit修饰,具有类型转换作用 //explicit修饰函数构造函数后,禁止类型转换,运行会报错//explicit Data(int year)Data(int year):_year(year){}}
  • 类类型的对象之间也可以隐式转换,需要相应的构造函数支持
class A
{
public:A(int a1):_a1(a1){}A(int a1, int a2):_a1(a1), _a2(a2){}void Print(){cout << _a1 << " " << _a2 << endl;}int Get() const//const修饰函数,实际修饰的是该成员函数隐含的this指针{return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 2;
};
int main()
{// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3 // 编译器遇到连续构造+拷⻉构造->优化为直接构造 A aa1 = 3;aa1.Print();const A& aa2 = 1;// C++11之后才⽀持多参数转化 A aa3 = { 3,5 };aa3.Print();

图形理解:

在这里插入图片描述

三、static成员

  • static修饰成员变量,称之为静态成员变量,静态成员一定要在类外进行初始化
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
  • 静态成员函数,可以访问其他静态成员,但是不能访问非静态的,因为没有this指针。
class Data
{
public://没有this指针,func1访问不了func2static void func1(){}void func2(){}
private:int _year;int _month;int _day;
}
  • 非静态的成员函数,可以访问其他的静态成员和静态成员函数。

在这里插入图片描述

  • 突破类域就可以访问静态成员,可以通过类名(::)静态成员或者对象(.)静态成员来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private访问限定符的限制。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
class A
{
public:Sum(){_ret+=_i;++_i;}static int GetRet(){return _ret;}
private:int _ret;//类内声明static int _i;
}
//类外初始化
int A::_i=0;

四、友元函数

  • 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,它不是类的成员函数。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数。
  • 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有成员和保护成员(这个点会在继承看的更明显)
  • 友元类关系是单向的,不具有交换性,如A是B的友元,但B不是A的友元(你是我的好朋友,但我不是你的好朋友);也不可以传递(我是你的好朋友,但我的另一个好朋友不是你的好朋友)
  • 友元提供了便利但破坏了封装性!

示例:

#include<iostream>
using namespace std;
// 前置声明,都则A的友元函数声明编译器不认识B 
class B;
class A
{// 友元声明 friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 2;
};
class B
{// 友元声明 friend void func(const A& aa, const B& bb);
private:int _b1 = 3;int _b2 = 4;
};
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}
int main()
{A aa;B bb;func(aa, bb);return 0;
}

五、内部类

  • 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类。
  • 内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

示例:

#include<iostream>
using namespace std;class A
{
private:static int _k;int _h = 1;
public:class B // B默认就是A的友元 {public:void foo(const A& a){cout << _k << endl; cout << a._h << endl; }};
};
int A::_k = 1;
int main()
{cout << sizeof(A) << endl;A::B b;A aa;b.foo(aa);return 0;
}

六、匿名对象

  • 用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参)定义出来的叫有名对象
  • 匿名对象生命周期只在当前一行,一般临时定义一个对象当前⽤一下即可,就可以定义匿名对象。
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 aa1();// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字, // 但是他的⽣命周期只有这一⾏,我们可以看到下一⾏他就会⾃动调用析构函数 A();A(1);A aa2(2);}

相关文章:

  • 多级侧边菜单(递归)
  • 汽车3d动画渲染选择哪个?选择最佳云渲染解决方案
  • 2025年营收1亿美元咨询代理机构的游戏策略:基于AIGC的无限可扩展业务
  • 默认成员函数的练习之实现日期类
  • Linux 学习笔记(十六)—— 重定向与缓冲区
  • Growthly Quest 增长工具:助力 Web3 项目实现数据驱动的增长
  • MySQL vs PostgreSQL:2024年深度对比与选择指南
  • 后端返回内容有换行标识,前端如何识别换行
  • 14.安卓逆向-frida基础-编写hook脚本2
  • 【Python】数据可视化之分布图
  • 在C#中实现WebSocket的单聊和分频道聊天
  • 域 缺省参数 函数重载 引用
  • 【Golang】Go语言中如何面向对象?
  • 【研赛A题成品论文】24华为杯数学建模研赛A题成品论文+可运行代码丨免费分享
  • GO Serial 学习与使用
  • CAP理论的例子讲解
  • co.js - 让异步代码同步化
  • JAVA并发编程--1.基础概念
  • Java知识点总结(JavaIO-打印流)
  • mysql innodb 索引使用指南
  • Terraform入门 - 1. 安装Terraform
  • vuex 笔记整理
  • 关于Flux,Vuex,Redux的思考
  • 今年的LC3大会没了?
  • 区块链技术特点之去中心化特性
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • 正则表达式-基础知识Review
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​卜东波研究员:高观点下的少儿计算思维
  • #pragma data_seg 共享数据区(转)
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (20050108)又读《平凡的世界》
  • (java)关于Thread的挂起和恢复
  • (Java入门)抽象类,接口,内部类
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (八)Flink Join 连接
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (回溯) LeetCode 77. 组合
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (正则)提取页面里的img标签
  • (转)linux下的时间函数使用
  • (转)memcache、redis缓存
  • (自适应手机端)行业协会机构网站模板
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .NET中使用Protobuffer 实现序列化和反序列化
  • [ IDE ] SEGGER Embedded Studio for RISC-V
  • [AR Foundation] 人脸检测的流程
  • [c]统计数字