C++友元和运算符重载

news2024/9/28 18:25:28

目录

一. 友元 friend

1.1 概念

1.2 友元函数

1.3 友元类

1.4 友元成员函数

二. 运算符重载

2.1 概念

2.2成员函数运算符重载

2.3 成员函数运算符重载

2.4 特殊运算符重载

2.4.1 赋值运算符重载

2.4.2 类型转换运算符重载

2.5 注意事项

三、std::string 字符串类(熟悉)


一. 友元 friend

1.1 概念

定义:

        类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

        友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的所有成员

作用:

        在于提高程序的运行效率,但是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。导致程序维护性变差,因此使用友元要慎用。

友元较为实际的应用是在运算符重载,这种应用可以提高软件系统的灵活性。

分类:

● 友元函数

● 友元类

● 友元成员函数

1.2 友元函数

友元函数是一种“声明”在类内,实际在类外的普通函数

#include <iostream>

using namespace std;

class Girl
{
private:
    int age;

public:
    Girl(int age):age(age){}

    int get_age() const
    {
        cout << &age << endl;
        return 18;
    }

    // 1. "声明"友元函数
    friend void access_true_age(Girl&);
};

// 2. 定义友元函数
void access_true_age(Girl& g)
{
    // 突破权限
    cout << &g.age << endl;
    cout << "真实年龄:" << g.age << endl;
    // 修改
    g.age = 18;
    cout << "修改后年龄:" << g.age << endl;
}

int main()
{
    Girl g(45);
    cout << g.get_age() << endl;
    // 通过友元函数访问Girl的年龄
    access_true_age(g);

    return 0;
}
需要注意的

● 由于属于成员函数因此友元函数没有this指针访问成员只能通过对象

● 友元函数中的声明”可以写在任何部分不受权限修饰符影响

● 理论上一个友元函数可以多个友元函数只需要在各个分别声明”

1.3 友元

一个B成为另一个A友元B可以访问A所有成员

需要注意的

● 友元关系单向的不具有交换性

如果BA友元类A不一定B友元类

● 友元关系不具有传递性

如果CB友元类BA友元类C不一定A友元类

● 友元关系不能被继承

#include <iostream>

using namespace std;

class A
{
private:
    string str = "A私有";

    // “声明”友元类
    friend class B;
};

class B
{
public:
    void func(A& a)
    {
//        cout << this->str << endl; 错误:this是B对象不是A对象
        cout << a.str << endl;
        a.str =  "我改了";
        cout << a.str << endl;
    }
};

int main()
{
    A a;
//    cout << a.str << endl; 错误
    B b;
    b.func(a);

    return 0;
}

1.4 友元成员函数

友元类任何成员函数都可以访问其他成员但是友元成员函数友元范围限制一个成员函数

例如,类B某个成员函数称为A友元成员函数这样B成员函数可以访问A所有成员

#include <iostream>

using namespace std;

// 3. 因为第二步中用到了类A,提前声明类A
class A;

// 2. 编写类B,并真正声明友元成员函数
class B
{
public:
    void func(A&);
};

class A
{
private:
    string str = "A私有";

    // 1. 确定友元的函数格式并“声明”
    friend void B::func(A&);
};

// 4. 类外定义友元成员函数
void B::func(A & a)
{
    //  cout << this->str << endl; 错误:this是B对象不是A对象
    cout << a.str << endl;
    a.str =  "我改了";
    cout << a.str << endl;
}


int main()
{
    A a;
    //    cout << a.str << endl; 错误
    B b;
    b.func(a);

    return 0;
}

二. 运算符重载

2.1 概念

如果运算符看做一个函数运算符也可以函数一样重载

C++中预定义的运算符的操作对象只能是基本数据类型。但实际上对于很多用户的自定义类型,也需要类似的运算操作、这时可以在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型,执行特定的操作。

可以被重载的运算符:

算术运算符:+、-、*、/、%、++、--

