当前位置: 首页 > article >正文

C++:面向对象之多态(运算符重载)

面向对象之多态

1.1 什么是多态

​ 多态性(polymorphism)是⾯向对象程序设计的⼀个重要特征。利⽤多态性可以设计和实现⼀个易于扩展的系统 。

​ C++程序设计中,多态性是指具有不同功能的函数可以⽤同⼀个函数名,这样就可以⽤⼀个函数名调⽤不同内容的函数。

​ 在⾯向对象⽅法中⼀般是这样表述多态性的: 向不同的对象发送同⼀个消息,不同的对象在接收时会产⽣不同的⾏为(即⽅法)。也就是说,每个对象可以⽤⾃⼰的⽅式去响应共同的消息 。

系统实现的⻆度看,多态性分为两类: 静态多态性和动态多态性。

​ 静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。

​ 动态多态性是在程序运⾏过程中才动态地确定操作所针对的对象。它⼜称运⾏时的多态性。动态多态性是通过虚函数(virtual function)实现的。

2.2 运算符重载

1)运算符重载简介

​ 引⼊运算符重载是为了实现类的静态多态性。运算符重载是对已有的运算符赋予多重含义,使⽤⼀个运算符作⽤与不同类型的数据时导致不同的⾏为。

​ 运算符重载的⽅法是定义⼀个重载运算符的函数,在需要执⾏被重载的运算符时,系统就⾃动调⽤该函数,以实现相应的运算。

运算符重载有两种⽅式:

  • 类内重载(运算符重载函数作为类的成员函数)
  • 类外重载(运算符重载函数作为类的友元函数)

运算符重载运算符的函数⼀般格式如下 :

函数类型 operator 运算符名称(形参列表)

{

函数体

}

运算符重载的规则:

  • C++不允许⽤户⾃⼰定义新的运算符,只能对已有的C++运算符进⾏重载。

  • C++中绝⼤部分的运算符允许重载。不能重载的运算符只有5个 :

.((成员访问运算符))

.*(成员指针访问运算符)

::(域运算符)

sizeof(长度运算符)

?:(条件运算符)

前两个运算符不能重载是为了保证访问成员的功能不能被改变:

  • 重载不能改变运算符运算对象(即操作数)的个数
  • 重载不能改变运算符的优先级别和结合性
  • 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与第3条冲突。
  • 重载的运算符必须和⽤户定义的⾃定义类型的对象⼀起使⽤,其参数⾄少应有⼀个是类对象(或类对象的引⽤)。也就是说,参数不能全部是C++的标准类型,以防⽌⽤户修改⽤于标准类型数据的运算符的性质 。
  • ⽤于类对象的运算符⼀般必须重载,但有两个例外,运算符“=”和“&”不必⽤户重载。

​ ① 赋值运算符(=)可以⽤于每⼀个类对象,可以利⽤它在同类对象之间相互赋值。

​ ② 地址运算符&也不必重载,它能返回类对象在内存中的起始地址。

  • 重载运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既⾮类的成员函数也不是友元函数的普通函 (最后⼀种情况较为少⽤)。

1)双⽬运算符重载

类内重载,双⽬运算符重载的第⼀个操作数传递给隐形参数this,函数只显式说明⼀个参数,该形参是运算符的右操作数。

#include <iostream>
#include <string>

using namespace std;

class stu
{
public:
    string name;
    int math;
    int chinese;
    stu(string sname, int m, int c);
    // 重载两个stu对象相加
    int operator+(const stu &pp) // 运算符类内重载
    {
        int sum = 0;
        sum = this->math + pp.math + this->chinese + pp.chinese;
        return sum;
    }
    // 重载stu和⼀个int相加
    stu operator+(const int num)
    {
        int sum = 0;
        this->math += num;
        this->chinese += num;
        return *this;
    }
};
stu::stu(string sname, int m, int c)
{
    this->name = sname;
    this->math = m;
    this->chinese = c;
}
int main()
{
    stu s1("yaoyao", 50, 60);
    stu s2("leilei", 30, 70);
    cout << s1 + s2 << endl; // s1 + s2 相当于 s1 对象调⽤+⽅法并且传⼊参数时 s2 对象

    stu temp = s1 + 20;
    cout << temp.math << temp.chinese << endl;
    return 0;
}
/*
=====结果
210 
70 80
*/

