一、继承
1.1 单一继承
继承就是在一个已经存在的类的基础上建立一个新的类,并拥有其特性。
继承是面向对象的三大特性之一,体现的是代码复用的思想。
- 已存在的类被称为“基类”或“父类”
- 新建立的类被称为“派生类”或“子类”
如果一个派生类只是继承一个基类,两个类的内容基本相同,这样的继承是没有意义的。通常派生类可以对继承来的成员做出必要的修改或增加。
- 对于继承来的属性,通常可以修改值
- 对于继承来的函数,可以通过函数隐藏“屏蔽”基类的同名函数
#include <iostream>
using namespace std;
/**
* @brief The Father class 基类
*/
class Father
{
public:
string first_name = "张";
void work()
{
cout << "我是一个厨师" << endl;
}
};
/**
* @brief The Son class 派生类,拥有基类的特性
*/
class Son:public Father
{
public:
Son()
{
// 修改继承到的基类属性
first_name = "王";
}
void work() // 函数隐藏
{
cout << "我是一个程序员" << endl;
}
void play() // 新增代码
{
cout << "打游戏" << endl;
}
};
int main()
{
Son s;
cout << s.first_name << endl;
s.work();
// 通过作用域限定符还能调用原来基类的被覆盖函数
s.Father::work();
s.play();
return 0;
}
需要注意的是,基类和派生类是相对的,一个基类可以派生出多个派生类,每一个派生类又可以派生出新的派生类。
间接继承的类之间也具有继承关系,也可以称之为间接基类和间接派生类。直接继承的类之间称为直接基类和直接派生类。
派生类是基类的具体化,而基类是派生类的抽象化。
构造函数和析构函数不能被继承。
1.2 继承中的构造函数
派生类的所有构造函数都要直接或间接调用基类的任一一个构造函数。
默认情况下,如果程序员在派生类中不写构造函数,编译器会自动给这个类添加一个无参构造函数且尝试调用基类的无参构造函数。但是,基类没有无参构造函数时,程序员则必须手写派生类类的构造函数并调用基类的构造函数。
派生类调用基类的构造函数,有以下三种方式:
- 透传构造
- 委托构造
- 继承构造 C++11
1.2.1 透传构造
透传构造指的是,派生类构造函数直接调用基类构造函数。
#include <iostream>
using namespace std;
class Father
{
public:
int age;
Father(int age):age(age){}
};
class Son:public Father
{
public:
int age;
// 透传构造
Son(int fage,int sage):Father(fage),age(sage){}
};
int main()
{
Son s;
return 0;
}
1.2.2 委托构造
委托构造指的是,同一个类的某个构造函数调用这个类的其它构造函数。
#include <iostream>
using namespace std;
class Father
{
public:
Father(int)
{
cout << "1" << endl;
}
};
class Son:public Father
{
public:
// 透传构造
Son(int a):Father(a){}
// 委托构造:委托了18行的构造函数
Son():Son(1){}
};
int main()
{
Son s;
return 0;
}
委托构造的使用要注意以下几点:
构造函数A可以委托B,B可以委托C…但是,最后那个被委托的构造函数必须透传构造,调用基类的构造函数,不能形成闭环。
1.2.3 继承构造
这是C++11新增的写法,实际上就是让编译器自动生成结构与基类的构造函数相同的派生类构造函数,并逐一透传构造。
#include <iostream>
using namespace std;
class Father
{
public:
Father(int)
{
cout << "1" << endl;
}
Father(int,int)
{
cout << "2" << endl;
}
};
class Son:public Father
{
public:
// 一句话搞定
using Father::Father;
};
int main()
{
Son s1(1,1);
Son s2(1);
return 0;
}
1.3 对象的创建与销毁流程
#include <iostream>
using namespace std;
/**
* @brief The Value class 作为其它类的变量
*/
class Value
{
private:
string name;
public:
Value(string name):name(name)
{
cout << name << "成员变量构建了" << endl;
}
~Value()
{
cout << name << "成员变量销毁了" << endl;
}
};
class Father
{
public:
Value value = Value("Father类的");
static Value s_value;
Father()
{
cout << "Father类的构造函数" << endl;
}
~Father()
{
cout << "Father类的析构函数" << endl;
}
};
Value Father::s_value = Value("Father类的静态");
class Son:public Father
{
public:
Value value = Value("Son类的");
static Value s_value;
Son()
{
cout << "Son类的构造函数" << endl;
}
~Son()
{
cout << "Son类的析构函数" << endl;
}
};
Value Son::s_value = Value("Son类的静态");
int main()
{
cout << "-------------主函数开始执行-------------" << endl;
{ // 局部代码块
Son s;
cout << "----------对象使用中----------" << endl;
}
cout << "-------------主函数结束执行-------------" << endl;
return 0;
}
运行结果符合以下规律:
- 构建和销毁呈现对称关系,基类内容先构建,派生类内容后构建;派生类内容先销毁,基类内容后销毁。
- 静态成员从程序开始就创建,程序执行结束再销毁,不与任何对象绑定。
1.4 多重继承
1.4.1 多重继承的概念
C++语言支持多重继承,即一个派生类有多个直接继承的基类,是单一继承的拓展,每个基类与派生类之间的关系都可以看做是一个单继承。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "沙发可以坐着" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "床可以躺着" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
1.4.2 多重继承的二义性
多重继承的二义性有两类:
- 第一种 当两个直接基类出现重名成员时
解决方法:使用作用域限定符
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "沙发可以坐着" << endl;
}
void position()
{
cout << "沙发放在客厅里" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "床可以躺着" << endl;
}
void position()
{
cout << "床放在卧室里" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
// sb.position(); 错误
sb.Bed::position();
sb.Sofa::position();
return 0;
}
- 第二种 菱形继承(钻石继承)
解决方法:使用作用域限定符或虚继承。
#include <iostream>
using namespace std;
class Furniture
{
public:
void function()
{
cout << "家具是在家里的东西" << endl;
}
};
class Sofa:virtual public Furniture
{
};
class Bed:virtual public Furniture
{
};
class SofaBed:public Sofa,public Bed{};
int main()
{
SofaBed sb;
// 使用虚继承
sb.function();
// 使用作用域限定符
sb.Bed::function();
sb.Sofa::function();
return 0;
}
二、权限
C++有三种权限修饰符:
- private 私有权限
- protected 保护权限
- public 公有权限
这三种权限除了可以给类中的变量和函数使用外,还可以修饰继承本身。
如果不写权限修饰符,默认为private。
2.1 修饰变量和函数
三种权限的访问能力如下所示。
本类中 | 派生类中 | 全局(例如主函数) | |
---|---|---|---|
private | √ | X | X |
protected | √ | √ | X |
public | √ | √ | √ |
通过代码来验证上面表格。
#include <iostream>
using namespace std;
class Dad
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
void test1()
{
// 在本类中验证三个权限的访问性
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
};
class Son:public Dad
{
public:
void test2()
{
// 在派生类中验证三个权限的访问性
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
Dad d1;
d1.test1();
Son s1;
s1.test2();
// 验证在全局三个权限的访问性
// cout << d1.s1 << endl; 错误
// cout << d1.s2 << endl; 错误
cout << d1.s3 << endl;
return 0;
}
2.2 不同权限的继承
2.2.1 公有继承
公有继承中,派生类能够继承基类的所有权限的成员,但是基类的私有成员不可直接访问,基类的保护与公有成员在派生类中权限不变。
#include <iostream>
using namespace std;
class Dad
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:public Dad
{
public:
void test1()
{
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
class Grandson:public Son
{
public:
void test2()
{
// cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
cout << s.s3 << endl;
s.test1();
Grandson gs;
gs.test2();
return 0;
}
公有继承是使用的最多的一种继承。
2.2.2 保护继承
保护继承中,派生类能够继承基类的所有权限的成员,但是基类的私有成员不可直接访问,基类的保护与公有成员在派生类中权限都变为protected。
#include <iostream>
using namespace std;
class Dad
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:protected Dad
{
public:
void test1()
{
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
class Grandson:public Son
{
public:
void test2()
{
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
// cout << s.s3 << endl; 错误
s.test1();
Grandson gs;
gs.test2();
return 0;
}
上面基于2.2.1节的代码,在16行把继承权限改为protected后,导致42行无法访问,因为s3在Son类中已经从原来的public变成了protected。
2.2.3 私有继承
私有继承中,派生类能够继承基类的所有权限的成员,但是基类的私有成员不可直接访问,基类的保护与公有成员在派生类中权限都变为private。
#include <iostream>
using namespace std;
class Dad
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:private Dad
{
public:
void test1()
{
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
class Grandson:public Son
{
public:
void test2()
{
// cout << s1 << endl; 错误
// cout << s2 << endl; 错误
// cout << s3 << endl; 错误
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
// cout << s.s3 << endl; 错误
s.test1();
Grandson gs;
gs.test2();
return 0;
}
上面基于2.2.2节的代码,在16行把继承权限改为private后,导致33行和34行无法访问,因为s2与s3在Son类中已经从原来的权限变成了private。
三、多态
多态按照字面的意思来讲就是“一种接口,多种状态”,不论传递过来的是哪个类的对象,函数都能够通过同一个接口来适配到各个对象不同的函数调用。
多态实现的前提条件:
- 公有继承(public)
- 有函数覆盖(virtual)
- 基类指针指向派生类对象 / 基类引用派生类对象
3.1 函数覆盖
函数覆盖与函数隐藏比较相似,区别在于需要给被覆盖的函数增加virtual关键字修饰,使其变为虚函数。
虚函数实现多态性:同一类族中不同类的对象,对同一函数调用做出的不同反应。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物能吃东西" << endl;
}
};
class Cat:public Animal
{
public:
void eat() // 虚函数
{
cout << "猫能吃鱼" << endl;
}
};
int main()
{
Cat c;
c.eat();
return 0;
}
函数覆盖一定要使用虚函数,虚函数的使用需要注意:
- 只有成员函数、析构函数可以是虚函数
- 如果函数的声明和定义分离,只需要把virtual关键字放在声明处
- 虚函数具有传递性(继承关系、函数名相同、参数列表完全一致、返回值相关)
在C++11中,新增override关键字以便于验证函数覆盖的有效性。
3.2 多态的使用
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物能吃东西" << endl;
}
};
class Cat:public Animal
{
public:
void eat() // 虚函数
{
cout << "猫能吃鱼" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
};
/**
* @brief test_eat1
* @param a 基类引用派生类对象
*/
void test_eat1(Animal& a)
{
a.eat();
}
/**
* @brief test_eat2
* @param a 基类指针指向派生类对象
*/
void test_eat2(Animal* a)
{
a->eat();
}
int main()
{
Animal a1;
Cat c1;
Dog d1;
test_eat1(a1);
test_eat1(c1);
test_eat1(d1);
Animal* a2 = new Animal;
Cat* c2 = new Cat;
Dog* d2 = new Dog;
test_eat2(a2);
test_eat2(c2);
test_eat2(d2);
delete a2;
delete c2;
delete d2;
return 0;
}
多态的原理与虚继承比较相似,都是通过成员指针指向一张表,在调用时查询完成的。多态在执行的过程中需要查表,因此会降低代码的执行速度。
3.3 虚析构函数
如果通过基类引用或指针指向派生类对象,当使用delete销毁对象时,只会调用基类的析构函数,不会调用派生类的构造函数。此时,如果派生类中有new申请的内存资源,那么会导致内训泄漏。
解决的方法是把基类的析构函数设置成虚函数,即虚析构函数。
除非一个类在设计时已经确定不会有派生类,否则需要把它的析构函数设置虚函数。
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物能吃东西" << endl;
}
virtual ~Animal()
{
cout << "Animal的析构函数" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "猫能吃鱼" << endl;
}
~Cat()
{
cout << "Cat的析构函数" << endl;
}
};
int main()
{
// 触发多态
Animal* a = new Cat;
a->eat();
// 销毁
delete a;
return 0;
}