虚和重载

虚基类

  • 需要解决的问题
    当派生类从多个基类派生,而这些基类又有共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余,并有可能因冗余带来不一致性。
  • 虚基类声明
    以virtual说明基类继承方式
    例:class B1:virtual public B
  • 作用
    1.主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;
    2.为最远的派生类提供唯一的基类成员,而不重复产生多次复制。
  • 注意:
    在第一继承时就要将共同基类设计为虚基类。
#include <iostream>
using namespace std;

class Base0 {
public:
    int var0;
    void fun0() { cout << "Base0 is used." << endl; }
};
// 如果这里不用虚基类,会有二义性,导致函数无法执行
class Base1:virtual public Base0 {
public:
    int var1;
};
class Base2 :virtual public Base0 {
public:
    int var2;
};
class Derived :public Base1, public Base2 {
public:
    int var;
    void fun() { cout << "Derived is used." << endl; }
};

int main()
{
    Derived d;
    d.var0 = 1;
    d.fun0();
    return 0;
}

虚基类及其派生类构造函数

  • 建立对象时所指定的类最远称为最远派生类
  • 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的
  • 在整个继承构造中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数,如果未列出,则表示调用该虚基类的默认构造函数。
  • 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。
#include <iostream>
using namespace std;

class Base0 {
public:
    Base0(int var) :var0(var) {} // 这里相当于var0 = var
    int var0;
    void fun0() { cout << "Member of Base0" << endl; }
};
class Base1 :virtual public Base0 {
public:
    Base1(int var):Base0(var){}
    int var1;
};
class Base2 :virtual public Base0 {
public:
    Base2(int var):Base0(var){}
    int var2;
};
class Derived :public Base1, public Base2 {
public:
    // 这里并没有给var赋值三次,其中只有第一个(最远派生类)执行了,其他都是传参不执行。因为虚基类
    Derived(int var) :Base0(var), Base1(var), Base2(var) {}
    int var;
    void fun() { cout << "Member of Derived." << endl; }
};

int main()
{
    Derived d(1);
    d.var = 2;
    d.fun();

    return 0;
}

运算符的重载

  • C++几乎可以重载所有的运算符,而且只能重载C++中已经有的。
    不能重载的运算符:"."、".*、"::"、"? :"
  • 重载之后运算符的优先级和结合性都不会改变。
  • 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。
  • 例如:
    使复数类的对象可以用“+”运算符实现加法;
    使时钟类对象可以用“++”运算符实现时间增加1秒。

重载为类成员的运算符函数定义形式:

函数类型 operator 运算符(形参)
{
...
}
参数个数=原操作数个数-1 (后置++、--除外)

双目运算符重载规则

  • 如果要重载B为类成员函数,使之能够实现表达式oprd1 B oprd2,其中oprd1为A类对象则B应被重载为A类的成员函数,形参类型应该是oprd2所属的类型。
  • 经重载后表达式oprd1 B oprd2相当于oprd1.operator B(oprd2)
#include <iostream>
using namespace std;

class Complex {
public:
    Complex(double r = 0.0, double i = 0.0):real(r), imag(i){}
    // 运算符+重载成员函数
    Complex operator + (const Complex& c2)const;
    // 运算符-重载成员函数
    Complex operator - (const Complex& c2)const;
    void display()const; // 输出复数
private:
    double real; // 复数实部
    double imag; // 复数虚部
};
Complex Complex::operator+(const Complex& c2)const {
    // 创建一个临时无名对象作为返回值
    return Complex(real + c2.real, imag + c2.imag);
}
Complex Complex::operator-(const Complex& c2)const {
    // 创建一个临时无名对象作为返回值
    return Complex(real - c2.real, imag - c2.imag);
}
void Complex::display()const {
    cout << "(" << real << "," << imag << ")" << endl;
}

int main()
{
    Complex c1(5, 4), c2(2, 10), c3;
    cout << "c1 = "; c1.display();
    cout << "c2 = "; c2.display();
    c3 = c1 - c2;
    cout << "c3 = c1 - c2 = "; c3.display();
    c3 = c1 + c2;
    cout << "c3 = c1 + c2 = "; c3.display();

    return 0;
}

前置单目运算符重载规则

  • 如果要重载U为类成员函数,使之能够实现表达式U oprd,其中oprd为A类对象,则U应被重载为A类的成员对象,无形参。
  • 经重载后,
    表达式U oprd相当于oprd.operator U()

