小编在学习完C++中继承的内容之后,觉得继承的内容很重要,所以今天给大家带来的内容就是继承的主要内容,今天的内容包括继承的定义与语法,继承方式与权限,基类和派生类的类型转换赋值,继承中的隐藏,继承中析构函数的特别之处,多继承和虚拟继承,对象组合和继承的区别。好啦,这就是今天的全部内容,该学习啦!~~~
一、继承的基础
1、继承的定义与语法
在了解继承之前先看看继承的定义:
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
//语法 子类(派生类) + : + 继承方式 + 父类(基类)
class Student : public Person
{
protected:
int _stuid; // 学号
};
语法图(帮助大家更加深刻理解继承的语法):
2、继承的语法与权限
继承类型有三种 1、public 继承 2、protected 继承 3、private 继承 , 在继承中 public > protected > private 的权限
// 1、public 继承 将父类(基类)所有部分都继承下来,但是 private 虽然会被继承下来 但是在类外面不可以去被访问,protected的部分可以在父类和基类中使用,但是不可以在类外使用,private 部分只能在父类中使用
// 如果在子类中(派生类)要访问父类(基类)的私有成员变量,就不能定义 private,应该定义为 protected 部分,并且要使用 public继承方式 或者 protected继承方式
// 2、protected 继承 将父类(基类)中的所有部分都继承下来 但是继承下来的public部分的权限被缩小到 protected 其他不变
// 3、private 继承 将父类(基类)的所有部分你都继承下来 但所有继承下来的部分权限都是private,都只可以在父类(基类)中使用,不可以在类外使用(对外不可见)
// 注意:
// 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
// 使用protetced / private继承,因为protetced / private继承下来的成员都只能在派生类的类里面使用
3、基类和派生类的类型转换赋值和隐藏,把隐藏放到这块讲解
为了明白类型转换赋值和隐藏,大家先看下面一段代码及其注释,来理解一下这两个新的概念是啥:
// 一般两个类不定义相同的函数或者类成员函数,当然也会有特殊情况
// 如果定义了相同名字的函数或者类成员函数,那就先在自己的域中寻找,如果找不到在去父类(基类)中去寻找
// 两个函数名相同的话被称为隐藏
class A
{
public:
A(const int& x = 1)
:num(x)
{}
void Print()
{
cout << num << endl;
}
private:
int num;
};
class B : public A
{
public:
// 默认构造
B(const int num = 0, string name = "张三", string tel = "110")
:_num(num)
, _name(name)
, _tel(tel)
, A(num) // 把从A中继承到的部分看作一个自定义类型成员
{} // 初始化链表的部分是从先定义的部分开始初始化,和写的先后顺序无关
void Print() // 初始化部分必须先初始化父类(基类)部分(编译器默认初始化顺序) 因为为了后面初始化派生类(子类)部分时使用父类进行初始化
{
cout << _num << endl;
}
private:
int _num = 0;
string _name;
string _tel;
};
void test2()
{
B aa;
// 隐藏之后如果非得要调用A中的Print()那就需要类引用操作符
aa.Print();
aa.A::Print(); // 这样就可以实现啦
B b1;
A a1;
// 这里有一个 类型转换赋值 操作,因为定义继承只有,B 定义的部分多了 A 中的成员
// 就会有这个操作
a1 = b1; // 类型转换赋值 -> 切割,切片(把b1和自己相同的部分直接赋值给自己,中间不存在临时变量)
a1.Print();
}
隐藏:
从上面的代码中可以看出,子类和父类中都有Print()函数,在子类和父类中两个函数的名字相同就会构成隐藏,这里只是函数名相同,一般在调用这个函数之后,会现在自己的类中寻找,找不到才会去父类中寻找。后面学到多态之后,大家还会学到一个叫虚函数的东西(函数名,返回值,函数参数)都要相同才可以构成虚函数。
类型转换赋值:
从上面的代码可以看出,父类中有一个_num的私有成员变量,子类中有 _num , _name , _tel 三个私有成员变量,这时候会存在一个特殊的转换方式,在强制转换的时候中间不会产生临时变量。
操作方式:
class Name
{
public:
// 默认构造
Name(const string& x)
:_name(x)
{
cout << "Name 默认构造" << endl;
}
Name& operator=(const Name& x)
{
_name = x._name;
return *this;
}
~Name()
{
_name = "";
cout << "~Name 析构函数" << endl;
}
protected:
string _name;
};
class Num : public Name
{
public:
// 默认构造
Num(const int& num = 0,const string& name = "张三")
:_num(num)
,Name("name")
{
cout << "Num 默认构造" << endl;
}
// 拷贝构造
Num(const Num& tem,const string& name)
:_num(tem._num)
,Name("name")
{
cout << "Num 拷贝构造" << endl;
}
// 赋值构造
Num& operator=(const Num& x)
{
Name(x._name); // 在进行赋值构造的时候还要对父类进行父类的构造
_num = x._num;
return *this;
}
~Num()
{
// 调用析构函数的时候应该要先析构子类(派生类) 然后在析构基类(父类)
// 如果先析构基类,如果后面子类仍然使用基类的成员就会出现越界访问
// 由于多态,析构函数的名字会被统一处理成 destructor()
// 父类析构不用自己调用,编译器会自己调用
//Name::~Name();
_name = "";
_num = 0;
cout << "~Num() 析构函数" << endl;
// 派生类(子类)析构完之后编译器会自己调用父类(基类)的析构函数 减少了我们的调用过程
}
protected:
int _num;
};
分析一下,在对子类对象进行构造的时候必须考虑到父类成员,在构造子类对象时我们必须手动写上对父类对象的构造,但是这里的析构函数可不一样,为了了解到有什么不同之处,请大家看下面代码及运行结果:
// 多继承 : 一个子类有两个或以上直接父类时称这个继承关系为多继承
class Student
{
public:
//.....
private:
string _name;
};
class Teacher
{
public:
//......
private:
string _id;
};
class Assistant : public Student, public Teacher
{
public:
//....
private:
int _num;
};
大家看完肯定会有点懵懵的,在这里我为大家准备了一幅图来说明多继承:
2、虚拟继承
在了解虚拟继承时,大家先看下面一段代码及注释来明白为什么要出现虚拟继承这个概念:
class P
{
public:
protected:
string _name; // 这里有一个名字
string _address;
string _gender;
};
// 对于相同的父类(基类)内容使用虚拟继承
class st : public P
{
public:
protected:
string _id;
string _name = "bingbing"; // 这里也有一个名字
};
// 同上
class te : public P
{
public:
protected:
string _name = "lisi"; // 这里也有一个名字
string _id;
};
从上面代码可以看出,st的类继承了P,te的类也继承了P,但是在继承的过程中,P类中有一个_name ,st类中也有_name,在继承完P之后st类中就有两个名字,te类和st类相同,正常情况下我们需要一个名字就好啦,这里就构成了数据冗余和二义性,为了解决这种情况,C++提出了虚拟继承的概念,语法就是在继承的时候加上 virtual 语法图如下:
语法就这么简单,虚拟继承之后就只会有一个 _name,对父类的_name 进行修改,就会修改每个虚拟继承处的_name。
三、继承与对象结合的区别