C++ 友元、重载、继承、多态

友元

关键字:friend

友元的三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
全局函数做友元
//建筑物类
class Building
{
    //goodGay全局函数是Building好朋友,可以访问Building中私有成员
    friend void goodGay(Building& building);
public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_bedRoom = "卧室";
    }
    string m_SittingRoom;
private:
    string m_bedRoom;
};
//全局函数
void goodGay(Building& building)
{
    cout << "好基友全局函数正在访问:" << building.m_SittingRoom << endl;
    cout << "好基友全局函数正在访问:" << building.m_bedRoom << endl;

}
void test01()
{
    Building building;
    goodGay(building);
}
类做友元
class Building;
class goodGay;
class goodGay
{
public:
    goodGay();
    void visit();
private:
    Building* building;
};
//建筑物类
class Building
{  
    friend class goodGay;
public:
    Building();
    string m_SittingRoom;
private:
    string m_bedRoom;
};
Building::Building()
{
    this->m_SittingRoom = "客厅";
    this->m_bedRoom = "卧室";
}
goodGay::goodGay()
{
    building = new Building;
}
void goodGay::visit()
{
    cout << "好基友全局函数正在访问:" << building->m_SittingRoom << endl;
    cout << "好基友全局函数正在访问:" << building->m_bedRoom << endl;
}

void test01()
{
    goodGay gg;
    gg.visit();
}
成员函数做友元
class Building;
class goodGay;
class goodGay
{
public:
    goodGay();
    void visit();
    void visit2();//只让visit访问私有属性,visit2不能访问
private:
    Building* building;
};
//建筑物类
class Building
{  
    friend void goodGay::visit();
public:
    Building();
    string m_SittingRoom;
private:
    string m_bedRoom;
};
Building::Building()
{
    this->m_SittingRoom = "客厅";
    this->m_bedRoom = "卧室";
}
goodGay::goodGay()
{
    building = new Building;
}
void goodGay::visit()
{
    cout << "好基友全局函数正在访问:" << building->m_SittingRoom << endl;
    cout << "好基友全局函数正在访问:" << building->m_bedRoom << endl;
}
void goodGay::visit2()
{
    cout << "好基友全局函数正在访问:" << building->m_SittingRoom << endl;
    //cout << "好基友全局函数正在访问:" << building->m_bedRoom << endl;
}

void test01()
{
    goodGay gg;
    gg.visit();
   
}

重载

运算符重载
加号运算符重载

成员函数重载
class Person
{
public:
    //1. 成员函数重载+号
    Person operator+(Person& p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }
    int m_A;
    int m_B;
};
void test01()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;
    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;

    //成员函数重载本质调用
    //Person p3 = p1.operator+(p2);
    Person p3 = p1 + p2;
    cout << "m_A:" << p3.m_A << ", m_B:" << p3.m_B << endl;

}
全局函数重载和函数重载
class Person
{
public:
    int m_A;
    int m_B;
};
//2. 全局函数重载+号
Person operator+(Person& p1, Person& p2)
{
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}
//函数重载的版本
Person operator+(Person& p1, int num)
{
    Person temp;
    temp.m_A = p1.m_A + num;
    temp.m_B = p1.m_B + num;
    return temp;

}
void test01()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;
    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;

    //全局函数重载本质调用
    //Person p3 = operator+(p1, p2);
    Person p3 = p1 + p2;

    //运算符重载 也可以发生函数重载
    Person p4 = p1 + 100;
    cout << "m_A:" << p3.m_A << ", m_B:" << p3.m_B << endl;
    cout << "m_A:" << p4.m_A << ", m_B:" << p4.m_B << endl;

}

总结1:对于内置的数据类型的表达式的运算符是不可能改变的

总结2:不要滥用运算符重载

左移运算符重载

只能利用全局函数重载左移运算符,因为如果利用成员函数重载,左移运算符 p.operator<<(cout) 简化版本为p<<cout,无法实现cout在左侧

重载运算符目的:只输出对象p就可以实现输出p中的所有成员属性。

class Person
{
    friend ostream& operator<<(ostream& cout, Person& p);
public:
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }
private:
    int m_A;
    int m_B;
};
// 只能利用全局函数重载左移运算符
ostream& operator<<(ostream& cout, Person& p)//本质:operstor(cout, p)
{
    cout << "m_A = " << p.m_A << ", m_B = " << p.m_B;
    return cout;
}

