第三弹:C++ 中的友元机制与运算符重载详解

文章目录

      • C++ 中的友元机制与运算符重载详解
      • 知识点1:C++ 友元机制
        • 1.1 友元的概念与意义
        • 1.2 友元的实现
          • 1.2.1 友元函数
          • 1.2.2 友元类
          • 1.2.3 友元成员函数
        • 1.3 友元的单向性和非传递性
        • 1.4 友元的谨慎使用
      • 知识点2:C++ 运算符重载
        • 2.1 运算符重载的概念
        • 2.2 运算符重载的实现方式
          • 2.2.1 友元函数实现运算符重载
          • 2.2.2 成员函数实现运算符重载
        • 2.3 运算符重载的特性
        • 2.4 不可重载的运算符
        • 2.5 自增自减运算符的前置与后置区别
        • 2.6 运算符重载的成员函数 vs. 友元函数
      • 知识点3:自定义字符串类的封装与运算符重载
        • 3.1 字符串类的封装
        • 3.2 深拷贝与浅拷贝
      • 知识点4:其他重要的运算符重载规则
        • 4.1 `=` 运算符重载的注意事项
        • 4.2 输入输出运算符重载
      • 总结与建议


C++ 中的友元机制与运算符重载详解

C++ 是一种强大的面向对象编程语言,具备封装、继承和多态等特性。而友元机制运算符重载作为 C++ 的两个重要特性,为开发者提供了更灵活和高效的编程方式。本文将深入探讨 C++ 中友元机制与运算符重载的概念、实现及其特性,并结合代码示例帮助读者理解这些机制的实际应用。


知识点1:C++ 友元机制

1.1 友元的概念与意义

在 C++ 中,封装性体现在类的成员变量和成员函数的访问控制上。通过访问权限修饰符(publicprotectedprivate)控制类的成员访问权限,以确保类的私有数据不被外部直接访问。然而,某些情况下,可能需要外部的某些函数或类能够访问类的私有成员,C++ 提供了友元机制来解决这一需求。

友元机制允许类的外部函数或其他类访问其私有成员。通过在类中使用 friend 关键字声明友元,打破了类的封装性,从而为复杂场景中的灵活操作提供了可能性。

1.2 友元的实现

C++ 中的友元机制可以通过以下几种方式实现:

  1. 友元函数:非类成员函数可以通过友元机制访问类的私有成员。
  2. 友元类:某个类的所有成员函数都可以通过友元机制访问另一个类的私有成员。
  3. 友元成员函数:某个特定的成员函数通过友元机制访问其他类的私有成员。
1.2.1 友元函数

友元函数是非类成员的普通函数。通过在类中声明为友元函数,它可以访问类的私有成员。这种机制常用于全局函数对类的操作,例如对类的输入输出操作。

示例代码:

#include <iostream>
using namespace std;class Demo {
private:int val;  // 私有成员变量public:// 公共成员函数,用于设置值void setVal(int myval) {val = myval;}// 声明友元函数,允许访问私有成员friend int getVal(const Demo& obj);
};// 友元函数定义,可以访问私有成员 val
int getVal(const Demo& obj) {return obj.val;
}int main() {Demo obj;obj.setVal(5);  // 设置值cout << "The value is: " << getVal(obj) << endl;  // 通过友元函数访问私有成员return 0;
}
1.2.2 友元类

友元类是指某个类的所有成员函数都可以访问另一个类的私有和保护成员。这种机制用于两个类之间的紧密合作场景,例如一个类封装数据,另一个类专门处理这些数据。

示例代码:

#include <iostream>
using namespace std;class DemoA;  // 前向声明class DemoB {
public:// 友元类成员函数可以访问 DemoA 的私有成员void modifyVal(DemoA& obj, int newVal);
};class DemoA {
private:int val;  // 私有成员// 声明 DemoB 为友元类,允许其访问私有成员friend class DemoB;public:DemoA(int v) : val(v) {}void showVal() const {cout << "Value: " << val << endl;}
};// 友元类 DemoB 中的成员函数可以修改 DemoA 的私有成员
void DemoB::modifyVal(DemoA& obj, int newVal) {obj.val = newVal;
}int main() {DemoA objA(10);DemoB objB;objA.showVal();  // 输出:Value: 10objB.modifyVal(objA, 20);  // 通过友元类修改私有成员objA.showVal();  // 输出:Value: 20return 0;
}
1.2.3 友元成员函数

