C++ 面向对象 - 运算符重载与友元函数

运算符重载

所谓重载,就是赋予新的含义。函数重载可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载也是一个道理,同一个运算符可以有不同的功能。

运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。

重载运算符声明方式如普通成员函数一样,只不过他的名字包含关键字 operator,以及紧跟其后的一个 C++ 预定义的操作符。格式如下:

函数类型 operator 运算符名称 (形参表列)
{
    // 对运算符的重载处理
}

例如:

class Test{ /* 类声明 */ };

Test operator+(Test t1,Test t2);     // 重载 + 运算符,这个运算符要求两个操作数

并不是所有 C++ 中的运算符都可以支持重载,我们也不能创建一个新的运算符出来并且不能改变运算符操作对象的个数。有的运算符只能作为类成员函数被重载,而有的运算符则只能当作普通函数来使用:

  • 不能被重载的运算符有:..*::?:sizeof

  • 只能作为类成员函数重载的运算符有:()[]->=

运算符重载有两种方式:

  1. 使用外部函数进行运算符重载;

  2. 使用成员函数进行运算符重载。

运算符重载之外部函数

要调用运算符重载函数,有两种方法,一种是通过函数名调用,即operator+(t1,t2),另一种是在使用运算符的时候自动调用,这里介绍第二种。

就如同函数重载的最佳匹配规则,使用一个运算符时,会寻找名为operator<运算符>,且与当前操作数最佳匹配的那个重载版本进行调用。

例如:

class Test{ /* 类声明 */ };

class D : public Test {};     //创建一个 Test 类的子类

//外部函数
Test operator+(Test t1,Test t2){ /* 一些操作 */ }

int main()
{
    Test t1,t2;
    Test t3 = t1 + t2;     // 最佳匹配是 operator+(Test,Test)
    D d1,d2;
    D d3 = d1 + d2;     // 最佳匹配也是 operator+(Test,Test)
}
至于运算符重载函数内部怎么实现(定义),那就可以根据需求来了,例如:
class Test
{
public:
    int a;
};

Test operator+(Test &t1,Test &t2)
{
    // 重载加法运算符,实际对 Test 类中的 a 成员变量进行加法运算
    Test t;
    t.a = t1.a + t2.a;
    return t;
}

