目录
一、什么是继承
二、继承的方式
1.基类private成员在派生类无论什么继承都不可见
2.基类成员只想在派生类访问,不想在类外访问用protect
3.基类的其他成员在子类的访问方式
4.默认的继承方式
三、基类和派生类对象赋值转换
四.继承中的作用域
五.派生类的默认成员函数
1.默认构造
2.拷贝构造
3.赋值重载
4.析构函数
编辑
六.继承与友元
七、继承与静态成员
一、什么是继承
C++继承的目的是为了让代码可以复用,在之前,我们有代码需要复用的时候,会写一个函数,然后对传递需要的参数给这个函数,也是实现了代码的复用。
C++推出了类这一概念,类里面不仅仅可以存放函数,还可以存放变量,同时我们也可以继承这个类,来使得类里面的数据可以被其他类共享。
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
这里Student类和Teacher类通过public来继承了Person类
Student类和Teacher类是子类,也叫派生类
public是继承方式,选择了共有继承(还有其他继承方式,我们下文再讲)
Person类是父类,也叫做基类
Student类和Teacher类并没有写_name成员和_age成员,但由于继承了Person,也就可以访问并使用Person类里面的成员了。
二、继承的方式
在之前类和对象里,我们讲解了private,和public访问限定符,这里我们有引入了protected访问限定符,同时这三个访问限定符也可以是继承方式。
他们之前的区别是什么呢?
我们对上面这个表做一个总结
1、2、 3最重要
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡 使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
具体什么意思我们一点一点的讲
1.基类private成员在派生类无论什么继承都不可见
这里访问没有问题
无论什么继承方式,基类(父类)的私有成员都无法在派生类和类外访问。
2.基类成员只想在派生类访问,不想在类外访问用protect
3.基类的其他成员在子类的访问方式
基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)
public > protected > private。
访问限定符和继承方式中选两个里面小的那个
举例子,下面是Person类是共有成员,Student类共有继承了Person类,因此新建个Student 类对象可以访问Person类里面的成员。
我们切换为protected继承,Student创建的类对象里的继承下来的成员会变成protected,类外面就无法访问了。
4.默认的继承方式
下面我们没有写继承的方式,直接 :类名,他说有的继承方式就是class或者struct的默认访问限定符, class 为私有,struct为共有。
第5点就不多讲解了,我们在实际开发中一般都是用public继承方式,protected和public访问限定符。
三、基类和派生类对象赋值转换
这里将b给到了a,发生了隐式类型转化。
因此如果使用&(引用)就会报错,因为临时对象具有常性,&需要变量。因此报错。
而在继承当中,也可以写出这样的代码,却不会报错
因为编译器做了切片处理,Student类对象给到Person,会将Student里面的内容给切割掉,只保留继承下来的内容,再给到Person类对象 。
注意:只能派生类对象赋值给基类类对象, 基类对象不能赋值给派生类对象。
四.继承中的作用域
当派生类和基类有着同名对象或者函数时,如果调用会调用自己的那一份数据,基类的数据会被隐藏起来。
如下
如果想访问基类的这份数据,需要再访问的时候添加上作用域
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
五.派生类的默认成员函数
1.默认构造
下面这个例子,在派生类初始化中不能直接使用基类的成员进行初始化,可以通过父类的构造函数来初始化
2.拷贝构造
拷贝构造也需要调用父类的拷贝构造来进行初始化,这里运用了之前我们学到的知识点,子类对象传递给父类对象,发生了切割。
3.赋值重载
赋值重载也可以通过代码服用来完成,注意要加上Person::代表是父类里面的operator=函数,否则会一直调用自己的导致栈溢出。
4.析构函数
我们写了父类和子类的析构
我们只定义了一个Student类对象,发现竟然调用了Person类的构造函数和析构函数,父类的析构函数不需要显示调用,子类析构函数结束后会自动调用父类的析构,析构保证先子后父
这是C++祖师爷这么设计的,如果发生了继承
会先调用父类的构造函数,在调用子类的构造函数,析构时会先析构子类的,再析构父类的。
测试代码
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int num)
: Person(name)
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
: Person(s)
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student operator=(const Student& s)
{
if(&s != this)
{
Person::operator=(s);
_num = s._num;
}
cout << "Student(const Student& s)" << endl;
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
void Test()
{
Student s("Jona", 18);
}
六.继承与友元
友元函数不能类继承,也就是说基类友元不能访问子类私有和保护成员
想要不报错,需要在子类也添加上友元函数。
七、继承与静态成员
静态成员的继承不会重新生成,而是同一份。
图中我们可以看到Person类和Student类的_count地址一样。