8.1クラスのデストラクタ
概念8.1.1デストラクタ
- デストラクタ:その範囲を超えて、不要またはオブジェクトの破壊のため。
- オブジェクトがスコープ外になると、プログラムが自動的にデストラクタを呼び出します。オブジェクトは、オブジェクトのデータメンバがメモリを占有解放する必要が破壊します。(静的メンバは、オブジェクトが存在する以外にクラスが存在しない場合でも存在し続けます)。
- クラスの名前とデストラクタメンバ関数は、クラス名のみを追加する必要が(- ):
デストラクタCBOXプロトタイプクラスを:CBOX〜を(); - 何も値が返されないクラスデストラクタは、何も定義されたパラメータはありません。クラスはデストラクタを持つことができます。
8.1.2デフォルトのデストラクタ
- コンパイラは常に自動的にデフォルトのデストラクタを生成し、独自のクラスのデストラクタを定義していません。
- デフォルトのデストラクタnew演算子は、アイドルやメモリオブジェクトメンバに割り当てられたオブジェクトを削除することはできません。
- クラスのメンバーの占有スペースを動的にコンストラクタに割り当てられている場合、明示的に動的にコンストラクタに割り当てられたメモリを解放するには、delete演算子を使用し、デストラクタをカスタマイズする必要があります。
#include <iostream>
using std::cout;
using std::endl;
class CBox
{
public:
~CBox()
{
cout << "Destructor called." << endl;
}
explicit CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
m_Length(lv),m_Width(wv),m_Height(hv)
{
cout << endl << "Constructor called.";
}
double volume() const
{
return m_Length*m_Width*m_Height;
}
bool compare(CBox* pBox) const
{
return this->volume() > pBox->volume();
}
private:
double m_Length;
double m_Width;
double m_Height;
};
int main()
{
CBox boxes[5];
CBox cigar(8.0,5.0,1.0);
CBox match(2.2,1.1,0.5);
CBox* pB1(&cigar);
CBox* pB2(0);
cout << endl
<< "Volume of cigar is "
<< pB1->volume();
pB2 = boxes;
boxes[2] = match;
cout << endl
<< "Volume of boxes[2] is "
<< (pB2+2)->volume();
cout << endl;
return 0;
}
明示的な呼び出しデストラクタは、クラスオブジェクトがスコープの外に出たとき、コンパイラが自動的にクラスのデストラクタを呼び出す手配ません。本実施形態では、呼び出しデストラクタが実行される主な機能の終了後に発生します。
8.1.3デストラクタと動的なメモリ割り当て
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
class CMessage
{
private:
char* pmessage; //数据成员:指向文本串的指针
public:
void ShowIt() const
{
cout << endl << pmessage;
}
CMessage(const char* text = "Default message") //构造函数
{
// strlen(),获取字符串实参的长度(不包括终止空字符)
// 通过将strlen()函数返回值+1,构造函数即可求出在空
// 闲存储器中存储该字符串所需的内存字节数
pmessage = new char[strlen(text)+1];
strcpy_s(pmessage,strlen(text)+1,text);
//第3个指针参数指定的字符串复制到第1个指针实参包含的地址中
}
~CMessage(); //析构函数
};
CMessage::~CMessage()
{
cout<< "Destructor called."
<< endl;
delete[] pmessage;
}
int main()
{
CMessage motto("A miss is as good as a mile.");
CMessage* pM(new CMessage("A cat can look at a queen."));
// 定义了一个指向CMessage对象的指针,并使用new操作符为指针指向的CMessage对象分配内存
motto.ShowIt();
pM->ShowIt();
cout << endl;
delete pM; // 删除空闲存储器中创建的对象
return 0;
}
- コンパイラは、オブジェクトが削除するために削除を使用する必要があり、空きメモリに作成されたオブジェクトを削除するための責任を負いません。また、オブジェクトによって占有メモリのメンバーを解放するデストラクタを呼び出します。
- モットーオブジェクトのデータメンバは、コンストラクタ内の空きメモリ、デストラクタを呼び出し、コンパイラによって占有メモリに割り当てられます。
8.2コピーコンストラクタを実装します
CMessage motto1("Radiation fades your genes.");
CMessage motto2(motto1);
デフォルトのコピーコンストラクタ実装レプリケーション・プロセスは、新しいオブジェクトに値保存された元のオブジェクトのデータメンバをコピーすることですので、デフォルトのコンストラクタ関数はクラスオブジェクトmotto1のmotto2のポインタメンバーに格納されているアドレスをコピーすることです。したがって、2つのオブジェクトが一つだけのテキスト文字列を共有します。図は次のとおりです。
在任何一个对象中对字符串进行的修改,也会修改另一个对象。如果销毁motto1 ,则motto2 中的指针将指向已经被释放、现在可能用于其他对象的内存区域,因此会发生混乱。
提供一个类复制构造函数来代替默认版本:
CMessage(const CMessage& aMess)
{
size_t len =strlen(aMess.pmessage)+1; //分配足够容纳aMess 对象中字符串的内存
pmessage = new char[len];
strcpy_s(pmessage,len,aMess.pmessage);
}
//新对象与旧对象相同,但与就对象完全无关
- 为了避免对复制构造函数的无穷调用,必须将形参指定为const引用。
CMessage thought("Eye awl weighs yews my spell checker.");
DisplayMessage(thought);
// DisplayMessage 函数定义如下:
void DisplayMessage (CMessage localMsg)
{
cout << endl << "The message is: "<< localMsg.ShowIt();
return;
}
形参是一个CMessage对象,因此调用过程中实参是通过传值方式传递的,使用默认的复制构造函数,将发生以下事件:
(1)创建thought对象,在空闲存储器中为消息"Eye awl weighs yews my spell checker." 分配空间。
(2)调用DisplayMessage()函数。因为实参是通过传值方式传递的,所以默认构造函数创建实参的副本localMsg。现在,副本中的指针指向空闲存储器中原来的对象所指向的字符串。
(3)DisplayMessage()函数结束时,局部对象localMsg 超出作用域,因此程序调用CMessage类的析构函数,通过释放pmessage指针指向的内存,删除这个局部对象(副本)。
(4)从DisplayMessage()函数返回时,原来的对象thought 包含的指针仍然指向刚释放的内存区域,下次在使用原来的对象时,程序将出现异常。
如果动态的为本地C++类的成员分配空间,则必须实现复制构造函数。
8.3 在变量之间共享内存
在C++中有一个功能允许多个变量共享相同的内存,称之为联合。使用联合的基本方法:
- 联合的用途是希望以2种或多种不同的方式解释相同数据的时候经常需要的。当有一个long类型的变量,却希望将其当作2个short类型的数值对待时,就可能产生上述需求。Windows有时将2个short值包装在单个long类型的形参中传递给函数。如果我们希望将某块包含数值数据的内存当作字节的字符串对待,以便能够四处移动这块内存,在这种情况下可以使用联合。
- 当预先不知道某个对象或数据值的类型时,可以使用联合来传递该对象或数据值。联合可以存储任何在允许范围内的类型。
8.3.1 定义联合
- 使用union 定义联合。
union shareLD
{
double dval;
long lval;
};
// 定义了一个联合类型shareLD,允许long 和double 类型的变量占用相同的内存。
联合类型名 通常称作标记名。
- 定义联合实例:
// 可以在声明中定义联合实例:
shareLD myUnion;
// 可以将myUnion 包括在联合的定义语句中来定义实列:
union shareLD
{
double dval;
long lval;
} myUnion ;
- 引用联合成员
//使用直接成员选择操作符(.) 与联合实例的名称。
myUnion.lval = 100;
// 使用类似的语句初始化double 类型的变量dval 将改写lval
联合可以使多个变量共享相同的内存。联合占用的内存大小取决于最大的成员。
union shareDLF //定义联合标记名
{
double dval;
long lval;
float fval;
} uinst = {1.5} ; //定义联合实例,并将实例初始化为值1.5
shareDLF 的实例将占用8个字节:
注意,当声明一个实例时,只能初始化联合的第一个成员。
8.3.2 匿名联合
可以定义没有联合类型名的联合,这种情况下程序将自动声明该联合的一个实列。
union
{
char* pval;
double dval;
long lval;
};
// 该语句定义了一个匿名联合,又定义了该联合的一个实例
// 使用double成员:
dval = 99.5;
8.4 运算符重载
- 运算符重载功能允许编写重新定义特定运算符的函数,从而使该运算符处理类对象时执行特定的动作。
- 运算符重载功能不允许使用新的运算符,也不允许改变运算符的优先级。因此运算符的重载版本在计算表达式的值时优先级与原来的基本运算符相同。
- 不能重载的运算符如下:
8.4.1 实现重载运算符
- operator 关键字,该关键字结合运算符符号或名称(本例 >)将定义一个运算符函数。
class CBox
{
public:
bool operator > (CBox& aBox) const;
//定义运算符函数operator>()
//函数声明为const,因为该函数不修改本类的数据成员
//运算符的右操作数由函数形参定义,左操作数由this指针隐式定义
// this 指针指向调用类成员函数时使用的对象
};
if(box1 > box2)
cout << endl <<"box1 is greater than box2";
// if(box1 > box2) 等价函数调用:
box1.operator>(box2);
表达式中CBox对象与运算符函数形参之间的对应关系
bool CBox::operator > (const CBox& aBox) const
{
return this->Volume() > aBox.Volume();
}
//operator>() 函数的工作原理:
//该函数使用引用形参,以避免调用时不必要的复制开销。
//因为该函数不需要修改调用它的对象,所以可将其声明为const
//如果不这样做,则不能使用该运算符比较CBox类型的const对象
//return表达式使用成员函数Volume()计算this指向的CBox对象的体积,然后使用基本运算符>
//比较结果与对象aBox的体积。基本运算符 > 返回int类型的数值 (而非bool),因此如果
//指针this指向的CBox对象比作为引用实参传递的对象aBox的体积大,则返回1,否则返回0.
//比较结果将自动转换为该运算符函数的返回类型bool。
#include <iostream>
using std::cout;
using std::endl;
class CBox
{
public:
//explicit:阻止不应该允许的经过转换构造函数进行的隐式转换的发生
explicit CBox(double lv=1.0,double wv=1.0,double hv=1.0):
m_Length(lv),m_Width(wv),m_Height(hv)
{
cout << endl << "Constructor called.";
}
double Volume() const
{
return m_Length*m_Width*m_Height;
}
bool operator > (const CBox& aBox) const; //声明运算符函数
~CBox() // 定义析构函数
{
cout << "Destructor called."<< endl;
}
private:
double m_Length;
double m_Width;
double m_Height;
};
// 运算符函数定义
bool CBox::operator> (const CBox& aBox) const
{
return this->Volume() > aBox.Volume();
}
int main()
{
CBox smallBox(4.0,2.0,1.0);
CBox mediumBox(10.0,4.0,2.0);
CBox bigBox(30.0,20.0,40.0);
if(mediumBox > smallBox)
cout << endl << "mediumBox is bigger than smallBox";
if(mediumBox > bigBox)
cout << endl << "mediumBox is bigger than bigBox";
else cout << endl << "mediumBox is not bigger than bigBox";
cout << endl;
return 0;
}
8.4.2 实现对比较运算符的完全支持
- 比较CBox对象与是指表达式
bool operator>(const double& value) const;
bool CBox::operator>(const double& value) const
{
return this->Volume() > value;
}
- 实现 if(20.0 > aBox)
将左操作数属于double类型的重载,运算符实现为普通函数
bool operator>(const double& value,const double& value) const;
bool operator>(const double& value,const double& value) const
{
return value > aBox.Volume();
}
普通函数只能访问公有成员,如果CBox类没有公有函数,可以直接将>运算符函数声明为能够访问私有数据成员的友元函数,或者提供一组返回私有数据成员数值的成员函数,然后在普通函数中使用这些函数来实现比较功能。
//完成> 运算符的重载
#include <iostream>
using std::cout;
using std::endl;
class CBox
{
public:
//explicit:阻止不应该允许的经过转换构造函数进行的隐式转换的发生
explicit CBox(double lv=1.0,double wv=1.0,double hv=1.0):
m_Length(lv),m_Width(wv),m_Height(hv)
{
cout << endl << "Constructor called.";
}
double Volume() const
{
return m_Length*m_Width*m_Height;
}
bool operator > (const CBox& aBox) const //定义运算符函数
{
return this->Volume() > aBox.Volume();
}
bool operator > (const double& value) const //定义运算符函数
{
return this->Volume() > value;
}
~CBox() // 定义析构函数
{
cout << "Destructor called."<< endl;
}
private:
double m_Length;
double m_Width;
double m_Height;
};
bool operator> (const double& value,const CBox& aBox);
int main()
{
CBox smallBox(4.0,2.0,1.0);
CBox mediumBox(10.0,4.0,2.0);
if(mediumBox > smallBox)
cout << endl << "mediumBox is bigger than smallBox";
if(mediumBox > 50.0)
cout << endl << "mediumBox capacity is more than 50.0";
else
cout << endl << "mediumBox capacity is not more than 50.0";
if(10.0 > smallBox)
cout << endl << "smallBox capacity is less than 10.0";
else
cout << endl << "smallBox is not less than 10.0";
cout << endl;
return 0;
}
bool operator> (const double& value,const CBox& aBox)
{
return value > aBox.Volume();
}
8.4.3 重载赋值运算符
//将文本复制到目标对象所拥有的内存区域
//赋值运算符函数定义在类定义内部:
CMessage& operator= (const CMessage& aMess) //赋值运算符函数 返回的是引用
{
delete[] pmessage;
pmessage = new char[strlen(aMess.pmessage)+ 1];
strcpy_s(this->pmessage,strlen(aMess.pmessage)+1,aMess.pmessage);
return *this;
}
-
赋值运算函数中返回的是引用,原因:
有时可能需要在表达式的右边使用赋值操作的结果
motto1 = motto2 = motto3;
//赋值运算具有右结合性,首先指定motto3 赋给motto2
motto1 = (motto2.operator= (motto3));
// 此处运算符函数调用的结果在等号的右边,该语句最终结果:
motto1.operator=(motto2.operator= (motto3));
要使这条语句工作,必须有返回对象。括号内对operator()函数的调用必须返回一个对象作为另一个operator 函数调用的实参。可以是CMessage 或CMessage&。
(motto1 = motto2) = motto3;
(motto1.operator=(motto2)).operator=(motto3);
如果返回类型仅仅是CMessage,则该语句不合法。因为实际返回的是原始对象的副本,编译器不允许使用临时对象调用成员函数。故唯一可能的返回类型是CMessage&。
- 两个对象都已经拥有为字符串分配的内存,因此赋值运算符函数首先要删除分配给第1个对象的内存,然后重新分配足够的内存,以容纳属于第2个对象的字符串。做完这件事后,就可以将来自第2个对象的字符串复制到第1个对象现在拥有的新内存中。
当出现下列情况:
motto1 = motto1,
目前的赋值运算符函数将释放供motto1 使用的内存,然后基于已经被删除的字符串的长度另外分配一些内存,并试图复制当时很可能已经被破坏的旧内存。
通过在函数的开头检查左右操作数是否相同,可以修改上述问题。
CMessage& operator= (const CMessage& aMess)
{
if(this == &aMess) // 判断左右操作数是否相同
return *this;
delete[] pmessage;
pmessage = new char[strlen(aMess.pmessage)+ 1];
strcpy_s(this->pmessage,strlen(aMess.pmessage)+1,aMess.pmessage);
return *this;
}
// 重载赋值运算符
// 类中添加一个Reset()成员函数,该函数作用是 将消息重新设置为星号字符串
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
class CMessage
{
private:
char* pmessage;
public:
void ShowIt() const
{
cout << endl << pmessage;
}
void Reset()
{
char* temp = pmessage;
while(*temp)
*(temp++) = '*';
}
CMessage& operator=(const CMessage& aMess) //定义赋值运算符函数
{
if(this == &aMess)
return *this;
delete[] pmessage;
pmessage = new char[strlen(aMess.pmessage) + 1];
strcpy_s(this->pmessage,strlen(aMess.pmessage) + 1,aMess.pmessage);
return *this;
}
CMessage(const char*text = "Default message") //构造函数
{
pmessage = new char[strlen(text)+1];
strcpy_s(pmessage,strlen(text)+1,text);
}
CMessage(const CMessage& aMess) //复制构造函数
{
size_t len = strlen(aMess.pmessage)+1;
pmessage = new char [len];
strcpy_s(pmessage,len,aMess.pmessage);
}
~CMessage() //析构函数
{
cout << "Destructor called."
<< endl;
delete[] pmessage;
}
};
int main()
{
CMessage motto1("The devil takes care of his own");
CMessage motto2;
cout << "motto2 contains - ";
motto2.ShowIt();
cout << endl;
motto2 = motto1;
cout << "motto2 contains - ";
motto2.ShowIt();
cout << endl;
motto1.Reset();
cout << "motto1 now contains - ";
motto1.ShowIt();
cout << endl;
cout << "motto2 still contains - ";
motto2.ShowIt();
cout << endl;
return 0;
}
如果需要给类的数据成员动态分配空间,则必须实现赋值运算符。
8.4.4 重载加法运算符
- 定义2个CBox对象的和:它是个CBox对象,其体积足够容纳这2个摞在一起的箱子。
- CBox类的加法运算符版本:
CBox operator+(const CBox& aBox) const;
CBox CBox::operator+(const CBox& aBox) const
{
return CBox(m_Length > aBox.m_Length ? m_Length:aBox.m_Length,
m_Width > aBox.m_Width ? m_Width:aBox.m_Width,
m_Height + aBox.m_Height);
}
根据当前对象(*this)和传递的实参对象aBox,构造出一个局部的CBox对象。返回过程将创建局部对象的临时副本,并将此副本返回给调用程序(而非从函数返回时将被抛弃的局部对象)。
// 使用 重载的加法运算符
#include <iostream>
using std::cout;
using std::endl;
class CBox
{
public:
// 构造函数
explicit CBox(double lv=1.0,double wv=1.0,double hv=1.0):
m_Height(hv)
{
m_Length = lv > wv ? lv : wv;
m_Width = wv < lv ? wv : lv; //确保长度 >= 宽度
}
double Volume() const
{
return m_Length*m_Width*m_Height;
}
bool operator > (const CBox& aBox)const //>运算符函数
{
return this->Volume() > aBox.Volume();
}
bool operator > (const double& value) const
{
return Volume() > value;
}
CBox operator+(const CBox& aBox) const //+运算符函数
{
return CBox(m_Length > aBox.m_Length ? m_Length:aBox.m_Length,
m_Width > aBox.m_Width ? m_Width:aBox.m_Width,
m_Height + aBox.m_Height);
}
void ShowBox() const
{
cout << m_Length << " " << m_Width << " " << m_Height << endl;
}
private:
double m_Length;
double m_Width;
double m_Height;
};
bool operator > (const double& value,const CBox& aBox);
int main()
{
CBox smallBox(4.0,2.0,1.0);
CBox mediumBox(10.0,4.0,2.0);
CBox aBox;
CBox bBox;
aBox = smallBox + mediumBox;
cout << "aBox dimensions are ";
aBox.ShowBox();
bBox = aBox + smallBox + mediumBox;
cout << "bBox dimensions are ";
bBox.ShowBox();
return 0;
}
bool operator > (const double& value,const CBox& aBox)
{
return value > aBox.Volume();
}
8.4.5 重载递增和递减运算符
class Length
{
private:
double len; //将长度存储为double类型的值
public:
Length& operator++(); // 前缀形式 ,没有形参
const Length operator++(int); //后缀形式,有一个int形参
Length& operator--();
const Length operator--(int);
};
-
区分重载运算符前缀和后缀形式的首要方法是利用形参列表。前缀形式没有形参后缀形式有一个int类型的形参。后缀运算符函数的形参只是为了将其同前缀形式区别开来,除此之外它在函数实现中没有任何用处。
-
前缀形式的递增或递减运算符 在表达式中使用操作数的值之前将其递增或递减,在递增或递减当前对象之后,只需返回该对象的引用即可。
Length& Length::operator++()
{
++(this->len);
return *this;
}
- 后缀形式中,操作数是在表达式中使用其当前值之后递增的。要实现这一点需要在递增当前对象之前创建当前对象的副本,并在修改过当前对象之后返回新创建的副本对象。
const Length Length::operator++(int)
{
Length length = *this;
++ *this;
return length;
}
- 将返回值声明为const ,以防止编译类似 data++++这样的表达式。
8.4.6 重载函数调用操作符
- 函数调用操作符是() , 此操作符的函数重载是 operator () ()。
- 重载函数调用操作符的类对象 称为函数对象 或 仿函数。
class Area
{
public:
int operator()(int length, int width)
{
return length*width;
}
};
Area area;
int pitchLength(100),pitchWidth(50);
int pitchArea = area(pitchLength,pitchWidth);
8.6 类模板
- 通过指定模板中 < >内的形参类型(T)来确定希望生成的类。以这种方式生成的类称作类模板的实例,根据模板创建类的过程称为实例化模板。
- 类模板本身不是类,而只是编译器用来生成类代码的一种方法。
8.6.1 定义类模板
// 定义几个可以存储大量数据样本的类,每个类都应当提供一个求出所存储样本中最大值的Max()函数
template <class T>
// 指出正在定义的是模板而非简单的类
// 形参T是类型变量,将在声明类对象时由具体类型代替
class CSamples
{
public:
CSamples(const T value[],int count) //存储样本的数组的类型指定为T
{
m_Free = count < 100 ? Cout : 100;
for(int i = 0; i<m_Free ; i++)
{
m_Values[i] = values[i];
}
}
CSamples(const T& value)
{
m_Values[0] = value;
m_Free = 1;
}
CSamples(){m_Free = 0};
bool Add(const T& value)
{
bool OK = m_Free < 100;
if(OK)
m_Values[m_Free++] = value;
return OK;
}
T Max() const
{
T theMax = m_Free? m_Values[0]:0;
for(int i = 1;i < m_Free; i++)
{
if(m_Values[i] > theMax)
theMax = m_Values[i];
return theMax;
}
}
private:
T m_Values[100];
int m_Free;
};
模板成员函数:
将类模板成员函数的定义放在模板定义的外部
template <class T>
class CSamples
{
// Rest of the template definition
T Max() const; //声明成员函数
// Rest of the template definition
};
// 创建时必须使用模板类的名称加上< >内的形参,以标识函数模板所属的类模板
Template <class T>
T CSamples<T> ::Max() const
{
T theMax = m_Free? m_Values[0]:0;
for(int i = 1;i < m_Free; i++)
{
if(m_Values[i] > theMax)
theMax = m_Values[i];
return theMax;
}
}
8.6.2 根据类模板创建对象
声明一个CSamples< > 对象来处理double类型的样本:
CSamples< double > myData(10.0);
该语句定义了一个CSamples< double > 类型的对象,它可以存储double 类型的样本,该对象是用值为10.0的已个存储的样本创建的。
#include <iostream>
using std::cout;
using std::endl;
class CBox // Class definition at global scope
{
public:
// Constructor definition
CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0): m_Height(hv)
{
m_Length = lv > wv ? lv : wv; // Ensure that
m_Width = wv < lv ? wv : lv; // length >= width
}
// Function to calculate the volume of a box
double Volume() const
{
return m_Length*m_Width*m_Height;
}
// Operator function for 'greater than' which
// compares volumes of CBox objects.
bool operator>(const CBox& aBox) const
{
return this->Volume() > aBox.Volume();
}
// Function to compare a CBox object with a constant
bool operator>(const double& value) const
{
return Volume() > value;
}
// Function to add two CBox objects
CBox operator+(const CBox& aBox) const
{
// New object has larger length & width, and sum of heights
return CBox(m_Length > aBox.m_Length ? m_Length : aBox.m_Length,
m_Width > aBox.m_Width ? m_Width : aBox.m_Width,
m_Height + aBox.m_Height);
}
// Function to show the dimensions of a box
void ShowBox() const
{
cout << m_Length << " " << m_Width << " " << m_Height << endl;
}
private:
double m_Length; // Length of a box in inches
double m_Width; // Width of a box in inches
double m_Height; // Height of a box in inches
};
template <class T> // 形参T是类型变量,将在声明类对象时由具体类型代替
class CSamples
{
public:
CSamples(const T value[],int count); //存储样本的数组的类型指定为T
CSamples(const T& value);
CSamples(){m_Free = 0};
bool Add(const T& value);
T Max() const;
private:
T m_Values[100];
int m_Free;
};
template<class T> CSamples<T>::CSamples(const T value[],int count)
{
m_Free = count < 100 ? count : 100;
for(int i = 0; i<m_Free ; i++)
{
m_Values[i] = value[i];
}
}
template<class T> CSamples<T>::CSamples(const T& value)
{
m_Values[0] = value;
m_Free = 1;
}
template<class T> bool CSamples<T>::Add(const T& value)
{
bool OK = m_Free < 100;
if(OK)
m_Values[m_Free++] = value;
return OK;
}
template<class T> T CSamples<T>::Max() const
{
T theMax = m_Free ? m_Values[0]:0;
for(int i = 1;i < m_Free; i++)
{
if(m_Values[i] > theMax)
theMax = m_Values[i];
return theMax;
}
}
int main()
{
CBox boxes[]={
CBox(8.0,5.0,2.0),
CBox(5.0,4.0,6.0),
CBox(4.0,3.0,3.0),
};
CSamples<CBox> myBoxes(boxes,sizeof boxes/sizeof CBox);
CBox maxBox = myBoxes.Max();
cout << endl
<< "The biggest box has a volume of "
<< maxBox.Volume() << endl;
return 0;
}
注意,当创建类模板的实例时,不能理解成用于创建函数成员的那些函数模板的实例也被创建。编译器只创建程序中实际调用的那些成员函数的模板实例。
8.6.3 使用有多个形参的类模板
定义一个使用2个类型形参的类模板:
template<class T1,class T2>
class CExampleClass
{
// Class data members
private:
T1 m_Value1;
T2 m_Value2;
//Rest of the template definition
}
8.6.4 函数对象模板
定义函数对象的类一般是由模板来定义的。
template<class T> class Area
{
public:
T operator()(T Length, T width){ return length*width;}
}
此模板允许定义函数对象来计算任何数值类型尺寸的面积。
template<class T> void printArea(T length,T width,Area<T> area)
{cout << "Area is " << area(length,width);}
// 调用printArea()函数
printArea(1.5,2.5,Area<double>());
printArea(100,50,Area<int>());
8.9 字符串的本地C++库类
string标准头文件中定义了表示字符串的string 和wstring 类。在string头文件中将这2个类定义为类模板basic_string 的实例:
- string类定义为:basic_string ,表示char类型的字符串
- wstring类定义为:basic_string<wchar_t>,表示wchar_t类型的字符串
8.9.1 创建字符串对象
1.创建并初始化字符串对象
string sentence = “This sentence is false.”;
sentence对象将用赋值运算符右边的字符串字面值来初始化。
-
可以在任何时候通过调用字符串对象的length()函数来查看字符串对象所疯转的字符串的长度。
cout<< "The string is of length " << sentence.length() << endl;
-
可以用与任何其他变量相同的方式将字符串对象输出到stdout:
cout << sentence << endl;
-
将字符串读到字符串对象中:
cin << sentence;
以这种方式从stdin 中读取字符串时会忽略开头的空格,直到发现非空格的字符。
当在一个或多个非空格字符后面输入一个空格时,这种方式也会终止输入。 -
getline()函数模板专门用来将数据从流中读入到string或wstring对象中。
getline(cin,sentence,’*’);
第1个参数:作为输入源的流(不一定是cin);
第2个参数:接收输入的对象;
第3个参数:终止读取的字符;
2.用函数表示法来初始化string对象:
string sentence(“This sentence is false.”);
如果在创建string对象时没有指定初始字符串字面值,该对象就会包含一个空字符串:
string astring;
3.用一个字符(重复指定的次数)来初始化字符串对象:
string bees(7,‘b’);
第1个实参:是第2个实参指定的字符的重复次数。
4.用另一个字符串对象的全部或一部分来初始化字符串对象。
string letters(bees);
这里将用bees中包含的字符串来初始化letters对象。
string sentence = “This sentence is false.”;
string part(sentence,5,11);
第1个实参:作为初始化字符串来源的字符串对象;
第2个实参:要选择的第1个字符的索引位置;
第3个实参:要选择的字符个数;
8.9.2 连接字符串
- 可以用 + 运算符连接2个字符串对象或一个字符串对象和一个字符字面值。
combined = sentence1 + sentence2;
字符串可以用+ 运算符连接,是因为string类实现了operator+(),这意味着操作数之一必须为string对象,因此不能用 + 运算符连接2个字符串字面值。
string类也可以实现operator+=(),因此右操作数可以是一个字符串字面值、一个字符串对象或者一个字符。
sentence1 += ‘\n’;
- 运算符+ 与 += 运算符的区别:
+运算符创建一个包含合并后的字符串的新字符串对象。
+= 运算符将作为右操作数的字符串或字符附加到作为左操作数的string对象后面,直接修改string对象,而不创建新的对象。
8.9.3 访问与修改字符串
1.使用下标操作符 [] 来访问string对象中的任何字符从而读取或重写它:
string sentence("Too many cooks spoil the broth.");
for(size_t i=0;i < sentence.length(); i++)
{
if(' '== sentence[i])
sentence[i] = '*';
}
2.用at()成员函数与用[]操作符得到的结果相同:
string sentence("Too many cooks spoil the broth.");
for(size_t i=0;i < sentence.length(); i++)
{
if(' '== sentence.at(i))
sentence.at(i) = '*';
}
- 使用[]与at()的区别:
利用下标值的速度比使用at()函数快,但缺点是没有检查索引的有效性。如果索引值超出了范围,那么使用下标操作符的结果将不确定。at()函数稍微慢一点,但它会检查索引的有效性,如果索引无效,该函数就会抛出一个out_of_range异常。
3.提取现有string对象的一部分作为一个新的string对象:
string sentence(“Too many cooks spoil the broth.”);
string substring = sentence.substr(4,10);
第1个实参:要提取的子字符串的第1个字符;
第2个实参:子字符串中的字符个数;
4.使用字符串对象的append()函数,可以在字符串末尾添加一个或多个字符。该函数有几个版本:包括向调用该函数的对象附加1个或多个给定字符、一个字符串字面值或者一个string对象。
string phrase("The higher");
string word("fewer");
phrase.append(1,' ');
phrase.append("the ");
phrase.append(word);
phrase.append(2,'!');
输出:phrase 将修改为"The higher the fewer!!".
- 带2个参数的append()版本:第1个实参:将被附加第2个实参指定的字符的次数,当调用append()函数时,它返回对调用该函数的对象的引用。上述示例可写成:
phrase.append(1,' ').append("the ").append(word).append(2,'!');
- 也可以用append()将字符串字面值的一部分或string对象的一部分附加到一个现有字符串后面:
string phrase("The more the merrier.");
string query("Any");
query.append(phrase,3,5).append(1,'?');
结果:Any more?
第1个实参:从中提取字符并附加到query后面的string对象。
第2个实参:要提取的第1个字符的索引位置。
第3个实参:要附加的字符总数。
- 当想向一个字符串对象后面附加一个字符时,可以使用
push_back()函数作为append()的替换函数。
query.push_back(’*’);
向query字符串的末尾附加一个 *字符。
5.insert()函数 , 在字符串中插入字符;返回对调用该函数的string对象的一个引用。
6.可以通过调用swap()成员函数来交换封装在2个string对象之间的字符串。
string phrase("The more the merrier.");
string query("Any");
query.swap(phrase);
query中的结果包含字符串"The more the merrier." phrase中包含"Any"。
7.将一个字符串对象转换为以空字符结尾的字符串,可以用c_str()函数:
string phrase("The more the merrier.");
const char *pstring = phrase.c_str();
c_str()函数返回一个指向以空字符结尾的字符串的指针,该字符串的内容与string对象相同。
8.通过调用data()成员函数来获得一个字符串对象的内容。注意,该数组仅包含字符串对象中的字符,不包括结尾的空字符。
9.replace()成员函数 替换字符串对象的一部分
8.9.4 比较字符串
当比较2个字符串时,实际上是比较对应的字符,直到发现一堆不同的字符,或者到达一个或两个字符串的末尾。当发现两个对应字符不相同时,字符代码的值决定比较结果。如果没有发现不同的字符对,那么字符较少的字符串小于另一个字符串。如果两个字符串包含相同的字符个数,而且对应的字符也相同,则这两个字符串相等。
//比较字符串
#include <iostream>
#include <iomanip>
#include <string>
using std::cout;
using std::cin;
using std::endl;
using std::ios;
using std::setiosflags;
using std::setw;
using std::string;
//strings:字符串数组的地址
//count:数组元素的个数
string* sort(string* strings,size_t count)
{
bool swapped(false);
while(true)
{
for(size_t i = 0;i < count - 1; i++)
{
if(strings[i] > strings[i+1])
{
swapped = true;
strings[i].swap(strings[i+1]); //调换2个元素
}
}
if(!swapped)
break;
swapped = false;
}
return strings;
}
int main()
{
const size_t maxstrings(100);
string strings[maxstrings];
size_t nstrings(0);
size_t maxwidth(0);
while(nstrings < maxstrings)
{
cout << "Enter a word or press Enter to end:";
getline(cin,strings[nstrings]);
if(maxwidth < strings[nstrings].length())
maxwidth = strings[nstrings].length(); //记录输入的最长字符串长度
//只需按下Enter键就会导致一个empty字符串
//当读取的最后一个string对象的empty()函数返回true时终止循环。
if(strings[nstrings].empty())
break;
++nstrings;
}
sort(strings,nstrings); //升序排列字符
cout << endl
<< "In ascending sequence,the words you enterd are:"
<< endl
<< setiosflags(ios::left); //字段中的各个单词保持左对齐
for(size_t i = 0;i < nstrings; i++)
{
if(i % 5 == 0)
cout << endl;
cout << setw(maxwidth+2) << strings[i];
}
cout << endl;
return 0;
}
8.9.5 搜索字符串
- find()函数 搜索string对象中给定的字符或字符串,所有的find()函数都定义为const。
find()函数的各个版本都返回发现的字符或子字符串的第一个字符的索引位置。如果没有找到要找的条目,该函数会返回值string::npos(string类中定义的一个常量,表示string对象中的一个非法位置它通常用来标识搜索失败).
- find_first_of() 和
find_last_of()成员函数在string对象中搜索出现的给定集合中的任何字符。表中的所有函数都定义为const,并返回size_t类型的值。对于这2个函数的所有版本,如果没有发现匹配的字符,就会返回string::npos.
- find_first_not_of() 和 find_last_not_of() 函数都定义为const,并返回size_t类型的值。
// 排序文本中的单词
#include <iostream>
#include <iomanip>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::ios;
using std::setiosflags;
using std::resetiosflags;
using std::setw;
using std::string;
string* sort(string* strings,size_t count)
{
bool swapped(false);
while(true)
{
for(size_t i = 0; i < count-1 ; i++)
{
if(strings[i] > strings[i+1])
{
swapped = true;
strings[i].swap(strings[i+1]);
}
}
if(!swapped)
break;
swapped = false;
}
return strings;
}
int main()
{
const size_t maxwords(100);
string words[maxwords];
string text;
string separators(" \".,:;!?()\n");
size_t nwords(0);
size_t maxwidth(0);
cout << "Enter some text on as many lines as you wish."
<< endl << "Terminate the input with an asterisk:"<< endl;
getline(cin,text,'*'); // 获取输入 终止符设定为*
size_t start(0),end(0),offset(0);
while(true)// while循环 从字符串对象text的输入中提取单个单词,并存储在words 中
{
start = text.find_first_not_of(separators,offset); //找到第一个字符的索引位置,函数返回位置offset中第1个不是separators中字符之一的字符的索引位置
if(string::npos == start)
break;
offset = start + 1;
//搜索任何分隔符字符
end = text.find_first_of(separators,offset); //从索引位置offset处搜索任何分隔符
if(string::npos == end)
{
offset = end;
end = text.length();
}
else
offset = end + 1;
words[nwords] = text.substr(start,end-start);// 在text中从start处的字符开始提取end-start个字符
if(maxwidth < words[nwords].length())
maxwidth = words[nwords].length();
if(string::npos == offset)
break;
if(++nwords == maxwords)
{
cout << endl << "Maximum number of words reached."
<< endl << "Processing what we have."<< endl;
break;
}
}
sort(words,nwords);
cout << endl
<< "In ascending sequence,the words in the text are:"
<< endl;
size_t count(0); // 记录重复的单词个数
for(size_t i = 0; i < nwords; i++)
{
if(0 == count)
count = 1;
if(i < nwords-2 && words[i] == words[i+1])
{
++count;
continue;
}
cout << setiosflags(ios::left)
<< setw(maxwidth+2) << words[i];
cout << resetiosflags(ios::right)
<< setw(5) << count << endl;
count = 0;
}
cout << endl;
return 0;
}
8.10 C++/CLI 编程
8.10.1 在值类中重载运算符
// Ex8_15.cpp: 主项目文件。
// 包含重载运算符的值类,Length类实现加法、乘法和除法的运算符重载
#include "stdafx.h"
using namespace System;
value class Length
{
private:
int feet;
int inches;
public:
static initonly int inchesPerFoot = 12; //类的静态和非静态函数成员可以直接使用它
Length(int ft,int ins):feet(ft),inches(ins){ }
virtual String^ ToString() override
{
return feet.ToString() + (1 == feet ? L" foot ": L" feet ")+
inches + (1 == inches ? L"inch":L"inches");
}
Length operator+(Length len)
{
int inchTotal = inches + len.inches + inchesPerFoot*(feet + len.feet);
return Length(inchTotal/inchesPerFoot,inchTotal%inchesPerFoot);
}
static Length operator/(Length len,double x)
{
int ins = safe_cast<int>((len.feet * inchesPerFoot + len.inches)/x);
return Length(ins/inchesPerFoot,ins%inchesPerFoot);
}
static Length operator* (double x,Length len);
static Length operator* (Length len,double x);
};
Length Length::operator * (double x,Length len)
{
int ins = safe_cast<int>(x*len.inches + x*len.feet*inchesPerFoot);
return Length(ins/inchesPerFoot,ins%inchesPerFoot);
}
Length Length::operator*(Length len,double x)
{
return operator*(x,len);
}
int main(array<System::String ^> ^args)
{
Length len1 = Length(6,9);
Length len2 = Length(7,8);
double factor(2.5);
Console::WriteLine(L"{0} plus {1} is {2}",len1,len2,len1+len2);
Console::WriteLine(L"{0} times {1} is {2}",factor,len2,factor*len2);
Console::WriteLine(L"{1} times {0} is {2}",factor,len2,len2*factor);
Console::WriteLine(L"The sum of {0} and {1} divided by {2} is {3}",len1,len2,factor,(len1+len2)/factor);
Console::ReadLine();
return 0;
}
8.10.2 重载递增和递减运算符
value class Length
{
public:
//code as before
static Length operator++(Length len)
{
++len.inches;
len.feet += len.inches/len.inchesPerFoot;
len.inches %= len.inchesPerFoot;
return len;
}
};
8.10.3 在引用类中重载运算符
在引用类中重载运算符 形参和返回值通常都是句柄。
// Ex8_17.cpp: 主项目文件。
#include "stdafx.h"
using namespace System;
ref class Length
{
private:
int feet;
int inches;
public:
static initonly int inchesPerFoot = 12;
Length(int ft,int ins):feet(ft),inches(ins){ }
virtual String^ ToString() override
{
return feet.ToString()+(1 == feet? L"foot":L"feet")+inches+
(1 == inches? L"inch":L"inches");
}
Length^ operator+(Length^ len)
{
int inchTotal = inches + len->inches + inchesPerFoot*(feet + len->feet);
return gcnew Length(inchTotal/inchesPerFoot,inchTotal%inchesPerFoot);
}
static Length^ operator/(Length^ len,double x)
{
int ins = safe_cast<int>((len->feet*inchesPerFoot + len->inches)/x);
return gcnew Length(ins/inchesPerFoot,ins%inchesPerFoot);
}
static int operator/(Length^ len1,Length^ len2)
{
return (len1->feet*inchesPerFoot + len1->inches)/
(len2->feet*inchesPerFoot + len2->inches);
}
static Length^ operator%(Length^ len1,Length^ len2)
{
int ins = (len1->feet*inchesPerFoot + len1->inches)%
(len2->feet*inchesPerFoot + len2->inches);
return gcnew Length(ins/inchesPerFoot,ins%inchesPerFoot);
}
static Length^ operator*(double x,Length^ len);
static Length^ operator*(Length^ len,double x);
static Length^ operator++(Length^ len)
{
Length^ temp = gcnew Length(len->feet,len->inches);
++temp->inches;
temp->feet += temp->inches/temp->inchesPerFoot;
temp->inches %= temp->inchesPerFoot;
return temp;
}
};
Length^ Length::operator*(double x,Length^ len)
{
int ins = safe_cast<int>(x*len->inches + x*len->feet*inchesPerFoot);
return gcnew Length(ins/inchesPerFoot,ins%inchesPerFoot);
}
Length^ Length::operator*(Length^ len,double x)
{
return operator*(x,len);
}
int main(array<System::String ^> ^args)
{
Length^ len1 = gcnew Length(2,6);
Length^ len2 = gcnew Length(3,5);
Length^ len3 = gcnew Length(14,6);
Length^ total = 12*(len1 + len2 + len3) + (len3/gcnew Length(1,7))*len2;
Console::WriteLine(total);
Console::WriteLine(L"{0} can be cut into {1} pieces {2} long with {3} left over.",
len3,len3/len1,len1,len3%len1);
Length^ len4 = gcnew Length(1,11);
Console::WriteLine(len4++);
Console::WriteLine(++len4);
Console::WriteLine(len4);
Console::ReadLine();
return 0;
}
8.10.4 实现引用类型的赋值运算符
Length% operator=(const Length% len)
{
if(this != %len)
{
feet = len.feet;
inches = len.inches;
}
return *this;
}
if语句检查实参与当前对象是否相同,如果相同,则函数仅返回*this,它将是当前对象。如果不同,就在返回当前对象之前将len 的每个数据成员复制到当前对象中。
8.13 本章主要内容
つづく…