后置单目运算符++和--重载规则

  • 如果要重载++或--为类成员函数,使之能够实现表达式oprd++或oprd--,其中oprd为A类对象,则++或--应被重载为A类的成员函数,且只有一个int类型形参。
  • 经重载后,表达式oprd++相当于oprd.operator++(0)
#include <iostream>
using namespace std;
class Clock    {    //时钟类声明定义
public:    //外部接口
    Clock(int hour = 0, int minute = 0, int second = 0);
    void showTime() const;
    Clock& operator ++ ();    //前置单目运算符重载
    Clock operator ++ (int);    //后置单目运算符重载
private:    //私有数据成员
    int hour, minute, second;
};

Clock::Clock(int hour/* = 0 */, int minute/* = 0 */, int second/* = 0 */) {    //构造函数
    if (0 <= hour && hour < 24 && 0 <= minute && minute < 60 && 0 <= second && second < 60) {
        this->hour = hour;
        this->minute = minute;
        this->second = second;
    } else
        cout << "Time error!" << endl;
}

void Clock::showTime() const {    //显示时间函数
    cout << hour << ":" << minute << ":" << second << endl;
}

Clock & Clock::operator ++ () {    //前置单目运算符重载函数
    second++;
    if (second >= 60) {
        second -= 60;
        minute++;
        if (minute >= 60) {
            minute -= 60;
            hour = (hour + 1) % 24;
        }
    }
    return *this;
}

Clock Clock::operator ++ (int) {    //后置单目运算符重载
    //注意形参表中的整型参数
    Clock old = *this;
    ++(*this);    //调用前置“++”运算符
    return old;
}

int main() {
    Clock myClock(23, 59, 59);
    cout << "First time output: ";
    myClock.showTime();
    cout << "Show myClock++:    ";
    (myClock++).showTime();
    cout << "Show ++myClock:    ";
    (++myClock).showTime();
    return 0;
}
/*
First time output: 23:59:59
Show myClock++: 23:59:59
Show ++myClock: 0:0:1
*/
#include <iostream>
using namespace std;

class Point {
    int _x, _y;
public:
    Point (int x = 0, int y = 0):_x(x), _y(y){}
    Point& operator++();
    Point operator++(int);
    Point& operator--();
    Point operator--(int);
    // 友元函数,重载插入运算符,输出整个Point类的对象
    friend ostream& operator << (ostream& o, Point& p);
};
Point& Point::operator++()
{
    _x++;
    _y++;
    return *this;
}
// ++i在C++里面的定义是,最后返回的是被++的对象的引用(系统就是这么实现的),所以++i可以作为左值,即可以写:++i=3

// 后缀式操作符接受一个额外的int型形参(不会去使用它,仅用作区分)
Point Point::operator++(int)
{
    Point temp = *this;
    ++* this; // 复用了前缀++的重载
    return temp;

    // 后缀版本中,返回值是尚未自增的原值,但对象本身已经做了自增操作了。
}
// i++在C++里面的定义是,最后返回的是被++的对象的的值(系统就是这么实现的),所以++i不可以作为左值,即不可以写:i++=3
Point& Point::operator--()
{
    _x--;
    _y--;
    return *this;
}
Point Point::operator--(int)
{
    Point temp = *this;
    --* this;
    return temp;
}
// 友元函数,返回值类型为ostream&,可以支持<<级连操作
ostream& operator << (ostream& o, Point& p) {
    o << '(' << p._x << ", " << p._y << ')';
    return o;
}

int main()
{
    Point p(1, 2);
    cout << p << endl;
    p++;
    cout << ++p << endl;
    // cout << p--(++) << endl;都不可以用,因为左值原因,但是有的编译器可以,不懂。
    cout << --p << endl;

    return 0;
}
/*
(1, 2)
(3, 4)
(2, 3)
*/

运算符重载为非成员函数的规则

  • 函数的形参代表依自左向右次序排列的各操作数
  • 重载为非成员函数时
    参数个数=源操作数个数(后置++、--除外)
    至少有一个自定义类型的参数
  • 后置单目运算符++和--的重载函数,形参列表中要增加一个int,但不必要写参数名。
  • 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
  • 双目运算符B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1, oprd2)
  • 前置单目运算符B重载后,表达式B oprd 等同于operator B(oprd)
  • 后置单目运算符++和--重载后,表达式 oprd B 等同于operator B(oprd, 0)

