一、面向对象程序设计概述

面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。OOP 的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP = 对象+类+继承+多态+消息,其中核心概念是类和对象。

类是构造对象的模板,而对象可以理解为类的实例。简单来讲,可以把类想象成制作甜饼的模具,而对象想象成小甜饼。由类创造出对象的过程称为创建类的实例。

面向对象的三大特性:封装、继承、多态。

二、封装

1、封装基础

封装(Encapsulation)是面向对象三大特性之一。

封装就是将数据和行为组合起来,并对对象的使用者隐藏具体的实现方式。

封装的意义,在于可以将对象中的属性和行为加以权限控制

例1:设计一个圆类,计算出圆的周长。

#include <iostream>
using namespace std;

const double PI = 3.14;
//class 代表设计一个类
class Circle {
//访问权限
public:
//属性
int m_r;
//行为
//获取周长
double calculateZC() {
return 2 * PI * m_r;
}
};

int main() {
//通过圆类,创建具体对象 圆
Circle c1;
//给圆 对象的属性进行赋值
c1.m_r = 10;
cout << "C = " << c1.calculateZC() << endl;
return 0;
}

例2:设计一个学生类,属性有姓名和学号,可以给学号和姓名赋值,并显示学生的姓名和学号。

#include <iostream>
#include <string.h>
using namespace std;

class Student {
public://权限
//类中的属性和行为成为 成员
//属性 成员属性
//行为 成员函数

//属性
string m_Name;
int m_ID;
//行为
void ShowStudent() {
cout << "name " << m_Name << " num " << m_ID << endl;
}

void setName(string name) {
m_Name = name;
}

void setID(int id) {
m_ID = id;
}
};

int main() {
Student stu;
// stu.m_Name = "Yonagi";
stu.setName("Yonagi");
// stu.m_ID = 1;
stu.setID(1);
stu.ShowStudent();

return 0;
}

2、访问权限

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有以下三种:

1、public 公共权限 类内可以访问,类外可以访问

2、protected 保护权限 类内可以访问,类外不可以访问

3、private 私有权限 类内可以访问,类外不可以访问

例3:

#include <iostream>
#include <string.h>
using namespace std;

//访问权限
//1、公共权限 public 成员 类内可以访问,类外可以访问
//2、保护权限 protected 成员 类内可以访问,类外不可以访问
//3、私有权限 private 成员 类内可以访问,类外不可以访问

class Person {
public:
string m_Name;
protected:
string m_Car;
private:
int m_Password;
public:
void func() {
m_Name = "Yonagi";
m_car = "TOYOTA";
m_Password = 123456;
}
};

int main() {
//实例化具体对象
Person p1;
p1.m_Name = "Sierra";
// p1.m_car = "NISSAN";//保护权限内容,类外无法访问
// p1.m_Password = 234567;//私有权限内容,类外无法访问
p1.func();
return 0;
}

3、struct和class的区别

初学面向对象时,会发现struct 和 class 很相似。

struct和class的一个区别就是,它们的默认访问权限是不同的,struct的默认访问权限是public,class的默认访问权限是private。

例4:

//struct和class默认访问权限不同
//struct 默认权限为public
//class 默认权限为private

#include <iostream>
using namespace std;

class C1 {
int m_A;//默认权限为private
};

struct C2 {
int m_A;//默认权限为public
};

int main() {
C1 c1;
// c1.m_A = 100;//私有
C2 c2;
c2.m_A = 100;
return 0;
}

4、成员属性私有化

对于一个类,实现封装的最好方法,就是将属性设置为private,将成员函数设置为public。

例5:

#include <iostream>
#include <string.h>
using namespace std;

//成员属性设置为私有
//1、可以自己控制读写权限
//2、对于写可以检测数据的有效性

class Person {
public:
void setAge(int age) {
if (age >= 0 && age <= 150) {
m_Age = age;
} else {
m_Age = 0;
}
}

void setName(string name) {
m_Name = name;
}

string getName() {
return m_Name;
}

int getAge() {
return m_Age;
}
private:
string m_Name; //可读可写
int m_Age; // 可读可写
};

int main() {
Person p;
p.setName("Yonagi");
cout << "name : " << p.getName() << endl;
p.setAge(1000);
cout << "age : " << p.getAge() << endl;
return 0;
}

例6:设计一个立方体类,求出立方体的面积和体积

#include <iostream>
using namespace std;

