【C++】类和对象——友元函数和友元类的概念、初始化列表、explicit关键字、static成员

news2024/12/28 20:56:29

文章目录

  • 1.友元函数和友元类的概念
    • 1.1友元函数
    • 1.2友元类
  • 2.构造函数知识补充
    • 2.1初始化列表
    • 2.2explicit关键字
  • 3.static成员
    • 3.1static成员概念
    • 3.2static成员特性

1.友元函数和友元类的概念

  在C++中,友元函数和友元类是指允许非成员函数或非成员类访问某个类中的私有成员或保护成员的一种机制,友元函数和友元类都是友元,只是类型不同。具体来说:

  友元函数: 是在类声明中通过 friend 关键字声明的非成员函数,该函数能够访问该类中被声明为私有成员或者保护成员的数据或函数。 友元函数可以是普通函数、成员函数、类的静态成员函数等,其实现视为平等于类成员函数。

  友元类:是指 使用 friend 关键字声明的其他类,可以访问该类中被声明为私有成员或者保护成员的数据和方法。

友元就和我们生活中的朋友伙伴一样。🙋‍♂️🙋

在这里插入图片描述

  朋友之间相互信任,同样,友元函数和友元类的作用是为某些具有特殊访问需求的函数或类提供访问私有成员的权限,从而更灵活地实现程序的设计。朋友之间要有一定的隐私,同样,使用友元机制可能会破坏封装性和安全性,因此需要谨慎使用,遵循良好的设计原则。

友元函数的举例:

class MyClass
{
    int dataA;
    friend int MyFunc(MyClass obj); // 声明类外的函数为友元函数
public:
    MyClass(int a) : dataA(a) {};
};

int MyFunc(MyClass obj) 
{
    return obj.dataA; // 可以访问 MyClass 的私有成员 dataA
}

int main() 
{
    MyClass obj(123);
    int result = MyFunc(obj);
    cout << result << endl; // 输出 123
}

友元类的举例:

class A 
{
    int x;
    friend class B; // 声明类 B 是 A 的友元类
public:
    A(int a) : x(a) {}
};

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

int main()
{
    A objA(10);
    B objB;
    objB.print(objA); // 输出 10
    return 0;
}

1.1友元函数

友元函数的特点:

(1)友元函数可访问类的私有和保护成员,但不是类的成员函数

(2)友元函数不能用const修饰;