友元函数类外重载:

友元函数没有this指针,需要指定两个操作数。

// 类内声明友元函数
friend int operator+(const stu &num1, const stu &num2); // 运算符类内重载
// 类外实现友元函数
int operator+(const stu &num1, const stu &num2) // 运算符类外重载
{
    int sum = 0;
    sum = num1.math + num2.math + num1.chinese + num2.chinese;
    return sum;
}

2)重载单目运算符

​ 单⽬运算符只有⼀个操作数,如!a, -b, &c, *p,还有最常⽤的++i和–i等。

​ 重载单⽬运算符的⽅法与重载双⽬运算符的⽅法是类似的 。

​ 但由于单⽬运算符只有⼀个操作数,因此运算符重载函数只有⼀个参数,如果运算符重载函数作为成员函数,则还可省略此参数。

重载前置++运算符例⼦:

#include <iostream>
using namespace std;
class Time
{
public:
    Time()
    {
        minute = 0;
        sec = 0;
    }                                                        // 默认构造函数
    Time(int m, int s) : minute(m), sec(s) {}                // 构造函数重载
    Time operator++();                                       // 声明运算符重载函数
    void display() { cout << minute << ":" << sec << endl; } // 定义输出时间函数
private:
    int minute;
    int sec;
};
Time Time::operator++() // 定义运算符重载函数
{
    if (++sec >= 60)
    {
        sec -= 60; // 满60秒进1分钟
        ++minute;
    }
    return *this; // 返回当前对象值
}
int main()
{
    Time time1(34, 0);
    for (int i = 0; i < 61; i++)
    {
        ++time1; // 调⽤成员函数的运算符格式time1.operator++();
        time1.display();
    }
    return 0;
}
/*
运⾏情况如下:
34 : 1 
34 : 2
┆ 
34 : 59 
35 : 0 
35 : 1(共输出61⾏)
前置单⽬运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。
也可以time1.operator++();
后置单⽬运算符重载为类的成员函数时,函数要带有⼀个整型形参。
*/

由于++与–运算符有两种使⽤⽅式即前置运算符和后置运算符,针对这⼀特点,C++约定:在⾃增或⾃减运算的重载函数中,增加⼀个int型形参,这就是后置的⾃增或⾃减运算符函数。

重载后置++运算符例⼦:

#include <iostream>
using namespace std;
class Time
{
public:
    Time()
    {
        minute = 0;
        sec = 0;
    }                                                        // 默认构造函数
    Time(int m, int s) : minute(m), sec(s) {}                // 构造函数重载
    Time operator++();                                       // 声明前置⾃增运算符++重载函数
    Time operator++(int);                                    // 声明后置⾃增运算符++重载函数
    void display() { cout << minute << ":" << sec << endl; } // 定义输出时间函数
private:
    int minute;
    int sec;
};
Time Time::operator++() // 定义运算符重载函数
{
    if (++sec >= 60)
    {
        sec -= 60; // 满60秒进1分钟
        ++minute;
    }
    return *this; // 返回当前对象值
}
// 多传⼊⼀个参数,让c++编译器知道,这是两个不同的函数,避免出现重定义的情况
Time Time::operator++(int) // 定义运算符重载函数
{
    if (sec++ >= 59)
    {
        sec -= 60; // 满60秒进1分钟
        ++minute;
    }
    return *this; // 返回当前对象值,⽬的是可以⽤Time类型变量接收
}
int main()
{
    Time time1(34, 0);
    for (int i = 0; i < 61; i++)
    {
        //++time1; //调⽤成员函数的运算符格式time1.operator++();
        time1++;
        time1.display();
    }
    return 0;
}

3)重载流插入与流提取运算符

在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进⾏了重载,使其能够⽤于不同数据的输⼊输出,但是输⼊输出的对象只能是 C++ 内置的数据类型(例如 bool、int、double 等)和标准库所包含的类类型(例如 string、complex、ofstream、ifstream 等)。

