C++ 第8章 继承

2024-09-07 04:48
文章标签 c++ 继承

本文主要是介绍C++ 第8章 继承,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

继承(Inheritance)是面向对象程序设计中软件重用的关键技术。

8.1 类之间的关系

一个大的应用程序,通常由多个类构成,类与类之间互相协同工作。

class Vehicle
{int wheels;double weight;double loading;
public:void initialize(int in_wheels, double in_weight);int get_wheels();double get_weight();double get_loading();
};class Car:public Vehicle
{int passenger_load;public initialize(int in_wheels, doublie in_weight, int people=4);int passengers();
}

前驱结点称为基类(或父类,超类)
后断结点称为派生类(或子类)

8.2 基类和派生类

C++中,描述类继承关系的语句格式:
class 派生类名 : 基类名表

数据成员和成员函数说明
};
其中,“基类名表”的语句格式:
访问控制 基类名1, 访问控制 基类名2,…,访问控制 基类名。
“访问控制”是表示继承权限的关键字,称为访问描述符。可以是:
public 公有继承
private 私有继承
protected 保护继承
如果省略访问描述符,则C++认为是私有继承。

8.2.1 访问控制

一个派生类的成员由两部分组成,一部分从基类继承过来,另一部分自已定义,即创建一个派生类对象时,系统会建立所有继承的和自身定义的成员。
一个派生类公有继承一个基类时,基类中所有公有成员(由public定义的数据成员或成员函数)成为派生类的公有(public)成员,基类中所有保护成员(由protected定义的数据成员或成员函数)成为派生类的保护(protected)成员。
一个派生类私有继承一个基类时,基类中所有公有成员和保护成员同时成为派生类的私有(private)成员。
一个派生类保护继承一个基类时,基类中所有公有成员和保护成员同时为派生类的保护(protected)成员。
1.公有继承

