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++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

C++实现获取本机MAC地址与IP地址

《C++实现获取本机MAC地址与IP地址》这篇文章主要为大家详细介绍了C++实现获取本机MAC地址与IP地址的两种方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实际工作中,项目上常常需要获取本机的IP地址和MAC地址,在此使用两种方案获取1.MFC中获取IP和MAC地址获取