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++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(