- C++面向对象的三大特性为:封装、继承和多态
- 万事万物皆为对象,对象上有其属性和行为
- 具有相同性质的对象可以抽象成一个类
一、封装
1.1 封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 类在设计的时候,可以把属性和行为放在不同的权限下,加以控制
名称 | 权限 | 访问权限 | 备注 |
---|---|---|---|
public | 公共权限 | 成员类内可以访问,类外也可以访问 | |
protected | 保护权限 | 成员类内可以访问,类外不可以访问 | 儿子可访问父亲中的保护内容 |
private | 私有权限 | 成员类内可以访问,类外不可以访问 | 儿子不可访问父亲中的保护内容 |
继承的时候用
eg.设计一个类,求出圆的周长:
// 公式:2*Pi*半径
#include <iostream>
using namespace std;
#define PI 3.14
class Circle {
// 访问权限
// 公共权限
public:
// 属性
// 半径
int m_r;
// 行为
// 获取圆的周长
double calculacteZC() {
return 2 * PI * m_r;
}
};
int main() {
// 通过圆类,创建具体的圆(对象)
// 实例化:通过一个类创建一个对象的过程
Circle cl;
// 给圆对象的属性进行赋值
cl.m_r = 10;
cout << "圆的周长为:" << cl.calculacteZC() << endl;
system("pause");
return 0;
}
e.g.设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
#include <iostream>
#include <string>
using namespace std;
class Student {
// 定义权限
public:
string m_name;
string m_ID;
void setName(string name) {
m_name = name;
}
void setID(string ID) {
m_ID = ID;
}
void showStu() {
cout << "学生的姓名为:" << m_name << endl;
cout << "学生的学号为:" << m_ID << endl;
}
};
int main() {
Student stu;
stu.setName("张三");
stu.setID("2022201293");
stu.showStu();
return 0;
}
- 类中的属性和行为都称为成员
- 属性:成员属性 成员变量
- 行为:成员函数 成员方法
1.2 struct和class的区别
- 唯一的区别在于默认的访问权限不同
| struct | class | | :—-: | :—: | | 默认公共 | 默认私有 |
1.3 成员属性设置为私有
- 优点1:可以自己控制读写权限
设置
setValue()
和getValue
函数控制读写权限; - 优点2:对于写权限,我们可以检测数据的有效性
```Cpp
#include
#include using namespace std;
class Person { private: string m_Name; int m_Age; int m_ID = 2022201;
public: // 可读可写 void setName(string name) { m_Name = name; } string getName() { return m_Name; }
// 只写
int setAge(int age) {
// 验证数据的有效性
if (age < 0 || age > 150) {
cout << ((age < 0) ? "输入的年龄为负数,请重新输入:" : "输入的年龄大于150,请重新输入:") << endl;
return -1;
}
m_Age = age;
return 1;
}
// 只读
int getID() {
return m_ID;
} };
int main() { Person p1;
string name;
cout << "请输入此人姓名:" << endl;
cin >> name;
p1.setName(name);
cout << "此人名字为:" << p1.getName() << endl;
int ret = -1;
int age = 0;
cout << "请输入此人年龄:" << endl;
while (ret < 0) {
cin >> age;
ret = p1.setAge(age);
}
cout << "此人的id为:" << p1.getID() << endl;
system("pause");
return 0; } ```
e.g.1、设计一个长方体类 e.g.2、点和圆的关系
- 在类中可以让另一个类作为本来的类的成员
- 类和主函数的分文件编写
二、对象的初始化和清理
- 每个对象都有初始设置以及对象销毁前的清理数据的设置
2.1 构造函数和析构函数
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 析构函数:主要作用在于创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动调用;
- 构造函数语法:
类名(){}
- 构造函数没有返回值也不用写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
- 构造函数语法:
- 析构函数:主要作用在于对象的销毁前系统自动调用,执行一些清理工作。
- 析构函数语法:
~类名(){}
- 析构函数没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会调用一次
2.2 构造函数的分类以及调用
- 构造函数分类:
1. 按照参数分类:有参构造和无参构造,无参又称为默认函数构造
2. 按照类型分类:普通构造和拷贝构造 - 构造函数的调用 1. 括号法 2. 显式法 3. 隐式转换法
- 析构函数语法:
#include<iostream>
using namespace std;
class Person{
public:
// 无参构造,也称默认构造函数
Person(){
cout << "使用默认构造函数" << endl;
}
// 有参构造函数
Person(int a){
age = a;
cout << "使用有参构造函数" << endl;
}
// 拷贝构造函数,使用const修饰变量,一定要使用引用传参
Person(const Person &p){
age = p.age;
cout << "使用拷贝构造函数" << endl;
}
// 析构函数,无参,不可重载
~Person(){
cout << "使用析构函数" << endl;
}
private:
int age = 18;
};
void test01(){
// 1. 括号法调用
Person p1;
Person p2(10);
Person p3(p2);
// 注意:使用默认构造函数不能使用括号,否则会被认为是一个函数声明
// Person p1(); 错误的
}
void test02(){
//2. 显式法调用
Person p1 = Person(10);
Person p2 = Person(p2);
// Person(10)单独写叫做匿名对象,当前行结束之后就马上析构
}
void test03(){
// 3. 隐式转换法
Person p1 = 10; // Person p1 = Person(10);
Person p2 = p1;
// 注意:不能利用拷贝构造函数初始化匿名对象,否则编译器认为是对象声明
// Person(p1) === Person p1
}
int main(){
test01();
test02();
test03();
system("pause");
return 0;
}
⚠注意事项:
- 调用默认构造函数时,不要加(),否则编译器认为是函数声明
- 不能利用拷贝构造函数初始化匿名对象,否则编译器认为是对象声明
2.3 拷贝构造函数的调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 值方式返回局部对象
```Cpp
#include
using namespace std;
class Person { public: Person() { cout « “调用了默认构造函数” « endl; }
Person(int a) {
age = a;
cout << "调用了有参构造函数" << endl;
}
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
Person(const Person &p1) {
age = p1.age;
cout << "调用了拷贝构造函数" << endl;
}
~Person() {
cout << "调用了析构函数" << endl;
}
private: int age = 18; };
void doWork01(Person p) { return; }
Person doWork02() { Person p; // 3. 值方式传回局部对象调用了拷贝构造函数 return p; }
void test() { Person p = doWork02(); }
int main() { // Person p(10); // 2. 传递值时调用了拷贝构造函数 // doWork01(p); test();
return 0; } #include <iostream> using namespace std;
class Person { public: Person() { cout « “调用了默认构造函数” « endl; }
Person(int a) {
age = a;
cout << "调用了有参构造函数" << endl;
}
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
Person(const Person &p1) {
age = p1.age;
cout << "调用了拷贝构造函数" << endl;
}
~Person() {
cout << "调用了析构函数" << endl;
}
private: int age = 18; };
void doWork01(Person p) { return; }
Person doWork02() { Person p; // 3. 返回值时调用了拷贝构造函数 // 实际调用时根本没有使用拷贝构造函数,不知道为啥??? return p; }
void test() { Person p = doWork02(); }
int main() { // Person p(10); // 2. 传递值时调用了拷贝构造函数 // doWork01(p); test();
return 0; } ``` ##### 2.4 构造函数调用规则 - 默认情况,c++编译器至少给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝 - 构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造函数,但是会有默认拷贝构造函数
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数 ##### 2.5 深拷贝与浅拷贝 - 浅拷贝:简答的赋值拷贝操作 - 深拷贝:在堆区重新申请空间,进行拷贝操作 ```Cpp #include <iostream> using namespace std;
class Person { public: Person() { cout « “Person 默认构造函数” « endl; } Person(int age, int height) { m_age = age; m_Height = new int(height); cout « “Person 有参构造函数” « endl; } Person(const Person &p) { m_age = p.m_age; // m_Height = p.m_Height; // 编译器自动实现的浅拷贝 m_Height = new int(*p.m_Height); // 深拷贝 cout « “Person 拷贝构造函数调用” « endl; }
~Person() {
// 将堆区开辟的数据释放干净
if (m_Height != NULL) {
delete m_Height;
m_Height = NULL;
}
cout << "调用了析构函数" << endl;
}
int m_age = 0;
int *m_Height; };
void test() { // 实际上在使用浅拷贝时,编译器只使用了一次析构函数而且没有报错,不知道为啥??? // cout « “this is a test.” « endl; Person p1(10, 180); cout « “p1的年龄为:” « p1.m_age « ” “ « “p1的身高为:” « *p1.m_Height « endl; Person p2(p1); cout « “p2的年龄为:” « p2.m_age « ” “ « “p2的身高为:” « *p2.m_Height « endl; }
int main() { test(); system(“pause”); return 0; }
**疑问**:不知为何,在使用浅拷贝时,编译器只调用了一次析构函数,而在深拷贝时则调用了两次析构函数,怀疑时编译器对代码的优化。
##### 2.6 初始化列表
- 语法:构造函数():变量1(值),变量2(值)...{}
```Cpp
#include <iostream>
using namespace std;
class Person {
public:
// 传统构造方法
// Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
// }
// 初始化列表构造方法
Person(int a, int b, int c) :
m_A(a), m_B(b), m_C(c) {
}
int getA() {
return m_A;
}
int getB() {
return m_B;
}
int getC() {
return m_C;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(10, 20, 30);
cout << p.getA() << endl
<< p.getB() << endl
<< p.getC() << endl;
}
三、C++对象模型和this指针
3.1 类对象作为类成员
- c++类中的成员可以时另一个类的成员,我们称该成员为对象成员
```Cpp
#include
#include using namespace std;
class Phone { public: Phone(string PName) { m_PName = PName; cout « “调用了Phone的构造函数” « endl; } ~Phone() { cout « “调用了Phone的析构函数” « endl; }
string m_PName; };
class Person { public: Person(string name, string PName) : m_Name(name), PName(PName) { cout « “调用了Person的构造函数” « endl; }
~Person() {
cout << "调用了Person的析构函数" << endl;
}
string m_Name;
Phone PName; };
void test() { Person p(“张三”, “iPhone 15 ProMax”); cout « “p的姓名为:” « p.m_Name « “p的手机为:” « p.PName.m_PName « endl; }
int main() { test(); system(“pause”); return 0; }
输出为:
```Cpp
调用了Phone的构造函数
调用了Person的构造函数
p的姓名为:张三p的手机为:iPhone 15 ProMax
调用了Person的析构函数
调用了Phone的析构函数
Press any key to continue . . .
- 由输出结果可知,创建类的时候先创建成员对象的类,再创建本身,析构的顺序相反
3.2 静态成员
- 静态成员就是在成员变量和成员函数前加上
static
关键字 - 静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存,在全局区
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能防问静态成员变量
3.3 成员变量和成员函数分别存储
- 空对象占用一个字节
- 非静态成员变量:属于类的对象上
- 静态成员变量:不属于类的对象上
- 非静态成员函数/静态成员函数:不属于类的对象上
3.4 this指针概念
- this指针指向被调用的成员函数所属的对象
- this指针时隐含在每一个非静态成员函数中的一种指针,不需要定义,直接使用即可
- 用途:
- 当形参和成员变量同名时,可用this指针区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this
```Cpp #includeusing namespace std;
class Person{
public:
// explicit 抑制隐式转换,一般用于只有一个参数的构造函数
explicit Person(int age){
// 1. 此处的this指针可以解决名称的冲突
this->age = age;
}
// 使用引用方式返回,否则将调用拷贝构造函数构造新的类
Person& personAddAge(Person &p){
this->age += p.age;
// 返回本身
return *this;
}
int age; };
void test01(){
Person p(10);
cout « “p的年龄为:” « p.age « endl;
}
void test02(){
Person p1(10);
Person p2(11);
// 链式编程的思想
p1.personAddAge(p2).personAddAge(p2);
cout<<p1.age<<endl; }
int main(){
// test01();
test02();
system(“pause”);
return 0;
}
- 返回是一个值,创建新的对象;
- 返回引用则返回本身
##### 3.5 空指针访问成员函数
- 空指针也可以访问成员函数,但是不能访问含有成员变量的函数
```Cpp
#include <iostream>
using namespace std;
class Person {
public:
void showClass() {
cout << "this ia a Person class." << endl;
}
void showAge() {
// 防止空指针报错
if (this == NULL) {
cout << "当前指针为空" << endl;
return;
}
cout << m_Age << endl;
}
int m_Age;
};
void test() {
Person *p = NULL;
p->showClass();
p->showAge();
}
int main() {
test();
return 0;
}
3.6 const修饰成员函数
- 常函数:
- 成员函数后加const后我们称之为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
- 常对象
- 声明对象前加const称之为常对象
- 常对象只能调用常函数
#include <iostream>
using namespace std;
class Person {
public:
// this 指针的本质是 指针常量,指针的指向是不可以修改的
// Person * const this;
// 在成员函数后边加const,修饰的时this指向,让指针指向的值也不可以修改
void showPerson() const {
// this = NULL; // 错误的
// this->m_A = 10; // 加上const之后值不可以修改
this->m_B = 10; // 加上mutable关键字就可以修改了
}
void func() {
m_A = 21;
}
int m_A = 10;
mutable int m_B = 5;
};
void test() {
Person p;
p.showPerson();
}
void test02() {
const Person p1; // 常对象
// p.m_A = 10; // 不可修改
p1.m_B = 100; // mutable修饰的变量可以修改
// 常对象只能调用常函数
p1.showPerson();
// p1.func(); // 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main() {
test();
test02();
return 0;
}
四、友元
- 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
- 目的:让一个函数或者类访问另一个类中私有成员
- 关键字:
friend
- 友元的三种实现:
- 全局函数作友元
- 类作友元
- 成员函数作友元
4.1 全局函数作友元
#include <iostream>
#include <string>
using namespace std;
class Building {
// 全局变量作友元
friend void goodFriend(Building *b);
public:
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodFriend(Building *b) {
cout << "好朋友正在参观:" << b->m_SittingRoom << endl;
cout << "好朋友正在参观:" << b->m_BedRoom << endl;
}
void test() {
Building b;
goodFriend(&b);
}
int main() {
system("chcp 65001");
test();
return 0;
}
4.2 类作友元
#include <iostream>
#include <string>
using namespace std;
class Building {
friend class goodFriend;
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
};
// 函数的类外实现
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
class goodFriend {
public:
goodFriend();
void visit();
Building *building;
};
// 函数的类外实现
goodFriend::goodFriend() {
building = new Building;
}
void goodFriend::visit() {
cout << "visit 正在访问:" << building->m_SittingRoom << endl;
cout << "visit 正在访问:" << building->m_BedRoom << endl;
}
void test() {
goodFriend gf;
gf.visit();
}
int main() {
system("chcp 65001");
test();
return 0;
}
4.3 成员函数作友元
#include <iostream>
#include <string>
using namespace std;
// 先声明一下Building类,不会报错
class Building;
class goodFriend {
public:
goodFriend();
void visit01();
void visit02();
private:
Building *building;
};
class Building {
// visit01作友元
friend void goodFriend::visit01();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
goodFriend::goodFriend() {
building = new Building;
}
// 成员函数作友元
void goodFriend::visit01() {
cout << "visit01 正在访问:" << building->m_SittingRoom << endl;
cout << "visit01 正在访问:" << building->m_BedRoom << endl;
}
// 成员函数不作友元
void goodFriend::visit02() {
cout << "visit02 正在访问:" << building->m_SittingRoom << endl;
}
void test() {
goodFriend gf;
gf.visit01();
gf.visit02();
}
int main() {
system("chcp 65001");
test();
return 0;
}
五、运算符重载
- 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
- 对于内置的数据类型,编译器知道如何进行运算
5.1 加号的运算符重载
- 重载运算符的方式
- 成员函数重载运算符
- 全局函数重载运算符
- 重载运算符 也可以进行函数重载
```Cpp
#include
using namespace std;
class Person {
public:
int m_A;
int m_B;
Person() {
}
Person(int a, int b) {
m_A = a;
m_B = b;
}
// 成员函数重载加号运算符
Person operator+(Person &p) {
Person temp;
temp.m_A = p.m_A + this->m_A;
temp.m_B = p.m_B + this->m_B;
return temp;
}
// 运算符重载可以发生函数重载
Person operator+(int num) {
Person temp;
temp.m_A = this->m_A + num;
temp.m_B = this->m_B + num;
return temp;
} };
// 全局函数进行运算符重载
// 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;
//}
void test() {
Person p1(10, 10);
Person p2(10, 10);
int num = 10;
// 成员函数重载调用的本质为 p3 = p1.operator+(p2) Person p3 = p1 + p2;
Person p4 = p1 + 100;
// 全局函数重载调用的本质为:p5 = operator+(p1, p2)
// Person p5 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
cout << "p4.m_A = " << p4.m_A << endl;
cout << "p4.m_B = " << p4.m_B << endl;
// cout << "p5.m_A = " << p5.m_A << endl;
// cout << "p5.m_B = " << p5.m_B << endl;}
int main() {
test();
return 0;
}
- 对于内置的数据类型的表达式的运算符是不可能改变的
- 不要滥用运算符重载
##### 5.2 左移运算符重载
- 重载左移运算符配合友元可以实现输出自定义数据类型
- 通常不用成员函数重载`<<`运算符,会使`cout`在右侧
```Cpp
#include <iostream>
using namespace std;
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) {
cout << "m_A = " << p.m_A << " " << "m_B = " << p.m_B;
// 返回cout,保证可以继续输出(链式编程)
return cout;
};
void test() {
Person p(10, 10);
cout << p << endl
<< "Test Successfully!" << endl;
}
int main() {
test();
return 0;
}
如果使用成员函数重载:
#include <iostream>
using namespace std;
class Person {
// 重载函数作友元
friend ostream &operator<<(ostream &cout, Person &p);
public:
Person(int a, int b) :
m_A(a), m_B(b) {
} ostream &operator<<(ostream &cout) {
cout << "m_A = " << this->m_A << " " << "m_B = " << this->m_B;
// 返回cout,保证可以继续输出(链式编程)
return cout;
}
private:
int m_A;
int m_B;
};
void test() {
Person p(10, 10);
p << cout; // cout在右边
}
int main() {
test();
return 0;
}
5.3 递增运算符重载
#include <iostream>
using namespace std;
class MyInteger {
friend ostream &operator<<(ostream &cout, MyInteger num);
public:
MyInteger() {
m_num = 0;
}
// 重载前置++运算符,返回引用
MyInteger &operator++() {
m_num++;
return *this;
}
// 重载后置++运算符,返回值,使用int占位参数区分前置++和后置++
// 返回值是因为返回的temp是一个临时参数,用后即销毁
MyInteger operator++(int) {
MyInteger temp = *this;
m_num++;
return temp;
}
private:
int m_num;
};
ostream &operator<<(ostream &cout, MyInteger num) {
cout << "MyInteger : " << num.m_num;
return cout;
}
void test() {
MyInteger num;
cout << num++ << endl;
cout << ++num << endl;
}
int main() {
test();
return 0;
}
- 重载前置 ++ 运算符返回引用,重载后置 ++ 运算符返回值
- 重载 – 运算符同样操作
5.4 赋值运算符重载
- c++编译器至少给一个类添加4个函数
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数
- 赋值运算符
operator=
,对属性进行拷贝 ```Cpp #includeusing namespace std;
class Person {
public:
explicit Person(int age) {
m_Age = new int(age);
}
// 浅拷贝会导致堆释放时出现重复释放的问题,所以重写赋值运算符
Person &operator=(Person &p) {
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
~Person() {
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
}
public:
int *m_Age;
};
void test() {
Person p1(10);
Person p2(18);
p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl; }
int main() {
system(“chcp 65001”);
test();
system(“pause”);
return 0;
}
##### 5.5 关系运算符重载
```Cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(string name, int age) {
m_Name = name;
m_Age = age;
}
// 重载==
bool operator==(Person &p) {
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {
return true;
}
return false;
}
// 重载!=
bool operator!=(Person &p) {
if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) {
return true;
}
return false;
}
private:
string m_Name;
int m_Age;
};
void test() {
Person p1("张三", 19);
Person p2("张三", 19);
Person p3("李四", 19);
cout << ((p1 == p2) ? "相等" : "不相等") << endl;
cout << ((p1 == p3) ? "相等" : "不相等") << endl;
}
int main() {
system("chcp 65001");
test();
return 0;
}
5.6 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此被称为仿函数
- 仿函数没有固定写法,非常灵活
```Cpp
#include
using namespace std;
class MyPrint {
public:
void operator()(string text) {
cout « text « endl;
}
};
class MyAdd {
public:
int operator()(int a, int b) {
return a + b;
}
};
void test() {
MyPrint print;
// 仿函数调用
print(“Hello, world!”);
MyAdd add;
// 仿函数调用
int ret = add(10, 15);
cout « ret « endl;
// 匿名函数对象
cout << MyAdd()(10, 20) << endl; }
int main() {
test();
return 0;
}
#### 六、继承
- 继承的好处:减少代码量
##### 6.1 继承的基础语法
- 基础语法:`class 子类:继承方式 父类`
- 父类又称基类
- 子类又称派生类
```Cpp
#include <iostream>
using namespace std;
class basePage {
public:
void Top() {
cout << "首页 注册 登录" << endl;
}
void Left() {
cout << "Python C++ JAVA" << endl;
}
void Bottom() {
cout << "联系我们 相关网页" << endl;
cout << "----------------" << endl;
}
};
class Python : public basePage {
public:
void content() {
cout << "这是Python课程" << endl;
}
};
class JAVA : public basePage {
public:
void content() {
cout << "这是JAVA课程" << endl;
}
};
class Cpp : public basePage {
public:
void content() {
cout << "这是Cpp课程" << endl;
}
};
void test() {
JAVA ja;
ja.Top();
ja.Left();
ja.content();
ja.Bottom();
Cpp c;
c.Top();
c.Left();
c.content();
c.Bottom();
Python py;
py.Top();
py.Left();
py.content();
py.Bottom();
}
int main() {
test();
return 0;
}
6.2 继承方式
- 公共继承
- 保护继承
- 私有继承 三种继承方式的区别:
| 继承方式/权限 | public | protected | private | | :———: | :——-: | :——-: | :—–: | | 公共继承 | public | protected | unavail | | 保护继承 | protected | protected | unavail | | 私有继承 | private | private | unavail |
- 保护权限子类可以访问,类外不可以访问
- 私有权限子类不可以访问,类外也不可以访问
6.3 继承中的对象模型
- 父类中所有非静态成员属性都会被子类继承下去
- 父类中私有成员属性是被编译器隐藏了,所以访问不到,但是确实是被继承下去了
- 利用vs开发人员命令提示工具查看对象模型
切换到相应目录下:cl /dl reportSingleClassLayout类名 文件名
6.4 构造和析构顺序
- 子类继承父类后,当创建子类对象,也会调用父类的构造函数
- 继承中的构造顺序:先父后子
- 继承中的析构顺序:先子后父
6.5 同名成员处理
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
- 当子类与父类拥有同名的成员函数,子类会隐藏掉父类中所有同名的成员函数,想要访问父类的成员函数需要加作用域
6.6 继承同名静态成员处理方式
- 静态成员和非静态成员处理方式相同
-
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
- 当子类与父类拥有同名的成员函数,子类会隐藏掉父类中所有同名的成员函数,想要访问父类的成员函数需要加作用域
```Cpp
#include
using namespace std;
-
class Base {
public:
static void func() {
cout « “Base - static func()” « endl;
}
// 静态变量,类内创建,类外赋值
static int m_A;
};
// 静态变量,类内创建,类外赋值 int Base::m_A = 100;
class Son : public Base {
public:
static void func() {
cout « “Son - static func()” « endl;
}
static int m_A;
};
int Son::m_A = 20;
void test() {
Son son;
cout << "通过对象访问:" << endl;
cout << "Son m_A = " << son.m_A << endl;
cout << "Base m_A = " << son.Base::m_A << endl;
son.func();
son.Base::func();
cout << "通过类名访问:" << endl;
cout << "Son m_A = " << Son::m_A << endl;
// 第一个::是通过Son类名访问,第二个::是Base作用域
cout << "Base m_A = " << Son::Base::m_A << endl;
Son::func();
Son::Base::func(); }
int main() {
test();
return 0;
}
##### 6.7 多继承语法
- 语法:`class 子类:继承方式 父类1,继承方式 父类2 ...`
- 实际开发中不建议使用,容易引起同名冲突
- 如果两个父类出现了同名情况,则子类中需要加上作用域
##### 6.8 菱形继承
- 两个派生类继承自同一个基类
- 又有两个类同时继承这两个派生类
- 这种继承方式叫做菱形继承,也叫做钻石继承
e.g.
动物类
羊 (动物)
骆驼(动物)
羊驼(羊、骆驼)
菱形继承的问题:
1. 羊继承了动物的数据,骆驼也继承了动物的数据,当羊驼使用数据时就会产生二义性;
2. 羊驼继承自动物的数据有两份,实际上只需要一份
- 利用虚继承的方法可以解决这些问题:virtual,最大的基0类称为虚基类
```Cpp
#include <iostream>
using namespace std;
class Animal {
public:
int m_Age;
};
// 虚继承,实际上是创建了一个指针指向父类,数据还是只有一份
class Sheep : virtual public Animal {};
class Camel : virtual public Animal {};
class Alpaca : public Sheep, public Camel {};
void test() {
Alpaca al;
al.Sheep::m_Age = 100;
al.Camel::m_Age = 200;
// 不利用虚继承的技术则会因为有两份m_Age数据而报错
cout << al.m_Age << endl;
}
int main() {
test();
return 0;
}
七、多态
7.1 多态的基本概念
多态分为两类:
- 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 动态多态:派生类 和 虚函数 实现运行时多态(C++中常说的多态) 静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
```Cpp
#include
using namespace std;
class Animal {
public:
// 地址早绑定
void speak() {
cout « “动物在说话” « endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout « “喵喵喵” « endl;
}
};
void doSpeak(Animal &animal) {
animal.speak();
}
void test() {
Cat cat;
doSpeak(cat);
}
int main() {
test();
return 0;
}
以上函数会输出:
```Cpp
动物在说话
原因是静态多态的地址是在编译的时候就已经编译好了,无论传入的参数是什么,都会执行Animal
下的speak
函数。
解决方法:给Animal中的speak函数加上virtual关键字,使之变成一个虚函数:
class Animal {
public:
// 虚函数,地址晚绑定
virtual void speak() {
cout << "动物在说话" << endl;
}
};
动态多态的条件:
- 有继承关系
- 子类重写父类的虚函数
重写:返回值和参数列表完全相同
动态多态的使用:父类的指针或者引用执行子类对象
7.2 多态的原理剖析
class Animal {
public:
// 虚函数,地址晚绑定
virtual void speak() {
cout << "动物在说话" << endl;
}
};
Animal类内部结构:
vfptr - 虚函数(表)指针 —> 指向一个虚函数表 vftable
v - virtual
f - function
ptr - pointer
虚函数表中记录了虚函数的地址 &Animal::speak
class Cat : public Animal {
public:
void speak() {
cout << "喵喵喵" << endl;
}
};
当Cat重写了speak函数之后:
Animal类内部结构:
vfptr - 虚函数(表)指针 —> 指向一个虚函数表 vftable
v - virtual
f - function
ptr - pointer
虚函数表中记录了虚函数的地址 &Cat::speak
当父类的指针或者引用指向子类对象的时候,发生多态
Animal &animal = cat;
animal.speak();
个人理解为重写之后的函数地址覆盖了父类的函数地址,达到调用时调用子类的函数的作用。
7.3 多态案例 计算器类
- 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
传统实现:
```Cpp #includeusing namespace std;
// 传统实现
class Calculator {
public:
Calculator(int num1, int num2) {
m_num1 = num1;
m_num2 = num2;
}
int Calculate(string sym) {
if (sym == “+”) {
return m_num1 + m_num2;
}
else if (sym == “-“) {
return m_num1 - m_num2;
}
else if (sym == “*”) {
return m_num1 * m_num2;
}
cout « “输入的运算符有误” « endl;
return 0;
}
private:
int m_num1;
int m_num2;
};
void test() {
Calculator cl(10, 10);
int ret = cl.Calculate(“*”);
cout « “The result of the calculating is “ « ret « endl;
}
int main() {
test();
return 0;
}
可见没有加入除法的算法,如果想要进行除法的添加则需要对类中的成员函数进行修改,但是**在真实的开发中提倡开闭原则:对扩展进行开发,对修改进行关闭**,传统的实现方法无法实现这个原则。
```Cpp
#include <iostream>
using namespace std;
// 使用多态的技术实现
class Calculator {
public:
virtual int Calculate() {
return 0;
}
int m_num1;
int m_num2;
};
class addNum : public Calculator {
public:
int Calculate() {
return m_num1 + m_num2;
}
};
class subNum : public Calculator {
public:
int Calculate() {
return m_num1 - m_num2;
}
};
class multiNum : public Calculator {
public:
int Calculate() {
return m_num1 * m_num2;
}
};
void test() {
// 父类的构造函数不能被子类继承
Calculator *cl = new addNum;
cl->m_num1 = 10;
cl->m_num2 = 20;
int ret = cl->Calculate();
cout << "The result of the calculating is " << ret << endl;
// 删除在堆区开辟的内存
delete cl;
}
int main() {
test();
return 0;
}
使用多态的技术增加一个除法方法只需要写一个Calculator
的派生类即可。
class divideNum : public Calculator {
public:
int Calculate() {
return m_num1 / m_num2;
}
};
7.4 纯虚函数和抽象类
- 在多态中,父类的虚函数实现往往是毫无意义的,因此可以将虚函数改为纯虚函数
- 纯虚函数语法:
virtual 返回值类型 函数名(参数列表) = 0;
- 当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
7.5 多态案例2 - 制作饮品
- 烧水
- 冲泡
- 倒入杯中
- 加入辅料
```Cpp
#include
using namespace std;
// 创建制作饮品抽象类
class makeDrink {
public:
// 创建纯虚函数
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void Pour() = 0;
virtual void addOther() = 0;
};
// 创建茶类继承抽象类
class Tea : public makeDrink {
public:
// 重写纯虚函数
void Boil() {
cout « “boil the water” « endl;
}
void Brew() {
cout « “brew method” « endl;
}
void Pour() {
cout « “pour the water to the bottle” « endl;
}
void addOther() {
cout « “add the honey to the tea” « endl;
}
};
// 创建咖啡类继承抽象类
class Coffee : public makeDrink {
public:
// 重写纯虚函数
void Boil() {
cout « “boil the water” « endl;
}
void Brew() {
cout « “brew method” « endl;
}
void Pour() {
cout « “pour the water to the bottle” « endl;
}
void addOther() {
cout « “add the milk to the coffee” « endl;
}
};
void test() {
// 将父类的指针指向子类,形成多态
makeDrink *makeTea = new Tea;
makeTea->Boil();
makeTea->Brew();
makeTea->Pour();
makeTea->addOther();
cout « ”————————————” « endl;
// 将父类的指针指向子类,形成多态
makeDrink *makeCoffee = new Coffee;
makeCoffee->Boil();
makeCoffee->Brew();
makeCoffee->Pour();
makeCoffee->addOther(); }
int main() {
test();
return 0;
}
##### 7.6 虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
- 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
- 虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
- 析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
##### 7.7 多态案例三 - 电脑组装
- 零件有:CPU、显卡、内存条
- 将每个零件封装成抽象基类,并且提供不同厂商生产不同的零件,例如Intel和Samsung
- 组装三台不同的电脑进行工作
```Cpp
#include <iostream>
using namespace std;
// 抽象CPU类
class Cpu {
public:
virtual void calculate() = 0;
};
// 抽象GPU类
class Gpu {
public:
virtual void display() = 0;
};
// 抽象Memory类
class Memory {
public:
virtual void storage() = 0;
};
// 具体CPU类
class IntelCpu : public Cpu {
public:
void calculate() {
cout << "IntelCpu 正在计算" << endl;
}
};
// 具体GPU类
class IntelGpu : public Gpu {
public:
void display() {
cout << "IntelGpu 正在显示" << endl;
}
};
// 具体Memory类
class IntelMemory : public Memory {
public:
void storage() {
cout << "IntelMemory 正在储存" << endl;
}
};
class SamsungCpu : public Cpu {
public:
void calculate() {
cout << "SamsungCpu 正在计算" << endl;
}
};
class SamsungGpu : public Gpu {
public:
void display() {
cout << "SamsungGpu 正在显示" << endl;
}
};
class SamsungMemory : public Memory {
public:
void storage() {
cout << "SamsungMemory 正在储存" << endl;
}
};
// 创建电脑类
class Computer {
public:
// 使用指针传入参数,使父类指针指向子类,形成多态
Computer(Cpu *cpu, Gpu *gpu, Memory *memory) {
m_Cpu = cpu;
m_Gpu = gpu;
m_Mem = memory;
}
void Work() {
m_Cpu->calculate();
m_Gpu->display();
m_Mem->storage();
}
// 析构函数释放零件类
~Computer() {
if (m_Cpu != NULL) {
delete m_Cpu;
m_Cpu = NULL;
}
if (m_Gpu != NULL) {
delete m_Gpu;
m_Gpu = NULL;
}
if (m_Mem != NULL) {
delete m_Mem;
m_Mem = NULL;
}
}
private:
Cpu *m_Cpu;
Gpu *m_Gpu;
Memory *m_Mem;
};
void test() {
// 父类指针或者引用指向子类对象的时候,发生多态
// 创建电脑配件
Cpu *intelcpu = new IntelCpu;
Gpu *intelgpu = new IntelGpu;
Memory *intelmem = new IntelMemory;
Cpu *samsungcpu = new SamsungCpu;
Gpu *samsunggpu = new SamsungGpu;
Memory *samsungmem = new SamsungMemory;
// 组建第一台电脑
Computer *computer1 = new Computer(intelcpu, intelgpu, intelmem);
computer1->Work();
cout << "-------------" << endl;
delete computer1;
// 组建第二台电脑
Computer *computer2 = new Computer(samsungcpu, samsunggpu, samsungmem);
computer2->Work();
cout << "-------------" << endl;
delete computer2;
// 组建第三台电脑
Computer *computer3 = new Computer(intelcpu, intelgpu, samsungmem);
computer3->Work();
cout << "-------------" << endl;
delete computer3;
}
int main() {
test();
return 0;
}