如果我们⾃⼰定义了⼀种新的数据类型,需要⽤输⼊输出运算符去处理,那么就必须对它们进⾏重载。

对“<<”和“>>”重载函数形式如下:

istream & operator>>(istream &, ⾃定义类&);

ostream & operator<<(ostream &, ⾃定义类&);

重载>>函数说明:

参数1:必须是istream&类型(即为istream对象的引⽤)

参数2:要进⾏输⼊操作的类

函数类型也必须为istream&类型。

注意:

只能将重载“>>”和“<<”的函数作为友元函数,⽽不能静它们定义成员函数。

重载插⼊运算符与提取运算符例⼦:

#include <iostream>
using namespace std;
class Time
{
public:
    Time()
    {
        minute = 0;
        sec = 0;
    }                                               // 默认构造函数
    Time(int m, int s) : minute(m), sec(s) {}       // 构造函数重载
    friend ostream &operator<<(ostream &, Time &t); // 重载插⼊运算符号
    friend istream &operator>>(istream &, Time &t); // 重载提取运算符>>

    void display() { cout << minute << ":" << sec << endl; } // 定义输出时间函数
private:
    int minute;
    int sec;
};
ostream &operator<<(ostream &out, Time &t) // 定义重载<<输出函数
{
    out << t.minute << ":" << t.sec << endl;
    return out;
}
istream &operator>>(istream &in, Time &t)
{
    in >> t.minute >> t.sec;
    return in;
}
int main()
{
    Time time1(34, 0);
    cin >> time1;
    // time1.display();
    cout << time1; // 编译系统会将其解释为operator<<(cout,time1)
}

4)重载()

重载后的使⽤⽅式⾮常像函数的调⽤,因此称为仿函数。

class display
{
public:
    // 运算符()⼩括号的重载,重载后的使⽤⽅式⾮常像函数的调⽤,因此称为仿函数。
    void operator()() const
    {
        cout << "sdfsf" << endl;
    }
};
// 调⽤
display()();
person &operator()(string str, person &p)
{
    // body
}
// 调⽤
person()("牛牛牛",p); // 1.调⽤类的()重载函数,建⽴⼀个匿名对象来调⽤

5)不同类型间转换

​ 我们都知道标准类型数据间可以进⾏转换,C++也提供了显式的类型转换,其形式为:类型名(数据);对于标准类型转换,编译系统知道如何转换,但是对于⽤户⾃⼰声明的类型,编译系统不知道如何转换,这个时候就需要定义专⻔的函数来处理。

1.⽤转换构造函数进⾏不同数据类型的转换

如果需要将系统预定义的数据类型转换为类类型的会⽤到转换构造函数

// 转换构造函数的作⽤就是将以个其他类型转换成⼀个类对象。
#include <iostream>
#include <algorithm>
using namespace std;
class Student
{
public:
    float score;
    int age;

public:
    Student(void)
    {
        age = 18;
        score = 0.2f;
    }
    Student(float s)
    {
        age = 0;
        score = s; // 转换构造函数只有⼀个参数
        cout << "ljs" << endl;
    }
    friend float operator+(Student &a, float p);
};
float operator+(Student &a, float p)
{
    return a.score + p;
}
int main()
{
    Student stu1, stu2;
    stu2 = stu1 + Student(3.2f); // 3.2f; //不同类型相加会发⽣隐式转换

    cout << stu2.score << endl;
    return 0;
}
//
ljs 3.4 class Student
{
public:
    float score;
    int age;

public:
    Student(void)
    {
        age = 18;
        score = 0.2f;
    }
    Student(float s)
    {
        age = 0;
        score = s;
        cout << "ljs" << endl;
    }
    friend float operator+(Student &a, Student b); // 重载+,返回float类型
};
float operator+(Student &a, Student b)
{
    return a.score + b.score;
}
int main()
{
    Student stu1, stu2;
    float pp = stu1 + Student(5.3f); // 3.2f;
    cout << pp << endl;
    return 0;
}

