【C++】友元、初始化列表、内部类、static修饰成员详解

news2024/11/20 2:28:58

文章目录

  • 前言
  • 1. 构造函数不为人知的那些事
    • 1.1 构造函数体赋值
    • 1.2 初始化列表
    • 1.3 explicit关键字
  • 2. static成员
    • 2.1 概念
    • 2.2 特性
    • 2.3 小总结
  • 3. C++11 成员变量初始化新用法
  • 4. 友元
    • 4.1 友元函数
    • 4.2 友元类
  • 5. 内部类
    • 5.1概念及特性
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

在C++编程中,深刻理解一些特殊的语法和概念是构建高效、可维护代码的关键。本博客将深入探讨C++中的友元(Friend)、初始化列表(Initialization List)、内部类(Nested Class)以及static修饰成员的用法和意义。这些特性为我们提供了更灵活的编程工具,帮助我们更好地组织和设计代码。通过对这些主题的详细讲解,我们将更好地理解C++语言的底层机制,为编写更出色的程序打下坚实基础。


提示:以下是本篇文章正文内容,下面案例可供参考

1. 构造函数不为人知的那些事

1.1 构造函数体赋值

在我们创建一个对象时,编译器会自动调用构造函数,来完成对象所属成员变量的初始化工作,接下来用代码来说明,很简单

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1) //全缺省构造函数,通过函数体内赋值的方式来初始化成员变量
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

虽然在调用完上述构造函数后,对象中已经有值了,但是这样并不能将其称为类对象成员的初始化,构造函数体内的语句只能将其称作为赋初值,而不能称作初始化。因为我们知道初始化只能是一次,而且是在变量定义时给初值才能叫做初始化,而构造函数体内可以多次赋值。

1.2 初始化列表

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


#include <iostream>
using namespace std;
class Date
{
public:
	//Date(int year = 0, int month = 1, int day = 1) //全缺省构造函数,通过函数体内赋值的方式来初始化成员变量
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	Date(int year = 0, int month = 1, int day = 1) //全缺省构造函数,通过初始化列表的方式来初始化成员变量
		:_year(year)
		, _month(month)
		, _day(day)
	{

	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

【注意】

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表进行初始化:
    - 引用成员变量
    - const成员变量
    - 自定义类型成员(该类没有默认构造函数)
#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour ,int minute , int second ) //带参构造函数
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	//Date(int year = 0, int month = 1, int day = 1) //全缺省构造函数,通过函数体内赋值的方式来初始化成员变量
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	Date(int year = 0, int month = 1, int day = 1) //全缺省构造函数,通过初始化列表的方式来初始化成员变量
		:_year(year)//引用成员必须在初始化列表初始化
		, _month(month)//const成员必须在初始化列表初始化
		,_t(1,2,3)//自定义类型成员(该类没有默认构造函数)必须在初始化列表初始化
	{

	}
private:
	int& _year;
	const int _month;
	Time _t;
};
int main()
{
	Date d1;
	return 0;
}

具体细节看注释!!!
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date d(1);
}
  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

来看一段代码,你能知道代码的输出结果是什么吗???

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后初始化_a1,那么结果就是如图所示!!!!
总结:大家如果不好理解,就记住初始化列表是成员变量定义的地方,而其他地方的成员变量都只是声明!!!

1.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用
来看一段代码:

class Date
{
public:
	Date(int year)
		:_year(year)
	{}

	//explicit Date(int year)
	//	:_year(year)
	//{}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2018);

	// 用一个整形变量给日期类型对象赋值,实际上涉及隐式类型的转换
	// 实际编译器背后会 1、用2019构造一个临时对象tmp(2019) 2、然后用拷贝构造d1=tmp -》需要注意,vs编译器在这里会一起优化成直接用2019构造,但是实际上分为两步
	d1 = 2019;
}

具体细节看注释即可,另外这里涉及隐式类型的转换的知识,如果想要了解的同学,可以看这篇博客


另外,如果使用explicit关键字在构造函数前面后:

#include <iostream>
using namespace std;
class Date
{
public:
	//Date(int year)
	//	:_year(year)
	//{}

