c++ 多态性

类的多态

多态概念入门

#include <iostream>
using namespace std;

/*
多态的前提: 拥有继承关系的类中有相同的函数(返回类型、函数名、形参列表)
多态解决的问题:
    1、派生类的对象被赋值给基类对象时
    2、派生类的对象初始化基类的引用时
    3、基类的指针指向派生类时
  函数调用的问题
总结:
    没有多态时看类型。
    有多态时看内存。
*/

class A
{
public:
    A() {cout << "A" << endl;}
    A(const A& a) {cout << "A(const A& a)" << endl;}

    void fun() {cout << "A::fun()" << endl;}
};

class B : public A
{
public:
    B() {cout << "B" << endl;}
    B(const B& b) {cout << "B(const B& b)" << endl;}

    void fun() {cout << "B::fun()" << endl;}
};

int main(int argc, char *argv[])
{
    B b;

    // 1、派生类的对象可以被赋值给基类对象
    A a = b;
    a.fun();

    // 2、派生类的对象可以初始化基类的引用
    A& a1 = b;
    a1.fun();


    // 3、指向基类的指针也可以指向派生类
    A* a2 = new B;
    a2->fun();	// 调用的是A类函数,但本质确实B的空间

    return 0;
}

多态性

多态性:例如函数重载,同一个函数名会产生不同的结果,而虚函数是实现多态性另一个重要途径

#include <iostream>
using namespace std;

class A
{
    private:
    	int a;
    public:
    	virtual void f();	// 这是虚函数的一个地址,而不是实际的函数,地址所指向的的才是对应的函数
};


int main()
{
    cout << sizeof(A) << endl;	// 4 + 8 = 12 内存对齐原则  最终打印 16
    
    return 0;
}

普通虚函数

#include <iostream>
using namespace std;

class Manmal
{
    public:
    	// 注意点一:
    	// virtual 的三种情况(为了书写规范,基类和派生类必须都写)
    	// 1、基类有     派生类没有   可以构成虚函数
    	// 2、基类有     派生类有     可以构成虚函数
    	// 3、基类没有   派生类有     不可以构成虚函数
    	virtual void sound() { cout << "发出叫声" << endl; }
};

/*
注意点二:
虚函数定义时不需要再次书写 virtual 关键字
void Manmal::sound()
{ cout << "发出叫声" << endl; }
*/

class Cat : public Manmal
{
    public:
    	// 注意点三:
    	// 此时,派生类中拥有和基类相同的函数,这一行为叫做函数的复写(又称覆盖),不是重载
    	// 函数复写的最大特点:基类的指针或引用可以忽略基类已存在的版本,直接调用派生类的版本
    	virtual void sound() { cout << "喵喵喵" << endl; }
};

class Dog : public Manmal
{
    public:
    	// 注意点四:
    	// 普通虚函数派生类可以不进行复写,当注释下方语句时,派生类进行调用则会打印基类虚函数叫声
    	
    	// 理解:基类的虚函数是所有派生类的共同特点,当派生类不具备自己的特点时,则会显示出共同特点
    	virtual void sound() { cout << "汪汪汪" << endl; }
};

// 可以处理一切哺乳动物发出声音这种行为
void feed(Manmal &m)
{
    m.sound();
}
------------------------------------------------------------------------------------------------------------------
/*
void feed(Cat &cat)
{
    cat.sound();
}

void feed(Dog &dog)
{
    dog.sound();
}
*/
------------------------------------------------------------------------------------------------------------------
int main()
{
    Cat *cat = new Cat;
    Dog *dog = new Dog;
    
    feed(*cat);
    feed(*dog);
    /*
    // 上述代码改写 并删除 feed 函数
    Manmal *cat = new Cat;
    Manmal *dog = new Dog;
    
    cat->sound();
    dog->sound();
    */
    
    delete cat;
    delete dog;
    return 0;
}

纯虚函数

概念:相较于普通虚函数(基类中的虚函数必须实现),纯虚函数可以不用实现基类中的虚函数(也可以实现),这个基类称之为抽象基类,但是同时要求派生类必须实现虚函数代码,否则不能定义对象仍旧为抽象基类。

#include <iostream>
using namespace std;

class A	// 抽象基类:专门用于派生子类,自己不能定义对象
{
    public:
    	// 本质:只有实现这个函数才能定义对象,这是所有派生类产生的必须条件。如:战斗机、明航客机、直升机等都必须能飞
    	virtual void f() = 0;	// 纯虚函数定义形式
};


int main()
{
    
    
    return 0;
}

虚函数与构造函数

构造函数不能是虚函数

虚函数意味着要被派生类的各类方法进行复写,而派生类的构造并不能通过基类。

虚函数与析构函数

结论:

若一个类不会派生新的类,则析构函数只需普通函数即可;

