首页 > 编程语言 >C++友元和运算符重载

C++友元和运算符重载

时间:2024-09-28 19:50:02浏览次数:3  
标签:友元 函数 int C++ 运算符 MyInt 重载

目录

一. 友元 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的所有成员。

需要注意的是:

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

如果类B是类A的友元类,类A不一定是类B的友元类。

● 友元关系不具有传递性。

如果类C是类B的友元类,类B是类A的友元类,类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;
}

标签:友元,函数,int,C++,运算符,MyInt,重载
From: https://blog.csdn.net/qq_64136247/article/details/142617049

相关文章

  • Java编程基础(基本语法==>运算符)
    文章目录一、基本语法①注释②标识符③关键字④常量二、变量①变量的定义②基本数据类型③基本数据类型的级别与数据转换三、运算符①算数运算符②赋值运算符(=)③关系运算符④逻辑运算符⑤三目运算符⑥位运算符⑦运算符优先级总结提示:以下是本篇文章正文内容,下面......
  • C++中的string类
    前言C语言中字符串是以‘\0’结尾的字符的集合,为了方便操作,C标准库中提供了一些str系列的库函数,接下来我们学习string类。1.标准库中的string类1.1 string类在使用string类的时候,必须包含#include头文件以及usingnamespacestd;1.2auto和范围forauto关键词auto可以......
  • llama-factory挂载pm2出现问题:node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9'
    使用ssh连接服务器上运行llama-factory进行微调,但是一旦关闭ssh,程序也会随之关闭,而使用nohup命令会出现nohup:ignoringinput尝试采用pm2:(base)[hongjiayin@localhostLLaMA-Factory]$pm2startstart.shnode:/lib64/libstdc++.so.6:version`CXXABI_1.3.9'notfound......
  • 准备蓝桥杯和ACM:C++标准库头文件及其常用功能简介
    概述        在C++编程中,标准库为开发者提供了丰富的工具和功能,使得代码更简洁、易于维护。本文将深入探讨一些常用的C++标准库头文件,如<iostream>、<algorithm>、<string>等,以及它们所提供的基本功能与常见用法。通过对这些头文件的理解和应用,开发者能够更加高效地......
  • Day4 C++(运算符重载,模板与容器)(友元函数,运算符重载,赋值运算符,string字符串类,模板)
    1.友元friend1.1概念(掌握)定义:类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全......
  • 【C++】内存管理:内存分布、new/delete
        本篇主要介绍一下C++的内存管理相关知识。C++的内存管理和C语言保持一致。 1.C/C++内存分布一个程序的数据存储是需要分区的。常见的内存区域划分如下。我们学C++主要了解栈,堆,数据段,代码段。我们先看下面代码和相关问题。intglobalvar=1;staticintst......
  • C++ Practical-2 day2 运算符重载之时钟类++运算符
    系列文章目录点击直达——文章总目录文章目录系列文章目录C++Practical-2day2运算符重载之时钟类++运算符Overview1.时间类重载后缀`++`运算符来递增时间1.1.解释1.2.注意事项2.如何确保时间递增操作在多线程环境中是线程安全的?关于作者C++Practical-2day......
  • 10.C++程序中的循环语句
    C++中提供了三种循环语句(for循环,while循环以及do-while循环)来使程序员可以更方便地对数据进行迭代操作。if语句for语句的格式为:for(初始化语句;循环条件;迭代语句){代码块}for循环首先会执行初始化语句,主要是用于初始化循环变量和其它变量,然后判断条件是否为真,如果为真,则执......
  • 【C++】set与map
    一、什么是set、mapset和map是专门用来搜索的数据结构,是一种适合查找的容器,set和map的底层是二叉搜索树。*setset的声明:set<T>set中的相同元素只能存在一个。multiset(与set用法相同)可以存放多个相同元素。*mapmap的声明:map<T1,T2>map存储的匀速是pairmap中的与T1......
  • 【C++篇】迈入新世界的大门——初识C++(下篇)
    文章目录   前言   引用        引用的概念和定义        引用的特性        引用的使用        const引用        指针和引用的关系  inline         ......