	explicit Date(int year)
		:_year(year)
	{}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2018);

	// 用一个整形变量给日期类型对象赋值,实际上涉及隐式类型的转换
	// 实际编译器背后会1、用2019构造一个临时对象tmp(2019),2、然后用拷贝构造d1=tmp -》需要注意,vs编译器在这里会一起优化成直接用2019构造,但是实际上分为两步
	d1 = 2019;
}

会发现,代码会出现问题,这是因为上述代码可读性不是很好,用explicit修饰构造函数,将会禁止单参构造函数的隐式类型转换。

拓展:上述是在单参构造函数的情况下,那么对于多参构造函数呢???

答案是:C++11提供了一种多参构造函数下的隐式类型转换
也就是说我们可以这么写:

#include <iostream>
using namespace std;
class Date
{
public:
	 Date(int year=0,int month=1,int day=1)
		:_year(year)
		,_month(month)
		,_day(day)
	{

	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1 = { 1,2,3 };
	return 0;
}

如果在构造函数前,加上explicit关键字同样隐式类型转换会失效,那么代码就会报错,这里就不赘述了。

2. static成员

2.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
具体对概念的运用,我们通过一个代码题来理解:

实现一个类,计算中程序中创建出了多少个类对象。

前提:我们需要知道对象的创建要么是通过构造函数初始化,要么是通过拷贝构造初始化创建对象,只能是这两个途径!!!

#include <iostream>
using namespace std;
class B
{
public:
	B() 
	{
		_count++;
	};
	B(const B& b)
	{
		_count++;
	}
	static int GetCount()//static 修饰的成员函数,不存在this指针
	{
		return _count;
	}
private:
	static int _count;//声明
};
int B::_count = 0;//类外进行初始化
int main()
{
	B b1;
	B b2;
	B b3(b1);
	cout << B::GetCount() << endl;
	return 0;
}

具体细节见注释

2.2 特性

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

2.3 小总结

在这里插入图片描述
在这里插入图片描述

3. C++11 成员变量初始化新用法

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值

#include <iostream>
using namespace std;
class Date
{
public:
	void print()
	{
		cout << _year << " " << _month <<" "<< _day << endl;
	}
private:
	int _year=0;//给成员变量缺省值
	int _month=1;
	int _day=1;
};
int main()
{
	Date d1;
	d1.print();
	return 0;
}

这个很好理解就不多加赘述了。

4. 友元

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

4.1 友元函数

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

可能有同学会对上面这段话有点不理解,那么我就来稍微继续解释一下,如果我们打算重载operator<<运算符,那么字面上cout<<d这样的输出对应的重载函数就是cout.operator<<(d),那么cout就成为了this位置上的参数,但是明明我的类并不是cout而是其他的类,怎么可能cout成为this,这就是为什么cout的输出流对象和隐含的this指针在抢占第一个参数的位置

答案就是只能利用友元让cout输出流对象到第一个参数的位置,函数定义成全局的
前提:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

#include <iostream>
using namespace std;
class Date
{
public:
	friend istream& operator>>(istream& in, Date& d);
	friend ostream& operator<<(ostream& out, const Date& d);
	Date(int year=0, int month=1, int day=1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day ;//这里为什么能直接用<<运算符,因为是内置类型,编译器本来就支持
	return out;
}
int main()
{
	Date d1(2024, 2, 8);
	cin >> d1;
	cout << d1 << endl;//对自定义类型的输出,进行重载
	return 0;
}

说明:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰(因为不是成员函数,那么也就没有this指针)
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用和原理相同

4.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  • 友元关系是单向的,不具有交换性。
    比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递(如果B是A的友元,C是B的友元,则不能说明C时A的友元。)
class Date; // 前置声明
class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour, int minute, int second)
		: _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)
		, _t(0,0,0)
	{}

	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;
};

5. 内部类

5.1概念及特性

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

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

特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。
class A
{
private:
	 static int k;
	 int h;
	 
public:
 class B
 {
 public:
	 void foo(const A& a)
 {
	 cout << k << endl;//OK
	 cout << a.h << endl;//OK
 }
 };
 
};