若一个类会派生出新的类,则析构函数必须定义为虚函数;

#include <iostream>
using namespace std;

class Base
{
    public:
    	virtual ~Base() { cout << "~Base()" << endl; }
};

class Drived1 : public Base
{
    public:
    	virtual ~Drived1() { cout << "~Drived1()" << endl; }
};

class Drived2 : public Base
{
    public:
    	virtual ~Drived2() { cout << "~Drived2()" << endl; }
};


int main()
{
    // 若不将析构函数定义为虚函数,那么派生类析构时就无法调用派生类的析构函数,不能产生穿透
    // 析构函数相较于普通函数,虚函数复写时名字可以不同,因为一个类只有一个析构函数
    Base *d1 = new Drived1;
    Base *d2 = new Drived2;
    
    delete d1;
    delete d2;
    return 0;
}

运算符重载与友元

运算符函数

在 c++ 中,操作数包含类对象时,运算符操作就是一个函数。除了赋值运算符默认之外,其余都需要进行重载才能使用。

class A
{
    public:
    	// 重载了 + 运算符
    	const A & operator+(const A &b);	// const 是为了保证和普通运算符运算方式一致,如:(a+b)=c 不应该被满足
};

int main()
{
    A a, b;
    
    a + b;	// -----> a.operator+(b)
    
}

/*
返回值类型:A &
函数名:operator+
参数列表:const A &r
*/

类成员运算符重载

#include <iostream>
using namespace std;

class Time
{
    private:
    	int hour;
    	int minute;
    	int second;
    
    public:
    	Time(int h = 0, int m = 0, int s = 0)
            : hour(h), minute(m), second(s){};
    
    	// 加法运算符操作函数重载 (t1 + t2)
    	const Time operator+(const Time &r);
    
    	// 显示时间
    	void show();
};

const Time Time::operator+(const Time &r)
{
    Time tmp;
    
    tmp.second = this->second + r.second;
    if(tmp.second >= 60)
    {
        tmp.minute++;
        tmp.second -= 60;
    }
    
    tmp.minute += this->minute + r.minute;
    if(tmp.minute >= 60)
    {
        tmp.hour++;
        tmp.minute -= 60;
    }
    
    tmp.hour += this->hour + r.hour;
    tmp.hour %= 24;
    
    return tmp;
}

void Time::show()
{
    cout << hour << ":" << minute << ":" << second << endl;
}


int main()
{
    Time t1(1, 50, 33);
    Time t2(0, 20, 33);
    
    Time t3 = t1 + t2;
    t3.show();
    
    return 0;
}

非类成员运算符重载

#include <iostream>
using namespace std;

class Time
{
    public:
    	int hour;
    	int minute;
    	int second;
    
    public:
    	Time(int h = 0, int m = 0, int s = 0)
            : hour(h), minute(m), second(s){};
    
    	// 加法运算符操作函数重载 (t1 + t2)
    	const Time operator+(const Time &r);
    
    	// 类内:输出运算符操作函数重载
    	ostream &operator<<(ostream &out);	
    
    	// 显示时间
    	void show();
};

const Time Time::operator+(const Time &r)
{
    Time tmp;
    tmp.second = this->second + r.second;
    if(tmp.second >= 60)
    {
        tmp.minute++;
        tmp.second -= 60;
    }
    
    tmp.minute += this->minute + r.minute;
    if(tmp.minute >= 60)
    {
        tmp.hour++;
        tmp.minute -= 60;
    }
    
    tmp.hour += this->hour + r.hour;
    tmp.hour %= 24;
    
    return tmp;
}

void Time::show()
{
    cout << hour << ":" << minute << ":" << second << endl;
}

// 类内:输出运算符操作函数重载
ostream &Time::operator<<(ostream &out)
{
    out << hour << ":" << minute << ":" << second << endl;
    return out;
}

// 类外:输出运算符操作函数重载
ostream &operator<<(ostream &out, Time &t)	// 由于是类外重载,所以有多少参数就需要传入多少个
{
    // 存在一个矛盾:此时需要调用类内成员,所以类内成员必须为 public,但是这并不符合类成员数据原则
    out << t.hour << ":" << t.minute << ":" << t.second << endl;
    return out;
}

int main()
{
    Time t1(1, 50, 33);
    Time t2(0, 20, 33);
    
    Time t3 = t1 + t2;
    t3.show();
    
    // 显然,此时类外重载更加匹配平时使用的习惯
    t3 << cout;	// 类内重载运算符
    
    cout << t3 << endl;	// 类外重载运算符
    
    return 0;
}


注意:
    1、定义非类成员运算符函数重载时,必须保证至少有一个参数是自定义类型
    2、运算符的操作数必须从左到右一次填入参数列表中