void test01()
{
    Person p(10, 10);
    cout << p << endl;
}
递增运算符重载
class MyInteger
{
    friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
    //重载前置++运算符,返回引用为了一直对一个数据进行递增操作
    MyInteger& operator++()
    {
        //先进行++运算
        m_Num++;
        //再将自身做返回
        return *this;
    }
    //重载后置++运算符
    //void operator++(int)  
	//int代表占位参数,可以用于区分前置和后置递增
    MyInteger operator++(int)
    {
        //先 记录当时结果
        MyInteger temp = *this;
        //后 递增
        m_Num++;
        //最后将记录结果做返回
        return temp;

    }
    MyInteger(int num)
    {
        m_Num = num;
    }
private:
    int m_Num;
};

ostream& operator<<(ostream& cout, MyInteger myint)
{
    cout << myint.m_Num << endl;
    return cout;
}

void test01()
{
    MyInteger myint(0);
    cout << ++(++myint);
    cout << myint;
    cout << myint++;
    cout << myint;
}

总结:前置递增返回引用,后置递增返回值。

赋值运算符重载
class Person
{
public:
    Person(int age);
    ~Person();
    Person& operator=(Person& p);
    int* m_Age;
};
Person::Person(int age)
{
    //将年龄数据开辟到堆区
    m_Age = new int(age);
}
Person::~Person()
{
    if (m_Age != NULL)
    {
        delete m_Age;
        m_Age = NULL;
    }
}
//重载 赋值运算符
Person& Person::operator=(Person& p)
{
    //编译器是提供浅拷贝
    //m_Age = p.m_Age;
    //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
    if (m_Age != NULL)
    {
        delete m_Age;
        m_Age = NULL;
    }
    //深拷贝
    m_Age = new int(*p.m_Age);
    //返回对象本身
    return *this;
}
void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(25);
    p1 = p2 = p3;
    cout << "p1年龄:" << *p1.m_Age << endl;
    cout << "p2年龄:" << *p2.m_Age << endl;
    cout << "p3年龄:" << *p3.m_Age << endl;

}
关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

class Person
{
public:
    Person(string name, int age);
    bool operator==(Person& p);
    bool operator!=(Person& p);

private:
    string m_Name;
    int m_Age;
};

Person::Person(string name, int age)
{
    m_Name = name;
    m_Age = age;
}
bool Person::operator==(Person& p)
{
    if (m_Name == p.m_Name && m_Age == p.m_Age)
    {
        return true;
    }
    return false;
}
bool Person::operator!=(Person& p)
{
    if (m_Name == p.m_Name && m_Age == p.m_Age)
    {
        return false;
    }
    return true;
}
void test01()
{
    Person p1("Bob", 18);
    Person p2("Bob", 20);
    if (p1 != p2)
    {
        cout << "p1和p2不相等!" << endl;
    }
    else
    {
        cout << "p1和p2相等!" << endl;
    }
}
函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活

重定义可打印函数和可加函数

class MyPrint
{
public:
    void operator()(string text);
private:

};
void MyPrint::operator()(string text)
{
    cout << text << endl;
}
class MyAdd
{
public:
    int operator()(int num1, int num2)
    {
        int ret = num1 + num2;
        return ret;
    }

private:

};


void test01()
{
    MyPrint myprint;
    myprint("hello world!"); //由于使用起来非常类似于函数调用,因此称为仿函数

    MyAdd myadd;
    int ret = myadd(10, 20);
    cout << "ret=" << ret << endl;

    //匿名对象
    cout << MyAdd()(23, 23) << endl;

}

继承

继承是面向对象三大特性之一

继承的好处:减少重复代码

语法 --- class 子类:继承方式

子类 也称为 派生类

父类 也称为 基类

class A: public B;

继承方法
  • 公共继承
  • 保护继承
  • 私有继承

继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去

父类中私有成员属性,是被编译器给隐藏了,因此是访问不到,但确实被继承下去了

使用开发工具查看对象模型:

打开工具窗口,定位到当前cpp文件的盘符,然后输入:

cl /d1 reportSingleClassLayout查看的类名 所属文件名

继承中构造和析构顺序

继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

子类和父类同名成员处理

子类对象可以直接访问到子类中同名成员

如果通过子类对象 访问到父类中同名成员,需要加作用域

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

如果想访问到父类中被隐藏的同名成员函数,需要加作用域

多继承语法

C++允许一个类继承多个类

语法:class 类:继承方式 父类1,继承方式 父类2

class Son: public Base1, public Base2

{}

多继承中如果父类出现了同名情况,子类使用时候要加作用域

C++实际开发中不建议用多继承

菱形继承
菱形继承概念

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或钻石继承

典型菱形继承案例

菱形继承问题
  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承的问题