位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)

逻辑运算符:!、&&、||

比较运算符:<、>、>=、<=、==、!=

赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=

其他运算符:[]、()、->、,、new、delete、new[]、delete[]

不被重载的运算符:

成员运算符“.”、指针运算符“*”、三目运算符“? :”、sizeof、作用域“::”

2.2成员函数运算符重载

#include <iostream>
using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // + 运算符重载
    friend MyInt operator +(MyInt &i,MyInt &i2);
    friend MyInt operator ++(MyInt &i); // 前置自增
    friend MyInt operator ++(MyInt &i, int);    // 后置自增
};

// 友元函数 实现
MyInt operator +(MyInt &i,MyInt &i2)
{
    // int → MyInt 触发构造函数隐式调用
    return i.a + i2.a;
}

// 前置自增
MyInt operator ++(MyInt &i)
{
    return ++i.a;
}

// 后置自增
MyInt operator ++(MyInt &i, int)
{
    return i.a++;
}

int main()
{
    MyInt int1(2);
    MyInt int2(int1);   // 拷贝构造函数

    MyInt int3 = int1 + int2;

    cout << (int3++).get_int() << endl; // 4
    cout << int3.get_int() << endl; // 5

    return 0;
}

2.3 成员函数运算符重载

成员函数运算符重载相比于友元函数重载,最主要的区别在于,友元函数的第一个输出参数,在成员函数运算符重载中使用this指针代替。因此相同的运算符重载,成员函数运算符重载比友元函数运算符重载参数少一个。

#include <iostream>
using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }


    MyInt operator +(MyInt &i2);
    
    MyInt operator ++();
    MyInt operator ++(int);
};

// 成员函数 类外实现
MyInt MyInt::operator +(MyInt &i2)
{
    // int → MyInt 触发构造函数隐式调用
    return this->a + i2.a;
}

// 前置自增
MyInt MyInt::operator ++()
{
    return ++this->a;
}



// 后置自增
MyInt MyInt::operator ++(int)
{
    return this->a++;
}

int main()
{
    MyInt int1(2);
    MyInt int2(int1);   // 拷贝构造函数

    MyInt int3 = int1 + int2;
    cout << (++int3).get_int() << endl;
    cout << int3.get_int() << endl;

    return 0;
}

2.4 特殊运算符重载

2.4.1 赋值运算符重载

除了之前学习的无参构造函数、拷贝构造函数、析构函数以外,如果程序员不手写,编译器就会给一个类添加赋值运算符重载函数。

#include <iostream>
using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // 编译器会自动添加赋值运算符重载函数
    MyInt & operator =(MyInt &i)
    {
        cout << "赋值运算符被调用了" << endl;    // 编译器自动添加的赋值运算符重载函数不会打印这句话
        this->a = i.a;
        return *this;
    }
};


int main()
{
    MyInt int1(2);

    MyInt int4(3);
    cout << int4.get_int() << endl;
    int4 = int1;    // 赋值运算符重载
    cout << int4.get_int() << endl;
    return 0;
}

当类中出现指针成员变量时,默认的赋值运算符重载函数会出现类似于浅拷贝构造函数的问题,因此也需要手动编写解决“浅拷贝”的问题。

【面试题】一个类什么都不写,编译器添加了那些代码?

无参构造函数、拷贝构造函数、析构函数、赋值运算符重载函数

2.4.2 类型转换运算符重载

必须使用成员函数运算符重载,且格式比较特殊。

#include <iostream>
using namespace std;

class MyInt
{
private:
    int a;
    string str = "hello";
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // 编译器会自动添加赋值运算符重载函数
    MyInt & operator =(MyInt &i)
    {
        cout << "赋值运算符被调用了" << endl;    // 编译器自动添加的赋值运算符重载函数不会打印这句话
        this->a = i.a;
        return *this;
    }

    // 类型转换运算符重载
    operator int()
    {
        return a;
    }

