友元
关键字: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++面向对象三大特性之一
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
动态多态满足条件:
- 有继承关系
- 子类重写父类的虚函数
动态多态的使用:
父类的指针或引用,指向子类对象
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;
}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
多态案例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;
}