一、C++基础之类的继承
1.1、类的继承概念
在C++中类和类之间存在三种关系,分别是 has-A, uses-A 和 is-A。
1、has-A
包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类,如下:
class A
{
public:
private:
};
class B
{
public:
private:
A a;
};
在类B中已经存在类A的定义对象,所以称这种关系是has-A。
2、uses-A
一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现,例如:
class A
{
public:
private:
};
class B
{
public:
void test(A &a)
{
}
private:
};
B类通过函数来引用类A,所以是uses-A。
3、is-A
机制称为“继承”。关系具有传递性,不具有对称性,例如:
class A
{
public:
private:
};
class B:public A
{
public:
private:
};
在这里就引出继承这个概念。
继承:
类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
我们看一段代码:
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal(string color)
{
this->color = color;
}
string color;
private:
};
class Dog :public Animal
{
public:
Dog(string color, string name) :Animal(color)
{
this->name = name;
}
void print(void)
{
cout << this->name << ":"<< color <<endl;
}
private:
string name;
};
int main(void)
{
Dog d("yellow","阿黄");
d.print();
return 0;
}
运行结果:
这段代码我们先定义一个父类动物,然后将狗继承于动物这个类,最终输出自己的信息,继承的定义格式如下:
class 派⽣生类名:[继承⽅方式] 基类名
{
派⽣生类成员声明;
};
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类, 称为单继承。
1.2、类的继承访问权限
关于类的访问权限总结到下面这个表中:
1、public公有继承:
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中 不变,而基类的私有成员不可访问。 即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无 论派生类的成员还是派生类的对象都无法访问基类的私有成员。
2、private私有继承:
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。 基类的公有成员和保 护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但 是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对 象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都 会成为不可访问。因此私有继承比较少用。
3、protected保护继承:
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。 派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员 还是派生类的对象,都无法访问基类的私有成员。
那么如何根据自己的实际正确的定义变量的属性呢,三个要点“
1、需要被外界访问的成员直接设置为public。
2、只能在当前类中访问的成员设置为private。
3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
我们来看一段代码分析一下这部分的信息:
#include <iostream>
using namespace std;
class A
{
public:
int a_pub;
A()
{
a_pub = 0;
a_pro = 0;
a_pri = 0;
}
void set(int a, int b, int c)
{
a_pub = a;
a_pro = b;
a_pri = c;
}
protected:
int a_pro;
private:
int a_pri;
};
class B :public A
{
public:
void printb(void)
{
cout << "a_pub:" << a_pub << endl;
cout << "a_pro:" << a_pro << endl;
cout << "a_pri:" << a_pri << endl;
}
};
class C :protected A
{
public:
void printc(void)
{
cout << "a_pub:" << a_pub << endl;
cout << "a_pro:" << a_pro << endl;
cout << "a_pri:" << a_pri << endl;
}
};
class D :private A
{
public:
void printd(void)
{
cout << "a_pub:" << a_pub << endl;
cout << "a_pro:" << a_pro << endl;
cout << "a_pri:" << a_pri << endl;
}
};
int main(void)
{
B b1;
C c1;
D d1;
b1.a_pub = 0;
b1.a_pro = 0;
b1.a_pri = 0;
c1.a_pub = 0;
c1.a_pro = 0;
c1.a_pri = 0;
d1.a_pub = 0;
d1.a_pro = 0;
d1.a_pri = 0;
return 0;
}
该段代码是没法运行的,但是我们可以通过编译器提示的错误来解释一下。
通过上面两张图,我们可以得到点结论:
1、在子类中,只要是父类的private成员变量,在子类中都不允许访问。
2、public继承的父类变量,之前是什么访问权限就保留之前的访问权限。
3、protected和private继承的父类变量,在类外都无法访问,只不过是前者属性是protected,后者是private。
1.3、类的兼容性问题
我们看一段代码:
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
a = 0;
}
private:
int a;
};
class Child :public Father
{
public:
private:
int b;
};
int main(void)
{
Father a1;
Child b1;
a1 = b1;
b1 = a1;
return 0;
}
可是我们可以看到,子类对象是可以赋值给父类对象,但是反过来却不行,这是为何?我们画一张图片来对比一下。
通过图片我们可以看到,子类是可以接纳父类的,但是父类却没有b的空间来接纳子类,所以就导致之前那里的报错。
所以有如下总结:
子类对象可以当作父类对象使用,子类对象可以直接赋值给父类对象,可以直接初始化父类对象,父类指针可以直接指向子类对象,父类引用可以直接引用子类对象。
1.4、子类构造函数和析构函数
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout << "Father()" << endl;
a = 0;
}
~Father()
{
cout << "~Father()" << endl;
}
private:
int a;
};
class Child :public Father
{
public:
Child()
{
cout << "Child()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
private:
int b;
};
void test(void)
{
Child b1;
}
int main(void)
{
test();
return 0;
}
运行结果:
对于上面的运行结果我们可以得到结论:
1、构造函数:先是父类,再是子类
2、对于析构函数,先是子类,然后才是父类。
1.5 继承中的static
因为static成员变量并不是单独属于每一个对象,而是属于这个类,也就是整个家族的共享资源,对于这一块的有点在哪里呢?
例如可以运用到统计学生例子中,创建一个共享变量,然后每创建一个对象后将分数统计到这个变量中,之后便可以直接处理。
1.6、多继承的关系
在讲这个之前,我们先谈论一下关于父类中和子类中变量重名的问题。
#include <iostream>
using namespace std;
class Father
{
public:
int value;
Father()
{
this->value = 10;
a = 0;
}
private:
int a;
};
class Child :public Father
{
public:
int value;
Child()
{
this->value = 20;
cout << "Child()" << endl;
}
private:
int b;
};
void test(void)
{
Child b1;
cout << b1.value << endl;
}
int main(void)
{
test();
return 0;
}
运行结果:
这里的运行结果是20,也就是当子类和父类有相同的变量时候,默认使用的是子类的,那么我该如何访问父类的value这个变量呢?
cout << b1.Father::value << endl;
通过访问区来确定访问的变量,只是这样觉得怪怪的。
既然单个继承之间存在变量名相同的情况,那么多个继承类中也肯定会存在,如下面这个图:
首先在父类中存在一个变量m。,因为中间的两个子类都继承父类,那么到了他的下一个类继承这两个子类的时候,是不是存在两个m了?难道还要是说使用访问区域来明确,可是我只需要我这个类中只有一个m而不是多个,这就造成访问不明确了。
多继承的概念:
一个类有多个直接基类的继承关系称为多继承。
使用格式:
例如:
class Son :public Child1, public Child2
{
public:
private:
};
我们看一下这段代码:
#include <iostream>
using namespace std;
class Father
{
public:
int value;
Father()
{
this->value = 10;
a = 0;
}
private:
int a;
};
class Child1 :public Father
{
public:
private:
};
class Child2 :public Father
{
public:
private:
};
class Son :public Child1, public Child2
{
public:
private:
};
void test(void)
{
Son s1;
s1.value = 10;
}
int main(void)
{
test();
return 0;
}
到那时编译器懵了,他到底该访问那个m?
所以对于这种菱形结构的多继承,在这里提出一个虚继承 概念。
所谓的虚继承是指在在子类中继承父类的时候,添加一个关键字virtual。
这类继承之后,son类不在继承子类中的m而是直接继承父类中的m,这样就不会存在访问不确定因素,也是为了避免成员使用不明确,这时候编译器就知道这是所以son类中的m变量。
所以最终得出:
1、如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
2、如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
3、要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
4、虚继承声明使用关键字virtual。