C++——继承与派生
1、类的继承与派生 保持已有类的特性而构造新类的过程成为继承;
在已有类的基础上新增自己的特性而产生新类的过程称为派生;
被继承的已有类为基类;派生出的新类成为派生类。继承和派生其实是一回事。
继承的目的是实现代码的重用,派生的目的是当新的问题出现的时候,原有的程序不能解决时,需要对原程序进行改造。派生类的声明: class 派生类名:继承方式 基类名{成员声明;}
不同的继承方式的影响主要体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限
三种继承方式:公有、私有和保护。
公有继承:基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可以直接访问;
派生类的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象只能访问基类的public成员。
//Rectangle.h
class Point //基类Point类的声明
{
public: //公有函数成员
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private: //私有数据成员
float X,Y;
};
class Rectangle: public Point //派生类声明部分
{
public: //新增公有函数成员
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //调用基类公有成员函数
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据成员
float W,H;
};//End of Rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
私有继承:基类的public和protected成员以private身份出现在派生类中,但是基类的private成员不可以直接访问;派生类的成员函数可以直接访问基类的public和protected成员,但不能直接访问private成员;通过派生类的对象不能直接访问基类中的任何成员。
//rectangle.h
class Point //基类声明
{
public:
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private:
float X,Y;
};
class Rectangle: private Point //派生类声明
{
public: //新增外部接口
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //派生类访问基类公有成员
void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}//自己重新定义一个move函数,其实是间接调用基类函数,
//为的是方便在类外(主函数中)使用move函数。
float GetX() {return Point::GetX();}
float GetY() {return Point::GetY();}
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据
float W,H;
};
//End of rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置,使用的是派生类的函数。
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
保护继承:基类的public和protected成员都已protected身份出现在派生类中,但基类的private成员不可直接访问;派生中成员函数可以直接访问类中的public和protected成员,但是不可以直接访问private成员;通过派生类的对象不能直接访问基类的任何成员。
protected成员的特点与作用:在类外通过类的对象是不可以访问的,对于派生类来说,在派生类中是与public一样的,既实现了数据隐藏,又方便继承,实现了代码重用。
class A{protected:int x;} …… class A{protected:int x;} class B:public A{public: void f();}
int main(){ A a; a.x=5;//错误} …… void B:f(){x=5;}//正确
类型兼容规则:一个公有派生类的对象在使用上被当做基类的对象,反之则禁止。
表现在:(轮胎和汽车的关系),派生类的对象含有的信息大于基类。
派生类的对象可以被赋值给基类对象;
派生类的对象可以初始化基类的引用;
指向基类的指针也可以指向派生类。
通过基类的对象名、指针只能使用从基类继承的成员。
#include <iostream>
using namespace std;
class B0 //基类B0声明
{
public:
void display(){cout<<"B0::display()"<<endl;} //公有成员函数
};
class B1: public B0 //公有派生类B1声明
{
public:
void display(){cout<<"B1::display()"<<endl;} //公有成员函数
};
class D1: public B1 //公有派生类D1声明
{
public:
void display(){cout<<"D1::display()"<<endl;} //公有成员函数
};
void fun(B0 *ptr) //普通函数
{ //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
}
int main() //主函数
{
B0 b0; //声明B0类对象
B1 b1; //声明B1类对象
D1 d1; //声明D1类对象
B0 *p; //声明B0类指针
p=&b0; //B0类指针指向B0类对象
fun(p);
p=&b1; //B0类指针指向B1类对象
fun(p);
p=&d1; //B0类指针指向D1类对象
fun(p);
}//运行结果:
B0::display()
B0::display()
B0::display()
多继承时派生类的声明:class 派生类名:继承方式1 基类名1,继承方式2 基类2,……{成员声明;};
每一个继承方式只用于限制对紧随其后的基类的继承。
class A{
public:
void setA(int);
void showA();
private:
int a;
};
class B{
public:
void setB(int);
void showB();
private:
int b;
};
class C : public A, private B{
public:
void setC(int, int, int);
void showC();
private:
int c;
};
void A::setA(int x)
{ a=x; }
void B::setB(int x)
{ b=x; }
void C::setC(int x, int y, int z)
{ //派生类成员直接访问基类的
//公有成员
setA(x);
setB(y);
c=z;
}
//其他函数实现略
int main()
{
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
obj.showC();
// obj.setB(6); 错误,私有继承的B,在类外不可以直接访问。
// obj.showB(); 错误
return 0;
}
继承时的构造函数:基类的构造函数是不被继承的,因为基类的构造函数不足以为派生类新增的成员初始化,派生类需要声明自己的构造函数,声明构造函数时,只需要对本类中的新增成员初始化,对继承来的基类的成员初始化,自动调用基类的构造函数完成;派生类的构造函数需要给基类的构造函数传递参数。
单继承:派生类名::派生类名(基类所需形参,本类成员所需形参)基类名(参数表){本类成员初始化;};
多继承:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数){本类成员初始化赋值语句;};
当基类中声明有默认形式的构造函数或者没有声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;若基类没有声明构造函数,派生类中也可以不声明,全部采用默认构造函数;当基类中声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类的构造函数。
多继承且有内嵌对象(组合类对象成员)时的构造函数:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数),对象数据成员的初始化{本类成员初始化赋值语句;};
构造函数的调用顺序:调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);调用成员对象的构造函数,调用顺序按照它们在类中的声明顺序;派生类的构造函数体中的内容。
拷贝构造函数:若建立派生类对象时调用默认拷贝构造函数,则自动调用基类的默认构造函数;若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。C::C(C&b):B(b){};
#include <iostream>
using namespace std;
class B1 //基类B1,构造函数有参数
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;}
};
class B2 //基类B2,构造函数有参数
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;}
};
class B3 //基类B3,构造函数无参数
{
public:
B3(){cout<<"constructing B3 *"<<endl;}
};
class C: public B2, public B1, public B3 //派生新类C
//注意基类名的顺序
{
public: //派生类的公有成员
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//注意基类名的个数与顺序
//注意成员对象名的个数与顺序
private: //派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
C obj(1,2,3,4);//传递参数顺序,首先基类:B2(b),B1(a),B3不要参数,接着是内嵌成员memberB1,memberB2,memberB3
}//运行结果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
析构函数也不被继承,需要自行声明,声明方法与一般析构函数一样,不需要显式的调用基类的析构函数,系统会自动调用,析构函数的调用顺序和构造函数相反。
#include <iostream>
using namespace std;
class B1 //基类B1声明
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;} //B1的构造函数
~B1() {cout<<"destructing B1 "<<endl;} //B1的析构函数
};
class B2 //基类B2声明
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;} //B2的构造函数
~B2() {cout<<"destructing B2 "<<endl;} //B2的析构函数
};
class B3 //基类B3声明
{
public:
B3(){cout<<"constructing B3 *"<<endl;} //B3的构造函数
~B3() {cout<<"destructing B3 "<<endl;} //B3的析构函数
};
class C: public B2, public B1, public B3 //派生类C声明
{
public:
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//派生类构造函数定义
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{ C obj(1,2,3,4);
}
//运行结果
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
2、同名隐藏规则
当派生类中和基类中有相同成员时,若没有强行指明,通过派生类对象使用的派生类的同名成员;如果通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。
#include <iostream>
using namespace std;
class B1 //声明基类B1
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B1"<<endl;}
};
class B2 //声明基类B2
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B2"<<endl;}
};
class D1: public B1, public B2 //声明派生类D1
{
public:
int nV; //同名数据成员
void fun(){cout<<"Member of D1"<<endl;} //同名函数成员
};
int main()
{
D1 d1;
d1.nV=1; //对象名.成员名标识
d1.fun(); //访问D1类成员
d1.B1::nV=2; //作用域分辨符标识
d1.B1::fun(); //访问B1基类成员
d1.B2::nV=3; //作用域分辨符标识
d1.B2::fun(); //访问B2基类成员
}
二义性:在多继承时,基类和派生类之间,或者基类之间出现同名成员,将出现访问时的二义性(不确定性),这种情况采用虚函数或者同名隐藏规则来解决;当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性,采用虚函数解决。
class A
{
public:
void f();
};
class B
{
public:
void f();
void g()
};
class C: public A, piblic B
{ public:
void g();
void h();
};
如果声明:C c1;
则 c1.f(); 具有二义性,解决方法1:用类名限定c1.A::f(); or c1.B::f();解决方法2:同名覆盖在c中声明一个同名函数f(),在根据需要调用A::f or B::f.
而 c1.g(); 无二义性(同名覆盖)
class B
{ public: ……
int b; ……
} ……
class B1 : public B ……
{ ……
private: ……
int b1; ……
} ……
class B2 : public B ……
{ ……
private: ……
int b2; ……
}; ……
class C : public B1,public B2……
{ ……
public: ……二义性 无二义性
int f(); …… C c; c.B1::b;
private: ……c.b; c.B2::b;
int d; ……c.B::b;
} //不光产生二义性,还将基类成员b产生了多次拷贝。 ……
虚基类:主要用于有共同基类的场合。
声明用virtual修饰基类;如class B1:virtual public B,其作用是用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
注意:在第一级继承时就要将共同基类设计成虚基类。
class B{ private: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;}
下面的访问是正确的:
C cobj;
cobj.b;
虚基类的派生类对象存储结构示意图:
#include <iostream>
using namespace std;
class B0 //声明基类B0
{ public: //外部接口
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0 //B0为虚基类,派生B1类
{ public: //新增外部接口
int nV1;
};
class B2: virtual public B0 //B0为虚基类,派生B2类
{ public: //新增外部接口
int nV2;
};
class D1: public B1, public B2 //派生类D1声明
{ public: //新增外部接口
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main() //程序主函数
{ D1 d1; //声明D1类对象d1
d1.nV=2; //使用最远基类成员的唯一一份拷贝
d1.fun();
}
虚基类和派生类构造函数:建立对象时所指定的类成为最(远)派生类,虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;在整个继承结构中,直接或间接继承虚基类的所有派生类都必须在构造函数成员初始化表中给出对虚基类构造函数的调用,否则表示调用该虚基类的默认构造函数;在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略;
#include <iostream>
using namecpace std;
class B0 //声明基类B0
{ public: //外部接口
B0(int n){ nV=n;}
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0
{ public:
B1(int a) : B0(a) {}
int nV1;
};
class B2: virtual public B0
{ public:
B2(int a) : B0(a) {}
int nV2;
};
class D1: public B1, public B2
{
public:
D1(int a) : B0(a), B1(a), B2(a){}
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main()
{
D1 d1(1);
d1.nV=2;
d1.fun();
}
C++——继承与派生的更多相关文章
- c++学习--继承与派生
继承和派生 1 含有对象成员(子对象)的派生类的构造函数,定义派生类对象成员时,构造函数的执行顺序如下: 1 调用基类的构造函数,对基类数据成员初始化: 2 调用对象成员的构造函数,对对象成员的数据成 ...
- 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)
[源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...
- [C++]类的继承与派生
继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义.利用类的继承和派 ...
- O-c中类的继承与派生的概念
什么是继承 众所周知,面向对象的编程语言具有: 抽象性, 封装性, 继承性, 以及多态性 的特征. 那么什么是继承呢? 传统意义上是指从父辈那里获得父辈留下的东西 在开发中, 继承就是"复用 ...
- 程序设计实习MOOC / 继承和派生——编程作业 第五周程序填空题1
描述 写一个MyString 类,使得下面程序的输出结果是: 1. abcd-efgh-abcd- 2. abcd- 3. 4. abcd-efgh- 5. efgh- 6. c 7. abcd- 8 ...
- 走进C++程序世界------继承和派生
继承和派生 继承是面向对象编程语言的最重要方面之一,正确的使用继承可编写出设计良好,容易于维护和扩展的应用程序.下面是在其他博客中的总结: ****************************** ...
- C++学习之路—继承与派生(一):基本概念与基类成员的访问属性
(本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1 基本思想与概念 在传统的程序设计 ...
- C/C++基础知识总结——继承与派生
1. 类的继承与派生 1.1 派生类的定义 (1) 定义规范 class 派生类名: 继承方式 基类1名, 继承方式 基类2名... { ...派生类成员声明; }; (2) 从以上形式上看可以多继承 ...
- python基础——继承与派生、组合
python基础--继承与派生 1 什么是继承: 继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类成为基类或超累,新建的类成为派生类或子类 1.1 继承分为:单 ...
- 【C++ 实验六 继承与派生】
实验内容 1. 某计算机硬件系统,为了实现特定的功能,在某个子模块设计了 ABC 三款芯片用于 数字计算.各个芯片的计算功能如下: A 芯片:计算两位整数的加法(m+n).计算两位整数的减法(m-n) ...
随机推荐
- ES6 - 基础学习(3): 变量的解构赋值
解构赋值概述 1.解构赋值是对赋值运算符的扩展. 2.它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值. 3.代码书写上显得简洁且易读,语义更加清晰明了:而且还方便获取复杂对象中的数据字 ...
- OpenResty + ngx_lua_waf使用
本篇介绍在CentOS7.6上安装.测试使用ngx_lua_waf + openresty. Preface # yum install epel-release -y # yum group ins ...
- 关系模式范式分解教程 3NF与BCNF口诀
https://blog.csdn.net/sumaliqinghua/article/details/86246762 [通俗易懂]关系模式范式分解教程 3NF与BCNF口诀!小白也能看懂原创置顶 ...
- Linux 文件、目录操作
Linux中的路径只能使用/,不能使用\ 或\\. cd 切换目录 cd / 切换到系统根目录,cd即change dir cd /bin 切换到根目录下的bin目录 cd .. ...
- 02.JS数据类型与数据类型转换
前言: 学习一门编程语言的基本步骤 (01)了解背景知识 (02)搭建开发环境 (03)语法规范 (04)常量和变量 (05)数据类型 (06)数据类型转换5.数据类型——datatype 数据 ...
- 页面上icon较多,又不想使用臃肿的结构怎么办?
[先看效果图] 例如这种排版,常规有两种情况 1.把[“ & ”]+ 白色背景切一起 2.写结构的时候复杂,例如:div>img*2 +文字标签 读到这里,可能有人说,第一种情况为什么两 ...
- centos7系统中忘记了root管理员账号密码的解决方式
随着计算机的使用越来越普遍,现在的用户都会有多个密码,不是这软件的密码就是那个的,QQ.邮箱.游戏,还有系统的登录密码!每一个密码都不一样!所以越来越多的密码需要去记住!也因为这样,只要其中一个长时间 ...
- 【python基础语法】第3天作业练习题
''' .将给定字符串的PHP替换为Python best_language = "PHP is the best programming language in the world! &q ...
- 6-Z字形变换
6-Z字形变换 将一个给定字符串根据给定的行数,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下: L C ...
- Bubble Sort HDU - 5775 树状数组
//每个数字只会被它后面的比它小的数字影响,且会向右移动相应个数的位置 //比如:6 4 3 5 2 1 .4后面比它小的有 三个,因此它的最右边位置就是当前位置 +3,即5 //如果该数字本身在标准 ...