提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
在本篇文章中,主要是对C++的基础语法进行回顾学习,回顾学习C++的基本语法规则、数据类型、控制结构以及面向对象编程的基本思想。
一、继承
继承是面向对象三大特性之一,有些类与类之间存在特殊的关系,例如下图中:
定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码。
1.继承的基本语法
(1) 基本语法
继承的语法:class 子类 : 继承方式 父类
示例:
class Person{
public:
void func(){}
public:
int m_age;
};
class goodPerson : public Person{
public:
void func(){}
public:
string m_name;
};
总结:goodPerson 类同时拥有自身的m_name和父类中的m_age属性
2. 继承的使用
(1) 继承方式
继承方式一共有三种:公共继承,保护继承,私有继承,子类继承父类的方式和继承的权限如下图所示:
(2) 继承中的对象模型
问题
:从父类继承过来的成员,哪些属于子类对象中?
回答
:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到(子类的内存大小 = 子类的非静态属性的内存 + 父类的非静态属性的内存大小)
(3) 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题
:父类和子类的构造和析构顺序是谁先谁后?
回答
:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
3. 继承中的同名成员的处理
(1) 继承同名成员处理方式
问题
:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
回答
:
a.
访问子类同名成员直接访问即可.
b.
访问父类同名成员需要加作用域
以上述为《继承的基本语法》的例示:
goodPerson s; //实例化对象
s.func(); //调用goodPerson 类中的fun()函数
s.Person::func(); //调用Person 类中的fun()函数
(2) 继承同名静态成员处理方式
问题
:继承中同名的静态成员在子类对象上如何进行访问?
回答
:静态成员和非静态成员出现同名,处理方式一致
对于静态的成员属性,要类内申明,类外定义:
class Person{
public:
static void func(){}
static int m_age;
};
int Person :: m_age = 100;
class goodPerson : public Person{
public:
static void func(){}
string m_name;
};
对于静态成员函数,两种访问的方式(通过对象和通过类名):
goodPerson s;
s.func();
s.Person::func(); //通过对象访问
goodPerson::func(); //通过类名访问
goodPerson::Person::func();
4. 多继承语法
C++中允许一个类继承多个类
语法
:class子类:继承方式父类1,继承方式父类2…
class goodPerson : public Person1, public Person1 {};
示例:goodPerson 继承了Person1,Person2中的m_age属性
goodPerson s
//加作用域才能
cout<<s.Person1::m_age<<endl;
cout<<s.Person2::m_age<<endl;
注意
:多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
5. 菱形继承
菱形继承概念:
两个派生类继承同一个基类,又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承 。典型的菱形继承案例:
示例:
菱形继承带来的问题:
a.
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
解决
:加作用域(同上子类访问父类同名成员的方法)
b.
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
解决
:利用虚继承来解决(在继承前加上vritual的关键字)
class Animal {
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal{};
class Tuo : virtual public Animal{};
class SheepTuo : public Sheep , public Tuo{};
//使用时
SheepTuo st;
st.Sheep::m_Age=100;
st.Tuo::m_Age=200;
cout << st.Sheep::m_Age << endl; //输出是100
cout << st.Tuo::m_Age << endl; //输出是200
cout << st.m_Age << endl; //虚继承的作用,此时st的年龄为200
总结
:
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义利用,虚继承可以解决菱形继承问题
二、多态
多态是C++面向对象三大特性之一,多态的优点:
a
. 代码组织结构清晰
b
. 可读性强
c
. 利于前期和后期的扩展以及维护
1. 多态的使用
(1) 多态分为两类:
a.
静态多态: 函数重载和运算符重载属于静态多态,复用函数名
b.
动态多态: 派生类和虚函数实现运行时多态
(2) 两者的区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
(3) 多态满足条件
a.
有继承关系
b.
子类重写父类中的虚函数
重写:函数返回值类型函数名参数列表完全一致称为重写
(4) 多态使用条件:父类指针或引用指向子类对象
虚函数的语法
:函数前面加上virtual关键字,变成虚函数
class Animal {
public:
virtual void Speak() {
cout << "动物在讲话" << endl; }
};
class Cat : public Animal { //1. 有继承关系
public:
virtual void Speak() { //2. 子类重写父类的虚函数
cout << "小猫在讲话" << endl; }
};
//3. 父类指针或引用指向子类对象
void DoSpeak(Animal & animal){
animal.speak();
}
int main(){
Cat cat;
DoSpeak(cat); //输出的是:小猫在讲话,因为传入的是猫对象,多态的作用就会调用猫对象的Speak成员函数函数
system("pause");
return 0;
}
2. 多态的原理剖析
3.纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual返回值类型函数名(参数列表)=0; 当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
a
. 无法实例化对象(不能使用该类创建对象)
b
. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:
class Animal {
public:
virtual void Speak() = 0;
};
class Cat : public Animal { //1. 有继承关系
public:
virtual void Speak() { //2. 子类重写父类的虚函数
cout << "小猫在讲话" << endl; }
};
class Dog : public Animal { //1. 有继承关系
public:
virtual void Speak() { //2. 子类重写父类的虚函数
cout << "小狗在讲话" << endl; }
};
//使用
void test01(){
Animal * animal = NULL;
//animal = new Animal ;//错误,抽象类无法实例化对象
animal = new Cat ; //在堆区开辟内存
animal ->Speak(); //输出:小猫在讲话
delete animal ;//记得销毁
}
int main(){
test01();
system("pause");
return0;
}
4.虚析构和纯虚析构
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决:将父类中的析构函数改为虚析构或者纯虚析构
(1) 虚析构
析构语法:virtual ~类名() {}
示例:
class Animal {
public:
Animal(){
cout << "Animal构造函数调用!" << endl;
}
virtual void Speak(){
cout << "动物在讲话" << endl;
};
virtual ~Animal(){ //父类的虚析构
cout << "Animal虚析构函数调用!" << endl;
}
};
class Cat : public Animal { //1. 有继承关系
public:
Cat(stringname){
cout<<"Cat构造函数调用!"<<endl;
m_Name = new string(name);
}
virtual void Speak(){
cout<< *m_Name << "小猫在说话!" <<endl;
}
~Cat(){
cout<<"Cat析构函数调用!"<<endl;
if(this->m_Name != NULL){
delete m_Name;
m_Name = NULL;
}
public:
string *m_Name;
};
//验证:
void test01(){
Animal *animal = new Cat("Tom");
//animal = new Animal ;//错误,抽象类无法实例化对象
animal->Speak();
delete animal;
}
int main(){
test01();
system("pause");
return0;
}
总结:
不使用虚构函数时:通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏/
解决:给基类增加一个虚析构函数,虚析构函数就是用来解决通过父类指针释放子类对象
(2) 纯虚析构
纯虚析构语法:virtual ~类名() = 0;
作用和虚析构一样,使用上面使用存虚函数的语法,示例:
class Animal {
public:
Animal(){
cout << "Animal构造函数调用!" << endl;
}
virtual void Speak() = 0;
virtual ~Animal() = 0;
};
class Cat : public Animal { //1. 有继承关系
public:
Cat(stringname){
cout<<"Cat构造函数调用!"<<endl;
m_Name = new string(name);
}
virtual void Speak(){
cout<< *m_Name << "小猫在说话!" <<endl;
}
~Cat(){
cout<<"Cat析构函数调用!"<<endl;
if(this->m_Name != NULL){
delete m_Name;
m_Name = NULL;
}
public:
string *m_Name;
};
//验证:
void test01(){
Animal *animal = new Cat("Tom");
animal->Speak();
delete animal;
}
int main(){
test01();
system("pause");
return0;
}
注意
:纯虚析构,该类属于抽象类,无法实例化对象
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
总结:
1.
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.
拥有纯虚析构函数的类也属于抽象类