当我们只希望另一个类的特定成员函数访问某个类的私有成员时,可以使用友元成员函数。这种方式可以更精细地控制访问权限。

示例代码:

#include <iostream>
using namespace std;class DemoB;  // 前向声明class DemoA {
private:int val;  // 私有成员public:DemoA(int v) : val(v) {}// 友元成员函数,允许访问私有成员friend int DemoB::getVal(const DemoA& obj);
};class DemoB {
public:// 访问 DemoA 的私有成员int getVal(const DemoA& obj) {return obj.val;}
};int main() {DemoA objA(100);DemoB objB;cout << "The value is: " << objB.getVal(objA) << endl;  // 输出私有成员return 0;
}
1.3 友元的单向性和非传递性

友元关系具有以下特性:

  • 单向性:如果类A声明类B为友元,类B可以访问类A的私有成员,但类A不能访问类B的私有成员,除非类B也声明类A为友元。
  • 非传递性:如果类A是类B的友元,类B是类C的友元,类A不能自动成为类C的友元。
1.4 友元的谨慎使用

尽管友元机制提供了访问私有成员的灵活性,但它也破坏了类的封装性。如果滥用友元,可能会导致代码的耦合度增加,数据的安全性降低。因此,友元应仅在必要时使用,避免破坏类的封装原则。


知识点2:C++ 运算符重载

2.1 运算符重载的概念

运算符重载是 C++ 提供的一种功能,它允许我们为自定义类型赋予运算符的新含义。通过重载运算符,我们可以像对待内置数据类型一样对自定义类进行运算操作。运算符重载的本质是函数重载,其中运算符被视为函数,操作数是函数的参数。

2.2 运算符重载的实现方式

运算符重载可以通过两种方式实现:友元函数成员函数

2.2.1 友元函数实现运算符重载

友元函数可以访问类的私有成员,因此在运算符需要访问私有成员时,可以通过友元函数进行重载。

示例代码:

#include <iostream>
using namespace std;class Demo {
private:int val;  // 私有成员变量public:Demo(int v = 0) : val(v) {}// 友元函数重载 + 运算符friend Demo operator+(const Demo& obj1, const Demo& obj2);// 获取值int getVal() const {return val;}
};// 重载 + 运算符,返回两个对象相加后的结果
Demo operator+(const Demo& obj1, const Demo& obj2) {return Demo(obj1.val + obj2.val);
}int main() {Demo obj1(10), obj2(20);Demo obj3 = obj1 + obj2;  // 使用重载的 + 运算符cout << "Result of addition: " << obj3.getVal() << endl;  // 输出结果return 0;
}
2.2.2 成员函数实现运算符重载

成员函数重载运算符时,左操作数隐式为调用该成员函数的对象,因此参数个数比友元函数少一个。

示例代码:

#include <iostream>
using namespace std;class Demo {
private:int val;  // 私有成员变量public:Demo(int v = 0) : val(v) {}// 成员函数重载 + 运算符Demo operator+(const Demo& obj) const {return Demo(this->val + obj.val);}// 获取值int getVal() const {return val;}
};int main() {Demo obj1(10), obj2(20);Demo obj3 = obj1 + obj2;  // 使用重载的 + 运算符cout << "Result of addition: " << obj3.getVal() << endl;  // 输出结果return 0;
}
2.3 运算符重载的特性
  1. **运算符重载不会改变运

算符的优先级**:例如重载 + 运算符后,它的优先级仍然和原始 + 运算符一致。
2. 不能改变运算符的参数个数:例如,重载二元运算符 + 时,必须有两个操作数。
3. 某些运算符必须以成员函数的方式重载:如 =[]()-> 这些运算符只能通过成员函数重载。

2.4 不可重载的运算符

C++ 中有少数运算符是不能被重载的,这些运算符包括:

  • .(成员访问运算符)
  • ::(作用域解析运算符)
  • .*(成员指针访问运算符)
  • ?:(三元条件运算符)
  • sizeof(计算大小运算符)
  • typeid(类型识别运算符)

这些运算符的语法和行为在 C++ 中是固定的,无法被修改或重载。

2.5 自增自减运算符的前置与后置区别