继承之前 加上关键字 virtual 变为虚继承

Animal类称为 虚基类

//动物类
class Animal
{
public:
    int m_Age;
};
//利用虚继承,解决菱形继承的问题
//继承之前 加上关键字 virtual 变为虚继承
//Animal类称为虚基类
class Sheep: virtual public Animal{};
class Tuo : virtual public Animal{};
class SheepTuo : public Sheep, public Tuo{};

void test01()
{
    SheepTuo st;
    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 21;
    //当菱形继承,两个父类拥有相同属性,需要加以作用域区分
    cout << "st.Sheep::m_Age =" << st.Sheep::m_Age << endl;
    cout << "st.Tuo::m_Age =" << st.Tuo::m_Age << endl;
    
    //这份数据我们知道 只要有一份数据就可以,菱形继承导致数据有两份,资源浪费
    //加了虚继承,可直接输出,不需加作用域
    cout << "st.m_Age =" << st.m_Age << endl;
}

多态

多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

动态多态满足条件:

  1. 有继承关系
  2. 子类重写父类的虚函数

动态多态的使用:

父类的指针或引用,指向子类对象

class Animal
{
public:
    //虚函数
    virtual void speak()
    {
        cout << "动物在说话!" << endl;
    }
};

//猫类
class Cat : public Animal
{
public:
    //重写 函数返回值类型 函数名 参数列表 完全相同
    void speak()
    {
        cout << "小猫在说话!" << endl;
    }
};
class Dog : public Animal
{
public:
    void speak()
    {
        cout << "小狗在说话!" << endl;
    }
};
//执行说话的函数 虽然传入Cat,但是还是动物说话,因为地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段绑定,地址晚绑定
//解决方法:virtual 虚函数
void doSpeak(Animal& animal)
{
    animal.speak();
}

void test01()
{
    Cat cat;
    doSpeak(cat);
    Dog dog;
    doSpeak(dog);
}
多态原理

多态案例 - 计算机类

案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护
传统写法
//传统计算器写法
class Calculator
{
public:
    int getResult(string oper)
    {
        if (oper == "+")
        {
            return num1 + num2;
        }
        else if (oper == "-")
        {
            return num1 - num2;
        }
        else if (oper == "*")
        {
            return num1 * num2;
        }
        //如果要提供新的运算,需要修改源码
    }
    int num1;
    int num2;
};
void test01()
{
    Calculator c;
    c.num1 = 10;
    c.num2 = 10;
    cout << c.num1 << "+" << c.num2 << "=" << c.getResult("+") << endl;
    cout << c.num1 << "-" << c.num2 << "=" << c.getResult("-") << endl;
    cout << c.num1 << "*" << c.num2 << "=" << c.getResult("*") << endl;
}
多态写法
//利用多态写计算器
//多态好处:
//1. 组织结构清晰
//2. 可读性强
//3. 对于前期和后期扩展以及维护性高

//实现计算器抽象类
class AbstractCalculator
{
public:
    virtual int getResult()
    {
        return 0;
    }
    int num1;
    int num2;
};
//加法计算器类
class AddCalculator: public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 + num2;
    }
};
//乘法计算器类
class MulCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 * num2;
    }
};
//减法计算器类
class SubCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return num1 - num2;
    }
};

void test02()
{
    //多态使用条件
    //父类指针或引用指向子类对象
    AbstractCalculator* c = new AddCalculator;
    c->num1 = 10;
    c->num2 = 10;
    cout << c->num1 << "+" << c->num2 << "=" << c->getResult() << endl;
    delete c;
    SubCalculator a;
    AbstractCalculator& m = a;
    m.num1 = 20;
    m.num2 = 20;
    cout << m.num1 << "-" << m.num2 << "=" << m.getResult() << endl;
}
纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
class Base
{
public:
    virtual void func() = 0;
};
class Son: public Base
{
public:
    void func()
    {

    }
};

void test01()
{
    //抽象类无法实例化对象
    //Base b; 
    //new Base;
    //Son s; //子类必须重写父类中的纯虚函数,否则无法实例化对象
    Base* base = new Son;
    base->func();
}
多态案例2 - 制作饮品

案例描述:

制作饮品大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

