c++(3)

C++ (3)

1. 运算符重载

1.1 运算符重载基本概念

运算符重载即对运算的功能重新定义,从而使得运算符支持不同的数据类型。

运算符重载(operator overloading)只是一种"语法上的方便",它是另一种函数调用的方式

运算符重载的函数名字由关键字operator+运算符组成。它像任何其他函数一样,当编译器遇到适当的模式时,就会调用这个函数

定义运算符重载函数时,依据运算符类别(单目、双目),定义函数的参数

运算符重载函数是全局函数时,单目运算符需要一个参数,双目需要两个

运算符重载函数是类成员函数时,单目运行符不需要参数(但++、--在后需要int占位参数),双目需要一个参数

1.2 运算符重载碰上友元函数

友元函数是一个全局函数时,和我们之前写的全局函数类似,只是友元函数可以访问某个类私有数据

如:重载c++的<<输出流和>>输入流

标准输出流(控制台):ostream,cout是ostream类的对象

标准输入流(键盘):istream,cin是istream类的对象

class A
{
    friend ostream &operator<<(ostream &cout,A &a);
    friend istream &operator>>(istream &cin,A &a);
private:
    int x,y;
};
//<<输出运算符重载
ostream &operator<<(ostream &cout,A &a)
{
    //x,y是A类的私有成员
    cout<<a.x<<","<<a.y;
    return cout;
}
//>>输入运算符重载
istream &operator>>(istream &cin,A &a)
{
    cout<<"x:";
    cin>>a.x;
    cout<<"y:";
    cin>>a.y;
    return cin;
}

1.3 可重载的运算符

image-20231121102822976

1.3.1 +

可以实现两个类的对象相加

1.3.2 ++/--

可以实现一个类对象的自加或自减

++或--在前的重载函数, 成员函数不需要参数:类名 &operator++/--(){}

在后,需要int占位参数:类名 &operator++/--(int){}

1.3.3 *、->重载

  • 如果对一个对象取指针时,要实现->重载函数,函数内部返回引用的其它类对象的指针
  • 如果对一个对象取值时,要实现*重载函数,函数内部返回引用其它类对象指针的值。
  • *,->运算符的重载函数不能有参数

如:

class A
{
private:
    int x;
public:
    explicit A(int x) { this->x = x; }
    void printA()
    {
        cout << "x = " << x << endl;
    }
};
class B
{
private:
    A *mA;
public:
    B(int x)
    {
        mA = new A(x);
    }
    A *operator->()
    {
        return mA;
    }
    A &operator*()
    {
        return *mA;
    }
};

1.3.4 <、>、>=、<=、==

如:

class Num
{
private:
    int n;
public:
    explicit Num(int n)
    {
        this->n = n;
    }
    bool operator>(int other) // 成员函数的运算符重载
    {
        return this->n > other;
    }
    bool operator>(Num &other)
    {
        return this->n > other.n;
    }
    bool operator<(Num &other)
    {
        return this->n < other.n;
    }
    bool operator<(int other)
    {
        return this->n < other;
    }
};

1.3.5 =

可能会调用类本身的拷贝构造函数。如果左值是没有创建的对象时,会调用拷贝构造函数,如果左值是已创建的类对象,会执行=重载函数,实现数据的拷贝

class A
{
private:
    int x;
public:
    A(int x)
    {
        cout << " A(int x)" << x << endl;
        this->x = x;
    }
    A(const A &obj)
    {
        cout << " A(const A &obj)" << endl;
        this->x = obj.x;
    }
    A &operator=(A &other)
    {
        cout << "A &operator=(A &other)" << endl;
        this->x = other.x;
        return *this;
    }
    A &operator=(int n)
    {
        cout << "A &operator=(int n)" << endl;
        this->x = n;
        return *this;
    }
    void show()
    {
        cout << "x=" << x << endl;
    }
};

1.3.6 ()

当类对象作为函数调用时,会执行operator()(参数列表)函数

class A{
private:
    int x,y;
public:
    A(int x, int y){
        this->x = x;
        this->y = y;
    }
    void show(){
        cout << x << "," << y << endl;
    }
    void operator()(int a, int b){
        this->x += a;
        this->y += b;
    }
};

1.3.7 &&和||

逻辑与和逻辑或存在逻辑短路的问题,但是在重载时,有可能达不到想要的效果,因此不推荐重载逻辑与和逻辑或

1.3 总结

=,[],()和->操作符只能通过成员函数进行重载
<<和>>只能通过全局函数配合友元函数进行重载
不要重载&&和||操作符,因为无法实现短路规则

image-20231121112305319

2. 继承和派生

2.1 继承的概述

继承的优点:

1)减少重复的代码,减轻程序整体的体量
2)继承的好处,可以将共性的内容封装成一个基类(父类),遇到专项业务时,可以扩展基类变为一个新类,在新类中重点扩展功能

c++中继承的最重要的特征是代码重用

子类与父类的关系:

类是由具有相同特征(属性)和行为(方法)的多个客观事物(对象)抽象出来的
子类是从父类中派生出来的类,子类继承了父类,父类由多个类的共同的部分抽象出来的
父类:基类
子类:派生类,可以扩展父类的功能(体量比父类大,功能比父类多)
子类的组成部分:1)父类的 2)自定义(新扩展的)

子类进程父类的语法:

class 子类名:继承方式 父类名{
};

继承方式:

public(公有继承):继承父类的成员访问权限不变
protected(保护继承):从父类中继承的成员访问权限变为受保护的
private(私有继承):从父类的继承的成员访问权限变为私有的

image-20231121113859938

继承源(单父类,多父类):

单父类:单继承
多父类:多继承,  :继承方式 父类名,继承方式 父类名2,...

【小结】