int main()
{
    Test t1,t2;
    t1.a = 10; t2.a = 20;
    cout << (t1 + t2).a <

输出结果为:30

注意:在运算符重载函数中也是要考虑类成员访问性的问题的。

运算符重载之成员函数

运算符重载的函数也可以写到某个类中成为一个成员函数,格式与写在外面没有区别,但在参数列表上有一些差异。

成为成员函数的运算符重载函数的参数需要少写一个最左边的参数,而少的这个参数就由当前的对象代替。

例如:

class Test
{
    public:
        int a;
        Test operator+(Test& t2);     // 少了左边的一个参数
};
Test Test::operator+(Test &t2)
{
    Test t;
    t.a = a + t2.a;     // 当前对象代替了原本的第一个参数
    return t;
}

int main()
{
    Test t1,t2;
    t1.a = 10; t2.a = 20;
    cout << (t1 + t2).a << endl;     // 调用的是 Test 类成员函数的那个重载版本
}
输出结果为:30

注意:作为成员函数的运算符重载函数也会受访问性影响。

友元函数

有时候我们希望某个函数能访问一个类的非公有成员,但又不想把它做成这个类的成员函数,这个时候就可以使用友元。

如果要将一个函数变成一个类的友元,只需要在类中函数前加一个 friend 关键字来声明函数即可,并且访问性不受限制。即表现形式为:

friend <返回类型> <函数名> (<参数列表>);

但这个友元函数他不属于该类的成员函数,他是定义在类外的普通函数,只是在类中声明该函数可以直接访问类中的 private 或者 protected 成员。

例如:

class Test{
private:
    int a;
protected:
    int b;
public:
    friend void fun(Test t);     // 友元函数
};

void fun(Test t)
{
    t.a = 100;
    t.b = 2000;
    cout << "a = " << t.a << ", b = " << t.b << endl;
}

int main()
{
    Test test;
    fun(test);
    return 0;
}
输出结果为:a = 100, b = 2000

友元类

C++ 中也允许声明一个类为某个类的友元类,方法和声明友元函数旗鼓相当。但是需要注意的是,作为友元的类的声明必须在友元类的声明之前。

例如:

class TestFriend
{
private:
    int a;
};     // TestFriend 类的声明在 Test 类之前

class Test
{
private:
    int a;
    friend class TestFriend;     // 声明友元类
};

有时候作为友元的类的声明不便放在友元声明之前,这个时候就可以使用前置声明。不过前置声明的类只是一个名字(或者说不完全类型),不能访问它内部的内容。

例如:

class TestFriend;     // 前置声明 TestFriend 类,只是一个名字

class Test
{
private:
    int a;
    friend class TestFriend;     // 声明友元类

    friend void TestFriend::Func();     // 尝试将 TestFriend 类的成员函数 Func 作为友元函数,但由于 TestFriend 类目前只有前置声明,所以会出错。
};

class TestFriend     // TestFriend 类的声明在 Test 类之后
{
private:
    int a;
public:
    void Func();
};

最后,友元声明还有如下限制:

  • 友元关系不能被继承。

  • 友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元。

  • 友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元。

转换构造函数

一个构造函数接收一个不同于其类类型的形参,可以视为将其形参转换成类的一个对象,像这样的构造函数称为转换构造函数。因此转换构造函数的作用就是将一个其他类型的数据转换成一个类的对象。

除了创建类对象之外,转换构造函数还为编译器提供了执行隐式类型转换的方法。只要在需要类的类型值的地方,给定构造函数的形参类型的值,就将由编译器执行这种类型的转换。

转换构造函数是构造函数的一个特例,当一个构造函数的参数只有一个,而且是一个其他类型的 const 引用时,它就是一个转换构造函数。

例如:

class T1{};

class T2
{
public:
    T2(const T1 &t);     // 从 T1 转换到 T2 的转换构造函数
};

有了转换构造函数,就可以实现不同类型之间的类型转换了,比如:

/* 类定义同上 */
int main()
{
    T1 t1;
    T2 t2= (T2)t1;     // 用类型转换语法,从 T1 转换到 T2
    T2 t3(t1);     // 或者直接调用转换构造函数
}

注意:转换构造函数只能有一个参数。如果有多个参数,就不是转换构造函数。

例题

第1关:复数运算

任务描述

本关任务:设计一个复数类并实现复数的三种运算。

编程要求

在右侧编辑器中的Begin-End之间补充代码,设计一个复数类( Complex ),该类有两个成员变量和两个函数(成员变量访问性为私有,函数为公有),并重载+-*运算符,实现复数的加、减、乘运算,具体要求如下:

  • 成员变量:float real,代表实部。

  • 成员变量:float image,代表虚部。

  • 构造函数:Complex(float r,float i),用两个参数设置 real 和 image 成员变量的值。

  • 输出复数函数:void Print(),输出格式为:实部 +/- 虚部i,比如1+1i,0−5i。

测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:

测试输入:

1 1 2 2(数据分为两组,前两个和后两个数分别表示一个复数 c1 和 c2,即c1=1+1ic2=2+2i

预期输出:

  1. c1 = 1+1i
  2. c2 = 2+2i
  3. c1 + c2 = 3+3i
  4. c1 - c2 = -1-1i
  5. c1 * c2 = 0+4i

测试输入:0 0 -1 0.5

预期输出:

  1. c1 = 0+0i
  2. c2 = -1+0.5i
  3. c1 + c2 = -1+0.5i
  4. c1 - c2 = 1-0.5i
  5. c1 * c2 = -0+0i
代码解析
#include 
using namespace std;

/********* Begin *********/
class Complex
{
//复数类的声明
private:
    float real;
    float image;
public:
    Complex(float r, float i){
        real = r;
        image = i;
    }
    void Print(){
        if(image >= 0)
            cout << real << "+" << image << "i" << endl;
        else if(image  <  0)
            cout << real << image << "i" << endl;
        else
            cout << real << endl;
    }
    Complex operator+(Complex &c){
            return Complex(real + c.real, image + c.image);
    }
    Complex operator-(Complex &c){
            return Complex(real - c.real, image - c.image);
    }    
    Complex operator*(Complex &c){
            return Complex(real*c.real - image*c.image, real*c.image + image*c.real);
    }
    
    
};
//复数类的定义


/********* End *********/

第2关:学生信息转换

任务描述

本关任务:设计学生和教师两个类,并实现它们之间的转换。

相关知识

为了完成本关任务,你需要掌握友元函数、友元类以及转换构造函数。

编程要求

在右侧编辑器中的Begin-End之间补充代码,设计学生和教师两个类,类中成员变量都为私有,成员函数都为公有,并实现它们之间的转换,具体要求如下:

  1. 学生类( Student )

    • 编号:int number

    • 姓名:string name

    • 性别:string sex

    • 带参构造函数:Student(int num,string nam,string se),用三个参数对应初始化内部的三个成员变量。

    • 输出函数:void Print()函数,输出学生信息,格式为:学生:name,编号:number,性别:sex

  1. 教师类( Teacher )

    • 与 Student 类有三个同样的成员变量

    • 输出函数:void Print(),输出教师信息,格式为:教师:name,编号:number,性别:sex的格式打印三个成员变量。

  1. 转换构造函数,用于从 Student 类转换到 Teacher 类,它将 Student 对象的成员变量对应复制 Teacher 对象的成员变量中。
测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:

测试输入:1 王大明 男

预期输出:

  1. 学生:王大明,编号:1,性别:男
  2. 教师:王大明,编号:1,性别:男

测试输入:2 雷珺 女

预期输出:

  1. 学生:雷珺,编号:2,性别:女
  2. 教师:雷珺,编号:2,性别:女
代码解析
#include 
#include 

using namespace std;

/********* Begin *********/
class Teacher;

class Student {
    private: 
        int number;
        string name;
        string sex;
    public: 
        Student(int num, string nam, string se) {
            number = num;
            name = nam;
            sex = se;
    }
    void Print() {
        cout << "学生:" << name << ",编号:" << number << ",性别:" << sex << endl;
    }
    friend class Teacher;
};

class Teacher {
    private: 
        int number;
        string name;
        string sex;
    public: 
        Teacher(const Student &s) {
            number = s.number;
            name = s.name;
            sex = s.sex;
        }
        void Print() {
            cout << "教师:" << name << ",编号:" << number << ",性别:" << sex << endl;
        }
};
/********* End *********/

第3关:矩阵运算

任务描述

本关任务:设计一个矩阵类,并实现简单的矩阵运算。

编程要求

在右侧编辑器中的Begin-End之间补充代码,设计一个矩阵类( Matrix ),并实现矩阵的简单运算,具体要求如下:

  1. 成员变量:这一部分学员可以自由发挥,但要求都是私有成员。

  2. 成员函数:

    • 构造函数:Matrix(int r,int c),参数 r 和 c 分别代表矩阵的行和列。

    • 全部设值函数:void Fill(int value),函数将矩阵内所有的元素都设置为参数 value 的值。

    • 指定位置设值函数:void Set(int r,int c,int value),函数将矩阵第 r 行 c 列的元素设置为 value 的值。

    • 获取元素函数:int Get(int r,int c)函数,函数返回矩阵第 r 行 c 列的元素。

    • 打印函数:void Print(),函数按照矩阵的形状打印出矩阵内容,每一个值后跟着一个空格。比如一个2x4元素全为1的矩阵,打印结果为(更明显表示格式,空格均用下划线_代替):

      1. 1_1_1_1_
      2. 1_1_1_1_
  3. 普通函数:

    • Matrix operator+(Matrix &m1,Matrix &m2)函数,重载Matrix类的加法运算符,实现矩阵的加法运算。

    • Matrix operator-(Matrix &m1,Matrix &m2)函数,重载Matrix类的减法运算符,实现矩阵的减法运算。

    • Matrix operator*(Matrix &m1,Matrix &m2)函数,重载Matrix类的乘法运算符,实现矩阵的乘法运算。

测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:

测试输入:1 1

预期输出:

  1. m1 + m2 :
  2. 3
  3. m1 - m2 :
  4. -1
  5. m1 * m3 :
  6. 1

测试输入:2 2

预期输出:

  1. m1 + m2 :
  2. 3 3
  3. 3 3
  4. m1 - m2 :
  5. -1 -1
  6. -1 -1
  7. m1 * m3 :
  8. 1 2
  9. 1 2
代码解析
#include  
#include  

using namespace std;

/*********  Begin  *********/
class Matrix {
    public: 
        int row, col;
        int ** data;
    //构造函数
    Matrix(int r, int c): row(r), col(c) {
        data = new int * [row];
        for (int i = 0; i < row; i++) {
            data[i] = new int[col];
        }
    }

    //全部设值函数
    void Fill(int value) {
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                data[i][j] = value;
            }
        }
    }

    //指定位置设值函数
    void Set(int r, int c, int value) {
        data[r][c] = value;
    }

    //获取元素函数
    int Get(int r, int c) {
        return data[r][c];
    }

    //打印函数
    void Print() {
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                cout << data[i][j] << " ";
            }
            cout << endl;
        }
    }

    //析构函数
    ~Matrix() {
        for (int i = 0; i < row; i++) {
            delete[] data[i];
        }
        delete[] data;
    }
};
//矩阵类的定义

Matrix operator + (Matrix & m1, Matrix & m2) {
    //实现矩阵加法
    Matrix m3(m1.row, m1.col);
        for (int i = 0; i < m1.row; i++) {
            for (int j = 0; j < m1.col; j++) {
                m3.Set(i, j, m1.Get(i, j) + m2.Get(i, j));
            }
        }
        return m3;
    }

Matrix operator - (Matrix & m1, Matrix & m2) {
    //实现矩阵减法
    Matrix m3(m1.row, m1.col);
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m1.col; j++) {
            m3.Set(i, j, m1.Get(i, j) - m2.Get(i, j));
        }
    }
        return m3;
}

Matrix operator * (Matrix & m1, Matrix & m2) {
    //实现矩阵乘法
    Matrix m3(m1.row, m2.col);
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m2.col; j++) {
            int sum = 0;
            for (int k = 0; k < m1.col; k++) {
                sum += m1.Get(i, k) * m2.Get(k, j);
            }
            m3.Set(i, j, sum);
        }
    }
    return m3;
}

/*********  End  *********/

你可能感兴趣的:(C++,面向对象,c++,开发语言)