【C++】4. 类和对象终章

news2025/1/18 6:59:17

在这里插入图片描述
专栏导读

🍁作者简介:余悸,在读本科生一枚,致力于 C++方向学习。
🍁收录于 C++专栏,本专栏主要内容为 C++初阶、 C++ 进阶、STL 详解等,持续更新中!
🍁相关专栏推荐: C语言初阶 、C语言进阶 、数据结构与算法 ( C语言描述)、 C++、 Linux、 Mysql

本文介绍主要静态成员的概念,友元、内部类匿名对象,包括匿名对象,相信看完这篇文章你会加深对类和对象的认识。

文章目录

  • 类和对象终章
    • Static成员
      • 什么是static成员
      • 类的静态成员特点
      • 全局函数的函数名重复问题
    • 友元
      • 友元函数
      • 友元函数的特点
      • 友元类
      • 友元类的特点
    • 内部类
      • 内部类的大小
      • 内部类访问
      • 内部类特点
    • 匿名对象及编译器的优化行为
      • 匿名对象
      • 单参数、多参数的隐式类型转换的优化行为
      • 匿名对象传参时的优化写法
      • 编译器对返回值的优化

类和对象终章

Static成员

什么是static成员

C++中的静态成员是指属于类而不属于类的任何特定实例的成员。静态成员存在于整个程序生命周期中,即使没有创建任何类实例,它仍然可以访问和使用。

静态成员可以是数据成员或成员函数。静态数据成员是类的一个数据成员,被所有该类的对象共享。声明静态数据成员时,需要在数据类型前加上static关键字,并在类声明中初始化静态数据成员。例如:

class MyClass {
public:
  static int myStaticInt;
};

// 初始化静态成员
int MyClass::myStaticInt = 9854;      

int main() {
  MyClass obj1;
  MyClass obj2;
  
  // 访问静态成员
  cout << MyClass::myStaticInt << endl;
  
  // 通过对象访问静态成员
  cout << obj1.myStaticInt << endl;
  cout << obj2.myStaticInt << endl;
  
  return 0;
}

静态函数成员在类中被声明为static,并使用类名作为前缀来调用。它们没有this指针,因为它们不与任何特定对象相关联。

class MyClass {
public:
  static void myStaticFunc() {
    // myStaticFunc function body
  }
};

int main() {
  MyClass::myStaticFunc();
  return 0;
}

类的静态成员特点

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

Q:为什么静态成员函数不能调用非静态成员变量?
A:因为静态成员函数没有this指针,而非静态成员变量是与类的对象相关联的。所以如果静态成员函数需要访问非静态成员变量,则需要通过传递对象或其他参数的方式来实现。静态成员函数可以调用静态成员变量和其他静态成员函数,因为它们是属于类而不是对象的。

全局函数的函数名重复问题

1、问题描述
当一个函数定义在全局的头文件时,它将会在预处理阶段在所有的.cpp文件中展开并包含。并在链接阶段发生函数名重复的问题。

2、解决方案

  • 声明和定义分离;
  • 在函数前加上static,改变函数的链接属性。当该函数在预处理阶段被多个.cpp包含时,因为是静态的函数,不会进符号表。(仅当前文件可见)
  • 在函数前加上inline,内联函数在编译时不会进符号表。

头文件中,尽量不要定义全局的变量或函数。

友元

类可以使用友元函数。友元函数是在类外部定义的非成员函数,但可以访问该类的非公有成员。

友元函数

在类中,使用 friend 关键字声明友元函数,并指定友元函数的名称,如下所示:

class MyClass {
private:
  int x;
public:
  friend void myFriendFunc(MyClass obj);
};

在上面的例子中,myFriendFunc 函数被声明为 MyClass 的友元函数。这意味着 myFriendFunc 函数可以直接访问 MyClass 的私有成员 x。

在定义友元函数时,可以直接访问 MyClass 类的私有成员如下:

void myFriendFunc(MyClass obj) {
  cout << obj.x << endl;  // 可以直接访问 MyClass 类的私有成员 x
}

在类的外部定义了一个普通函数,并将其声明为该类的友元函数,这样该函数就可以通过对象访问关联类的非公有成员。需要注意的是,友元关系一般是单向的,这意味着如果函数 A 是类 B 的友元函数,并不能直接访问类 A 的非公有成员。

友元函数的使用应该谨慎,因为它打破了类的封装性,使外部函数可以访问类的私有成员,从而增加了代码的耦合性。

//类内声明为友元函数
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//需要在类外定义
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >>d._month >> d._day;
	return in;
}

流插入和流提取运算符在重载时,根据使用习惯,第一个参数不应是本身,所以将这两个运算符放在类外定义,在类中放入该声明,并在声明前加上friend修饰。让这个函数成为友元函数。

友元函数的特点