    operator string()
    {
        return str;
    }
};


int main()
{
    MyInt int1(2);
    int a = int1;
    string str = int1;
    cout << a << endl;
    cout << str << endl;

    return 0;
}

2.5 注意事项

● 重载的运算符限制在C++语言中已有的运算符范围,不能创建新的运算符。

● 运算符重载的本质也是函数重载,但是不支持函数参数默认值设定。

● 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数和语法结构。

● 运算符重载必须基于或包含自定义类型,即不能改变基本数据类型的运算符规则。

● 重载功能应该与原有功能类似,避免没有目的的滥用运算符重载。

● 一般情况下,双目运算符建议使用友元函数进行重载,单目运算符建议使用成员函数进行重载。

三、std::string 字符串类(熟悉)

字符串对象是一个特殊类型的容器,专门设计用于操作字符串。

#include <iostream>
#include <string.h>
using namespace std;

int main()
{
    string s;   // 创建一个空字符串
    // 判断是否为空
    cout << s.empty() << endl;  // 1

    // 隐式调用构造函数
    string s1 = "hello";
    cout << s1 << endl; // hello

    // 显式调用构造函数,等同于上面写法
    string s2("world");
    cout << s2 << endl;

    // ==、!=、<、> 都是判断编码
    cout << (s1 == s2) << endl; // 0
    cout << (s1 != s2) << endl; // 1
    cout << (s1 > s2) << endl;  // 0
    cout << (s1 < s2) << endl;  // 1


    // 拷贝构造函数
    string s3(s2);  // string s3 = s2;
    cout << s3 << endl;

    // 参数1:char *源字符串
    // 参数2:保留的字符数
    string s4("ABCDEFG",3);
    cout << s4 << endl; // ABC

    // 参数1:std::string 源字符串
    // 参数2:不保留的字符数
    string s5(s2,3);
    cout << s5 << endl; // ld

    // 参数1:字符的数量
    // 参数2:字符的内容char
    string s6(5,'a');
    cout << s6 << endl; // aaaaa

    // 交换
    cout << "原s5=" << s5 << " " << "原s6=" << s6 << endl;    // 原s5=ld 原s6=aaaaa
    swap(s5,s6);
    cout << "s5=" << s5 << " " << "s6=" << s6 << endl;  // s5=aaaaa s6=ld

    // 字符串拼接、连接
    string s7 = s5 + s6;
    cout << s7 << endl; // aaaaald


    // 向后追加字符串
    s7.append("jiajia");
    cout << s7 << endl; // aaaaaldjiajia


    // 向后追加单字符
    s7.push_back('s');
    cout << s7 << endl; // aaaaaldjiajias

    // 插入
    // 参数1:插入的位置
    // 参数2:插入的内容
    s7.insert(1,"234");
    cout << s7 << endl; // a234aaaaldjiajias

    // 删除字符串
    // 参数1:起始位置
    // 参数2:删除的字符数量
    s7.erase(2,5);
    cout << s7 << endl; // a2aldjiajias

    // 替换
    // 参数1:起始位置
    // 参数2:被替换的字符数
    // 参数3:替换的新内容
    s7.replace(0,3,"***");
    cout << s7 << endl; // ***ldjiajias

    // 清空
    s7.clear();
    cout << s7.length() << endl;    // 0

    // 直接赋值初始化(隐式调用构造函数)
    string s8 = "hahaha";
    cout << s8 << endl;

    // 重新赋值
    s8 = "ABCDEFGH";
    cout << s8 << endl; // ABCDEFGH


    // 参数1:拷贝的目标
    // 参数2:拷贝的字符数量
    // 参数3:拷贝的起始位置
    char arr[20] = {0};
    s8.copy(arr,6,1);
    cout << arr << endl;    // BCDEFG

    // C++string 到 c string 用到了C语言的strcpy
    // c_str C++的字符串转换成C语言的字符数组
    // c_str返回值类型是一个const char*
    char c[20] = {0};
    strcpy(c,s8.c_str());
    cout << c <<endl;   // ABCDEFGH

    return 0;
}

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

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