上述重载存在着一个问题:就是进行类外重载时,必须要让类内部成员数据权限变成public,而这并不符合类成员权限原则。所以需要借助后续的友元来进行解决。

前缀运算符重载

#include <iostream>
using namespace std;

class Time
{
    public:
    	int hour;
    	int minute;
    	int second;
    
    public:
    	Time(int h = 0, int m = 0, int s = 0)
            : hour(h), minute(m), second(s){};
    
    	// 加法运算符操作函数重载 (t1 + t2)
    	const Time operator+(const Time &r);
    
    	// 类内:输出运算符操作函数重载
    	ostream &operator<<(ostream &out);
    
    	// 前缀运算符操作重载
    	Time & operator++();
    
    	// 显示时间
    	void show();
};

const Time Time::operator+(const Time &r)
{
    Time tmp;
    tmp.second = this->second + r.second;
    if(tmp.second >= 60)
    {
        tmp.minute++;
        tmp.second -= 60;
    }
    
    tmp.minute += this->minute + r.minute;
    if(tmp.minute >= 60)
    {
        tmp.hour++;
        tmp.minute -= 60;
    }
    
    tmp.hour += this->hour + r.hour;
    tmp.hour %= 24;
    
    return tmp;
}

void Time::show()
{
    cout << hour << ":" << minute << ":" << second << endl;
}

// 类内:输出运算符操作函数重载
ostream &Time::operator<<(ostream &out)
{
    out << hour << ":" << minute << ":" << second << endl;
    return out;
}

// 类外:输出运算符操作函数重载
ostream &operator<<(ostream &out, Time &t)	// 由于是类外重载,所以有多少参数就需要传入多少个
{
    // 设置宽度为 2,不足则填充为 0
    out.width(2);
    out.fill('0');
    // 存在一个矛盾:此时需要调用类内成员,所以类内成员必须为 public,但是这并不符合类成员数据原则
    out << t.hour << ":" << t.minute << ":" << t.second << endl;
    return out;
}

// 前缀运算符操作重载
Time & Time::operator++()
{
    second++;
    
    second = second >= 60 ? (minute++, 0) : second;
    minute = minute >= 60 ? (hour++, 0) : minute;
    hour %= 24;
    
    return *this;
}


int main()
{
    Time t1(1, 50, 33);
    Time t2(0, 20, 33);
    
    Time t3 = t1 + t2;
    
    cout << "t3.show()" << endl;
    t3.show();
    
    // 显然,此时类外重载更加匹配平时使用的习惯
    cout << "t3 << cout" << endl;
    t3 << cout;	// 类内重载运算符
    
    cout << "cout << t3 << endl" << endl;
    cout << t3 << endl;	// 类外重载运算符
    
    ++t1;
    cout << "++t1" << endl;
    cout << t1 << endl;
    
    return 0;
}

后缀运算符重载

#include <iostream>
using namespace std;

class Time
{
    public:
    	int hour;
    	int minute;
    	int second;
    
    public:
    	Time(int h = 0, int m = 0, int s = 0)
            : hour(h), minute(m), second(s){};
    
    	// 加法运算符操作函数重载 (t1 + t2)
    	const Time operator+(const Time &r);
    
    	// 类内:输出运算符操作函数重载
    	ostream &operator<<(ostream &out);
    
    	// 前缀运算符操作重载
    	Time & operator++();
    
    	// 后缀运算符操作重载
    	const Time operator++(int);	// int 没有实际作用,只是用于区分前缀还是后缀
    
    	// 显示时间
    	void show();
};

const Time Time::operator+(const Time &r)
{
    Time tmp;
    tmp.second = this->second + r.second;
    if(tmp.second >= 60)
    {
        tmp.minute++;
        tmp.second -= 60;
    }
    
    tmp.minute += this->minute + r.minute;
    if(tmp.minute >= 60)
    {
        tmp.hour++;
        tmp.minute -= 60;
    }
    
    tmp.hour += this->hour + r.hour;
    tmp.hour %= 24;
    
    return tmp;
}

void Time::show()
{
    cout << hour << ":" << minute << ":" << second << endl;
}

// 类内:输出运算符操作函数重载
ostream &Time::operator<<(ostream &out)
{
    out << hour << ":" << minute << ":" << second << endl;
    return out;
}

// 类外:输出运算符操作函数重载
ostream &operator<<(ostream &out, Time &t)	// 由于是类外重载,所以有多少参数就需要传入多少个
{
    // 设置宽度为 2,不足则填充为 0
    out.width(2);
    out.fill('0');
    // 存在一个矛盾:此时需要调用类内成员,所以类内成员必须为 public,但是这并不符合类成员数据原则
    out << t.hour << ":" << t.minute << ":" << t.second << endl;
    return out;
}