友元函数是一种特殊的函数,在 C++ 中常用于访问类的私有成员。

  1. 友元函数不是类的成员函数,但可在类中声明,在类外部进行定义和调用。 友元函数的声明可以放在类中的任意位置,它不受类访问限定符限制。

  2. 友元函数可以直接访问类的私有成员,包括私有数据和私有函数。一个函数可以是多个类的友元,这样可以提高程序的灵活性和效率,但也可能会降低封装性和安全性。

  3. 友元函数可以被定义为全局函数、类的成员函数或其他类的成员函数。对于类的成员函数,可以通过访问控制符来限制访问权限。

  4. 声明友元函数不影响该函数的作用域,也不会影响该函数在全局作用域内的可见性,友元函数不能用const修饰。

  5. 友元关系一般是单向的,即如果函数A是类B的友元函数,则函数A可以访问类B的私有成员,但不一定可以访问函数A所在类的私有成员。如果要访问多个类的私有成员,需要为每个类分别定义友元函数或将它们放在同一个类中。

  6. 友元关系不能被继承,即派生类不能访问其基类的友元函数。

需要注意的是,友元函数在程序设计中应该尽量少用。友元函数打破了类的封装性,可能会引入新的安全隐患,而且容易影响代码的可读性和可维护性。在使用友元函数时,应该根据实际情况严格控制访问权限,避免滥用。

友元类

在C++中,可以使用友元类来授权其他类或函数访问类的私有成员。友元类是指可以访问类的私有成员的其他类。在类的定义中,使用 friend 关键字声明友元类,例如:

class MyClass2;   // 前置声明

class MyClass1 {
private:
  int x;
  friend class MyClass2;   // 声明 MyClass2 为 MyClass1 的友元类

public:
  void setX(int a) {
    x = a;
  }
};

class MyClass2 {
public:
  void display(MyClass1 obj) {
    cout << obj.x << endl;   // 可以访问 MyClass1 的私有成员 x
  }
};

int main() {
  MyClass1 obj1;
  MyClass2 obj2;

  obj1.setX(123);
  obj2.display(obj1);   // 调用 MyClass2 的 display() 函数

  return 0;
}

我们声明了一个友元类 MyClass2,该类可以访问 MyClass1 的私有成员 x,在 MyClass2 中的 display函数中,使用 obj.x 访问了 MyClass1 类的私有成员 x。

需要注意的是,友元关系不能被继承,即派生类不能访问其基类的友元类。此外,使用友元类也可能降低类的封装性,应谨慎使用。

友元类的特点

  • 友元关系没有继承性,即派生类不能访问基类的友元类。

  • 友元没有传递性,比如A是B的友元,B是C的友元,但是不能说A是C的友元。

  • 友元类可以访问类的私有成员,但不能继承类的成员。

使用友元类需要注意以下几点:

建议将友元类的访问权限设置为private,以限制友元类的使用范围。

友元类不能访问类的静态成员和静态成员函数,但可以通过访问非静态成员函数来访问静态成员。

总的来说,友元类可以提供方便灵活的类之间成员访问控制,但不当地使用可能会降低代码质量。建议使用友元关系时要考虑完整项目的设计和开发策略。

内部类

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

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

内部类的大小

class A
{
public:
	class B//B类不在A中
	{
		int b;
	};
private:
	int _a;
};

sizeof(外部类)=外部类,和内部类没有任何关系。这里A的大小是4字节。B是A的内部类,但是B不存储于A的类中。

内部类访问

A::B b;

内部类特点

1、注意B类也会受A类访问限定符的影响

2、B类天生是A类的友元。(B可以偷A的家,但A无法偷B的家)所以内部类也尽量少用。

  1. 内部类可以定义在外部类的public、protected、private都是可以的。

  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

匿名对象及编译器的优化行为

匿名对象

Date();//创建了个匿名对象
Date().Func();//使用匿名对象调用成员函数
Date Func()//使用匿名对象返回
{
	return Date(10);
}

匿名对象的生命周期在这一行。匿名对象具有常性。

单参数、多参数的隐式类型转换的优化行为

//日期类略
int main()
{
	//单参数的构造,构造+拷贝,编译器直接优化为构造C++98
	Date d1 = 2022;
	//临时对象具有常性,这里就发生了一次构造,编译器没有优化空间了
	const Date& d2 = 2023;
	//多参数的构造C++11
	Date d3 = { 2022,10,16 };
	return 0;
}

单参数、多参数的构造由构造+拷贝构造优化为直接构造。

匿名对象传参时的优化写法

  1. 优化前
void Func(Date d)
{
}
int main()
{
	Date d1(2022);//构造
	Func(d1);//传参发生拷贝构造
	return 0;
}
  1. 优化后