相关文章

什么是期望最大化算法?

一、期望最大化算法 期望最大化&#xff08;EM&#xff09;算法是一种在统计学和机器学习中广泛使用的迭代方法&#xff0c;它特别适用于含有隐变量的概率模型参数估计问题。在统计学和机器学习中&#xff0c;有很多不同的模型&#xff0c;例如高斯混合模型&#xff08;GMM&…

NSSCTF [HNCTF 2022 Week1]超级签到

查看主函数 看到遍历 Str2&#xff0c;如果字符为 o&#xff0c;则替换为 0 int __fastcall main_0(int argc, const char **argv, const char **envp) {char *v3; // 指向 v7 的指针__int64 i; // 循环计数器size_t v5; // 存储 Str2 的长度char v7; // 存储输入字符int j; …

如何快速自定义一个Spring Boot Starter!!

目录 引言&#xff1a; 一. 我们先创建一个starter模块 二. 创建一个自动配置类 三. 测试启动 引言&#xff1a; 在我们项目中&#xff0c;可能经常用到别人的第三方依赖&#xff0c;又是引入依赖&#xff0c;又要自定义配置&#xff0c;非常繁琐&#xff0c;当我们另一个项…

mysql8.0安装后没有my.ini

今天安装mysql后想改一下配置文件看了一下安装路径 C:\Program Files\MySQL\MySQL Server 8.0 发现根本没有这个文件查看隐藏文件也没用查了之后才知道换地方了和原来的5.7不一样 新地址是C:\ProgramData\MySQL\MySQL Server 8.0 文件也是隐藏的记得改一下配置

【Redis 源码】7RDB持久化

1 功能说明 RDB (Redis Database Backup) 是 Redis 的一种持久化方式&#xff0c;它通过将某一时刻的内存快照&#xff08;snapshot&#xff09;以二进制格式保存到磁盘上。这种持久化方式提供了高性能和紧凑的数据存储&#xff0c;但相对于 AOF (Append Only File) 来说&…

充电桩安装-理想充电桩如何安装全流程-从准备到材料准备全流程

充电桩安装 Willya 2023年3月6日 新能源车出行成本低&#xff0c;那肯定是要在便利的条件下&#xff0c;得有自己的充电桩才行&#xff0c;实在安装不了自己的充电桩&#xff0c;那也要保证居住周边有充足的充电站&#xff0c;这样才能保证用车的便捷。 理想汽车充电桩安装一般…

智能化转型新篇章:EasyCVR引领大型连锁超市视频监控进入AI时代

随着科技的飞速发展&#xff0c;视频监控系统在各行各业中的应用日益广泛&#xff0c;大型连锁超市作为人员密集、商品繁多的公共场所&#xff0c;其安全监控显得尤为重要。为了提升超市的安全管理水平、减少损失、保障顾客和员工的安全&#xff0c;引入高效、全面的视频监控系…

胤娲科技:AI界的超级充电宝——忆阻器如何让LLM告别电量焦虑

当AI遇上“记忆橡皮擦”&#xff0c;电量不再是问题&#xff01; 嘿&#xff0c;朋友们&#xff0c;你们是否曾经因为手机电量不足而焦虑得像个无头苍蝇&#xff1f;想象一下&#xff0c;如果这种“电量焦虑”也蔓延到了AI界&#xff0c; 特别是那些聪明绝顶但“耗电如喝水”的…

逃离陷阱:如何巧妙避免机器学习中的过拟合与欠拟合

逃离陷阱&#xff1a;如何巧妙避免机器学习中的过拟合与欠拟合 前言过拟合&#xff1a;定义与识别定义表现原因示例&#xff1a;决策树模型的过拟合 欠拟合&#xff1a;定义与识别定义表现原因示例&#xff1a;线性回归模型的欠拟合 避免过拟合的策略减少模型复杂度使用正则化…

