继承
- 被继承的作为父类(基类)
- 继承父类的作为子类(派生类)
- 子类继承父类的所有特性
对于继承的描述
- 继承作为一个典型的树状结构
- 从基类向下延伸,形成各种分支
- 分支具有向上分支的特性,同时因自己的独特特性与其他分支区分开
- 分支越向下必然是描述的越来越精细
- 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有的特性基础上进行扩展,增加功能,增加属性,这样产生新的类,称作是派生类。
- 继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
- 举例: 以people类作为基类,向下延伸的student类,teacher类,worker类,这样的类之后再延伸出带有新的特性的子类
以people->worker->salesman为例,salesman具有其worker的特性,区别于worker,salesman多出了自己的新特性,新的功能。
继承程序举例
#include <iostream>
#include <string>
using namespace std;
class people
{
public:
people(string name, string sex, int age) : m_name(name), m_sex(sex), m_age(age){};
public:
string m_name;
string m_sex;
int m_age;
};
class student : public people
{
public:
student(string name, string sex, int age, int sid) : people(name, sex, age), m_sid(sid){};
void print_s()
{
cout << m_name << ","
<< m_sex << ","
<< m_age << ","
<< m_sid << endl;
}
public:
int m_sid;
};
class representative : public student
{
public:
representative(string name, string sex, int age, int sid, string course) : student(name, sex, age, sid), m_course(course){};
void print_r()
{
cout << m_name << ","
<< m_sex << ","
<< m_age << ","
<< m_sid << ","
<< m_course << endl;
}
public:
string m_course;
};
int main()
{
representative r("王明", "男", 20, 1, "数学");
r.print_s();
cout << endl;
r.print_r();
return 0;
}
运行结果:
王明,男,20,1
王明,男,20,1,数学
- 上例实现的是公有成员与公有继承。保护成员与继承、私有成员与继承的实现读者可以自行尝试。
以上程序,旨在于体现继承的作用,子类继承父类的属性与方法,并可以实现调用父类的方法
Tips
- 子类拥有父类的所有特性
- 继承方式有公有继承,私有继承,保护继承。默认是私有继承。
- 公有继承中父类的公有和保护成员在子类中不变,私有的在子类中不可访问。(public)
- 私有继承中父类的公有和保护成员在子类中变为私有,但私有的在子类中不可访问。(private)
- 保护继承中父类的公有和保护成员在子类中变为保护,但私有的在子类中不可访问。(protected)
继承的优缺点
- 首先,最明显的好处,顾名思义的继承,父辈走过的路,后辈如果想去走相同的路的话,不用再走一遍,父辈的工具(定义好的函数方法)与经验(定义好的使用方法需要的属性),只要后辈继承了,那就都属于后辈,减少重复的代码,父辈私有(private)的除外
- 增加了类之间的关联程度,即耦合度(既是优点,也是缺点)
- 继承的使用就意味着父类至少定义了子类的部分行为,父类的改变会影响所有继承的该父类的子类
- 子类会直接拷贝父类的内存空间,如果父类继承的东西,子类没有都用到,就是浪费空间,少用一个就多浪费一点。当然,这个浪费只是这样说,只要不是父类弄的特别大特别全,然后继承过来就用一点,都不算太过分。总的来说,继承减少了代码量,但是该给的内存空间一点没少,多数情况还要多出一点作为没有使用到拷贝空间的所有内容的浪费。
关于继承体系中的成员函数的几种情况
重载:写一个与已有函数同名但是参数列表不同的函数
重写:对函数的重写,函数列表与参数列表都相同,但是对内部实现进行修改
隐藏:子类对象、指针、引用访问基类和派生类都有同名函数时,先访问子类的函数,即隐藏父类的同名函数。更通俗的讲,父类定义过的函数,子类又重新定义了一次,这时子类对象用函数,优先用子类里面的,子类里面找不到再往上找。
继承体系里几种情况,相比较的关系
继承体系同名成员函数的关系
| 作用域 | 函数名称 | 参数 | 返回值 |
---|
重载 | 同一作用域 | 相同名称 | 不同 | 可以不同 |
---|
隐藏(覆盖) | 不同作用域,父类与子类 | 相同名称 | 相同 | 相同 |
---|
重写 | 不同作用域,父类与子类,父类中必须要有virtual | 相同名称 | 相同 | 相同(协变除外) |
---|
对继承的补充:
一,多继承
子类只继承一个父类的情况被称为单继承 此外,C++也支持多继承,即一个子类继承了多个父类——多继承 形如:
class C : public A, public B
{
}
多继承下的构造函数如果想要调用不同父类中的参数,当然也不同于单继承 形如:
class C : public A, public B
{
public:
C(int a, int b, int c) : A(a), B(b)
{
}
~C()
{
}
int m_c;
};
多继承完整程序举例:
#include "iostream"
using namespace std;
class A
{
public:
A(int a)
{
}
~A()
{
}
int m_a;
};
class B
{
public:
B(int b)
{
}
~B()
{
}
int m_b;
};
class C : public A, public B
{
public:
C(int a, int b, int c) : A(a), B(b)
{
}
~C()
{
}
int m_c;
};
int main()
{
C c(1, 2, 3);
cout << &c << endl;
cout << &c.m_a << endl;
cout << &c.m_b << endl;
cout << &c.m_c << endl;
cout << sizeof(c) << endl;
return 0;
}
二,虚继承
虚继承作为解决多继承产生的问题的一种手段。
多继承主要产生两种问题:
- 多个子类继承了同一父类,每个子类都拷贝了一份父类的内存空间,显而易见,浪费了存储空间。
- 数据的二义性问题。具体的举例来说,“菱形”问题:有最基类A,有A的派生类B、C,又有D同时继承B、C,那么若A中有成员a,那么在派生类B,C中就存在a,又D继承了B,C,那么D中便同时存在B继承A的a和C继承A的a,那么当D的实例调用a的时候就不知道该调用B的a还是C的a,就导致了二义性。
菱形二义性的解决:
- 用作用域限定符,‘’::‘’,明确的指出来,谁是谁的谁
- 在子类中定义同名的成员覆盖掉父类继承过来的成员。只是不报错了,相对应的,父类继承过来的成员也不能直接用了
- 虚继承:父类对祖父类的继承改为虚继承(virtual),各父类共享一个祖父类的拷贝,那么子类从父类继承过来的祖父类成员也就只有一个了。
菱形继承的解决代码举例:
#include "iostream"
using namespace std;
class A
{
public:
A(int a) {}
~A() {}
int m_a;
};
class B : virtual public A
{
public:
B(int a, int b) : A(a) {}
~B() {}
int m_b;
};
class C : virtual public A
{
public:
C(int a, int c) : A(a) {}
~C() {}
int m_c;
};
class D : public B, public C
{
public:
D(int a, int b, int c, int d) : A(a), B(a, b), C(a, c)
{
m_a = a;
m_b = b;
m_c = c;
m_d = d;
}
~D() {}
int m_d;
};
int main()
{
D d(1, 2, 3, 4);
cout << d.m_a << endl;
cout << sizeof(d) << endl;
cout << &d << endl;
cout << &d.m_a << endl;
cout << &d.m_b << endl;
cout << &d.m_c << endl;
cout << &d.m_d << endl;
return 0;
}
|