class Cube{
public:
void setM_H(double h) { //设置高
m_H = h;
}

void setM_L(double l) { //设置长
m_L = l;
}

void setM_W(double w) { //设置宽
m_W = w;
}

double getM_H() { //获取高
return m_H;
}

double getM_L() { //获取长
return m_L;
}

double getM_W() { //获取宽
return m_W;
}

double S() { //获取面积
return (m_H * m_L) * 2 + (m_H * m_W) * 2 + (m_L * m_W) * 2;
}

double V() { //获取体积
return m_H * m_L * m_W;
}
private:
double m_H; //高
double m_L; //长
double m_W; //宽
};

int main() {
Cube c1;
c1.setM_H(1.0);
c1.setM_L(1.0);
c1.setM_W(1.0);

cout << "S of c1 is " << c1.S() << endl;
cout << "V of c1 is " << c1.V() << endl;

Cube c2;
c2.setM_H(2.0);
c2.setM_L(2.0);
c2.setM_W(2.0);
cout << "S of c2 is " << c2.S() << endl;
cout << "V of c2 is " << c2.V() << endl;
return 0;
}

例7:设计一个圆类,和一个点类,计算点和圆的位置关系

#include <iostream>
using namespace std;

class Point {
public:
void setX(int x) {
c_x = x;
}

int getX() {
return c_x;
}

void setY(int y) {
c_y = y;
}

int getY() {
return c_y;
}
private:
int c_x;
int c_y;
};

class Circle {
public:
void setR(int r) {
c_r = r;
}

int getR() {
return c_r;
}

void setCenter(Point center) {
c_center = center;
}

Point getCenter() {
return c_center;
}
private:
int c_r;
Point c_center;
};

void judge(Circle &c, Point &p) {
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) + (c.getCenter().getY() - p.getY())
* (c.getCenter().getY() - p.getY());
int rDistance = c.getR() * c.getR();

if (distance == rDistance) {
cout << "On the circle" << endl;
} else if (distance < rDistance) {
cout << "In the circle" << endl;
} else {
cout << "Out of circle" << endl;
}
}

int main() {
Circle c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);

Point p;
p.setX(10);
p.setY(10);

judge(c, p);
return 0;
}

三、对象初始化与清理

1、构造函数与析构函数

一个对象或变量,如果没有初始状态,则其使用后果是未知的。

同样,如果使用完一个对象或变量,如果没能及时清理,也会带来问题。

C++提供了构造函数析构函数,用于解决以上的问题。这两个函数会被编

译器自动调用,完成对象的初始化和清理工作。

因为对象的初始化和清理是编译器强制要求,所以如果不提供构造函数和析

构函数,编译器也会自动提供。

编译器自动提供的构造函数和析构函数是空实现。

构造函数析构函数
主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用主要作用在于对象销毁前系统自动调用,执行一些清理工作

构造函数:

  1. 基本语法:类名() {}
  2. 构造函数,没有返回值,也不写void
  3. 函数名与类名相同
  4. 构造函数可以有参数,因此可以发生重载
  5. 程序在调用对象时可以自动调用构造,无需手动调用,且会调用一次

析构函数:

  1. 基本语法:~类名() {}
  2. 析构函数,没有返回值,也不写void
  3. 函数名与类名相同,在前面加~
  4. 析构函数不能有参数,因此不能发生重载
  5. 程序在销毁对象时会自动调用析构,无需手动调用,且会调用一次

例1:

#include <iostream>
using namespace std;

class Person {
//1、构造函数
public:
Person() {
cout << "Constructor" << endl;
}
//2、析构函数
~Person() {
cout << "Destructor" << endl;
}
};

void test01() {
Person p;
}

int main() {
// test01();
Person p;
return 0;
}

2、构造函数分类与调用

构造函数有两种分类方式:

按参数分类按类型分类
有参构造&无参构造(默认构造)普通构造&拷贝构造

构造函数有三种调用方法:

  1. 括号法
  2. 显示法
  3. 隐式转换法

注意:构造默认函数时,不要加()。因为编译器会认为这是函数声明。

例2:

#include <iostream>
using namespace std;

class Person {
public:
//构造函数
Person() {
cout << "Constructor" << endl;
}
Person(int a) {
age = a;
cout << "Constructor01" << endl;
}
//析构函数
~Person() {
cout << "Destructor" << endl;
}

//拷贝构造函数
Person(const Person &p) {
//将传入的人身上所有的属性,拷贝
age = p.age;
cout << "Constructor02" << endl;
}
private:
int age;
};

void test01() {
//1、括号法
// Person p; //默认函数调用
// Person p2(10); //有参构造函数
// Person p3(p2); //拷贝构造函数

//2、显示法
Person p1;
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造

Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象

//注意:
//不要利用拷贝构造函数 初始化匿名对象
//Person(p3); // Person(p3) == Person p3; 对象声明

//3、隐式转换法
Person p4 = 10; //相当于 写了Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4); 拷贝构造
}

