待定
本章内容包括:
is-a 关系的继承
如何以公有方式从一个类派生出另一个类
保护访问
构造函数成员初始化列表
向上向下强制转换
虚成员函数
早期(静态)联编与晚期(动态)联编
抽象基类
抽象基类
纯虚函数
何时及如何使用公有继承
前言:
很多厂商提供类库,类库由类声明和实现构成,其组合了数据表示和类方法,提供了比函数库更加完整的程序包。例如单个类就可以提供用于管理对话框的全部资源。通常类库以源代码的方式提供,也就是说可以对其进行修改以满足需求。
C++提供了比修改源码更好的方法来扩展和修改类,即类继承。它能够从已有的类派生出新的类,而派生类继承了原有类(基类)的特征和方法。
以下是可通过继承完成的一些工作:
1、可在已有类的基础上添加功能。例如数组类,可添加数学运算。
2、可给类添加数据。例如字符串类,可派生出一个类,并添加指定字符串显示颜色的数据成员
3、可修改类方法的行为。例如,对于代表提供给飞机乘客的服务的Passenger类,可派生出提供更高级别服务的VipClassPassenger类
继承机制只需提供新特性,不需要访问源码就可以派生出类。所以,对于只提供了类方法的头文件和编译后代码,仍可以使用库中的类派生出新的类。而且可以在不公开实现的情况下将自己的类分发给他人,同时也允许他人在类中添加新特性。
13.1 一个简单的基类
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。
假设原始类TableTenniesPlayer:
class TableTenniesPlayer
{
private:
string firstName;
string lastName;
bool hasTable;
public:
TableTennisPlayer(const string &fn = "none", const string &ln = "none", bool ht = false);
void Name() const;
bool HasTable() const{return hasTable};
void ResetTable(bool v) {hasTable = v;};
}
在原始类TableTennisClass类新增一个属性,其包括成员在比赛中的得分,语法格式:
class RatePlayer:public TableTennisPlayer
{
private:
unsigned int rating;
public:
RatedPlayer(unsigned int r = 0, const string &fn = "none", const string &ln = "none", bool ht = false);
RatePlayer(unsigned int r, const TableTennisPlayer &tp);
unsigned int Rating() const { return rating;};
void ResetRating(unsigned int r) {rating = r;};
};
上述代码中,冒号:
指出RatePlayer类的基类时TableTenniePlayer,“public”表示TableTennisPlayer是一个公有基类,称为公有派生。
使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。RatedPlayer具有以下特征:
1、派生类对象存储了基类的数据成员(继承了基类的实现);
2、派生类对象可以使用基类的方法(继承了基类的接口)。
派生类需要在继承特性中添加以下内容:
1、自己的构造函数,且构造函数必须给新成员(如果有)和继承的成员提供数据;
2、可根据需要添加额外的数据成员和成员函数。
上述RatePlayer第一个构造函数中每个成员对应一个形参;第二个构造函数使用一个TableTennisPlayer参数。
13.1.2 构造函数:访问权限
派生类不能直接访问基类的私有成员,只能通过基类方法进行访问;RatePlayer构造函数不能直接设置继承的成员,必须使用基类的公有方法来访问私有的基类成员。
派生类构造函数必须使用基类构造函数。创建派生类对象时,应首先创建基类对象。即基类对象应当在程序进入派生类构造函数之前被创建。(此处类似上一章的const 成员的成员初始化列表),
RatedPlayer::RatedPlayer(unsigned int r = 0, const string &fn = "none", const string &ln = "none", bool ht = false):TableTennisPlayer(fn, ln, ht)
{
rating = r;
}
" :TableTennisPlayer(fn, ln, ht) “即为成员初始化列表。
但此处若将” :TableTennisPlayer(fn, ln, ht) “注释掉” //:TableTennisPlayer(fn, ln, ht) "程序将使用默认的基类构造函数。(此种情况不保证是否正确,可根据实际情况,选择默认或显示地调用正确的构造函数)
13.3 多态公有继承
同一个方法在派生类和基类中的行为不同,其具体行为取决于调用该方法的对象——多态。
以下2种机制可实现多态公有继承:
● 在派生类中重新定义基类的方法
● 使用虚方法(虚函数)
举例,Brass Account。
Brass Account信息:
- 客户姓名;
- 账号;
- 当前结余。
可执行操作: - 创建账户;
- 存款;
- 取款;
- 显示账户信息
Brass Plus新增信息项: - 透支上限;
- 透支贷款利率;
- 当前的透支总额。
无新增操作,修改以下2个操作: - 对于取款操作,考虑透支保护;
- 显示操作必须 显示Brass Plus账户的其他信息。
brass.h
#ifndef __BRASS_H_
#define __BRASS_H_
#include<iostream>
#include<string>
using namspace std;
class Brass
{
privat:
string fullName;
long acctNum;
double balance;
public:
Brass(const string &s = "NullBody", long an = -1, double bal = 0.0);
void Deposit(double amt);
virtual void WithDraw(double amt);
double Balance() const;
virtual void ViewAcct() const;
virtual ~Brass() {};
}
class BrassPlus:public Brass
{
private:
double maxLoan;
double rate;
double owesBank;
public:
Brass(const string &s = "NullBody", long an = -1, double bal = 0.0, dobule ml = 0.0, double rate = 0.11255);//函数定义时用初始化列表实现或再写一个如下构造函数
Brass(Brass &ba, dobule ml = 0.0, double rate = 0.11255);
virtual void WithDraw(double amt);
virtual void ViewAcct() const;
void ResetMax(double m) {max Loan = m;}
void ResetRate(double r) {rate = r;}
voied ResetOwes() {owesBank = 0;}
virtual ~Brass() {};
}
#endif
程序说明:
-
对于在两个类中行为相同的方法只在基类中声明。
-
关键字
virtual
。用于方法通过引用或指针调用时确定使用哪种方法(基类or派生类中的方法)如果没有使用virtual
,程序将根据引用类型或指针类型选择方法;如果使用了virtual
,则根据引用或指针指向的对象的类型来选择方法。
对于Brass对象dom
,BrassPlus对象dot
:
//
Brass &b1 = dom;
Brass &b2 = dot;
//非virtual
b1.ViewAcct(); //调用Brass::ViewAcct()
b2.ViewAcct(); //调用Brass::ViewAcct()
//virtual
b1.ViewAcct(); //引用指向基类,调用Brass::ViewAcct()
b2.ViewAcct(); //应用指向派生类,调用BrassPlus::ViewAcct()
注意:如果要在派生类中重新定义基类的方法,应将基类方法声明为virtual
。方法在基类中被声明为virtual
后将在派生类中自动生成虚方法,建议加上清晰明了。
1、类实现
Brass.cpp/Brass method
Brass.cpp/BrassPlus method
Brass::BrassPlus(const string &s, long an, double bal, double ml, double r):Brass(s, an, bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass &a, double ml, double r):Brass(ba)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void Brass::ViewAcct() const
{
Brass::ViesAcct();
cout << "Maxium load : $" << maxLoan << endl;
cout << "Load rate : $" << rate << endl;
cout << "Owed to bank : $" << owesBank << endl;
}
void BrassPlus::WithDraw(double amt)
{
double bal = Balance();
if(amt <= bal)
BrassWithDraw(amt);
else if(amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank = advance *(1.0 + rate);
cout << "Bank Advance : $" << advance << endl;
cout << "Finance charge : $" << advance * rate << endl;
Deposit(advance);
Brass::WithDraw(amt);
}
else
cout << "Credit limit exceeded." << endl;
}
2、类使用
#include <iostream>
#include "brass.h"
using namespace std;
int main(void)
{
Brass Rick("Rick", 123456, 4000.00);
BrassPlus Jack("Jack", 654123, 3000.00);
Rick.ViewAcct();
Jack.ViewAcct();
Jake.Deposit(100.00);
Rick.WithDraw(502.00);
return 0;
}
以上代码通过类的对象进行调用只会调用基类的方法,没有使用虚方法的特性。
虚方法只能通过引用或指针来调用。
3、虚方法的使用
虚方法和指针或引用配合使用
#include <iostream>
#include "brass.h"
using namespace std;
connst int CLIENTS = 3;
int main(void)
{
//类的基类指针数组
Brass *pClients[CLIENTS];
for(int i = 0; i< CLIENTS; i++)
{
cout << "Enter the name: ";
getline(cin, temp); //字符串
cout << "Enter account number:";
cin >> tempNum;
cout << "Enter account balence:";
cin >> tempBal;
cout << "Enter 1 for Brass or 2 for BrassPlus: ";
while(cin >> kind && (kind != 1 && kind != 2))
cout << "Enter either 1 or 2: ";
if(kind == 1)
p_Clients[i] = new Brass(temp, tempNum, tempBal); //new会调用构造函数创建对象
else
{
double tmax, trate;
cout << "Enter the overDraft limit: ";
cin >> tmax;
cout << "Enter the rate: ";
cin >> trate;
pClient[i] = new BrassPlus(temp, tempNum, tempBal, tmax, trate);
}
//一次循环结束需要把回车干掉才能进入下一次循环
while(cin.get() != '\n');
}
cout << endl;
for(int i = 0; i< CLIENTS; i++)
{
pClients[i]->ViewAcct(); //根据指针指向对象的类型确定选用哪个ViewAcct()
cout << endl;
}
for(int i = 0; i< CLIENTS; i++)
{
delete pClients[i]; //new后需要delete
cout << endl;
}
return 0;
}
基类指针可以指向基类对象也可以指向派生类对象,反之不可(派生类只能指向派生类)。
4、为何需要虚析构函数
3中所示代码中delete
释放由new
分配的对象代码说明积累应该包含一个虚析构函数。如果析构函数不是虚的,则将只调用对应于指针类型的析构函数。上述代码中也就是只有Brass的析构函数被调用即使指针指向的是BrassPlus对象;反之,则调用相应对象类型的析构函数。
则应在Brass.h的基类中添加虚析构函数如下:
virtual ~Brass() {};
13.4 静态联编和动态联编
函数联编(binding):源代码中的函数调用解释为执行特定的函数代码块。
静态联编:在编译过程中进行联编即静态联编,又名早期联编。
虚函数使得在编译时无法确定选用哪个函数(其取决于运行时的调用类型)。
动态联编:编译器生成能够在程序运行时选择正确虚函数的代码称为动态联编,又名晚期联编。
13.4.2 虚成员函数和动态联编
1、默认静态联编
静态联编依然存在的原因:效率和概念模型。
效率:类不会用于基类时,无派生类,不需要动态联编;有派生类,但派生类不重新定义基类的任何方法,无需动态联编。
概念模型:对于一些不在派生类中重新定义的成员函数,则不应设置为虚函数,即仅将预期将被重新定义的方法声明为虚的。
2、虚函数的工作原理
每个类的对象都有一个隐藏成员——一个指针,指向一个数组,该数组中每一个成员均为指向一个函数的指针。该数组称为虚函数表。
使用虚函数在内存和执行速度方面有一定代价。
13.4.3 虚函数注意事项
- 基类方法声明中使用关键字
virtual
可使该方法在基类以及所有派生类中是虚的; - 如果使用指向对象的引用或指针来调用虚方法,程序将使用对象类型定义的方法,而非引用或指针类型定义的方法(13.3中虚方法的使用代码)。这称为动态联编。
- 如果定义的类将被用作基类,则应将要在派生类中重新定义的类方法声明为虚的。
虚方法相关补充:
1、构造函数:构造函数不能为虚函数。创建派生类对象时将调用派生类的构造函数(派生类的构造函数将使用基类的一个构造函数)而非基类的构造函数。
2、析构函数:应当是虚函数,除非类不用做基类。定义后运行时,将会先调用派生类的析构函数释放内存(派生类新增组件),再调用基类的析构函数释放内存(基类组件)。
注意:通常应给基类提供一个虚析构函数,即使并不需要。
3、友元:不能是虚函数,友元并非成员函数,只有成员才能是虚函数。
4、没有重新定义:如果派生类没有重新定义函数将使用基类中的该函数;如果派生类位于派生链中将使用最新的虚函数版本(除积累版本是隐藏的以外)
5、重新定义将隐藏方法:两个规则需遵守: - 如果重新定义继承的方法,应确保与原来的原型完全相同,但若返回类型是基类引用或指针,则可修改为指向派生类的引用或指针。
- 如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义一个版本,另外版本将隐藏。
13.5 访问控制:protected
关键字protected
和private
相似:在类外只能用公有类成员来访问protected
部分中的类成员;区别:只表现在基类派生的类中,派生类的成员可以直接访问基类的protected
成员,但不能访问基类的private
成员。
举例Brass
类将balance
声明为protected
:
class Brass
{
protected:
double balance;
...
}
此时,BrassPlus类可以直接访问balance(无需通过Brass方法)。
void BrassPlus::Reset(double amt)
{
balance = amt;
}
可能存在缺陷:Brass类设计是只能通过Deposit()和Withdraw()才能修改balance,但BrassPlus类可以直接访问balance也就意味着可以修改而忽略了Withdraw()中的保护措施,使之成为公有变量。
警告:最好对类数据成员采用private而非protected。
但对于成员函数来说protected很有用,它让派生类可以访问公众不能使用的内部函数。
13.6 抽象基类
例如,一个图形程序,实现圆和椭圆的显示。圆是椭圆的一个特殊情况,因此所有的圆都是椭圆,即可以从Ellipse类派生Circle类。
问题:椭圆Ellipse类的数据成员可包括椭圆中心坐标、半长轴、短半轴以及方向角,而圆Circle只需要中心坐标、半径。即椭圆中的部分参数对于圆来说是冗余的。
解决方法:从Ellipse和Circle中抽象出共性(中心坐标、Move()(两个类相同)和Area()方法(两个类不同)),将共性放到一个ABC中,而后从ABC中派生出Ellipse和Circle类。C++通过纯虚函数提供未实现的函数,纯虚函数声明的结尾处为=0
(参见Area()方法)。
class BaseEllipse //抽象基类
{
private:
double x;
double y;
...
public:
BaseEllipse(double x0 = 0, double y0 = 0) : x(x0), y(y0)) {}
virtual ~BaseEllipse() {}
void Move(int nx, ny) { x = nx; y = ny; }
virtual double Area() const = 0; //纯虚函数
}
抽象类中必须包含纯虚函数或带有纯虚函数的类即为抽象类;在原型中使用=0
表示类是一个抽象基类,在类中可以不定义该函数。
使用纯虚函数情景:在抽象基类中定义一个在不同派生类中有不同的实现方法时通过纯虚函数来定义。
抽象类不可实例化,即类声明中包含纯虚函数时不能创建该类的对象。
即可以创建Ellipse和Circle对象,但不能创建BaseEllipse对象。可以用BaseEllipse指针数组同时管理派生类(亦具体类)Ellipse和Circle对象。
13.6.1 应用ABC概念
基于Brass和BrassPlus进行修改。
acctABC.h
#ifndef __ACCTABC_H_
#define __ACCTABC_H_
#include<iostream>
#include<string>
using namspace std;
//新增抽象类AcctABC
class AcctABC
{
//共同的数据成员
privat:
string fullName;
long acctNum;
double balance;
publlic:
AcctABC(const string &s = "NullBody", long an = -1, double bal = 0.0);
//相同的函数
void Deposit(double amt);
//实现方式不同的函数
virtual void WithDraw(double amt) = 0; //=0表示是纯虚函数
double Balance() const{return balance}; //此处直接写成内联函数
//实现方式不同的函数
virtual void ViewAcct() const = 0;
//虚函数的析构函数(不要忘记)
virtual ~AcctABC() {};
//
protected:
const string &FullName() const{returen fullName;}
long AcctNum() const{return acctNum;}
}
//修改Brass类
class Brass : public AcctABC
{
//1、公有数据成员删除(独有的则保留参见BrassPlus类)
//2、公有成员函数删除;虚析构函数可保留
public:
Brass(const string &s = "NullBody", long an = -1, double bal = 0.0) : AcctABC(s, an, bal);
virtual void WithDraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {};
}
//修改BrassPlus类
class BrassPlus:public AcctABC
{
//1、保留独有的数据成员
private:
double maxLoan;
double rate;
double owesBank;
//2、保留独有的成员函数
public:
Brass(const string &s = "NullBody", long an = -1, double bal = 0.0, dobule ml = 0.0, double rate = 0.11255);//函数定义时用初始化列表实现或再写一个如下构造函数
Brass(Brass &ba, dobule ml = 0.0, double rate = 0.11255);
virtual void WithDraw(double amt);
virtual void ViewAcct() const;
void ResetMax(double m) {max Loan = m;}
void ResetRate(double r) {rate = r;}
voied ResetOwes() {owesBank = 0;}
virtual ~Brass() {};
}
#endif
acctABC.cpp/AcctABC类
对于纯需函数需要用到的时候可以在CPPl里边编写其实现方法,方便基类访问ABC类中的数据成员,参见AcctABC中的WithDraw()
访问balance
。
acctABC.cpp/Brass类
Brass::WithDraw()
修改为通过AcctABC::WithDraw()
访问balance
;
Brass::ViewAcct()
中的参数输出也修改为调用基类的方法(FullName()
,AcctNum()
等)进行输出显示。
acctABC.cpp/BrassPlus类
首先是构造函数基类改为AcctABC();
BrassPlus::ViewAcct()
同理Brass::ViewAcct()
;
Brass::WithDraw()
中Balance()可直接调用,无需修改,WithDraw()t同样修改为AcctABC::WithDraw()
类的使用:
usebrass3.cpp
13.7 继承和动态内存分配
继承和动态内存分配(new和delete)的互动。
13.7.1 第一种情况:派生类不使用new
基类使用了动态内存分配:
- 基类
//
class baseDMA
{
private:
char *label;
int rating;
public:
baseDMA(const char *l = "null", int r = 0);
//涉及到new的需要复制构造函数以及重载赋值运算符
baseDMA(const baseDMA &rs);
virtual ~baseDMA();
baseDMA &operator=(const baseDMA &rs);
friend ostream &operator<<(ostream &os, const baseDMA &RS);
}
对应的实现cpp:
赋值运算符重载知识点参见12.3
baseDMA::baseDMA(const char *l, int r)
{
label = new char[strlen(l) + 1];
strcp(label, l);
rating = r;
}
baseDMA::baseDMA(const baseDMA &rs)
{
label = new char[strlen(rs.label) + 1];
strcpy(label, rs.label);
rating = rs.rating;
}
baseDMA::~baseDMA()
{
delete []label;
}
//赋值运算符重载知识点参见12.3
baseDMA& baseDMA::operator=(const baseDMA &rs)
{
if(this == &rs)
return this;
delete []label;
label = new char[strlen(rs.label) + 1];
strcpy(label, rs.label);
rating = rs.rating;
return *this;
}
ostream &operator<<(ostream &os, const baseDMA)
{
os << "Label : " << rs.label << endl;
os << "rating: " << rs.rating << endl;
}
- 派生类
class lacksDMA : public baseDMA
{
private:
enum{COL_LEN = 40};
char color[COL_LEN];
public:
//派生类的构造函数调用时需要先调用基类的构造函数
lacksDMS(const char *l = "null", int r = 0, const char *c = "blank") :
lacksDMA(const baseDMA &rs, const char *c = "blank");
//此派生类成员内存已通过数组分配完毕无需析构函数
frend ostream &operator<<(ostream &os, const lacksDMA &rs)
}
对应实现的cpp:
```cpp
lacksDMA::lacksDMA(coonst char *l, int r, const char*c) : baseDMA(L, r)
{
strncp(color, c, COL_LEN - 1);
COLOR[COL_LEN -1] = '\0';
}
lacksDMA::lacksDMA(const baseDMA &rs, const char *c) : baseDMA(rs)
{
strncp(color, c, COL_LEN - 1);
COLOR[COL_LEN -1] = '\0';
}
ostream &operator<<(ostream &os, const lacksDMA &ls)
{
//此处将派生类强转化为基类调用基类的重载运算方法
os << (const baseDMA &)ls;
os << "Color: " << ls.color;
}
默认复制构造函数(系统自动创建的)对于动态内存分配来说不合适(见12章——默认复制构造函数不会提供new操作,但默认的析构函数会提供delete操作),即使用了动态内存分配的类不能使用默认复制构造函数。
但此处派生类lacksDMA成员可以使用默认复制构造函数,因为复制类成员或继承的类组件时,是使用该类的复制构造函数完成。即lacksDMA类的默认复制构造函数使用显示baseDMA复制构造函数来复制lacksDMA对象的baseDMA部分。
总结一句话:如果派生类无需动态内存分配,则可以使用默认复制构造函数,而无需编写复制构造函数,默认复制构造函数会自动先调用基类的复制构造函数完成基类成员拷贝。
对于赋值同理。
- main调用
#include "dma.h"
using namespace std;
int main(void)
{
baseDMA shirt("Protabelly", 8);
cout << shirt;
lacksDMA Ballon("Blimpo", 4, "red");
cout << ballon;
//直接拷贝的ballon
lacksDMA ballon2(ballon);
cout << ballon2;
}
无论是构造函数还是复制构造函数,派生类在调用时都会先调用基类的构造函数或复制构造函数。
总结:派生类中没有使用new时,派生类中无需编写析构函数、复制构造函数以及重载的复制运算符。
13.7.2 第二种情况:派生类使用new
派生类使用了new:
class hasDMA:public baseDMA
{
private:
char *style;
public:
hasDMA(const char *l = "null", int r = 0, const char *s = "none");
hasDMA(const baseDMA &rs, const char *s);
hasDMA(const hasDMA &hs);
~hasDMA();
hasDMA &operator=(const hasDMA &hs);
friend ostream &operator<<(ostream &os, const hasDMA &hs)
...
};
这种情况下,必须为派生类定义显示析构函数、复制构造函数和赋值运算符。
对应的cpp文件:
hasDMA::hasDMA(const char *l, int r, const char *s) : baseDMA(l, r)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(cosnt baseDMA &rs, const char *s) :baseDMA(rs)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA &hs) : baseDMA(hs)
{
style = new char[strlen(hs.style) + 1];
strcpy(style, hs,style);
}
hasDMA::~hasDMA()
{
delete []style;
}
hasDMA &hasDMA::operator=(const hasDMA &hs)
{
//都要先检查要拷贝的对象和this是否是同一个
if(this == &hs)
return *this;
//下边代码=默认调用派生类的=将会进入无限递归,应该声明为基类的=
//*this = hs;
baseDMA::operator=(hs);
delete []style;
style = new char[strlen(hs.style) + 1];
strcpy(style, hs.style);
return *this;
}
ostream &operator<<(ostream &os, const hasDMA &hs)
{
os << (const baseDMA &)hs;
os << "hs style: " << hs.style << endl;
return os;
}
总结:当基类和派生类都采用new(动态内存分配)时,派生类的析构函数、复制构造函数、赋值运算符都必须使用相应的基类方法来处理基类元素。
13.8 类涉及回顾
13.8.1 编译器生成的成员函数
- 默认构造函数(第12章)
默认构造函数要么没有参数,要么所有的参数都有默认值;
如果没有定义任何构造函数,编译器将定义默认构造函数;
如果定义了某种构造函数,编译器将不再定义默认构造函数。 - 复制构造函数
当基类和派生类都采用new(动态内存分配)时,派生类的析构函数、复制构造函数、赋值运算符都必须使用相应的基类方法来处理基类元素。 - 赋值运算符
不要将赋值与初始化搞混:语句创建新的对象,使用初始化;语句修改已有对象的值,则是赋值。
Star sirius;
Star alpha = sirinus; //初始化
Star dogstar;
dogstar = sirinus; //赋值
记住:只要代码中需要显示的定义复制构造函数,则同理,也需要显示定义赋值运算符。
13.8.2 其它的类方法
- 构造函数
构造函数区别于其它类方法,它创建新的对象,而其它类方法只是被现有的对象调用(构造函数不能被继承的原因之一;继承的名字一样,而构造函数不能一样)。 - 析构函数
使用了new分配内存的构造函数必须定义显示析构函数来释放分配的内存;对于基类,即使它不需要析构函数,也应提供一个析构函数且必须是虚的。 - 转换函数(11.6.1)
构造函数只用于从某种类型到类类型的转换;要实现相反的转换,从类类型转换到其它类型,必须使用特殊的C++运算符函数——转换函数。
关键字explicti
:禁止隐式转换,必须显示转换。 - 按值传递对象与传递引用
编写使用对象作为参数时,应按引用而非按值来传递对象(提高效率)。 - 返回对象和返回引用
返回引用效率高于返回对象,但不能返回在函数中创建的临时对象的引用。
6.使用const
const可以确保方法不修改参数;不修改调用的对象。
13.8.3 公有继承的考虑因素
1.is-a关系
2.什么不能被继承
构造函数;析构函数;赋值运算符(特征标)。