本专栏记录C++学习过程包括C++基础以及数据结构和算法,其中第一部分计划时间一个月,主要跟着黑马视频教程,学习路线如下,不定时更新,欢迎关注。
当前章节处于:
---------第1阶段-C++基础入门
---------第2阶段实战-通讯录管理系统,
=====>第3阶段-C++核心编程,
---------第4阶段实战-基于多态的企业职工系统
---------第5阶段-C++提高编程
---------第6阶段实战-基于STL泛化编程的演讲比赛
---------第7阶段-C++实战项目机房预约管理系统
文章目录
- 一、友元
- 1.1 全局函数做友元
- 1.2 类做友元
- 1.3 全局函数做友元
- 二、运算符重载
- 2.1 加号运算符重载
- 2.2 左移运算符重载
- 2.3 递增运算符重载
- 2.4 赋值运算符重载
- 2.5 关系运算符重载
- 2.6 函数调用运算符重载
- 三、继承
- 3.1 继承概述
- 3.2 继承方式
- 3.3 继承中的对象模型
- 3.4 继承构造和析构顺序
- 3.5 继承同名成员处理方式
- 3.6 继承同名静态成员
- 3.7 多继承
- 3.8 菱形继承
- 四、多态
- 4.1 多态基本概念
- 4.2 纯虚函数和抽象类
- 4.3 虚析构和纯虚析构
一、友元
在程序里,有些私有属性也想让类外特殊的有些函数或者类进行访问,就需要用到友元的技术,目的是让一个函数或者类访问另一个类中私有成员。关键词为friend
,友元的是三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
1.1 全局函数做友元
#include <iostream>
using namespace std;
class Person {
friend void test(); // 申明友元
public:
Person() {
name = "张三";
gender = "男";
}
string name;
private:
string gender; // 私有属性
};
void test() {
Person p;
cout << "姓名:" << p.name << endl;
cout << "性别:" << p.gender << endl;
}
// 定义友元函数
int main() {
test();
system("pause");
return 0;
}
姓名:张三
性别:男
请按任意键继续. . .
1.2 类做友元
#include <iostream>
using namespace std;
class Student {
friend class Teacher; // 类做友元
public:
Student() {
name = "张三";
score = 100;
}
string name;
private:
int score;
};
class Teacher {
public:
void test(Student stu) {
cout << "学生姓名:" << stu.name << endl;
cout << "学生分数:" << stu.score << endl;
}
};
int main() {
Student stu;
Teacher teacher;
teacher.test(stu);
system("pause");
return 0;
}
学生姓名:张三
学生分数:100
请按任意键继续. . .
1.3 全局函数做友元
#include <iostream>
using namespace std;
class Student;
class Teacher;
class Teacher {
public:
Teacher();
public:
void test();
void test2();
Student* stu;
};
class Student {
friend void Teacher::test(); // 成员函数做友元
public:
Student();
public:
string name;
private:
int score;
};
Student::Student() {
name = "张三";
score = 100;
} // 写在Teacher构造函数之前
Teacher::Teacher() {
stu = new Student;
}
void Teacher::test() {
cout << "学生姓名:" << stu->name << endl;
cout << "学生分数:" << stu->score << endl;
}
void Teacher::test2() {
cout << "学生姓名:" << stu->name << endl;
//cout << "学生分数:" << stu->score << endl;
}
int main() {
Teacher teacher;
teacher.test();
system("pause");
return 0;
}
学生姓名:张三
学生分数:100
请按任意键继续. . .
二、运算符重载
对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型。需要重载的运算符为operator+
- 对于内置的数据类型的表达式的运算符是不可能改变的
- 不要滥用运算符重载
2.1 加号运算符重载
#include <iostream>
using namespace std;
class Person {
public:
int m_A = 0;
int m_B = 0;
Person() {
}
Person(int a,int b) {
m_A = a;
m_B = b;
}
// 成员函数加号运算符重载
Person operator+(const Person& p2) { // 用引用节省内存
Person temp;
temp.m_A = this->m_A + p2.m_A;
temp.m_B = this->m_B + p2.m_B;
return temp;
}
};
// 全局函数加号运算符重载
//Person operator+(const Person &p1,const Person &p2) { // 用引用节省内存
// Person temp;
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
int main() {
Person p1(1, 2);
Person p2(3, 4);
Person p3 = p1 + p2;
cout << "p3.m_A=" << p3.m_A << " p3.m_B=" << p3.m_B << endl;
system("pause");
return 0;
}
p3.m_A=4 p3.m_B=6
请按任意键继续. . .
在这里插入代码片
2.2 左移运算符重载
#include <iostream>
using namespace std;
class Person {
friend ostream& operator<<(ostream& cout,const 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,const Person &p) {
cout << "p.m_A=" << p.m_A << " p.m_B=" << p.m_B;
return cout;
}
int main() {
Person p(10, 20);
cout << p << endl;
system("pause");
return 0;
}
p.m_A=10 p.m_B=20
请按任意键继续. . .
2.3 递增运算符重载
#include <iostream>
using namespace std;
class MyInt {
friend ostream& operator<<(ostream& cout, const MyInt& p);
public:
MyInt(){}
MyInt(int a) {
num = a;
}
// 重载++前置运算
MyInt& operator++() {
num++;
return *this;
}
// 重载++后置运算
MyInt operator++(int) { // int 占位符表示后置++
MyInt temp = *this; // 存储当前num
num++;
return temp; // 返回局部变量,只能以值的方式返回
}
private:
int num;
};
ostream& operator<<(ostream& cout, const MyInt& p) {
cout << "num=" << p.num;
return cout;
}
// 测试前置++
void test1() {
MyInt a(10);
cout << ++a << endl;
cout << a << endl;
}
// 测试后置++
void test2() {
MyInt a(10);
cout << a++ << endl;
cout << a << endl;
}
int main() {
cout << "测试前置++:" << endl;
test1();
cout << "测试后置++:" << endl;
test2();
system("pause");
return 0;
}
测试前置++:
num=11
num=11
测试后置++:
num=10
num=11
请按任意键继续. . .
2.4 赋值运算符重载
#include <iostream>
using namespace std;
class Person {
friend void test();
public:
Person(){}
Person(int a) {
age = new int(a); // 在堆区存放a
}
// 析构函数
~Person() {
if (age != NULL) {
delete age;
age = NULL;
}
}
// 重构赋值运算符
Person& operator=(Person &p) {
age = new int(*p.age);
return *this;
}
private:
int *age;
};
void test() {
Person p1(10);
Person p2(20);
p1 = p2; //不报错,深拷贝重新申请一片地址
cout << "p1=" << *p1.age << endl;
}
int main() {
test();
system("pause");
return 0;
}
p1=20
请按任意键继续. . .
2.5 关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
Person(string n,int a) {
name = n;
age = a;
}
bool operator==(Person p) {
if ((name == p.name) && (age == p.age)) {
return true;
}
else {
return false;
}
}
};
void test() {
Person p1("A", 12);
Person p2("A", 12);
if (p1 == p2) {
cout << "p1和p2相等!" << endl;
}
else {
cout << "p1和p2不相等" << endl;
}
}
int main() {
test();
system("pause");
return 0;
}
p1和p2相等!
请按任意键继续. . .
2.6 函数调用运算符重载
- 函数调用运算符
()
也可以重载 - 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include <iostream>
using namespace std;
class MyPrint {
public:
// 重载函数调用运算符
void operator()(string test) {
cout << test << endl;
}
};
int main() {
MyPrint myprint;
myprint("Hello World");
system("pause");
return 0;
}
Hello World
请按任意键继续. . .
三、继承
3.1 继承概述
继承的好处:可以减少重复的代码。
语法:class 子类 :继承方式 父类
子类也称为派生类,父类也称为基类。
子类中的成员,包括两大部分:一类是从基类继承过来的(表现共性),一类是自己增加的成员(体现个形)
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "吃东西!" << endl;
}
void play() {
cout << "出去玩!" << endl;
}
};
class Cat :public Animal { // 继承父类
public:
void drink() {
cout << "小猫喝水!" << endl;
}
};
int main() {
Cat cat;
cat.eat();
cat.play();
cat.drink();
system("pause");
return 0;
}
吃东西!
出去玩!
小猫喝水!
请按任意键继续. . .
3.2 继承方式
继承方式包括三种:
- 公共继承
- 保护继承
- 私有继承
继承关系如下:
#include <iostream>
using namespace std;
// 父类
class Base1 {
public :
int a;
protected:
int b;
private:
int c;
};
// 子类 继承方式为public
class Son1 : public Base1 {
public:
void func() {
a = 100;
b = 100;
//c = 100;// 报错,父类private不可访问
}
};
// 继承方式为protected
class Son2 : protected Base1 {
public:
void func() {
a = 100;
b = 100;
//c = 100;// 报错,父类private不可访问
}
};
// 继承方式为private
class Son3 : private Base1 {
public:
void func() {
a = 100;
b = 100;
//c = 100;// 报错,父类private不可访问
}
};
int main() {
Son2 s2;
//s2.a = 100; // 报错
//s2.b = 100; // 报错,子类继承为protected,类外不可访问
Son3 s3;
//s3.a = 100; // 报错
//s3.b = 100; // 报错,子类继承为private,类外不可访问
system("pause");
return 0;
}
3.3 继承中的对象模型
从父类继承过来的成员,哪些属于子类对象中?
#include <iostream>
using namespace std;
class Base1 {
public:
int a;
protected:
int b;
private:
int c;
};
// 子类 继承方式为public
class Son1 : public Base1 {
public:
int s;
};
int main() {
Son1 s1;
cout << "sizeof s1=" << sizeof(s1) << endl; // 16 继承了父类中的所有非静态成员属性,但是private被编译器隐藏无法访问
system("pause");
return 0;
}
sizeof s1=16
请按任意键继续. . .
3.4 继承构造和析构顺序
#include <iostream>
using namespace std;
class Base{
public:
int a;
Base() {
cout << "Base的构造函数" << endl;
}
~Base() {
cout << "Base的析构函数" << endl;
}
protected:
int b;
private:
int c;
};
class Son1 : public Base {
public:
int s;
Son1() {
cout << "Son1的构造函数" << endl;
}
~Son1() {
cout << "Son1的析构函数" << endl;
}
};
void test() {
Son1 s1;
}
int main() {
test();
system("pause");
return 0;
}
Base的构造函数
Son1的构造函数
Son1的析构函数
Base的析构函数
请按任意键继续. . .
现有父类构造再有子类构造,然后子类析构,最后父类析构
3.5 继承同名成员处理方式
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
#include <iostream>
using namespace std;
class Base {
public:
int a;
Base() {
a = 100;
}
};
class Son1 : public Base {
public:
int a;
Son1() {
a = 200;
}
};
void test() {
Son1 s1;
cout << "调用Son中的a:" << s1.a << endl;
cout << "调用Base中的a:" << s1.Base::a << endl;
}
int main() {
test();
system("pause");
return 0;
}
调用Son中的a:200
调用Base中的a:100
请按任意键继续. . .
成员函数也是一样,当继承了相同名称的同名函数时,编译器会将父类的同名函数隐藏,想要调用父类的同名函数需要添加作用域。
3.6 继承同名静态成员
#include <iostream>
using namespace std;
class Base {
public:
static int a;
};
int Base::a = 100;
class Son1 : public Base {
public:
static int a;
};
int Son1::a = 200;
void test() {
Son1 s1;
// 实例化调用
cout << "调用Son中的a:" << s1.a << endl;
cout << "调用Base中的a:" << s1.Base::a << endl;
// 类名调用
cout << "调用Son中的a:" << Son1::a << endl;
cout << "调用Base中的a:" << Son1::Base::a << endl;
}
int main() {
test();
system("pause");
return 0;
}
调用Son中的a:200
调用Base中的a:100
调用Son中的a:200
调用Base中的a:100
请按任意键继续. . .
静态成员函数也是同理
3.7 多继承
语法: class 子类:继承方式 父类1 ,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
实际开发中不建议使用多继承
#include <iostream>
using namespace std;
class Base1 {
public:
Base1() {
a = 100;
}
int a;
};
class Base2 {
public:
Base2() {
a = 200;
}
int a;
};
class Son1 : public Base1,public Base2 {
public:
int a;
Son1() {
a = 300;
}
};
int main() {
Son1 s1;
cout << "Son1 中的a:" <<s1.a<< endl;
cout << "Base1 中的a:" <<s1.Base1::a<< endl;
cout << "Base2 中的a:" <<s1.Base2::a << endl;
system("pause");
return 0;
}
Son1 中的a:300
Base1 中的a:100
Base2 中的a:200
请按任意键继续. . .
3.8 菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承这两个派生类
这种继承被称为菱形继承或者钻石继承
#include <iostream>
using namespace std;
// 基类
class Animal {
public:
int age;
};
class Sheep :virtual public Animal { // 虚基类
};
class Tuo :virtual public Animal {
};
class SheepTuo:public Sheep,public Tuo{}; // 菱形继承
void test() {
SheepTuo t1;
t1.Sheep::age = 100;
t1.Tuo::age = 200;
cout << "t1.Sheep::age " << t1.Sheep::age << endl;
cout << "t1.Tuo::age " << t1.Tuo::age << endl;
}
int main() {
test();
system("pause");
return 0;
}
t1.Sheep::age 200
t1.Tuo::age 200
请按任意键继续. . .
四、多态
4.1 多态基本概念
多态是C++面向对象三大特性之一一
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定——编译阶段确定函数地址
- 动态多态的函数地址晚绑定——运行阶段确定函数地址
#include <iostream>
using namespace std;
class Animal {
public:
// 实现晚绑定
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
class Cat :public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
// 必须是引用
void doSpeak(Animal &animal) {
animal.speak();
}
int main() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
system("pause");
return 0;
}
小猫在说话
小狗在说话
请按任意键继续. . .
多态需要满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态的使用条件
父类指针或应用指向子类对象
实战案例
#include <iostream>
using namespace std;
class Caulater {
public:
int num1;
int num2;
virtual int getresult() {
// 空实现
return 0;
}
};
// 加法
class CaulaterAdd:public Caulater {
public:
int getresult() {
return num1 + num2;
}
};
// 减法
class CaulaterJian :public Caulater {
public:
int getresult() {
return num1 - num2;
}
};
// 乘法
class CaulaterChen :public Caulater {
public:
int getresult() {
return num1 * num2;
}
};
void test(Caulater &c) {
CaulaterAdd ad;
ad.num1 = 10;
ad.num2 = 10;
cout << ad.getresult() << endl;
}
int main() {
Caulater c;
test(c);
system("pause");
return 0;
}
20
请按任意键继续. . .
4.2 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
。当类中有纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;
class Base {
public:
// 纯虚函数,该类叫抽象类
virtual void func() = 0;
};
class Son:public Base {
public:
void func() {
cout << "调用func函数!" << endl;
}
};
int main() {
//Base b;// 抽象类无法实例化对象
Base * base = new Son;
Son s; // 重写纯虚函数后可以实例化对象,否则子类也是抽象类不能实例化对象
base->func();
system("pause");
return 0;
}
调用func函数!
请按任意键继续. . .
实战案例
要求:利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。
#include <iostream>
using namespace std;
// 饮品抽象类
class Drink {
public:
virtual void func() = 0;
};
// 咖啡子类
class Coffee :public Drink {
public:
void func() {
cout << "制作咖啡!" << endl;
}
};
// 茶叶子类
class Tea :public Drink {
public:
void func() {
cout << "制作茶叶!" << endl;
}
};
int main() {
Drink* c = new Coffee;
c->func();
Drink* t = new Tea;
t->func();
system("pause");
return 0;
}
制作咖啡!
制作茶叶!
请按任意键继续. . .
4.3 虚析构和纯虚析构
多态中,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,需要将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构的区别
如果是纯虚析构,该类属于抽象类,无法实例化对象
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base的构造函数!" << endl;
}
// 虚析构函数
//virtual ~Base() {
// cout << "Base的析构函数" << endl;
// if (name != NULL) {
// delete name;
// name = NULL;
// }
//}
// 纯虚析构函数
virtual ~Base() = 0;
string* name;
};
Base::~Base(){
cout << "Base的纯虚析构函数" << endl;
if (name != NULL) {
delete name;
name = NULL;
}
}
class Son :public Base {
public:
Son(){
cout << "Son的构造函数!" << endl;
}
~Son() {
cout << "Son的析构函数!" << endl;
}
};
void test() {
Base* b = new Son;
delete b;
}
int main() {
test();
system("pause");
return 0;
}
Base的构造函数!
Son的构造函数!
Son的析构函数!
Base的纯虚析构函数
请按任意键继续. . .
实战案例
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例救Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函敌,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include <iostream>
using namespace std;
// CPU抽象类
class CPU {
public:
// 纯虚函数
virtual void caculate() = 0;
};
// 显卡抽象类
class VideoCard {
public:
// 纯虚函数
virtual void display() = 0;
};
// 内存条抽象类
class Memory {
public:
// 纯虚函数
virtual void storage() = 0;
};
// Inter的零件
class InterCPU :public CPU {
public:
void caculate() {
cout << "Inter的CPU开始计算!" << endl;
}
};
class InterVideoCard :public VideoCard {
public:
void display() {
cout << "Inter的显卡开始显示!" << endl;
}
};
class InterMemory :public Memory {
public:
void storage() {
cout << "Inter的存储条开始存储!" << endl;
}
};
// Lenovo的零件
class LenovoCPU :public CPU {
public:
void caculate() {
cout << "Lenovo的CPU开始计算!" << endl;
}
};
class LenovoVideoCard :public VideoCard {
public:
void display() {
cout << "Lenovo的显卡开始显示!" << endl;
}
};
class LenovoMemory :public Memory {
public:
void storage() {
cout << "Lenovo的存储条开始存储!" << endl;
}
};
// 电脑类
class Computer {
public:
Computer(CPU* c, VideoCard* v, Memory* m) {
mc = c;
mv = v;
mm = m;
}
void dowork() {
mc->caculate();
mv->display();
mm->storage();
}
virtual ~Computer() {
//cout << "电脑的析构函数!" << endl;
if (mc != NULL) {
delete mc;
mc = NULL;
}
if (mv != NULL) {
delete mv;
mv = NULL;
}
if (mm != NULL) {
delete mm;
mm = NULL;
}
}
private:
CPU * mc;
VideoCard * mv;
Memory* mm;
};
void start() {
// 创建三台电脑
cout << "第一台电脑:" << endl;
Computer c1(new InterCPU, new InterVideoCard, new InterMemory);
c1.dowork();
cout << "第二台电脑:" << endl;
Computer c2(new LenovoCPU, new LenovoVideoCard, new InterMemory);
c2.dowork();
cout << "第三台电脑:" << endl;
Computer c3(new InterCPU, new InterVideoCard, new LenovoMemory);
c3.dowork();
}
int main() {
start();
system("pause");
return 0;
}
第一台电脑:
Inter的CPU开始计算!
Inter的显卡开始显示!
Inter的存储条开始存储!
第二台电脑:
Lenovo的CPU开始计算!
Lenovo的显卡开始显示!
Inter的存储条开始存储!
第三台电脑:
Inter的CPU开始计算!
Inter的显卡开始显示!
Lenovo的存储条开始存储!
请按任意键继续. . .