int A::k = 1;

int main()
{
	 A::B b;
	 b.foo(A());
 
 return 0;
}

总结

在C++中,友元机制使得类能够授予外部类或函数对其私有成员的访问权限,为实现某些特殊需求提供了便利。初始化列表能够在对象构造时直接对成员进行初始化,提高了代码的效率和可读性。内部类允许在一个类内定义另一个类,有效地组织代码结构。static修饰成员使得成员变得独立于类的实例,可以用于共享数据或实现单例模式。通过深入理解这些特性,我们能够更好地运用C++语言的强大功能,写出更具健壮性和可维护性的代码。

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

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

相关文章

Qt安装配置教程windows版(包括:Qt5.8.0版本,Qt5.12,Qt5.14版本下载安装教程)(亲测可行)

目录 Qt5.8.0版本安装教程Qt5.8.0版本下载安装 Qt5.12.2版本安装教程下载安装 Qt 5.14.2安装教程下载安装和创建项目 参考视频 QT为嵌入式系统提供了大量的库和可重用组件。 WPS Office&#xff0c;咪咕音乐&#xff0c;Linux桌面环境等都是QT开发的。 Qt5.8.0版本安装教程 Q…

MinIO对象存储介绍和使用

一、MinIO介绍 MinIO 是一个开源的对象存储服务器。MinIO 提供了一个强大而灵活的对象存储解决方案&#xff0c;适用于各种规模的应用场景。详细介绍可看官网文档&#xff1a;MinIO对象存储 Windows — MinIO中文文档 | MinIO Windows中文文档 1.1 特点 高性能: MinIO 具有出…

【深度学习】:滴滴出行-交通场景目标检测

清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 代码和报告均为本人自己实现&#xff08;实验满分&#xff09;&#xff0c;只展示主要任务实验结果&#xff0c;如果需要详细的实验报告或者代码可以私聊博主&#xff0c;接实验技术指导1对1 有任…

单片机学习笔记---DS1302时钟

上一节我们讲了DS1302的工作原理&#xff0c;这一节我们开始代码演示。 新创建一个工程写上框架 我们需要LCD1602进行显示&#xff0c;所以我们要将LCD1602调试工具那一节的LCD1602的模块化代码给添加进来 然后我们开始创建一个DS1302.c和DS1302.h 根据原理图&#xff0c;为了…

【Web】Spring rce CVE-2022-22965漏洞复现学习笔记

目录 原理概览 漏洞简述 Tomcat AccessLogValve 和 access_log 例题: 原理概览 spring框架在传参的时候会与对应实体类自动参数绑定&#xff0c;通过“.”还可以访问对应实体类的引用类型变量。使用getClass方法&#xff0c;通过反射机制最终获取tomcat的日志配置成员属性…

FOC--有感--clion

配置CLion用于STM32开发【优雅の嵌入式开发】 - 知乎 TIM1_CH3N是TIM1_CH3的互补输出通道。TIM1是一个高级定时器&#xff0c;具有互补输出功能。TIM1_CHx是PWM的主通道&#xff0c;而TIM1_CHxN则是PWM的互补输出通道。 开漏输出和推挽输出: 输出电平能力&#xff1a; 推挽输…

HiveSQL——共同使用ip的用户检测问题【自关联问题】

注&#xff1a;参考文章&#xff1a; SQL 之共同使用ip用户检测问题【自关联问题】-HQL面试题48【拼多多面试题】_hive sql 自关联-CSDN博客文章浏览阅读810次。0 问题描述create table log( uid char(10), ip char(15), time timestamp);insert into log valuesinsert into l…

2.6日学习打卡----初学RabbitMQ(一)

2.6日学习打卡 初识RabbitMQ、 一. MQ 消息队列 MQ全称Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保 存消息的容器。多用于系统之间的异步通信。 同步通信相当于两个人当面对话&#xff0c;你一言我一语。必须及时回复 异步通信相当于通…

猫头虎分享:2024龙年IT行业热门技术大全

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Python爬虫之文件存储#5

