类之间可以建立联系,这就使得类可以有某种关系

类之间的关系

has-A:包含关系,一个类使用另一个已经定义好的类的数据

uses-A:使用关系,友元或者对象参数传递

is-A:是的关系,这就是继承,具有传递性不具有对称性

继承是类之间定义的一种重要关系,一个B类继承A类,或称从类A派生类B,类A称为基类(父类),类B称为派生类(子类)

基类和派生类

类继承关系的语法形式

class 派生类名:基类名表
{
数据成员和成员函数声明
};

基类名表构成:访问控制 基类名1, 访问控制 基类名2,···, 访问控制 基类名n

访问控制 表示派生类对基类的继承方式,使用关键字:

  • public 公有继承
  • private 私有继承
  • protected 保护继承

访问控制

派生类对基类成员的使用,与继承访问控制和基类中成员性质有关

公有继承:基类的公有成员 -> 派生类的公有成员,基类的保护成员 -> 派生类的保护成员

私有继承:基类的公有成员和保护成员 -> 派生类的私有成员

保护继承:基类的公有成员和保护成员 -> 派生类的保护成员

不论种方式继承基类,派生类都不能直接使用基类的私有成员

重名成员

派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员

在派生类中使用基类的同名成员,显式地使用类名限定符:

类名::成员

派生类中的静态成员

基类定义的静态成员,将被所有派生类共享

根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质

派生类中访问静态成员,用以下形式显式说明:

通过类:类名::成员

通过对象:对象名.成员

基类的初始化

建立一个类层次后,通常创建某个派生类的对象,包括使用基类的数据和函数

C++提供一种机制,在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据

派生类构造函数声明为

派生类构造函数(变元表):基类(变元表),对象成员1(变元表)···对象成员n(变元表);

构造函数执行顺序:基类 -> 对象成员 -> 派生类

继承的应用实例

#include <iostream>
#include <iomanip> using namespace std; class Point
{
friend ostream &operator<< (ostream &, const Point &);
public:
Point(int = 0, int = 0);//带默认参数的构造函数
void setPoint(int, int);//对点坐标数据赋值
int getX() const
{
return x;
}
int getY() const
{
return y;
}
protected:
int x, y;//Point类的数据成员
}; class Circle :public Point
{
friend ostream &operator<<(ostream &, const Circle &);//友员函数
public:
Circle(double r = 0.0, int x = 0, int y = 0);//构造函数
void setRadius(double);/*置半径*/
double getRadius() const;/*返回半径*/
double area() const;//返回面积
protected:
double radius;//数据成员,半径
}; class Cylinder :public Circle
{
friend ostream & operator<<(ostream &, const Cylinder &);//友员函数
public:
Cylinder(double h = 0.0, double r = 0.0, int x = 0, int y = 0);//构造函数
void setHeight(double);/*置高度值*/
double getHeight() const;/*返回高度值*/
double area() const;/*返回面积*/
double volume() const;/*返回体积*/
protected:
double height;//数据成员,高度
}; //Point 类的成员函数
//构造函数,调用成员函数对x,y作初始化
Point::Point(int a, int b)
{
setPoint(a, b);
}
//对数据成员置值
void Point::setPoint(int a, int b)
{
x = a;
y = b;
}
//重载插入算符,输出对象数据
ostream &operator<<(ostream &output, const Point &p)
{
output << '[' << p.x << "," << p.y << "]";
return output;
} //Circle 类的成员函数
//带初始化式构造函数,首先调用基类构造函数
Circle::Circle(double r, int a, int b):Point(a, b)
{
setRadius(r);
}
//对半径置值
void Circle::setRadius(double r)
{
radius = (r >= 0 ? r : 0);
}
// 返回半径值
double Circle::getRadius() const
{
return radius;
}
// 计算并返回面积值
double Circle::area() const
{
return 3.14159 * radius * radius;
}
// 输出圆心坐标和半径值
ostream & operator<< (ostream &output, const Circle &c)
{
output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = "
<< setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << c.radius;
return output;
} //Cylinder 类的成员函数
//带初始化式构造函数,首先调用基类构造函数
Cylinder::Cylinder(double h, double r, int x, int y):Circle(r, x, y)
{
setHeight(h);
}
// 对高度置值
void Cylinder::setHeight(double h)
{
height = (h >= 0 ? h : 0);
}
// 返回高度值
double Cylinder::getHeight() const
{
return height;
}
// 计算并返回圆柱体的表面积
double Cylinder::area() const
{
return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
// 计算并返回圆柱体的体积
double Cylinder::volume() const
{
return Circle::area()*height;
}
// 输出数据成员圆心坐标、半径和高度值
ostream &operator<< (ostream &output, const Cylinder &cy)
{
output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = "
<< setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << cy.radius
<< "; Height = " << cy.height << endl;
return output;
} int main()
{
Point p(72, 115);//定义点对象并初始化
cout << "The initial location of p is " << p << endl; p.setPoint(10, 10);//置点的新数据值
cout << "\nThe new location of p is " << p << endl;//输出数据 Circle c(2.5, 37, 43);//定义圆对象并初始化
cout << "\nThe initial location and radius of c are\n" << c << "\nArea = " << c.area() << "\n"; //置圆的新数据值
c.setRadius(4.25);
c.setPoint(2, 2);
//输出圆心坐标和圆面积
cout << "\nThe new location and radius of c are\n" << c << "\nArea = " << c.area() << "\n"; Cylinder cyl(5.7, 2.5, 12, 23);//定义圆柱体对象并初始化
//输出圆柱体各数据和表面积,体积
cout << "\nThe initial location, radius ang height of cyl are\n" << cyl
<< "Area = " << cyl.area() << "\nVolume = " << cyl.volume() << '\n';
//置圆柱体的新数据值
cyl.setHeight(10);
cyl.setRadius(4.25);
cyl.setPoint(2, 2);
cout << "\nThe new location, radius ang height of cyl are\n" << cyl
<< "Area = " << cyl.area() << "\nVolume = " << cyl.volume() << "\n"; system("pause");
return 0;
}

多继承

一个类有多个直接基类的继承关系称为多继承

多继承声明语法

class 派生类名:访问控制 基类名1, 访问控制 基类名2,···,访问控制 基类名n
{
数据成员和成员函数声明
};

类C可以根据访问控制同时继承类A和类B的成员,并添加自己的成员

class C:public A, public B

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

  • 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
  • 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序
  • 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别

    e.g.
class Base1
{
public:
Base1(int x)
{
value = x;
}
int getData() const
{
return value;
}
protected:
int value;
}; class Base2
{
public:
Base2(char c)
{
letter = c;
}
char getData() const
{
return letter;
}
protected:
char letter;
}; class Derived : public Base1, public Base2
{
friend ostream &operator<< (ostream &, const Derived &);
public:
Derived(int, char, double);
double getReal() const;
private:
double real;
};

虚基类

如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性

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();
private:
int d;
};
C c;
c.B;// error
c.B::b;// error,从哪里继承的?
c.B1::b;// ok,从B1继承的
c.B2::b;// ok ,从B2继承的