int main() {
test01();
return 0;
}

3、拷贝构造函数的调用时机

拷贝构造函数调用通常有三种情况:

  1. 使用一个已经创建完毕的对象来初始化一个新对象
  2. 值传递的方式给函数参数传值
  3. 值方式返回局部对象

例3:

#include <iostream>
using namespace std;

class Person {
public:
int m_Age;
Person() {
cout << "Person Constructor" << endl;
}

Person(int age) {
cout << "Person Function Constructor" << endl;
m_Age = age;
}

~Person() {
cout << "Person Destructor" << endl;
}

Person(const Person &p) {
cout << "Person Copy Constructor" << endl;
m_Age = p.age;
}
private:
int age;
};

//拷贝构造函数调用时机

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person p1(20);
Person p2(p1);
}
//2、值传递的方式给函数参数传值
void Work(Person p) {

}

void test02() {
Person p;
Work(p);
}
//3、值方式返回局部对象
void Work02() {
Person p1;
return p1;
}

void test03() {
Person p = Work02();
}

int main() {
// test01();
test02();
return 0;
}

4、构造函数调用规则

默认情况下,编译器会自动为一个类提供三个函数:

  1. 默认构造函数(无参,空实现)
  2. 默认析构函数(无参,空实现)
  3. 默认拷贝构造函数,对属性进行值拷贝

规则:1、如果写了有参构造函数,编译器就不再提供默认构造,依然提供拷

贝构造。

2、如果写了拷贝构造函数,编译器不再提供其他普通构造函数

例4:

#include <iostream>
using namespace std;

class Person {
public:
// Person() {
// cout << "Person Constructor" << endl;
// }

Person(int age) {
cout << "Person Function Constructor" << endl;
m_Age = age;
}

Person(const Person &p) {
m_Age = p.m_Age;
cout << "Person Copy Constructor" << endl;
}

~Person() {
cout << "Person Destructor" << endl;
}

int m_Age;
};

// void test01() {
// Person p;
// p.m_Age = 18;

// Person p2(p);
// cout << "P2's age " << p2.m_Age << endl;
// }

void test02() {
Person p(28);

Person p2(p);
}

int main() {
// test01();
test02();
return 0;
}

5、深拷贝与浅拷贝

浅拷贝深拷贝
简单的复制拷贝操作在堆区重新申请空间,进行拷贝操作

深拷贝后,析构函数中应当对堆区开辟的数据进行释放。

例5:

#include <iostream>
using namespace std;

//深拷贝和浅拷贝

class Person {
public:
Person() {
cout << "Person Constructor" << endl;
}

Person(int age, int height) {
cout << "Person Function Constructor" << endl;
m_Age = age;
m_Height = new int(height);
}

//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p) {
cout << "Person Copy Constructor" << endl;
m_Age = p.m_Age;
//深拷贝操作
m_Height = new int(*p.m_Height);
}

~Person() {
cout << "Person Destructor" << endl;
//析构代码
//将堆区开辟的数据进行释放
if (m_Height != NULL) {
delete m_Height;
}
m_Height = NULL;
}

int m_Age;
int *m_Height;
};

void test01() {
Person p1(18, 160);
cout << "The age of p1 is " << p1.m_Age << " height is " << *p1.m_Height << endl;
Person p2(p1);
cout << "The age of p2 is " << p2.m_Age << " height is " << *p2.m_Height << endl;
}

int main() {
test01();
return 0;
}

6、初始化列表

C++提供了初始化列表语法,用来初始化属性。

语法:构造函数() : 属性1(值1), 属性2(值2), …

例6:

#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 m_A;
int m_B;
int m_C;

};

void test01() {
// Person p(10, 20, 30);
Person p(30, 20, 10);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}

int main() {
test01();
return 0;
}

7、类对象作为类成员

类中的成员可以是另一个类的对象,可以称该成员为对象成员。

当其他类的对象作为本类成员时,构造时先构造类对象,再构造自身。

析构的顺序与构造顺序相反。

例7:

#include <iostream>
#include <string.h>
using namespace std;

//类对象作为类成员

class Phone {
public:
Phone (string pName) {
m_PName = pName;
cout << "Phone" << endl;
}
~Phone() {
cout << "Phone Destructor" << endl;
}
string m_PName;
};

class Person {
public:
Person(string name, string pName) : m_Name(name), m_Phone(pName) {
cout << "Person" << endl;
}

~Person() {
cout << "Person Destructor" << endl;
}

string m_Name;
Phone m_Phone;
};
//当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
//析构顺序与构造相反
void test01() {
Person p("Yonagi", "iPhone 14 Pro");
cout << p.m_Name << " use " << p.m_Phone.m_PName << endl;
}

int main() {
test01();
return 0;
}

8、静态成员

静态成员就是在成员变量或成员函数前加关键词static,称为静态成员。

静态成员分为:

静态成员变量静态成员函数
1、所有对象共享一份数据1、所有对象共享同一个函数
2、在编译过程分配内存2、静态成员函数只能访问静态成员变量
3、类内声明,类外初始化

静态成员变量,不属于某个对象上,所有对象都共享一份数据,因此静态

成员变量有两种访问方式:

  1. 通过对象进行访问
  2. 通过类名进行访问

例8:

#include <iostream>
using namespace std;

class Person {
public:
static int m_A;
//静态成员变量也有访问权限
private:
static int m_B;
};

int Person :: m_A = 100;
// int Person :: m_B = 200;

void test01() {
Person p;
cout << p.m_A << endl;

Person p2;
p2.m_A = 200;
cout << p.m_A << endl;//共享一份数据
}

void test02() {
//静态成员变量 不属于某个对象上,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式

//1、通过对象进行访问
Person p;
cout << p.m_A << endl;
//2、通过类名进行访问
cout << Person :: m_A << endl;
// cout << Person :: m_B << endl; //类外访问不到私有成员变量
}

int main() {
// test01();
test02();
return 0;
}

静态成员函数,所有对象都共享同一个函数,且静态函数只能访问静态成员

变量。

例9:

#include <iostream>
using namespace std;

class Person {
public:
static void func() {
m_A = 100;
// m_B = 200; //静态成员函数不能访问非静态成员变量
cout << "static func()" << endl;
}
static int m_A;
int m_B;

//静态成员函数也有访问权限
private:
static void func2() {
cout << "static func2()" << endl;
}
};

int Person :: m_A = 0;

void test01() {
//1、通过对象进行访问
Person p;
p.func();
//2、通过类名访问
Person :: func();
// Person :: func2(); //类外访问不到私有成员函数
}

int main() {
test01();
return 0;
}

四、C++对象模型

1、成员变量与成员函数的存储

在C++中,类内的成员变量和成员函数是分开存储的。

只有非静态成员变量属于类对象。

空对象占用内存为1。

编译器会给每个空对象分配一个字节的空间,是为了区分空对象占内存的位

置,因此每个空对象也拥有一个独一无二的内存地址。

例1:

#include <iostream>
using namespace std;

//成员变量和成员函数是分开存储的

class Person {
int m_A; //非静态成员变量 属于类对象
static int m_B; //静态成员变量 不属于类对象上
void func() {} //非静态成员函数 不属于类对象上
static void func2() {} //静态成员函数 不属于类对象上
};

int Person :: m_B = 100;

void test01() {
Person p;
cout << "sizeof p is " << sizeof(p) << endl;
}

int main() {
test01();
return 0;
}

2、this指针

this指针是隐含在每个非静态成员函数内的一种指针。

this指针指向被调用的成员函数所属的对象。

this指针不需要定义,可以直接使用。

this指针的用途:

  1. 当形参和成员变量同名时,可以用this指针区分
  2. 在类的非静态成员函数中返回对象本身,可以使用return *this

this指针的本质是指针常量,指针指向不能修改。

例2:

#include <iostream>
using namespace std;

class Person {
public:
Person (int age) {
//this指针指向的是被调用成员函数所属的对象
this -> age = age;
}

Person& PersonAddAge(Person &p) {
this->age += p.age;
//this是指向p2的指针
return *this;
}

int age;
};

//1、解决名称冲突
void test01() {
Person p1(18);
cout << "p1's age is " << p1.age << endl;
}
//2、返回对象本身用 *this
void test02() {
Person p1(10);

Person p2(10);

p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

cout << "p2's age is " << p2.age << endl;
}

int main() {
test01();
test02();
return 0;
}

3、空指针访问成员函数

C++中,空指针是可以访问成员函数的。

如果用到了this指针,就需要对指针是否为空进行判定。

例3:

#include <iostream>
using namespace std;

//空指针调用成员函数

class Person {
public:
void showClassName() {
cout << "this is Person class" << endl;
}

void showPersonAge() {
//报错原因是传入的指针为null,需要进行判定
if (this == NULL) {
return;
}
cout << "age = " << m_Age << endl;
}
int m_Age;
};

void test01() {
Person *p = NULL;
p -> showClassName();
p -> showPersonAge();
}

int main() {
test01();
return 0;
}

4、常函数和常对象

常函数:

  1. 成员函数后加const称这个函数为常函数
  2. 常函数内不能修改成员属性
  3. 成员属性声明时加关键词mutable,在常函数中依然可以修改

常对象:

  1. 声明对象前加const称该对象为常对象
  2. 常对象只能调用常函数

例4:

#include <iostream>
using namespace std;

//常函数
class Person {
public:
// const Person * const this;
//在成员函数后面加const,修饰的是this指向,使指针指向的值不可以修改
void showPerson() const {
// this -> m_A = 100;
// this = NULL; //this指针不能修改指针指向
this -> m_B = 100;
}
void func() {
m_B = 100;
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值,加mutable
};

void test01() {
Person p;
p.showPerson();
}
//常对象

void test02() {
const Person p; //在对象前加const,变为常对象
// p.m_A = 100;
p.m_B = 100; //m_B是特殊值,在常对象下可以修改

//常对象只能调用常函数
p.showPerson();
//p.func(); //常对象不能调用普通成员函数,因为普通成员函数可以修改属性
}

int main() {
test01();
return 0;
}

五、友元

在程序里,有些私有属性也想让类外一些特殊的函数或类进行访问,这就需

要用到友元。

友元的目的,就是让一个函数或类访问另一个类中的私有成员。

友元的关键字 friend。

友元有三种实现方式:

  1. 全局函数做友元
  2. 类做友元
  3. 成员函数做友元

1、全局函数做友元

如果需要全局函数做友元,就把全局函数的 返回类型 函数名(参数列表) 部分复制

粘贴到类的前面,然后在语句前加关键字friend。

例1:

#include <iostream>
using namespace std;

//友元
//目的:让一个函数或者类 访问另一个类中私有成员
//关键词:friend

class Building {
//goodFriend 是building的友元,可以访问私有属性
friend void goodFriend(Building *building);
public:
string m_SittingRoom;
public:
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;
};

//全局函数

void goodFriend(Building *building) {
cout << "Good Friend is founding " << building -> m_SittingRoom << endl;

cout << "Good Friend is founding " << building -> m_BedRoom << endl;
}

void test01() {
Building building;
goodFriend(&building);
}

int main() {
test01();
return 0;
}

2、类做友元

和全局函数做友元同理,如果类做友元,只需要将 class 做友元的类名 复制

粘贴到另一个类的前面,然后在语句前加入关键字friend。

例2:

#include <iostream>
using namespace std;
#include <string.h>

//类作友元
class Building;

class GoodFriend {
public:
GoodFriend();
void visit(); //参观函数 访问building中的属性

Building *building;
};

class Building {
//GoodFriend是本类的友元
friend class GoodFriend;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};

//类外写成员函数
Building :: Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

GoodFriend :: GoodFriend() {
//创建建筑物对象
building = new Building;
}

void GoodFriend :: visit() {
cout << "Good Friend is visiting " << building -> m_SittingRoom << endl;
cout << "Good Friend is visiting " << building -> m_BedRoom << endl;
}

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

int main() {
test01();
return 0;
}

3、成员函数做友元

成员函数做友元,只需要将 返回类型 成员函数所属类 ::做友元的函数名

(参数列表) 输入到另一个类的前面,然后在语句前加入关键字friend。

例3:

#include <iostream>
using namespace std;
#include <string>

class Building;
class GoodFriend {
public:
GoodFriend();

void visit(); //visit函数可以访问Buil中私有成员
void visit2(); //visit2函数不可以访问Building中私有成员
Building *building;
};

class Building {
//GoodFriend下visit函数作为building的友元函数
friend void GoodFriend :: visit();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};

Building :: Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

GoodFriend :: GoodFriend() {
building = new Building;
}

void GoodFriend :: visit() {
cout << "visit " << building ->m_SittingRoom << endl;

cout << "visit " << building ->m_BedRoom << endl;
}
void GoodFriend :: visit2() {
cout << "visit " << building ->m_SittingRoom << endl;

// cout << "visit " << building ->m_BedRoom << endl;
}

int main() {
GoodFriend gg;
gg.visit();
//gg.visit2();
return 0;
}

六、继承

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

有些类与类之间具有共性,此时就可以使用继承,减少重复代码。

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

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

其中,子类又称为派生类,父类又称为基类。

例1:

#include <iostream>
using namespace std;

//普通实现
// class Java {
// public:
// void header() {
// cout << "首页 公开课 注册 登录" << endl;
// }
// void footer() {
// cout << "帮助中心 站内地图" << endl;
// }
// void left() {
// cout << "Java、Python、C/C++" << endl;
// }
// void content() {
// cout << "Java视频" << endl;
// }
// };

// class Python {
// public:
// void header() {
// cout << "首页 公开课 注册 登录" << endl;
// }
// void footer() {
// cout << "帮助中心 站内地图" << endl;
// }
// void left() {
// cout << "Java、Python、C/C++" << endl;
// }
// void content() {
// cout << "Python视频" << endl;
// }
// };

// class C {
// public:
// void header() {
// cout << "首页 公开课 注册 登录" << endl;
// }
// void footer() {
// cout << "帮助中心 站内地图" << endl;
// }
// void left() {
// cout << "Java、Python、C/C++" << endl;
// }
// void content() {
// cout << "C++视频" << endl;
// }
// };

//void test01() {
// Java ja;
// ja.header();
// ja.footer();
// ja.left();
// ja.content();
// cout << "===============" << endl;
// Python py;
// py.header();
// py.footer();
// py.left();
// py.content();
// cout << "===============" << endl;
// C c;
// c.header();
// c.footer();
// c.left();
// c.content();
//}

//继承实现
class BasePage {
public:
void header() {
cout << "首页 公开课 注册 登录" << endl;
}
void footer() {
cout << "帮助中心 站内地图" << endl;
}
void left() {
cout << "Java、Python、C/C++" << endl;
}
};

class Java : public BasePage {
public:
void content() {
cout << "Java视频" << endl;
}
};

class Python : public BasePage {
public:
void content() {
cout << "Python视频" << endl;
}
};

class C : public BasePage {
public:
void content() {
cout << "C++视频" << endl;
}
};

int main() {
//test01();
return 0;
}

1、继承方式

继承有三种方式:公共继承、保护继承、私有继承。

公共继承:

  1. 父类中公共权限成员,到了子类中依然是公共权限。
  2. 父类中保护权限成员,到了子类中依然是保护权限。
  3. 父类中私有权限成员,到了子类中无法访问。

保护继承:

  1. 父类中公共权限成员,到了子类变为保护权限。
  2. 父类中保护权限成员,到了子类依然是保护权限。
  3. 父类中私有权限成员,到了子类中无法访问。

私有继承:

  1. 父类中公共权限成员,到了子类变为私有权限。
  2. 父类中保护权限成员,到了子类变成私有权限。
  3. 父类中私有权限成员,子类无法访问。

例2:

#include <iostream>
using namespace std;

//继承方式
//公共继承,保护继承,私有继承

class Base1 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 : public Base1 {
public:
void func() {
m_A = 10; //父类中公共权限成员 到子类中依然是公共权限
m_B = 10; //父类中保护权限成员 到子类中依然是保护权限
// m_C = 10; //父类中私有权限成员 到子类中无法访问
}
};

void test01() {
Son1 s1;
s1.m_A = 100;
// s1.m_B = 100; //保护权限不能在类外访问
}
//保护继承
class Base2 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son2 : protected Base2 {
public:
void func() {
m_A = 100; //公共成员 到子类变为保护权限
m_B = 100;
m_C = 100; //父类中私有成员 到子类访问不到
}
};

void test02() {
Son2 s1;
s1.m_A = 100; //m_A保护权限,类外访问不到
}
//私有继承
class Base3 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son3 : private Base3 {
public:
void func() {
m_A = 100; //父类中公共成员到子类变成私有成员
m_B = 100; //父类中保护成员到子类变成私有成员
// m_C = 100; //父类中私有成员,子类无法访问
}
};

class GrandSon3 : public Son3 {
public:
void func() {
m_A = 1000; //到了Son3中,m_A为私有,无法访问
m_B = 1000; //到了Son3中,m_A为私有,无法访问
m_C = 1000; //到了Son3中,m_A为私有,无法访问
}
};

void test03() {
Son3 s1;
// s1.m_A = 1000; //到Son3中变为私有成员,类外无法访问
}

int main() {
test01();
test02();
test03();
return 0;
}

2、继承中的对象模型

例3:

#include <iostream>
using namespace std;

//继承中的对象模型
class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

class Son : public Base {
public:
int m_D;
};

void test01() {
cout << "size of Son is " << sizeof(Son) << endl;
}

int main() {
test01();
return 0;
}

上面这个案例,最终输出应该是16。

继承中,父类的所有非静态成员属性都会被子类继承。父类的私有成员属性被编译器隐藏,因而无

法访问,但是继承下来了。

3、继承中构造与析构顺序

继承中构造和析构顺序:

父类先构造,子类再构造。

析构顺序与构造顺序相反。

例4:

#include <iostream>
using namespace std;

//继承中构造和析构顺序

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

class Son : public Base {
public:
Son() {
cout << "Son Constructor" << endl;
}
~Son() {
cout << "Son Distructor" << endl;
}
};

void test01() {
// Base b;
Son s;
}

int main() {
test01();
return 0;
}

4、继承中同名成员处理

当子类和父类出现同名成员时,如果要访问子类中成员,可以直接访问;如

访问父类中成员,则需要加作用域

例5:

#include <iostream>
using namespace std;

//继承中同名成员处理方式

class Base {
public:
Base() {
m_A = 100;
}

void func() {
cout << "Base-func()" << endl;
}

void func(int a) {
cout << "Base-func(int a)" << endl;
}

int m_A;
};

class Son : public Base {
public:
Son() {
m_A = 200;
}

void func() {
cout << "Son-func()" << endl;
}

int m_A;
};

//同名属性处理
void test01() {
Son s;
cout << "m_A = " << s.m_A << endl;
cout << "Base m_A = " << s.Base :: m_A << endl;
//如果通过子类对象访问同名父类中同名成员,需要加作用域
}

//同名函数处理
void test02() {
Son s;
s.func(); //直接调用子类中成员函数
s.Base :: func(); //调用父类中同名成员函数,加作用域
s.Base :: func(100);
//如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
//如果访问父类中被隐藏的同名成员函数,需要加作用域
}

int main() {
// test01();
test02();
return 0;
}

5、继承中同名静态成员处理

静态成员和非静态成员出现同名成员处理方式相同。访问子类成员可以直

接访问访问父类成员需要加作用域

例6:

#include <iostream>
using namespace std;

//继承中同名静态成员处理方式

class Base {
public:
static int m_A;

static void func() {
cout << "Base-static func()" << endl;
}

static void func(int a) {
cout << "Base-static func(int a)" << endl;
}
};

int Base :: m_A = 100;

class Son : public Base {
public:
static int m_A;

static void func() {
cout << "Son-static func()" << endl;
}
};

int Son :: m_A = 200;

//同名静态成员属性

void test01() {
//1、通过对象访问
Son s;
cout << "Son m_A = " << s.m_A << endl;
cout << "Base m_A = " << s.Base :: m_A << endl;
//2、通过类名访问
cout << "Son m_A = " << Son :: m_A << endl;
//第一个::代表通过类名访问
//第二个::代表访问父类作用域下的属性
cout << "Base m_A = " << Son :: Base :: m_A << endl;
}

//同名静态成员函数

void test02() {
//1、通过对象访问
Son s;
s.func();
s.Base :: func();
//2、通过类名访问
//第一个::代表通过类名访问
//第二个::代表访问父类作用域下的函数
Son :: func();
Son :: Base :: func();
//如果子类中出现和父类同名的静态成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
//如果访问父类中被隐藏的同名静态成员函数,需要加作用域
Son :: Base :: func(100);
}

int main() {
test01();
test02();
return 0;
}

6、多继承

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

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

多继承可能会导致不同父类有同名成员出现,这时候需要加作用域加以区

分。

实际开发不建议使用多继承。

例7:

#include <iostream>
using namespace std;

class Base1 {
public:
Base1() {
m_A = 100;
}

int m_A;
};

class Base2 {
public:
Base2() {
m_A = 200;
}

int m_A;
};

//子类需要继承base1和base2
class Son : public Base1, public Base2 {
public:
Son() {
m_C = 300;
m_D = 400;
}

int m_C;
int m_D;
};

void test01() {
Son s;
//16
cout << "size of Son is " << sizeof(s) << endl;
//当父类中出现同名成员,需要加作用域
cout << "base1 m_A = " << s.Base1 :: m_A << endl;
cout << "base2 m_A = " << s.Base2 :: m_A << endl;
}

int main() {
test01();
return 0;
}

7、菱形继承

菱形继承,是指有两个子类继承同一个父类,又有某一个类同时继承这两个

子类,这样形成的继承关系就是菱形继承。

菱形继承中可能出现同名成员的问题,可以采用虚继承解决这个问题。

在继承前加上关键字virtual就变为虚继承。

例8:

#include <iostream>
using namespace std;

//菱形继承

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 = 28;
st.Tuo :: m_Age = 18;
//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
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;
//这份数据只要一份就可以
}