// 前缀运算符操作重载
Time & Time::operator++()
{
    second++;
    
    second = second >= 60 ? (minute++, 0) : second;
    minute = minute >= 60 ? (hour++, 0) : minute;
    hour %= 24;
    
    return *this;
}

// 后缀运算符操作重载
const Time Time::operator++(int)
{
    // 作出说明1:相较于前缀运算,此时加入 const 是防止出现 t1++ = t2,这一点相较于未重载前冲突
    // 作出说明2:为什么不返回引用?因为在函数内部创建了一个临时变量来保存原值并返回,那么函数结束会释放销毁,返回引用则找不到对应的地址,所以直接返回临时变量的内部数值
    Time tmp = *this;
    
    second++;
    
    second = second >= 60 ? (minute++, 0) : second;
    minute = minute >= 60 ? (hour++, 0) : minute;
    hour %= 24;
    
    return tmp;
}


int main()
{
    Time t1(1, 50, 33);
    Time t2(0, 20, 33);
    
    Time t3 = t1 + t2;
    
    cout << "t3.show()" << endl;
    t3.show();
    
    // 显然,此时类外重载更加匹配平时使用的习惯
    cout << "t3 << cout" << endl;
    t3 << cout;	// 类内重载运算符
    
    cout << "cout << t3 << endl" << endl;
    cout << t3 << endl;	// 类外重载运算符
    
    ++t1;
    cout << "++t1" << endl;
    cout << t1 << endl;
    
    t1++;
    cout << "t1++" << endl;
    cout << t1 << endl;
    
    return 0;
}

友元函数

目的:在类外部非本类成员对类内部非公有数据进行访问。

例如:遥控器仅仅通过自身按钮而非电视机公开按钮去操纵电视机

友元函数并没有破坏封装性原则,应为决定函数是否为友元函数的对象还是类本身

#include <iostream>
using namespace std;

class A
{
    private:
    	int x;
    
    public:
    	A(int x = 0) : x(x){}
    
    // 声明一个友元函数
    friend void show(const A &r);
};

void show(const A &r)
{
    cout << r.x << endl;
}

int main()
{
    A a(6);
    // 友元函数的调用与普通函数一样,不需要类对象引用
    show(a);

    return 0;
}


注意:
    1、友元函数可以直接访问类私有成员,且友元函数定义不需要写 friend 关键字

修改上述非类成员(类外函数)运算符重载代码存在的问题如下:

#include <iostream>
using namespace std;

class Time
{
    private:
    	int hour;
    	int minute;
    	int second;
    
    public:
    	Time(int h = 0, int m = 0, int s = 0)
            : hour(h), minute(m), second(s){};
    
    	// 类外:输出运算符操作函数重载
    	friend ostream &operator<<(ostream &out, Time &t);	
    
    	// 显示时间
    	void show();
};

void Time::show()
{
    cout << hour << ":" << minute << ":" << second << endl;
}


// 类外:输出运算符操作函数重载
ostream &operator<<(ostream &out, Time &t)	// 由于是类外重载,所以有多少参数就需要传入多少个
{
    out << t.hour << ":" << t.minute << ":" << t.second << endl;
    return out;
}

int main()
{
    Time t1(1, 50, 33);
    
    cout << t1 << endl;	// 类外重载运算符
    
    return 0;
}

友元类

#include <iostream>
using namespace std;

class A
{
    private:
    	int a;
    
    public:
    	void show() { cout << a << endl; }
    
    // 将 B 类作为 A 类的友元类
    friend class B;	// B 类中所有类方法都可以访问 A 类中所有成员数据
};

class B
{
    public:
    	void set_A(A &r, int b) { r.a = b; }
};


int main()
{
    A a;
    B b;
    
    b.set_A(a, 6);
    a.show();
    
    return 0;
}

友元类方法

相较于友元类,友元类方法缩小了范围,意思就是在 A 类中只允许 B 类中的部分类方法来开放访问所有成员数据权限。

#include <iostream>
using namespace std;

class A
{
    private:
    	int a;
    
    public:
    	void show() { cout << a << endl; }
    
    // 将 B 类中的部分类方法作为 A 类的友元类方法
    friend void B::set_A(A &r, int b);	// B 类中仅该类方法可以访问 A 类中所有成员数据
};

class B
{
    public:
    	void set_A(A &r, int b) { r.a = b; }
};


int main()
{
    A a;
    B b;
    
    b.set_A(a, 6);
    a.show();
    
    return 0;
}


注意:
    上述方法极其容易出现未定义等现象,要解决上述问题就要分文件处理,主要可以分为 
    main.cpp A.cpp A.h B.cpp B.h

上一篇:C# 单例模式的多种实现


下一篇:力扣—不同路径(路径问题的动态规划)-代码实现