void Func(Date d)//形参改成const Date& d也是一种优化
{
 
}
int main()
{
	Func(Date(2022));//使用匿名对象传参,构造+拷贝构造,编译器直接优化为构造
    Func(2022);//隐式类型转换的优化,也是由构造+拷贝构造,编译器直接优化为构造
	return 0;
}

编译器对返回值的优化

  1. 无优化
Date Func()
{
	Date d(2022);//构造
	return d;//return时拷贝构造一份临时对象
}
int main()
{
	Date ret;
	ret = Func();//使用返回的临时对象进行赋值
	return 0;
}
  1. 构造优化
Date Func()
{
	Date d(2022);//构造
	return d;//return时拷贝构造一份临时对象
}
int main()
{
	Date ret = Func();//使用返回的临时对象拷贝构造ret
	return 0;
}

正常的流程如注释所示,需要构造+拷贝构造+拷贝构造,但是编译器会将其优化为构造+拷贝构造。

  1. 使用匿名对象极致优化
Date Func()
{
	return Date(2022);//使用2022构造一个临时匿名对象,return时再拷贝构造一份临时对象
}
int main()
{
	Date ret = Func();//使用返回的临时对象进行拷贝构造
	return 0;
}

这样写由构造+拷贝构造+拷贝构造,会被编译器直接优化为构造。是最优的写法。由于匿名对象的生命周期只在这一行,所以只能传值返回,而不能传引用返回。

本节结束,希望可以帮助到读者,如果对你有帮助请关注、点赞、评论支持一下。
在这里插入图片描述

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

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

相关文章

做F牌独立站要做好功课,拒绝被割韭菜!

做过爆品独立站的朋友们都知道&#xff0c;遇到爆品不容易&#xff0c;很多都具有滞后性&#xff0c;都是当你发现了之后&#xff0c;这个帖子/视频/产品已经被人跑烂了&#xff0c;你再去跑&#xff0c;这样只会浪费大量的广告费。既然爆品独立站的广告费烧不过大卖&#xff0…

知识图谱学习笔记——(五)知识图谱推理

一、知识学习 声明&#xff1a;知识学习中本文主体按照浙江大学陈华钧教授的《知识图谱》公开课讲义进行介绍&#xff0c;并个别地方加入了自己的注释和思考&#xff0c;希望大家尊重陈华钧教授的知识产权&#xff0c;在使用时加上出处。感谢陈华钧教授。 &#xff08;一&…

Java配置方式使用Spring MVC:实战练习

文章目录 续写任务1、创建登录页面、登录成功与登录失败页面1、创建登录页面2、创建登录成功页面3、创建登录失败页面 任务2、首页添加登录链接&#xff0c;单击可跳转到登录页面1、 修改首页&#xff0c;添加超链接2、修改Spring MVC配置类&#xff0c;定义视图控制器3、创建登…

Spark - 创建 _SUCCESS 文件与获取最新可用文件

目录 一.引言 二.增加 _SUCCESS 标识 1.SparkContext 生成 2.FileSystem 生成 3.Hadoop 生成 三.获取最新文件 1.获取 SparkContext 2.按照时间排序 3.遍历生成 Input 四.总结 一.引言 有任务需要每小时生成多个 split 文件分片&#xff0c;为了保证线上任务读取最新…

Linux实操篇---常用的基本命令5(进程管理类和crontab系统定时任务)

一、进程管理类 进程是正在执行的一个程序或命令&#xff0c;每一个进程都是一个运行的实体&#xff0c;都有自己的地址空间&#xff0c;并占用一定的系统资源。 守护进程和系统服务就是一一对应的关系。 有系统级别的进程和用户级别的进程。 进程管理&#xff1a;所有的进…

如何使用自定义知识库构建自定义ChatGPT机器人

目录 隐藏 使用自定义数据源为您的 ChatGPT 机器人提供数据 1. 通过Prompt提示工程提供数据 2. 使用 LlamaIndex&#xff08;GPT 索引&#xff09;扩展 ChatGPT 如何添加自定义数据源 先决条件 怎么运行的 最后的总结 使用自定义数据源为您的 ChatGPT 机器人提供数据…

rt-thread启动流程

资料下载 RT-Thread Simulator 例程 操作流程 将上面的仿真例程下载并解压&#xff0c;通过MDK打开&#xff0c;编译&#xff0c;调试&#xff0c;并打开串口点击运行&#xff0c;就可以看到如下输出了&#xff1a; 添加自己的 thread&#xff1a;在main()函数中添加即可&am…

[Java基础]基本概念(上)(标识符,关键字,基本数据类型)

hello 大家好&#xff0c;计算机语言各有不同&#xff0c;但本质上都是操作内存和计算。这章的内容是介绍Java中的基本概念展开&#xff0c;包括&#xff1a;标识符&#xff0c;关键字&#xff0c;Java基本数据类型&#xff0c;运算符&#xff0c;表达式和语句&#xff0c;分支…

