代码重用
- C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的类来创建新类,而不是重新创建它们。
组合
介绍:
class A
{
public:
void FunA();
};
class B
{
public:
void FunB()
{
...
a_.FunA();
...
}
private:
A a_;
};
继承
继承介绍
- 继承是使用已经编写好的类来创建新类,新的类具有原类的所有属性和操作,也可以在原有类的基础上做一些修改和增补;
- 新类称为派生类或子类,原有类称为基类或父类
- 派生类是基类的具体化
语法声明
class 派生类名:继承方式 基类名
{
派生类新增成员的说明;
}
公有、私有、保护成员
- 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
- 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
- 在关键字protected后面声明,与private类似,其差别就表现在继承与派生时对派生类的影响不同。
公有、私有、保护继承
默认继承保护级别
- class Base{};
- struct D1 : Base{}; //未指定继承方式,默认为公有继承
- class D2 : Base{};//未指定继承方式,默认为私有继承
接口继承与实现继承
- 我们将类的公有成员函数称为接口。
- 公有继承,基类的所有公有函数在派生类中仍然是公有的,换句话说基类的接口成为了派生类的接口,因而将它称为接口继承。
- 实现继承,对于私有、保护继承,派生类不能继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。
继承与重定义
- 对基类的数据成员的重定义
- 如果继承类中重定义了基类中的数据成员,那么基类中的数据成员会被隐藏
class Base
{
public:
Base():x_(0)
{}
int GetBaseX() const
{
return x_;
}
int x_;
};
class Derived : public Base
{
public:
Derived() :x_(0)
{}
int GetDerivedX() const
{
return x_;
}
int x_; //对x_重定义
};
int main(void)
{
Derived d;
d.x_ = 10;
cout << d.GetBaseX() << endl;
cout << d.GetDerivedX() << endl;
return 0;
}
输出结果:
- 对基类成员函数的重定义分为两种
- overwrite
- 与基类完全相同 (此时基类中成员函数被隐藏)
- 与基类成员函数名相同,参数不同(只能直接访问到派生类中的成员函数,基类中成员函数被隐藏)
- override
- overwrite
/*----------------示例1-----------------------*/
#include<iostream>
using namespace std;
class Base
{
public:
void Show()
{
cout << "Base::Show ..." << endl;
}
};
class Derived : public Base
{
public:
void Show()
{
cout << "Derived::Show ..." << endl;
}
};
int main(void)
{
Derived d;
d.Show();
return 0;
}
输出结果:
/*--------------------示例2---------------------------*/
#include<iostream>
using namespace std;
class Base
{
public:
void Show()
{
cout << "Base::Show ..." << endl;
}
};
class Derived : public Base
{
public:
void Show(int n)
{
cout << "Derived::Show " << n << endl;
}
};
int main(void)
{
Derived d;
//d.Show(); Error! 已无法再访问基类中的成员函数
d.Base::Show();
d.Show(100);
return 0;
}
输出结果:
继承与组合
- 无论是继承与组合,本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
- 组合通常是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口而不是来自老类的接口。(has-a)
- 如果希望新类与已存在的类具有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也成为子类型化。(is-a)
不能自动继承的成员函数
- 构造函数
- 析构函数
- =运算符
继承与构造函数
- 基类的构造函数(包括拷贝构造函数)不被继承,派生类中需要声明自己的构造函数。
- 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化(调用基类构造函数完成)。
- 派生类的构造函数(包括拷贝构造函数)需要给基类的构造函数传递参数
- 构造函数(包括拷贝构造函数)初始化列表中必须对基类、对象成员、数据成员进行初始化
#include<iostream>
using namespace std;
class ObjectD
{
public:
ObjectD()
{
cout << "ObjectD ..." << endl;
}
~ObjectD()
{
cout << "~ObjectD ..." << endl;
}
};
class Base
{
public:
Base(int b) : b_(b)
{
cout << "Base ..." << endl;
}
~Base()
{
cout << "~Baes ..." << endl;
}
int b_;
};
class Derived : public Base
{
public:
Derived(int b, int d): d_(d),Base(b)
{
cout << "Derived ..." << endl;
}
~Derived()
{
cout << "~Derived ..." << endl;
}
int d_;
ObjectD objd_;
};
int main(void)
{
Derived d(10,100);
cout << d.b_ << " " << d.d_ << endl;
return 0;
}
输出结果:
从上面结果中可以看出,派生类对象的构造次序:
基类对象成员的构造函数->基类构造函数->派生类的对象成员的构造函数->派生类自身的构造函数
友元关系与继承
- 友元关系不能被继承
静态成员与继承
- 静态成员无所谓继承
转换与继承
- 派生类对象也是基类对象。这就意味着使用基类的地方可以用派生类来替换。
派生类到基类的转换
- 当派生类以public方式继承基类时,编译器可自动执行的转换(向上转型upcasting安全转换)
- 派生类对象指针自动转化为基类对象指针
- 派生类对象引用自动转化为基类对象引用
- 派生类对象自动转换为基类对象(特有的成员消失)
- 当派生类以private/protected方式继承基类时
- 派生类对象指针(引用)转化为基类对象指针(引用)需用强制类型转化。但不能用static_cast,要用reinterpret_cast
- 不能把派生类对象强制转换为基类对象
/*--------------------------示例代码1-------------------------------*/
#include<iostream>
#include<string>
using namespace std;
class Employee //基类
{
public:
Employee(const string& name, const int age, const int department) : name_(name), age_(age), department_(department)
{
}
private:
string name_;
int age_;
int department_;
};
class Manager : public Employee //公有继承的派生类
{
public:
Manager(const string& name, const int age, const int department, const int level) : Employee(name, age, department), level_(level)
{
}
private:
int level_;
};
class Manager2 : private Employee //私有继承的派生类
{
public:
Manager2(const string& name, const int age, const int department, const int level) : Employee(name, age, department), level_(level)
{
}
private:
int level_;
};
int main(void)
{
Employee e1("zhangsan",25,20);
Manager m1("lisi",38,20,10);
Manager2 m2("wangwu", 40, 15, 8);
Employee* pe;
Manager* pm;
Manager2* pm2;
pe = &e1;
pm = &m1;
pm2 = &m2;
pe = &m1; //派生类对象指针可以转化为基类对象指针。将派生类对象看成基类对象
//pm = &e1; Error!// 基类对象指针无法转化为派生类对象指针。无法将基类对象看成是派生类对象
Employee& e2 = m1;//派生类对象引用可以转化为基类对象引用。将派生类对象看成基类对象
//Manager& m2 = e1; Error!基类对象引用无法转化为派生类对象引用。无法将基类对象看成是派生类对象
e1 = m1; //派生类对象可以转化为基类对象,将派生类对象看成基类对象
//会产生切割(派生类特有成员消失)
//pe = &m2; //Error! 私有或保护继承的时候,派生类对象指针不可以自动转化为基类对象指针
pe = reinterpret_cast<Employee*>(&m2);
//e1 = m2; //私有或保护继承的时候,派生类对象无法转化为基类对象
//e1 = reinterpret_cast<Employee>(m2);//私有或保护继承的时候,派生类对象无法强制转化为基类对象
return 0;
}
基类到派生类的转换
- 基类对象指针(引用)可用强制类型转换为派生类对象指针(引用),而基类对象无法执行这类转换
- 向下转型不安全,没有自动转换的机制
pm = static_cast<Manager*>(pe); //基类指针可以强制转化为派生类指针,但是不安全
多重继承
- 单重继承——一个派生类最多只能有一个基类
- 多重继承——一个派生类可以有多个基类
- class 类名:继承方式 基类1,继承方式 基类2,…
{…}; - 派生类同时继承多个基类的成员,更好的软件重用
- 可能会有大量的二义性,多个基类中可能包含同名变量多函数
- class 类名:继承方式 基类1,继承方式 基类2,…
- 多重继承中解决访问歧义的方法
- 基类名::数据成员名(或成员函数(参数表))
- 明确指明要访问定义于哪个基类中的成员
#include<iostream>
using namespace std;
class Bed
{
public:
Bed(int weight) : weight_(weight)
{}
void Sleep()
{
cout << "Sleep ..." << endl;
}
int weight_;
};
class Sofa
{
public:
Sofa(int weight) : weight_(weight)
{}
void WatchTV()
{
cout << "Watch TV ..." << endl;
}
int weight_;
};
class SofaBed : public Bed, public Sofa
{
public:
SofaBed(int weight = 0) : Bed(weight),Sofa(weight)
{}
void Foldout()
{
cout << "Fold out ..." << endl;
}
void Foldin()
{
cout << "Fold in ..." << endl;
}
};
int main(void)
{
SofaBed sofabed;
sofabed.Bed::weight_ = 20;
sofabed.Sofa::weight_ = 30;
sofabed.Sleep();
sofabed.WatchTV();
return 0;
}
虚继承与虚基类
介绍
- 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员是,将产生二义性——采用虚基类来解决。
- 虚基类的引入
- 用于有共同基类的场合
- 声明
- 以virtual修饰说明基类
例:class B1 : virtual public BB
- 以virtual修饰说明基类
- 作用
- 主要用来解决多继承时可能产生的对同一基类继承多次而产生的二义性问题
- 为最终的派生类提供唯一的基类成员,而不重复产生多次拷贝
虚基类及其派生类构造函数
- 虚基类的成员是有最远派生类的的构造函数通过调用虚基类的构造函数进行初始化的。
- 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。
- 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。
#include<iostream>
using namespace std;
class Furniture
{
public:
Furniture(int weight):weight_(weight)
{}
int weight_;
};
class Bed : virtual public Furniture
{
public:
Bed(int weight) : Furniture(weight)
{}
void Sleep()
{
cout << "Sleep ..." << endl;
}
};
class Sofa : virtual public Furniture
{
public:
Sofa(int weight) : Furniture(weight)
{}
void WatchTV()
{
cout << "Watch TV ..." << endl;
}
};
class SofaBed : public Bed, public Sofa
{
public:
SofaBed(int weight = 0) : Bed(weight),Sofa(weight),Furniture(weight)
//直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用
{}
void Foldout()
{
cout << "Fold out ..." << endl;
}
void Foldin()
{
cout << "Fold in ..." << endl;
}
};
int main(void)
{
SofaBed sofabed;
sofabed.weight_ = 20;
sofabed.Sleep();
sofabed.WatchTV();
return 0;
}
虚继承对类大小的影响
- 虚基类表(virtual base table)(vbtl)
- 本类地址与虚基类表指针地址的差
- 虚基类地址与虚基类表指针地址的差
- 虚基类表指针(virtual base table pointer)(vbptr)
下面我们同个一个简单的例子来看一下虚继承对于C++类对象内存的影响
#include<iostream>
using namespace std;
class BB
{
public:
int bb_;
};
class B1 : virtual public BB
{
public:
int b1_;
};
class B2 : virtual public BB
{
public:
int b2_;
};
class DD : public B1,public B2
{
public:
int dd_;
};
int main(void)
{
cout << "sizeof(BB) : " << sizeof(BB) << endl;
cout << "sizeof(B1) : " << sizeof(B1) << endl;
cout << "sizeof(DD) : " << sizeof(DD) << endl;
return 0;
}
输出结果:
首先本例中,继承方式如下:
首先让我们看一下B1是如何影响内存的:
int main(void)
{
B1 b1;
cout << "&b1 : " << &b1 << endl;
cout << "&b1.bb_ : " << &b1.bb_ << endl;
cout << "&b1.b1_ : " << &b1.b1_ << endl;
return 0;
}
输出结果:
可以看出内存分配是如下图所示:
再看一下DD是如何影响内存的:
int main(void)
{
DD d1;
cout << "&d1 : " << &d1 << endl;
cout << "&d1.bb_ : " << &d1.bb_ << endl;
cout << "&d1.b1_ : " << &d1.b1_ << endl;
cout << "&d1.b2_ : " << &d1.b2_ << endl;
cout << "&d1.dd_ : " << &d1.dd_ << endl;
return 0;
}
输出结果:
内存分配是如下图所示: