处理第一个问题:
1)某书店以文件形式保存其每一笔交易。没一笔交易记录某本书的销售情况,含有ISBM、销售册数和销售单
价。每一笔交易形如:0-201-70352-X 4 24.99
-------------------------------------------------------------------
指针和const限定符
1)指向const对象的指针
const double *cptr
这里的cptr是一个指向double类型const对象的指针,const限定了cptr指针所指向的对象类型,而并非cptr本身。也就是说,cptr本身并不是const。在定义时不需要对它进行初始化。如果需要的话,允许给cptr重新赋值,使其指向另一个const对象。但不能通过cptr修改其所指对象的值。
不能给一个常量赋值,常量指针所指的对象都不能修改:
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
double dbValue = 4;
const double* cptr = &dbValue;
*cptr = 5;
return 0;
}
2)const指针
int iValue = 1;
int *const pNumber = &iValue;
此时,可以从右向左把这个定义语句读作"pNumber"是指向int型对象的const指针。与其他const变量一样,const指针的值也不能修改,这就意味着不能使pNumber指向其他对象。任何企图给const指针赋值的行为(即使pNumber赋回同样的值)都会导致编译错误。
pNumber = pNumber;
与任何const量一样,const指针也必须在定义的时候进行初始化。
指针本身是const的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int iValue = 1;
int *const pNumber = &iValue;
*pNumber = 2;
return 0;
}
3)指向const对象的const指针
还可以如下定义指向const对象的const指针:
const double pNumble = 3.14;
const double *const pi_ptr = π
这时,既不能修改pi_ptr所指向对象的值,也不允许修改该指针的指向(即pi_ptr中存放的地址值)。可从右向左阅读上述声明语句:pi_ptr首先是一个const指针,指向double类型的const对象。
4)理解复杂的const类型的声明
阅读const声明语句产生的部分问题,源于const限定符既可以放在类型前也可以放在类型后:
string const s1;
const string s2;
用typedef写const类型定义时,const限定符加载类型名前面容易引起对所定义的真正类型的误解:
string s;
typedef string* pString;
const pString cstr1 = &s;
pString const cstr2 = &s;
string* const cstr3 = &s;
把const放在类型pString之后,然后从右向左阅读该声明语句就会非常清楚地知道cstr2是const pString类型,即指向string对象的const指针
字符串
1)C语言风格的字符串:char* cp1;
此时一般用指针的算法操作来遍历C风格只付出,每次对指针进行测试并递增1,直到到达结束符null为止
2)C风格字符串的标准库函数
C的头文件:
C++的头文件:
3)操作C风格字符串的标准库函数
strlen(str);-----------------------返回s的长度,不包括字符串结束符null
strcmp(str1, str2);----------------比较两个字符串
strcat(str1, str2);----------------将字符串str2连接到str1后,并返回str1
strcpy(str1, str2);----------------将str2复制给str1,并返回str1
strncat(str1, str2, n);------------将str2的前n个字符连接到到str1的后面,并返回str1
strncpy(str1, str2, n);------------将str2的前n个字符复制给str1,并返回str1
4)尽可能使用标准库类型string
上面的C风格字符串,不小心的使用都无可避免的出现内存管理问题,如果使用C++的标准库类型string,则不存在上述问题,此时标准库负责处理所有的内存管理问题,不必再担心每一次修改字符串时涉及到的大小问题。
写入到文件当中
使用ofstream写入到文件中,就像使用cout类似
使用文件输出的步骤:
1)包含头文件fstream
2)创建ofstream对象outFile
3)outFile.open()
4)outFile << (就像使用cout那样)
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "fstream"
int _tmain(int argc, _TCHAR* argv[])
{
char automoblie[20];
int year;
double a_price;
double d_price;
ofstream outFile;
outFile.open("test.txt");
cout << "Enter the make and model of automobile:" << endl;
cin.getline( automoblie, 20 );
cout << "Enter the model year:" << endl;
cin >> year;
cout << "Enter the original asking price:" << endl;
cin >> a_price;
d_price = 0.96 * a_price;
cout << fixed; // floating point numbers using a general way the output
cout.precision( 2 ); // output decimal point behind 1 bits
cout.setf( ios_base::showpoint ); // compulsory output decimal point
cout << "make and model of automobile is:" << automoblie << endl;
cout << "model year is:" << year << endl;
cout << "was asking:" << a_price << endl;
cout << "now asking:" << d_price << endl;
outFile << fixed;
outFile.precision( 2 );
outFile.setf( ios_base::showpoint );
outFile << "make and model of automobile is:" << automoblie << endl;
outFile << "model year is:" << year << endl;
outFile << "was asking:" << a_price << endl;
outFile << "now asking:" << d_price << endl;
outFile.close();
return 0;
}
读取文件
文件输出和文件输入极其类似
使用文件输出的步骤:
1)包含头文件fstream
2)创建ifstream对象inFile
3)inFile.is_open()
4)getline( inFile, strOut );
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
#include "algorithm"
#include "fstream"
#include "vector"
#define FILENAMELENGTH 20
void Print( const string str)
{
cout << str << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector vecStr;
char strFileName[ FILENAMELENGTH ];
string strOut;
cout << "Enter the data file:" << endl;
cin >> strFileName;
ifstream inFile;
inFile.open( strFileName );
if ( !inFile.is_open() )
{
cout << "could not open the datafile" << strFileName << endl;
cout << "Program terinating.\n";
exit( EXIT_FAILURE );
}
while ( inFile.good() )
{
getline( inFile, strOut );
vecStr.push_back( strOut );
}
for_each( vecStr.begin(), vecStr.end(), Print );
inFile.close();
return 0;
}
如果试图打开一个不存在的文件,这将导致后面使用ifstream对象进行输入时失败。检查文件是否被成功打开的首先方法是使用方法is_open(),为此,可以用类似的代码:
inFile.open( strFileName );
if ( !inFile.is_open() )
{
cout << "could not open the datafile" << strFileName << endl;
cout << "Program terinating.\n";
exit( EXIT_FAILURE );
}
如果文件被成功地打开,方法is_open()将返回true;因此如果文件没有被打开,表达式!inFile.is_open()将为true。函数exit()的原型在头文件cstdlib中定义,在该头文件中,还定义了一个用于同操作系统通信的参数值EXIT_FAILURE。函数exit()终止程序。
使用good()方法,该方法在没有发生任何错误时返回true
while ( inFile.good() )
{
getline( inFile, strOut );
vecStr.push_back( strOut );
}
如果愿意,可以使用其它方法来确定循环种终止的真正原因:
数组作为函数参数
当数组作为函数参数时,将数组的地址作为参数可以节省复制整个数组所需的时机和内存
验证一下,函数中数组的地址与外层调用时,函数的地址是一样的,并且在函数中
#include "stdafx.h"
#include "iostream"
using namespace std;
int sumArray( const int arr[], int nValue )
{
int sum = 0;
int nArrsize = 0;
cout << "In sumArray function arr[] address:" << arr << endl;
nArrsize = sizeof arr;
cout << "In sumArray function sizeof arr is:" << nArrsize << endl;
for ( int i = 0; i < nValue; i++ )
{
sum += arr[i];
}
return sum;
}
/*
int sumArray( const int* arr, int nValue )
{
int sum = 0;
int nArrsize = 0;
cout << "In sumArray function arr[] address:" << arr << endl;
nArrsize = sizeof arr;
cout << "In sumArray function sizeof arr is:" << nArrsize << endl;
for ( int i = 0; i < nValue; i++ )
{
sum += arr[i];
}
return sum;
}*/
int _tmain(int argc, _TCHAR* argv[])
{
int nArrsize = 0;
int arr[ 5 ] = { 1, 2, 3, 4, 5 };
cout << "In _tmain function arr[] address:" << arr << endl;
nArrsize = sizeof arr;
cout << "In _tmain function sizeof arr is:" << nArrsize << endl;
cout << "sum is:" << sumArray( arr, 5 ) << endl;
return 0;
}
指针和const
将const用于指针有一些很微妙的地方,可以用两种不同的方式将const关键字用于指针。
1)
第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
const int iValue = 10;
int* pIValue = &iValue;
*pIValue = 11;
return 0;
}
2)
第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置。
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int iValue = 10;
const int* pIValue = &iValue;
*pIValue = 11;
return 0;
}
3)有这种情况:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int iAge = 39;
int* pIAge = &iAge;
const int* pIValue = pIAge;
*pIValue = 10;
return 0;
}
这是情况变得很复杂。假如涉及的是一级间接关系,则将非const指针赋给const指针时可以的。
不过,进入两级间接关系时,与一级间接关系一样将const和非const混合的指针赋值方式将不再安全。如果允许这样做,则可以编写这样的代码:
const int** ppIValue;
int* pIValue;
const int n = 13;
ppIValue = &pIValue;
*ppIValue = &n;
*pIValue = 10;
上述代码将非const地址(&pIValue)赋给了const指针(ppIValue),因此可以使用pIValue来修改const数据。所以,仅当只有一层间接关系(如指针指向基本数据类型)时,才可以将非const地址或指针赋给const指针。
4)
int IValue = 10;
const int* pIValue = &IValue;
这时能够防止pIValue修改IValue的值,但是却不能防止修改pIValue所指向的值。执行
int IAge = 39;
pIValue = &IAge;
是可行的
如果修改成
int* const pIValue = &IValue;
此时便不能修改pIValue所指向的值了。
在这个声明中,关键字const的位置与以前不同。这种声明格式使得pIValue只能指向IValue,但允许使用pIValue来修改IValue的值。
5)指向常量的常指针
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int IValue = 10;
const int* const pIValue = &IValue;
// *pIValue = 11;
int IAge = 39;
pIValue = &IAge;
return 0;
}
这时既不能修改IValue的值,也不能修改pIValue的指向
函数和二维数组
二维数组在函数参数的形式
1)既可以是int sumArray( int arr[][ 4 ], int nSize )
由于指针类型指定了列数,因此sum()函数只能接受由4列组成的数组。
原文中202页31行“但长度变量指定了行数,因此sum()对数组的行数没有限制”这话说得有点让人无法理解。其实应该是这样的“行数需要由长度变量指定,因此sum()的数组参数对于行数没有限制”。
2)或者是int sumArray( int (*arr)[ 4 ], int nSize )
其中(*arr)中的括号是必不可少的,因为声明int *arr[ 4 ]将声明一个有4个指向int的指针组成的数组,而不是一个指向由4个int组成的数组的指针。另外函数参数不能是数组。
3)二维函数的相关元素
arr2[ r ][ c ] = *( *( ar2 + r ) + c);
arr2 // 指向二维数组的首地址
arr2 + r // 指向二维数组偏移r行的首地址
*(arr2 + r) // 相当于arr2[ r ]
*(arr2 + r) + c // 相当于arr2[ r ] + c
*( *(arr2 + r) + c ) // 相当于arr2[ r ][ c ]
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int arr2[ 2 ][ 4 ] = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 }
};
cout << arr2[1][2] << endl;
cout << arr2 << endl;
int r = 1;
cout << arr2 + r << endl;
int c = 2;
cout << *( arr2 + r ) << endl;
cout << *( arr2 + r ) + c << endl;
cout << *( ( arr2 + r) + c ) << endl;
cout << *(*( arr2 + r ) + c ) << endl;
return 0;
}
递归
1)
仅包含一个递归的调用
#include "stdafx.h"
#include "iostream"
using namespace std;
// recutsion with iValue
int sum( int iValue )
{
if ( 0 == iValue )
{
return 1;
}
else
{
return sum( iValue - 1 ) * iValue;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << " 5 recursion:" << sum( 5 ) << endl;
return 0;
}
此代码起警示作用:
#include "stdafx.h"
#include "iostream"
using namespace std;
// recutsion with iValue
int sum( int iValue )
{
if ( 0 == iValue )
{
return 1;
}
else
{
return sum( iValue-- ) * iValue;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << " 5 recursion:" << sum( 5 ) << endl;
return 0;
}
2)
包含多个递归调用的递归,在需要将一项工作不断分为两项较小的、类似的工作时,递归非常有用。
#include "stdafx.h"
#include "iostream"
using namespace std;
const int Len = 66;
const int Divs = 6;
void subdivide( char arr[], int low, int high, int level )
{
if ( 0 == level )
{
return;
}
int mid = ( low + high ) / 2;
arr[ mid ] = '|';
subdivide( arr, low, mid, level - 1);
subdivide( arr, mid, high, level - 1);
}
int _tmain(int argc, _TCHAR* argv[])
{
char ruler[ Len ] = { 0 };
int i;
// initialize
for ( i = 1; i <= Len - 2; i++ )
{
ruler[ i ] = ' ';
}
ruler[ Len - 1 ] = '\0'; // present end
int iMax = Len - 2; // 64min length is Len - 2
int iMin = 0; // set min length is 0
ruler[ iMin ] = ruler[ iMax ] = '|'; // min and max pos now is '|'
cout << ruler << endl; // output none but have min and max
// cout 6 row
for ( i = 1; i <= Divs; i++ )
{
subdivide( ruler, iMin, iMax, i); // transfer i
cout << ruler << endl;
// resume array is NULL
for ( int j = i; j < Len - 2; j++ )
{
ruler[ j ] = ' ';
}
}
return 0;
}
在subdivide()函数,使用便利level来控制递归层。subdivide()函数调用自己两次,一次针对左半部分,另一次针对右半部分。也就是说,调用一次导致两个调用,然后导致4个调用,在导致8个调用,以此类推。这就是6层调用能够填充64个元素的原因pow( 2, 6 )=64。这将不断导致函数调用数(以及存储的变量数)翻倍,因此如果要求的递归层次很多,这种递归方式将是一种糟糕的选择;然而,如果递归层次较少,这将是一种精致而简单的选择。
函数指针
历史与逻辑
为何pf和(*pf)等家呢?一种学派认为,由于pf是函数指针,而*pf是函数,因此应将(*pf)()用作函数调用;另一种学派认为,由于函数名师指向该函数的指针,指向函数的指针的行为应与函数名相似,因此应将pf()用作函数调用使用。C++进行了折中----这两种方式都是正确的,或者至少是允许的,虽然它们在逻辑上是相互冲突的。在认为折中折中粗糙之前,应该想远类思维活动的特点。
函数指针示例:
1)使用typedef
#include "stdafx.h"
#include "iostream"
using namespace std;
double betsy( int );
double pam( int );
typedef double (*estimate)( int );
int _tmain(int argc, _TCHAR* argv[])
{
int iValue = 5;
estimate estimateFun;
estimateFun = betsy;
cout << "transfer betsy:" << estimateFun( iValue ) << endl;
estimateFun = pam;
cout << "transfer pam:" << estimateFun( iValue ) << endl;
return 0;
}
double betsy( int iValue )
{
return ( iValue * iValue );
}
double pam( int iValue )
{
return ( iValue * 0.89 );
}
2)直接使用
#include "stdafx.h"
#include "iostream"
using namespace std;
double betsy( int );
double pam( int );
double estimateFun( int iValue, double ( *pf )(int) );
int _tmain(int argc, _TCHAR* argv[])
{
int iValue = 5;
cout << "transfer betsy:" << estimateFun( iValue, betsy) << endl;
cout << "transfer pam:" << estimateFun( iValue,pam ) << endl;
return 0;
}
double betsy( int iValue )
{
return ( iValue * iValue );
}
double pam( int iValue )
{
return ( iValue * 0.89 );
}
double estimateFun( int iValue, double ( *pf )(int) )
{
return ( *pf )( iValue );
}
C++函数
1)内联函数
常规函数:在执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置以为着使用函数时,需要一定的开销。
C++内联函数提供了另一种选择。内联汗水的编译代码与其他程序代码"内联"起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无须跳到另一个位置跳到另一个位置处执行代码,然后再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数的10个代码拷贝。
#include "stdafx.h"
#include "iostream"
using namespace std;
inline int sum( int iLeftValue, int iRightValue )
{
return ( iLeftValue + iRightValue );
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = 10;
int b = 14;
cout << sum( a , b ) << endl;
return 0;
}
2)引用作为函数的参数
引用作为某个变量的别名而存在
他与指针的区别在于,声明的时候就必须进行初始化
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int a = 14;
int b = 10;
int& ra = a;
cout << "current a value is:" << a << endl;
cout << "current reference ra value is:" << ra << endl;
ra++;
cout << "current a value is:" << a << endl;
cout << "current reference ra value is:" << ra << endl;
ra = b;
cout << "current b value is:" << b << endl;
cout << "current reference ra value is:" << ra << endl;
return 0;
}
将引用作为函数的参数,以达到不进行按值传递的目的
#include "stdafx.h"
#include "iostream"
using namespace std;
void sum( int& a )
{
a++;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = 14;
cout << "current a value is:" << a << endl;
sum( a );
cout << "current a value is:" << a << endl;
return 0;
}
3)对象、继承、引用
简单的说,ostream是基类,而ofstream是派生类。派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性,如格式化方法precision()和self()
继承的另一个特征是基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。例如,参数类型为ostream&的函数可以接受ostream对象(如cout)或ofstream对象作为参数
4)何时使用引用参数
a.程序员能够修改调用函数中的数据对象
b.通过传递引用而不是整个数据对象,可以提高程序的运行速度
5)何时使用按值传递
a.如果数据对象很小,如内置数据类型或小型结构,则按值传递
b.如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针
c.如果数据对象时较大的结构,则使用const指针或const引用,以提高程序的效率,这样可以节省复制结构所需的时间和空间
d.如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递
5)函数重载
参数个数不同构成的重载
#include "stdafx.h"
#include "iostream"
using namespace std;
void sum( int iLeftValue, int iRightValue )
{
}
void sum( int iLeftValue, int iMidValue, int iRightValue )
{
}
参数类型不同构成的重载
#include "stdafx.h"
#include "iostream"
using namespace std;
void sum( int iLeftValue, int iRightValue )
{
}
void sum( double fLeftValue, double fMidValue )
{
}
其他的比如,返回值类型,不能区别一个函数
6)函数模板
函数模板是通用的函数描述,也就是说它们使用通用类型来定义函数,其中的通用类型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许通用类型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parametarized types)。
#include "stdafx.h"
#include "iostream"
using namespace std;
/*
template
void Swap( T* a, T* b )
{
T temp;
temp = *a;
*a = *b;
*b = temp;
}*/
template
void Swap( T& a, T& b )
{
T temp;
temp = a;
a = b;
b = temp;
}
int _tmain(int argc, _TCHAR* argv[])
{
int ia = 14;
int ib = 10;
cout << "current ia value is:" << ia << endl;
cout << "current b value is:" << ib << endl;
Swap( ia, ib );
cout << "current ia value is:" << ia << endl;
cout << "current ib value is:" << ib << endl;
cout << "\n";
double fa = 14.4;
double fb = 10.4;
cout << "current fa value is:" << fa << endl;
cout << "current fb value is:" << fb << endl;
Swap( fa, fb );
cout << "current fa value is:" << fa << endl;
cout << "current fb value is:" << fb << endl;
return 0;
}
a、重载的函数模板
需要多个对不同类型使用同一种算法的函数时,可使用模板。不过,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载函数模板定义。和常规重载一样,被重载的模板的函数特征为( T&, T& )。而新模板的特征为( T[], T[], int ),最后一个参数的类型为具体类型( int ),而不是通用类型。并非所有的模板参数都必须是模板参数类型。
#include "stdafx.h"
#include "iostream"
using namespace std;
#define ARRAYMAXLENGTH 4
template
void Swap( T& a, T& b )
{
T temp;
temp = a;
a = b;
b = temp;
}
template
void Swap( T arr[], T brr[], const int nLength )
{
T temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr[ i ] = temp;
}
}
template
void DisplayArray( const T arr[], int nLength )
{
for ( int i = 0; i < nLength; i++ )
{
cout << "current " << i << " value is:" << arr[ i ] << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int ia = 14;
int ib = 10;
cout << "current ia value is:" << ia << endl;
cout << "current b value is:" << ib << endl;
Swap( ia, ib );
cout << "current ia value is:" << ia << endl;
cout << "current ib value is:" << ib << endl;
cout << "\n";
int arr[] = { 1, 2, 3, 4 };
int brr[] = { 9, 8, 7, 6 };
DisplayArray( arr, ARRAYMAXLENGTH );
Swap( arr, brr, ARRAYMAXLENGTH );
cout << "\n";
DisplayArray( arr, ARRAYMAXLENGTH );
return 0;
}
b、模板的显示具体化
对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
显示具体化的圆形和定义应以template<>打头,并通过名称来指出类型。
具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。
#include "stdafx.h"
#include "iostream"
using namespace std;
#define ARRAYMAXLENGTH 4
template
void Swap( T arr[], T brr[], const int nLength )
{
T temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr[ i ] = temp;
}
}
template<>
void Swap( double arr[], double brr[], const int nLength)
{
double temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr [ i ] = temp;
}
cout << "enter in this function!" << endl;
}
template
void DisplayArray( const T arr[], int nLength )
{
for ( int i = 0; i < nLength; i++ )
{
cout << "current " << i << " value is:" << arr[ i ] << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "\n";
int arr[] = { 1, 2, 3, 4 };
int brr[] = { 9, 8, 7, 6 };
DisplayArray( arr, ARRAYMAXLENGTH );
Swap( arr, brr, ARRAYMAXLENGTH );
cout << "\n";
DisplayArray( arr, ARRAYMAXLENGTH );
cout << "\n";
double dArr[] = { 1.1, 2.2, 3.3, 4.4 };
double dBrr[] = { 9.9, 8.8, 7.7, 6.6 };
DisplayArray( dArr, ARRAYMAXLENGTH );
Swap( dArr, dBrr, ARRAYMAXLENGTH );
cout << "\n";
DisplayArray( dArr, ARRAYMAXLENGTH );
return 0;
}
c、非模板函数和模板函数共存
如果不是对模板进行实例化,比如:
Swap
的调用,那么调用模板函数,如果调用形式是Swap( dArr, dBrr, ARRAYMAXLENGTH );,则优先调用非模板函数
#include "stdafx.h"
#include "iostream"
using namespace std;
#define ARRAYMAXLENGTH 4
template
void Swap( T arr[], T brr[], const int nLength )
{
T temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr[ i ] = temp;
}
}
template<>
void Swap( double arr[], double brr[], const int nLength)
{
double temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr [ i ] = temp;
}
cout << "enter in this function1!" << endl;
}
void Swap( double arr[], double brr[], const int nLength)
{
double temp;
for ( int i = 0; i < nLength; i++ )
{
temp = arr[ i ];
arr[ i ] = brr[ i ];
brr [ i ] = temp;
}
cout << "enter in this function2!" << endl;
}
template
void DisplayArray( const T arr[], int nLength )
{
for ( int i = 0; i < nLength; i++ )
{
cout << "current " << i << " value is:" << arr[ i ] << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "\n";
int arr[] = { 1, 2, 3, 4 };
int brr[] = { 9, 8, 7, 6 };
DisplayArray( arr, ARRAYMAXLENGTH );
Swap( arr, brr, ARRAYMAXLENGTH );
cout << "\n";
DisplayArray( arr, ARRAYMAXLENGTH );
cout << "\n";
double dArr[] = { 1.1, 2.2, 3.3, 4.4 };
double dBrr[] = { 9.9, 8.8, 7.7, 6.6 };
DisplayArray( dArr, ARRAYMAXLENGTH );
Swap( dArr, dBrr, ARRAYMAXLENGTH );
cout << "\n";
DisplayArray( dArr, ARRAYMAXLENGTH );
return 0;
}
限定符volatile、mutable
volatile关键字表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,可以将一个指针指向某个硬件的位置,其实包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)了能修改其中的内容。或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会发生变化。如果不将变量声明为volatile,则编译器将进行这种优化;将变量声明为volatile,相当于告诉编译器,不要进行这种优化。
mutable,可以用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改。
#include "stdafx.h"
#include "iostream"
using namespace std;
struct Student
{
mutable int iAge;
mutable char szName[ 10 ];
};
int _tmain(int argc, _TCHAR* argv[])
{
const Student stu = { 23, "zengraoli" };
cout << "current student age is:" << stu.iAge << endl;
cout << "current student name is:" << stu.szName << endl;
stu.iAge = 24;
memcpy( stu.szName, "zeng", sizeof("zeng") );
cout << "\n";
cout << "current student age is:" << stu.iAge << endl;
cout << "current student name is:" << stu.szName << endl;
return 0;
}
显示指出调用约定
在C语言中,一个名称只对应一个函数,因此这很容易实现。因此,为满足内部需要,C语言编译器可能将max这样的函数名翻译成_max。这种方法被称为C语言链接性(C language linkage)。但在C++中,同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此,C++编译器执行名称纠正或名称修饰,为重载函数生成不同的函数名称。例如,可能将max2( int,int )转换成_max2_i_i,而将max2( double, double )转换成_max_d_d。这种方法称为C++语言链接。
当然,可以显示的指出调用约定:
extern "C" int max( int a, int b )
{
return ( (a > b ) ? a : b );
}
extern "C++" int max2( int a, int b )
{
return ( (a > b ) ? a : b );
}
名称空间
1)未命名的名称空间
可以通过省略名称空间的名称来创建未命名的名称空间
namespace
{
int iValue;
int IAge;
}
这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称潜在作用于为:从声明点到该声明区域末尾。从这个方面看,他们与全局变量相似。不过,由于这种名称空间没有名称,因此不能显示地使用using编译指令或using声明来使它在其他位置都可用。具体地说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称,因此这种方法可以替代链接性为内部的静态变量。
#include "stdafx.h"
namespace
{
int iValue;
int IAge;
}
int _tmain(int argc, _TCHAR* argv[])
{
iValue = 14;
IAge = 39;
return 0;
}
2)名称空间及其前途
随着程序员逐渐熟悉名称空间,将出现同一的编程理念。下面是当前的一些指导原则:
a、使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
b、使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
c、如果开发了一个函数库或类库,讲起放在一个名称空间中。
d、仅将编译指令using作为一种将旧代码转换为使用名称空间的权益之计
e、不要再头文件中使用using编译指令。首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为。如果非要使用编译指令using,应将其放在所有预处理器编译指令#include之后
f、导入名称时,首选使用作用域解析操作符或using声明的方法
g、对于using声明,首选将其作用域设置为局部而不是全局。
使用名称空间的主旨是简化大型编程项目的管理工作。对于只有一个文件的简单程序,使用using编译指令并非什么大逆不道的事。
抽象和类
生活中充满复杂性,处理复杂性的方法之一是简化和抽象。人的身体是由无数个原子组成的,而一些学者认为人的思想是由半自主的主体组成的。但将人自己看做一个实体将简单得多。在计算中,为了根据信息与用户之间的接口来表示他,抽象是至关重要的。也就是说,将问题的本质特征抽象出来,并根据特征来描抽象是通往用户定义类型的捷径,在C++中,用户定义类型指的是实现抽象接口的类的设计。
接口
接口是一个共享框架,供两个系统交互时使用;例如,用户可能是自己,而程序可能是字处理器。使用字处理器时,不能直接将脑子中想到的词传输到计算机内存中,而必须同程序提供的接口交互、敲打键盘时,计算机将字符显示到屏幕上;移动鼠标时,计算机移动屏幕上的光标。
对于类,所说的公共接口。在这里,公众(public)是使用类的程序,交互系统由类对象组成。而接口由编写类的人提供的方法组成,接口让程序员能够编写与类对象交互的代码。从而让程序能够使用类对象。例如,要计算string对象中包含多少个字符,无须打开对象,而只需使用string类提供的size()方法。类设计禁止公共用户直接访问类。但公众可以使用size()方法。size()方法是用户和string类对象之间的公共接口的组成部分。通常,方法getline()是istream类的公共接口的组成部分,使用cin的程序不是直接与cin对象内部交互来读取一行输入,而是使用getline();
this指针
每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式*this。在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值。
仔细选择数据类型,使类最小
在设计类时,应认真考虑类成员的数据类型。贸然使用非标准或依赖于平台的数据类型将使类急剧增大,从而增加所需的内存或程序的工作了。这种做法既低效又非常糟糕。
与boo(在多数平台上通常只占1个字节)不同的是,每个BOOL通常都将占据4个字节。如果类成员的用途是管理Boolean值(true或false),则实际只需要1个字节。
实现一个堆栈的类
1)可创建空堆栈
2)可将数据项添加到栈顶(压入)
3)可从栈顶删除数据项(弹出)
4)可查看堆栈是否填满
5)可查看堆栈是否为空
可以将上述描述转换为一个类声明,其中公有成员函数提供了表示堆栈操作的接口,而私有数据成员负责存储堆栈数据。
// testStack.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
typedef int MyType;
template< typename Item >
class Stack
{
public:
Stack()
{
top = 0;
memset( data, 0, sizeof( data ) );
}
bool Push( const Item item )
{
if ( MAX == top )
{
return false;
}
data[ top++ ] = item;
return true;
}
bool Pop( Item &item )
{
if ( 0 == top )
{
return false;
}
item = data[ --top ];
return true;
}
bool IsEmpty()
{
return ( 0 == top );
}
bool IsFull()
{
return ( MAX == top );
}
void Print()
{
for ( int i = 0; i < top; i++ )
{
cout << "the " << i << " value is:" << data[ i ] << endl;
}
}
private:
enum { MAX = 10 };
Item data[ MAX ];
int top;
};
int _tmain(int argc, _TCHAR* argv[])
{
int i;
MyType temp;
Stack test;
cout << "isEmpty:" << test.IsEmpty() << endl;
for ( i = 0; i <= 9; i++ )
{
test.Push( i + 1 );
}
cout << "isFull:" << test.IsFull() << endl;
if ( !test.Push( 11 ) )
{
cout << "push failure!" << endl;
}
for ( i = 0; i <= 9; i++ )
{
if ( test.Pop( temp ))
{
cout << "pop a elem:" << temp << endl;
}
}
if ( !test.Push( 11 ) )
{
cout << "push failure!" << endl;
}
test.Print();
return 0;
}
使用类
1)类中一个加法操作符的重载例子
#include "stdafx.h"
#include "string"
#include "iostream"
using namespace std;
namespace
{
class CTest_A
{
public:
CTest_A( int nValue, string strName )
{
m_nAge = nValue;
m_strName = strName;
}
// override operator +
CTest_A operator +( const CTest_A& rCTest_A ) const
{
m_nAge += rCTest_A.m_nAge;
return *this;
}
~CTest_A(){}
inline int GetAge() const
{
return m_nAge;
}
inline string GetName() const
{
return m_strName;
}
private:
int m_nAge;
string m_strName;
};
}
int _tmain(int argc, _TCHAR* argv[])
{
CTest_A CA( 23, "zengraoli" );
CTest_A CB( 23, "zengraoli2" );
CB = CB +CA;
cout << "current student name is:" << CB.GetName() << endl;
cout << "current student age is:" << CB.GetAge() << endl;
return 0;
}
2)重载限制
多数C++操作符都可以用这样的方式重载。重载的操作符(有些例外情况)不必是成员函数,但必须至少有一个操作数是用户定义的类型。
a、重载后的操作符必须至少有一个操作数使用用户自定的类型,这将防止用户为标准类型重载操作符。因此,不恩能够将减法操作符(-)重载为计算两个double值的和,而不是他们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
b、使用操作符时不能违反操作符原来的句法规则。例如,不能将求模操作符(%)重载成使用一个操作数。通用不能修改操作符的优先级。因此,如果将加好操作符重载成将两个类相加,则新的操作符与原来的加好具有相同的优
先级。
c、不能定义新的操作符。例如,不能定义operator**()函数里表示求幂。
3)为何需要友元
在位类重载二院操作符时(带两个参数的操作符)常常需要用到友元。将Time对象乘以实数就属于这种情况。乘法的操作符使用了两种不同的类型,也就是说,假发恶化减法操作符都结合连个Time值,而乘法操作符将一个Time值与一个double值结合在一起。这限制了该操作符的使用方式。左侧的操作数是调用对象。也就是说,下面的语句:
A = B * 2.75;将被转换为下面的成员函数调用:A = B.operator*( 2.75 );但是如果写成A = 2.75 * B;因为2.75不是Time类型的对象。因此,编译器不能使用成员函数调用来替换该表达式。
所以这个时候,要把乘法重载为非成员函数(大多是操作符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用的,他使用的所有值(包括对象)都是显示参数。这样,编译器就能够顺利编译A = 2.75 * B;
#include "stdafx.h"
#include "iostream"
using namespace std;
namespace
{
class CTime
{
public:
CTime( int nHours = 0, int nMiniutes= 0 )
:m_nHours( nHours ), m_nMiniutes( nMiniutes )
{
}
friend CTime operator*( double ftime, const CTime& ct );
inline int GetHours() const
{
return m_nHours;
}
inline int GetMiniute() const
{
return m_nMiniutes;
}
private:
int m_nHours;
int m_nMiniutes;
};
CTime operator*( double ftime, const CTime& ct )
{
CTime result;
long totalminutes = static_cast( ct.m_nHours * ftime * 60 + ct.m_nMiniutes * ftime );
result.m_nHours = totalminutes / 60;
result.m_nMiniutes = totalminutes % 60;
return result;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
CTime CA( 4, 10 );
CA = 2.0 * CA;
cout << "current hours is:" << CA.GetHours() << " " << "current miniutes is:" << CA.GetMiniute() << endl;
return 0;
}
4)常用的友元:重载<<操作符
一个很有用的类特性是,可以对<<操作符进行重载,使之能与cout一起来显示对象的内容。当输出time对象的时候,可以直接使用cout << time;之所以可以这样做,是因为<<是可被重载的C++操作符之一。实际上,它已经被重载很多次了。最初,他表示额含义是按位移。ostream类对该操作符进行了重载,将其转换为一个输出工具。
在重载<<的时候应使用cout对象本身(void operator<<( ostream& os, CTime& ct )),而不是他的拷贝。因此该函数按应用(而不是按值)来传递该对象。这样,表达式cout << time;将导致os成为cout的一个别名;而表达式cout << time;将导致os成为cerr的另一个别名。
#include "stdafx.h"
#include "iostream"
using namespace std;
namespace
{
class CTime
{
public:
CTime( int nHours = 0, int nMiniutes= 0 )
:m_nHours( nHours ), m_nMiniutes( nMiniutes )
{
}
friend CTime operator*( double ftime, const CTime& ct );
inline int GetHours() const
{
return m_nHours;
}
inline int GetMiniute() const
{
return m_nMiniutes;
}
private:
int m_nHours;
int m_nMiniutes;
};
CTime operator*( double ftime, const CTime& ct )
{
CTime result;
long totalminutes = static_cast( ct.m_nHours * ftime * 60 + ct.m_nMiniutes * ftime );
result.m_nHours = totalminutes / 60;
result.m_nMiniutes = totalminutes % 60;
return result;
}
ostream& operator<<( ostream& os, CTime& ct )
{
os << "current hours is:" << ct.GetHours() << " " << "current miniutes is:" << ct.GetMiniute() << endl;
return os;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
CTime CA( 4, 10 );
CA = 2.0 * CA;
// cout << "current hours is:" << CA.GetHours() << " " << "current miniutes is:" << CA.GetMiniute() << endl;
cout << CA << CA;
return 0;
}
类的自动转换和强制类型转换
1)如果构造函数中含有类似的拷贝函数:
CStonewt( double lbs )
{
m_nStone = int( lbs ) / Lbs_per_stn;
m_fPds_left = int( lbs ) % Lbs_per_stn + lbs - int( lbs );
m_fPounds = lbs;
}
那么使用
CStonewt myCat;
myCat = 19;
程序将使用构造函数CStonewt( double lbs )来创建一个临时的CStonewt对象,并将19.6作为初始值。随后,采用逐成员赋值方式将该临时对象的内容赋值到myCat中(比如m_nStone = int(
lbs ) / Lbs_per_stn;)。这一过程称为隐式转换,因为他是自动进行的,而不需要显示强制类型转换。如果换成CStonewt( double lbs, int i )有两个参数,因此不能用来转换类型。
#include "stdafx.h"
#include "iostream"
using namespace std;
namespace
{
class CStonewt
{
public:
CStonewt( double lbs )
{
m_nStone = int( lbs ) / Lbs_per_stn;
m_fPds_left = int( lbs ) % Lbs_per_stn + lbs - int( lbs );
m_fPounds = lbs;
}
CStonewt( int stn, double lbs )
{
m_nStone = stn;
m_fPds_left = lbs;
m_fPounds = stn * Lbs_per_stn + lbs;
}
CStonewt()
{
m_nStone = m_fPounds = m_fPds_left = 0;
}
~CStonewt()
{
}
void show_lbs() const
{
cout << m_nStone << " stone" << m_fPds_left << " pound\n" << endl;;
}
void show_stn() const
{
cout << m_fPounds << " pound\n" << endl;
}
private:
enum { Lbs_per_stn = 14 };
int m_nStone;
double m_fPds_left;
double m_fPounds;
};
}
int _tmain(int argc, _TCHAR* argv[])
{
CStonewt myCat;
myCat = 19;
return 0;
}
2)将构造函数用作自动类型转换函数似乎是一项不错的特性。不过,当程序员拥有更丰富的C++经验时,将发现这种自动也行并非总是合乎需要的,因为这会导致意外的类型转换。因此,最新
的C++实现新增了一个关键字(explicit),用来关闭这种自动特性。也就是说,可以这样声明构造函数
explicit CStonewt( double lbs )
{
m_nStone = int( lbs ) / Lbs_per_stn;
m_fPds_left = int( lbs ) % Lbs_per_stn + lbs - int( lbs );
m_fPounds = lbs;
}
但此时仍然可以进行myCat = (CStonewt)19.5;强制类型转换。
3)把CStonewt类对象赋值给int、double变量
要进行这样的操作时,编译器发现右侧是CStonewt类型,而左侧是int、double类型,因此它将查看程序员是否定义了与此匹配的转换函数(如果没有找到这样的定义,编译器将生成错误消息
,指出无法将CStonewt赋给int、double)
如果想要使用这种转换函数,要转换为typeName类型,需要使用这种形式的转换函数:
operator typeName();
注意以下几点:
a、转换函数必须是类方法
b、转换函数不能指定返回类型
c、转换函数不能有参数
#include "stdafx.h"
#include "iostream"
using namespace std;
namespace
{
class CStonewt
{
public:
explicit CStonewt( double lbs )
{
m_nStone = int( lbs ) / Lbs_per_stn;
m_fPds_left = int( lbs ) % Lbs_per_stn + lbs - int( lbs );
m_fPounds = lbs;
}
CStonewt( int stn, double lbs )
{
m_nStone = stn;
m_fPds_left = lbs;
m_fPounds = stn * Lbs_per_stn + lbs;
}
CStonewt()
{
m_nStone = m_fPounds = m_fPds_left = 0;
}
~CStonewt(){}
operator int() const
{
return int( 100.5 );
}
operator double() const
{
return 999.5 ;
}
private:
enum { Lbs_per_stn = 14 };
int m_nStone;
double m_fPds_left;
double m_fPounds;
};
}
int _tmain(int argc, _TCHAR* argv[])
{
CStonewt myCat;
myCat = (CStonewt)19.5;
double fValue = myCat;
int iValue = myCat;
cout << "iValue is:" << iValue << endl;
cout << "fValue is:" << fValue << endl;
return 0;
}
类和动态内存分配
小插曲 : strlen()返回字符串长度,但不包括末尾的空字符,因此构造函数len + 1,使分配的内存能够存储包含空字符的字符串
1)含有很多隐藏错误的stringBad类
#ifndef _STRINGBAD_H_
#define _STRINGBAD_H_
#include "iostream"
#include "string"
using namespace std;
class StringBad
{
public:
StringBad();
StringBad( const char* str );
~StringBad();
friend ostream& operator<< ( ostream& os, const StringBad& sb );
private:
char* m_str;
int m_nLen;
public:
static int num_strings;
};
#endif
StringBad.cpp:
#include "stdafx.h"
#include "StringBad.h"
int StringBad::num_strings = 0;
StringBad::StringBad()
{
m_nLen = 4;
m_str = new char[ 4 ];
num_strings++;
strcpy_s( m_str, strlen( "C++" ) + 1,"C++" );
cout << StringBad::num_strings << ": \"" << m_str << "\" object created" << endl;
}
StringBad::StringBad( const char* str )
{
m_nLen = strlen( str );
m_str = new char[ m_nLen + 1 ];
num_strings++;
strcpy_s( m_str,m_nLen + 1 , str );
cout << num_strings << ": \"" << m_str << "\" object created" << endl;
}
StringBad::~StringBad()
{
if ( m_str )
{
delete[] m_str;
}
num_strings--;
cout << "in the StringBad::~StringBad() num_strings is:" << num_strings << endl;
}
ostream& operator<< ( ostream& os, const StringBad& sb )
{
os << "this StringBad str is:" << sb.m_str << endl;
os << "this StringBad len is:" << sb.m_nLen << endl;
return os;
}
testStringBad.cpp:
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "StringBad.h"
void callme1( StringBad& rStringBad )
{
cout << "in the function callme1" << endl;
cout << "in the function callme1" << rStringBad << endl;
}
void callme2( StringBad stringBad )
{
cout << "in the function callme2" << endl;
cout << "in the function callme1" << stringBad << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
StringBad headline1( "Create headline1" );
StringBad headline2( "Create headline2" );
StringBad sport( "Create sport" );
cout << headline1 << endl;
cout << headline2 << endl;
cout << sport << endl;
callme1( headline1 );
cout << headline1 << endl;
callme2( headline2 );
cout << headline2 << endl;
cout << "Initialize one object to another:" << endl;
StringBad sailer = sport;
cout << "sailer" << sailer << endl;
cout << "Assign one object to anther:" << endl;
StringBad knot;
knot = headline1;
cout << "knot" << knot << endl;
cout << "End of main()" << endl;
cout << "num_strings is:" << StringBad::num_strings << endl;
return 0;
}
2)
复制拷贝函数
StringBad& StringBad::operator= ( const StringBad& st)
{
if( this == &st )
{
return *this;
}
delete[] str;
len = st.len;
str = new char[ strlen( len + 1 ) ];
str::strcpy( str, st.str );
return *this;
}
3)
重写下标运算符
char& operator[] ( int i )
{
return m_str[ i ];
}
const char& operator[] ( int i ) const
{
return m_str[ i ];
}
为什么要提供两个版本。原因是m_str是常量,而上述方法无法确保不修改数据。
但在重载时,C++将区分常量和非常量函数的特征标,因此提供了另一个仅供const String对象使用的
operator[]()版本
4)
新的String类
#include "stdafx.h"
#include "iostream"
#include "string"
using namespace std;
#define MAXTEMPSIZE 256
class myString
{
public:
myString()
{
m_nLen = 4;
m_str = new char[ m_nLen + 1 ];
strcpy_s( m_str, strlen( "C++" ) + 1, "C++" );
num_string++;
cout << "in the myString():" << endl;
}
myString( char* str )
{
m_nLen = strlen( str );
m_str = new char[ m_nLen + 1 ];
strcpy_s( m_str, m_nLen + 1, str );
num_string++;
cout << "in the myString( char* str ):" << endl;
}
myString( const myString& rString )
{
m_nLen = strlen( rString.m_str );
m_str = new char[ m_nLen + 1 ];
strcpy_s( m_str, m_nLen + 1, rString.m_str );
num_string++;
cout << "in the myString( const myString& rString ):" << endl;
}
~myString()
{
if ( m_str )
{
cout << "this m_str is:" << m_str << endl;
delete[] m_str;
}
num_string--;
cout << "in the ~myString():" << endl;
}
static int HowMany()
{
return num_string;
}
inline int length() const
{
return m_nLen;
}
myString& operator= ( const myString& rString )
{
if ( this == &rString )
{
return *this;
}
m_nLen = rString.m_nLen;
m_str = new char[ m_nLen + 1 ];
strcpy_s( m_str, m_nLen + 1, rString.m_str );
num_string++;
cout << "in the myString& operator= ( const myString& rString ):" << endl;
return *this;
}
myString& operator= ( const char* str )
{
m_nLen = strlen( str );
m_str = new char[ m_nLen + 1 ];
strcpy_s( m_str, m_nLen + 1, str );
num_string++;
cout << "in the myString& myString& operator= ( const char* str ):" << endl;
return *this;
}
char& operator[] ( int i )
{
return m_str[ i ];
}
const char& operator[] ( int i ) const
{
return m_str[ i ];
}
friend ostream& operator<< ( ostream& os, const myString& rString );
friend istream& operator>> ( istream& is, myString& rString );
friend bool operator< ( const myString& rLeft, const myString& rRight );
friend bool operator> ( const myString& rLeft, const myString& rRight );
friend bool operator== ( const myString& rLeft, const myString& rRight );
private:
int m_nLen;
char* m_str;
static int num_string;
};
int myString::num_string = 0;
ostream& operator<< ( ostream& os, const myString& rString )
{
os << "this myString m_str is:" << rString.m_str << endl;
return os;
}
istream& operator>> ( istream& is, myString& rString )
{
char temp[ MAXTEMPSIZE ];
is.get( temp, MAXTEMPSIZE );
if ( is )
{
rString = temp;
}
while ( is && is.get() != '\n' )
{
continue;
}
return is;
}
bool operator< ( const myString& rLeft, const myString& rRight )
{
return ( strcmp( rLeft.m_str, rRight.m_str ) < 0 );
}
bool operator> ( const myString& rLeft, const myString& rRight )
{
return ( strcmp( rLeft.m_str, rRight.m_str ) > 0 );
}
bool operator== ( const myString& rLeft, const myString& rRight )
{
return ( strcmp( rLeft.m_str, rRight.m_str ) == 0 );
}
void showString( const myString myStr )
{
cout << "in the showString:" << endl;
cout << myStr << endl;
}
void showString2( const myString& myStr )
{
cout << "in the showString2:" << endl;
cout << myStr << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
myString headLine1( "create headLine1" );
myString headLine2( "create headLine2" );
cout << headLine1<< endl;
cout << headLine2<< endl;
myString sport;
cout << sport<< endl;
myString sport2 = sport;
cout << sport2<< endl;
showString( headLine1 );
showString( headLine2 );
showString( sport );
showString( sport2 );
showString2( headLine1 );
showString2( headLine2 );
showString2( sport );
showString2( sport2 );
cout << "headLine1 > headLine2:" << ( headLine1 > headLine2 ) << endl;
cout << "headLine1 < headLine2:" << ( headLine1 < headLine2 ) << endl;
cout << "headLine1 == headLine2:" << ( headLine1 == headLine2 ) << endl;
cout << "headLine1 get howmany is:" << headLine1.HowMany() << endl;
cout << "headLine2 get length is:" << headLine2.length() << endl;
myString headLine3 = "create headLine3";
cout << headLine3<< endl;
showString( headLine3 );
showString2( headLine3 );
cout << headLine3<< endl;
myString headLine4 = headLine3;
cout << headLine4<< endl;
showString( headLine4 );
showString2( headLine4 );
cout << headLine4<< endl;
cout << "return [] is:" << headLine4[3] << endl;
cin >> headLine4[3];
cout << "return [] is:" << headLine4[3] << endl;
cout << headLine4<< endl;
showString( headLine4 );
showString2( headLine4 );
cout << headLine4<< endl;
cout << "========================================" << endl;
return 0;
}
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "new"
#include "string"
#define BUF 512
class CJustString
{
public:
CJustString( const string& str = "create CJustString", int iValue = 0 )
{
m_strWords = str;
m_nNumber = iValue;
cout << m_strWords << " constructed" << endl;
}
~CJustString()
{
cout << m_strWords << " destroyed" << endl;
}
void Show() const
{
cout << m_strWords << ", " << m_nNumber << endl;
}
private:
string m_strWords;
int m_nNumber;
};
int _tmain(int argc, _TCHAR* argv[])
{
int* buffer = new int[ BUF ];
// char* buffer = new char[ BUF ];
CJustString *pc1, *pc2;
pc1 = new ( buffer ) CJustString;
pc2 = new CJustString( "Heap1", 20 );
cout << "Memory block address:\n" << "buffer:" << ( void* )buffer << " heap:" << pc2 << endl;
cout << "Memory contents:" << endl;
cout << pc2 << ":" << endl;
pc2->Show();
CJustString *pc3, *pc4;
pc3 = new ( buffer ) CJustString( "Bad Idea", 6 );
pc4 = new CJustString( "Heap2", 6 );
cout << "Memory contents:" << endl;
cout << pc3 << ":" << endl;
pc3->Show();
cout << pc4 << ":" << endl;
pc4->Show();
delete pc2;
delete pc4;
delete[] buffer;
cout << "Done" << endl;
return 0;
}
该程序使用new操作符创建了一个512*4字节的内存换成去,然后使用new操作符在堆中创建两个CJustString对象,并试图使用布局new操作符在内存缓冲区中创建两个CJustString对象。最后
,他使用delete来释放使用new分配的内存。当然这里不需要单独delete pc1,只要delete[] buffer;便可。
7)队列模拟
进一步了解类后,可将这方面的知识用于解决编程问题。Headther银行打算在Food Heap超市开设一个自动柜员机(ATM)。Food Heap超市的管理则担心排队等待使用ATM的人流会干扰超市
而定交通,希望限制排队等待的人数。Headther银行希望对顾客排队等待的时间进行估测。要编写一个程序来模拟这种情况,让超市的管理者可以了解ATM可能造成的影响。
对于这种问题,最自然的方法是使用顾客队列。队列是一种抽象的数据类型(ADT),可以存储有序的项目序列。新项目被添加在队尾,并可以删除队首的项目。队列有点像堆栈,不过堆栈
在同一段进行添加和删除。这使得堆栈是一种后进先出(LIFO)的结构,而队列是先进先出(FIFO)的。从概念上说,队列就好比是收款台或者ATM前面排的队,所以对于上述问题,队列非常合适。因此,工程的任务之一是定义一个Queue类。
队列的项目是顾客。Headther银行的代表介绍:通常,三分之一的顾客只需要一分钟便可获得服务器,三分之一的顾客需要两分钟,另外三分之一的顾客需要三分钟。另外,顾客到达的
时间是随机的,但每个小时使用自动柜员机的顾客数量相当稳定。工程的另外两项任务是:设计一个表示顾客的类:编写一个程序来模拟顾客和队列之间的交互。
队列类:
1.队列存储有序的项目序列
2.队列所能容纳的项目数有一定的限制
3.应当能够创建空队列
4.应当能够检查队列是否为空
5.应当能够检查队列是否满的
6.应当能够在队尾添加项目
7.应当能够在队首删除项目
8.应当能够确定队列中的项目数
设计类时,需要开发公有接口和私有实现
Customer类:
通常ATM客户有很多属性,例如姓名、账户和账户结余。不过,这里的模拟需要使用的唯一一个属性是客户核实进入队列以及客户交易所需的时间。当模拟生成新客户时,程序将创建一个新的
客户对象,并在其中存储客户的到达时间以及一个随机生成的交易时间。当客户到达队首时,程序将记录此时的时间,并将其与进入队列的时间相减,得到客户的等待时间。
模拟:
1、判断是否来了新的客户。如果来了,并且此时队列未满,则将它添加到队列中,否则拒绝客户入队。
2、如果没有客户在进行交易,则选取队列的第一个客户。确定该客户的已等待时间,并将wait_time
计数器设置为新客户所需的办理时间
3、如果客户正在处理中,则将wait_time计数器减1
4、记录各种数据,如获得服务的客户数目、被拒绝的客户数目、排队等候的累积时间以及累积的队列长度等。
当模拟循环结束时,程序将报告各种统计结果
bool newCustomer( double x )
{
return ( rand() * x / RAND_MAX < 1 );
}
其工作原理如下:值RAND_MAX是在cstdlib文件(以前是stdlib.h)中定义的,是rand()函数可能返回的最大值(0是最小值)。假设客户到达的平均间隔时间x为6,则rand()*x / RAND_MAX的值将
位于0到6之间。具体地说,平均每个6次,这个值会有一次小于1。不过,这个函数可能会导致客户到达的时间间隔有时为1分钟,有时为20分钟。这种方法虽然很笨拙,但可使实际情况不同于
有规则地没6分钟来一个客户。如果客户到达的平均时间间隔少于1分钟,则上述方法将无效,但模拟并不是针对这种情况设计的。如果确实需要处理这种情况,最好提高时间分辨率吗,比如
每次循环代表10秒钟。
#include "stdafx.h"
#include "iostream"
using namespace std;
class CCustomer
{
public:
CCustomer()
{
m_nArrive = 0;
m_nProcessTime = 0;
}
void set( long when )
{
m_nProcessTime = rand() % 3 + 1;
m_nArrive = when;
}
inline long when() const
{
return m_nArrive;
}
inline long pTime() const
{
return m_nProcessTime;
}
private:
long m_nArrive;
int m_nProcessTime;
};
typedef CCustomer Item;
class Queue
{
public:
enum { Q_SIZE = 10 };
Queue( int qs ) : m_nQSize( qs )
{
front = rear = NULL;
m_Items = 0;
}
~Queue()
{
m_Items = 0;
Node* temp;
while ( NULL != front )
{
temp = front;
front = front->next;
delete temp;
}
}
int queueCount() const
{
return m_Items;
}
bool isEmpty() const
{
return ( 0 == m_Items );
}
bool isFull() const
{
return ( m_Items == m_nQSize );
}
bool dequeue( Item& item ) // 出队列
{
if ( NULL == front )
{
return false;
}
item = front->item;
Node* temp;
temp = front;
front = front->next;
delete temp;
m_Items--;
if ( 0 == m_Items )
{
rear = NULL;
}
return true;
}
bool enqueue( const Item& item ) // 入队列
{
if ( isFull() )
{
return false;
}
Node* add = new Node;
if ( NULL == add )
{
return false;
}
add->item = item;
add->next = NULL;
if ( NULL == front )
{
front = add;
}
else
{
rear->next = add;
}
m_Items++;
rear = add;
return true;
}
private:
struct Node
{
Item item;
struct Node* next;
};
Node* front;
Node* rear;
int m_Items;
const int m_nQSize;
Queue( const Queue& rQueue ) : m_nQSize( 0 ) { };
Queue& operator= ( const Queue& rQueue ) { };
};
#include "cstdlib"
#include "ctime"
const int MIN_PER_HR = 60;
bool newCustomer( double x )
{
return ( rand() * x / RAND_MAX < 1 );
}
int _tmain(int argc, _TCHAR* argv[])
{
srand( time( 0 ) );
cout << "Case Study Bank of Heather Automatic Teller" << endl;
cout << "Enter maxnum size of queue" << endl;
int qs;
cin >> qs;
Queue line( qs ); // 分配一个当前队列
cout << "Enter the number of simulation hours:"; // 模拟时间
int hours;
cin >> hours;
long cyclelimit = MIN_PER_HR * hours;
cout << "Enter the average number of customers per hour:"; // 每小时
double perhour;
cin >> perhour;
double min_per_cust;
min_per_cust = MIN_PER_HR;
Item temp;
long turnaways = 0;
long customers = 0;
long served = 0;
long sum_line = 0;
int wait_time = 0;
long line_wait = 0;
for ( int cycle = 0; cycle < cyclelimit; cycle++ )
{
if ( newCustomer( min_per_cust ) )
{
if ( line.isFull() )
{
turnaways++;
}
else
{
customers++;
temp.set( cycle );
line.enqueue( temp );
}
}
if ( wait_time <= 0 && !line.isEmpty() )
{
line.dequeue( temp );
wait_time = temp.pTime();
line_wait += cycle - temp.when();
served++;
}
if ( wait_time > 0 )
{
wait_time--;
}
sum_line += line.queueCount();
}
//report result
if ( customers > 0 )
{
cout << "customers accepted:" << customers << endl;
cout << "customers served:" << served << endl;
cout << " turnaways:" << turnaways << endl;
cout << "average queue size:";
cout.precision( 2 );
cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout << ( double )sum_line / cyclelimit << endl;
cout << " average wait_time:" << ( double )line_wait / served << " minutes" << endl;
}
else
{
cout << "no customers!" << endl;
}
cout << "Done!" << endl;
return 0;
}
类
继承-----is-a关系
派生类和基类之间的特殊关系是基于C++继承的底层模型的。实际上,C++有3中继承方法:共有继承、保护继承和私有继承。公有继承是最常用的方式,它建立一种is-a关系,即派生类对
象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。
公有继承不能建立is-like-a关系,也就是说,它不采用明喻。人们通常说律师就像鲨鱼,但律师并不是鲨鱼。例如,鲨鱼可以在水下生活。所以,不应从Shark类派生出Lawyer类。继承
可以在基类的基础是哪个添加属性,但不能删除基类的属性。在一些情况下,可以设计一个包含公有特征的类,然后以is-a或has-a关系,使用这个类来定义相关的类。
公有继承不简历is-implemented-as-a(作为……来实现)关系。例如,可以使用数组来实现堆栈,但从Array类派生出Stack类是不合适额,因为堆栈不是数组。例如,数组所以不是堆栈的
属性。另外,可以以其他方式实现堆栈,如使用链表。正确的方法是:通过让堆栈含一个私有的Array对象成员,来隐藏数组实现。
公有继承不简历uses-a关系。例如,计算机可以使用激光打印机,但是从Computer类派生出Printer类(或反过来)是没有意义的。不过,可以使用友元函数或类来处理Printer对象和
Computer对象之间的通信。
在C++语言中,完全可以使用公有继承来建立has-a、is-implemented-as-a或uses-a关系,不过,这样做通常或导致编程方面的问题。因此,还是坚持使用is-a关系吧。
多态公有继承的一个例子:
#include "stdafx.h"
#include "string"
#include "iostream"
using namespace std;
class Brass
{
public:
Brass( const char* s = "Nullbody", long an = -1, double a = 0.0 )
{
m_szCustomerName = new char[ strlen( s ) + 1 ];
memcpy( m_szCustomerName, s, strlen( s ) + 1 );
m_szNumber = an;
m_fBalance = a;
}
virtual ~Brass()
{
if ( m_szCustomerName )
{
delete[] m_szCustomerName;
m_szCustomerName = NULL;
}
}
virtual void deposit( double amt ) // 存款
{
m_fBalance += amt;
}
virtual void teller( double amt ) // 取款
{
if ( m_fBalance - amt >= 0 )
{
m_fBalance -= amt;
}
}
virtual void showAccountInfo() const // 显示账号信息
{
ios_base::fmtflags initialState = cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout.precision( 2 );
cout << "CustomerName is:" << m_szCustomerName << endl;
cout << "Number is:" << m_szNumber << endl;
cout << "Balance is:" << m_fBalance << endl;
cout.setf( initialState );
}
inline double GetBalance() const
{
return m_fBalance;
}
private:
char* m_szCustomerName;
long m_szNumber;
double m_fBalance; // 当前结余
};
class BrassPlus : public Brass
{
public:
BrassPlus( const char* s = "Nullbody", long an = -1, double a = 0.0,
double dOverdrawlimit = 500, double dOverdraft = 0.1, double dOverdraw = 0 )
: Brass( s, an, a )
{
m_fOverdrawlimit = dOverdrawlimit;
m_fOverdraft = dOverdraft;
m_fOverdraw = dOverdraw;
}
BrassPlus( const Brass& rBrass,
double dOverdrawlimit = 500, double dOverdraft = 0.1, double dOverdraw = 0 )
: Brass( rBrass )
{
m_fOverdrawlimit = dOverdrawlimit;
m_fOverdraft = dOverdraft;
m_fOverdraw = dOverdraw;
}
virtual void teller( double amt ) // 取款
{
ios_base::fmtflags initialState = cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout.precision( 2 );
double bal = GetBalance();
if ( amt < bal )
{
Brass::teller( amt );
}
else if ( amt < bal + m_fOverdrawlimit - m_fOverdraw )
{
double advance = amt - bal;
m_fOverdraw += advance * ( 1.0 + m_fOverdraft );
cout << "Bank advance:$" << advance << endl;
cout << "Finance charge:$" << advance * m_fOverdraft << endl;
deposit( advance );
Brass::teller( amt );
}
else
{
cout << "Credit limit exceeded. Transaction cancelled" << endl;
}
cout.setf( initialState );
}
virtual void showAccountInfo() const // 显示账号信息
{
ios_base::fmtflags initialState = cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout.precision( 2 );
Brass::showAccountInfo();
cout << "m_fOverdrawlimit is:" << m_fOverdrawlimit << endl;
cout << "m_fOverdraft is:" << m_fOverdraft << endl;
cout << "m_fOverdraw is:" << m_fOverdraw << endl;
cout.setf( initialState );
}
inline int setOverdrawlimit( double dOverdrawlimit )
{
m_fOverdrawlimit = dOverdrawlimit;
}
inline int setOverdraft( int iOverdraft )
{
m_fOverdraft = iOverdraft;
}
inline int setOverdraw()
{
m_fOverdraw = 0;
}
private:
double m_fOverdrawlimit; // 透支上限
double m_fOverdraft; // 贷款利率
double m_fOverdraw; // 当前的透支金额
};
int _tmain(int argc, _TCHAR* argv[])
{
Brass Piggy( "porcelot Pigg", 381299, 4000.00 );
BrassPlus Hoggy( "Horatio Hogg", 382288, 3000.00 );
Piggy.showAccountInfo();
cout << "\n";
Hoggy.showAccountInfo();
cout << "\n";
cout << "Depositing $1000 into the Hogg Account:" << endl;
Hoggy.deposit( 1000.00 );
cout << "\n";
cout << "New balance:$" << Hoggy.GetBalance() << endl;
cout << "WithDrawing $4200 from the Piggy Account:" << endl;
Piggy.teller( 4200.00 );
cout << "Pigg account balance:$" << Piggy.GetBalance() << endl;
cout << "\n";
cout << "teller $4200 from the Hoggy Account:" << endl;
Hoggy.teller( 4200.00 );
cout << "\n";
Hoggy.showAccountInfo();
return 0;
}
访问控制protected
关键字protected与private相似,在类外只能用公有类成员来访问protected部分中的类成员。protected和private之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直
接访问基类的保护成员,但不能直接访问基类的私有成员。因此对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员的行为与公有成员相似。
单例设计模式
#include "stdafx.h"
#include "iostream"
using namespace std;
class CTheSingle
{
public:
static CTheSingle* GetTheOnlyInstance()
{
static CTheSingle objCTheSingle;
return &objCTheSingle;
}
protected:
CTheSingle(){}
~CTheSingle(){}
private:
int m_nNumber;
};
int _tmain(int argc, _TCHAR* argv[])
{
CTheSingle* CTest = CTheSingle::GetTheOnlyInstance();
return 0;
}
GetTheOnlyInstance()方法尽在第一次被调用时,创建CTheSingle类的一个实例。以这种方式构造的静态对象一直有效,直到程序终止,此时这种静态对象将自动释放。要检索指向这个类的
唯一一个实例的指针,只需要调用静态方法GetTheOnlyInstance(),该方法返回单对象的地址。
因为静态变量在函数调用结束后仍保存在内存中,所以以后在调用GetTheOnlyInstance()时,将返回同一个静态对象的地址。
抽象基类(abstract base class,ABC)
有时候,使用is-a规则并不像看上去的那样简单。例如,假设正在开发一个图形程序,该程序会显示圆和椭圆等。圆是椭圆的一个特殊情况----长轴和短轴等长的椭圆。因此,所有的圆
都是椭圆,可以从Ellipse类排成出Circle类。但涉及到细节时,将发现许多问题。
首先考虑Ellipse类包含的内容。数据成员可以报考椭圆中心的坐标、长半轴、短半轴以及方向角(水平坐标轴与长轴之间的角度)。另外,还可以包括一些移动椭圆、返回椭圆面积、旋转
椭圆以及缩放长半轴和短半轴的方法,但是Circle类从Ellipse类派生出来并不合适,因为很多数据成员根本不需要。
一种解决办法,即从Ellipse类和Circle类中抽象出他们的共性,将这些特性放到一个ABC类中。然后从该ABC派生出Circle类和Ellipse类。这样,便可以使用基类指针数组同时管理
Ellipse对象和Circle对象,即可以使用多台方法。在这里,这两个类的共同点是中心坐标、move()、area()。确实,甚至不能在ABC中实现area(),因为他没有包含必要的数据成员。C++通过
使用纯虚函数(pure virtual function)提供为实现的函数。
应用ABC概念
一个应用ABC的范例,因此这里将这一概念用于Brass和BrassPlus账户,首先定义一个名为AcctABC的ABC。这个类包含Brass和BrassPlus类共有的所有方法和数据成员,而哪些在Brass和
BrassPlus类中的行为不同的方法应呗声明为寻函数。至少应有一个虚函数是纯虚函数,这一才能使AcctABC成为抽象类
#include "stdafx.h"
#include "string"
#include "iostream"
using namespace std;
class IAcctABC
{
public:
IAcctABC( const char* s = "Nullbody", long an = -1, double a = 0.0 )
{
m_szCustomerName = new char[ strlen( s ) + 1 ];
memcpy( m_szCustomerName, s, strlen( s ) + 1 );
m_szNumber = an;
m_fBalance = a;
}
virtual ~IAcctABC()
{
if ( m_szCustomerName )
{
delete[] m_szCustomerName;
m_szCustomerName = NULL;
}
cout << "AAAAAAAAAAAAAAAAAA" << endl;
}
void deposit( double amt ) // 存款
{
m_fBalance += amt;
}
inline char* GetCustomerName() const
{
if ( m_szCustomerName )
{
return m_szCustomerName;
}
return "";
}
inline long GetNumber() const
{
return m_szNumber;
}
inline double GetBalance() const
{
return m_fBalance;
}
ios_base::fmtflags SetFormat() const
{
ios_base::fmtflags initialState = cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout.precision( 2 );
return initialState;
}
virtual void teller( double amt ) = 0;
virtual void showAccountInfo() const = 0;
private:
char* m_szCustomerName;
long m_szNumber;
double m_fBalance; // 当前结余
};
void IAcctABC::teller( double amt )
{
m_fBalance -= amt;
}
class Brass : public IAcctABC
{
public:
Brass( const char* s, long an, double a )
: IAcctABC( s, an, a )
{
}
~Brass()
{
cout << "BBBBBBBBBBBBBBBB" << endl;
}
virtual void teller( double amt ) // 取款
{
if ( IAcctABC::GetBalance() - amt >= 0 )
{
IAcctABC::teller( amt );
}
}
virtual void showAccountInfo() const // 显示账号信息
{
ios_base::fmtflags initialState = SetFormat();
cout << "CustomerName is:" << IAcctABC::GetCustomerName() << endl;
cout << "Number is:" << IAcctABC::GetNumber() << endl;
cout << "Balance is:" << IAcctABC::GetBalance() << endl;
cout.setf( initialState );
}
};
class BrassPlus : public IAcctABC
{
public:
BrassPlus( const char* s = "Nullbody", long an = -1, double a = 0.0,
double dOverdrawlimit = 500, double dOverdraft = 0.1, double dOverdraw = 0 )
: IAcctABC( s, an, a )
{
m_fOverdrawlimit = dOverdrawlimit;
m_fOverdraft = dOverdraft;
m_fOverdraw = dOverdraw;
}
BrassPlus( const IAcctABC& rIAcctABC,
double dOverdrawlimit = 500, double dOverdraft = 0.1, double dOverdraw = 0 )
: IAcctABC( rIAcctABC )
{
m_fOverdrawlimit = dOverdrawlimit;
m_fOverdraft = dOverdraft;
m_fOverdraw = dOverdraw;
}
virtual void teller( double amt ) // 取款
{
ios_base::fmtflags initialState = cout.setf( ios_base::fixed, ios_base::floatfield );
cout.setf( ios_base::showpoint );
cout.precision( 2 );
double bal = GetBalance();
if ( amt < bal )
{
IAcctABC::teller( amt );
}
else if ( amt < bal + m_fOverdrawlimit - m_fOverdraw )
{
double advance = amt - bal;
m_fOverdraw += advance * ( 1.0 + m_fOverdraft );
cout << "Bank advance:$" << advance << endl;
cout << "Finance charge:$" << advance * m_fOverdraft << endl;
deposit( advance );
IAcctABC::teller( amt );
}
else
{
cout << "Credit limit exceeded. Transaction cancelled" << endl;
}
cout.setf( initialState );
}
virtual void showAccountInfo() const // 显示账号信息
{
ios_base::fmtflags initialState = SetFormat();
cout << "CustomerName is:" << IAcctABC::GetCustomerName() << endl;
cout << "Number is:" << IAcctABC::GetNumber() << endl;
cout << "Balance is:" << IAcctABC::GetBalance() << endl;
cout << "m_fOverdrawlimit is:" << m_fOverdrawlimit << endl;
cout << "m_fOverdraft is:" << m_fOverdraft << endl;
cout << "m_fOverdraw is:" << m_fOverdraw << endl;
cout.setf( initialState );
}
inline int setOverdrawlimit( double dOverdrawlimit )
{
m_fOverdrawlimit = dOverdrawlimit;
}
inline int setOverdraft( int iOverdraft )
{
m_fOverdraft = iOverdraft;
}
inline int setOverdraw()
{
m_fOverdraw = 0;
}
private:
double m_fOverdrawlimit; // 透支上限
double m_fOverdraft; // 贷款利率
double m_fOverdraw; // 当前的透支金额
};
int _tmain(int argc, _TCHAR* argv[])
{
Brass Piggy( "porcelot Pigg", 381299, 4000.00 );
BrassPlus Hoggy( "Horatio Hogg", 382288, 3000.00 );
Piggy.showAccountInfo();
cout << "\n";
Hoggy.showAccountInfo();
cout << "\n";
cout << "Depositing $1000 into the Hogg Account:" << endl;
Hoggy.deposit( 1000.00 );
cout << "\n";
cout << "New balance:$" << Hoggy.GetBalance() << endl;
cout << "WithDrawing $4200 from the Piggy Account:" << endl;
Piggy.teller( 4200.00 );
cout << "Pigg account balance:$" << Piggy.GetBalance() << endl;
cout << "\n";
cout << "teller $4200 from the Hoggy Account:" << endl;
Hoggy.teller( 4200.00 );
cout << "\n";
Hoggy.showAccountInfo();
return 0;
}
在设计ABC之前,首先应开发一个模型----指出编程问题所需的类以及它们之间的相互关系。一种学院派思想认为,如果要设计类继承层次,则只能将那些不会被用作基类的类设计为具体的类。这种方法的设计更清晰,复杂程度更低。
实际应用注意事项:使用ABC实施接口规则
可以将ABC看做是一种必须实施的接口。ABC要求具体派生类覆盖其纯虚函数----迫使派生类遵循ABC所设置的接口规则。这种模型在基于组件的编程模式中很常见,在这种情况下,,使用
ABC使得组件设计人员能够制定“接口规定”,这样确保了从ABC派生的所有组件都至少支持ABC制定的功能。
友元函数
由于友元函数并非类成员,因此不能继承。然而,可能希望派生类的友元函数能够使用基类的友元函数。为此,可以通过强制类型转换将派生类引用或指针转换为基类引用或指针,然后
使用转换后的指针或引用来调用基类的友元函数:
ostream& operator << ( ostream& os, const hasDMC& hs )
{
os << ( const baseDMA& )hs;
os << "Style:" << hs.style << endl;
return os;
}
当然也可以使用dynamic_case<>来进行强制类型转换
子类中同时需要new的情况:
包含对象成员的类
valarray类是由头文件valarray支持的。顾名思义,这个类用于处理数值,他支持诸如将数组中的所有元素的值想家以及在数组中找出最大和最小的值等操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。
一个Student类----一个getline导致构造函数递归的类
// readBook2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "valarray"
#include "string"
class CStudent
{
public:
typedef std::valarray ArrayDb;
CStudent() : sz_Name( "Null Student" ), m_ArrScores()
{
}
CStudent( const string& name ) : sz_Name( name ), m_ArrScores()
{
}
explicit CStudent( int n ) : sz_Name( "Nully" ), m_ArrScores( n )
{
}
CStudent( const string& name, int n ) : sz_Name( name ), m_ArrScores( n )
{
}
CStudent( const string& name, const ArrayDb& a ) : sz_Name( name ), m_ArrScores( a )
{
}
CStudent( const char* name, const double* pd, int n ) : sz_Name( name ), m_ArrScores( pd, n )
{
}
double Average() const
{
if ( m_ArrScores.size() > 0 )
{
return ( m_ArrScores.sum() / m_ArrScores.size() );
}
else
{
return 0;
}
}
const string& GetName() const
{
return sz_Name;
}
double& operator[]( int i)
{
return m_ArrScores[ i ];
}
double operator[]( int i ) const
{
return m_ArrScores[ i ];
}
ostream& CStudent::arr_out( ostream& os ) const
{
int i;
int lim = m_ArrScores.size();
if ( lim > 0 )
{
for ( i = 0; i < lim; i++ )
{
os << m_ArrScores[ i ] << " ";
if ( 4 == i % 5 )
{
os << endl;
}
}
if ( 0 != i % 5 )
{
os << endl;
}
}
else
{
os << "empty array";
}
return os;
}
friend istream& operator >>( istream& is, CStudent& stu );
friend istream& operator <<( istream& os, const CStudent& stu );
friend istream& getline( istream& is, const CStudent& stu );
~CStudent(){};
private:
string sz_Name;
ArrayDb m_ArrScores;
};
istream& operator >>( istream& is, CStudent& stu )
{
is >> stu.sz_Name;
return is;
}
ostream& operator <<( ostream& os, const CStudent& stu )
{
os << "this student name is:" << stu.GetName() << endl;
os << "this student scores is:" << endl;
stu.arr_out( os );
return os;
}
istream& getline( istream& is, const CStudent& stu )
{
getline( is, stu.sz_Name );
return is;
}
const int puplis = 3;
const int quizzes = 5;
void set( CStudent& sa, int n );
int _tmain(int argc, _TCHAR* argv[])
{
CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) };
int i;
for ( i = 0; i < puplis; ++i )
{
set( ada[ i ], quizzes );
}
cout << "\nStudent List:" << endl;
for ( i = 0; i < puplis; ++i )
{
cout << ada[ i ].GetName() << endl;
}
cout << "\nResults:" << endl;
for ( i = 0; i < puplis; i++ )
{
cout << endl << ada[ i ];
cout << "average" << ada[ i ].Average() << endl;
}
cout << "Done." << endl;
return 0;
}
void set( CStudent& sa, int n )
{
cout << "Please enter the student name:" << endl;
getline( cin, sa );
cout << "Please enter " << n << "quiz scores:" << endl;
for ( int i = 0; i < n; i++ )
{
cin >> sa[ i ];
}
while( '\n' != cin.get() )
{
continue;
}
}
// 在
istream& getline( istream& is, const CStudent& stu )
{
getline( is, stu.sz_Name );
return is;
}
//const CStudent& stu导致递归
修改之后的版本
// readBook2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "valarray"
#include "string"
class CStudent
{
public:
typedef std::valarray ArrayDb;
CStudent() : sz_Name( "Null Student" ), m_ArrScores()
{
}
CStudent( const string& name ) : sz_Name( name ), m_ArrScores()
{
}
explicit CStudent( int n ) : sz_Name( "Nully" ), m_ArrScores( n )
{
}
CStudent( const string& name, int n ) : sz_Name( name ), m_ArrScores( n )
{
}
CStudent( const string& name, const ArrayDb& a ) : sz_Name( name ), m_ArrScores( a )
{
}
CStudent( const char* name, const double* pd, int n ) : sz_Name( name ), m_ArrScores( pd, n )
{
}
double Average() const
{
if ( m_ArrScores.size() > 0 )
{
return ( m_ArrScores.sum() / m_ArrScores.size() );
}
else
{
return 0;
}
}
const string& GetName() const
{
return sz_Name;
}
double& operator[]( int i)
{
return m_ArrScores[ i ];
}
double operator[]( int i ) const
{
return m_ArrScores[ i ];
}
ostream& CStudent::arr_out( ostream& os ) const
{
int i;
int lim = m_ArrScores.size();
if ( lim > 0 )
{
for ( i = 0; i < lim; i++ )
{
os << m_ArrScores[ i ] << " ";
if ( 4 == i % 5 )
{
os << endl;
}
}
if ( 0 != i % 5 )
{
os << endl;
}
}
else
{
os << "empty array";
}
return os;
}
friend istream& operator >>( istream& is, CStudent& stu );
friend istream& operator <<( istream& os, const CStudent& stu );
friend istream& getline( istream& is, CStudent& stu );
~CStudent(){};
private:
string sz_Name;
ArrayDb m_ArrScores;
};
istream& operator >>( istream& is, CStudent& stu )
{
is >> stu.sz_Name;
return is;
}
ostream& operator <<( ostream& os, const CStudent& stu )
{
os << "this student name is:" << stu.GetName() << endl;
os << "this student scores is:" << endl;
stu.arr_out( os );
return os;
}
istream& getline( istream& is, CStudent& stu )
{
getline( is, stu.sz_Name );
return is;
}
const int puplis = 3;
const int quizzes = 5;
void set( CStudent& sa, int n );
int _tmain(int argc, _TCHAR* argv[])
{
CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) };
int i;
for ( i = 0; i < puplis; ++i )
{
set( ada[ i ], quizzes );
}
cout << "\nStudent List:" << endl;
for ( i = 0; i < puplis; ++i )
{
cout << ada[ i ].GetName() << endl;
}
cout << "\nResults:" << endl;
for ( i = 0; i < puplis; i++ )
{
cout << endl << ada[ i ];
cout << "average" << ada[ i ].Average() << endl;
}
cout << "Done." << endl;
return 0;
}
void set( CStudent& sa, int n )
{
cout << "Please enter the student name:";
getline( cin, sa );
cout << "Please enter " << n << "quiz scores:" << endl;
for ( int i = 0; i < n; i++ )
{
cin >> sa[ i ];
}
while( '\n' != cin.get() )
{
continue;
}
}
私有继承
c++还有另一种实现has-a关系的途径----私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用公有继承,基类的公有方法将成为派生类的公有方法。简而言之,派生类将继承基类的接口,这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。简而言之,派生类不能继承基类的接口。正如从被包含对象中看到的,这种不完全继承是has-a关系的一部分。
因此私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "valarray"
#include "string"
class CStudent : private valarray, private string
{
private:
typedef std::valarray ArrayDb;
public:
CStudent() : string( "Null Student" ), ArrayDb()
{
}
CStudent( const string& name ) : string( name ), ArrayDb()
{
}
explicit CStudent( int n ) : string( "Nully" ), ArrayDb( n )
{
}
CStudent( const string& name, int n ) : string( name ), ArrayDb( n )
{
}
CStudent( const string& name, const ArrayDb& a ) : string( name ), ArrayDb( a )
{
}
CStudent( const char* name, const double* pd, int n ) : string( name ), ArrayDb( pd, n )
{
}
~CStudent(){};
double Average() const
{
if ( ArrayDb::size() > 0 )
{
return ( ArrayDb::sum() / ArrayDb::size() );
}
else
{
return 0;
}
}
const string& GetName() const
{
return ( const string& ) *this;
}
double& operator[] ( int i )
{
return ArrayDb::operator[]( i );
}
const double operator[] ( int i ) const
{
return ArrayDb::operator[]( i );
}
ostream& arr_out( ostream& os ) const
{
int i;
int lim = ArrayDb::size();
if ( lim > 0 )
{
for ( i = 0; i < lim; i++ )
{
// os << ArrayDb[ i ] << " ";
os << ArrayDb::operator[]( i ) << " ";
if ( 4 == i % 5 )
{
os << endl;
}
}
if ( 0 != i % 5 )
{
os << endl;
}
}
else
{
os << "empty array";
}
return os;
}
friend istream& operator >>( istream& is, CStudent& stu );
friend istream& operator <<( istream& os, const CStudent& stu );
friend istream& getline( istream& is, CStudent& stu );
};
istream& operator >>( istream& is, CStudent& stu )
{
is >> ( string& )stu;
return is;
}
ostream& operator <<( ostream& os, const CStudent& stu )
{
os << "this student name is:" << stu.GetName() << endl;
os << "this student scores is:" << endl;
stu.arr_out( os );
return os;
}
istream& getline( istream& is, CStudent& stu )
{
getline( is, ( string& )stu );
return is;
}
const int puplis = 3;
const int quizzes = 5;
void set( CStudent& sa, int n );
int _tmain(int argc, _TCHAR* argv[])
{
CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) };
int i;
for ( i = 0; i < puplis; ++i )
{
set( ada[ i ], quizzes );
}
cout << "\nStudent List:" << endl;
for ( i = 0; i < puplis; ++i )
{
cout << ada[ i ].GetName() << endl;
}
cout << "\nResults:" << endl;
for ( i = 0; i < puplis; i++ )
{
cout << endl << ada[ i ];
cout << "average" << ada[ i ].Average() << endl;
}
cout << "Done." << endl;
return 0;
}
void set( CStudent& sa, int n )
{
cout << "Please enter the student name:";
getline( cin, sa );
cout << "Please enter " << n << "quiz scores:" << endl;
for ( int i = 0; i < n; i++ )
{
cin >> sa[ i ];
}
while( '\n' != cin.get() )
{
continue; }
}
const string& GetName() const
{
return ( const string& ) *this;
}
引用stu不会自动转换为string引用,根本原因在于,在私有继承中,不在进行显示类型转换的清华下,不能讲指向派生类的引用或指针赋给基类引用或指针。不过,即使这个例子使用的是公有继承,也必须使用显示类型转换。原因之一是,如果不适用类型转换,代码is >>stu;与友元函数原型匹配,从而导致递归调用:
istream& operator >>( istream& is, CStudent& stu )
{
is >> ( string& )stu;
return is;
}
另一个原因是,由于这个类使用的是多重继承,编译器将无法确定应转换成哪个基类,如果两个基类都提供了函数operator<<()。
使用包含还是私有继承
由于既可以使用包含,也可以使用私有继承来建立has-a关系。大多数c++程序员倾向于前者。不过私有继承所提供的特性确实比包含多。例如,假设类包含保护成员,则这样的成员在派生类中式可用的,但在继承层次机构外是不可用的。如果使用组合奖这样的类保护在另一类中,则后者将不是排成类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承的到的将是派生类,因此他能够访问保护成员。
另一种需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。
多重继承(MI)
为了解决多重继承而引入虚基类,也就是继承的两个类都含有相同函数的时候,产生无法识别该函数,引入了虚基类,只需要在调用某个父类的方法的时候,加上类名限定符即可
一个例子(注意这里为了能够进行显示转换需要使用virtual public方式继承)
// testMI.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
class Worker
{
public:
Worker( const string& Name, long Id ) : m_fullName( Name ), m_lId( Id )
{
}
Worker() : m_fullName( "no one" ), m_lId( 0L )
{
}
virtual ~Worker() = 0;
virtual void Set() = 0;
virtual void Show() const = 0;
protected:
void Data() const
{
cout << "Name:" << m_fullName << endl;
cout << "Employee ID:" << m_lId << endl;
}
void Get()
{
getline( cin, m_fullName );
cout << "Enter worker's ID:";
cin >> m_lId;
while ( '\n' != cin.get() )
{
continue;
}
}
private:
string m_fullName;
long m_lId;
};
Worker::~Worker()
{
}
class Waiter : virtual public Worker
{
public:
Waiter() : Worker(), m_nPanache( 0 )
{
}
Waiter( const string& Name, long Id, int p = 0 ) : Worker( Name, Id ), m_nPanache( p )
{
}
Waiter( const Worker& rWorker, int p = 0 ) : Worker( rWorker ), m_nPanache( p )
{
}
void Set()
{
cout << "Enter waiter's name:";
Worker::Get();
Get();
}
void Show() const
{
cout << "Category:waiter" << endl;
Worker::Data();
Data();
}
protected:
void Data() const
{
cout << "Panache rating:" << m_nPanache << endl;
}
void Get()
{
cout << "Enter waiter's Panache rating:";
cin >> m_nPanache;
while ( '\n' != cin.get() )
{
continue;
}
}
private:
int m_nPanache;
};
class Singer : virtual public Worker
{
public:
Singer() : Worker(), voice( 0 )
{
}
Singer( const string& Name, long Id, int v = 0 ) : Worker( Name, Id ), voice( v )
{
}
Singer( const Worker& rWorker, int v = 0 ) : Worker( rWorker ), voice( v )
{
}
void Set()
{
cout << "Enter singer's name:";
Worker::Get();
Get();
}
void Show() const
{
cout << "Category:singer" << endl;
Worker::Data();
Data();
}
protected:
enum{ other, alto, contralto, soprano, base, baritone, tenor };
enum{ Vtypes = 7 };
void Data() const
{
cout << "Vocal range:" << pv[ voice ] << endl;
}
void Get()
{
cout << "Enter number for singer's vocal range:" << endl;
int i;
for ( i = 0; i < Vtypes; i++ )
{
cout << i << ":" << pv[ i ] << " ";
if ( 3 == i % 4 )
{
cout << endl;
}
}
if ( 0 != i % 4 )
{
cout << endl;
}
cin >> voice;
while ( '\n' != cin.get() )
{
continue;
}
}
private:
static char* pv[ Vtypes ];
int voice;
};
char* Singer::pv[ Singer::Vtypes ] = { "other", "alto", "contralto", "Soprano", "bass", "baritone", "tenor" };
class SingingWaiter : public Singer, public Waiter
{
public:
SingingWaiter(){}
SingingWaiter( const string& Name, long Id, int p = 0, int v = other )
: Worker( Name, Id ), Waiter( Name, Id, p ), Singer( Name, Id, v )
{
}
SingingWaiter( const Worker& rWorker, int p = 0, int v = other )
: Worker( rWorker ), Waiter( rWorker, p ), Singer( rWorker, v )
{
}
SingingWaiter( const Waiter& rWaiter, int v = other )
: Worker( rWaiter ), Waiter( rWaiter ), Singer( rWaiter, v )
{
}
SingingWaiter( const Singer& rSinger, int p = 0 )
: Worker( rSinger ), Waiter( rSinger, p ), Singer( rSinger )
{
}
void Set()
{
cout << "Enter singing waiter's name:";
Worker::Get();
Get();
}
void Show() const
{
cout << "Category:singing waiter" << endl;
Worker::Data();
Data();
}
protected:
void Data() const
{
Singer::Data();
Waiter::Data();
}
void Get()
{
Waiter::Get();
Singer::Get();
}
};
const int SIZE = 5;
int _tmain(int argc, _TCHAR* argv[])
{
Worker* loals[ SIZE ];
int ct;
for ( ct = 0; ct < SIZE; ct++ )
{
char choice;
cout << "Enter the employee category:" << endl;
cout << "w:waiter s:singer " << "t:sing waiter q:quit" << endl;
cin >> choice;
while ( NULL == strchr( "wstq", choice ) )
{
cout << "Please enter a, w, s, t, or, q:";
cin >> choice;
}
if ( 'q' == choice )
{
break;
}
switch ( choice )
{
case 'w':
loals[ ct ] = new Waiter; break;
case 's':
loals[ ct ] = new Singer; break;
case 't':
loals[ ct ] = new SingingWaiter; break;
}
cin.get();
loals[ ct ]->Set();
}
cout << "\nHere is your staff:" << endl;
int i;
for ( i = 0; i < ct; i++ )
{
cout << endl;
loals[ i ]->Show();
}
for ( i = 0; i < ct; i++ )
{
delete loals[ i ];
}
cout << "Done." << endl;
return 0;
}
类模板
模板的声明template
类模板里面的成员函数,每个函数头都将以相同的模板声明打头
除非编译器实现了新的export关键字,否则将模板成员函数放置在一个独立的实现文件中将无法运行。因为模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。
深入探讨模板类
使用指针堆栈的方法之一是,让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。把这些指针放在堆栈中是有意义的,因为每个指针都将指向不同的字符串。注意,创建不同指针时调用程序的职责,而不是堆栈的职责。堆栈的任务是管理指针,而不是创建指针。
例如,假设要模拟下面的情况。某人将一车文件夹交付给了Plodson。如果Plodson的收取篮(in-basket)是空的,他将取出车中最上面的文件夹,将其放入收取篮(in-basket)中。如果收取篮既不是空的也不是满的,Plodson将处理收取篮中最上面的文件,也可能取出车中的下一个文件,把它放入收取篮。他采取了自认为是比较鲁莽的行为----仍硬币来决定要才去的措施。
可以用一个指针数组来模拟这种情况,其中的指针指向表示车中文件的字符串。每个字符串都包含所描述的人的姓名。可以用堆栈表示收取篮,并使用第二个指针数组来表示发出篮。通过将指针从输入数组压入到堆栈中来表示将文件添加到收取篮中,同时通过从堆栈中弹出项目,并将它添加到发出篮中来表示处理文件。
堆栈类的模拟:
// testTemplate.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
#include "ctime"
template
class CStack
{
public:
explicit CStack( int ss = SIZE );
CStack( const CStack& st );
~CStack()
{
if ( items )
{
delete[] items;
}
}
public:
bool isEmpty()
{
return ( 0 == m_nTop );
}
bool isFull()
{
return ( m_StackSize == m_nTop );
}
bool push( const Type& Item );
bool pop( Type& Item );
CStack& operator= ( const CStack& rCStack );
private:
enum { SIZE = 10 };
int m_StackSize;
Type* items;
int m_nTop;
};
template
CStack::CStack( int ss /* = SIZE */ ) : m_StackSize( ss ), m_nTop( 0 )
{
items = new Type[ m_StackSize ];
}
template
CStack::CStack( const CStack& st )
{
m_StackSize = st.m_StackSize;
m_nTop = st.m_nTop;
items = new Type[ st.m_StackSize ];
for ( int i = 0; i < m_StackSize; i++ )
{
items[ i ] = st.items[ i ];
}
}
template
bool CStack::push( const Type& Item )
{
if ( !isFull() )
{
items[ m_nTop ] = Item;
m_nTop++;
return true;
}
return false;
}
template
bool CStack::pop( Type& Item )
{
/*
if ( !isEmpty() )
{
Item = items[ m_nTop ]; // 注意这样写是不对的,因为未满的时候items[ m_nTop ]指向未知
m_nTop--;
return true;
}*/
if ( m_nTop > 0)
{
Item = items[ --m_nTop ];
return true;
}
return false;
}
template
CStack& CStack::operator= ( const CStack& rCStack )
{
if ( rCStack == *this)
{
return *this;
}
if ( items )
{
delete[] items;
}
m_StackSize = st.m_StackSize;
m_nTop = st.m_nTop;
items = new Type[ st.m_StackSize ];
for ( int i = 0; i < m_StackSize; i++ )
{
items[ i ] = st.items[ i ];
}
}
const int NUM = 10;
int _tmain(int argc, _TCHAR* argv[])
{
srand( time( 0 ) );
cout << "Please enter stack size:";
int stacksize;
cin >> stacksize;
CStack< const char* > st( stacksize );
// in basket
const char* in[ NUM ] = {
"1:Hack Gilgamesh", "2:KiKi Ishtar",
"3:Betty Rocker", "4:Ian Flagranti",
"5:Wolfgang Kibble", "6:Portia Koop",
"7:Joy Almondo", "8:Xaverie Parika",
"9:Juan Moore", "10:Misha Mache"
};
// out basket
const char* out[ NUM ];
int processed = 0;
int nextin = 0;
while ( processed < NUM )
{
if ( st.isEmpty() )
{
st.push( in[ nextin++ ] );
}
else if ( st.isFull() )
{
st.pop( out[ processed++ ] );
}
else if ( rand() % 2 && nextin < NUM )
{
st.push( in[ nextin++ ] );
}
else
{
st.pop( out[ processed++ ] );
}
}
for ( int i = 0; i < NUM; i++ )
{
cout << out[ i ] << endl;
}
cout << "Done." << endl;
return 0;
}
数组模板范例和非类型参数
模板常被用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。确实,为容器类提供可重用代码是引入模板的主要动机。
比如Array
可以将用于常规类的技术用于模板类。模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。
1.递归使用模板
// testTemplate2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
class MyArray
{
public:
MyArray(){}
explicit MyArray( const T& v );
public:
virtual T& operator[]( int i );
virtual T operator[]( int i ) const;
private:
T ar[ n ];
};
template // init
MyArray::MyArray( const T& v )
{
for ( int i = 0; i < n; i++ )
{
ar[ i ] = v;
}
}
template // init
T& MyArray::operator[]( int i )
{
if ( i < 0 || i >= n)
{
cerr << "Error in array limits:" << i << " is out of range" << endl;
exit( EXIT_FAILURE );
}
return ar[ i ];
}
template // init
T MyArray::operator[]( int i ) const
{
if ( i < 0 || i >= n)
{
cerr << "Error in array limits:" << i << " is out of range" << endl;
exit( EXIT_FAILURE );
}
return ar[ i ];
}
int _tmain(int argc, _TCHAR* argv[])
{
MyArray sums;
MyArray aves;
MyArray< MyArray, 10 > twodee;
int i, j;
for ( i = 0; i < 10; i++ )
{
sums[ i ] = 0;
for ( j = 0; j < 5; j++ )
{
twodee[ i ][ j ] = ( i + 1 ) * ( j + 1 );
sums[ i ] += twodee[ i ][ j ];
}
aves[ i ] = ( double )sums[ i ] / 10;
}
for ( i = 0; i < 10; i++ )
{
for ( j = 0; j < 5; j++ )
{
cout.width( 2 );
cout << twodee[ i ][ j ] << " ";
}
cout << ":sum = ";
cout.width( 3 );
cout << sums[ i ] << ", average = " << aves[ i ] << endl;
}
cout << "Done." << endl;
return 0;
}
MyArray< MyArray
2.模板中使用多个类型参数
模板可以保护多个类型参数。例如,假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。
// testPair.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
class CPair
{
public:
CPair(){}
CPair( const T1& aval, const T2 bval ) : a( aval ), b( bval )
{
}
public:
T1& first()
{
return a;
}
T2& second()
{
return b;
}
T1& first() const { return a }
T2& second() const { return b }
private:
T1 a;
T2 b;
};
int _tmain(int argc, _TCHAR* argv[])
{
CPair ratings[ 4 ] =
{
CPair( "The Purple Duke", 5 ),
CPair( "Jake's Frisco Al Fresco", 4 ),
CPair( "Mont Souffle", 5 ),
CPair( "Gertie's Eats", 3 ),
};
int joins = sizeof( ratings ) / sizeof( CPair );
cout << "Raring:\tEatery\n";
for ( int i = 0; i < joins; i++ )
{
cout << ratings[ i ].second() << ":\t" << ratings[ i ].first() << endl;
}
cout << "Oops ! Revised rating:" << endl;
ratings[ 3 ].first() = "Gertie's Fab Eat";
ratings[ 3 ].second() = 6;
cout << ratings[ 3 ].second() << ":\t" << ratings[ 3 ].first() << endl;
cout << "Done." << endl;
return 0;
}
模板的具体化
类模板与函数模板很相似,因为可以有隐式实例化、显示实例化和显示具体化,他们统称为具体化(specialization)。模板以通用类型的方式描述类,而具体化是使用具体的类型生成类声明。
1.隐式实例化
一般的永华都是隐式实例化(implicit instantiation),即他们声明一个活多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义
2.显示实例化
当使用关键字template并支出所需类型来声明类时,编译器将生成类声明的显示实例化(explicit insantiation)。声明必须位于模板定义所在的名称空间中。例如,
template class MyArray
将MyArray
3.显示具体化(explicit specialization)
显示具体化是特定类型(用于替换模板的通用类型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以创建显示具体化。例如:
template
class CSortedArray
{};
在排序的时候,使用>操作符来对值进行比较。对于数字,这管用;如果T表示一种类,则只用定义了T::operator>()方法,这也管用;但如果T是有char*表示的字符串,这将不管用。所以需要对其具体化
template<> class CSortedArray
{};
4.部分具体化
c++还允许部分具体化(partial specialization),即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型
template
template
关键字后面的<>声明的是没有被具体化的类型参数。因此,上述第二个声明将T2具体化为int,但T1保存不变。注意,如果指定所有的类型,则<>内将为空,这将导致显示具体化。
template<> class CPair
// testParSpe.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
template< typename T1, typename T2 >
class Test
{
public:
Test( T1 a, T2 b )
{
m_a = a;
m_b = b;
}
void show()
{
cout << "the a value is:" << m_a << endl;
cout << "the b value is:" << m_b << endl;
}
private:
T1 m_a;
T2 m_b;
};
template< typename T1 >
class Test< T1, int >
{
public:
Test( T1 a, int b )
{
m_a = a;
m_b = b;
}
void show()
{
cout << "this template< typename T1 >, the a value is:" << m_a << endl;
cout << "this template< typename T1 >, the b value is:" << m_b << endl;
}
private:
T1 m_a;
int m_b;
};
int _tmain(int argc, _TCHAR* argv[])
{
Test< double, int > test( 20.1, 4 );
test.show();
return 0;
}
// testTemplateFriend.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
class CBeta
{
public:
CBeta( T t, int i ) : q( t ), n( i )
{}
template
U blab( U u, T t )
{
return ( n.Value() + q.Value() ) * u / t;
}
void show() const
{
q.show();
n.show();
}
private:
template
class CHold
{
public:
CHold( V v = 0 ) : val( v ){}
void show() const { cout << val << endl; }
V Value() const { return val; }
private:
V val;
};
CHold q;
CHold n;
};
int _tmain(int argc, _TCHAR* argv[])
{
CBeta quy( 3.5, 3 );
quy.show();
cout << quy.blab( 10, 2.3 ) << endl;
cout << "Done." << endl;
return 0;
}
在这个程序中CHold模板是在私有部分声明的,因此只能在CBeta类中访问他。CBeta类使用CHold模板声明了两个数据成员:
CHold
CHold
n是基于int类型的CHold对象,而q成员是基于T类型(CBeta模板参数)的CHold对象。
将模板用作参数
模板可以包含类型参数(如typename T)和非类型参数(例如int n)。模板还可以包含本身就是模板的参数。这种参数是模板新增的特性,用于实现STL。
// testTemplateParam.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
#include "ctime"
template
class CStack
{
public:
explicit CStack( int ss = SIZE );
CStack( const CStack& st );
~CStack()
{
if ( items )
{
delete[] items;
}
}
public:
bool isEmpty()
{
return ( 0 == m_nTop );
}
bool isFull()
{
return ( m_StackSize == m_nTop );
}
bool push( const Type& Item );
bool pop( Type& Item );
CStack& operator= ( const CStack& rCStack );
private:
enum { SIZE = 10 };
int m_StackSize;
Type* items;
int m_nTop;
};
template
CStack::CStack( int ss /* = SIZE */ ) : m_StackSize( ss ), m_nTop( 0 )
{
items = new Type[ m_StackSize ];
}
template
CStack::CStack( const CStack& st )
{
m_StackSize = st.m_StackSize;
m_nTop = st.m_nTop;
items = new Type[ st.m_StackSize ];
for ( int i = 0; i < m_StackSize; i++ )
{
items[ i ] = st.items[ i ];
}
}
template
bool CStack::push( const Type& Item )
{
if ( !isFull() )
{
items[ m_nTop ] = Item;
m_nTop++;
return true;
}
return false;
}
template
bool CStack::pop( Type& Item )
{
/*
if ( !isEmpty() )
{
Item = items[ m_nTop ]; // 注意这样写是不对的,因为未满的时候items[ m_nTop ]指向未知
m_nTop--;
return true;
}*/
if ( m_nTop > 0)
{
Item = items[ --m_nTop ];
return true;
}
return false;
}
template
CStack& CStack::operator= ( const CStack& rCStack )
{
if ( rCStack == *this)
{
return *this;
}
if ( items )
{
delete[] items;
}
m_StackSize = st.m_StackSize;
m_nTop = st.m_nTop;
items = new Type[ st.m_StackSize ];
for ( int i = 0; i < m_StackSize; i++ )
{
items[ i ] = st.items[ i ];
}
}
template< template class Thing >
class Crab
{
public:
Crab(){}
bool push( int iA, double fB )
{
return ( s1.push( iA ) && s2.push( fB ) );
}
bool pop( int& iA, double& fB )
{
return ( s1.pop( iA ) && s2.pop( fB ) );
}
private:
Thing s1;
Thing s2;
};
int _tmain(int argc, _TCHAR* argv[])
{
Crab nebula;
int nj;
double nb;
cout << "Enter int double pairs, such as 4 3.5 ( 0 0 to be end):" << endl;
while ( cin >> nj >> nb && nj > 0 && nb > 0 )
{
if ( !nebula.push( nj, nb ) )
{
break;
}
}
while ( nebula.pop( nj, nb) )
{
cout << nj << ", " << nb << endl;
}
cout << "Done." << endl;
return 0;
}
在这个类里还可以使用模板参数和常规参数,例如:
template< template
class Crab
{
private:
Thing s1;
Thing
}
模板类和友元
模板类声明也可以有友元。模板的友元分3类:
1.非模板友元
// testTemplateFriend2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
class CHasFriend
{
public:
CHasFriend( const T& t ) : item( t )
{
ct++;
}
~CHasFriend()
{
ct--;
}
friend void counts();
friend void report( const CHasFriend& );
private:
explicit CHasFriend( const CHasFriend& rCHasFriend ){}
private:
T item;
static int ct;
};
template
int CHasFriend::ct = 0;
void counts()
{
cout << "int count:" << CHasFriend::ct;
cout << " double count:" << CHasFriend::ct << endl;
}
void report( const CHasFriend& hf )
{
cout << "CHasFriend item:" << hf.item << endl;
}
void report( const CHasFriend& hf )
{
cout << "CHasFriend item:" << hf.item << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "No objects declared:";
counts();
CHasFriend hfil1( 10 );
cout << "After hfil1 declared:";
counts();
CHasFriend hfil2( 20 );
cout << "After hfil2 declared:";
counts();
CHasFriend hfdb( 10.5 );
cout << "After hfdb declared:";
counts();
report( hfil1 );
report( hfil2 );
report( hfdb );
return 0;
}
代码中声明使counts()函数成为模板所有实例化的友元。它内部的CHasFriend
2.约束(bound)模板友元,即友元的类型取决于类呗实例化时的类型
// testTemplateFriend3.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
void counts()
{
cout << "this count value is:" << CHasFriend::ct << endl;
}
template
void report( const T& hr )
{
cout << "this count value is:" << hr.m_item << endl;
}
template
class CHasFriend
{
public:
CHasFriend( const TT item ) : m_item( item )
{
ct++;
}
~CHasFriend()
{
ct--;
}
friend void counts();
friend void report<>( const CHasFriend& hr );
private:
explicit CHasFriend( const CHasFriend& rCHasFriend ){}
private:
TT m_item;
static int ct;
};
template
int CHasFriend::ct = 0;
int _tmain(int argc, _TCHAR* argv[])
{
counts();
CHasFriend hfil( 10 );
CHasFriend hfi2( 20 );
CHasFriend hfdb( 10.5 );
report( hfil );
report( hfi2 );
report( hfdb );
cout << "counts() output :" << endl;
counts();
cout << "counts() output :" << endl;
counts();
return 0;
}
声明中的<>指出这是模板具体化。对于report(),<>可以为空,这是因为可以从函数参数推断出模板类型参数(CHasFriend)。不过,也可以使用report< CHasFriend >( CHasFriend& )。但counts()函数没有参数,因此必须使用模板参数句法()来指明其具体化。还需要注意的是,TT是CHasFriend类的参数类型。
3.非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元
// testTemplateFriend4.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
template
class CManyFriend
{
public:
CManyFriend( const T& i ) : item( i )
{
}
template friend void show( C&, D& );
private:
T item;
};
templatevoid show( C& c, D& d )
{
cout << c.item << " " << d.item << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
CManyFriend hfil( 10 );
CManyFriend hfil2( 20 );
CManyFriend hfdb( 10.5 );
cout << "hfil, hfil2:";
show( hfil, hfil2 );
cout << "hfil2, hfdb:";
show( hfil2, hfdb );
return 0;
}
前面的1、2,int类具体化获得int函数具体化,依此类推。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:但前提是必须在类内部声明模板
友元
类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元。有一些函数、成员函数或类为友元只能是由类定义,而不能从外部强加友情。因此,金光友元被授予从外部访问类的私有部分的限制,但他们并不与面向对象的编程思想相悖;相反,他们提高了公有接口的灵活性。
嵌套类
在c++中,可以将类声明放在另一个类中。在另一个类中声明的类被称为嵌套类(nested class),他通过提供新的类型作用域来避免名称混乱。包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析操作符。
1.嵌套类的访问权限
有两种访问权限适合于嵌套类。首先,嵌套类的声明位置决定了嵌套类的作用域,即他决定了程序的哪些部分可以创建这种类的对象。其次,和其他类一样,嵌套类的公有部分、保护部分和私有部分控制了对类成员的访问。在哪些地方可以使用嵌套类以及如何使用嵌套类,取决于作用域和访问控制。
如果嵌套类是在另一个类的私有部分声明的,则只有后者知道他。派生类不能直接访问基类的私有部分。
如果嵌套类是在另一个类中到的保护部分声明的,则他对于后者来说是课件的,但是对于外部世界则是不可见的。不过,在这种情况中,派生类将知道嵌套类,并可以直接创建这种类型的对象。
如果嵌套类是在另一个类中的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它,因为他是公有的。不过,由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符。
// testNestedClass.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
class Test
{
public:
Test( int a, int b )
{
m_a = a;
m_b = b;
}
void show()
{
cout << "this class Test a value is:" << m_a << endl;
cout << "this class Test b value is:" << m_b << endl;
}
void newNestedClass( double a, double b )
{
NestedClass( a, b ).show();
}
class NestedClass
{
public:
NestedClass( double a, double b )
{
m_NestedClass_a = a;
m_NestedClass_b = b;
}
void show()
{
cout << "this class NestedClass a value is:" << m_NestedClass_a << endl;
cout << "this class NestedClass b value is:" << m_NestedClass_b << endl;
}
private:
double m_NestedClass_a;
double m_NestedClass_b;
};
private:
int m_a;
int m_b;
};
int _tmain(int argc, _TCHAR* argv[])
{
Test test( 7, 14 );
test.show();
test.newNestedClass( 10.7, 10.14 );
Test::NestedClass nestedTest( 10.8, 10.14 );
nestedTest.show();
return 0;
}
异常
程序又是会遇到运行阶段错误,导致程序无法正常地运行下去。例如,程序可能试图打开一个不可用的文件,请求过多的内存,或者遭遇不能容忍的值。通常,程序员都会试图预防这种意外的情况。C++异常为处理这种情况提供了一种功能强大而灵活的工具。
1.调用abort()
对于处理( X + Y ) / ( X + Y )这类问题,处理方式之一是,如果其中一个参数等于另一个参数的负值,则调用abort()函数。abort()函数的圆形位于头文件cstdlib(或stdlib.h)中,其典型实现是向标准错误流(即cerr使用的错误流)发送消息,然后终止程序。他还返回一个随实现而异的值,告诉操作系统,处理失败。abort()是否刷新文件缓冲区(用于存储读写到文件中的数据的内存的区域)取决于实现。如果愿意,也可以使用exit(),该函数刷新文件缓冲区,但不显示消息。
:";
// testException.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
double hmean( const double a, double b );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
cout << "Enter two numbers:";
while ( cin >> x >> y )
{
z = hmean( x, y );
cout << "harmonic mean of:" << x << " and " << y << " is " << z << endl;
cout << "Enter next set of numbers :";
}
cout << "Done." << endl;
return 0;
}
double hmean( const double a, double b )
{
if ( a == -b )
{
cout << "untenable arguments to hmean()" << endl;
abort();
}
return 2.0 * a * b / ( a + b );
}
在hmean()中调用abort()函数将直接终止程序,而不是首先返回到main()。一般而言,显示的程序异常中断消息随编译器而异。为便面异常终止,程序应在调用hmean()函数之前检查x和y的值。不过,依靠程序员来执行这种检查是不安全的。
2.返回错误码
一种比一场终止更灵活的方法是,使用函数的返回值来指出问题。例如,ostream类的get(void)成员通常返回下一个输入字符的ASCII码,但到达文件尾时,将返回特殊值EOF。对hmean()来说,这种方法不管用。任何数值都是有效的返回值,因此不存在可用于指出问题的特殊值。在这种情况下,可使用指针参数或引用参数来将值返货给调用程序,并使用函数的返回值来指出成功还是失败。istream族重载>>操作符使用了这种技术的变体。通过告知调用程序是成功了还是失败了,他将hmean()的返回值重新定义为bool函数,让返回值指出成功了还是失败了,另外还给该函数增加了第三个参数,用于提供答案。
:";
// testException2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "cfloat"
bool hmean( double a, double b, double* ans );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
cout << "Enter two number:";
while ( cin >> x >> y )
{
if ( hmean( x, y, &z ) )
{
cout << "Harmonic mean of:" << x << " and " << y << " is " << z << endl;
}
else
{
cout << "One value should not be the negative " << "of the other - try again." << endl;
}
cout << "Enter next set of numbers :";
}
cout << "Done." << endl;
return 0;
}
bool hmean( double a, double b, double* ans )
{
if ( a == -b )
{
*ans = DBL_MAX;
return false;
}
else
{
*ans = 2.0 * a * b / ( a + b );
return true;
}
}
3.异常机制
C++异常是针对程序运行过程中发生的异常情况(例如被0除)的一种相应。异常提供了将控制权从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分:
a、引发异常
b、捕获有处理程序的异常
c、使用try块
程序在出现问题时将引发异常。例如,可以修改程序testException2.cpp中的hmean(),使之引发异常,而不是调用abort()函数。throw语句实际上是跳转,即命令程序跳到另一条语句。throw关键字表示引发异常,紧随其后的值(例如字符串或对象)指出了异常的特征。
程序使用异常处理程序(exceptiong handler)来捕获异常,异常处理程序位于要处理问题的程序中。catch关键字表示捕获异常。处理程序以关键字catch开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型。然后是一个用花括号括起的代码块,指出要采取的措施。catch关键字和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch快。
try块标识其中特定的异常可能被激活的代码块,他后面跟一个或多个catch块。try块是由关键字try指示的,关键字try的后面是一个有花括号括起来的代码块,表明需要注意这些代码引发的异常。
:";
// testException3.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
double hmean( double a, double b );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
cout << "Enter two number:";
while ( cin >> x >> y )
{
try
{
z = hmean( x, y );
}
catch ( const char* s )
{
cout << s << endl;
cout << "Enter a new pair of numbers:";
continue;
}
cout << "Harmonic mean of:" << x << " and " << y << " is " << z << endl;
cout << "Enter next set of numbers :";
}
cout << "Done." << endl;
return 0;
}
double hmean( double a, double b )
{
if ( a == -b )
{
throw "bad hmean() arguments:a = -b not allowed";
}
return 2.0 * a * b / ( a * b );
}
这个例子里被引发的异常是字符串"bad hmean() arguments:a = -b not allowed"。异常类型可以是字符串或其他C++类型,通常为类类型。
执行throw语句类似于执行返回语句,因为它也将终止函数的执行,但throw不是将该控制权返回给调用程序,而是导致程序沿函数调用序列后退,直到找到包含try块的函数。
catch块有点类似于函数定义,但并不是函数定义。关键字catch表明这是一个处理程序,而char* s则表明该处理程序与字符串异常匹配。s与函数参数定义及其类似,因为匹配的引发将被赋给s。另外,当异常与该处理程序匹配时,程序就爱那个执行括号中的代码。
如果函数引发了异常,而没有try块或没有匹配的处理程序时,在默认情况下,程序最终将调用abort()函数,但可以修改这种行为。
4.将对象用作异常类型
通常,引发异常的函数将传递一个对象。这样做的主要优点之一是,可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常。另外,对象可以携带信息,程序员可以根据这些信息来确定引发异常的原因。同时,catch块可以根据这些信息来决定采取什么样的措施。
// testException4.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "math.h"
#pragma warning( disable:4290 )
class CBad_hmean
{
public:
CBad_hmean( double a = 0, double b = 0 ) : v1( a ), v2 ( b ){}
void mesg() const;
private:
double v1;
double v2;
};
inline void CBad_hmean::mesg() const
{
cout << "hmean (" << v1 << "," << v2 << "):" << "invalid arguments:a = -b" << endl;
}
class CBad_gmean
{
public:
CBad_gmean( double a = 0, double b = 0 ) : v1( a ), v2 ( b ){}
void mesg() const;
public:
double v1;
double v2;
};
inline void CBad_gmean::mesg() const
{
cout << "gmean() arguments should be >= 0" << endl;
}
double hmean( double a, double b ) throw( CBad_hmean );
double gmean( double a, double b ) throw( CBad_gmean );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
cout << "Enter two number:";
while( cin >> x >> y )
{
try
{
z = hmean( x, y );
cout << "Harmonic mean of:" << x << " and " << y << " is " << z << endl;
cout << "Geometric mean of:" << x << " and " << y << " is " << gmean( x, y) << endl;
}
catch ( CBad_hmean& bg )
{
bg.mesg();
cout << "try again" << endl;
continue;
}
catch ( CBad_gmean& hg )
{
hg.mesg();
cout << "Values used:" << hg.v1 << ", " << hg.v2 << endl;
cout << "Sorry, you don't get to play any more." << endl;
break;
}
}
cout << "Done." << endl;
return 0;
}
double hmean( double a, double b ) throw( CBad_hmean )
{
if ( a == -b )
{
throw CBad_hmean( a, b );
}
return ( 2.0 * a * b ) / ( a * b );
}
double gmean( double a, double b ) throw( CBad_gmean )
{
if ( a < 0 || b < 0 )
{
throw CBad_gmean( a, b );
}
return ( sqrt( a * b ) );
}
首先CBad_hmean异常处理程序使用了一条continue语句,而CBad_gmean异常处理程序使用了一条break语句。因此,如果用户给函数hmean()提供的参数不正确,将导致程序跳过循环中余下的代码,进入下一次循环,而用户给函数gmean()提供的参数不正确时将结束循环。其次,异常类CBad_gmean和CBad_hmean使用的技术不同,具体来说,CBad_gmean使用的是公有数据和一个公有方法,该方法返回一个C风格字符串。
5.堆栈解退
假设try块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序流程将从引发异常的函数跳到包含try块和处理程序的函数。这就会涉及到堆栈解退(unwinding the stack)。
c++通过通过将信息放在堆栈中来处理函数调用。具体来说,程序将调用函数的指令的地址(返回地址)放到堆栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里开始继续执行。另外,函数调用将函数参数放到堆栈中。在堆栈中,这些函数参数被视为自动变量。如果被调用的函数创建了新的自动变量,则这些变量也将被添加到堆栈中。如果被调用的函数调用了另一个函数,则后者的信息将被添加到对战中,依此类推。当函数结束时,程序流程将跳到该函数被调用时存储的地址处,同事堆栈顶端的元素被释放。因此,函数通常都返回到调用它的函数,依此类推,同事每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。
现在假设函数由于出现异常(而不是由于返回)而终止,则程序也将释放堆栈中的内存,但不会在释放堆栈的第一个返回地址后停止,而是继续释放堆栈,知道找到一个位于try块中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为堆栈解退。引发机制的一个非常重要的特性是,和函数返回一样,对于堆栈中的自动类对象,类的析构函数将被调用。不过,函数返回仅仅处理该函数放在堆栈中的对象,而throw语句则处理try块和throw之间整个函数调用序列放在堆栈中的对象。如果没有堆栈解退这种特性,则引发异常后,对于中间函数调用放在堆栈中的自动类对象,其析构函数将不会被调用。
// testException5.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "math.h"
#pragma warning( disable:4290 )
#pragma warning( disable:4996 )
class CBad_hmean
{
public:
CBad_hmean( double a = 0, double b = 0 ) : v1( a ), v2 ( b ){}
void mesg() const;
private:
double v1;
double v2;
};
inline void CBad_hmean::mesg() const
{
cout << "hmean (" << v1 << "," << v2 << "):" << "invalid arguments:a = -b" << endl;
}
class CBad_gmean
{
public:
CBad_gmean( double a = 0, double b = 0 ) : v1( a ), v2 ( b ){}
void mesg() const;
public:
double v1;
double v2;
};
inline void CBad_gmean::mesg() const
{
cout << "gmean() arguments should be >= 0" << endl;
}
class demo
{
public:
demo( const char* str )
{
strcpy( word, str );
cout << "demo " << word << " created" << endl;
}
~demo()
{
cout << "demo " << word << " destoryed" << endl;
}
void show() const
{
cout << "demo " << word << " lives ! " << endl;
}
private:
char word[ 40 ];
};
double hmean( double a, double b ) throw( CBad_hmean );
double gmean( double a, double b ) throw( CBad_gmean );
double means( double a, double b ) throw( CBad_hmean, CBad_gmean );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
demo d1("found in main()");
cout << "Enter two numbers:";
while ( cin >> x >> y )
{
try
{
z = means( x, y );
cout << "The mean mean of:" << x << " and " << y << " is " << z << endl;
cout << "Enter next pair:";
}
catch ( CBad_hmean& bg )
{
bg.mesg();
cout << "Try again." << endl;
continue;
}
catch ( CBad_gmean& hg )
{
hg.mesg();
cout << "Values used:" << hg.v1 << ", " << hg.v2 << endl;
cout << "Sorry, you don't get to play any more." << endl;
break;
}
}
d1.show();
cout << "Done." << endl;
return 0;
}
double hmean( double a, double b ) throw( CBad_hmean )
{
if ( a == -b )
{
throw CBad_hmean( a, b );
}
return ( 2.0 * a * b ) / ( a * b );
}
double gmean( double a, double b ) throw( CBad_gmean )
{
if ( a < 0 || b < 0 )
{
throw CBad_gmean( a, b );
}
return ( sqrt( a * b ) );
}
double means( double a, double b) throw( CBad_hmean, CBad_gmean )
{
double am, hm, gm;
demo d2( "found in means()" );
am = ( a + b ) / 2.0;
try
{
hm = hmean( a, b );
gm = gmean( a, b );
}
catch ( CBad_hmean& bg )
{
bg.mesg();
cout << "Caught in means()." << endl;
throw;
}
d2.show();
return ( am + hm + gm ) / 3.0;
}
在mean()可以引发CBad_hmean和CBad_gmean异常,但只有在means()中显式地引发的CBad_hmean异常被CBad_hmean异常处理程序重新引发。然而,异常规范中不仅要包含函数本身引发的异常,还应包含该函数调用的其他函数引发的异常,依此类推。因此,由于means()调用了gmean(),因此它应宣称自己还可以传递gmean()引发的异常。
如果省略异常规范中的CBad_gmean&,当gmean()引发这种异常时将是一个意料外的异常。直接传递出来在main中捕获。
6.其他异常特性
虽然throw-catch机制类似于函数参数和函数返回机制,但还是有些不同之处。其中之一是函数fun()中的返回语句将控制权返回到调用func()的函数,但throw语句将控制权向上返回到第一个这样的函数:包含能够捕获相应异常的try-catch组合。例如5的程序中,当函数hmeans()引发异常时,控制权将传递给函数means();然而,当gmean()引发异常时,控制权将向上传递到main()。
另一个不同之处是,引发异常时编译器总创建一个临时拷贝,即使异常规范和catch块中指定的是引用。
catch ( CBad_hmean& bg )
{
bg.mesg();
cout << "Try again." << endl;
continue;
}
bg指向CBad_hmean的拷贝而不是CBad_hmean&本身。这是件好事,因为函数mesg()执行完毕后,CBad_hmean将不复存在。顺便说一句,将引发异常和创建对象组合在一起将更简单:
throw CBad_hmean();
在这里使用引用有一个重要特征:基类引用可以执行派生类对象。假设有一组通过继承关联起来的异常类型,则在异常规范中只需列出一个基类引用,他将与任何派生类对象匹配。
假设有一个异常类层次结构,并要分别处理不同的异常类型,则使用基类引用将能够捕获任何异常对象,而使用派生对象只能捕获它所属类及从这个类派生而来的类的对象。引发的异常对象将被第一个与之匹配的catch块捕获。这意味着catch块的排列顺序应该与派生顺序相反。
通过正确地排列catch块的顺序,能够在如何处理异常方面有选择的余地。然而,有时候可能不知道会发生哪些异常。例如,编写了一个调用另一个函数的函数,并不知道被调用你的函数可能引发哪些异常。在这种情况下,仍能够捕获异常,即使不知道异常的类型。方法是使用省略号来表示异常类型,从而捕获任何异常:
catch(...) {}
7.exception类
c++异常的主要目的是为设计容错程序提供语音级支持,即异常使得在程序设计中包含错误处理功能更容易,以免事后采取一些严格的错误处理方式。异常的灵活性和相对方便性激励着程序员在条件允许的情况下载程序设计中加入错误处理功能。简而言之,异常是这样一种特性:类似于类,可以更变自己的编程方式。
为支持该语言,exception头文件(以前为exception.h或except.h)定义了exception类,c++可以把它用作其他异常类的基类。代码可以引发exception异常,也可以将exception类用作基类。有一个名为what()的序列成员函数,他返回一个字符串,该字符串的特征随实现而异。然后,由于这是一个虚方法,因此可以在从exception派生而来的类中重新定义它:
// testException6.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "math.h"
#include "exception"
#pragma warning( disable:4290 )
class CBad_hmean : public exception
{
public:
CBad_hmean( double a = 0, double b = 0 ) : v1( a ), v2 ( b ){}
const char* what() const;
private:
double v1;
double v2;
};
inline const char* CBad_hmean::what() const
{
return ("invalid arguments:a = -b");
}
double hmean( double a, double b ) throw( CBad_hmean );
int _tmain(int argc, _TCHAR* argv[])
{
double x, y, z;
cout << "Enter two number:";
while( cin >> x >> y )
{
try
{
z = hmean( x, y );
cout << "Harmonic mean of:" << x << " and " << y << " is " << z << endl;
}
catch ( exception& e )
{
cout << e.what() << endl;
cout << "try again" << endl;
continue;
}
}
cout << "Done." << endl;
return 0;
}
double hmean( double a, double b ) throw( CBad_hmean )
{
if ( a == -b )
{
throw CBad_hmean( a, b );
}
return ( 2.0 * a * b ) / ( a * b );
}
头文件exception提供了bad_exception,供函数unexpected()使用。
a、stdexcept异常类
头文件stdexcept定义了其他几个异常类。首先,该文件定义了logic_error和runtime_error类,他们都是以公有方式从exception派生而来的:
class logic_error
: public _XSTD exception
{ // base of all logic-error exceptions
public:
explicit __CLR_OR_THIS_CALL logic_error(const string& _Message)
: _Str(_Message)
{ // construct from message string
}
virtual __CLR_OR_THIS_CALL ~logic_error() _THROW0()
{ // destroy the object
}
virtual const char *__CLR_OR_THIS_CALL what() const _THROW0()
{ // return pointer to message string
return (_Str.c_str());
}
#if !_HAS_EXCEPTIONS
protected:
virtual void __CLR_OR_THIS_CALL _Doraise() const
{ // perform class-specific exception handling
_RAISE(*this);
}
#endif /* _HAS_EXCEPTIONS */
private:
string _Str; // the stored message string
};
// CLASS domain_error
class domain_error
: public logic_error
{ // base of all domain-error exceptions
public:
explicit __CLR_OR_THIS_CALL domain_error(const string& _Message)
: logic_error(_Message)
{ // construct from message string
}
virtual __CLR_OR_THIS_CALL ~domain_error() _THROW0()
{ // destroy the object
}
#if !_HAS_EXCEPTIONS
protected:
virtual void __CLR_OR_THIS_CALL _Doraise() const
{ // perform class-specific exception handling
_RAISE(*this);
}
#endif /* _HAS_EXCEPTIONS */
};
这些类的构造函数接受一个string对象作为参数,该参数提供了方法what()以C风格字符串方式返回的字符数据。
这两个类被用作两个派生系列的基类。异常类系列logic_error描述了典型的逻辑错误。总体而言,通过合理的编程可以避免这种错误,但实际上这些错误还是可能发生的。每个类的名称指出了他用于报告的错误类型:
domain_error
invalid_argument
length_error
out_of_bounds
用到的时候再看吧
b、bad_alloc异常和new
对于处理使用new时可能出现的内存分配问题,c++提供了两种可供选择的方式。第一种方式(一度是唯一的方式)是,让new在无法满足内存请求时返回一个空指针;第二种方式是,让new引发bad_alloc异常。头文件new(以前名为new.h)中包含了bad_alloc类的声明,他是从exception类公有派生而来的。实现智能提供一种方式,但也可以使用编译器开关或其他一些方法。
8.异常何时会迷失方向
异常被引发后,在两种情况下,会导致问题。首先,如果他是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配(在继承层次结构中,类类型与这个类及其派生类的对象匹配),否则成为意外异常。在默认情况下,这将导致程序异常终止。如果异常不是在函数中引发的(或者函数没有异常规范),则他必须被捕获。如果没有被捕获(在没有try块或没有匹配的catch块时,将出现这种情况),则异常被床位未捕获异常。在默认情况下,这将导致程序异常终止。不过,可以修改程序对意外异常和未捕获异常的反应。
未捕获异常不会导致程序立刻异常终止。相反,程序将首先调用函数terminate()。在默认情况下,terminate()调用abort()函数。可以指定terminate()应调用的函数(而不是abort())来修改terminate()的这种行为。为此,可调用set_terminate()函数。set_terminate()和terminate()都是在头文件exception中声明的:
// testException8.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "math.h"
#include "exception"
void myQuit()
{
cout << "Terminating due to uncaught exception" << endl;
exit( EXIT_FAILURE );
}
void myExceptio()
{
double a = 0.0;
throw a;
};
int _tmain(int argc, _TCHAR* argv[])
{
set_terminate( myQuit );
try
{
myExceptio();
}
catch ( int e )
{
cout << "this value is:" << e << endl;
}
cout << "Done." << endl;
return 0;
}
原则上,异常规范应包含函数调用的其他函数引发的异常。但如果函数引发了其异常规范中没有的异常,程序将调用unexpected()函数。这个函数将调用terminate(),后则在默认情况下将调用abort()。正如有一个科用于修改terminate()行为的set_terminate()函数一样,也有一个可用于修改unexpected()的行为的set_unexpected()函数。
不过,与提供给set_terminate()函数的行为相比,提供给set_unexpected()函数的行为受到更严格的限制。具体地说,unexpected_handler函数可以:
a、通过调用terminate()(默认行为)、abort()或exit()来终止程序。
b、引发异常
引发异常(第二种选择)的结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范:
如果新引发的异常与原来的异常规范匹配,则程序从那里开始进行正常处理,即需找与新引发的异常匹配的catch块。基本上,这种方法将用预期的异常取代意外异常。
如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括bad_exception类型,则程序将调用terminate()。bad_exception是从exception派生而来的。
如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了bad_exception类型,则不匹配的异常将被bad_exception异常所取代。
简而言之,如果要捕获所有的异常(不管是预期的异常还是意外异常),则可以这样做:
// testException9.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "math.h"
#include "exception"
void myUnexcepted()
{
throw bad_exception();
}
void myException() throw( double )
{
double a = 0.0;
throw a;
}
int _tmain(int argc, _TCHAR* argv[])
{
set_unexpected( myUnexcepted );
try
{
myException();
}
catch ( bad_exception* e )
{
cout << e->what() << endl;
}
return 0;
}
RTTI
RTTI是运行阶段类型识别(Runtime Type Identification)的简称。RTTI旨在为程序在运行阶段确定对象的类型提供一种标准方式。
1.用途
假设有一个类层次结构,其中的类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。这样便可以调用这样的函数:在处理一些消息后,选择一个类,并创建这种类型的对象,然后返回他的地址,而该地址可以被赋给基类指针。如何知道指针指向的是哪种对象呢?
在回答这个问题之前,先考虑为何要知道类型。可能希望调用类方法的正确版本,在这种情况下,只要该函数是类层次结构中所有成员都拥有的虚函数,则并不真正需要知道对象的类型。但派生对象可能包含不是继承而来的方法,这种情况下,只有某些类型的对象可以使用该方法。也可能是出于调试的目的,想跟踪生成的对象的类型。对于后两种情况,RTTI提供了解决方案。
2.RTTI的工作原理
c++有3个支持RTTI的元素:
a、如果可能的话dynamic_cast操作符将使用一个指向基类的指针来生成一个指向派生类的指针;否则,该操作符返回0----空指针。
b、typeid操作符返回一个指向对象的类型的值
typeid操作符使得能够确定两个对象是否为同种类型。它与sizeof有些相像,可以接受两种参数:
1)类名
2)结果为对象的表达式
typeid操作符返回一个对type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类。type_info类冲在了==和!=操作符,以便可以使用这些操作符来对类型进行比较。例如,如果pg指向的是一个Magnificent对象,则表达式
typeid( Magnificent ) == typeid( *pg )
的结果将为bool值true,否则为false。如果pg是一个空指针,程序将引发bad_typeid异常。
// testrtti.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "ctime"
class Grand
{
public:
Grand( int h = 0 ) : hold( h ) { }
virtual void Speak() const
{
cout << "I am a grand class!" << endl;
}
virtual int Value() const
{
return hold;
}
private:
int hold;
};
class Superb : public Grand
{
public:
Superb( int h = 0 ) : Grand( h ){ }
void Speak() const
{
cout << "I am a superb class!!" << endl;
}
virtual void Say() const
{
cout << "I hold the superb value of " << Value() << "!" << endl;
}
};
class Magnificent : public Superb
{
public:
Magnificent( int h = 0, char cv = 'A' ) : ch( cv ), Superb( h ){ }
void Speak() const
{
cout << "I am a magnificent class!!" << endl;
}
virtual void Say() const
{
cout << "I hold the character:" << ch << " and the integer" << Value() << "!" << endl;
}
private:
char ch;
};
Grand* GetOne();
int _tmain(int argc, _TCHAR* argv[])
{
srand( time( 0 ) );
Grand* pg;
Superb* ps;
for ( int i = 0; i < 5; i++ )
{
pg = GetOne();
cout << "Now processing type " << typeid( *pg ).name() << "." << endl;
pg->Speak();
if ( ps = dynamic_cast( pg ) )
{
ps->Say();
}
if ( typeid( Magnificent ) == typeid( *pg ) )
{
cout << "Yes, your are really magnificent." << endl;
}
}
return 0;
}
Grand* GetOne()
{
Grand* p;
switch ( rand() % 3 )
{
case 0:
p = new Grand( rand() % 100 ); break;
case 1:
p = new Superb( rand() % 100 ); break;
case 2:
p = new Magnificent( rand() % 100, 'A' + rand() % 26 ); break;
}
return p;
}
c、type_info结构存储了有关特定类型的信息
只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针。
类型转换操作符
1.dynamic_cast
该操作符的用途是,使得能够在类层次结构中进行向上转换(由于is-a关系,这样的类型转换是安全的),而不允许其他转换
2.const_cast
const_cast操作符用于执行只有一种用途的类型转换,即改变值为const或volatile,其句法与dynamic_cast操作符相同
如果类型的其他方面也被修改,则上述类型转换将出错。也就是说,除了const或volatile特征(有或无)可以不同外,type_name和expression的类型必须相同。假设High和Low是两个类:
High bar;
const High* pbar = &bar;
...
High* pb = const_cast
const Low* pl = const_cast
第一个类型转换使得*pb成为一个可用于修改bar对象值的指针,它删除const标签。第二个类型转换是非法的,因为它同时尝试将类型从const Hight*为const Low*。
提供该操作符的原因是,有时候可能需要这样一个值,他在大多是时候是常量,而有时又是可以修改的。在这种亲看下,可以将这个值声明为const,并在需要修改它的时候,使用const_cast。这也可以通过通用类型转换来实现,但通用转换也可能同时改变类型。
// testconstcast.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
void change( const int* pt, int n )
{
int* pc;
if ( n < 0 )
{
pc = const_cast( pt );
*pc = 100;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int popl = 38383;
const int pop2 = 2000;
cout << "pop1, pop2:" << popl << ", " << pop2 << endl;
change( &popl, -1 );
change( &pop2, -1 );
cout << "pop1, pop2:" << popl << ", " << pop2 << endl;
return 0;
}
在这个程序中,调用change()时,修改了pop1,但没有修改pop2(这里使用的是编译器生成pop2的一个临时拷贝,并将地址赋给pc,但正如前面指出的,c++标准指出,在这种情况下,上述行为是不确定的)
3.static_cast
static_cast操作符的句法与其他类型转换操作符相同,static_cast< type_name >( expression )仅当type_name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时,上述转换才是合法的,否则将出错。假设High是Low的基类,而Pond是一个无关的类,则从High到Low的转换、从Low到High的转换都是合法的,而从Low到Pond的转换是不允许的:
High bar;
Low blow;
...
High* pb = static_cast
Low* p1 = static_cast
Pond* pmer = static_cast
第一种转换是合法的,因为向上转换可以显示地进行。第二种转换是从基类指针到派生类指针,在不进行显示类型转换的情况下,将无法进行。但由于无需进行类型转换,便可以进行另一个方向的类型转换,因此使用static_cast来进行向下转换是合法的。
4.reinterpret_cast
reinterpret_cast操作符用于天生危险的类型转换,他不允许删除const,但会后自行其他令人生厌的操作。有时程序员必须做一些依赖于实现的、令人生厌的操作,使用reinterpret_cast操作符可以简化对这种行为的跟踪工作。
struct dat { short a; short b };
long value = 0xA224B118;
dat* pd = reinterpret_cast< dat* >( &value );
cout << pd->a;
通常,这样的转换适用于依赖实现的底层编程技术,是不可抑制的。然而reinterpret_cast操作符并不支持所有的类型转换。例如,可以将指针类型转换为足以存储指针类型表示的转型,但不能将指针转换为更小的整型或浮点型。另一个限制是,不能将函数指针转换为数据指针,反之亦然。
在c++中,普通类型转换也收到限制。基本上,可以执行其他类型转换可执行的操作,加上一些组合,如static_cast或reinterpret_cast后跟const_cast,但不能执行其他转换。因此:
char ch = char ( &d );
在c语言中式允许的,但在c++中通常不允许,因为对于大多数c++实现,char类型都太小,不能存储指针。
string对象
1.string版本的getline()函数从输入中读取字符,并将其存储到模板string中,知道发生下列3中情况之一:
a、达到文件尾,在这种情况下,输入流的eofbit将被设置,这意味着方法fail()和eof()都将会发true
b、遇到分界字符(默认为\n),在这种情况下,将分界字符从输入流中删除,单步存储他
c、读取的字符数达到最大允许值(string::npos和可供分配的内存字节数中较小的一个),在这种情况下,将设置输入流的failbit,这意味着方法fail()将返回true
string版本的operator>>()函数的行为与此类似,只是它不断读取,知道遇到空白字符将其留在输入对了中,而不是不断读取,直到遇到分解字符并将其丢弃。空白字符指的是空格、换行符和制表符,更普遍的说,是任何将其作为参数来调用isspace()时,该函数返回true的字符
// teststring.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
#include "fstream"
int _tmain(int argc, _TCHAR* argv[])
{
ifstream fin;
fin.open( "tobuy.txt" );
if ( false == fin.is_open() )
{
cerr << "Can't open file . Done." << endl;
exit( EXIT_FAILURE );
}
string item;
int count = 0;
getline( fin, item, ':' );
while ( fin )
{
++count;
cout << count << ":" << item << endl;
getline( fin, item, ':');
}
cout << "Done." << endl;
fin.close();
return 0;
}
这是一个从文件中读取字符串的简短范例,他假设文件中包含冒号字符分隔的字符串,并使用指定分界符的getline()方法。然后,显示字符串并给他们编号,每个字符串占一行。
这里值得注意的是,指定为分界字符后,换行符将被视为常规字符。
2.使用字符串
string除了可以获取长度和比较之外,还提供了相关的方法:rfind()、find_first_of()等,他们的重载函数特征标都与find()方法相同。rfind()方法查找子字符串或字符最后一次出现的位置。
3.length()与size()
这两个函数完成相同的任务,length()成员来自较早版本的string类,而size()则是为提供STL兼容性而添加的
4.string的自动调整大小功能
下面的程序中,将一个字母附加到字符串末尾时将发生什么呢?不能仅仅将已有的字符串加大,因为相邻的内存可能被占用了。因此,可能需要分配一个新的内存块,并将原来的内容复制到新的内存单元中。如果执行大量这样的操作,效率将非常低,因此很多c++实现分配一个比实际字符串大的内存块,为字符串提供了增大空间。然后,如果字符串不断增大,超过了内存块的大小,程序将分配一个大小为原来两倍的新内存块,以提供足够的增大空间,避免不断地分配新的内存块。方法capacity()返回当前分配给字符串的内存块的大小,而reserve()方法能够请求内存块的最小长度
// teststring2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
int _tmain(int argc, _TCHAR* argv[])
{
string szEmpty;
string szSmall = "bit";
string szLarger = "Elephants are a girl's best friend";
cout << "Size:" << endl;
cout << "\tempty:" << szEmpty.size() << endl;
cout << "\tsmall:" << szSmall.size() << endl;
cout << "\tlarger:" << szLarger.size() << endl;
cout << "Capacities:" << endl;
cout << "\tempty:" << szEmpty.capacity() << endl;
cout << "\tsmall:" << szSmall.capacity() << endl;
cout << "\tlarger:" << szLarger.capacity() << endl;
szEmpty.reserve( 50 );
cout << "Capacities after empty.reserve( 50 ):" << szEmpty.capacity() << endl;
return 0;
}
5.string转到C风格字符串
如果现在有string对象,但需要C风格字符串,比如,可能想打开一个其名称存储在string对象中的文库:
string fileName;
cout << "Enter file name:";
cin >> fileName;
ofstream fout;
不幸的是,open()方法要求使用一个C风格字符串作为参数;幸运的是,c_str()方法返回一个指向C风格字符串的指针,该C风格字符串的内容与用于调用c_str()方的string对象相同。因此可以这样做:
fout.open( fileName.c_str() );
6.重载C函数以使用string对象
可以使用重载的==操作符来比较string对象。不过,在某些情况下,==操作符执行相等比较时的区分大小写特征会带来问题。通常,比较两个字符串是否相等时不区分大小写。例如,程序可能将用户的输入与常数值进行比较,而用户输入时的大小写可能与常量不完全相同。可以这么做,很多C库都提供了stricmp()或_stricmp()函数,能够执行不区分大小写的比较(不过,该函数不属于C标准,因此不一定普通适用)。通过创建该函数的重载版本,可以避免上述问题
// teststring3.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
inline bool stricmp( const string& strA, const string& strB )
{
return stricmp( strA.c_str(), strB.c_str() ) == 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
string strA;
cin >> strA;
string strB = "apple";
cout << stricmp( strA, strB ) << endl;
cout << "Done." << endl;
return 0;
}
auto_ptr类
auto_ptr是一个模板类,用于管理动态内存分配的用法。常规指针在函数结束或者程序结束后,并不能自动释放所占用的内存,如果他是对象,则可以在对象过期时,让他的析构函数删除被指向的内存。这正是auto_ptr背后的思想
1.使用auto_ptr
auto_ptr模板定义了类似指针的对象,可以就爱那个new获得(直接或间接)的地址赋给这种对象。当auto_ptr对象过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给auto_ptr对象后,无须记住稍后释放这些内存。在auto_ptr对象过期时,这些内存将自动被释放。
要创建auto_ptr对象,必须包含头文件memory,该文件包括auto_ptr模板。然后使用通常的模板句法来实例化所需类型的指针。
auto_ptr是一种智能指针(samrt pointer)----类似于指针,但特性比指针更多。auto_ptr类被定义为在很多方面与常规指针类似。例如,如果ps是一个auto_ptr,则可以对他执行解除引用操作( *ps )和递增操作( ++ps ),用它来访问结构成员( ps->puffIndex ),将它赋给指向相同类型的常规指针。还可以将auto_ptr赋给另一个同类型的auto_ptr,但将引起一个问题,请看2。
2.有关auto_ptr的注意事项
auto_ptr并不是万灵丹。例如,下面的代码:
auto_ptr
对于new和new[],必须相应地使用delete和delete[]。auto_ptr模板使用的是delete,而不是delete[],因此它只能与new一起使用,而不能与new[]一起使用。没有适用于动态数组的auto_ptr等同位语。可以复制头文件memory中的auto_ptr模板,将它重命名为auto_arr_ptr,然后对其进行修改,使之使用delete[],而不是delete。在这种情况下,可能希望添加对[]操作符的支持。
3.有关auto_ptr赋值的情况
auto_ptr
auto_ptr
vocation = ps;
上面的代码,如果ps和vocation是常规指针,则两个指针将指向同一个string对象,其中的一个是另一个的拷贝。这是不可接受的,因为ps和vocation都过期时,程序将试图删除同一个对象两次。要避免这种问题,方法有很多种:
a、定义赋值操作符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的拷贝。
b、建立所有权概念,对于特定的对象,只能有一个智能指针可拥有它。智能指针的构造函数只能删除该智能指针拥有的对象。并使复制操作转让所有权。这就是用于auto_ptr的策略。
c、创建智能更高的指针,跟踪引用特定对象的智能指针数。这被称为引用计数。例如,赋值时,计数将+1;指针过期时,计数将-1.仅当最后一个指针过期时,delete才被调用。
当然,同样的策略也适用于赋值构造函数
智能指针
c++库中auto_ptr对象是一种只能指针。智能指针是这样一种类,即其对象的特征类似于指针。例如,只能指针可以存储new分配的内存地址,也可以被解除引用。引用智能指针是一个类对象,因此他可以修改和扩充简单指针的行为。例如,智能指针可以建立引用计数,这样多个对象可共享由智能指针跟踪的同一值。当使用该值的对象数为0时,智能指针将删除这个值。智能指针可以提供内存的使用效率,帮助防止内存泄露,但并不要求用户熟悉新的编程技术。
函数对象
很多STL算法都使用函数对象----也叫函数符(functor)。函数符是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了()操作符的类对象(即定义了函数operator()()的类)。
在STL中的for_each( x.beging(), x.end(), ShowReview )函数的第三个参数是常规函数,也可以使函数符。实际上,这提出了一个问题:如何声明第三个参数呢?不能把它声明为函数指针。STL通过使用模板解决了这个问题。for_each()的圆形看上去就像这样:
template< class InputIterator, class Function >
Function for_each( InputIterator first, InputIterator last, Function f );
ShowReview()的原型如下:
vodi ShowReview( const Review7 )
这样标识符ShowReview的类型键位void( * )( const Review& ),这也是赋给模板承诺书Function的类型。对于不同的函数调用,Function参数可以表示具有重载的()操作符的类类型。最终for_each()代码将具有一个使用f(...)的表达式。
// testforeach.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "string"
#include "vector"
#include "algorithm"
void show( const int a )
{
cout << "current value is:" << a << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector test;
for ( int i = 0; i < 10; i++ )
{
test.push_back( i );
}
for_each( test.begin(), test.end(), show );
return 0;
}
流、缓冲区和iostream文件
管理流和缓冲区的工作有点复杂,不过iostream文件中包含了一些专门设计用来实现、管理流和缓冲区的类。最新版本的c++ i/o定义了一些类模板,以支持char和wchar_t数据。通过使用typedef攻击,c++使得这些模板char具体化能够模仿传统的非模板i/o实现。下面是其中的一些类
a、streambuf类为缓冲区提供了内存,并提供了用于填充缓存区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法
b、ios_base类表示流的一般特征,如是否可读取、是二进制流还是文本流等
c、ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员
d、ostream类是从ios类派生而来的,提供了输出方法
e、isteram类是从ios类派生而来的,提供了输入方法
f、iostream类是基于isteram和ostream类的,因此继承了输入方法和输出方法
要使用这些工具,必须使用适当的类对象。例如,使用ostream对象(如cout)来处理输出。创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来,同时使得能够使用类成员函数。
c++的iostream类库管理了很多细节。例如,在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流,4个用于宽字符流):
a、cin对象对应于标准输入流。在默认情况下,这个流被关联到标准输入设备(通常为键盘)。wcin对象与此类似,但处理的是wchar_t类型
b、cout对象与标准输出流相对应。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。wcout
c、cerr对象与标准错误流相对应,可用于显示错误信息。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流没有被缓存,这意味着信息将被直接发送给屏幕,而不会等到缓存区填满或新的换行符。wcerr
d、clog对象也对应着标准错误流。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流被缓存。wclog
使用cout进行输出
要在屏幕上显示数字-2.34,需要将5个字符(-、2、.、3和4),而不是这个值的64位内部浮点表示发送到屏幕上。因此,ostream类最重要的任务之一是将数值类型(如int或float)转换为以文本形式表示的字符流。也就是说,ostream类将数据内部表示(二进制位模式)转换为由字符字节组成的输出流。为执行这些转换任务,ostream类提供了多个类方法。
1.其他ostream方法----put()
除了各种operator<<()函数外,ostream类还提供了put()方法和write()方法, 前者用于显示字符,后者用于显示字符串。
最初,put()方法的原型为:
ostream& put( char );
当前标准与此相同,但被模板化,以适用于wchar_t。可以用类方法表示法来调用它:
cout.put('A');
其中,cout是调用方法的对象,put()是类成员函数。和<<操作符函数一样,该函数也返回一个指向调用对象的引用,因此可以用它将拼接除数:
cout.put('t').puit('a');
函数调用cout.put('t')返回cout,cout然后被用作puit('a')调用的调用对象
// testforeach.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << 'w' << endl;
cout.put( 'A' ) << endl;
cout.put( 65 ) << endl;
cout.put( 97 ) << endl;
return 0;
}
2.write()
write()方法显示整个字符串,其模板原型:
basic_ostream< charT, traits >& write( const char_type* s, streamsize n );
write()的第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符。使用cout调用write()时,将调用char具体化,因此返回类型为ostream&。
// testforeach.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
const char* state = "zeng2";
int len = strlen( state );
cout << "Increasing loop index:" << endl;
int i;
for ( i = 0; i <= len; i++ )
{
cout.write( state, i );
cout << endl;
}
cout << "Decreasing loop index:" << endl;
for ( i = len; i > 0; i-- )
{
cout.write( state, i ) << endl;
}
cout << "Exceeding string length:" << endl;
cout.write( state, len + 5 ) << endl;
return 0;
}
write()方法并不会在遇到空字符时自动停止打印字符,而只是打印指定数目的字符,即使超过了字符串的边界!
3.修改显示时使用的进制系统
ostream类是从ios类派生而来的,而后者是从ios_base类派生而来的。ios_base类存储了描述格式状态的信息。例如,一个类成员中某些位决定了使用的计数系统,而另一个成员决定了字段宽度。通过使用控制符(manipulator),可以控制显示整数时使用的计数系统。通过使用ios_base的成员函数,可以控制字段宽度和小数位数。由于ios_base类是ostream的间接基类,因此可以将其方法用于ostream对象,如cout
例如hex(cout);将cout对象的计数系统格式状态设置为十六进制。完成上述设置后,程序将以十六进制形式打印整数值,知道将格式状态设置为其他选项为止。
虽然控制符实际上市函数,但他们通常的使用方式为:
cout << hex;
// testcout.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Enter an integer:";
int n;
cin >> n;
cout << "n n * n" << endl;
cout << n << " " << n * n << "( decimal )" << endl;
// set to hex mode
cout << hex;
cout << n << " ";
cout << n * n << "( hexadecimal )" << endl;
// set to octal mode
cout << oct << n << " " << n * n << "( octal )" << endl;
// alternative way to call a manipulator
dec( cout );
cout << n << " " << n * n << "( decimal )" << endl;
return 0;
}
4.调整字段宽度
int width();
int width( int i );
第一种格式返回字段宽度的当前设置;第二种格式将字段宽度设置为i个空格,并返回以前的字段宽度值。这使得能够保存以前的值,以便以后恢复宽度值时使用。
width()方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值。例如:
cout << '#';
cout.width( 12 );
cout << 12 << "#" << 24 << "#" << endl;
由于width()是成员函数,因此必须使用对象(这里为cout)来调用它。
// testcout2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int w = cout.width( 30 );
cout << "default field width = " << w << ":" << endl;
cout.width( 5 );
cout << "N" << ':';
cout.width( 8 );
cout << "N * N" << ':';
for ( long i = 1; i <= 100; i *= 10 )
{
cout.width( 5 );
cout << i << ':';
cout.width( 8 );
cout << "i * i" << ':' << endl;
}
return 0;
}
5.填充字符
在默认情况下,cout用空格填充字段中未被使用的部分,可以用fill()成员函数来改变填充字符。例如:cout.fill( '*' )
// testcout3.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout.fill( '*' );
const char* staff[ 2 ] = { "Waldo Whipsnade", "Wilmarie Wooper" };
long bonus[ 2 ] = { 900, 1350 };
for ( int i = 0; i < 2; i++ )
{
cout << staff[ i ] << ":$";
cout.width( 7 );
cout << bonus[ i ] << endl;
}
return 0;
}
6.设置浮点数的显示精度
浮点数精度的含义取决于输出模式。在默认模式下,它指的是显示的总位数。在定点模式和科学模式下,精度指的是小数点后面的位数。precision()成员函数使得能够选择其他值。例如:
cout.precision( 2 );
将cout的精度设置为2.和width()的情况不同,但与fill()类似,新的精度设置将一直有效,知道被创新设置
// testcout4.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
float pricel = 20.40;
float pricel2 = 1.9 + 8.0 / 9.0;
cout << "\Furry Friends\" is $" << pricel << "!" << endl;
cout << "\Fiery Friends\" is $" << pricel2 << "!" << endl;
cout.precision( 2 );
cout << "\Furry Friends\" is $" << pricel << "!" << endl;
cout << "\Fiery Friends\" is $" << pricel2 << "!" << endl;
return 0;
}
7.打印末尾的0和小数点
对于有些输出(如价格或栏中的数字),保留末尾的0将更加美观。iostream系列类没有提供专门用于完成这项任务的函数,但ios_base类提供了一个setf()函数(用于set标记),能够控制多种格式化特征。这个类还定义了多个常量,可用作该函数的参数。例如,下面的调用:
cout.setf( ios_base::showpoint );
showpoint仅仅是ios_base类中定义的一个常量,如果使用默认精度(6位)时,cout不会将2.00显示为2,而是将它显示为2.000000,但是结合6中的precision()成员函数设定浮点数的显示精度,就可以更改这个默认精度
// testcout5.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
double a = 2.0f;
cout << "this double variable:" << a << endl;
cout.precision( 3 );
cout.setf( ios_base::showpoint );
cout << "this double variable:" << a << endl;
return 0;
}
8.在谈setf()
setf()方法控制了小数点被显示时其他几个格式选项。ios_base类有一个受保护的数据成员,其中的各位(这里叫做标记)分别控制着格式化的各个方面,例如计数系统、是否显示末尾的0等。打开一个标记成为设置标记(或位),并意味着相应的位被设置为1.位标记是编程开关,相当于设置DIP开关以配置计算机硬件。例如,hex、dec和oct控制符调整控制计数系统的3个标记位。
ios_base::boolalpha----输入和输出bool值,可以为true或false
ios_base::showbase----对于输出,使用c++基数前缀(0,0x)
ios_base::showpoint----显示末尾的小数点
ios_base::uppercase----对于16进制输出,使用大写字母,E表示法
ios_base::showpos----在正数前面加上+
当做了这些修改之后,直到被覆盖为止。
9.标准控制符
使用setf()不是进行格式化的、对用户最为友好的方法,c++提供了多个控制符,能够调用setf(),并自动提供正确的参数。比如前面的dec、hex和oct,这些控制符的工作方式都与hex相似,例如:
cou << left << fixed;
打开左对齐和定点选项
10.iomanip头文件
使用iostream工具来设置一些格式值(如字段宽度)不太方便。为简化工作,c++在iomanip头文件中提供了其他一些控制符,它们能够提供前面的服务,但表示起来更方便。3个最常用的龙支付分别是setprecision()、setfill()和setw(),它们分别用来设置精度、填充字符和字段宽度。与前面讨论的控制符不同的是,这3个控制符带参数。setprecision()控制符接受一个指定精度的整数参数;setfill()控制符接受一个指定填充字符的char参数;setw()控制符接受一个指定字段宽度的整数参数。由于它们都是控制符,因此可以用cout语句连接起来。这样,setw()控制符在显示多列值时尤其方便。
// testcout6.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "iomanip"
#include "math.h"
int _tmain(int argc, _TCHAR* argv[])
{
// use new standard manipulators
cout << showpoint << fixed << right;
// use iomanip mainpulators
cout << setw( 6 ) << "N" << setw( 14 ) << "square root" << setw( 15 ) << "fourth root" << endl;
double root;
for ( int i = 0; i <= 100; i++ )
{
root = sqrt( double( i ) );
cout << setw( 6 ) << setfill( '.' ) << i << setfill( ' ' )
<< setw( 12 ) << setprecision( 3 ) << root
<< setw( 14 ) << setprecision( 4 ) << sqrt( root ) << endl;
}
return 0;
}
该程序生成的是几乎完全对齐的列。
cin>>如何检查输入
不同版本的抽取操作符查看输入流的方法是相同的。他们跳过空白(空格、换行符和制表符),直到遇到非空白字符。即使对于单字符模式(参数类型为char、unsigned char或signed char),情况也是如此,但对于c语言的字符输入函数,情况并非如此。在单字符模式下,>>操作符将读取该字符,将它放置到指定的位置。在其他模式下,>>操作符将读取一个指定类型的数据。也就是说,他读取从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容。
// testcin1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Enter number:";
int sum = 0;
int input;
while ( cin >> input )
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
return 0;
}
输入有时可能没有满足程序的期望。例如,假设输入的是Zcar,那么cin将不会获取输入的值
如果输入的是
Enter numbers:200 10 -50 -123Z 60
由于输入被缓冲。因此通过键盘输入的第二行在用户按下回车键之前,不会被发送给程序。但是,循环在字符Z处停止了对输入的处理,因此他不与任何一种浮点格式匹配。输入与预期格式不匹配发过来将导致表达式cin >> input的结果为false,因此while循环被终止
流状态
1.设置流状态
clear()和setstate()很相似。他们都重置状态,但采取的方式不同。clear()方法将状态设置为他的参数。
clear()
将使用默认参数0,这将清除全部3个状态位( eofbit、badbit和failbit ),同样,下面的调用:
clear( eofbit );
将状态设置为eofbit;也就是说eofbit将被设置,另外两个状态为被清除。
而setstate()方法只影响其参数中已设置的位。因此下面的调用:
setstate( eofbit );
将设置eofbit,而不会影响其他为。因此,如果failbit被设置,则仍将被设置
2.I/O异常
假设某个输入函数设置了eofbit,在默认情况下,不会导致异常被引发。但可以使用exceptions()方法来控制异常如何被处理。
exceptions()方法返回一个位字段,他包含3位,分别对应于eofbit、badbit和failbit。修改流状态涉及到clear()或setstate(),这都将使用clear()。修改流状态后,clear()方法将当前的流状态与exceptions()返回的值进行比较。如果在返回值中某一位被设置,而当前状态中的对应为也被设置,则clear()将引发ios_base::failure异常。如果两个值都设置了badbit,将发生这种情况。如果exceptions()返回goodbit,则不会引发任何异常。ios_base::failure异常是从exception类派生而来的,因此包含一个what()方法
// testcin2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "exception"
int _tmain(int argc, _TCHAR* argv[])
{
// have failbit cause an exception to be throw
cin.exceptions( ios_base::failbit );
cout << "Enter numbers:";
int sum = 0;
int input;
try
{
while ( cin >> input )
{
sum += input;
}
}
catch ( ios_base::failure* bf )
{
cout << bf->what() << endl;
cout << "O! the horror! " << endl;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
return 0;
}
其他的istream类方法
1.单字符输入
在使用char参数或没有参数的情况下,get()方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get( char& ch )版本将输入字符赋给其参数,而get( void )版本将输入字符转换为整型(通常是int),并将其返回。
get( void )一般用在文本读取中比如:while( ( ch = cin.get() ) != EOF ){}
get( char& ch )一般用在输入中
2.采用哪种单字符输入形式
假设可以选择>>、get( char& ch )或get( void ),应使用哪一个呢?首先,应确定是否希望跳过空白。如果跳过空白更方便,则使用抽取操作符>>。例如,提供菜单选项时,跳过空白更为方便:
如果希望程序检查每个字符,请使用get()方法,例如,计算字数的程序可以使用空格来判断单纯合适结束。在get()方法中,get( char& ch )的接口更加。
get( void )的主要优点是:他与标准c语言中的getchar()函数极其类似,这意味着可以用过包含iostream(而不是stdil.h),并用cin.get()替换所有的getchar(),用cout.put( ch )替换所有的putchar( ch ),来讲c程序转换为c++程序
3.字符串输入:getline()、get()和ignore()
getline()成员函数和get()的字符串读取版本都读取字符串,他们的函数特征相同:
istream& get( char*, int, char );
istream& get( char*, int );
istream& getline( char*, int, char );
istream& getline( char*, int );
第一个参数是用于放置输入字符串的内存单元的地址。第二个参数比要读取的最大字符数大1(额外的一个字符用于存储结尾的空字符,以便将输入存储为一个字符串)。第三个参数指定用作分界符的字符,只有两个参数的版本将换行符用作分界符。上述函数都在读取最大数目的字符或遇到换行符后为止。例如:
char line[ 50 ];
cin.get( line, 50 );
将字符输入读取到字符数组line中。cin.get()函数将在到底第49个字符或遇到换行符(默认情况)后停止将输入读取到数组中。get()和geiline()之间的主要区别在于,get()将换行符留在输入流中,这样接下来的输入操作首先看到的将是换行符,而getline()抽取并丢弃输入流中的换行符
// testget.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
const int Limit = 255;
int _tmain(int argc, _TCHAR* argv[])
{
char input[ Limit ];
cout << "Enter a string for getline() processing:" << endl;
cin.getline( input, Limit, '#' );
cout << "Here is your input:" << endl;
cout << input << "\nDone with phase 1" << endl;
char ch;
cin.get( ch );
cout << "The next input character is:" << ch << endl;
if ( '\n' != ch )
{
cin.ignore( Limit, '\n' );
}
cout << "Enter a string for get() processing:" << endl;
cin.get( input, Limit, '#' );
cout << "Here is your input:" << endl;
cout << input << "\nDone with phase 1" << endl;
cin.get( ch );
cout << "The next input character is:" << ch << endl;
return 0;
}
get()和getline()之间的主要区别在于,get()将换行符留在输入流中,这样接下来的输入操作首先看到的是换行符,而getline()抽取并丢弃输入流中的换行符
4.read()、peek()、gcount()和putback()
read()函数读取指定数目的字符,并将他们存储在指定的位置中
char gross[ 144 ];
cin.read( gross, 144 );
从标准输入中读取144个字符,并将他们存储在gross数组中。与getline()和get()不同的是,read()不会在输入后加上空值字符,因此不能就爱那个输入转换为字符串。read()方法不是专为键盘输入设计的,它最常与ostream write()函数结合使用,来完成文件输入和输出。
peek()函数返回输入中的下一个字符,但不抽取输入流中的字符。也就是说,它使得能够查看下一个字符。假设要读取输入,直到遇到换行符或据点,则可以用peek()查看输入流中的下一个字符,以此来判断是否继续读取:
char great_input[ 80 ];
char ch;
int i = 0;
while( ( ch = cin.peek() != '.' && ch != '\n' )
cin.get( great_input[ i++ ] );
great_input[ i ] = '\0';
文件输入和输出
1.简单的文件I/O
c++ I/O类软件包处理文件输入和输出的方式与处理标准输入和输出的方式非常相似。要写入文件,需要创建一个ofstream对象,并使用ostream方法,如<<插入操作符或write()。要读取文件,需要创建一个ifstream对象,并使用istream方法,如>>抽取操作符或get()。不过,与标准输入和输出相比,文件的管理更为浮躁。例如,必须将新打开的文件和流关联起来。可以以只读模式、只写模式或读写模式打开文件。写文件时,可能想创建新文件、取代就文件或添加到旧文件中,还可能想在文件中来回移动。为帮助处理这些任务,c++在头文件fstream中定义了多个新类,其中包括用于文件输入的ifstream类和用于文件输出的ofstream类。c++还定义了一个fstream类,用于同步文件I/O,这些类都是从头文件iostream中的类派生而来的。
注意:以默认模式打开文件进行输出将自动把文件的长度截短为0,这相当于删除已有的内容
// testfileoperator.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "fstream"
#include "string"
int _tmain(int argc, _TCHAR* argv[])
{
string fileName;
cout << "Enter name for new file:";
cin >> fileName;
// create output stream object for new file call it fout
ofstream fout( fileName.c_str() );
fout << "For you eyes only! " << endl;
cout << "Enter your secret number:";
float secret;
cin >> secret;
fout << "Your secret number is:" << secret << endl;
fout.close();
// create input stream object for new file and call it fin
ifstream fin( fileName.c_str() );
cout << "\nHere are the contents of:" << fileName << endl;
char ch;
while ( fin.get( ch ) )
{
cout << ch;
}
cout << "Done." << endl;
fin.close();
return 0;
}
2.流状态检查和is_open()
在试图打开一个不存在的文件进行输入时,将设置failbit位。但是c++提供了一种更好的检查文件是否被打开的方法----is_open()方法:
if( !fin.is_open() )
{...}
3.打开多个文件
程序可能需要打开多个文件。打开多个文件的策略取决于他们将被如何使用。如果需要同时打开两个文件,则必须为每个文件创建一个流。例如,将两个排序后的文件拼接成第三个文件的程序,需要为两个输入文件创建两个ifstream对象,并为输出文件创建一个ofstream对象。可以同门师打开的文件数取决于操作系统,但通常为20个左右。
4.文件模式
文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时(无论是使用文件名初始化文件流对象,还是使用open()方法),都可以提供指定文件模式的第二个参数:
ifstream fin( "test.dat", model1 );
ofstream fout();
fout.open( "test.dat", model2 );
ios_base类定义了一个openmode类型,用于表示模式;与fmtflags和iostate类型一样,他也是一种bitmask类型。可以选择ios_base类中定义的多个变量来指定模式。
ios_base::in----打开文件,以便读取
ios_base::out----打开文件,以便写入
ios_base::ate----打开文件,并移到文件尾
ios_base::app----追加到文件尾
ios_base::trun----如果文件存在,则截短文件
ios_base::binary----二进制文件
// testfileoperator1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
using namespace std;
#include "fstream"
#include "string"
const char* fileName = "test.dat";
int _tmain(int argc, _TCHAR* argv[])
{
char ch;
// show initial contens
ifstream fin;
fin.open( fileName );
if ( fin.is_open() )
{
cout << "Here are the current contents of the " << fileName << endl;
while ( fin.get(ch) )
{
cout << ch;
}
fin.close();
}
cout << "In the first fin.is_open()" << endl;
// add new names
ofstream fout( fileName, ios::out | ios::app );
if ( !fout.is_open() )
{
cerr << "Can't open " << fileName << "file for output." << endl;
exit( EXIT_FAILURE );
}
cout << "Enter guest names(enter a blank line to quit:)" << endl;
string name;
while ( getline( cin, name ) && name.size() > 0 )
{
fout << name << endl;
}
fout.close();
// show revesed file
fin.clear();
fin.open( fileName );
if ( fin.is_open() )
{
cout << "Here are the current contents of the " << fileName << endl;
while ( fin.get(ch) )
{
cout << ch;
}
fin.close();
}
cout << "In the second fin.is_open()" << endl;
cout << "Done." << endl;
return 0;
}