(3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制

(4) 一个函数可以是多个类的友元函数;

(5) 友元函数的调用与普通函数的调用原理相同。

  问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。

  因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将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关键字。

  我们定义了一个友元输入运算符operator>>,它的作用是重载了输入流(比如cin)中提取对象的操作符“>>”,可以将用户在控制台中输入的日期信息提取到Date对象中。这里也使用了友元的机制来实现对私有成员变量的访问。

  这个例子展示了如何使用重载输入输出运算符,在控制台中进行输入输出自定义类型对象,并且使用友元机制来实现对私有成员变量的访问。友元机制可以在某些情况下方便地实现对私有成员变量的访问而不暴露给外界。

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友元类

友元类的特点:

(1)友元关系是单向的,不具有交换性;

(2)友元关系不能传递;

(3)友元关系不能继承。

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

  我们定义了两个类Date和Time,其中Time类表示时间,Date类表示日期。在Time类的定义中,通过使用friend关键字声明Date类为Time类的友元类。这个声明的作用是,允许在Date类中直接访问Time类的私有成员变量。

  Date类有一个名为SetTimeOfDate的公有成员函数,用于设置日期的时分秒信息。在SetTimeOfDate函数的实现中可以直接访问Time类的私有成员变量来设置时间的时分秒信息。

  这个例子展示了如何在一个类中声明另一个类为友元类,使得友元类能够访问当前类中的私有成员,并且提供了一种简便的方法来完成复杂操作。同时需要注意的是,友元机制在一定程度上破坏了封装性,建议在实际编程中慎重使用,遵从信息隐蔽、分离的设计原则,保证程序的可维护性和可扩展性。

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

2.构造函数知识补充

2.1初始化列表

  初始化列表(Initialization list)是在创建对象时初始化成员变量的一种方式。在C++中,构造函数可以通过初始化列表来指定构造对象时的初值。初始化列表用一个冒号分隔,列出每个成员变量和初始值。

语法格式为:

class MyClass 
{
public:
    MyClass(int x, int y, int z) 
    : _x(x)
    , _y(y)
    , _z(z)
     {}
     
private:
    int _x;
    int _y;
    int _z;
};

  在上面的代码示例中,MyClass类的构造函数需要三个参数,分别是x,y,z,并且每个参数都被用来初始化成员变量_x,_y,_z。构造函数通过初始化列表的方式将参数值赋给成员变量,这种写法比在构造函数体中显式地进行赋值更为高效,尤其在成员变量具有类类型时表现更为明显。

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

这个不就是显示赋值吗?为什么要弄一个初始化列表?

使用初始化列表和在构造函数中显式赋值的主要区别在于效率。

当一个类的成员变量本身是一个类对象时,在构造函数中对其进行赋值是需要先构造临时对象,然后再调用复制构造函数将复制这个临时对象到成员变量中。而使用初始化列表则可以直接在成员变量构造函数时调用复制构造函数,减少了临时对象的创建和销毁操作,提高代码效率。

此外,在使用初始化列表的情况下,成员变量的初始化顺序和它们在类中的声明顺序是一致的,而在构造函数体内部显式赋值的情况下,成员变量的初始化顺序则与其在初始化列表中的顺序无关。这在一些情况下可能会影响程序的正确性和性能。因此,在编写构造函数时尽可能使用初始化列表是一种提高程序效率和可读性的好习惯。

初始化列表需要注意:

(1)每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

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

    1.引用成员变量

    2.const成员变量

    3.自定义类型成员(且该类没有默认构造函数时)


  我们定义了两个类A和B,其中A类有一个整型成员变量_a,构造函数可以用传入的参数a为_a初始化。

  B类有三个成员变量:一个A类对象_aobj,一个整型的引用变量_ref和一个const常量整数_n,分别通过初始化列表进行初始化。注意到A类没有默认构造函数,所以在B类中需要显式地传入一个int类型的参数用于初始化_aobj。

  引用变量_ref必须在初始化列表中进行初始化,因为引用是一种没有独立地址的别名类型,必须在创建时初始化,不能后期再初始化。同时_n是一个const常量成员变量,也必须通过初始化列表进行初始化,因为const常量成员变量的值不能在构造函数体中被修改。

  总之,这段代码演示了如何使用初始化列表对类的成员变量进行初始化,避免了在构造函数体中进行显式赋值的低效率问题,并且介绍了引用和const常量成员变量的初始化方法。

class A
{
public:
	A(int a)
	:_a(a)
	{}

private:
	int _a;
};

class B
{
public:
B(int a, int ref)
	:_aobj(a)
	,_ref(ref)
	,_n(10)
	{}
	
private:
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const 
};

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

(4)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

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

//成员变量在类中声明次序就是其在初始化列表中的初始化顺序,
//与其在初始化列表中的先后次序无关。
//因此,在上述程序中,_a2会比_a1先进行初始化,因此输出的结果应该是:

//1和随机值。

2.2explicit关键字

  explicit是C++中的一个关键字,用于修饰单参数构造函数。它的作用是防止编译器进行隐式类型转换,只允许显式调用构造函数完成对象的初始化。

  我们定义一个类Person,用于表示一个人员的姓名和年龄等信息。我们可以使用以下代码定义这个类:

class Person
 {
public:
    Person(const std::string& name, int age)
     : m_name(name)
     , m_age(age) 
     {}

    // 使用 explicit 关键字禁止隐式类型转换
    explicit Person(int age)
     : m_name("Unknown")
     , m_age(age) {}

    std::string getName() const 
    {
        return m_name;
    }

    int getAge() const
     {
        return m_age;
    }

private:
    std::string m_name;
    int m_age;
};

  上述代码中,我们定义了两个构造函数。第一个构造函数接受一个姓名和年龄,用于初始化一个Person对象。第二个构造函数只接受年龄作为参数,并将姓名的默认值设置为"Unknown"。最重要的是,我们将第二个构造函数声明为explicit,以避免隐式类型转换。

  现在,我们可以在程序中使用这两个构造函数来创建Person对象,在 main() 函数中,我们首先用姓名和年龄初始化一个Person对象 p1,然后使用年龄初始化一个Person对象 p2,并将姓名默认设为"Unknown"。最后,我们注释掉了隐式类型转换的语句。如下所示:

int main()
{
    // 直接使用姓名和年龄初始化一个Person对象
    Person p1("Tom", 32);
    std::cout << p1.getName() << " is " << p1.getAge() << " years old.\n"; 
    // 输出 "Tom is 32 years old."

    // 使用年龄初始化一个Person对象
    Person p2(25);
    std::cout << p2.getName() << " is " << p2.getAge() << " years old.\n"; 
    // 输出 "Unknown is 25 years old."

    // 隐式类型转换会导致编译错误
    // Person p3 = 28;
    
    return 0;
}

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

3.static成员

3.1static成员概念

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

  在上面的代码中,我们使用了一个静态局部变量count来统计函数foo()被调用的次数。由于它是静态变量,会在程序启动时被分配内存,只会被初始化一次,并且在每次函数调用时都会保留上次计数的结果。因此,每次调用该函数都会输出不同的计数结果。

  另外,需要注意的是静态变量的作用域只限于包含它的函数,其他函数无法直接访问它。因此,我们可以使用静态变量来保持函数内部的数据并防止它们被意外地修改或破坏。

void foo() {
    static int count = 0; // 声明一个静态局部变量
    count++;
    std::cout << "This function has been called " << count << " times." << std::endl;
}

int main() {
    foo(); // 输出 "This function has been called 1 times."
    foo(); // 输出 "This function has been called 2 times."
    foo(); // 输出 "This function has been called 3 times."
    return 0;
}

3.2static成员特性

(1)静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区;

(2)静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明;

(3) 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问;

(4) 静态成员函数没有隐藏的this指针,不能访问任何非静态成员;

(5) 静态成员也是类的成员,受public、protected、private 访问限定符的限制。

这些就是C++中类和对象中友元和初始化列表等的简单介绍了😉
如有错误❌望指正,最后祝大家学习进步✊天天开心✨🎉

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

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

相关文章

LeetCode面向运气之Javascript—第13题-罗马数字转整数-99.21%

LeetCode第13题-罗马数字转整数 题目要求 给定一个罗马数字&#xff0c;将其转换成整数。 罗马数字 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M 分别代表1&#xff0c;5&#xff0c;10&#xff0c;50&#xf…

一些常用的分布式组件实现技巧

广播 可用redis的pubsub机制来支持集群内的广播。 基于redis的分布式锁 加锁 使用setnx命令&#xff1a; SET lock_key random_value NX PX 5000 其中&#xff1a; random_value 是客户端生成的唯一的字符串&#xff0c;用于在删除时唯一标识client身份。 NX 代表只在键不…

【开发实用】还在用BeanUtils?不如用MapStruct

文章目录 1. 什么是MapStruct2. 为什么使用MapStruct3. 如何使用MapStruct 1. 什么是MapStruct MapStruct是一个Java注解处理器&#xff0c;它可以简化Java bean之间的转换。它使用基于生成器的方法创建类型安全的映射代码&#xff0c;这些代码在编译时生成&#xff0c;并且比…

2023夏季黑客松大赛,Moonbeam邀请你来BUIDL

由Parity和OneBlock联合举办的「2023 夏季波卡黑客松大赛」正在火热开启中。自报名开启之日&#xff0c;便获得了来自海内外对波卡生态的高度专注和对Web3开发的热情。 本次黑客松聚焦智能合约、开发工具、社交网络等大赛命题&#xff0c;邀请了行业领军人、技术大咖、投资人等…

推进开源法律知识普及|2023开放原子全球开源峰会开源法律与合规分论坛即将启幕

随着开源在推动创新、促进协作方面的作用日益凸显&#xff0c;开源领域的法律与合规问题日益受到关注。 6月11日&#xff0c;开放原子全球开源峰会开源法律与合规分论坛将在北京经济开发区国家信创园召开&#xff0c;论坛以“开源知识产权的深度现实与广阔未来”为主题&#x…

基于SSM的校园旧书交易交换平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Mybatis_plus——标准分页功能制作

mybatispuls中提供分页查询中需要两个参数&#xff0c;一个是IPage接口的实现类&#xff0c;还有一个后面说。 IPage有且只有一个实现类Page类型在里面已经提供有了&#xff0c;传两个参数即可使用&#xff0c;一个是页码值&#xff0c;一个是每页显示数据的条数。查询完之后可…

chatgpt赋能python:Python代做:让您的网站更友好的SEO利器

Python代做&#xff1a;让您的网站更友好的SEO利器 如果您是一位网站管理员或者SEO工程师&#xff0c;您一定知道SEO对于网站的重要性。那么在SEO中&#xff0c;Python代做可以为您提供什么&#xff1f;在本文中&#xff0c;我们将通过介绍Python代做的技术和方法&#xff0c;…

unity发布webGL后无法预览解决

众所周知&#xff0c;unity发布成webgl后是无法直接预览的。因为一般来说浏览器默认都是禁止webgl运行的。 直接说我最后的解决方法&#xff1a;去vscode里下载一个live server ,安装好。 下载vscode地址Visual Studio Code - Code Editing. Redefined 期间试过几种方法都不管…

Ansys Zemax | 探究 OpticStudio 偏振分析功能

本文介绍了 OpticStudio 模拟基于偏振的光学现象的几种方法。本文的目的是在对基于偏振的光学进行建模时检查这些特征的优势和正确应用。讨论的功能包括偏振光瞳图、琼斯矩阵、双折射、表面涂层等。这些对于波片和隔离器等实际应用很重要。&#xff08;联系我们获取文章附件&am…

plt.loglog()函数的用法和示例(含代码)

目录 常用坐标下的图像显示在loglog函数下的显示同时显示参考文献 plt.loglog()函数通常是用于和对数函数相关的显示中。 在研究plt.loglog()函数之前&#xff0c;我们可以先从常见的线性平面坐标系入手。 如 np.linespace()函数,它在指定的间隔内返回均等的数字。 np.linespa…

Redis主从架构、数据同步原理、全量同步、增量同步

目录 专栏导读一、Redis主从架构二、数据同步原理三、全量同步的流程三、可以从以下几个方面来优化Redis主从就集群四、全量同步和增量同步区别&#xff1f;五、什么时候执行全量同步&#xff1f;六、什么时候执行增量同步&#xff1f;七、超卖问题 大家好&#xff0c;我是哪吒…

高完整性系统工程(八):Hoare Logic

目录 1. 霍尔逻辑&#xff08;Proving Programs Correct&#xff09; 1.1 警告&#xff08;Caveats&#xff09; 1.2 误解&#xff08;Misconception&#xff09; 1.3 编程语言&#xff08;Programming Language&#xff09; 1.4 程序&#xff08;Programs&#xff09; 1…

java学习 spring mybatis maven juc并发 缓存 分布式

Spring系列第11篇&#xff1a;bean中的autowire-candidate又是干什么的&#xff1f;_路人甲Java的博客-CSDN博客 Spring系列 Spring系列第1篇&#xff1a;为何要学spring&#xff1f; Spring系列第2篇&#xff1a;控制反转&#xff08;IoC&#xff09;与依赖注入&#xff08;DI…

I.MX RT1170加密启动详解(1):加密Boot镜像组成

使用RT1170芯片构建的所有平台一般都是高端场合&#xff0c;我们需要考虑软件的安全需求。该芯片集成了一系列安全功能。这些特性中的大多数提供针对特定类型攻击的保护&#xff0c;并且可以根据所需的保护程度配置为不同的级别。这些特性可以协同工作&#xff0c;也可以独立工…

macOS Ventura 13.5beta2 OpenCore 双引导分区原版黑苹果镜像

镜像特点&#xff08;本文原地址&#xff1a;http://www.imacosx.cn/113805.html&#xff0c;转载请注明出处&#xff09; 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clove…

【cfeng work】什么是云原生 Cloud Native

WorkProj 内容管理 云原生云原生应用十二要素应用cfeng的work理解 本文introduce 云原生 Cloud Native相关内容 随着技术的迭代&#xff0c;从最初的物理机—> 虚拟机&#xff0c;从单机 —> 分布式微服务&#xff0c; 现在的热门概念就是云☁&#xff08;cloud&#xff…

Windows 11 绕过 TPM 方法总结,通用免 TPM 镜像下载 (2023 年 5 月更新)

Windows 11 绕过 TPM 方法总结&#xff0c;通用免 TPM 镜像下载 (2023 年 5 月更新) 在虚拟机、Mac 电脑和 TPM 不符合要求的旧电脑上安装 Windows 11 的通用方法总结 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11-no-tpm/&#xff0c;查看最新版。原创作品…

Tomcat安全配置

1.删除webapps里面自带文件&#xff08;关闭manage页面等&#xff09; 删除webapps目录中的docs、examples、host-manager、manager等正式环境用不着的目录&#xff0c;这一步就可以解决大部分漏洞。有的网站没删除manager页面&#xff0c;并且管理员弱口令&#xff0c;导致直…

PCL点云处理之三维凸包点提取与凸包模型生成,分别保存到PCD与PLY文件(一百七十一)

PCL点云处理之三维凸包点提取与凸包模型生成,分别保存到PCD与PLY文件(一百七十一) 一、算法介绍二、算法实现1.代码2.结果总结一、算法介绍 现给定一块点云,需要实现下面两个功能开发 (1)获取点云的三维凸包点,保存至PCD格式的文件中 (2)获取点云的三维凸包模型,保存…