1. 继承的概念和定义
1.1 继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。一句话说明:继承=子类复用+拓展父类的功能。
举个简单例子:

说白了继承就是继承"家产"和拥有独立的个性。
通过一个代码详细分析:
cpp
class teacher
{
public:
teacher()
{}
private:
string _name;
string _address;
int _age;
string _id;
string _title;
};
class student
{
public:
student()
{}
private:
string _name;
string _address;
int _age;
string _id;
};
class person
{
public:
person()
{}
int tel = 10086;
protected:
//string _name = "xiaoli";
private:
string _name = "xiaoli";
string _address;
int _age;
string _id;
};
按照之前学的类,定义的话需要定义如上三个类,但是仔细观察你会发现三者有许多共同的部分:名称、地址、年龄、地址等

因此如果通过继承的方式,可以得到:
cpp
class person
{
public:
person()
{}
int tel = 10086;
protected:
//string _name = "xiaoli";
private:
string _name = "xiaoli";
string _address;
int _age;
string _id;
};
class student : public person
{
public:
student()
{
//cout << _name << endl; //私有成员无法访问
cout << tel << endl;
}
private:
int _grade;
};
class teacher : public person
{
public:
teacher()
{
//_name;
}
private:
string _title;
};

问题1:子类继承的父类的成员变量,子类能不能修改?
答案是:要看继承方式:
父类private成员:子类不能直接改,必须用父类提供的函数修改
父类public/protected成员:子类可以直接改
那么什么是继承方式?你这代码里public,protect都是啥意思?🆗接下来就介绍继承方式!
1.2 继承的定义
这里person是基类(也称作父类),student和teacher都是派生类(也称作子类)。