自增 (++) 和自减 (--) 运算符既可以作为前置运算符,也可以作为后置运算符使用,二者的区别在于运算的顺序:

  • 前置运算符:先自增/自减,再返回值。
  • 后置运算符:先返回值,再自增/自减。

示例代码:

#include <iostream>
using namespace std;class Demo {
private:int val;public:Demo(int v = 0) : val(v) {}// 前置自增运算符重载Demo& operator++() {++val;return *this;}// 后置自增运算符重载Demo operator++(int) {Demo temp = *this;val++;return temp;}int getVal() const {return val;}
};int main() {Demo obj(5);cout << "Initial value: " << obj.getVal() << endl;  // 输出 5++obj;  // 前置自增cout << "After prefix ++: " << obj.getVal() << endl;  // 输出 6obj++;  // 后置自增cout << "After postfix ++: " << obj.getVal() << endl;  // 输出 7return 0;
}
2.6 运算符重载的成员函数 vs. 友元函数
  • 成员函数重载:适用于当运算符左边的操作数是当前类对象的情况。例如,赋值运算符 = 和下标运算符 [] 通常通过成员函数重载。
  • 友元函数重载:用于当运算符左操作数不是类对象,或需要访问类的私有成员时。例如,输入输出运算符 <<>> 通常通过友元函数重载。

知识点3:自定义字符串类的封装与运算符重载

为了更好地理解运算符重载的实际应用,接下来我们将通过封装一个自定义的字符串类 String 来展示如何通过运算符重载实现字符串的赋值、拼接和比较操作。

3.1 字符串类的封装

自定义 String 类的核心是动态管理字符数组,并通过重载运算符实现字符串的各种操作。

3.2 深拷贝与浅拷贝

在自定义类中,尤其是涉及动态内存的类,必须正确实现深拷贝,以避免多个对象共享同一块内存区域的问题。浅拷贝只复制指针,可能导致内存管理冲突,而深拷贝则复制内容,确保每个对象拥有独立的内存。

示例代码:

#include <iostream>
#include <cstring>
using namespace std;class String {
private:char* data;public:// 构造函数String(const char* str = nullptr) {if (str) {data = new char[strlen(str) + 1];strcpy(data, str);} else {data = new char[1];*data = '\0';}}// 拷贝构造函数(深拷贝)String(const String& other) {data = new char[strlen(other.data) + 1];strcpy(data, other.data);}// 重载赋值运算符(深拷贝)String& operator=(const String& other) {if (this != &other) {delete[] data;data = new char[strlen(other.data) + 1];strcpy(data, other.data);}return *this;}// 重载加法运算符,用于字符串拼接String operator+(const String& other) const {String newStr;newStr.data = new char[strlen(data) + strlen(other.data) + 1];strcpy(newStr.data, data);strcat(newStr.data, other.data);return newStr;}// 重载下标运算符,访问字符char& operator[](int index) {return data[index];}// 析构函数~String() {delete[] data;}// 输出字符串void print() const {cout << data << endl;}
};int main() {String str1("Hello");String str2(" World");String str3 = str1 + str2;  // 使用 + 运算符拼接字符串str3.print();  // 输出:Hello Worldstr1 = "Hi";  // 使用赋值运算符str1.print();  // 输出:Hicout << "First character of str1: " << str1[0] << endl;  // 使用下标运算符return 0;
}

知识点4:其他重要的运算符重载规则

4.1 = 运算符重载的注意事项

在重载赋值运算符时,必须考虑自我赋值的情况。自我赋值可能会导致数据丢失,因此需要特别检查对象是否为自身。如果是自身,则无需执行赋值操作。

4.2 输入输出运算符重载

<<>> 运算符通常用于输入输出操作。由于 ostreamistream 是标准库中的类对象,且不属于用户自定义类,因此它们通常通过友元函数来进行重载。

示例代码:

#include <iostream>
using namespace std;class Point {
private:int x, y;public:Point(int x = 0, int y = 0) : x(x), y(y) {}// 友元函数,重载 << 运算符用于输出friend ostream& operator<<(ostream& out, const Point& p);// 友元函数,重载 >> 运算符用于输入friend istream& operator>>(istream& in, Point& p);
};// 重载 << 运算符
ostream& operator<<(ostream& out, const Point& p) {out << "(" << p.x << ", " << p.y << ")";return out;
}// 重载 >> 运算符
istream& operator>>(istream& in, Point& p) {in >> p.x >> p.y;return in;
}int main() {Point p1(3, 4);cout << "Point 1: " << p1 << endl;Point p2;cout << "Enter coordinates for Point 2: ";cin >> p2;cout << "Point 2: " << p2 << endl;return 0;
}