1)子类继承父类的成员函数,成员变量,不能继承父类的构造与析构函数
每一个类的构造函数和析构函数,只负责当前类对象的创建与回收
2)子类的构造函数定义时,可以调用父类的构造函数进行父类成员变量的初始化
子类名(参数列表):父类名(参数列表),...{}
3)子类定义成员函数名同父类的成员函数名,则表示子类重写父类的成员函数(功能重定义)
在重写的函数内,可以通过 父类名::成员函数名(参数列表) 调用父类的成员函数(原功能)

2.2 继承中的构造与析构

在c++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成

继承关系下的构造函数与析构函数的调用顺序:

1)子类对象在创建时会首先调用父类的构造函数
2)父类构造函数执行完毕后,才会调用子类的构造函数
3)当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
4)析构函数调用顺序和构造函数相反

image-20231121141645940

2.3 继承中同名成员的处理方法

当子类成员和父类成员同名时,子类依然从父类继承同名成员,子类访问同名成员时默认访问子类的成员(本作用域,就近原则),在子类中通过作用域::进行父类同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

如果是成员函数时,表示重写父类的成员函数,访问父类的成员函数加父类名::修饰前缀

2.4 非自动继承的函数

不是所有的函数都能自动从基类继承到派生类中

不会被继承的父类成员:

1)构造函数和析构函数
它们用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建
2operator=也不能被继承,因为它完成类似构造函数的行为
也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效,在继承的过程中,如果没有创建这些函数,编译器会自动生成它
3operator>>或operator<<友元全局函数,以及friend友元(类、成员函数)也不会被继承

2.5 继承中的静态成员特性

静态成员函数和非静态成员函数的共同点:

  • 他们都可以被继承到派生类中
  • 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏
  • 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏

2.6 多继承

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名产生较多的歧义

如果存在多个父类中同时的成员时,在子类访问时,需要加父类名::成员名来消除歧义

子类对象名.父类名::成员名或成员函数(参数列表);

2.7 菱形继承和虚继承

两个派生类继承同一个基类而又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石型继承。

这种继承会产生二义性和重复继承的问题,我们可以采用虚基类来解决

class A{};
class B:virtual public A{};
class C:virtual public A{};
class D: public B, public C{}

3. 多态

3.1 多态的基本概念

多态性提供接口与具体实现之间的另一层隔离,从而将what和how分离开来,多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。

C++支持静态多态(编译时)和动态多态(运行时)

运算符重载和函数重载就是编译时多态
派生类和虚函数实现的是运行时多态

静态多态和动态多态的区别

函数地址是早绑定(静态联编)还是晚绑定(动态联编)
在编译时确定函数的入口,属于早绑定
在编译时不确定,只有再运行时才确定的,属于晚绑定

多态的体现,父类对象的指针指向是子类对象的空间

多态的前提条件:继承+重写(重新实现父类的虚函数或纯虚函数)

3.2 向上类型转换

对象可以作为自己的类或者作为它的基类的对象来使用。还能通过基类的地址来操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来处理,这种称为向上类型转换。即父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。

如果父类的函数是一个虚函数时,则会产生一个vfptr(虚函数指针)指向一个虚函数表(表中存放的是虚函数入口地址和偏移量-(多继承))。子类重写了父类的虚函数时,则会更新父类的vfptr指向的位置为子类的重写函数,所以在父类引用调用函数时(父类引用指向是子类对象), 则调用vfptr实际上指向函数位置(子类的重写函数)。

3.3 多态的条件和应用方式

继承+子类重写父类的虚函数(返回值、函数名、参数列表必须相同,virtual关键字可写可不写,建议写上)

【注意】子类扩展新的功能和属性,不能作为多态的方式应用

多态的应用方式:

  • 局部使用,父类的引用或指针指向子类对象
  • 设计函数时,父类的引用或指针作为函数(全局函数,某类的成员函数)的参数,调用函数时,传入的子类的对象

3.4 纯虚函数和多继承

纯虚函数可以解决多继承的二义性的问题

如果一个类存在一个纯虚函数,则这个类为抽象类,抽象类不创建实例(对象)

如果一个类中的所有函数都是纯虚函数,又称之为"接口"。

格式:

virtual 返回值类型 函数名(参数列表)=0;

多重继承接口不会带来二义性和复杂性问题。接口类只是一个功能声明,并不是功能实现,子类需要根据功能说明定义功能实现

【注意】

  • 除了析构函数外,其他声明都可以是纯虚函数
  • 多继承时,超级父类不能出现纯虚函数,子类的父类需要从超级父类虚继承

3.5 虚析构函数

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

3.6 重写、重载、重定义

重载(overload):同一作用域的同名函数,参数个数、参数顺序、参数类型等不同,返回值无关的;const也可以引起重载

重定义(overwrite)(隐藏):不能体现多态,有继承+子类(派生类)重新定义父类(基类)的(静态与非静态)同名成员(非virtual函数)。

重写(override)(覆盖):体现多态,有继承+子类(派生类)重写父类(基类)的virtual函数,函数返回值,函数名字,函数参数,必须和基类中的虚函数一致。

class A
{
public:
    void f1() { cout << "f1" << endl; }
    void f1(int) { cout << "f1(int)" << endl; }
    void f1(int, int) { cout << "f1(int,int)" << endl; }
    void f4() { cout << "f4()" << endl; }
    virtual void f5() { cout << "virtual f5() " << endl; }
};
class B : public A
{
public:
    void f1(double) // 函数的重定义,父类的f1的所有函数将隐藏了
    {
        cout << "f1(double)" << endl;
    }
    // void f5(int) // 重定义
    void f5() // 重写
    {
        cout << "B f5()" << endl;
    }
};
posted @   常羲和  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
// 侧边栏目录
点击右上角即可分享
微信分享提示