继承
1、继承的写法 派生类:继承方式(三种属性) 基类,
2,示例:
class Father
{};
class son:pulbic Fathe
{};
子类son继承了父类Father
子类拥有父类得所有成员变量和成员函数
可以在子类添加父类没有方法和属性
子类对象可以当作父类对象来使用(子类拥有父类的所有成员变量和成员函数)
继承方式
1、类的访问属性有三种,public(共有),proteted(保护),private(私有)
2、类的继承方式也有三种就是上面的三种,每种继承方式都会有不同的地方
3、被继承的类称之为基类(父类),继承的类称之为派生类(子类)
4、一个基类可以有很多个派生类,一个派生类也可以有很多个基类
如果在一个子类继承多个父类容易出现二义性,即同样的的数据或函数在这个子类所继承的多个父类当中都有这样一个成员,子类在调用这个同名的成员的时候就不知道是再调用那个父类的成员了,所以容易导致二义性
解决方法
就是使用作用域,在调用的时候明确说明调用按个父类的就可以了, <子类对象.父类::数据或成员>
class Son :private Father, public Mather
//加作用域
cout << this->Mather::money << endl;
继承中得构造和析构
1、子类会继承父类的成员属性,成员函数,但是不会继承构造和析构
,这个只有父类知道如果构造析构自己的属性,子类不知道
2、构造顺序是,先构造父类,然后子类;析构顺序时,先析构子类,然后父类,这个是一个入栈和出栈得过程
3、子类不会继承父类得构造和析构,但是在子类对象构造或析构的时候会调用父类的构造或析构,所以在构造或析构子类对象的时候要确保父类里面的有构造或析构可用
class Father
{
public:
Father(){
cout << "Father类构造" << endl;
money = 999999;
}
~Father(){cout << "Father析构" << endl;}
int money;
};
class Mather
{
public:
Mather(){
cout << "Mather类构造" << endl;
money = 100000;
}
~Mather(){cout << "Mather析构" << endl;}
int money;
};
class Son:private Father, public Mather
{
public:
Son(){
money = 100;
cout << "Son构造" << endl;
}
~Son(){ cout << "Son析构" << endl;}
int money;
void fun(){
cout << this->Mather::money << endl;
cout << this->money << endl;
}
};
继承中的同名处理
1、如果子类和父类拥有同名数据和函数,子类会把父类的所有同名数据和函数都隐藏掉,在调用的时候默认调用子类成员
2、如果想要调用父类成员,就需要用作用域访问<子类对象.父类::父类的同名数据或函数>,就需要如此访问
3、子类不会覆盖父类的同名数据和函数,只是隐藏
虚继承
防止继承中成员访问的二义性
在A类中如果有一个成员a,那么在b和c也同样会继承过来,d也会继承A,B,C中的成员,在这里就会有类似同名的问题,在d这里的对象调用那么就不知道调用谁的了,可以使用虚继承在继承的前面加上关键字virtual <virtual public A>
,操作的是共有一份数据,如果在BC中还是有同名的,还是需要用作用域来访问对应的成员
class C :virtual public A
多态
静态联编和动态联编
1、将源代码中的函数调用解释为执行特定的函数代码被称为函数名联编
2、根据函数名选择对应的函数体执行,但是在C++有函数重载所以这必须还要看函数的参数及函数名才能确定使用那个函数
3、这个可以在编译过程就完成
这种编译(静态联编)
class Animal
{
public:
void speak()
{
cout << "动物在交流" << endl;
}
void eat()
{
cout << "动物在吃午饭" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在交流" << endl;
}
virtual void eat()
{
cout << "小猫在吃肉" << endl;
}
};
无论传得是小猫还是动物都是动物在说话,这个就是静态联编
void doSpeak(Animal & animal)
{
animal.speak();
}
4、根据程序运行的时候
来选择使用(动态联编)
class Animal
{
public:
void speak()//虚函数,在函数前面加上virtual
{
cout << "动物在说话" << endl;
}
void eat()
{
cout << "动物在吃饭" << endl;
}
};
使用虚函数时,每个对象都将增大,增大量为存这个虚函数得地址得空间,就是一个指针,对于每个类,编译器都会创建一个虚函数得地址表,用前面讲的那个指针来管理,这里对面每个函数得调用就对了一步额外得操作,即到表中查找所调用得函数地址,虽然非虚函数得效率比虚函数稍高,但是不具备动态联编的功能
class Cat :public Animal
{
public:
void speak()//如果父类声明了虚函数,子类可以不用写,建议都写上
{
cout << "小猫在交流" << endl;
}
virtual void eat()
{
cout << "小猫在吃肉" << endl;
}
};
这里传动物就是动物交流,传小猫就是小猫交流
void doSpeak(Animal & animal)
{
animal.speak();
}
在这个函数其实就会发生多态,父类指针或引用指向子类对象,如果传的时子类小猫,那么就是父类引用指向子类对象,这个指向的是谁,就是调用的谁的虚函数的。这里从父类继承过来的虚函数一般会重写,不重写没有意义。
虚析构和纯虚析构
一个类中如果有了纯虚函数或纯虚析构,那么这个类就不能用来实例化对象,但是可以定义指针
纯虚函数 virtual void fun()=0;这个就是一个纯虚函数
class Animal
{
public:
virtual void speak()
{
cout << "动物在交流" << endl;
}
普通析构 是不会调用子类的析构的,所以可能会导致释放不干净
利用虚析构来解决这个问题,在析构的时候回调用子类的析构
virtual ~Animal()
{
cout << "Animal的析构调用" << endl;
}
纯虚析构 ,需要声明 还需要实现 类内声明,类外实现
virtual ~Animal() = 0;
如果函数中出现了 纯虚析构函数,那么这个类也算抽象类 抽象类 不可实例化对象
virtual void fun() = 0;
不能用抽象类实例化对象,但是可以定义指针
#include <iostream>
using namespace std;
class Animal//当类中有一个纯虚函数,那么这个类就会变成抽象类,不能实例化对象
{
public:
char* name;
virtual void eat() = 0;//纯虚函数
virtual void speak() = 0;
//纯虚析构
virtual ~Animal() = 0;//需要实现
};
Animal::~Animal()
{
cout << "父类的纯虚析构" << endl;
}
class Shop :public Animal
{
public:
Shop(const char*name)
{
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
}
virtual void eat()//继承过来的虚函数要重写
{
cout << "羊在吃草" << endl;
}
virtual void speak()
{
cout << "羊在交流" << endl;
}
~Shop()
{
cout << "羊析构" << endl;
if (name != NULL)
{
delete[]name;
name = NULL;
}
}
};
Animal aaa;//不能用抽象类实例化对象,但是可以定义指针
void text()
{
Animal* a = new Shop("小羊");//多态的使用
a->eat();
delete a;
}
int main()
{
text();
system("pause");
return 0;
}