总结与建议

C++ 的友元机制运算符重载为开发者提供了强大的编程工具,使得自定义类型的设计更加灵活和高效。然而,这些机制在使用时需要遵循以下原则:

  1. 谨慎使用友元:友元机制打破了类的封装性,应仅在必要时使用,避免增加类之间的耦合性。
  2. 合理运用运算符重载:运算符重载能够增强代码的可读性,但应确保重载后的行为符合用户的直观预期。
  3. 深拷贝的实现:当类涉及动态内存管理时,必须正确实现深拷贝,以避免浅拷贝带来的内存管理问题。

通过对友元和运算符重载的深入理解,开发者可以编写出更加健壮、高效的 C++ 程序,实现更优雅的代码结构和逻辑。

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

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

相关文章

mysql-索引笔记

索引 1、什么是索引 索引是对数据库中数据的一种结构化表示。它像一本书的目录&#xff0c;能够快速定位信息&#xff0c;而无需逐行扫描所有数据。 索引的出现其实就是为了提高数据查询的效率&#xff0c;就像书的目录一样。 2、索引的常见模型 2.1.哈希表 用一个哈希函…

828华为云征文|华为云 Flexus X 实例初体验

一直想有自己的一款的服务器&#xff0c;为了更好的进行家庭娱乐&#xff0c;甚至偶尔可以满足个人搭建开发环境的需求&#xff0c;直到接触到了华为云 Flexus X 云服务器。Flexus 云服务器 X 实例是面向中小企业和开发者打造的轻量级云服务器。提供快速应用部署和简易的管理能…

每日论文5—06TCAS2锁相环电流匹配的gain-boosting电荷泵

《Gain-Boosting Charge Pump for Current Matching in Phase-Locked Loop》 06TCAS2 本质上和cascode来增加输出电阻&#xff0c;从而减小电流变化的思路是一样的。这里用了放大器来增加输出电阻。具体做法如下图&#xff1a; 如图1(a)&#xff0c;A3把Vb和Vx拉平&#xff0…

vscode安装及c++配置编译

1、VScode下载 VS Code官网下载地址&#xff1a;Visual Studio Code - Code Editing. Redefined。 2、安装中文插件 搜索chinese&#xff0c;点击install下载安装中文插件。 3、VS Code配置C/C开发环境 3.1、MinGW-w64下载 VS Code是一个高级的编辑器&#xff0c;只能用来写代…

基础算法--枚举

枚举算法是一种简单而有效的算法&#xff0c;它通过枚举所有可能的情况来解决问题。它通常用于解决问题规模比较小的问题&#xff0c;因为它的时间复杂度很高&#xff0c;随着问题的规模增加&#xff0c;算法的效率会急剧下降。 枚举算法的基本思路是通过循环遍历所有可能的情…

Rust和Go谁会更胜一筹

在国内&#xff0c;我认为Go语言会成为未来的主流&#xff0c;因为国内程序员号称码农&#xff0c;比较适合搬砖&#xff0c;而Rust对心智要求太高了&#xff0c;不适合搬砖。 就个人经验来看&#xff0c;Go语言简单&#xff0c;下限低&#xff0c;没有什么心智成本&#xff0c…

使用MTVerseXR SDK实现VR串流

1、概述​ MTVerseXR SDK 是摩尔线程GPU加速的虚拟现实&#xff08;VR&#xff09;流媒体平台&#xff0c;专门用于从远程服务器流式传输基于标准OpenXR的应用程序。MTVerseXR可以通过Wi-Fi和USB流式将VR内容从Windows服务器流式传输到XR客户端设备, 使相对性能低的VR客户端可…

【CSS in Depth 2 精译_043】6.5 CSS 中的粘性定位技术 + 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09;第二章 相对单位&#xff08;已完结&#xff09;第三章 文档流与盒模型&#xff08;已完结&#xff09;第四章 Flexbox 布局&#xff08;已…