爬虫专栏&#xff1a;http://t.csdnimg.cn/WfCSx 文件存储形式多种多样&#xff0c;比如可以保存成 TXT 纯文本形式&#xff0c;也可以保存为 JSON 格式、CSV 格式等&#xff0c;本节就来了解一下文本文件的存储方式。 TXT 文本存储 将数据保存到 TXT 文本的操作非常简单&am…

Maui blazor ios 按设备类型设置是否启用safeArea

需求&#xff0c;新做了个app&#xff0c; 使用的是maui blazor技术&#xff0c;里面用了渐变背景&#xff0c;在默认启用SafeArea情况下&#xff0c;底部背景很突兀 由于现版本maui在SafeArea有点bug&#xff0c;官方教程的<ContentPage SafeAreafalse不生效&#xff0c;于…

【web前端开发】HTML及CSS简单页面布局练习

案例一 网页课程 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wi…

二阶系统的迹-行列式平面方法(trace-determinant methods for 2nd order system)

让我们再次考虑二阶线性系统 d Y d t A Y \frac{d\mathbf{Y}}{dt}A\mathbf{Y} dtdY​AY 我们已经知道&#xff0c;分析这种二阶系统。最主要的是注意它的特征值情形。 &#xff08;此处没有重根的情形&#xff0c;所有是partial&#xff09; 而特征值&#xff0c;也就是系…

Java异常的处理 try-catch-finally

目录 什么是异常通过if-else处理异常用if-else堵漏洞的缺点 try-catch例第一种处理第二种处理第三种处理第四种处理 try-catch-finally例 System.exit(0);//终止当前的虚拟机执行 什么是异常 Exception&#xff1a;在程序的运行过程中&#xff0c;发生了不正常的现象&#xff0…

探索未来:集成存储器计算(IMC)与深度神经网络(DNN)的机遇与挑战

开篇部分&#xff1a;人工智能、深度神经网络与内存计算的交汇 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为科技领域的一股强大力量&#xff0c;而深度神经网络&#xff08;DNN&#xff09;则是AI的核心引擎之一。DNN是一种模仿人类神经系统运作…

【Java八股面试系列】并发编程-并发关键字,线程池

目录 并发关键字 Synchronized synchronized最主要的三种使用方式&#xff1a; 具体使用&#xff1a;双重校验锁单例模式 synchronized 底层实现原理&#xff1f; synchronized锁的优化 偏向锁 轻量级锁 重量级锁 Mark Word 与 Monitor 之间的关系 总结 偏向锁、轻量…

2024年【高压电工】报名考试及高压电工操作证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年高压电工报名考试为正在备考高压电工操作证的学员准备的理论考试专题&#xff0c;每个月更新的高压电工操作证考试祝您顺利通过高压电工考试。 1、【单选题】 高压电动机发生单相接地故障时,只要接地电流大于()…

苹果mac电脑如何优化系统?保持不卡顿呢

再强悍的性能和优秀的操作系统&#xff0c;但长时间使用后&#xff0c;有时也会出现卡顿的情况。为了让你的苹果电脑保持高效运行&#xff0c;我们将深入探讨导致电脑卡顿的原因&#xff0c;并提供苹果电脑如何优化系统的解决方案&#xff0c;帮助你优化系统。 过多的启动项 …

第十七篇【传奇开心果系列】Python的OpenCV库技术点案例示例:自适应阈值二值化处理图像提取文字

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文目录前言一、自适应阈值二值化处理图像提取文字轮廓的初步示例代码:二、扩展思路介绍三、调整自适应阈值二值化的参数示例代码四、对二值化图像进行形态学操作示例代码五、使用轮廓特征进行筛选示…

C++ 中的模型预测控制(01/2)

目录 一、说明二、MPC原理说明三、分解算法的来源并显示关键特征&#xff0c;四、C 实现说明五、平衡 Q 和 R六、资源下载地址 一、说明 以下文章介绍了应用模型预测控制器的简单控制系统方法。本文讨论了这种控制的基本机制&#xff0c;该机制适用于各种工程领域。 MPC 涉及对…