基于nodejs+vue的校园二手物品交易系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

SSM超市售卖管理系统-计算机毕业设计源码23976

目 录 摘要 Abstract 1 绪论 1.1研究的背景和意义 1.2研究内容 1.3论文结构与章节安排 2 开发技术介绍 2.1 SSM框架 2.2 MySQL数据库 3 超市售卖管理系统系统分析 3.1 可行性分析 3.2 系统流程分析 3.2.1 数据流程 3.3.2 业务流程 3.3 系统功能分析 3.3.1 功…

低代码可视化-UniApp二维码可视化-代码生成器

市面上提供了各种各样的二维码组件&#xff0c;做了一简单的uniapp二维码组件&#xff0c;二维码实现依赖davidshimjs/qrcodejs。 组件特点 跨浏览器支持&#xff1a;利用Canvas元素实现二维码的跨浏览器兼容性&#xff0c;兼容微信小程序、h5、app。 无依赖性&#xff1a;QR…

留学生如何适应海外生活以及应对文化差异

对于即将出国学习和生活的留学生来说&#xff0c;文化差异和生活方式的变化常常是一个紧迫的问题。那么&#xff0c;如何应对这些文化差异&#xff0c;以及如何适应新的学习环境和社交生活呢&#xff1f;本文将分享一些具体可行的建议和方法&#xff0c;助您顺利跨越这道难关&a…

数据结构:队列及其应用

队列&#xff08;Queue&#xff09;是一种特殊的线性表&#xff0c;它的主要特点是先进先出&#xff08;First In First Out&#xff0c;FIFO&#xff09;。队列只允许在一端&#xff08;队尾&#xff09;进行插入操作&#xff0c;而在另一端&#xff08;队头&#xff09;进行删…

Hadoop三大组件之YARN(一)

YARN架构与任务提交流程详解 1. YARN的组成架构 YARN&#xff08;Yet Another Resource Negotiator&#xff09;是Hadoop生态系统中的一个重要组成部分&#xff0c;主要用于资源管理和调度。YARN的架构主要由以下几个关键组件构成&#xff1a; 1.1 ResourceManager&#xff…

vue3结合 vue-router和keepalive实现路由跳转保持滚动位置不改变(超级简易清晰)

1.首先我们在路由跳转页面设置keepalive(Seeall是我想实现结果的页面) 2. 想实现结果的页面中如果不是全屏实现滚动而是有单独的标签实现滚动效果

Spring Boot技术栈:打造高效在线商城

2 相关技术 2.1 Springboot框架介绍 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Spring…

AI大模型对我国劳动力市场潜在影响研究报告(2024)|附19页PDF文件下载

前言 北京大学国家发展研究院与智联招聘日前联合发布《AI大模型对我国劳动力市场潜在影响研究》。该研究显示&#xff0c;2024年上半年&#xff0c;招聘职位数同比增速前五的人工智能职业&#xff0c;包括大语言模型方面的自然语言处理&#xff08;111%&#xff09;、深度学习…

STM32 RTC实时时钟学习总结

STM32 RTC实时时钟学习总结 写于2024/9/25下午 文章目录 STM32 RTC实时时钟学习总结1. 简介2. 流程框图介绍3. 相关寄存器介绍4. 代码解析 1. 简介 STM32F103 的实时时钟&#xff08;RTC&#xff09;是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器&#xff…

【C语言】动态内存管理:malloc、calloc、realloc、free

本篇介绍一下C语言中的malloc/calloc/realloc。 使用这些函数需要包含头文件<stdlib.h>。malloc/calloc/realloc申请的空间都是 堆区的。 1.malloc和free 1.1 malloc C语言提供了一个动态内存开辟的函数malloc&#xff0c;函数原型如下。 void* malloc(size_t size);…