一.继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
下面的student类中,Student是子类,也叫派生类,Person是父类,也叫基类
继承方式和访问限定符
1.public 2.private 3.protected
他们三三组合,共同构成了9中关系
但其实,真真用到的只有public继承与基类public,protected结合这两种关系,其他方式用的很少
二.基类和派生类赋值转换
两个不同类型的对象之间一般不能相互赋值,或者能相互赋值但是要发生类型转换,那么,一个子类和他的一个父类能相互转化吗,以一个例子为例
#include<string>
using namespace std;
class Person
{
public:
string _name;
string _sex;
int age;
}
class Student:public Person
{
public:
int _No;
}
int main()
{
Person a;
Student b;
a=b;//可以发生
b=a;//不能发生
return 0;
}
其中,Person是父类,Student是子类,子类对象可以赋值给父类,但是父类对象不能赋值给子类对象。其中子类对象赋值给父类叫做向上转换,反之,父类对象赋值给子类对象叫做向下转换,那么为什么向上转换可以,向下转换不行呢?
我们观察上图,显然,如果我们把Person对象赋值给Student对象,那么_No的值是什么,随机值吗?显然这是不行的,所以语法规定不能进行向下转换,那么向上转换自然就可行,因为子类的信息足够多,当然,也可以把子类看作是特殊的父类来理解。还有一点要注意,这里发生的赋值转化不是强制类型转化或者隐式类型转化,而是一种赋值兼容,或者叫切割,他的特点是会切割子类特有的部分,直接用子类中父类的那部分去进行拷贝,而不会产生临时变量,以下是证明
int main()
{
int a=10;
double& b=a;//会报错,因为会发生隐式类型的转换,产生临时变量,而临时变量具有常性,必须使用const double& 来接受,否则会因权限扩大而报错
Person A;
Student B;
Person& pA=B;//不会报错,说明不是隐式类型的转换
return 0;
}
还有我觉得比较有意思的一点是,如果我用父类的引用或指针去接受子类的对象,那么我们可以通过这个父类的指针或引用改变子类中父类的那部分成员变量,其实那样是给子类父类的那部分引用和指针
三.继承中的作用域
class person
{
public:
void print()
{
cout<<"person"<<endl;
}
int _num=100;
}
class student: public person
{
public:
void print()
{
cout<<"studen"<<_num<<endl;
}
int _num=999;
}
int main()
{
sudent s;
s.print();
return 0;
}
这其中,子类和父类能写相同的成员变量和函数吗,这是可以的,其实,编译器会首先在局部域找,然后分别是子类,父类,全局域寻找成员变量,如果找到,就会停止寻找,如果都找不到,就直接报错。所以如果直接打印_num,打印出的值应为999,这个就是隐藏,成员函数也是一样的,不过函数需特别注意,如果是下面的情况,也会报错
class person
{
public:
void print(int)
{
cout<<"person"<<endl;
}
int _num=100;
}
class student: public person
{
public:
void print()
{
cout<<"studen"<<_num<<endl;
}
int _num=999;
}
int main()
{
sudent s;
s.print(1);//也会报错,因为函数名相同,构成隐藏,如果,而父类中的print没有形参,就会报错
return 0;
}
还有一点,请问子类中和父类中的同名函数是否构成函数重载,答案是否定的,因为构成函数重载的前提条件是在同一个域里面,他们构成的时隐藏或重定义。
四.派生类的默认成员函数
class person
{
public:
person(const string& s="",int age=0)
:_name(s)
,_age(age)
{}
person(const person& p)
:_name(p._name)
,_age(p._age)
{}
person& operator=(const person& p)
{
if(this!=&p)
{
_name=p._name;
_age=p._age;
}
return *this;
}
string _name;
int _age;
};
class Student:public person
{
public:
Student(const string& s,int age,int id)
:person(s,age)//不能写成_name(s),_age(age),C++不允许分开写,而是把父类当做一个整体
,_id(id)
{}
Student(const Student& s)
:person(s)//切片,同时不能不写,不写会调用默认构造,而不是拷贝构造
,_id(s._id)
{}
Student& operator=(const Student& s)
{
if(this!=&s)
{
person::operator=(s);//隐藏,要调用父类的同名函数,要指定类域
_id=s._id;
}
return *this;
}
int _id;
};
int main()
{
Student s("lijian",18,1234556);
return 0;
}
五.继承与友元
六. 继承与静态成员
七.复杂的菱形继承及菱形虚拟继承

struct A
{
int _a=1;
};
struct B : A
{
int _b = 2;
};
struct C : A
{
int _c = 3;
};
struct D :B, C
{
int _d=4;
};
int main()
{
D a;
a._a=2;//在父类B和C中,都有一个_a,编译器不至道防问谁,解决办法,指定类域,改为a.B::_a=2或a.C::_a=2,这就是二义性问题,虽然可以这样解决,但是数据冗余无法解决
return 0;
}
为解决该问题,C++引入了虚继承,以下是实例
struct A
{
int _a=1;
};
struct B : virtual A//注意,关键字virtual在继承交点处的前面写
{
int _b = 2;
};
struct C : virtual A
{
int _c = 3;
};
struct D :B, C
{
int _d=4;
};
int main()
{
D a;
a._a=2;
return 0;
}