建立 C 类的对象时,B 的构造函数将被调用两次:一次由B1调用,另一次由 B2 调用,以初始化 C 类的对象中所包含的两个 B 类的子对象



  • 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
  • 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
  • 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类
  • 虚继承声明使用关键字:virtual
class B
{
public:
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 cc;
cc.b;// ok

由于类 C 的对象中只有一个 B 类子对象,名字 b 被约束到该子对象上,所以,当以不同路径使用名字 b 访问 B 类的子对象时,所访问的都是那个唯一的基类子对象。即cc.B1::bcc.B2::b引用是同一个基类 B 的子对象



面向对象中的继承指类之间的父子关系

  1. 子类拥有父类的所有成员变量和成员函数
  2. 子类就是一种特殊的父类
  3. 子类对象可以当作父类对象使用
  4. 子类可以拥有父类没有的方法和属性

C++中的继承方式(public、private、protected)会影响子类的对外访问属性

  • public继承:父类成员在子类中保持原有访问级别
  • private继承:父类成员在子类中变为private成员
  • protected继承:父类中public成员会变成protected,父类中protected成员仍然为protected,父类中private成员仍然为private

    注意:private成员在子类中依然存在,但是却无法访问到。

继承总结

  • 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
  • 单继承的派生类只有一个基类。多继承的派生类有多个基类。
  • 派生类对基类成员的访问由继承方式和成员性质决定。
  • 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
  • C++提供虚继承机制,防止类继承关系中成员访问的二义性。
  • 多继承提供了软件重用的强大功能,也增加了程序的复杂性

C++学习笔记-继承的更多相关文章

  1. Java学习笔记---继承和super的用法

    自从换了个视频教学,感觉比原来那个好多了,就是学校网速太渣,好多视频看一会卡半天,只能先看看已经下载的了. 不过也好,虽然不能从开始开始重新开,但是已经看过一次,在看一次也是好的,就当巩固学习了. 继 ...

  2. Java学习笔记--继承和多态(中)

    1.通过继承来开发超类(superclass) 2.使用super 关键词唤起超类的构造方法 3.在超类中覆盖方法 4.区分override和overload 5.在Object类中探索toStrin ...

