差异于隐式转换,显式转换运算符必须经过更换的主意来调用。
假诺转换操作会造成极度或遗失新闻,则应将其标志为explicit
。
那可掣肘编写翻译器静默调用可能爆发意料之外后果的更换操作。
简短转换将造成编写翻译时不当 CS026陆。
implicit
关键字用于评释隐式的用户定义类型转换运算符。
假设能够保障转换进程不会导致数据丢失,则可利用该重大字在用户定义类型和其余类型之间展开隐式转换。
类库:类库由类注解和贯彻构成。类组合了数据表示和类方式,由此提供了比函数库尤其完整的程序包。
本章内容:
该引用摘自:explicit(C#
参考)
引用摘自:implicit(C#
参考)
类继承:从已有的类派生出新的类,派生类继承了土生土长类(称为基类)的性状,包涵方法。
本节日假期设你未曾耳闻过STL中的vector或array的模版,大家来协调实现三个动态分配的数组类。那几个类允许设置和收获钦赐索引地点的要素,并自动完结有着的内部存款和储蓄器分配操作。一个动态分配数组的定义类如下所示:
template <typename T>
class Array
{
public:
// 创建一个可以按需要增长的设置了初始化大小的数组
Array();
virtual ~Array();
// 不允许分配和按值传递
Array<T>& operator=(const Array<T>& rhs) = delete; // C++11 禁用赋值函数重载
Array(const Array<T>& src) = delete; // C++11 禁用拷贝构造函数
// 返回下标x对应的值,如果下标x不存在,则抛出超出范围的异常。
T getElementAt(size_t x) const;
// 设置下标x的值为val。如果下标x超出范围,则分配空间使下标在范围内。
void setElementAt(size_t x, const T& val);
private:
static const size_t kAllocSize = 4;
void resize(size_t newSize);
// 初始化所有元素为0
void initializeElement();
T *mElems;
size_t mSize;
};
以此接口援助设置和走访成分。它提供了随机走访的承接保险:客户能够创制数组,并安装成分壹、100和1000,而不用驰念内部存款和储蓄器管理的题目。
下边是这个格局的达成:
template <typename T> Array<T>::Array()
{
mSize = kAllocSize;
mElems = new T[mSize];
initializeElements();
}
template <typename T> Array<T>::~Array()
{
delete[] mElems;
mElems = nullptr;
}
template <typename T> void Array<T>::initializeElements()
{
for (size_t i=0; i<mSize; i++)
{
mElems[i] = T();
}
}
template <typename T> void Array<T>::resize(size_t newSize)
{
// 拷贝一份当前数组的指针和大小
T *oldElems = mElems;
size_t oldSize = mSize;
// 创建一个更大的数组
mSize = newSize; // 存储新的大小
mElems = new T[newSize]; // 给数组分配新的newSize大小空间
initializeElements(); // 初始化元素为0
// 新的size肯定大于原来的size大小
for (size_t i=0; i < oldSize; i++)
{
// 从老的数组中拷贝oldSize个元素到新的数组中
mElems[i] = oldElems[i];
}
delete[] oldElems; // 释放oldElems的内存空间
oldElems = nullptr;
}
template <typename T> T Array<T>::getElementAt(size_t x) const
{
if (x >= mSize)
{
throw std::out_of_range("");
}
return mElems[x];
}
template <typename T> void Array<T>::setElementAt(size_t x, const T& val)
{
if (x >= mSize)
{
// 在kAllocSize的基础上给数组重新分配客户需要的空间大小
resize(x + kAllocSize);
}
mElems[x] = val;
}
上面是应用那些类的事例:
Array<int> myArray;
for (size_t i=0; i<10; i++)
{
myArray.setElementAt(i, 100);
}
for (size_t j=0; i< 10; j++)
{
cout << myArray.getElementAt(j) << " ";
}
从中能够看来,大家不供给告诉数组要求有个别空间。数组会分配保存给定成分所急需的足足空间,可是接连利用setElementAt()
和getElementAt()
主意不是太有利。于是大家想像上边包车型客车代码1样,使用数组的目录来表示:
Array<int> myArray;
for (size_t i=0; i<100; i++)
{
myArray[i] = 100;
}
for (size_t j=0; j<10; j++)
{
cout << myArray[j] << " ";
}
要利用下标方法,则要求利用重载的下标运算符。通过以下方法给类添加operator[]
:
template <typename T> T& Array<T>::operator[] (size_t x)
{
if (x >= mSize)
{
// 在kAllocSize的基础上给数组重新分配客户需要的空间大小
resize(x + kAllocSize);
}
return mElems[x];
}
现行反革命,上边运用数组索引表示法的代码能够符合规律使用了。operator[]
能够设置和收获成分,因为它回到的是岗位x处的要素的目录。能够通过这一个引用对这么些成分赋值。当operator[]
用在赋值语句的右边时,赋值操作实际修改了mElems数组中地点x处的值。
展现转换关键字
explicit
能向阅读代码的种种人驾驭地提示您要转移类型。
仍以Student求和举例
通过类继承能够形成的工作:
固然有时operator[]
重回能够当作左值的要素会很有益,但并非总是须要那种表现。最佳还是能够回去const值或const引用,提供对数组申月素的只读访问。理想图景下,能够提供三个operator[]
:三个回来引用,另三个回去const引用。示例代码如下:
T& operator[] (size_t x);
const T& operator[] (size_t x); // 错误,不能基于返回类型来重载(overload)该方法。
唯独,这里存在3个难点:不能够仅根据重返类型来重载方法或运算符。由此,上述代码不能编写翻译。C++提供了一种绕过那个界定的诀窍:借使给第三个operator[]
标志天性const
,编写翻译器就能分别这多个本子。假诺对const
目的调用operator[]
,编写翻译器就会采纳const operator[]
;如果对非const
指标调用operator[]
,编写翻译器会采取非const
的operator[]
。上面是那四个运算符的科学原型:
T& operator[] (size_t x);
const T& operator[] (size_t x) const;
下面是const operator[]
的兑现:要是索引超出了限定,那一个运算符不会分配新的内部存款和储蓄器空间,而是抛出极度。假设只是读取成分值,那么分配新的空中就平昔不意义了:
template <typename T> const T& Array<T>::operator[] (size_t x) const
{
if (x >= mSize)
{
throw std::out_of_range("");
}
return mElems[x];
}
上边的代码演示了那三种方式的operator[]
:
void printArray(const Array<int>& arr, size_t size);
int main()
{
Array<int> myArray;
for (size_t i=0; i<10; i++)
{
myArray[i] = 100; // 调用non-const operator[],因为myArray是一个non-const对象
}
printArray(myArray, 10);
return 0;
}
void printArray(const Array<int>& arr, size_t size)
{
for (size_t i=0; i<size; i++)
{
cout << arr[i] << ""; //调用const operator[],因为arr是一个const对象
}
count << endl;
}
留意,仅仅是因为arr是const,所以printArray()
中调用的是const operator[]
。如果arr不是const
,则调用的长短const operator[]
,固然事实上并不曾改动结果值。
该引用摘自:利用转换运算符(C#
编制程序指南)
class Student
{
/// <summary>
/// 语文成绩
/// </summary>
public double Chinese { get; set; }
/// <summary>
/// 数学成绩
/// </summary>
public double Math { get; set; }
}
*能够在已有类的底蕴上添加效果;
本条是因而提供某种类型的键,对二个集合实行“索引”的范例的自然延伸;vector(或更广义的任何线性数组)是一种特例,在那之中的“键”只是数组中的地方。将operator[]
的参数作为提供四个域之间的投射:键域到值域的投射。因而,可编写制定3个将随机档次作为目录的operator[]
。那些项目未必是整数类型。STL的涉嫌容器正是那样做的,例如:std::map
。
譬如,能够创造二个事关数组,在这之中使用string
而不是整数作为键。下边是涉及数组的定义:
template <typename T>
class AssociativeArray
{
public:
AssociativeArray();
virtual ~AssociativeArray();
T& operator[] (const std::string& key) const;
const T& operator[] (const std::string& key) const;
private:
// 具体实现部分省略……
}
留意:不能够重载下标运算符以便接受七个参数,假若要提供接受多少个目录下标的造访,能够选用函数调用运算符。
仍以Student为例,取语文和数学成就的和,不采取explicit
不使用implicit
求和
*能够给类添加多少;
C++允许重载函数调用运算符,写作operator()
。若是自定义类中编辑1个operator()
,那么这一个类的指标就足以当作函数指针使用。只可以将那些运算符重载为类中的非static
艺术。下边包车型大巴例证是2个简约的类,它含有1个重载的operator()
以及一个独具同等行为的措施:
class FunctionObject
{
public:
int operator() (int inParam); // 函数调用运算符
int doSquare(int inParam); // 普通方法函数
};
// 实现重载的函数调用运算符
int FunctionObject::operator() (int inParam);
{
return inParam * inParam;
}
上面是利用函数调用运算符的代码示例,注意和类的不足为奇方法调用举办相比较:
int x = 3, xSquared, xSquaredAgain;
FunctionObject square;
xSquared = square(x); // 调用函数调用运算符
xSquaredAgain = square.doSquare(x); // 调用普通方法函数
富含函数调用运算符的类的对象称为函数对象,或简称为仿函数(functor)。
函数调用运算符看上去有点奇怪,为啥要为类编排3个新鲜措施,使这些类的目的看上去像函数指针?为何不直接编写一个函数或专业的类的法子?相比较标准的对象方法,函数函数对象的裨益如下:这几个指标有时能够伪装为函数指针。只要函数指针类型是模板化的,就能够把这么些函数对象正是回调函数字传送入须求接受的函数指针的例程。
对照全局函数,函数对象的裨益尤其扑朔迷离,重要有多个好处:
(1)对象能够在函数对象运算符的重复调用之间,在多少数据成员中保存新闻。例如,函数对象可以用于记录每一遍经过函数调用运算符调用采集到的数字的连天总和。
(二)能够通过安装数据成员来自定义函数对象的表现。例如,能够编写3个函数对象,来相比较函数参数和数码成员的值。那么些数据成员是可安顿的,因而这么些指标能够自定义为实施此外相比操作。
自然,通过全局变量或静态变量都足以兑现上述任何好处。可是,函数对象提供了一种更简洁的格局,而选用全局变量或静态变量在三十二线程应用程序中也许会发出难题。
由此遵守壹般的主意重载规则,可为类编排任意数量的operator()
。确切的讲,区别的operator()
必须有例外数额的参数或不相同类别的参数。例如,能够向FunctionObject
类添加二个带string
引用参数的operator()
:
int operator() (int inParam);
void operator() (string& str);
函数调用运算符还足以用来提供数组的多重索引的下标。只要编写三个行为看似于operator[]
,但接受多个参数的operator()
即可。那项技艺的绝无仅不经常是急需使用()而不是[]进行索引,例如myArray(3, 4) = 6
。
class Student
{
/// <summary>
/// 语文成绩
/// </summary>
public double Chinese { get; set; }
/// <summary>
/// 数学成绩
/// </summary>
public double Math { get; set; }
}
class Program
{
static void Main(string[] args)
{
var a = new Student
{
Chinese = 90.5d,
Math = 88.5d
};
//a的总成绩 语文和数据的总分数
Console.WriteLine(a.Chinese + a.Math);
}
}
*可以修改类的一颦一笑。
能够重载三个化解引用运算符:*、->、->*。近期不思考->(在后面包车型大巴章节有议论),该节只缅怀\和->的本来意义。化解对指针的引用,允许直接待上访问那一个指针指向的值,->是\免除引用之后再接.成员选取操作的简写。下边包车型地铁代码演示了那三头的1致性:
SpreadsheetCell* cell = new SpreadsheetCell;
(*cell).set(5); // 解除引用加成员函数调用
cell->set(5); // 单箭头解除引用和成员函数调用
在类中重载解除引用运算符,能够使那一个类的对象行为和指针壹致。那种能力的首要用途是贯彻智能指针,还是能用来STL使用的迭代器。本节因而智能指针类模板的事例,讲解重载相关运算符的焦点机制。
告诫:C++有多少个标准的智能指针:std::shared_ptr和std::unique_ptr。强烈使用那一个标准的智能指针而不是友善编排。本节列举的例证是为了演示怎么着编写解除引用运算符。
下边是那几个示例智能指针类模板的概念,在那之中还未曾填入解引用运算符:
template <typename T> class Pointer
{
public:
Pointer(T* inPtr);
virtual ~Pointer();
// 阻止赋值和按值传值
Pointer(const Pointer<T>& src) = delete; // C++11 禁用拷贝构造函数
Pointer<T>& operator=(const Pointer<T>& rhs) = delete; // C++11 禁用赋值函数重载
// 解引用运算符将会在这里
private:
T* mPtr;
};
其1智能指针只是保存了3个无独有偶指针,在智能指针销毁时,删除这几个指针指向的储存空间。那些完成平等非凡简便:构造函数接受叁个确实的指针(普通指针),该指针保存为类中仅有的数据成员。析构函数释放那几个指针引用的仓库储存空间。
template <typename T> Pointer<T>::Pointer(T* inPtr) : mPtr(inPtr);
{
}
template <typename T> Pointer<T>::~Pointer()
{
delete mPtr;
mPtr = nullptr;
}
能够动用以下措施采纳那个智能指针模板:
Pointer<int> smartInt(new int);
*smartInt = 5; //智能指针解引用
cout << *smartInt << endl;
Pointer<SpreadsheetCell> smartCell(new SpreadsheetCell);
smartCell->set(5); //解引用同时调用set方法
cout << smartCell->getValue() << endl;
从那么些例子能够看到,这些类必须提供operator*
和operator->
的落实。其促成都部队分在下两节中教师。
求和:
使用implicit
继承机制只须要提供新特色,甚至不必要拜访源代码就能够派生出类。
当免除对指针的引用时,平日希望能访问那么些指针指向的内部存款和储蓄器。假使那块内部存款和储蓄器包括了二个大致类型,例如int,应该能够直接修改那一个值。借使内部存储器中包罗了复杂的类型,例如对象,那么应该能透过.运算符访问它的数据成员或方式。
为了提供这几个语义,operator*
有道是回到叁个变量或对象的引用。在Pointer类中,申明和概念如下所示:
template <typename T> class Pointer
{
public:
// 构造部分同上,所以省略
T& operator*();
const T& operator*() const;
// 其它部分暂时省略
};
template <typename T> T& Pointer<T>::operator*()
{
return *mPtr;
}
template <typename T> const T& Pointer<T>::operator*() const
{
return *mPtr;
}
从这么些例子中得以见到,operator*
回来的是底层普通指针指向的靶子或变量的引用。与重载下标运算符一样,同时提供格局的const版本合非const版本也很有用,那多个本子分别重返const引用和非const引用。
class Program
{
static void Main(string[] args)
{
var a = new Student
{
Chinese = 90.5d,
Math = 88.5d
};
//a的总成绩 语文和数据的总分数
Console.WriteLine(a.Chinese + a.Math);
}
}
class Student
{
/// <summary>
/// 语文成绩
/// </summary>
public double Chinese { get; set; }
/// <summary>
/// 数学成绩
/// </summary>
public double Math { get; set; }
/// <summary>
/// 隐式求和
/// </summary>
/// <param name="a"></param>
public static implicit operator double(Student a)
{
return a.Chinese + a.Math;
}
}
箭头运算符稍微复杂1些,应用箭头运算符的结果应该是目的的3个分子或格局。不过,为了兑现那或多或少,应该要贯彻operator*
和operator.
;而C++有丰盛的理由不兑现运算符operator.
:不只怕编写单个原型,来捕捉任何恐怕采用的积极分子或艺术。因而,C++将operator->
正是一个特例。例如下边包车型大巴那行代码:
smartCell->set(5);
C++将那行代码解释为:
(smartCell.operator->())->set(5);
隐式转换关键字,显式转换关键字。从中能够见见,C++给重载的operator->
回来的任何结果使用了另一个operator->
。由此,必须再次来到三个针对性对象的指针,如下所示:
template <typename T> class Pointer
{
public:
// 省略构造函数部分
T* operator->();
const T* operator->() const;
// 其它部分省略
};
template <typename T> T* Pointer<T>::operator->()
{
return mPtr;
}
template <typename T> const T* Pointer<T>::operator->() const
{
return mPtr;
}
使用explicit
求和:
一、多个简易的基类
在C++中,得到类成员和艺术的地点,以博取指向这几个分子和情势的指针是完全合法的。但是,不能够在未曾指标的意况下访问非static数据成员或调用非static方法。类数据成员和章程的重大在于它们依附于对象。因而,通过指针调用方法和走访数据成员时,必须在指标的上下文中清除那些指针的引用。上边包车型客车事例表达了.和->运算符:
SpreadsheetCell myCell;
double (SpreadsheetCell::*methodPtr)() const = &SpreadsheetCell::getValue;
cout << (myCell.*methodPtr)() << endl;
注意,.*运算符解除对章程指针的引用并调用那几个方式。假若有一个针对性对象的指针而不是指标自小编,还有1个一模一样的operator->*
能够由此指针调用方法。那些运算符如下所示:
SpreadsheetCell *myCell = new SpreadsheetCell();
double (SpreadsheetCell::*methodPtr)() const = &SpreadsheetCell::getValue();
cout << (myCell->*methodPtr)() << endl;
C++分裂意重载operator.*
(就像不容许重载operator.
壹样),不过足以重载operator->*
。然则那个运算符的重载分外复杂,标准库中的share_ptr
模板也向来不重载operator->*
。
class Student
{
/// <summary>
/// 语文成绩
/// </summary>
public double Chinese { get; set; }
/// <summary>
/// 数学成绩
/// </summary>
public double Math { get; set; }
public static explicit operator double(Student a)
{
return a.Chinese + a.Math;
}
}
class Program
{
static void Main(string[] args)
{
var a = new Student
{
Chinese = 90.5d,
Math = 88.5d
};
double total = a;
//a的总成绩 语文和数据的总分数
Console.WriteLine(total);
}
}
首先大家定义二个大致的基类Person,其安排如下:
归来SpreadsheetCell例子,思量如下两行代码:
SpreadsheetCell cell(1.23);
string str = cell; //不能编译通过
SpreadsheetCell包括贰个字符串表明式,因而将SpreadsheetCell赋值给string变量看上去是吻合逻辑的。但不能这么做,编写翻译器会表示不知底什么将SpreadsheetCell转换为string。你或然会因而下述情势逼迫编写翻译器进行那种转移:
string str = (string)cell; //仍然不能编译通过
率先,上述代码依旧不可能编译,因为编译器依然不理解哪些将SpreadsheetCell转换为string。从那行代码中编写翻译器已经精通你想让编写翻译器做转换,所以编写翻译器要是精通如何转移,就会进展转移。其次,1般景色下,最棒不用在先后中添加那种无理由的类型转换。如若想同意那类赋值,必须告诉编写翻译器怎么样进行它。约等于说,可编写制定一个将SpreadsheetCell转换为string的变换运算符。其原型如下:
operator std::string() const;
函数名字为operator std::string
。它并没有回到类型,因为再次来到类型是通过运算符的名称分明的:std::string
。那一个函数时const
,因为那个函数不会修改被调用的靶子。达成如下:
SpreadsheetCell::operator string() const
{
return mString;
}
那就完结了从SpreadsheetCell到string的更换运算符的编写。今后的编写翻译器能够承受上边那行代码,并在运维时不易的操作。
SpreadsheetCell cell(1.23);
string str = cell; //按照预期的执行
能够一样的语法编写任何项目标转换运算符。例如,下边是从SpreadsheetCell到double的转移运算符:
SpreadsheetCell::operator double() const
{
return mValue;
}
目前得以编写制定以下代码:
SpreadsheetCell cell(1.23);
double d1 = cell;
求和:
Person.h
瞩目,为SpreadsheetCell对象编排double转换运算符时会引入多义性难点。例如上边那行代码:
SpreadsheetCell cell(1.23);
double d2 = cell + 3.3; // 不能编译通过,如果你已经重载了operator double()
近日这一行不可能得逞编写翻译。在编写制定运算符double()
事先,那行代码可以编写翻译,那么现在面世了哪些难点?难题在于,编译器不知底应该经过operator double()
将cell
转换为double
,再执行double
加法,仍然经过double
构造函数将3.三转换为SpreadsheetCell
,再执行SpreadsheetCell加法。在编写operator double()
事先,编写翻译器唯有三个摘取:通过double
构造函数将叁.三转换为SpreadsheetCell
,再执行SpreadsheetCell
加法。然则,今后编译器能够推行三种操作,存在2义性,所以编译器便报错。
在C++1一事先,常常消除那几个难题的方法是将构造函数标记为explicit
,防止止使用这么些构造函数举办机动转换。然则,我们不想把那些构造函数标记为explicit
,常常希望举办从double
到SpreadsheetCell
的全自动类型转换。自C++1壹从此,能够将double
类型转换运算符标记为explicit
,来解决那么些标题:
explicit operator double() const;
上边包车型地铁代码演示了那种方法的施用:
SpreadsheetCell cell = 6.6; // [1]
string str = cell; // [2]
double d1 = static_cast<double>(cell); // [3]
double d2 = static_cast<double>(cell + 3.3); // [4]
下边解释了上述代码中的各行:
[1]使用隐式类型转换从double
转换到SpreadsheetCell
。由于那是在宣称中,所以那个是因此调用接受double
参数的构造函数实行的。
[2]使用了operator string()
转换运算符。
[3]使用了operator double()
更换运算符。注意,由于那么些转换运算符以后宣称为explicit,所以供给强制类型转换。
[4]通过隐式类型转换将3.三更换为SpreadsheetCell
,再展开八个SpreadsheetCell
和operator+
操作,之后展开须要的显式类型转换到调用operator double()
。
class Program
{
static void Main(string[] args)
{
var a = new Student
{
Chinese = 90.5d,
Math = 88.5d
};
//a的总成绩 语文和数据的总分数
Console.WriteLine((double)a);
}
}
1 #include <iostream>
2 #include <string>
3 using std::string;
4 class Person{
5 private:
6 string name_;
7 int age_;
8 public:
9 Person(const string & name = "none", int age = 0);//形参类型声明为const string &,那么实参既可以是string对象,也可以是字符串常量。
10 void setName(const string &name);
11 void setAge(int age);
12 string getName()const;
13 int getAge() const;
14 friend std::ostream & operator<<(std::ostream & os, const Person & p);
15 };
偶尔,能将指标用在布尔表明式中会相当实惠。例如,程序员日常在标准化语句中如此使用指针:
if (prt != nullptr) { /* 执行一些解除引用的操作 */}
突发性程序员会编写那样的简写条件:
if (prt) { /* 执行一些解除引用的操作 */}
偶尔还是能见到那样的代码:
if (!prt) { /* 执行一些操作 */}
而今,上述任何表明式都无法和原先概念的Pointer智能指针类1起编写翻译。可是,能够给类添加一个转换运算符,将它转换为指针类型。然后,这些种类和nullptr的相比较,以及单独贰个对象在if语句中的情势都会触发这些指标向指针类型的变换。转换运算符常用的指针类型为void*,因为那个指针类型除了在布尔表明式中测试之外,不可能执行此外操作。
operator void*() const
{
return mPtr;
}
后天下边包车型地铁代码可以成功编写翻译,并能实现预期的任务:
void process(Pointer<SpreadsheetCell>& p)
{
if (p != nullptr)
{
cout << "not nullptr" << endl;
}
if (p != NULL)
{
cout << "not NULL" << endl;
}
if (p)
{
cout << "not nullptr" << endl;
}
if (!p)
{
cout << "nullptr" << endl;
}
}
int main()
{
Pointer<SpreadsheetCell> smartCell(nullptr);
process(smartCell);
cout << endl;
Pointer<SpreadsheetCell> anotherSmartCell(new SpreadsheetCell(5.0));
process(anotherSmartCell);
}
输出结果如下所示:
nullprt
not nullptr
not NULL
not nullptr
另一种艺术是重载operator bool()
而不是operator void*()
。毕竟是在布尔表明式中运用对象,为何无法一向转换为bool呢?
operator bool() const
{
return mPtr != nullptr;
}
上面包车型客车可比还是能够运营:
if (p != NULL)
{
cout << "not NULL" << endl;
}
if (p)
{
cout << "not nullptr" << endl;
}
if (!p)
{
cout << "nullptr" << endl;
}
然而,使用operator bool()
时,下面和nullptr
的比较会促成编写翻译器错误:
if (p != nullptr) { cout << "not nullptr" << endl; } //Error
那是不易的一坐一起,因为nullptr
有自身的品种nullptr_t
,这一个项目没有活动类型转换为整数0。编写翻译器找不到接受Pointer
对象和nullptr_t
对象的operator!=
。能够把如此的operator!=
实现为Pointer
类的友元:
template <typename T>
bool operator!=(const Pointer<T>& lhs, const std::nullptr_t& rhs)
{
return lhs.mPtr != rhs;
}
唯独,完结那个operator!=
后,上边包车型大巴比较会无法工作,因为编写翻译器知道该用哪个operator!=
:
if (p != NULL)
{
cout << "not NULL" << endl;
}
因此这些例子,你恐怕得出以下结论:operator bool()
技术看上去只适合于不表示指针的指标,以及转换为指针类型并不曾意义的靶子。遗憾的是,添加转换至bool的转移运算符会产生别的1些不能够预感的后果。当规则允许时,C++会使用“类型升高”规则将bool类型自动转换为int类型。因而,选用operator bool()
时,上面包车型地铁代码能够编写翻译运行:
Pointer<SpreadsheetCell> smartCell(new SpreadsheetCell);
int i = smartCell; //转换smartCell指针从bool到int
那平日并不是期待或需求的行为。由此,很多程序员更偏爱选拔operator void*()
而不是operator bool()
。
从中能够看到,重载运算符时须要思索规划因素。哪些操作符供给重载的决策会直接影响到客户对类的利用方法。
Person.cpp
1 #include "Person.h"
2 Person::Person(const string & name, int age){
3 name_ = name;
4 age_ = age;
5 }
6 std::ostream & operator<<(std::ostream & os, const Person & p){
7 os << "name:" << p.name_ << ", age:" << p.age_;
8 return os;
9 }
10 void Person::setName(const string &name){
11 name_ = name;
12 }
13 void Person::setAge(int age){
14 age_ = age;
15 }
16 string Person::getName()const{
17 return name_;
18 }
19 int Person::getAge()const{
20 return age_;
21 }
C++最复杂的地点之一便是new
和delete
的细节。思考下边几行代码:
SpreadsheetCell* cell = new SpreadsheetCell();
new SpreadsheetCell()
这有的称作new
表明式。它实现了两件工作。首先,通过调用opetator new
为SpreadsheetCell
目的分配了内部存款和储蓄器空间。然后,为这些目标调用构造函数。唯有这么些构造函数完成了,才回来指针。
delete
的做事方式与此类似。思虑下边那行代码:
delete cell;
这行称为delete
表明式。它首先调用cell的析构函数,然后调用operator delete
来刑释内部存款和储蓄器。
可以重载operator new
和operator delete
来控制内部存款和储蓄器的分配和刑满释放解除劳教,但无法重载new
表达式和delete
威尼斯人线上娱乐,表明式。由此,能够自定义实际的内部存款和储蓄器分配和刑满释放解除劳教,但不能够自定义构造函数和析构函数的调用。
(壹). new表明式和operator new
有陆种不相同款型的new表达式,每一个格局都有照应的operator new
。前4种new
表达式:new
、new[]
、nothrow new
、nothrow new[]
。上边列出了<new>
头文件种对应的四种operator new
形式:
void* operator new(size_t size); //For new
void* operator new[](size_t size); //For new[]
void* operator new(size_t size, const nothrow_t&) noexcept; //For nothrow new
void* operator new[](size_t size, const nothrow_t&) noexcept; //For nothrow new[]
有二种非常的new
表达式,它们不举办内部存款和储蓄器分配,而在已部分存储段上调用构造函数。那种操作称为placement new
运算符(蕴含单对象和数组格局)。它们在已存在的内部存款和储蓄器上协会对象,如下所示:
void* ptr = allocateMemorySomehow();
SpreadsheetCell* cell = new(prt) SpreadsheetCell();
其1个性有点偏门,但知情那项特色的存在格外重大。假如必要完毕内存池,以便在不自由内存的情状下录取内部存款和储蓄器,那项特殊性就分外有利于。对应的operator new
格局如下,但C++标准禁止重载它们:
void* operator new(size_t size, void* p) noexcept;
void* operator new[](size_t size, void* p) noexcept;
(二). delete表明式和operator delete
唯有三种分化款式的delete
表明式能够调用:delete
和delete[]
;没有nothrow
和placement
形式。然而,
operator delete
有陆种形式。为何有那种不对称性?二种nothrow
和placement
的款型唯有在构造函数抛出十一分时才会使用。那种景色下,匹配调用构造函数从前分配内部存款和储蓄器时使用的operator new
的operator delete
会被调用。可是,即使正常地删除指针,delete
会调用operator delete
或operator delete[]
(绝不会调用nothrow
或placement
花样)。在事实上中,那并从未涉嫌:C++标准建议,从delete
抛出尤其的行事是未定义的,约等于说delete
世代都不该抛出极度,由此nothrow
版本的operator delete
是剩下的;而placement
版本的delete
应该是2个空操作,因为在placement operator new
中并不曾分配内部存款和储蓄器,因而也不必要自由内存。上面是operator delete
各类格局的原型:
void operator delete(void* ptr) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete(void* ptr, const nothrow_t&) noexcept;
void operator delete[](void* ptr, const nothrow_t&) noexcept;
void operator delete(void* ptr, void*) noexcept;
void operator delete[](void* ptr, void*) noexcept;
如有供给,能够替换全局的operator new
和operator delete
例程。那些函数会被先后中的每一个new
表达式和delete
说明式调用,除非在类中有更特别的版本。然则,引用Bjarne
Stroustrup的一句话:“……替换全局的operator new
和operator delete
是内需胆量的。”。所以我们也不提出轮换。
告诫:如若控制一定要替换全局的operator new
,一定要注意在那些运算符的代码中并非对new
展开其余调用:不然会生出Infiniti循环。
更使得的技能是重载特定类的operator new
和operator delete
。仅当分配或释放特定类的目的时,才会调用那么些重载的运算符。下边是叁个类的事例,它重载了伍个非placement
形式的operator new
和operator delete
:
#include <new>
class MemoryDemo
{
public:
MemoryDemo();
virtual ~MemoryDemo();
void* operator new(std::size_t size);
void operator delete(void* ptr) noexcept;
void* operator new[](std::size_t size);
void operator delete[](void* ptr) noexcept;
void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void* operator new[](std::size_t size, const std::nothrow_t&) noexcept;
void operator delete[](void* ptr, const std::nothrow_t&) noexcept;
};
上面是这么些运算符的简练完毕,这么些完成将参数字传送递给了那个运算符全局版本的调用。注意nothrow
其实是2个nothrow_t
品类的变量:
void* MemoryDemo::operator new(size_t size)
{
cout << "operator new" << endl;
return ::operator new(size);
}
void MemoryDemo::operator delete(void* ptr) noexcept
{
cout << "operator delete" << endl;
::operator delete(ptr);
}
void* MemoryDemo::operator new[](size_t size)
{
cout << "operator new[]" << endl;
return ::operator new[](size);
}
void MemoryDemo::operator delete[](void* ptr) noexcept
{
cout << "operator delete[]" << endl;
::operator delete[](ptr);
}
void* MemoryDemo::operator new(size_t size, const nothrow_t&) noexcept
{
cout << "operator new nothrow" << endl;
return ::operator new(size, nothrow);
}
void MemoryDemo::operator delete(void* ptr, const nothrow_t&) noexcept
{
cout << "operator delete nothrow" << endl;
::operator delete(ptr, nothrow);
}
void* MemoryDemo::operator new[](size_t size, const nothrow_t&) noexcept
{
cout << "operator new[] nothrow" << endl;
return ::operator new[](size, nothrow);
}
void MemoryDemo::operator delete[](void* ptr, const nothrow_t&) noexcept
{
cout << "operator delete[] nothrow" << endl;
::operator delete[](ptr, nothrow);
}
下面包车型大巴代码以不相同措施分配和假释这么些类的靶子:
MemoryDemo* mem = new MemoryDemo();
delete mem;
mem = new MemoryDemo[10];
delete[] mem;
mem = new (nothrow) MemoryDemo();
delete mem;
mem = new (nothrow) MemoryDemo[10];
delete[] mem;
上面是运作结果:
operator new;
operator delete;
operator new[];
operator delete[];
operator new nothrow;
operator delete;
operator new[] nothrow;
operator delete[];
这些operator new
和operator delete
的兑现格外简单,但功用十分的小。它们目的在于介绍语法方式,以便在落到实处真正版本时参考。
警告:当重载operator new
时,要重载对应格局的operator delete
。不然,内部存款和储蓄器会依照内定的艺术分配,可是依据内建的语义释放,那两边大概不合作。
重载全数差别情势的operator new
看起来有个别过于。但是在一般景色下最棒那样做,从而制止内部存款和储蓄器分配区别。固然不想提供任何完毕,可利用=delete
来得地删除函数,以制止别人选取。具体内容可参考下壹节。
提示:在设计三个类的时候,大家供给思索一下多少个难点:
来得地删除或暗中同意化不局限用于构造函数和赋值运算符。例如,下边包车型地铁类删除了operator new
和new[]
,也正是说那几个类不可能经过new
或new[]
动态创造:
class MyClass
{
public:
void* operator new(std::size_t size) = delete;
void* operator new[](std::size_t size) = delete;
};
按以下方法使用这么些类会产生编写翻译器错误:
int main()
{
MyClass* p1 = new MyClass; // Error
MyClass* p2 = new MyClass[2]; // Error
return 0;
}
*是还是不是需要显式提供私下认可构造函数;
除此而外重载标准情势的operator new
之外,还是能编写带有额外参数的版本。例如上面是Memory德姆o类中有额外整数参数的operator new
和operator delete
原型:
void* operator new(std::size_t size, int extra);
void operator delete(void* ptr, int extra) noexcept;
福寿康宁如下所示:
void* MemoryDemo::operator new(size_t size, int extra)
{
cout << "operator new with extra int arg: " << extra << endl;
return ::operator new(size);
}
void MemoryDemo::operator delete(void* ptr, int extra) noexcept
{
cout << "operator delete with extra in arg: " << extra << endl;
return ::operator delete(ptr);
}
编纂带有额外参数的重载operator new
时,编写翻译器会自行允许编写对应的new表明式。因此能够编写制定那样的代码:
MemoryDemo* pmem = new (5) MemoryDemo();
delete pmem;
new
的附加参数以函数调用的语法传递(和nothrow new
同等)。那个额外参数可用来向内部存款和储蓄器分配例程传递各类标志或计数器。例如,1些周转时库在调节和测试形式中使用那种样式,在分配对象的内存时提供文件名和行号,那样,在发生内部存款和储蓄器泄漏时,能够识别出产生难题的抽成内部存款和储蓄器所在的代码行数。
概念带有额外参数的operator new
时,还应有定义带有额外参数的照应operator delete
。不能够自身调用那些蕴藏额外参数的operator delete
,唯有在运用了带额外参数的operator new
且对象的构造函数抛出拾叁分时,才会调用这么些operator delete
。
另1种方式的operator delete
提供了需释放的内部存款和储蓄器大小和指针。只需评释带有额外大小参数的operator delete
原型。
警戒:如果类注明了七个1律版本的operator delete
,只但是2个经受大小参数,另3个不收受,那么不接受额外参数的版本总是会调用。假设急需选拔带大小参数的本子,则请只编写那三个版本。
可独立地将其余版本的operator delete
轮换为接受大小参数的operator delete
本子。下面是MemoryDemo类的概念,个中的率先个operator delete
改为接受要释放的内部存款和储蓄器大小作为参数:
class MemoryDemo
{
public:
// 省略其他内容
void* operator new(std::size_t size);
void operator delete(void* ptr, std::size_t size) noexcept;
// 省略其他内容
};
这个operator delete
兑现调用未有轻重参数的大局operator delete
,因为并不设有接受这么些小大参数的大局operator delete
。
void MemoryDemo::operator delete(void* ptr, size_t size) noexcept
{
cout << "operator delete with size" << endl;
::operator delete(ptr);
}
唯有须要为自定义类编写复杂的内部存储器分配和刑释方案时,才使用这一个功用。
*是还是不是供给显式提供析构函数;
*是或不是须求显式提供复制构造函数;
*是否要求显式提供赋值运算符重载函数;
*是不是须要显式提供地方运算符函数;
一般的话,要是在类的构造函数中使用了new运算符,或然在其它成员函数中应用了new运算符来修改类的积极分子,那么就须求思量显式提供复制构造函数、赋值运算符重载函数、析构函数。在Person类中,大家应用编写翻译器提供的默许析构函数、私下认可复制构造函数和暗中认可的赋值运算符重载函数即可。
一、派生一个类
上面大家统一筹划一个Teacher类继承自Person类。首先将Teacher类注解为从Person类派生而来:
1 #include <iostream>
2 #include "Person.h"
3
4 class Teacher:public Person{
5 // ...
6 };
冒号提议Teacher类的基类是Person类。上述特殊的生命头证明Person是三个国有基类,那杯称为公有派生。派生类对象涵盖基类对象。
使用国有派生,基类的国有成员将改成派生类的公有成员;基类的个体部分也将改为派生类的一部分,但只好透过基类的国有和保卫安全格局访问。
派生类将兼具以下特征:
*派生类对象存款和储蓄了基类的多少成员(派生类继承了基类的兑现);
*派生类对象能够使用基类的章程(派生类继承了基类的接口)。
接下去,大家就足以在持续特性中添加上面包车型大巴剧情:
*派生类须求协调的构造函数;
*派生类能够依照要求添加额外的数额成员和成员函数。
在大家规划的Teacher类须要四个数目成员来存款和储蓄工作的单位、薪酬以及所教学的教程。还应蕴涵检查那么些消息和重置这一个音讯的方法:
1 #include <iostream>
2 #include "Person.h"
3
4 class Teacher:public Person{
5 private:
6 string workUnit_;//工作单位
7 float salary_;//工资
8 string course_;//教授的课程
9 public:
10 Teacher(const string & , int , const string &, float, const string &);
11 Teacher(const Person &, const string &, float, const string &);
12 Teacher();
13 void setWorkUnit(const string & );
14 void setSalary(float );
15 void setCourse(const string &);
16 string getWorkUnit()const;
17 float getSalary()const;
18 string getCourse()const;
19 friend std::ostream & operator<<(std::ostream & os , const Teacher &);
20 };
构造函数必须给新成员(假使有新成员)和一连的积极分子提供数据。
二、构造函数:访问权限的设想
派生类不可能直接待上访问基类的私家成员,而必须通过基类方法开始展览走访。例如,派生类构造函数不能够直接设置继承来的积极分子,而必须选取基类的国有方法来访问私有的基类成员。具体地说,派生类构造函数必须利用基类的构造函数。
成立派生类对象时,程序首先成立基类对象。从概念上说,那象征基类对象应当在程序进入派生类构造函数以前被创制。C++使用成员伊始化列表语法来完成那种工作。例如,下边是率先个Teacher类的构造函数代码:
1 Teacher::Teacher(const string & name, int age, const string & workUnit, float salary, const string & course):Person(name,age){
2 workUnit_ = workUnit;
3 salary_ = salary;
4 course_ = course;
5 }
必须首先创立基类对象,就算不调用基类构造函数,程序将选取暗许的基类构造函数,由此下边包车型大巴两段代码是如出1辙的:
1 Teacher::Teacher(const string & name, int age, const string & workUnit, float salary, const string & course){
2 workUnit_ = workUnit;
3 salary_ = salary;
4 course_ = course;
5 }
1 Teacher::Teacher(const string & name, int age, const string & workUnit, float salary, const string & course):Person(){
2 workUnit_ = workUnit;
3 salary_ = salary;
4 course_ = course;
5 }
除非要使用暗许的构造函数,不然应显式调用正确的基类构造函数。
上面来看第1个构造函数的代码:
1 Teacher::Teacher(const Person & per, const string & workUnit, float salary, const string & course):Person(per){
2 workUnit_ = workUnit;
3 salary_ = salary;
4 course_ = course;
5 }
由于per的品类为Person,因而调用基类的复制构造函数。在此地,基类Person未有概念复制构造函数,假设急需复制构造函数但又从未定义,编写翻译器将生成三个。在那种状态下,执行成员复制的隐式复制构造函数是稳当的,因为这几个类未有行使动态内部存款和储蓄器分配。
同样,也足以对派生类使用成员开始化列表语法。在那种情形下,应在列表中利用成员名,而不是类名。所以,第2个构造函数可以遵照上面包车型地铁艺术编写:
Teacher::Teacher(const Person & per, const string & workUnit, float salary, const string & course):Person(per),workUnit_(workUnit),salary_(salary),course_(course){}
有关派生类构造函数的要义有如下几点:
*首先创设基类对象;
*派生类构造函数应透过分子初步化列表将基类新闻传递给基类构造函数;
*派生类构造函数应开首化派生类新增的数码成员。
这么些事例未有提供显式析构函数,由此利用隐式析构函数。释放对象的相继与创制对象的相继相反,即首先实施派生类的析构函数,然后自动调用基类的析构函数。
三、使用派生类
要利用派生类,程序必要求力所能及访问基类注明。可以将基类和派生类的证明置于同2个头文件中,也可以将种种类位居独立的头文件中,但鉴于那五个类是相关的,所以把其类注明放在一块儿更妥当。
上边是Teacher的完全方法落成公文:
1 #include "Teacher.h"
2 Teacher::Teacher(const string & name, int age, const string & workUnit, float salary, const string & course):Person(name,age){
3 workUnit_ = workUnit;
4 salary_ = salary;
5 course_ = course;
6 }
7 Teacher::Teacher(const Person & per, const string & workUnit, float salary, const string & course):Person(per){
8 workUnit_ = workUnit;
9 salary_ = salary;
10 course_ = course;
11 }
12 Teacher::Teacher(){
13 workUnit_ = "none";
14 salary_ = .0;
15 course_ = "none";
16 }
17 void Teacher::setCourse(const string & course){
18 course_ = course;
19 }
20 void Teacher::setWorkUnit(const string & workUnit){
21 workUnit_ = workUnit;
22 }
23 void Teacher::setSalary(float salary){
24 salary_ = salary;
25 }
26 string Teacher::getWorkUnit()const{
27 return workUnit_;
28 }
29 string Teacher::getCourse()const{
30 return course_;
31 }
32 float Teacher::getSalary()const{
33 return salary_;
34 }
35 std::ostream & operator<<(std::ostream & os,const Teacher & te){
36 os << "name:" << te.getName() << ",age:" << te.getAge() << ", workUnit:" << te.workUnit_ << ", salary:" << te.salary_ << ", course:" << te.course_;
37 return os;
38 }
肆、派生类和基类之间的分歧平日关系
派生类和基类之间有①对非凡关系。
*派生类能够利用基类的办法,条件是措施不是个人的。
*基类指针能够在不进行显式类型转换的状态下指向派生类对象;
*基类引用能够在不开始展览显式类型转换的场所下引用派生类对象。
但是,基类指针或引用只好调用基类方法。
经常,C++要求引用和指针类型与赋给的门类匹配,但那1平整对延续来说是例外。但是,那种不一致只是单向的,不得以将基类对象和地方赋给派生类引用和指针。
二、继承:is-a关系
派生类和基类之间的奇异关系是依照C++继承的最底层模型的。实际上,C++有3种持续格局:共有继承、爱戴持续和村办继承。公有继承是最常用的章程,它创设1种is-a关系,即派生对象也是3个基类对象,能够对基类执行的操作,也足以对派生类对象实施。
不过国有继承不享有下列关系:
*公有继承不成立has-a关系;
*公有继承不创造is-like-a关系;
*公有继承不创设is-implemented-as-a(作为….来达成)关系。
三、多态公有继承
多态:方法的行为取决于调用该措施的对象,即同二个办法的作为随上下文而异。
有几种关键的编制可用于达成多态公有继承:
*在派生类中重新定义基类的不二等秘书诀;
*使用虚方法。
上边大家重新设计Person类和Teacher类,
Person.h
1 #ifndef __Demo__Person__
2 #define __Demo__Person__
3
4 #include <iostream>
5 #include <string>
6 using namespace std;
7
8 class Person{
9 private:
10 string name_;
11 int age_;
12 public:
13 Person(const string & name = "无名氏", int age = 0 );
14 virtual ~Person(){};
15 void setName(const string & name);
16 void setAge(int age);
17 const string & getName()const;
18 int getAge()const;
19 virtual void showMessage()const;
20 void setMessage(const string & name, int age);
21 friend ostream & operator<<(ostream & os, const Person & per);
22
23
24 };
25 #endif /* defined(__Demo__Person__) */
Person.cpp
1 #include "Person.h"
2 Person::Person(const string & name, int age){
3 name_ = name;
4 age_ = age;
5 }
6 void Person::setAge(int age){
7 age_ = age;
8 }
9 void Person::setName(const string &name){
10 name_ = name;
11 }
12 const string & Person::getName()const{
13 return name_;
14 }
15 int Person::getAge()const{
16 return age_;
17 }
18 void Person::showMessage()const{//虚方法
19 cout <<"调用了Person对象的showMessage()方法:"<< *this;
20 }
21 void Person::setMessage(const string &name,int age){
22 cout << "调用了Person对象的setMessage()方法\n";
23 name_ = name;
24 age_ =age;
25 }
26 ostream & operator<<(ostream & os, const Person & per){
27 os << "name:" << per.name_ << ", age:" << per.age_;
28 return os;
29 }
Teacher.h
1 #ifndef __Demo__Teacher__
2 #define __Demo__Teacher__
3
4 #include <iostream>
5 #include "Person.h"
6
7 class Teacher:public Person{
8 private:
9 string school_;
10 float salary_;
11 public:
12 Teacher(const string & name = "无名氏", int age = 0, const string & school = "无", float salary = .0);
13 void setSchool(const string &);
14 void setSalary(float salary);
15 const string & getSchool()const;
16 float getSalary()const;
17 virtual void showMessage()const;
18 void setMessage(const string & school, float salary);
19 friend ostream & operator<<(ostream & , const Teacher &);
20 };
21
22 #endif /* defined(__Demo__Teacher__) */
Teacher.cpp
1 #include "Teacher.h"
2 Teacher::Teacher(const string & name , int age, const string & school, float salary ):Teacher(name, age){
3 school_ = school;
4 salary_ = salary;
5 }
6 void Teacher::setSchool(const string & school){
7 school_ = school;
8 }
9 void Teacher:: setSalary(float salary){
10 salary_ = salary;
11 }
12 const string & Teacher:: getSchool()const{
13 return school_;
14 }
15 float Teacher:: getSalary()const{
16 return salary_;
17 }
18 void Teacher:: showMessage()const{
19 cout << "调用了Teacher对象的showMessage()方法:" << *this;
20 }
21 void Teacher:: setMessage(const string & school, float salary){
22 cout << "调用了Teacher的setMessage()方法\n";
23 school_ = school;
24 salary_ = salary;
25 }
26 ostream & operator<<(ostream & os, const Teacher & per){
27 os <<"调用了Teacher对象的<<运算符方法,"<< "name:" << per.getName() << ", age:" << per.getAge() << ", school:" << per.school_ << ", salary:"<< per.salary_;
28 return os;
29 }
main.cpp
1 #include <iostream>
2 #include "Teacher.h"
3
4 using namespace std;
5
6 int main(int argc, const char * argv[]) {
7 Person *per = new Person{"王晓红",24};
8 Person *per2 = new Teacher{"刘晓东",30,"成都七中",5000.0};
9 per->showMessage();
10 per2->showMessage();
11 per->setMessage("王晓玲", 40);
12 per2->setMessage("刘翔情", 35);
13 per->showMessage();
14 per2->showMessage();
15 return 0;
16 }
出口结果:
1 调用了Person对象的showMessage()方法,name:王晓红, age:24
2 调用了Teacher对象的showMessage()方法,name:刘晓东, age:30, school:成都七中, salary:5000
3 调用了Person对象的setMessage()方法
4 调用了Person对象的setMessage()方法
5 调用了Person对象的showMessage()方法,name:王晓玲, age:40
6 调用了Teacher对象的showMessage()方法,name:刘翔情, age:35, school:成都七中, salary:5000
说明:
首先,在下边包车型地铁代码中,在基类Person和Teacher类注脚中注解showMessage()方法时都应用了C++关键字virtual,这么些主意措施叫做虚方法。从出口结果中得以看来,尽管在main.cpp函数中Person对象和Teacher对象都以用Person指针指向的,可是在调用showMessage()方法的时候,都调用了目的分其他主意,即延续类Teacher对象未有调用基类的showMessage()方法。
其次,在基类Person和Teacher类申明中宣示setMessage()方法的时候从不选用首要字virtual。从出口结果能够看到,用Person指针指向的Person对象和Teacher对象在调用setMessage()方法的时候,都以调用的基类Person类的setMessage()方法。Teacher类就算重载了setMessage()方法,不过在用指向Teacher对象的基类Person指针或引用调用该方法的时候并从未调用Teacher对象自作者的setMessage()方法。
有上述能够得出以下结论:
借使艺术是通过引用或指针而不是指标调用的,它将规定使用哪1种艺术。借使未有动用重要字virtual,程序将依据引用或指针类型选用格局;如若利用了virtual,程序将基于引用或指针指向的对象类型来采用格局。
由此,大家须求在基类少校派生类会重新定义的主意评释为虚方法。方法在基类中被声称为虚的后,它在派生类元帅自动变成虚方法。不过,在派生类注明中应用主要字virtual来提出什么函数是虚函数也不失为叁个好法子。
此外,基类声惠氏(Karicare)个虚析构函数,能够确定保证释放派生对象的时候,按正确的顺序调用析构函数。
注意,关键字virtual只用于类证明的秘诀原型中,而不能够用于方法定义中。
非构造函数无法使用成员起首化列表语法,可是派生类方法能够调用公有的基类方法。
在重定义派生类继承方法的代码中调用基类中被接续的同名方法时,假诺不选择效益域解析运算符很有十分大可能率带来不供给的忙绿,将会创立2个最为递归函数,为幸免那种张冠李戴必须对基类被三番六回的同名方法应用效果域解析运算符。例如下面包车型大巴代码将会创制2个极端递归函数:
void Teacher::showMessage()const{
…..
showMessage();//那样将会创设1个最棒递归函数,
因为该函数的默许调用对象是协调笔者,即该语句与this->showMessage();等效
…..
}
然则下边包车型客车不会出错:
void Teacher::showMessage() const{
….
Person::showMessage();//那将调用基类的showMessage()方法,在此间并不汇合世其余不当。
…..
}
虚析构函数
在上头代码中,在基类Person申明中,我们利用了虚析构函数,即virtual
~Person();那样做的说辞在于:
要是析构函数不是虚的,则将调用对应于指针或引用类型的析构函数;即使析构函数是虚的,将调用相应对象类型的析构函数。因而,使用虚析构函数能够保障正确的析构函数系列被调用。
四、静态联编和动态联编
静态联编:在编写翻译进程中展开联编,又称作早期联编;
动态联编:编写翻译器在程序运维时生成选择正确虚方法的代码,称为动态联编,又称作晚期联编。
一、指针和引用类型的包容性
在C++中,动态联编与通过指针和引用调用方法有关,从某种爱丁堡上说,那是由持续控制的。公有继承担建设立的is-a关系的1种方法是什么处理指向对象的指针和引用。日常,C++不容许将1种类型的地址赋给另1种档次的指针,也分裂意将1系列型的引用指向另1系列型。
指向基类的引用或指针能够引用派生类对象,而不要进行显式类型转换。
将派生类引用或指针转换为基类引用或指针被喻为进步强制转换,那使国有继承不供给展开显式类型转换。该规则是is-a关系的一局地。向上强制转换是足以传递的,即A是B的基类,B是C的基类,则A引用或指针能够引用A对象、B对象和C对象。
将基类指针或引用转换为派生类指针或引用称为向下强制转换。要是不应用显式类型转换,则向下强制类型转换是分歧意的。原因是is-a关系是不可逆的。派生类可以激增多少成员,因而利用那些多少成员的类成员函数不可能采纳于基类。
对于使用基类引用或指针作为参数的函数调用,将开始展览发展转换。隐式向上强制转换使基类指针或引用可以本着基类对象或派生类对象,因而需求动态联编。
二、虚成员函数和动态联编
编写翻译器对非虚方法使用静态联编,对虚方法使用动态联编。
编写翻译器将静态联编设为暗中同意联编方案,原因如下:
(壹)静态联编功用更高。仅当在程序设计时确实供给虚函数时,才使用它们。提示:要是要在派生类中重定义基类的点子,则将它设置为虚方法;不然,设置为非虚方法。
(二)使用虚方法时,在内部存储器和进行进程方面将有一定的财力,包罗:
*各样对象都将叠加,增多量为存款和储蓄地方的上空;
*对于每一个类,编写翻译器都将创制二个虚函数地址表(数组);
*对于每一种函数调用,都急需实践一项附加的操作,即到表中搜寻地址。
叁、有关虚函数的注意事项
*在基类方法的宣示中使用重要字virtual能够使该办法在基类以及全数的派生类(包括从派生类派生出来的类)中是虚的;
*就算应用指向对象的指针或引用来调用虚方法,程序将动用为对象类型定义的方法,而不接纳为引用或指针类型定义的法子。那称之为动态联编或中期联编。那类别型非凡关键,因为如此基类指针或引用能够本着派生类对象。
*如果定义的类将被视作基类,则应将那四个要在派生类中再一次定义的类格局注脚为虚的。
对于虚方法,还索要了然上面的知识:
(一)构造函数
构造函数不能够是虚函数。成立派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将选拔基类的构造函数,那种顺序不一样于继承机制。因而,派生类不继续基类的构造函数。
(贰)析构函数
析构函数相应是虚函数,除非类不用做基类。固然基类不要求显式析构函数提供劳动,也不应信赖于默许的析构函数,而应提供虚析构函数,就算它不做此外操作。因而,常常应该给基类提供3个虚析构函数,即使它并不必要析构函数。
(3)友元
友元无法是虚函数,因为友元不是类成员,而唯有成员函数才能是虚函数。如若是因为那些缘故引起了规划难点,能够透过让友元函数使用虚成员函数来解决。
(四)未有重新定义
要是派生类未有再度定义函数,将应用该函数的基类版本。假若派生类位于派生链中,则将利用新型的虚函数版本,例外的情事是基类版本是藏匿的。
(伍)重新定义将潜伏方法
假使创制了如下的代码:
class Dwelling{
public:
virtual void showperks(int a)const;
….
};
class Hovel:public Dewlling{
public:
virtual void showperks()const;
…
};
那将促成难题,恐怕会产出类似于上面这样的警告:
Warning :Hovel::showperks(void) hides Dewlling::showperks(ing)
也恐怕不出现警示。但无论如何,代码将富有如下含义:
Hovel trump;
trump.showperks();//允许
turmp.showperks(5);//不允许
新定义将showperks()定义为三个不收受任何参数的函数。重新定义不会生成函数的五个重载版本,而是隐藏了接受一个int参数的基类版本。总而言之,重新定义继承的办法并不是重载。倘若重复定义派生类中的函数,将不只是使用相同的函数参数列表覆盖基类注明,无论参数列表是或不是一致,该操作将躲藏全部的同名基类方法。
那里引出了两条经验规则:
第一、假设重新定义继承的主意,应确认保障与原先的原型完全相同,但假使回去类型是基类引用或指针,则足以修改为指向派生类的引用或指针(那种差别是新出现的)。那种特征被称作重返类型协变,因为允许重临类型随类类型的更动而变更:
class Dwelling{
public:
virtual Dewlling& build(int a);
….
};
class Hovel:public Dewlling{
public:
virtual Hovel& build(int a);
…
};
注意,那种分歧只适用于再次回到值,而不适用于参数。
第二、借使基类注明被重载了,则应在派生类中再度定义全数的基类版本。
五、访问控制:protected
关键字protected与private类似,在类外只可以用公有类成员函数来访问protected部分中的类成员。private与protected之间的界别唯有在基类派生的类中才会议及展览现出来。派生类的分子能够直接待上访问基类的维护成员,但不可能平素访问基类的私家成员。因而,对于外部来说,保护成员的作为与私家成员类似;但对此派生类来说,珍视成员的行事与国有成员类似。
警告:最棒对类数据成员选择私有访问控制,不要采纳爱戴访问控制;同时通过基类方法使派生类能够访问基类数据。
对于成员函数来说,珍惜访问控制很有用,它让派生类能够访问公众不可能一向运用的里边函数。
6、抽象基类
抽象基类(abstract base class, ABC)
C++通过使用纯虚函数来提供未兑现的函数。纯虚函数申明的结尾处为=0.
当类申明中隐含纯虚函数时,则不可能创制该类的目的。那里的定义是,包涵纯虚函数的类只用作基类。要改成真正的ABC,必须至少含有七个纯虚函数。纯虚函数可以有函数定义,也得以未有函数定义。
ABC理念
设计ABC在此之前,首先应支付三个模型——提议编制程序难点所需的类以及她们之间的相互关系。一种高校派思想认为,假若要设计类继承层次,则不得不将那几个不会被当作基类的类设计为切实的类。
能够将ABC看作是一种无法不实行的接口。ABC必要具体派生类覆盖其纯虚函数——迫使派生类服从ABC设置的接口规则。那种模型在依照组件的编制程序方式中很普遍,在那种状态下,使用ABC使得组件设计职员能够制定“接口约定”,那样保险了从ABC派生的有着组件都至少帮助ABC钦点的成效。
7、继承和动态内部存款和储蓄器分配
壹般的话,在设计类的时候,大家会根据类是还是不是采取了动态内部存款和储蓄器分配来设想是还是不是供给提供显式析构函数、复制构造函数和赋值运算符,对于派生类同样须要思量这么些元素。壹般在筹划派生类的时候会有刹那间二种意况:
一、派生类不使用new
(1)析构函数
派生类的暗许析构函数延续要实践下边包车型客车操作:执行本人的代码后调用基类的析构函数。因而,对于从未接纳动态内部存款和储蓄器分配的派生类来说,暗中同意析构函数是适合的。
(二)复制构造函数
暗中同意复制构造函数执行成员复制,成员复制将根据类成员类型应用相应的复制方式;并且在复制类成员和持续的类组件时,则是行使该类的复制构造函数实现的。由此,对于未有动用动态内存分配的派生类来说,默许复制构造函数是适合的。
(三)赋值运算符
类的默许赋值运算符将自动使用基类的赋值运算符来对基类组件举行赋值,由此对于尚未动用动态内部存款和储蓄器分配的派生类来说,暗许的赋值运算符是适当的。
二、派生类使用new
下边包车型大巴座谈都以基于A是B的基类,并且A和B使用了动态内部存储器分配。
(一)析构函数
派生类的析构函数自动调用基类的析构函数,故其自笔者的职务是对派生类构造函数执行工作的拓展清理。
(2)复制构造函数
派生类B的复制构造函数只好自个儿的多少,由此它必须调用基类A的复制构造函数来拍卖共享的基类数据,派生类的复制构造函数的主导情势如下:
B::B(const B & b):A(b){
//复制基类本身的数码
….
}
要求留意的一点是,成员初叶化列表将一个派生类B的引用传给基类A的复制构造函数,那里运用了提高强制类型转换(基类引用或指针能够针对派生类对象),那样基类A的复制构造函数将接纳派生类B引用对象参数共享的基类数据部分来协会新对象的共享基类数据部分。
(三)赋值运算符
派生类的复制运算符应服从上边包车型大巴大旨格式:
B & B::operator=(const B & b){
if (this == & b)
return *this;
A::operator=(b);
//…..
return *this;
}
在派生类的赋值运算符中,必须选用作用域解析运算符显式调用基类的赋值运算符,不然将会促成极端递归。同时,给基类赋值运算符提供参数的时候只须要提供派生类对象引用即可,这里会自动使用向上强制类型转换,那样基类赋值运算符就只会动用派生类共享的基类数据部分来展开赋值操作。
同理可得,当基类和派生类都选用动态内部存款和储蓄器分配时,派生类的析构函数、复制构造函数和赋值运算符都必须接纳相应的基类方法来处理基类元素。那种须求是因此二种区别方法来满意的。对于析构函数,那是自动网毕节。对于构造函数,那是经过在伊始化成员列表中调用基类的复制构造函数来形成的,假如不这么做,将机关调用基类的私下认可构造函数。对于赋值运算符,那是通过运用效能域解析运算符显式地调用基类的赋值运算符来完毕的。
三、使用动态内部存款和储蓄器分配和友元的接续示例
由于友元不是成员函数,所以不能够选择功效域解析运算符来提议要动用哪个函数。那么些题指标消除方法是行使强制类型转换,以便匹配原型时亦可挑选正确的函数。在此地,要是类A是类B的基类,operator<<(ostream
&, const A
&)为基类A的<<重载函数原型,那么派生类B的<<运算符重载函数应使用下边包车型大巴定义:
ostream & operator<<(ostream & os, const B & b){
os << (const A
&)b;//必须显式使用向上强制类型转换,那样将会调用基类A的友元<<运算符重载函数;不然将会导致极端递归
//……
return os;
}
8、类设计回看
一、编译器生成的积极分子函数
(壹)暗中同意构造函数
默许构造函数大概未有参数,要么全体的参数都有暗许值。假诺未有定义任何构造函数,编译器将概念默许构造函数。
自动生成的暗中同意构造函数的一项功效是,调用基类的暗许构造函数以及调用自个儿是指标的积极分子所属类的暗中认可构造函数。
别的,假使派生类的构造函数的成员开首化列表中从未显式调用基类构造函数,则编写翻译器将利用基类的暗中同意构造函数来组织派生类对象的基类部分。在这种状态下,若是基类没有过构造函数,将造成编译阶段错误。
假诺定义了某种构造函数,编写翻译器将不会定义暗许构造函数。在那种景况下,即使急需暗中同意构造函数,则必须协调提供。
提供构造函数的念头之一是保障目的总能被正确地初阶化。别的,假设类富含指针成员,则必须早先化这几个分子。由此,最好提供多个显式默许构造函数,将全数的类数据成员都初步化为客体的值。
(二)复制构造函数
复制构造函数接受其所属类的指标作为参数。
在下述情况下将运用复制构造函数:
*将指标开端化为另二个同类对象;
*按值将对象传递给函数;
*函数按值重临对象;
*编写翻译器生成暂且对象。
假若程序未有动用(显式或隐式)复制构造函数,编写翻译器将提供原型,但不提供函数定义;不然,程序将定义一个实施成员初步化的复制构造函数。也正是说,新指标的每一种成员都被开始化为本来对象相应成员的值。假使成员为类对象,则开头化该成员时,将选用相应类的复制构造函数。
在少数情状下,成员开头化是不适当。例如,使用new起首化的积极分子指针平常须求深度复制,也许类只怕包涵须要修改的静态变量。在上述意况下,必要定义本身的复制构造函数。
(三)赋值运算符
暗中认可的赋值运算符用于处理同类对象时期的赋值。不要将赋值和开始化混淆了。假设语句创立新的对象,则用开端化;即便语句修改已有指标的值,则是赋值。
默许赋值为成员赋值。假如成员为类对象,则暗中同意赋值运算符将使用相应类的赋值运算符。倘诺急需显式定义复制构造函数,则基于相同的来头,也急需显式定义赋值运算符。
编写翻译器不会生成将1种档次赋给另壹连串型的赋值运算符。
二、其余的类措施
(①)构造函数
构造函数不相同于别的类措施,因为它创设新的靶子,而其他类情势只是被现有的对象调用。那是构造函数不被接续的缘故之一。继承意味着派生类对象足以接纳基类的办法,不过,构造函数在成功其工作在此以前,对象并不设有。
(二)析构函数
一定要定义显式析构函数来刑释类构造函数使用new分配的持有内部存款和储蓄器,并达成类对象所需的其他相当的清理工科作。对于基类,纵然它不供给析构函数,也应提供3个虚析构函数。
(3)转换
使用一个参数就足以调用的构造函数定义了从参数类型到类类型的更换。
将可转换的体系传递给以类为参数的函数时,将调用转换构造函数。
在带二个参数的构造函数原型中使用explicit将禁止开始展览隐式转换,但仍允许显式转换。
要将类对象转换为其他种类,应定义转换函数。转换函数能够是未曾参数的类成员函数,也能够是回去类型被声称为对象项指标类成员函数。固然没有评释重返类型,函数也应重临所需的转换值。
不过,对于1些类,包罗转换函数将扩展代码的二义性。能够将重大字explicitshiyong一于转换函数,那样将禁止隐式转换,但仍允许显式转换。
(肆)按值传递对象与传递引用
平时,编写使用对象作为参数的函数时,应按引用而不是按值来传递对象。那样做的由来之一是为了升高功用。按值传递对象关联到变化一时半刻拷贝,即调用复制构造函数,然后调用析构函数。调用那么些函数须要时间,复制大型对象比传递引用费用的时间多得多。要是函数不改动对象,应将参数申明为const引用。
按引用传递传递对象的别的贰个缘故是,在此起彼伏使用虚函数时,被定义为接受基类引用参数的函数尚可派生类。
(伍)重回对象和重临引用
有个别类措施重临对象。有些成员函数直接回到对象,而另一对回到引用。有时方法必须重回对象,但万一得以不回去对象,则应再次来到引用。来具体看一下:
首先,在编码方面,直接再次来到对象与重回引用之间唯壹的分别在于函数原型和函数头:
Star noval1(const Star &);//返回Star对象
Star noval2(const Star &);//返回Star引用
其次,应再次回到引用而不是回去对象的由来在于,再次回到对象关联生成再次来到对象的一时半刻副本,这是调用函数的次序能够使用的副本。由此,再次回到对象的时间开销包涵调用复制构造函数来变化副本所需的时刻和调用析构函数删除副本所需的时日。再次来到引用能够节省时间和内部存款和储蓄器。直接重临对象与按值传递对象一般:它们都生成近年来副本。同样,重返引用与按引用传递对象1般:调用和被调用的函数对同2个对象开始展览操作。
不过,并不总是能够回来引用。函数不可能回来在函数中开创的一时对象的引用,因为当函数甘休时,近期对象将消失,因而那种引用是违法的。在那种情状下,应重回对象,以生成叁个调用程序能够利用的副本。
通用的规则是,假设函数再次来到在函数中开创的暂且对象,则不用采纳引用。
假若函数重临的是透过引用或指针传递给她的靶子,则应按引用重回对象。
(6)使用const
使用const时应尤其注意。可以用它来确认保障艺术不修改参数:
Star:: Star(const char * s){…..}
使用const能够来保障艺术不改动调用它的指标:
void Star::show()const{….}//这里const表示const Star*
this,而this指向调用的对象。
平时,能够将回到引用的函数放在赋值语句的左手,那其实意味着能够将值赋给引用的靶子。但能够利用const确认保障引用或指针的值不能够用来修改对象中的数据:
const Stock & Stock::topval(const Stock & s)const{
if(s.total_val > total_val)
return s;
else
return *this;
}
该措施重回对this或s的引用。因为this和s被声称为const,所以函数不可能对它们进行修改,这意味着再次来到的引用也亟须被声称为const。
注意,假使函数将参数注脚为指向const的引用或指针,则不能够将该参数字传送递给另多个函数,除非后者也准保了参数不会被涂改。
三、公有继承的设想因素
日常,在先后中央银行使持续时,有诸多难题亟待小心。上边来看里面包车型大巴一些题材。
(1)is-a关系
要遵守is-a关系。如若派生类不是1种特别的基类,则毫不接纳国有派生。
在好几意况下,最棒的主意或然是开创包含纯虚函数的架空数据类,并从它派生出别样的类。
代表is-a关系的格局之一是,无需实行显式类型转换,基类指针就能够针对派生类对象,基类引用能够引用派生类对象。此外,反过来是无用的,即无法在不实行显式类型转换的境况下,将派生类指针或引用指向基类对象。那种显式类型转换(向下强制转换)大概有含义,也说不定未有,那取决于类评释。
(2)什么无法被持续
构造函数是无法被一连的,也正是说,创立派生类时,必须调用派生类的构造函数。然则,派生类构造函数经常选取成员初阶化列表语法来调用基类构造函数,以创设派生类对象的基类部分。要是派生类构造函数未有使用成员起先化列表语法显式调用基类构造函数,将采纳基类的默许构造函数。在继续链中,每一个类都得以利用成员初步化列表将音讯传递给隔壁的基类。C++1一新增了1种能够持续构造函数的体制,但默许仍不继续构造函数。
析构函数也是不能够一而再的。不过,在放出对象时,程序将第1调用派生类的析构函数,然后调用基类的析构函数。假若基类有私下认可析构函数,编写翻译器将为派生类生成默许析构函数。日常,对于基类,其析构函数应安装为虚的。
赋值运算符是无法被持续的,原因很简短。派生类继承的窍门的特征标与基类完全相同,但赋值运算符的表征标随类而异,那是因为它蕴涵五个项目为其所属类的形参。
(3)赋值运算符
假诺编写翻译器发现先后将3个对象赋给同三个类的另三个指标,它将机关为那几个类提供叁个赋值运算符。那几个运算符的暗许或隐式版本将运用成员赋值,即将原对象的对应成员赋给指标对象的各类成员。然则,借使目的属于派生类,编写翻译器将使用基类赋值运算符来处理派生类对象中基类部分的赋值。就算显式地为基类提供了赋值运算符,将应用该运算符。于此类似,如果成员是另1个类的对象,则对此该成员,将采纳其所属类的复制运算符。
正如反复关系,假若类构造函数使用new来开始化指针,则供给提供三个显式赋值运算符。因为对此派生类对象的基类部分,C++将运用基类的赋值运算符,所以不要求为派生类重新定义赋值运算符,除非它添加了索要尤其在意的数量成员。
可是,就算派生类使用了new,则必须提供显式复制运算符。必须给类的各样成员提供赋值运算符,而不仅仅是新成员。
其余,将派生类对象赋给基类对象,将调用基类赋值运算符,基类赋值运算符的参数为三个基类引用,它能够针对派生类对象。只是,赋值运算符只处理基类成员,而忽略派生类新增添的分子(就算派生类新扩展了成员)。综上可得,能够将派生类对象赋给基类对象,但那只涉嫌基类的成员。
相反,假设把基类对象赋给派生类对象,除非派生类有将基类对象转换为其连串的更换构造函数(可以承受三个体系为基类的参数和别的参数,条件是别的参数有私下认可值);不然,将会造成错误(派生类引用无法自动引用基类对象)。
同理可得,难题“是不是能够将基类对象赋给派生类对象?”的答案是“大概”。假使派生类包罗了那般的构造函数,即对将基类对象转换为派生类对象进行了概念,则足以将基类对象赋给派生类对象。即使派生类定义了将基类对象赋给派生类对象的赋值运算符,则也可以那样做。要是上述七个原则都不满意,则不能够这么做,除非动用显式强制类型转换。
(④)私有成员与珍重成员
对派生类而言,爱惜成员类似于国有成员;但对于外部而言,爱惜成员与私家成员类似。派生类能够一向访问基类的保险成员,但只可以通过基类的分子函数来拜会基类的个人成员。由此,将基类成员设置为民用成员能够增加安全性,而将她们设置为维护成员则足以简化代码的编辑撰写工作,并提升访问速度。
(5)虚方法
设计基类时,必须分明是或不是将类方式注明为虚的。借使期待派生类能够再次定义方法,则应在基类中校方法定义为虚的,那样可以启用晚期联编(动态联编);假若不希望再一次定义方法,则无需将其宣称为虚的,那样即使不能够禁止别人重新定义方法,不过却发表了这么的意趣:不期待它被重复定义。
注意,不适当的代码将阻碍动态联编。例如,请看上面包车型地铁三个函数:
void show(const Brass &rba){
rba.ViewAcct();
cout << endl;
}
void inadequate(Brass ba){
ba.ViewAcct();
cout << endl;
}
第3个函数按引用传递对象,第3个按值传递对象。
以后要是派生类参数传递给上述三个函数:
BrassPlus buzz(….);
show(buzz);
inadequate(buzz);
show()函数调用使rba成为BrassPlus对象buzz的引用,由此,rba.ViewAcct()被诠释为BrassPlus版本,正如应该的那么。但在inadequate()函数中(它是按值传递参数的),ba是Brass(const
Brass
&)构造函数创设的三个指标(自动进化强制转换使得构造函数能够引用3个BrassPlus对象)。因而,在indaquate()中,ba.ViewAcct()是Brass版本,所以唯有buss的Brass部分被突显。
(六)析构函数
正如前方介绍的,基类的析构函数应当是虚的。那样,当通过指向对象的基类指针或引用来删除派生类对象时,程序将第二调用派生类的析构函数,然后调用基类的析构函数,而不只是调用基类的析构函数。
(柒)友元函数
由于友元函数并非类成员,因而不能够持续。但是,大家兴许希望派生类的友元函数能够选拔基类的友元函数。为此,能够通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后接纳转换后的指针或引用来调用基类的友元函数。
(八)有关使用基类方法的辨证
以国有格局派生的类的对象能够通过三种办法来利用基类的方法。
*派生类对象活动使用持续而来的基类方法,尽管派生类未有重新定义该办法;
*派生类的构造函数自动调用基类的构造函数;
*派生类的构造函数自动调用基类的私下认可构造函数,假设未有在成员起初化列表中钦赐其余构造函数;
*派生类构造函数字显示式地调用成员开首化列表中内定的基类构造函数
*派生类方法能够利用成效域解析运算符来调用公有的和受保证的基类方法;
*派生类的友元函数能够透过强制类型转换,将派生类引用或指针转换来基类引用或指针,然后采取该引用或指针调用基类的友元函数。
四、类函数小结
C++类函数有很多两样的变体,在那之中有个别能够继续,有些不得以。有个别运算符函数既能够是成员函数,也足以是友元,而有点运算符函数只可以是成员函数。下边包车型大巴表计算了那一个特点,在那之中op=表示诸如+=、*=等格式的赋值运算符。注意,op=运算符的特色与“别的运算符”体系并不曾差距。单独列出op=意在提议那些运算符与=运算符的一颦一笑不一致。
函数 | 能够继承 | 成员还是友元 | 默认能否生成 | 能否为虚函数 | 是否可以有返回类型 |
构造函数 | 否 | 成员 | 能 | 否 | 否 |
析构函数 | 否 | 成员 | 能 | 能 | 否 |
= | 否 | 成员 | 能 | 能 | 能 |
& | 能 | 任意 | 能 | 能 | 能 |
转换函数 | 能 | 成员 | 否 | 能 | 否 |
() | 能 | 成员 | 否 | 能 | 能 |
[] | 能 | 成员 | 否 | 能 | 能 |
-> | 能 | 成员 | 否 | 能 | 能 |
op= | 能 | 任意 | 否 | 能 | 能 |
new | 能 | 静态成员 | 否 | 否 | void* |
delete | 能 | 静态成员 | 否 | 否 | void |
其他运算符 | 能 | 任意 | 否 | 能 | 能 |
其他成员 | 能 | 成员 | 否 | 能 | 能 |
友元 | 否 | 友元 | 否 | 否 | 能 |