int main() {
test01();
return 0;
}

七、多态

1、多态基础

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

多态可分为静态多态动态多态

静态多态动态多态
函数地址早绑定,编译期间确定地址函数地址晚绑定,运行期间确定地址

多态的条件:

  1. 有继承关系
  2. 子类要重写父类中的虚函数 重写:函数返回值 函数名 参数列表 完全相同

多态使用:父类的指针或引用,执行子类对象

例1:

#include <iostream>
using namespace std;

class Animal {
public:
//虚函数
virtual void speak() {
cout << "Animal is speaking" << endl;
}
};

//当子类重写父类的虚函数
//子类的虚函数表内部会替换成子类的虚函数地址
class Cat : public Animal {
public:
void speak() {
cout << "Cat is speaking" << endl;
}
};

class Dog : public Animal {
public:
void speak() {
cout << "Dog is speaking" << endl;
}
};

//执行说话函数
//地址早绑定,在编译阶段就确定地址
//如果想执行让猫说话,那么函数地址就不能提前绑定,需要运行阶段进行绑定

void doSpeak(Animal &animal) {
animal.speak();
}

void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}

void test02() {
cout << "size of Animal = " << sizeof(Animal) << endl;
}


int main() {
test01();
test02();
return 0;
}

多态的优点:

  1. 代码组织结构清晰
  2. 可读性强
  3. 利于前期和后期的维护