class AbstractDrink
{
public:
    //煮水
    virtual void boil() = 0;
    //冲泡
    virtual void brew() = 0;
    //倒入杯中
    virtual void pour() = 0;
    //加入辅料
    virtual void addSomething() = 0;
    //制作饮品
    void makeDrink()
    {
        boil();
        brew();
        pour();
        addSomething();

    }
};
//制作咖啡
class Coffee : public AbstractDrink
{
public:
    void boil()
    {
        cout << "煮矿泉水" << endl;
    }
    void brew()
    {
        cout << "冲泡咖啡" << endl;
    }
    void pour()
    {
        cout << "倒入咖啡杯中" << endl;
    }
    void addSomething()
    {
        cout << "加糖和牛奶" << endl;
    }
};
//制作茶叶
class Tea : public AbstractDrink
{
public:
    void boil()
    {
        cout << "煮水" << endl;
    }
    void brew()
    {
        cout << "冲泡茶叶" << endl;
    }
    void pour()
    {
        cout << "倒入茶杯中" << endl;
    }
    void addSomething()
    {
        cout << "加柠檬" << endl;
    }
};
//制作函数
void doWork(AbstractDrink* abs)
{
    abs->makeDrink();
    delete abs;
}
void test01()
{
    //制作咖啡
    doWork(new Coffee);
    cout << "-----------------------------------------" << endl;
    //制作茶叶
    doWork(new Tea);
}
虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0;

class Animal
{
public:
    virtual void speak() = 0;
    Animal()
    {
        cout << "animal构造函数调用" << endl;
    }
    ~Animal()
    {
        cout << "animal析构函数调用" << endl;
    }
};
class Cat : public Animal
{
public:
    Cat(string name)
    {
        m_name = new string(name);
        cout << "cat构造函数调用" << endl;
    }
    ~Cat()
    {
        if (m_name != NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_name;
            m_name = NULL;
        }  
    }
    void speak()
    {
        cout <<*m_name<< "小猫在说话" << endl;
    }
    string* m_name;
};
void test01()
{
    Animal* cat = new Cat("Tom");
    cat->speak();
    //父类指针在析构的时候,不会调用子类中析构函数
    //导致子类如果有堆区属性,出现内存泄漏
    delete cat;
}

上面代码无法调用cat的析构函数

将父类的析构函数变为虚函数即可访问子类析构函数。

class Animal
{
public:
    //纯虚函数
    virtual void speak() = 0;
    Animal()
    {
        cout << "animal构造函数调用" << endl;
    }
    //利用虚析构可以解决 父类指针释放子类对象时不干净的问题
    /*virtual ~Animal()
    {
        cout << "animal析构函数调用" << endl;
    }*/
    //纯虚析构
    virtual ~Animal();

};
Animal::~Animal()
{
    cout << "animal纯析构函数调用" << endl;
}
class Cat : public Animal
{
public:
    Cat(string name)
    {
        m_name = new string(name);
        cout << "cat构造函数调用" << endl;
    }
    ~Cat()
    {
        if (m_name != NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_name;
            m_name = NULL;
        }
        
        
    }
    void speak()
    {
        cout <<*m_name<< "小猫在说话" << endl;
    }
    string* m_name;
};
void test01()
{
    Animal* cat = new Cat("Tom");
    cat->speak();
    delete cat;

}

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类
多态案例3 - 电脑组装

private:
    CPU* m_cpu;
    VideoCard* m_videoCard;
    Memory* m_memory;
};
class IntelCpu : public CPU
{
public:
    void calculate()
    {
        cout << "Intel的CPU正在计算。。。。。。" << endl;
    }
};
class IntelVc : public VideoCard
{
public:
    void display()
    {
        cout << "Intel显卡正在显示。。。。。。" << endl;
    }
};
class IntelMem : public Memory
{
public:
    void storage()
    {
        cout << "Intel的内存条正在存储。。。。。。" << endl;
    }
};

class LenovoCpu : public CPU
{
public:
    void calculate()
    {
        cout << "Lenovo的CPU正在计算。。。。。。" << endl;
    }
};
class LenovoVc : public VideoCard
{
public:
    void display()
    {
        cout << "Lenovo显卡正在显示。。。。。。" << endl;
    }
};
class LenovoMem : public Memory
{
public:
    void storage()
    {
        cout << "Lenovo的内存条正在存储。。。。。。" << endl;
    }
};


void test01()
{
    Computer* computer1 = new Computer(new IntelCpu, new IntelVc, new IntelMem);
    Computer* computer2 = new Computer(new LenovoCpu, new LenovoVc, new LenovoMem);
    Computer* computer3 = new Computer(new IntelCpu, new IntelVc, new LenovoMem);
    computer1->doWork();
    cout << "-------------------------------------" << endl;
    computer2->doWork();
    cout << "-------------------------------------" << endl;
    computer3->doWork();
    delete computer1;
    delete computer2;
    delete computer3;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值