转型构造函数可以将其它类型的参数转换为类类型,如果我们要进⾏相反的转换过程,将类类型转换为其它数据类型,则需重载转型操作符。

转型操作符重载函数的声明语法如下:

operator 类型名 ();

转型操作符重载函数有⼏点需要注意的:

  • 函数没有返回类型;

  • 虽然没有返回类型但是函数体中必须有return语句,其返回类型是由类型名来指定的;

  • 转型操作符重载函数只能以类的成员函数的形式进⾏重载,⽽不能以友元函数或顶层函数的形式进⾏重载;

  • 转换函数不能有参数;

  • C++中的operator主要有两个作⽤,⼀是操作符的重载,⼀是操作符的转换。

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

class Student
{
private:
    float score;
    int age;

public:
    Student(void)
    {
        age = 18;
        score = 0;
    }
    Student(int a, float s)
    {
        age = a;
        score = s;
    }
    operator float() { return score; } // 转换函数⼀般不应该改变被转换的对象,因此转换操作符通常应定义为 const 成员;
};
int main()
{
    Student stu1(18, 86), stu2(18, 97.5);
    float f;
    f = 6.75 + stu2; // 当需要的时候,编译器会⾃动调⽤这些函数
    cout << f << endl;
    return 0;
}
/*
运⾏结果:
 104.25
 Press any key to continue
 */

只要存在转换,编译器将在可以使⽤内置转换的地⽅⾃动调⽤它,⽐如隐式转换或强制转换(类名(指定类型的数据))。

如何关闭转换构造函数特性?

C++中explicit关键字

在C++中,我们有时可以将构造函数⽤作⾃动类型转换函数。但这种⾃动特性并⾮总是合乎要求的,有时会导致意外的类型转换,因此,C++新增了关键字。

explicit,⽤于关闭这种⾃动特性。即被explicit关键字修饰类的构造函数,不能进⾏⾃动地隐式类型转换,只能显式地进⾏类型转换。

#include <iostream>
#include <string>

using namespace std;

class Demo
{
public:
    Demo()
    {
        cout << "⽆参构造函数" << endl;
    }
    // explicit Demo(double a) //⽤explicit修饰构造函数就会关闭⾃动隐式转换
    Demo(double a) // 转换构造函数就是将其他类型转换成类类型
    {
        cout << "转换构造函数" << a << endl;
        a = a + 5.0;
    }
};
int main()
{
    Demo p1 = 1.2; // 赋值时会发⽣隐式转换
    // Demo p1=Demo(1.2); //如果⽤explicit修改构造函数,只能通过强制转换才可以调⽤。
    return 0;
}
/*
输出结果:
转换构造函数1.2
⼩结:写代码时要尽量的避免隐式类型转换;可以⽤关键字explicit 关闭隐式转换
*/

http://www.kler.cn/a/579857.html

相关文章:

  • AF3 squeeze_features函数解读
  • Vue3 Pinia 符合直觉的Vue.js状态管理库
  • 超越经典:量子通信技术的发展与未来
  • MySQL8.0窗口函数
  • HTML 表格详解(简单易懂较详细)
  • 云服务运维智能时代:阿里云操作系统控制台
  • 利用paddleocr解决图片旋转问题
  • 死锁的产生以及如何避免
  • PAT乙级(1091 N-自守数)C语言解析
  • 日期类、Date、Calendar、IO 流、File
  • Windows简易操作(二)
  • Science Advances 多功能粘性皮肤增强了机器人与环境的交互
  • JavaScript网页设计案例:打造动态与交互性并存的用户体验
  • 【3DMAX插件】3DMAX建筑大师插件MasterBuilder使用方法
  • Rabbitmq--延迟消息
  • 深入理解C语言预处理器:从原理到实战
  • 游戏引擎学习第148天
  • 使用Python和p5.js创建的迷你游戏示例,该游戏包含多个屏幕和动画,满足在画布上显示图像、使用键盘命令移动图像
  • AXI接口总结
  • DeepSeek-进阶版部署(Linux+GPU)