例2:

#include <iostream>
#include <string>
using namespace std;

//多态优点
//代码组织结构清晰
//可读性强
//利于前期和后期的维护

//普通写法
class Calculator {
public:
int getResult(string oper) {
if (oper == "+") {
return m_Num1 + m_Num2;
} else if (oper == "-") {
return m_Num1 - m_Num2;
} else if (oper == "*") {
return m_Num1 * m_Num2;
}
}

int m_Num1;
int m_Num2;
};

void test01() {
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << " = " << c.getResult("*") << endl;
}

//利用多态

//实现计算器抽象类
class AbstractCal {
public:
virtual int getResult() {
return 0;
}

int m_Num1;
int m_Num2;
};

//加法类
class Add : public AbstractCal {
public:
int getResult() {
return m_Num1 + m_Num2;
}
};
//减法类
class Sub : public AbstractCal {
public:
int getResult() {
return m_Num1 - m_Num2;
}
};
//乘法类
class Mul : public AbstractCal {
public:
int getResult() {
return m_Num1 * m_Num2;
}
};

void test02() {
AbstractCal *abc = new Add;
abc ->m_Num1 = 10;
abc ->m_Num2 = 10;
cout << abc ->m_Num1 << "+" << abc ->m_Num2 << " = " << abc ->getResult() << endl;
delete abc;
abc = new Sub;
abc ->m_Num1 = 10;
abc ->m_Num2 = 10;
cout << abc ->m_Num1 << "-" << abc ->m_Num2 << " = " << abc ->getResult() << endl;
delete abc;
abc = new Mul;
abc ->m_Num1 = 10;
abc ->m_Num2 = 10;
cout << abc ->m_Num1 << "*" << abc ->m_Num2 << " = " << abc ->getResult() << endl;
delete abc;
}

int main() {
// test01();
test02();
return 0;
}

2、纯虚函数和抽象类

虚函数可以改写为纯虚函数。

语法:virtual 返回类型 函数名(参数) = 0;

当类中有纯虚函数时,这个类就称为抽象类。

抽象类的特点:

  1. 抽象类无法实例化
  2. 子类必须重写抽象类中纯虚函数,否则也是抽象类

例3:

#include <iostream>
using namespace std;

//抽象类
//无法实例化对象
//子类必须重写抽象类中纯虚函数,否则也是抽象类

class Base {
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
virtual void func() = 0;
};

class Son : public Base {
public:
virtual void func() {
cout << "func()" << endl;
}
};

void test01() {
// Base b;//无法实例化对象
Base *base = new Son;
base -> func();
}

int main() {
test01();
return 0;
}

2、虚析构与纯虚析构

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

子类的析构代码。

解决:将父类中的析构改为虚析构或纯虚析构。

共性:可以解决父类指针释放子类对象的问题、都需要具体函数实现。

区别:如果是纯虚析构,则该类属于抽象类,无法实例化对象。

例4:

#include <iostream>
#include <string>
using namespace std;

class Animal {
public:
Animal() {
cout << "Animal Constructor" << endl;
}
//虚析构可以解决父类指针释放子类对象时不干净的问题
// virtual ~Animal() {
// cout << "Animal Destructor" << endl;
// }
//纯虚析构 需要声明也需要实现
//有了纯虚析构后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
//纯虚函数
virtual void speak() = 0;
};

Animal :: ~Animal() {
cout << "Animal Destructor" << endl;
}

class Cat : public Animal {
public:
Cat(string name) {
cout << "Cat Constructor" << endl;
m_Name = new string(name);
}

virtual void speak() {
cout << *m_Name << " is speaking" << endl;
}

string *m_Name;

~Cat() {
if (m_Name != NULL) {
cout << "Cat Destructor" << endl;
delete m_Name;
m_Name = NULL;
}
}
};

void test01() {
Animal *animal = new Cat("Tom");
animal -> speak();
delete animal;
}

int main() {
test01();
return 0;
}