#include <iostream>
#include <cstring>
#include <cmath>using namespace std;class A
{public:void get_XY(){cout<<"Enter two number of x,y:";cin>>x>>y;}void put_XY(){cout<<"x = "<<x<<",y = "<<y<<"\n";}protected:int x,y;
};class B:public A
{public:int get_S(){ return s; }void make_S(){ s = x * y; } //使用基类数据成员x,yprotected:int s;
};class C:public B
{public:void get_H(){cout <<"Enter a number of h:";cin >> h;} int get_V(){return v;}void make_V(){make_S();       //使用基类成员函数v = get_S()*h;  //使用基类成员函数}protected:int h,v;
};int main()
{A objA;B objB;C objC;cout<<"It is object_A:\n";objA.get_XY();objA.put_XY();cout <<"It is object_B:\n";objB.get_XY();objB.make_S();cout<<"S = "<<objB.get_S()<<endl;cout<<"It is object_C:\n";objC.get_XY();objC.get_H();objC.make_V();cout<<"v = "<<objC.get_V()<<endl;
}

在派生类中,通过this指针调用基类的成员函数
void make_V()
{
make_S(); //this->make_S()
v = get_S() * h; //this->get_S()
}

测试派生类对象继承基类的私有数据成员:

#include <iostream>
using namespace std;
class A
{public:A() { x = 1; }int out() { return x; }void addX() { x++ ;}private:int x;
};class B : public A
{public:B()         // B类构造函数{ y = 1; }int out()   // B类成员函数{ return y; } //返回this->y的值void addY(){ y++; }private:int y;
};int main()
{A a;cout<<"Structed a:\n";cout<<"a.x = "<<a.out()<<endl;B b;cout<<"Structed b:\n";cout<<"b.x = "<<b.A::out()<<endl; //输出b.xcout<<"b.y = "<<b.out()<<endl;  //输出b.ycout<<"Object b data + 1:\n";b.addX();   //b.x++b.addY();   //b.y++cout<<"b.x="<<b.A::out()<<endl; //输出b.xcout<<"b.y="<<b.out()<<endl;    //输出b.y
}

2.私有继承
以私有方式继承的派生类,基类的public和protected成员会成为派生类的私有成员,即基类中定义的public和protected成员只能在私有继承的派生类中可见,而不能在类外使用。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>using namespace std;class A
{public:void get_XY(){cout<<"Enter two numbers of x and y:";cin>>x>>y;};void put_XY(){cout<<"x = "<<x<<",y = "<<y<<endl;};protected:int x,y;
};class B :private A
{public:int get_S(){ return s;};void make_S(){get_XY();   //调用基类成员函数s = x * y;};private:int s;
};int main()
{B objB;cout<<"It is object_B:\n";objB.make_S();cout<<"S = "<<objB.get_S()<<endl;
}

3.保护继承
保护继承把基类的公有成员和保护成员作为派生类的保护成员,使其在派生类中被屏蔽。保护继承和私有继承方式在程序设计中应用较少。

4.访问声明
C++提供一种访问调节机制,使一些本来在派生类中不可见的成员变为可访问的,称为访问声明。

class B:private A
{   public:A::get_XY; //声明继承的成员函数名int get_S(){return s;};void make_S(){ s = x * y;};private:int s;  
};int main()
{B objB;objB.get_XY(); //调用调整后的成员函数objB.make_S();cout<<"S = "<<objB.get_S()<<endl;
}

访问声明的格式:
基类名::成员
注意以下事项:
(1)访问声明仅调整名字的访问权限;
当被声明对象是数据成员时,不可说明为任何类型;当被声明对象为成员函数时,只能是函数名本身,不能带参数和返回类型说明:

class B
{public:int c;//...
};
class D:private B
{int d;public :int B::c //错误,带类型
}

(2)访问声明不允许在派生类中降低或提升基类成员的可访问性

class B
{public: int a;private: int b;protect: int c;
};
class D:private B
{public:B::a; //正确B::b; //错误,私有成员不能用于访问声明protectd:B::c; //正确B::a; //错误,不能降低基类成员的可访问性    
};

(3)对重载函数名的访问声明将调整基类所有同名函数的访问域
a.调整同名的重载函数

class X
{pubic:f();f(int);
};class Y:private X
{public:X::f; //使X::f()和X::f(int)在Y中都为公有的
};

b.不同访问域的重载函数名不能用于访问声明

class X
{private:f(int);public:f();
};
class Y:private X
{X::f; //错误,访问声明具有二义性,不能调整其访问性
};

c.派生类中与基类名字相同的成员不可调整访问权限

class X
{public:f();
};
class Y:private X
{public:void f(int);X::f(); //错误,f有两次说明,不能调整其访问权限
};

8.2.2 重名成员

C++允许派生类的成员与基类成员重名。在派生类中访问重名成员时,屏蔽基类的同名成员。如果要在派生类中使用基类的同名成员,可以显式地使用作用域符指定:
类名::成员

1.重名数据成员

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdlib>using namespace std;class Base
{public:int a, b;
};class Derived :public Base
{public:int b,c;
};int main()
{Derived d;d.a = 1;d.Base::b = 2;  //Base::b 使用的是Base类的数据成员bd.b = 3;        //这里使用的是Derived类的数据成员bd.c = 4;cout<<"d.a="<<d.a<<"\td.Base::b="<<d.Base::b<<"\td.b="<<d.b<<"\td.c"<<d.c<<endl;
}

2.重名成员函数

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>using namespace std;
class A
{public:int a1, a2;A(int i1=0, int i2 = 0){ a1 = i1; a2 = i2;}void print(){cout<<"a1="<<a1<<"\ta2="<<a2<<endl;}
};
class B:public A
{public:int b1, b2;B(int j1=1, int j2=2){ b1 = j1; b2 = j2;}void print()    //定义同名函数{cout<<"b1="<<b1<<"\tb2="<<b2<<endl;}void printAB(){A::print(); //派生类对象调用基类版本同名成员函数print();    //派生类对象调用自身的成员函数}
};int main()
{B b;b.A::print();b.print();b.printAB();
}

8.2.3 派生类中访问静态成员

如果在基类中定义了静态成员,这些静态成员将在整个类体系中被共享,根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质。
在派生类中访问静态成员:

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdlib>using namespace std;class B
{public:static void Add(){i++;}static int i;void out(){cout<<"static i="<<i<<endl;}
};int B::i=0;class D:private B
{public:void f();
};void D::f()
{i = 5;  //i是类D的私有静态数据成员,在类中可见Add();  //Add()是类D的私有静态成员函数,在类中可见B::i++;B::Add();
}int main()
{B x;D y;x.Add();x.out();y.f();cout<<"static i="<<B::i<<endl; //正确,i是类B的公有静态数据成员cout<<"static i="<<x.i<<endl; //正确,i是类B的公有静态数据成员//cout<<"static i="<<y.i<<endl; //错误,i是类D的私有静太数据成员
}

8.3 基类的初始化

在派生类创建对象时,能够通过派生类的构造函数将指定参数传递给基类的带参构造函数。派生类的构造函数使用冒号语法的参数初始式实现这种功能:
构造函数名(变元表):基类(变元表),数据成员1(变元表),…,数据成员n(变元表)
类继承关系中构造函数的执行顺序:

#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstring>using namespace std;class Base
{public:Base(){cout<<"Base Created.\n";};
};class D_class:public Base
{public:D_class(){cout<<"D_class Created.\n";};
};int main()
{D_class d;
}//运行结果
Base Created.       //首先,执行基类构造函数
D_class Created.    //其次,执行派生类构造函数

对基类数据成员进行初始化:

#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstring>using namespace std;class Base
{public:int x;Base(int i):x(i){}
};class Derived:public Base
{int a;public:Derived(int j):a(j*5),Base(2){cout<<"Base::x="<<x<<"\tDerived::a="<<a<<endl;}
};int main()
{Derived d(3);
}//运行结果
Base::x=2   Derived::a=15

8.4 继承的应用实例

8.5 多继承

一个派生类只有一个直接基类,称为“单继承”。一个类可以从多个基类派生出来,即一个类有多个直接基类,称为“多继承”。
多继承说明只需在派生类名的冒号”:”之后跟上用逗号分隔的基类名列表即可。

class B1
{//...
};
class B2
{//...
};class C:public B1, public B2
{//...
};

8.5.1 多继承的派生类构造和访问

多个基类的派生类构造函数可以通过继承路径调用基类构造函数,执行顺序与单继承构造函数情况类似,多个直接基类,由派生类时指定的顺序执行构造函数。

Base1.h
#ifndef BASE1_H
#define BASE1_H
class Base1
{public:Base1(int x){value = x;}int getData()const{return value;}protected:int value;  
};
#endif//Base2.h
#ifndef BASE2_H
#define BASE2_H
class Base2
{public:Base2(char c){letter = c;}char getData()const{return letter;}protected:char letter;        
};
#endif//Derived.h
#ifndef DERIVED_H
#define DERIVED_H
class Derived:public Base1, public Base2 //公有继承Base1,Base2
{friend ostream &operator <<(ostream &, const Derived &);public:Derived(int , char, double);double getReal()const;private:double real;
}//派生类的构造函数
Derived::Derived(int i, char c, double f):Base1(i), Base2(c),real(f){}double Derived::getReal()const
{return real;
}ostream &operator<<(ostream &output, const Derived & d)
{output<<" Interger:"<<d.value<<"\n Character:"<<d.letter<<"\n Real number:"<<d.real;return output;
}
#endif//main.cpp
#include <iostream>
using namespace std;
#include "Base1.h"
#include <Base2.h>
#include <Derived.h>int main()
{Base1 b1(10);Base2 b2('k');Derived d(2, 'A', 2.5);
}

8.5.2 虚继承

C++中,一个类不能被多次说明为一个派生类的直接基类,便可以不止一次地成为间接基类。
1.非虚继承

class B
{public:int x;//...
};class B1:public B
{//...};class B2:public B1
{//...};class D:public B1, public B2
{public:void fun(){x=0;}
};

类B两次成为类D的间接基类。这意味着,D类对象将生成两份从B类继承的类据成员:由B1继承的B类成员和由B2继承的B类成员。因为在D类对象中有两个继承B类的成员副本,所以称B是非虚基类。

#include <iostream>
using namespace std;
class B
{public:B(){cout<<"Constructor called: B\n";};~B(){cout<<"Destructor called: B\n";}int b;
};class B1:public B
{public:B1(){cout<<"Constructor called: B1\n";}~B1(){cout<<"Destructor called: B1\n";}int b1;
};class B2:public B
{public:B2(){cout<<"Constructor called: B2\n";}~B2(){cout<<"Destructor called: B2\n";}int b2;
};class D:public B1, public B2
{public:D(){cout<<"Constructor called: D\n";}~D(){cout<<"Destructor called: D\n";}int d;
};void test()
{D dd;dd.B1::b = 5;dd.B2::b = 10;dd.b1 = 25;dd.b2 = 244;dd.d  = 567;cout<<"dd.B1::b="<<dd.B1::b<<"\tdd.B2::b="<<dd.B2::b<<"\n";cout<<"dd.b1="<<dd.b1<<"\tdd.b2="<<dd.b2<<"\tdd.d="<<dd.d<<endl;
}int main()
{test();
}//程序运行结果:
Constructor called: B
Constructor called: B1
Constructor called: B
Constructor called: B2
Constructor called: D
dd.B1::b=5  dd.B2::b=10
dd.b1=25    dd.b2=244   dd.d=567
Destructor called: D
Destructor called: B2
Destructor called: B
Destructor called: B1
Destructor called: B

2.虚继承
通常希望建立D类对象dd时,只要一个dd.b的版本,避免产生二义性。需要把B1和B2对B的继承说明为“虚继承”。在类继承的关键字前添加关键字virtual.
B1,B2类虚继承B类,B是它们的虚基类:

class B
{//...};class B1:virtual public B
{//...};class B2:virtual public B
{//...};

虚继承的测试:

#include <iostream>
using namespace std;class B
{public:B(int x=10){b = x;cout<<"Constructor called:B\n";}~B(){cout<<"Destructor called:B\n";}int b;
};class B1:virtual public B
{public:B1(int x1 = 11, int y1 = 22):B(x1){b1 = y1;cout<<"Constructor called:B1\n";}~B1(){cout<<"Destructor called:B1";}int b1;
};class B2:virtual public B
{public:B2(int x2 = 12, int y2 = 22):B(x2){b2 = y2;cout<<"Constructor called:B2\n";}~B2(){cout<<"Destructor called:B2";}int b2;
};class D:public B1, public B2
{public:D(int i=1, int j1=2, int j2=3,int k=4):B(i),B1(j1),B2(j2){d = k;cout<<"Constructor called:d\n";}~D(){cout<<"Destructor called:D\n";}int d;
};void test()
{D objD;cout<<"objD.b="<<objD.b<<endl;cout<<"objD.b1="<<objD.b1<<"\tobjD.b2"<<objD.b2<<"\tobjD.d"<<objD.d<<endl;B1 objB1;   cout<<"objB1.b="<<objB1.b<<"\tobjB1.b1="<<objB1.b1<<endl;
}int main()
{test();
}//运行结果
Constructor called:B
Constructor called:B1
Constructor called:B2
Constructor called:d
objD.b=1
objD.b1=22  objD.b222   objD.d4
Constructor called:B
Constructor called:B1
objB1.b=11  objB1.b1=22
Destructor called:B1
Destructor called:B
Destructor called:D
Destructor called:B2
Destructor called:B1
Destructor called:B

这篇关于C++ 第8章 继承的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1144105

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(