前端架构师-week6-require源码解析

require 源码解析——彻底搞懂 npm 模块加载原理 require 的使用场景 加载模块类型 加载内置模块&#xff1a;require(fs)加载 node_modules 模块&#xff1a;require(ejs)加载本地模块&#xff1a;require(./utils)支持文件类型 加载 .js 文件加载 .mjs 文件加载 .json 文件…

AI女友同时和1000人谈恋爱,狂赚500万

AI女友&#xff0c;预计暴赚4亿 要说当下什么最火&#xff0c;AI首当其冲无可置疑。00后网络红人红卡琳玛乔丽&#xff08;Caryn Marjorie&#xff09;最近与Forever Voices公司合作&#xff0c;通过视频训练等方式打造出个人形象、声音和性格的AI虚拟女友&#xff0c;就像在和…

Redis高可用--持久化

在Web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准实在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提提供正常服务&a…

大疆无人机 MobileSDK(遥控器/手机端)开发 v4版<3>

导读 第三篇文章准备单独拿出来写,因为在大疆为人机的所有功能中,航线规划的功能最为复杂,也相当的繁琐,这里需要说仔细一点,可能会将代码进行多步分解。 航线规划 1)航线打点 点击 按钮进行打点,在地图中手动选择点位选择完成后点击**[完成]**按钮,即可完成航线打点…

新展预告 | YT U LOVE——许峰个展即将亮相!

深圳东方美术馆荣幸地宣布&#xff0c;将于5月20日呈现艺术家许峰在鹏城的首次个展“YT U LOVE”&#xff0c;展出艺术家从2020年至2023年创作的油画、纸本及雕塑40余件作品。此次展览以“YT U LOVE”为题&#xff0c;恰逢兔年&#xff0c;yutu在中国意指玉兔&#xff0c;前后两…

美创科技首家互联网医院数据安全建设案例实践

互联网医院作为医疗服务模式创新发展的新产物&#xff0c;在各项配套政策支持下快速发展。然而&#xff0c;蓬勃之势下&#xff0c;无数双“暗夜之手”也在蠢蠢欲动&#xff0c;试图从中渔利&#xff0c;关乎患者隐私、种类繁多的医疗数据迎来愈加严峻的安全挑战。 某市中心医院…

劳有所学|文献可视化分析工具CiteSpace、vosviewer使用指南

【基于Citespace和vosviewer文献计量学相关论文 】 专题一&#xff1a;文献计量学方法与应用 1 文献计量学方法基本介绍 2 与其他综述方法区别联系 3 各学科领域应用趋势近况 4 主流分析软件优缺点对比 5 经典高分10SCI思路复盘 6 软件安装与Java环境配置 专题二&#…

理解PMP的顺序

PMP&#xff0c;大量考的是“下一步”、“本应该”的顺序逻辑。在学习的时候&#xff0c;我们需要把整本书十个知识领域&#xff0c;穿起来形成一个线性的结构。 在整理的过程中&#xff0c;很多人都会认为&#xff0c;线性结构&#xff0c;应该是这样的&#xff1a; 每个过程…

安卓播放H264/H265实时流(安卓实时预览H264/H265 安卓实时预览AVC/HEVC)

实际项目中经常遇到两种场景&#xff0c;第一种从无人机拿H264/H265码流转GB28181等协议&#xff0c;转协议的同时可能还需要实时预览无人机画面; 第二种是安卓接USB外置摄像头, 由于USB2.0传输带宽有限&#xff0c;对于高分辨率图像, 带宽无法满足YUV图像的传输, 摄像头只好先…

数据的比较

前言 在学习Java过程中&#xff0c;数据的比较是必学的。 对于不同的数据有不同的比较方式。 目录 前言 一、算术比较器 二、equals() 三、Comparable接口 四、Comparator接口 结语 一、算术比较器 算数比较器有&#xff1a;、>、<、>、<、! 但是算数比较器…

win下C++部署深度学习模型之clion配置pytorch+opencv教程记录

win下clion配置pytorch和OpenCV 一、clion配置vs编译器以及测试二、clion配置pytorch2.1、下载libtorch2. 2、环境变量配置2.3、cmakelist.txt编写2.4、main函数测试运行 三、clion配置opencv3.1、源码下载3.2、编译3.3、环境变量配置3.4、cmakelist.txt编写3.5 main函数测试运…

揭 秘~月薪2-3万的程序员一天到底是怎么度过的?

程序员的高薪资&#xff0c;一直是大家热衷讨论的话题&#xff0c;几乎每隔一段时间就会在社交平台被网友们热议一番。 比如这条“月薪2万到3万的程序员的一天是怎么样度过的&#xff1f;”的帖子就一直排在知乎前列。 作为薪资可观的岗位&#xff0c;大家都非常好奇&#xff…