✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】
目录
1.继承的概念及定义
1.1继承的概念
1.2 继承定义
1.2.1定义格式
1.2.2继承关系和访问限定符
1.2.3继承基类成员访问方式的变化
2.基类和派生类对象赋值转换
3.继承中的作用域
1.继承的概念及定义
1.1继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象
程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继
承是类设计层次的复用。
下面是父类与子类的基本概念:
- 父类(基类):父类是一个更一般、更抽象的类,它定义了一组属性和方法,这些属性和方法会被其子类所继承。父类通常用于表示一种广泛的、更通用的概念或对象。
- 子类(派生类):子类是一个更具体、更特殊的类,它继承了父类的属性和方法,并可以添加新的属性或 覆盖(重写) 父类的方法。子类通常用于表示一种更具体、更特殊的概念或对象,这些对象属于父类所定义的广泛类别中的一部分。
下面我们可以通过学校的成员举例,学校有学生,老师,宿管,保安等成员,这些成员都有相同的特性,有自己的名字,年龄,家庭地址,但是因为身份不同也会有不同的属性,例如学生有学号,宿舍号,老师有工号,把办公室等等,这种有相同属性又有不同属性的情况则可以通过继承来进行描述,如下图:
代码举例:
// 父类/基类
class Person
{
public:
void Print()
{
cout << "_name = " << _name << endl;
cout << "_age = " << _age << endl;
}
protected:
string _name = "jack";
private:
int _age = 18;
};
// 子类/派生类,继承Person
class Student :public Person
{
protected:
int _stuid;
};
// 子类/派生类,继承Person
class Teacher : public Person
{
protected:
int _jobid;
};
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。
下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。
int main()
{
Student s;
s.Print();
Teacher t;
t.Print();
return 0;
}
1.2 继承定义
1.2.1定义格式
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。
1.2.2继承关系和访问限定符
1.2.3继承基类成员访问方式的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected 成员 | 派生类的private 成员 |
基类的protected 成员 | 派生类的protected 成员 | 派生类的protected 成员 | 派生类的private 成员 |
基类的private成 员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可 见 |
总结:
1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私
有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
都不能去访问它。
父类/基类
class Person
{
public:
void print()
{
cout<<_age<<endl;
}
// 成员访问限定符为私有
private:
int _age = 18;
};
子类 /派生类
//class Student : private Person //继承方式为私有
//class Student : protected Person //继承方式为保护
class Student : public Person //继承方式为公有
{
protected:
int _stuid;
};
主函数
int main()
{
Student s;
//cout << s._age << endl;// 基类的私有成员无法访问
s.print();// 虽然不能直接访问父类的private成员,但是可以父类的print函数间接访问
return 0;
}
监视窗口查看成员
测试结果
理解:
基类的私有成员不能直接进行访问,但是可以通过父类的公有成员函数间接进行访问。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
父类/基类
class Person
{
protected:
string _name = "jack";
private:
int _age = 18;
};
子类/派生类
class Student : public Person
{
public:
void print()
{
cout << _name << endl;
//cout << _age << endl;// 屏蔽私有成员则可以访问
}
protected:
int _stuid;
};
主函数
int main()
{
Student s;
s.print();// 派生类中可以访问父类protected成员
return 0;
}
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected
> private。
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。
父类/基类
class Person
{
protected:
string _name = "jack";
private:
int _age = 18;
};
子类/派生类
// struct的默认访问限定符和默认继承方式均为public
// class 的默认访问限定度和默认继承方式均为private
//class Student : Person
struct Student : Person
{
void print()
{
cout << _name << endl;
}
protected:
int _stuid;
};
主函数
int main()
{
Student s;
s.print();
return 0;
}
5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强。
2.基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。(公有继承才可以,且不产生临时对象)
class Person
{
public:
string _name = "jack";
private:
int _age = 18;
};
class Student : public Person
{
protected:
int _stuid;
};
切片/切割
int main()
{
//切割/切片赋值兼容 子类赋值给父类
Student s;
Person p = s;//1.子类对象赋值给父类对象
Person& ref = s;//2.子类对象赋值给父类的引用
Person* ptr = &s;//3.子类对象赋值给父类的指针
ref._name += 'x';//ref的修改会使子类s修改,证明不会产生临时对象
ptr->_name += 'y';
return 0;
}
基类对象不能赋值给派生类对象。
class Person
{
public:
string _name = "jack";
private:
int _age = 18;
};
class Student : public Person
{
protected:
int _stuid;
};
int main()
{
Student s;
Person p;
s = p;//基类对象不能赋值给派生类对象。
return 0;
}
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。
3.继承中的作用域
1. 在继承体系中基类和派生类都有独立的作用域(可以创建同名成员)。
class Person
{
public:
protected:
string _name;
int _age;
int _num = 0;
};
class Student : public Person
{
public:
private:
int _stuid;
int _num = 1;
};
int main()
{
Student s;//默认访问子类/派生类
return 0;
}
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
class Person
{
public:
void fun()
{
cout << "_name = " << _name << endl;
cout << "_age = " << _age << endl;
}
protected:
string _name;
int _age;
int _num = 0;
};
class Student : public Person
{
public:
void fun()
{
//Person的_num 与 Student的_num构成隐藏关系
//父类子类成员变量均有_num
cout << _num << endl;
//访问不报错,默认访问子类 局部域 全局域 命名空间域 类域(基类 派生类)
cout << Person::_num << endl;//指定基类域 ::显示访问
}
private:
int _stuid;
int _num = 1;
};
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
class A
{
public:
void func()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void func(int i)
{
func();
cout << "func(int i)->" << i << endl;
}
};
B中的func和A中的func 不是构成重载,因为不是在同一作用域。
B中的func和A中的func 构成隐藏,成员函数满足函数名相同就构成隐藏 。
class B : public A
{
public:
void func(int i)
{
func();
//A::func();//指定域调用则可以
cout << "func(int i)->" << i << endl;
}
};
func()函数与父类func()函数构成隐藏,根据函数调用的就近原则,会先调用子类的func函数,但是发现参数不匹配,因此会编译报错。解决办法是通过指定作用域调用。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。