当前位置: 首页 > news >正文

【C++】继承(1)

目录

1. 继承的概念及定义

2. 继承类模版

3. 基类和派生类间的转换

3.1 对象赋值

派生类对象-->基类对象(合法,隐式进行)

基类对象-->派生类对象(危险,禁止使用)

3.2 类型转换

向上转换:派生类指针/引用-->基类指针/引用

向下转换:基类指针/引用-->派生类指针/引用

4. 隐藏关系

练习


1. 继承的概念及定义

继承是面向对象编程的三大特性之一,它允许一个类(称为派生类 / 子类)继承另一个类(称为基类 / 父类)的属性和行为,从而实现代码复用和建立类之间的层次关系。

继承的语法

派生类通过 : 指定继承关系,语法如下:

class 派生类名 : 继承方式 基类名 
{//派生类的成员
};

public继承是C++中最常用的继承方式,其核心规则是:

  • 基类的public成员:在子类仍然保持public属性(子类对象可直接访问);
  • 基类的protected成员:在子类中仍然保持protected属性(仅子类内部可访问,子类对象不可直接访问);
  • 基类的private成员:在子类中完全不可见(即使子类内部也不能直接访问,仅基类自己的成员函数可访问)。

Student和Teacher作为Person的特殊类型,通过继承直接复用了Person的属性(姓名、电话等)和行为(identity身份认证),无需在子类中重复定义这些共性内容,仅需扩展各自的特有属性(如Student的_stuid学号,Teacher的title职称)和行为(如study()学习、teaching授课)。

class Person
{
public:// 进入校园/图书馆/实验室刷二维码等身份认证void identity(){cout << "void identity()" << _name << endl;cout << _age << endl; }
protected:string _name = "张三"; //姓名string _address;       //地址string _tel;           //电话
private:int _age = 18;         //年龄	
};//派生类(子类)
class Student : public Person
{
public:	void study() //学习{// ...//cout << _age << endl;//父类的私有成员在子类中不可见,仅父类自己的成员函数可访问,子类不可直接访问cout << _tel << endl; //受保护成员,子类内部可访问}
protected:int _stuid; //学号
};//派生类(子类)
class Teacher : public Person
{
public:	void teaching() //授课{//...}
protected:string title; //职称
};int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}

不同继承方式下派生类的访问权限如下:

基类成员类型public继承protected继承private继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问

public继承是实际开发中最常用的继承方式,几乎很少使用protected/private继承,也不提倡使用。

注意:基类的private成员无论哪种继承方式,派生类都无法直接访问(需要通过基类的piblic/protected成员函数间接访问)。

在C++中,用class定义类时,默认的继承方式是private,用struct定义类时,默认的继承方式是public,不过,为了让代码更清晰、易读,建议显示写出继承方式。

基类Person中各成员的原始权限:

class Person 
{
public:void Print() { cout << _name << endl; }  // public成员(函数)
protected:string _name;  // protected成员(变量)
private:int _age;  // private成员(变量)
};

举例说明不同继承关系下的成员访问权限的变化:

1. public继承

class Student : public Person 
{
public:void Test() {Print();     //合法:子类成员函数可访问基类public成员_name = "张三";  //合法:子类成员函数可访问基类protected成员// _age = 18;   //错误:基类private成员不可见}
protected:int _stunum;
};int main() 
{Student s;s.Print();  //合法:子类对象可访问继承的public成员// s._name = "李四";  //错误:protected成员子类对象不可访问return 0;
}

2. protected继承

class Student : protected Person 
{
public:void Test() {Print();    //合法:子类成员函数可访问protected成员_name = "张三";  //合法:子类成员函数可访问protected成员// _age = 18;   //错误:基类private成员不可见}
protected:int _stunum;
};int main() 
{Student s;// s.Print(); //错误:Print()在子类中是protected,子类对象不可访问return 0;
}

3. private继承

class Student : private Person 
{
public:void Test() {Print();    //合法:子类成员函数可访问private成员_name = "张三";  //合法:子类成员函数可访问private成员// _age = 18;  //错误:基类private成员不可见}
protected:int _stunum;
};// 孙子类(继承Student)
class Graduate : public Student 
{
public:void Test2() {// Print();  //错误:Print()在Student中是private,孙子类不可访问// _name = "李四";  //错误:_name在Student中是private,孙子类不可访问}
};int main() 
{Student s;// s.Print();  //错误:Print()在子类中是private,子类对象不可访问return 0;
}

private与protected的区别

private和protected的核心区别体现在是否允许派生类访问,protected平衡了封装和继承的需需求,允许子类访问但限制外部访问。

private确保了类的内部实现不被外部访问,但继承时,如果基类的某些成员需要被子类使用但又不想暴露给外部,private就不够了,这时候protected就派上用场了。

比如假设设计一个Animal基类,其中“体重”是需要被子类(Dog)使用的属性,但不希望外部代码随意直接修改动物的体重,这时就可以使用protected。

class Animal 
{
protected:int _weight;  //体重:允许派生类访问,不允许外部直接修改
public://外部只能通过接口间接修改,保证合法性(比如体重不能为负)void SetWeight(int w) {if (w > 0) _weight = w;}
};class Dog : public Animal 
{
public://派生类可以使用_weight计算食量(复用基类成员_weight)int CalculateFood() {return _weight * 6;  //假设每公斤体重每天吃6g食物}
};

如果_weight用private,Dog类无法访问_weight,就无法实现CalculateFood() ;如果用public,外部可以直接写dog._weight = -100,破坏数据合法性,protected完美解决了这个矛盾。