函数重载
函数重载的规则:

  • 函数名称必须相同。
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  • 函数的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为函数的重载
    (出处:https://blog.csdn.net/zhanghow/article/details/53588458)
    判断重载函数的正确性

int add(int x, int y);
float add (float x, int y); // √

int add(int x, int y);
int add (float x, int y); // √

int add(int x, int y);
float add (int c, int d); // ×

int add(int x, int y);
float add (int x, int y); // ×

int add(int x, int y, int z);
int add (float x, int y); // √


虚函数

#include <iostream>
using namespace std;

class Base1 {
public:
    virtual void display() const { // 虚函数
        cout << "Base1::display()" << endl;
    }
};
class Base2 :public Base1 {
public:
    virtual void display() const {
        cout << "Base2::display()" << endl;
    }
};
class Derived :public Base2 {
public:
    virtual void display() const {
        cout << "Derived::display()" << endl;
    }
};
void fun(Base1* ptr) {
    ptr->display();
}

int main()
{
    Base1 base1;
    Base2 base2;
    Derived derived;

    fun(&base1);
    fun(&base2);
    fun(&derived);

    return 0;
}
/*
Base1::display()
Base2::display()
Derived::display()
*/

定义

  • 用virtual关键字说明的函数
  • 虚函数是实现运行时多态性的基础
  • C++中的虚函数是动态绑定的函数
  • 虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。

什么可以成为虚函数

  • 一般成员函数可以是虚函数
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数

一般虚函数成员

  • 虚函数的声明
    virtual 函数类型 函数名(形参表);
  • 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。
  • 在派生类中可以对基类中的成员函数进行覆盖
  • 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。

总结 virtual 关键字

  • 派生类可以不显式地用virtual声明函数,这时系统就会用以下规则来判断派生类的一个函数成员不是虚函数
    该函数是否与基类的虚函数有相同的名称、参数个数及对应参数类型
    该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针,引用型的返回值
  • 如果从名称、参数及返回值三个方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。
  • 派生类中的虚函数还会隐藏基类中同名函数的所有其他重载形式
  • 一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。

虚析构函数

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base(); // 不是虚函数
};
Base::~Base() {
    cout << "Base destrutor" << endl;
}
class Derived :public Base {
public:
    Derived();
    virtual ~Derived(); // 不是虚函数
private:
    int* p;
};
Derived::Derived() {
    p = new int(0);
}
Derived::~Derived() {
    cout << "Derived destructor" << endl;
    delete p;
}
void fun(Base* b) {
    delete b; // 静态绑定,只会调用~Base()
}

int main()
{
    Base* b = new Derived();
    fun(b);

    return 0;
}

纯虚函数

  • 纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明为:
    virtual 函数类型 函数名(参数表) = 0;
  • 抽象类的语法
    带有纯虚函数的类称为抽象类:
    class 类名
    {
      virtual 类型 函数名(参数表) = 0;
      // 其他成员......
    }
  • 抽象类不能实例化,但是可以统一对外接口
    好处:可以将基类对象和各级不同派生类对象统一处理。

抽象类作用

  • 将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为
  • 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现
  • 抽象类只能作为基类使用
  • 不能定义抽象类的对象
#include <iostream>
using namespace std;

class Base1 {
public:
    virtual void display() const = 0 { // 纯虚函数
        cout << "Base1::display()" << endl;
    }
};

class Base2 :public Base1 {
public:
    virtual void display() const { // 覆盖基类的虚函数
        cout << "Base2::display()" << endl;
    }
};

class Derived :public Base2 {
public:
    virtual void display() const {
        cout << "Derived::display()" << endl;
    }
};

void fun(Base1* ptr) {  // 参数为指向基类对象的指针
    ptr->display();        // “对象指针->成员名”
}

int main()
{
    // 纯虚函数不能产生对象,所以不能有Base1 base;的声明。
    Base2 base2;
    Derived derived; 

    fun(&base2);
    fun(&derived);

    return 0;
}
/*
Base2::display()
Derived::display()
*/
全部评论

相关推荐

1 收藏 评论
分享
牛客网
牛客企业服务