  3. java学习笔记-继承中super关键字

    背景: 在java继承的概念中我们得知,被声明为私有的类成员对所属的类来说仍然是私有的.类之外的任何代码都不能访问,包括子类. super关键字的两种用法: 1.用于调用超类的构造函数: 2.用于访问 ...

  4. JS高级程序设计学习笔记——继承

    我们知道,在OO语言中,继承可分为接口继承和实现继承.而ECMAScript的函数没有签名,不能实现“接口继承”,只能通过原型链实现“实现继承”. 在学习了各种继承模式之后,简单总结一下各种继承模式的 ...

  5. Java学习笔记--继承和多态(下)

    1.通过继承来开发超类(superclass) 2.使用super 关键词唤起超类的构造方法 3.在超类中覆盖方法 4.区分override和overload 5.在Object类中探索toStrin ...

  6. Java学习笔记--继承和多态(上)

    1.通过继承来开发超类(superclass) 2.使用super 关键词唤起超类的构造方法 3.在超类中覆盖方法 4.区分override和overload 5.在Object类中探索toStrin ...

  7. JavaScript学习笔记----- 继承的实现及其原理

    按照自己在极客上学习的顺序整理了一下,参考了几位前辈的随笔,十分感谢:                       参见http://blog.yemou.net/article/query/info ...

  8. [Android学习笔记]继承自ViewGroup的控件的过程学习

    ViewGroup文档 http://developer.android.com/training/index.html 继承自ViewGroup需要重写onLayout方法用来为子View设定位置信 ...

  9. java学习笔记 --- 继承

    继承 (1)定义:把多个类中相同的成员给提取出来定义到一个独立的类中.然后让这多个类和该独立的类产生一个关系,    这多个类就具备了这些内容.这个关系叫继承.  (2)Java中如何表示继承呢?格式 ...

  10. Java学习笔记——继承、接口、多态

    浮点数的运算需要注意的问题: BigDecimal operand1 = new BigDecimal("1.0"); BigDecimal operand2 = new BigD ...

随机推荐

  1. BZOJ 2243: [SDOI2011]染色 (树剖+线段树)

    树链剖分后两个区间合并的时候就判一下相交颜色是否相同来算颜色段数就行了. CODE #include <vector> #include <queue> #include &l ...

  2. java中简单的反射

    1.为什么会用到反射机制? 最近需要写定时服务,如果一个一个去写定时服务的话,后期维护是很烦人的,通过反射机制,我们就可以将定时服务的信息通过数据配置来实现,这样我们后期就可以将整个模块交给运维人员去 ...

  3. Vue:选中商品规格改变字体和边框颜色(默认选中第一种规格)

    效果图: CSS: <div class="label"> <p>标签类别</p> <ul> <li v-for=" ...

  4. Malloc Maleficarum复盘

    1.hos复盘 hos即伪造堆块,free栈上地址,然后下一个malloc去分配一个fastbin(栈上),包含返回地址. 代码来源 他这个我直接复现有问题,咨询了joker师傅,应该是gcc版本问题 ...

  5. Laravel Artisan Tinker: The optimize Command

    Laravel Artisan Tinker: The optimize Command December 7, 2016 Laravel JOHN KOSTER The optimize comma ...

  6. CCPC-Wannafly & Comet OJ 夏季欢乐赛(2019)H

    题面 被神葱安利安利了本题. 我们贪心的想,如果有那么一坨相等的学号,那么肯定是保留一个人学号不变,其余的再推到学号+1的位置(准备与那个位置的其他人合并)处理. 虽然a[i]可大至1e18,不过如果 ...

  7. HashMap在什么场景下会由哪些内部方法导致线程不安全,至少给出一种场景

    一直以来只是知道HashMap是线程不安全的,但是到底HashMap为什么线程不安全,多线程并发的时候在什么情况下可能出现问题? HashMap底层是一个Entry数组,当发生hash冲突的时候,ha ...

  8. mysql统计前24小时数据没有补0

    SELECT t1. HOUR HOUR, COUNT(t2. HOUR) count FROM ( SELECT DATE_FORMAT( HOUR), '%y-%m-%d %H' ) HOUR F ...

  9. SRS之分发HLS

    来自: Delivery HLS 1. 综述 SRS支持HLS/RTMP两种成熟而且广泛应用的流媒体分发方式. RTMP指Adobe的RTMP(Realtime Message Protocol),广 ...

  10. LeetCode 73. 矩阵置零(Set Matrix Zeroes)

    题目描述 给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0.请使用原地算法. 示例 1: 输入: [   [1,1,1],   [1,0,1],   [1,1,1 ...