继承是面向对象的主要特征(此外还要封装和多态)之一,它使得一个类从现有类中派生,而不必重新定义一个新类。继承的实质就是用已有的数据类型创建新的数据类型,并保存已有数据类型的特点,以旧类为基础创建新类,新类包含了旧类的数据成员和成员函数,并且可以在新类中添加新的数据成员和成员函数。旧类被称为基类或者父类,新类被称为派生类或子类。
例如我们看到很多网页中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。
#include
using namespace std;
普通实现
//class Java
//{
//public:
// void head()
// {
// cout << "首页、公开课、登录、注册...(公共头部)" << endl;
// }
// void foot()
// {
// cout << "帮助中心、交流合作..." << endl;
// }
// void left()
// {
// cout << "Java、Python、C++..." << endl;
// }
// void content()
// {
// cout << "java学科视频" << endl;
// }
//};
//class Python
//{
//public:
// void head()
// {
// cout << "首页、公开课、登录、注册...(公共头部)" << endl;
// }
// void foot()
// {
// cout << "帮助中心、交流合作..." << endl;
// }
// void left()
// {
// cout << "Java、Python、C++..." << endl;
// }
// void content()
// {
// cout << "python学科视频" << endl;
// }
//};
...
//继承实现页面
class BasePage
{
public:
void head()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void foot()
{
cout << "帮助中心、交流合作..." << endl;
}
void left()
{
cout << "Java、Python、C++..." << endl;
}
};
class Java :public BasePage
{
public:
void content()
{
cout << "java学科视频" << endl;
}
};
class Python :public BasePage
{
public:
void content()
{
cout << "python学科视频" << endl;
}
};
class Cpp : public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
//继承的好处:减少重复代码
//语法:class 子类:继承方式 父类
//子类 也称为 派生类
//父类 也称为 基类
void test01()
{
cout << "Java页面如下" << endl;
Java ja;
ja.head();
ja.foot();
ja.left();
ja.content();
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
继承的好处:可以减少重复的代码
class A:public B;
A类称为子类或派生类
B类称为父类或基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现起共性,而新增的成员体现了其个性。
继承方式有三种:
#include
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;//父类中的私有权限成员 子类访问不到
}
};
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 :protected Base2
{
void func() {
m_A = 100;//父类中公共成员,到子类中变为了保护成员
m_B = 100;父类中公共成员,到子类中变为了保护成员
//m_C = 100;//父类中的私有成员 子类访问不到
}
};
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base3
{
void func() {
m_A = 100;//父类中公共成员,到子类中变为了私有成员
m_B = 100;父类中公共成员,到子类中变为了私有成员
//m_C = 100;//父类中的私有成员 子类访问不到
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
m_A = 100;//报错,此时Son的时候已经为私有属性
}
};
void test01()
{
Son1 son1;
son1.m_A = 100;
//son1.m_B = 100;//到Son1中m_B是保护权限
}
void test02()
{
Son2 s2;
//s2.m_A = 100;//在son2中m_A变成了保护权限,因此类外访问不到
}
void test03()
{
Son3 s3;
s3.m_A = 100;//到Son3中变为私有成员,类外访问不到
s3.m_B = 1000;//到Son3中变为私有成员,类外访问不到
}
int main()
{
test01();
system("pauese");
}
问题:从父类继承过来的成员,哪些属于子类对象?
#include
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 << sizeof(Son) << endl;//16 父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实是被继承下去了
}
int main()
{
test01();
system("pause");
return 0;
}
子类继承父类后,当创建子类对象,也会调用父类的构造函数
就是按照栈的原理先进先出,首先子类先创建,所以会调用子类的构造函数,后面是父类的构造函数,销毁时是父类先调用析构函数,子类再调用析构函数。
问题:当子类中出现与父类同名的成员,如何通过子类对象,访问到父类或子类中同名的数据呢?
#include
using namespace std;
//继承中同名成员处理
class Base
{
public:
int m_A;
Base()
{
m_A = 100;
}
void func()
{
cout << "Base-func()调用" << endl;
}
void func(int a)
{
cout << "Base-func(int)调用" << endl;
}
};
class Son :public Base
{
public:
int m_A;
Son()
{
m_A = 200;
}
void func()
{
cout << "Son-func()调用" << endl;
}
};
//同名成员变量处理
void test01()
{
Son s1;
cout << s1.m_A<< endl;//子类中的100
//如果通过子类对象访问父类中同名成员,需要加上作用域
cout << s1.Base::m_A << endl;//父类中的200
}
//同名成员函数处理
void test02()
{
Son s;
s.func();//调用子类中的成员函数(直接调用子类中的成员函数)
s.Base::func();//调用父类中的成员函数(加作用域调用父类中的成员函数)
s.func(100);//错误
//如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
//如果想要访问发哦父类中的同名成员函数,需要加作用域
s.Base::func(100);
}
int main()
{
test02();
test01();
system("pause");
return 0;
}
总结:
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致。
#include
using namespace std;
//
class Base
{
public:
static int m_A;
static void func()
{
cout << "访问Base-func()函数" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "访问Son-func()函数" << endl;
}
};
int Son::m_A = 200;
//同名静态成员属性
void test01()
{
Son s;
//1、通过对象访问
cout << s.m_A << endl;//访问子类中的静态成员变量
cout << s.Base::m_A << endl;//访问父类中的静态成员变量
//2、通过类名访问
cout << Son::m_A << endl;//访问子类中的静态成员变量
cout << Base::m_A << endl;//访问父类中的静态成员变量
}
//同名静态成员函数
void test02()
{
Son s;
//通过对象访问
s.func();//访问子类中的成员函数
s.Base::func();//访问父类中的成员函数
//通过类名访问
Son::func();//访问子类中的成员函数
Base::func();//访问父类中的成员函数
}
int main()
{
test01();
system("pause");
return 0;
}
总结:同名静态成员处理方式与非静态处理方式一样,只不过有两种访问的方式(通过类名和通过对象)。
C++允许一个类继承多个类
语法:class子类:继承方式 父类1,继承方式2 父类…
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
C++实际开发中不建议用多继承
#include
using namespace std;
//父类
class Base1
{
public:
int m_A;
Base1()
{
m_A = 100;
}
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//子类 需要继承Base1和Base2
class Son1:public Base1,public Base2
{
public:
Son1()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01()
{
Son1 s;
cout << sizeof(s) << endl;
//当父类中出现同名成员,需要加作用域区分
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:多继承中如果父类出现了同名情况,子类使用时需要加作用域。
菱形继承概念:
(1)两个派生类继承同一个基类;
(2)又有某个类同时继承两个派生类;
(3)这种继承被称为菱形继承,或者钻石继承。
#include
using namespace std;
class Animal
{
public:
int m_Age;
};
//利用虚继承,解决菱形继承的问题
//Animal类称为虚基类
class Sheep:virtual public Animal
{
public:
};
class Tuo:virtual public Animal
{
public:
};
class SheepTuo:public Sheep,public Tuo
{
public:
};
void test01()
{
SheepTuo st;
st.m_Age = 18;//此时不会报错
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
//菱形继承导致数据有两份,资源浪费
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
多态是C++面向对象三大特性之一
多态分为两类:
静态多态和动态多态区别:
#include
using namespace std;
//多态
//动物类
class Animal
{
public:
virtual void speak()//加virtual关键字就会进行地址晚绑定
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog :public Animal
{
public:
//重写 函数返回值类型 函数名 参数列表 完全相同
void speak()//在此前也可加virtual,可写可不写
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能早绑定,需要在运行阶段进行绑定,地址晚绑定
//动态多态满足条件
//1、有继承关系
//2、子类要重写父类中的虚函数(如在此中的speak函数,父类中加了virtual关键字的函数)
//动态多态的使用
//父类的指针或者引用 执行子类对象(Animal &animal=cat)
void doSpeak(Animal &animal)//Animal &animal=cat;//允许父类和子类的类型转换
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
父类满足条件:
多态使用条件
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
在多态中,通常父类中虚函数的实现时毫无意义的,主要都是调用子类重写的内容。
因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这类也称为抽象类
抽象类特点:
(1)无法实例化对象;
#include
using namespace std;
class Animal
{
public:
//纯虚函数
//只要有一个抽象函数,这个类就称为抽象类
//抽象类特点:
//1、无法实例化对象
//2、抽象类的子类,必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Cat :public Animal
{
public:
void func()
{
cout << "func函数调用" << endl;
}
};
void test01()
{
//Animal a1;//报错
Cat cat;
Animal* animal = new Cat;
animal->func();
}
int main()
{
test01();
system("pause");
return 0;
}
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构代码改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
虚析构和纯虚析构区别:
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()=0;
类名…~类名(){}
#include
using namespace std;
//虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
//虚析构和纯虚析构只能存在一个
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
/*virtual ~Animal()
{
cout << "Animal虚析构函数调用" << endl;
}*/
//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
virtual void speak() = 0;//纯虚函数(不需要实现)
};
Animal::~Animal()
{
cout << "纯虚析构函数调用" << endl;
}
class Cat:public Animal
{
public:
Cat(string name)
{
m_Name = new string(name);
*m_Name=name;
}
virtual void speak()
{
cout << *m_Name<<"小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "cat析构函数" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
在程序中,有些私有属性也想让类外特殊的一些函数或者类访问,就需要用到友元的技术。
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字friend
友元的三个实现:
#include
using namespace std;
#include
//建筑物类
class Building
{
friend void GoodGay(Building* building);//友元
public:
Building()
{
m_SettingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SettingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//全局函数做友元
void GoodGay(Building* building)
{
cout << "好基友的全局函数正在访问:" << building->m_SettingRoom << endl;
cout << "好基友的全局函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
Building building;
GoodGay(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
#include
using namespace std;
#include
//建筑物类
class Building
{
friend class GoodGay;//友元
public:
Building()
{
m_SettingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SettingRoom;//客厅
private:
string m_BedRoom;//卧室
};
class GoodGay
{
public:
GoodGay()
{
building = new Building;
}
void visit()//参观函数 访问Building中的属性
{
cout << "好基友类正在访问:" << building->m_SettingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
Building* building;
};
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
system("pause");
return 0;
}
#include
using namespace std;
#include
//建筑物类
class Building
{
friend void GoodGay::visit();//友元
public:
Building()
{
m_SettingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SettingRoom;//客厅
private:
string m_BedRoom;//卧室
};
class GoodGay
{
public:
GoodGay();
void visit();//参观函数 访问Building中的属性
Building* building;
};
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_SettingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
system("pause");
return 0;
}