一. 继承
C++ 面向对象设计有三个基本要点:封装,继承,多态
通过 继承 联系在一起的类构成一种层次关系,通常在层次关系的根部有一个 基类 ,其它类则直接或间接地从基类继承而来,这些继承得到的类称为 派生类
继承连接可以是 public、protected、private
1.1 public 继承
public 继承意味着 “is-a” (是一种)的关系,比如:“学生”是一个“人”,“老师”是一个“人”,“学生”和“老师”便可以通过 public 继承成为 “人” 的派生类
#include <cstdio>
#include <string>
class Person{
private:
int age;
std::string name;
public:
explicit Person(const std::string &name, int age)
:age(age), name(name){}
public:
void describe(void){
printf("my name is %s, my age is %d years old.\n", this->name.c_str(), this->age);
}
};
class Student : public Person{
public:
explicit Student(const std::string &name, int age)
:Person(name, age){}
};
class Teacher : public Person{
public:
explicit Teacher(const std::string &name, int age)
:Person(name, age){}
};
int main(){
Student you("Alice", 18);
you.describe();
Teacher me("Jack", 30);
me.describe();
return 0;
}
public 继承可以拿到基类中访问权限为 public、protect 的属性和方法,并且访问权限与父类一致
public 继承意味着适用于“基类”身上的每一件事情一定也适用于“派生类”身上,因为每一个“派生类”对象也都是一个“基类”对象
protect 继承意义不明确,不用学
private 继承虽然有意义的 implemented-in-terms-of (根据某物实现出),即派生类的实现需要基类的某些特性。然而这种关系也可以由复合实现,并尽可能使用复合实现,必要时才使用 private 继承
二. 多态
多态的实现依靠基类的指针或引用调用虚函数或纯虚函数实现
2.1 虚函数、纯虚函数
在 public 继承中
(1)有时候你会希望“派生类”只继承“基类”成员函数的接口
(2)有时候你会希望“派生类”同时继承“基类”成员函数的接口和实现,并且能够覆写所继承的实现
(3)有时候你会希望“派生类”同时继承"基类"成员函数的接口和实现,并且不允许覆写任何东西
(1)的实现依靠继承纯虚函数 (2)的实现依靠继承虚函数 (3)的实现依靠继承普通函数就可以了
声明虚函数
virtual void test(void){
printf("This is a test.\n");
}
声明纯虚函数
virtual void test(void) = 0;
#include <cstdio>
#include <string>
class Person{
private:
int age;
std::string name;
public:
explicit Person(const std::string &name, int age)
:age(age), name(name){}
virtual ~Person() = default;
public:
void describe(void) const{
printf("my name is %s, my age is %d years old.\n", this->name.c_str(), this->age);
}
virtual void work(void) const = 0;
};
class Student : public Person{
public:
explicit Student(const std::string &name, int age)
:Person(name, age){}
public:
void work(void) const override{
printf("I'm a student, my work is studying.\n");
}
};
class Teacher : public Person{
public:
explicit Teacher(const std::string &name, int age)
:Person(name, age){}
public:
void work(void) const override{
printf("I'm a teacher, my work is teaching.\n");
}
};
void yourWork(const Person &person){
person.work();
return;
}
int main(){
Student you("Alice", 18);
Teacher me("Jack", 30);
yourWork(you);
yourWork(me);
return 0;
}
含有纯虚函数的类被称为抽象基类
带有多态性质的基类应该声明一个 virtual 析构函数。原因在于当”派生类“对象被一个”基类“指针删除,而该基类带着一个 non-virtual 析构函数时,其结果是未定义的,通常是对象的”派生“成分未被销毁,于是造成一个诡异的”局部销毁“对象
当派生类覆写它继承的某个成员函数时,应在形参列表后添加一个关键字 override
三. auto 关键字
auto 可以在声明变量时根据初始化表达式自动推断该变量的类型
auto num_1 = 5; // 推导为 int
auto num_2 = 5.0; // 推导为 double
auto str = "hello, world"; // 推导为 const char *
如果我们编写的代码让编译器无法进行推导,那么使用 auto 会导致编译失败
auto i;
i = 5;
3.1 decltype
decltype 可以获取对象或表达式的类型
int num_1 = 1;
decltype(num_1) num_2 = 2;
3.2 如何查看推导的类型?
利用编译器诊断,要让编译器显示出它推导的类型,一个有效的方法就是,错误地使用那种类型,从而导致编译报错,错误报告里面会很清楚地显示出那个出错类型
首先,声明一个未定义的类模板
template <typename Type>
class Test;
然后尝试去实例化这个模板,这将会产生一个错误信息,因为没有模板定义可以实例化,我们想看哪个类型,就用哪个类型去实例化这个类模板即可
Test<decltype(num_1)> test;
Test<decltype(num_2)> test;
Test<decltype(str)> test;
作业
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
示例 1: 输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2: 输入:nums = [2,0,1]
输出:[0,1,2]