这些方式有什么区别?
|------------------|-------------------|-------------------|-------------------|
| 派生类继承方式 | public继承 | protetced继承 | private继承 |
| 基类的public成员 | 派生类的public成员 | 派生类的protect成员 | 派生类的private成员 |
| 基类的protect成员 | 派生类的protect成员 | 派生类的protect成员 | 派生类的private成员 |
| 基类的private成员 | 派生类的private成员 | 派生类的private成员 | 派生类的private成员 |
1)基类的private成员无论以何种方式继承都不可见。也就是派生类还是会继承基类的私有成员到类里面,但是派生类无法访问和修改它。当然不能说完全不能改,可以间接的修改(比如通过父类的公有成员访问和修改)。
2)基类的protected继承:由于基类private成员在派生类无法访问,于是官方就给出了protected,派生类里面可以访问,但是类外面不能访问。说白了自己能用 + 儿子(子类)能用 + 外人不能用!!
3)基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式==Min(成员在基类的访问限定符,继承方式),public >protected> private。
4)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
cpp
class student : person
{
public:
student()
{
//cout << tel << endl;//私有继承,变成student的私有成员,类外无法访问
}
private:
int _grade;
};
int main()
{
student s1;
//cout << s1.tel << endl; //error
return 0;
}
5)在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
1.3 继承容易混淆的点
查找:继承访问的时候先到子类中找;子类如果未找到,再去父类中继承的部分找。
本质原因:因为子类继承父类时,父类的所有成员变量(public/protected/private)都会实实在在地复制一份,存到子类对象的内存里。因此****子类对象里自带一份完整的父类成员副本
补充1:父类的成员函数并不会复制到子类,因为函数存在代码区,所有对象共用一份。
只有成员变量会复制。补充2:静态成员变量不会复制,静态成员属于类,不属于对象,所有对象共享一份。(详见第6点)
cpp
class Father
{
public:
void test()
{
int s = 10;
}
private:
int a; // 私有,照样占空间
};
class Son : public Father
{
private:
int b;
};
int main()
{
cout << sizeof(Father) << endl; // 4
cout << sizeof(Son) << endl; // 8!证明多了一份 int
}
由此可以观察正式子类存储一份父类的副本!但是增加了test函数后,结果依旧不改变,证实了父类的成员函数不存储!!!
对于查找,举个简单例子:
cpp
// 基类模板继承按需实例化
template<class T>
class stack : public vector<T>
{
public:
void push(const T& x)
{
// 基类模板继承需要实例化,否则找不到push_back
// 具体解释见模板那一节
vector<T>::push_back(x);
// 只有实例化才知道去哪里找
//push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
const T& top()
{
return vector<T>::back();
}
};
大致就是:公有继承vector,当你调用push的时候,先在子类找,找到了就调用vector的push_back函数,如果没有找到,就需要去vector内部查找push。
2. 基类和派生类的转换
(1)public继承的 派生类对象 可以赋值给 基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分
cpp
class person
{
public:
person()
{
}
int tel = 10086;
protected:
string _name = "xiaoli";
string _address;
int _age;
};
class student : public person
{
public:
int tel = 10000;
int count = 0;
private:
string _id;
};
int main()
{
student s1;
cout << s1.tel << endl;
return 0;
}
int main()
{
student s1;
// 把派生类赋值给基类的指针
person* ptr = &s1;
// 把派生类赋值给基类的引用
person& rs = s1;
cout << s1.tel << endl;
cout << ptr->tel << endl;
cout << rs.tel << endl;
//cout << rs.count << endl; //error,person不含有count
//基类不能赋值给子类
person p1;
//student s2 = p1; //error
p1 = s1;
return 0;
}


也就是person* 和 person& 只会把属于person的部分给切走,不属于他的部分他带不走,因此如果强行访问不属于person的内容,编译器会报错!!!
(2)基类不能赋值给派生类。基类(父类)是 "小" 的、通用的;派生类(子类)是 "大" 的、扩展的。直接把基类赋值给派生类,会破坏数据完整性、类型安全,程序会直接出错。可以认为派生类的数据被覆盖,导致数据丢失。
cpp
//基类不能赋值给子类
person p1;
//student s2 = p1; // ×
p1 = s1;// √
**(3)基类的指针或者引用可以通过 ++强制类型转换++赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。来看两个例子:
示例1:基类的指针指向派生类对象
cpp
// 基类
class Animal {
public:
void speak()
{
cout << "动物叫" << endl;
}
};
// 派生类
class Dog : public Animal {
public:
void speak()
{
cout << "汪汪汪" << endl;
}
// 派生类独有函数
void watchDoor()
{
cout << "小狗看门" << endl;
}
};
int main()
{
// 1. 创建一个Dog对象,用基类指针指向它
Animal* animal= new Dog();
// 2. 强制把基类指针转成派生类指针 // √
Dog* dog = (Dog*)animal;
// 3. 安全调用派生类独有函数
dog->speak(); // 正常输出:汪汪汪
dog->watchDoor(); // 正常输出:小狗看门
delete animal;
return 0;
}
注意:这种写法animal虽然是切片,也就是animal本身只能看到(调用)基类成员,但是它指向的内存里100%存储着完整的Dog!!!强转之后完全可以访问Dog独有的成员变量/函数( 因为内存里本来就是完整的Dog,只是之前被基类指针 "遮住了")。
打个比方:带上口罩我叫老王(了解片面),但是去掉口罩我是王五(全面了解)。
示例2:基类的指针指向基类对象
cpp
int main() {
// 1. 创建一个 纯基类对象
Animal* Pa= new Animal(); // 区别在这里
// 2. 强制转成派生类指针(语法允许,但逻辑错误!)
Dog* pdog = (Dog*)Pa;
// 3. 调用派生类函数 → 未定义行为!
pdog->speak(); // 可能乱码、崩溃
pdog->watchDoor(); // 大概率直接崩溃
delete Pa;
return 0;
}
注意:这样写很危险,animal指向的是纯Animal对象,内存里根本没有dog成员和函数。强行转换为dog,就相当于这里的animal里面啥都没有!!
3. 继承中的作用域
3.1 隐藏规则
(1)无论是基类还是派生类都拥有独立的作用域。
(2)如果基类和派生类拥有重名的变量/函数,优先访问派生类的变量/函数(也就是屏蔽基类对同名函数的访问),构成隐藏。(在派生类成员函数中,可以使用基类::基类成员显示访问)
(3)建议在实际场景中不要使用重名变量和对象。
cpp
class person
{
public:
int add(int x, int y)
{
return x + y;
}
protected:
int _num = 111;
string _name = "xiaoli";
};
class student : public person
{
public:
int add(int x, int y)
{
return x * y;
//return person::add(x, y);
}
void Print()
{
cout << " 姓名:"<<_name<< endl;
cout << " 身份证号:" << person::_num << endl;
cout << " 学号: "<<_num<<endl;
// 构成隐藏,如果在同一类域则构成重载
// 隐藏的如果不指定,则会报错
cout << add(10, 5) << endl;
cout << person::add(10, 5) << endl;
}
protected:
int _num = 999;
};
int main()
{
student s1;
s1.add(1, 5);
s1.Print();
return 0;
};
在student中调用add,会优先走x*y,但当显示调用person::add的时候就会走person的add函数!
4. 继承和友元函数
- 什么是友元函数?它是一个不属于某个类的外部函数,但被这个类授权,可以直接访问类里的私有(private)和保护(protected)成员
说白话就是:声明你是我的朋友,你原来无法访问的私有变量可以被我访问!!!(可以用好朋友的东西)
- 友元关系不可以被继承,就比如我是你的朋友,但是你的朋友不一定是我的朋友!!!
比如:基类的友元不能访问派生类的私有成员和保护成员变量!!!
cpp
class sub;
class add
{
public:
friend void test(const add& a, const sub& s);
private:
int _m = 1;
int _n = 2;
public:
static double id;
int a = 10;
};
double add::id = 10;
class sub:public add
{
public:
friend void test(const add& a, const sub& s);
void print()
{
cout << a << endl;
cout << id << endl;
//cout << _n << endl; //不是基类的友元函数,无法访问!
}
//cout << _count << endl;
private:
int _count = 99;
};
void test(const add& a,const sub& s)
{
cout << a._m << endl;
cout << a._n << endl;
cout << s._count << endl;
}
int main()
{
add a;
sub s;
cout << &a.id << endl;
cout << &s.id << endl;
cout << &a.a << endl;
cout << &s.a << endl;
return 0;
}

add函数无法访问sub的私有成员变量,虽然都是test的好朋友但是add和sub并不是好朋友!!
5. 继承与静态成员
父类定义的静态成员变量存储在静态区,也就是只有一份;
整个继承体系里面只有一个这样的成员,无论有多少个子类都不会复制!!
cpp
class Father
{
public:
void test()
{
int s = 10;
}
private:
int a; // 私有,照样占空间
static int sum;
};
int Father::sum = 0;
class Son : public Father
{
private:
int b;
};
int main()
{
cout << sizeof(Father) << endl; // 4
cout << sizeof(Son) << endl; // 8!证明多了一份 int
}

这里需要注意:类里面的静态成员变量是类里面声明,类外面定义!!
根据运行结果可以发现,静态成员只有一份!!
下节课会继续介绍继承,主要是派生类的默认成员函数,虚继承,组合与继承的知识!!如果对你有帮助的话,点赞收藏一下吧~