  • private成员:仅在当前类的内部可见(类自己的成员函数可访问,任何外部代码包括派生类都不可直接访问)。
  • protected成员:在当前类内部和其派生类内部可见(类自己的成员函数、派生类的成员函数可访问,但类的外部对象不可直接访问)

2. 继承类模版

MyStack<T>继承自std::vector<T>,复用了vector的底层存储能力。

template<class T>
class Mystack : public std::vector<T>
{
public:void push(const T& x){//push_back(x); error! 编译报错:error C3861: “push_back”: 找不到标识符vector<T>::push_back(x); //right!基类是类模板时,需要指定⼀下类域	this->push_back(x);     //两种写法等效 this的类型依赖T,延迟到第二阶段模板实例化时,再去基类中查找该成员	}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};int main()
{Mystack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}

模版类继承时,访问基类成员需使用this->成员名基类名<T>::成员名,避免编译器因模版延迟实例化导致的查找错误。

类模版的编译分为两个阶段:

  • 第一阶段:仅做“语法层面的检查”,不会实例化参数T相关成员。
  • 第二阶段:模版实例化时,编译器才会根据实际传入的模版参数实例化。

上面程序派生类模版Mystack<T>继承基类模版std::vector<T>,基类成员是依赖模版参数T的,在第一阶段时,模版未实例化,编译器无法确定push_back的归属,所以需要显式指定告诉编译器这个成员依赖于模版参数,明确成员来源,否则会报“找不到标识符”的错误。

虽然代码能够正常工作,但不推荐使用public继承实现栈,public继承会暴露基类所有的public成员,用户可以直接调用st.insert()、st.erase()、st[ ]等接口,从而破坏栈的特性。推荐使用私有继承或组合:

  • 私有继承:将std::vector作为私有基类,此时基类的public成员在派生类中变为private,外部无法访问。
  • 组合:将td::vector作为stack的私有成员(“has-a关系”,栈有一个vector)(更推荐的方式)

3. 基类和派生类间的转换

3.1 对象赋值

对象赋值的本质是两个独立对象之间的内容复制,需遵循“派生类包含基类,基类不包含派生类”的内存结构逻辑,仅有一种场景合法且常用。

派生类对象-->基类对象(合法,隐式进行)

将派生类对象中的基类部分(如_name、 _age)复制到基类对象中,派生类特有的成员(如_stuid)会被“切片丢弃”,此过程称为对象切片。

本质:派生类对象s-->隐式类型转换为Person临时对象(切片,丢弃_stuid)-->将临时对象拷贝给基类对象p。

class Person  //基类
{ 
public:string _name;int _age;
};class Student : public Person  //派生类
{ 
public:int _stuid;
};int main()
{Student s;s._name = "张三";s._age = 18;s._stuid = 1001;//派生类对象-->基类对象(会发生“对象切片”)Person p;p = s; //隐式类型转换:派生类对象s隐式类型转换为Person临时对象(切片丢弃_stuid),再将临时对象拷贝给基类对象p//p._stuid;   //错误:p是Person对象,没有_stuid成员return 0;
}

基类对象-->派生类对象(危险,禁止使用)

基类对象的内存中不包含派生类的特有成员,强制赋值时只能复制基类部分到派生类中,而派生类的特有成员会是随机值(未初始化),访问这些成员会触发未定义行为,即使通过static_cast强制转换,也会导致严重问题。

3.2 类型转换

在C++中,基类和派生类之间的转换(也称为“类型转换”),核心围绕“向上转换”(派生类->派生类->基类)“向下转换”(基类->派生类)两者的安全性和使用场景有显著差别。

向上转换:派生类指针/引用-->基类指针/引用

隐式转换,完全安全,是继承场景中最常用的转换。

因为派生类对象包含基类对象,派生类指针/引用指向的内存区域,必然包含基类的完整数据。转换后,基类指针/引用仅“聚焦”于内存中的基类部分,不会越界或访问非法数据,所以安全。

class Person  //基类
{ 
public:string _name;int _age;
};class Student : public Person  //派生类
{ 
public:int _stuid;
};Student s;
Student* s_ptr = &s;
Student& s_ref = s;//隐式向上转换
Person* p_ptr = s_ptr;  // 基类指针指向派生类对象的基类部分
Person& p_ref = s_ref;  // 基类引用绑定派生类对象的基类部分p_ptr->_name = "王五";  // 正确:访问基类成员
// p_ptr->_stuid;       // 错误:基类指针无法解读派生类特有成员

向下转换:基类指针/引用-->派生类指针/引用

这是不安全的,且不能隐式转换,必须显式使用static_cast或dynamic_cast转换。

因为基类对象不包含派生类的特有成员(如Person没有_stuid),若强制转换,访问派生类特有成员会导致未定义行为,所以不安全。

两种显式转换方式:

  • static_cast:编译时转换,不做运行时检查,风险较高。
  • dynamic_cast:运行时转换,仅适用于多态类(包含虚函数的类),会检查转换的有效性,更安全。

4. 隐藏关系

继承中,隐藏关系指的是:派生类中定义了与基类同名的成员(变量或函数)时,派生类的成员会“隐藏”基类的同名成员——即默认情况下,在派生类的作用域内,直接访问该同名成员时,优先访问派生类自己的成员,基类的同名成员会被“屏蔽”,必须通过基类域限定符(::)才能访问。

隐藏的前提:作用域不同+名称相同

隐藏、重载、重写的区别:

  • 重载:同一作用域内,同名函数的参数列表不同(个数 / 类型 / 顺序);
  • 重写:派生类与基类的虚函数,参数列表、返回值、cv 限定符完全相同(多态的核心);
  • 隐藏:不同作用域(基类 vs 派生类),同名成员(变量或函数,函数参数可同可不同);

同名变量的隐藏:无论变量类型是否相同,基类的变量都会被隐藏。

class Base 
{
public:int _a = 10;  //基类的变量_a
};class Derived : public Base 
{
public:double _a = 20.5;  //派生类的变量_a(与基类同名,隐藏基类的_a)
};int main() 
{Derived d;cout << d._a << endl;        // 输出 20.5 默认访问派生类的_a(基类的被隐藏)cout << d.Base::_a << endl;  // 输出 10   通过基类域限定符访问被隐藏的基类_areturn 0;
}

同名函数的隐藏:无论函数的参数列表是否相同,基类的同名函数都会被隐藏。

class Base 
{
public:void show(int x) {cout << "Base::show(int): " << x << endl;  //基类的int版本}
};class Derived : public Base 
{
public:void show(double x) {cout << "Derived::show(double): " << x << endl; //派生类的double版本}
};int main() 
{Derived d;d.show(10);        // 实际运行:输出 Derived::show(double): 10(int→double隐式转换)cout对double类型输出有简化显示的默认行为d.show(3.14);      // 输出 Derived::show(double): 3.14(直接匹配double)d.Base::show(10);  // 输出 Base::show(int): 10(显式访问基类被隐藏的函数)return 0;
}

练习

1. 下面程序中A类和B类中的两个fun构成什么关系?

A.  重载                        B.  隐藏                        C.  没关系

派生类中定义与基类同名的函数(无论参数是否相同),会隐藏基类的同名函数。这里B::fun(int i)隐藏了A::fun(),因此构成隐藏关系

2. 下面程序的运行结果是什么?

A.  编译报错                        B.  运行报错                       C.  正常运行

由于B::fun(int i)隐藏了A::fun(),编译器在B的作用域内,发现B只有func(int),但调用时没有传参,参数不匹配,因此编译阶段直接报错。

class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" << i << endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};

实际开发中应尽量避免派生类与基类成员同名,若必须同名,访问基类成员时务必用基类名::显式指定,避免歧义。

http://www.dtcms.com/a/487804.html

相关文章:

  • 网站 字号 英文国家企业信息管理系统
  • 合肥html5网站建设中文wordpress企业
  • 力扣Hot100--226.翻转二叉树
  • 园岭网站建设百度做广告
  • LeetCode每日一题——矩阵置0
  • 网站规划和建设的基本步骤怎么做属于自己的免费网站
  • 东莞个人网站设计天津平台网站建设企业
  • Python全栈(基础篇)——Day12:函数进阶(闭包+装饰器+偏函数+实战演示+每日一题)
  • 【完整源码+数据集+部署教程】 【运输&加载码头】仓库新卸物料检测系统源码&数据集全套:改进yolo11-DRBNCSPELAN
  • 网站建设发展历程怎样在微信中做网站
  • 阿里巴巴网站推广方法免费网络推广网站大全
  • 信息分类网站建设字节跳动员工人数多少
  • Android 如何开启 16KB 模式
  • VOFA添加取消光标标记使用方法
  • 【学习笔记】大模型慢思考模式的优缺点
  • 精读《JavaScript 高级程序设计 第4版》第12章 BOM
  • 临桂城乡建设局网站开源wordpress
  • BRPC基础使用
  • 如何用网站模板建设网站南京模板建网站哪家好
  • 称多县公司网站建设网上做家教那个网站好
  • 做家装模型的效果图网站宁德市住房和城乡建设局网站
  • Burp Suite抓包软件使用说明1-Http history
  • 买了两台服务器可以做网站吗不起眼的暴利小生意
  • glibc升级到指定版本
  • 做一个智能体搭建复盘吧
  • 销售网站建设的意义企业网站建设策划书 前言
  • 做房产网站在百度推广推广费前端素材网
  • 家政服务网站建设方案建筑建设网站
  • DirectShow帮助文档
  • No032:休眠的智慧——当DeepSeek学会在静默中更新