【2022工业3D异常检测文献】AST: 基于归一化流的双射性产生不对称学生-教师异常检测方法

Asymmetric Student-Teacher Networks for Industrial Anomaly Detection 1、Background 所谓的学生-教师网络&#xff0c;首先&#xff0c;对教师进行训练&#xff0c;以学习语义嵌入的辅助性训练任务&#xff1b;其次&#xff0c;训练学生以匹配教师的输出。主要目的是让学生…

SpringBoot日志打印实践

背景 在项目当中&#xff0c;我们经常需要打印一些日志埋点信息&#xff0c;这些日志埋点信息&#xff0c;在后续软件的运维、稳定性建设中发挥了巨大的作用&#xff1a; 问题追踪&#xff1a;通过埋点日志中的关键信息&#xff0c;帮助定位系统异常原因系统监控&#xff1a;…

移动硬盘传输中断后无法识别:问题解析与数据恢复策略

一、移动硬盘传输中断后的无法识别现象 在日常的数据传输过程中&#xff0c;移动硬盘作为便携式的存储介质&#xff0c;扮演着举足轻重的角色。然而&#xff0c;当传输过程被意外中断&#xff0c;且移动硬盘随后无法被系统识别时&#xff0c;这无疑会给用户带来极大的困扰。你…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(上篇)

Deforum 与 AnimateDiff 不太一样&#xff0c; AnimateDiff 是生成丝滑变化视频的&#xff0c;而 Deforum 的丝滑程度远远没有 AnimateDiff 好。 它是根据对比前面一帧的画面&#xff0c;然后不断生成新的相似图片&#xff0c;来组合成一个完整的视频。 Deforum 的优点在于可…

Pikachu-Sql Inject-宽字节注入

基本概念 宽字节是相对于ascII这样单字节而言的&#xff1b;像 GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是常说的宽字节&#xff0c;实际上只有两字节 GBK 是一种多字符的编码&#xff0c;通常来说&#xff0c;一个 gbk 编码汉字&#xff0c;占用2个字节。一个…

【一文理解】conda install pip install 区别

大部分情况下&#xff0c;conda install & pip install 二者安装的package都可以正常work&#xff0c;但是混装多种package后容易版本冲突&#xff0c;出现各种报错。 目录 检查机制 支持语言 库的位置 环境隔离 编译情况 检查机制 conda有严格的检查机制&#xff0c…

【C++】模拟实现红黑树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 目录 一.了解项目功能 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现RBTreeNode类模板 &#x1f38f;构造RBTreeNode类成员变量 &#x1f38f;实现RBTreeNode类构…

图解C#高级教程(二):事件

在现实生活当中&#xff0c;有一些事情发生时&#xff0c;会连带另一些事情的发生。例如&#xff0c;当某国的总统发生换届时&#xff0c;不同党派会表现出不同的行为。两者构成了“因果”关系&#xff0c;因为发生了A&#xff0c;所以发生了B。在编程语言当中&#xff0c;具有…

Android问题笔记五十:构建错误-AAPT2 aapt2-7.0.2-7396180-windows Daemon

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

Visual Studio 字体与主题推荐

个人推荐&#xff0c;仅供参考&#xff1a; 主题&#xff1a;One Monokai VS Theme 链接&#xff1a;One Monokai VS Theme - Visual Studio Marketplacehttps://marketplace.visualstudio.com/items?itemNameazemoh.onemonokai 效果&#xff1a; 字体&#xff1a;JetBrain…

SpringBoot项目请求不中断动态更新代码

在开发中&#xff0c;有时候不停机动态更新代码热部署是一项至关重要的功能&#xff0c;它可以在请求不中断的情况下下更新代码。这种方式不仅提高了开发效率&#xff0c;还能加速测试和调试过程。本文将详细介绍如何在 Spring Boot 项目在Linux系统中实现热部署&#xff0c;特…

《业务三板斧:定目标、抓过程、拿结果》读书笔记1

这个书是24年新书&#xff0c;来自阿里系的人的作品&#xff0c;还可以。今天先看前沿部分的精彩部分&#xff1a; 我们在服务企业的过程中&#xff0c;发现了一个常见的管理现象&#xff1a;管理者自 己承担了团队里重要的项目&#xff0c;把风险和压力都集中在自己身上。因 此…