处理第一个问题:
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修改其所指对象的值。
不能给一个常量赋值,常量指针所指的对象都不能修改:
- <span style="font-size:18px;">#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;
- }</span>
2)const指针
int iValue = 1;
int *const pNumber = &iValue;
此时,可以从右向左把这个定义语句读作"pNumber"是指向int型对象的const指针。与其他const变量一样,const指针的值也不能修改,这就意味着不能使pNumber指向其他对象。任何企图给const指针赋值的行为(即使pNumber赋回同样的值)都会导致编译错误。
pNumber = pNumber;
与任何const量一样,const指针也必须在定义的时候进行初始化。
指针本身是const的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
- <span style="font-size:18px;">#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;
- }</span>
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的头文件:<string.h>
C++的头文件:<cstring>
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那样)
- <span style="font-size:18px;">#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;
- }</span>
读取文件
文件输出和文件输入极其类似
使用文件输出的步骤:
1)包含头文件fstream
2)创建ifstream对象inFile
3)inFile.is_open()
4)getline( inFile, strOut );
- <span style="font-size:18px;">#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<string> 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;
- }
- </span>
如果试图打开一个不存在的文件,这将导致后面使用ifstream对象进行输入时失败。检查文件是否被成功打开的首先方法是使用方法is_open(),为此,可以用类似的代码:
- <span style="font-size:18px;">inFile.open( strFileName );
- if ( !inFile.is_open() )
- {
- cout << "could not open the datafile" << strFileName << endl;
- cout << "Program terinating.\n";
- exit( EXIT_FAILURE );
- }</span>
如果文件被成功地打开,方法is_open()将返回true;因此如果文件没有被打开,表达式!inFile.is_open()将为true。函数exit()的原型在头文件cstdlib中定义,在该头文件中,还定义了一个用于同操作系统通信的参数值EXIT_FAILURE。函数exit()终止程序。
使用good()方法,该方法在没有发生任何错误时返回true
- <span style="font-size:18px;"> while ( inFile.good() )
- {
- getline( inFile, strOut );
- vecStr.push_back( strOut );
- }
- </span>
如果愿意,可以使用其它方法来确定循环种终止的真正原因:
- <span style="font-size:18px;"> if ( inFile.eof() )
- {
- cout << "End of the file reached.\n";
- }
- else if ( inFile.fail() )
- {
- cout << "Input terminated by data mismatch.\n";
- }
- else
- {
- cout << "Input terminated fo
- r Unknown reason.\n";
- }
- </span>
数组作为函数参数
当数组作为函数参数时,将数组的地址作为参数可以节省复制整个数组所需的时机和内存
验证一下,函数中数组的地址与外层调用时,函数的地址是一样的,并且在函数中
- <span style="font-size:18px;">#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;
- }</span>
指针和const
将const用于指针有一些很微妙的地方,可以用两种不同的方式将const关键字用于指针。
1)
第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。
- <span style="font-size:18px;">#include "stdafx.h"
- int _tmain(int argc, _TCHAR* argv[])
- {
- const int iValue = 10;
- int* pIValue = &iValue;
- *pIValue = 11;
- return 0;
- }</span>
2)
第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置。
- <span style="font-size:18px;">#include "stdafx.h"
- int _tmain(int argc, _TCHAR* argv[])
- {
- int iValue = 10;
- const int* pIValue = &iValue;
- *pIValue = 11;
- return 0;
- }</span>
3)有这种情况:
- <span style="font-size:18px;">#include "stdafx.h"
- int _tmain(int argc, _TCHAR* argv[])
- {
- int iAge = 39;
- int* pIAge = &iAge;
- const int* pIValue = pIAge;
- *pIValue = 10;
- return 0;
- }</span>
这是情况变得很复杂。假如涉及的是一级间接关系,则将非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)指向常量的常指针
- <span style="font-size:18px;">#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;
- }</span>
这时既不能修改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 ]
- <span style="font-size:18px;">#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;
- }</span>
递归
1)
仅包含一个递归的调用
- <span style="font-size:18px;">#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;
- }
- </span>
此代码起警示作用:
- <span style="font-size:18px;">#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;
- }</span>
2)
包含多个递归调用的递归,在需要将一项工作不断分为两项较小的、类似的工作时,递归非常有用。
- <span style="font-size:18px;">#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;
- }
- </span>
在subdivide()函数,使用便利level来控制递归层。subdivide()函数调用自己两次,一次针对左半部分,另一次针对右半部分。也就是说,调用一次导致两个调用,然后导致4个调用,在导致8个调用,以此类推。这就是6层调用能够填充64个元素的原因pow( 2, 6 )=64。这将不断导致函数调用数(以及存储的变量数)翻倍,因此如果要求的递归层次很多,这种递归方式将是一种糟糕的选择;然而,如果递归层次较少,这将是一种精致而简单的选择。
函数指针
历史与逻辑
为何pf和(*pf)等家呢?一种学派认为,由于pf是函数指针,而*pf是函数,因此应将(*pf)()用作函数调用;另一种学派认为,由于函数名师指向该函数的指针,指向函数的指针的行为应与函数名相似,因此应将pf()用作函数调用使用。C++进行了折中----这两种方式都是正确的,或者至少是允许的,虽然它们在逻辑上是相互冲突的。在认为折中折中粗糙之前,应该想远类思维活动的特点。
函数指针示例:
1)使用typedef
- <span style="font-size:18px;">#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 );
- }</span>
2)直接使用
- <span style="font-size:18px;">#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 );
- }</span>
C++函数
1)内联函数
常规函数:在执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置以为着使用函数时,需要一定的开销。
C++内联函数提供了另一种选择。内联汗水的编译代码与其他程序代码"内联"起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无须跳到另一个位置跳到另一个位置处执行代码,然后再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数的10个代码拷贝。
- <span style="font-size:18px;">#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;
- }</span>
2)引用作为函数的参数
引用作为某个变量的别名而存在
他与指针的区别在于,声明的时候就必须进行初始化
- <span style="font-size:18px;">#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;
- }</span>
将引用作为函数的参数,以达到不进行按值传递的目的
- <span style="font-size:18px;">#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;
- }</span>
3)对象、继承、引用
简单的说,ostream是基类,而ofstream是派生类。派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性,如格式化方法precision()和self()
继承的另一个特征是基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。例如,参数类型为ostream&的函数可以接受ostream对象(如cout)或ofstream对象作为参数
4)何时使用引用参数
a.程序员能够修改调用函数中的数据对象
b.通过传递引用而不是整个数据对象,可以提高程序的运行速度
5)何时使用按值传递
a.如果数据对象很小,如内置数据类型或小型结构,则按值传递
b.如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针
c.如果数据对象时较大的结构,则使用const指针或const引用,以提高程序的效率,这样可以节省复制结构所需的时间和空间
d.如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递
5)函数重载
参数个数不同构成的重载
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- void sum( int iLeftValue, int iRightValue )
- {
- }
- void sum( int iLeftValue, int iMidValue, int iRightValue )
- {
- }</span>
参数类型不同构成的重载
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- void sum( int iLeftValue, int iRightValue )
- {
- }
- void sum( double fLeftValue, double fMidValue )
- {
- }</span>
其他的比如,返回值类型,不能区别一个函数
6)函数模板
函数模板是通用的函数描述,也就是说它们使用通用类型来定义函数,其中的通用类型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许通用类型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parametarized types)。
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- /*
- template<class T>
- void Swap( T* a, T* b )
- {
- T temp;
- temp = *a;
- *a = *b;
- *b = temp;
- }*/
- template<class T>
- 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<int>( 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<double>( fa, fb );
- cout << "current fa value is:" << fa << endl;
- cout << "current fb value is:" << fb << endl;
- return 0;
- }</span>
a、重载的函数模板
需要多个对不同类型使用同一种算法的函数时,可使用模板。不过,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载函数模板定义。和常规重载一样,被重载的模板的函数特征为( T&, T& )。而新模板的特征为( T[], T[], int ),最后一个参数的类型为具体类型( int ),而不是通用类型。并非所有的模板参数都必须是模板参数类型。
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- #define ARRAYMAXLENGTH 4
- template<class T>
- void Swap( T& a, T& b )
- {
- T temp;
- temp = a;
- a = b;
- b = temp;
- }
- template<class T>
- 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<class T>
- 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<int>( 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<int>( arr, ARRAYMAXLENGTH );
- Swap<int>( arr, brr, ARRAYMAXLENGTH );
- cout << "\n";
- DisplayArray<int>( arr, ARRAYMAXLENGTH );
- return 0;
- }
- </span>
b、模板的显示具体化
对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
显示具体化的圆形和定义应以template<>打头,并通过名称来指出类型。
具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- #define ARRAYMAXLENGTH 4
- template<class T>
- 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<class T>
- 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<int>( arr, ARRAYMAXLENGTH );
- Swap<int>( arr, brr, ARRAYMAXLENGTH );
- cout << "\n";
- DisplayArray<int>( 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<double>( dArr, ARRAYMAXLENGTH );
- Swap<double>( dArr, dBrr, ARRAYMAXLENGTH );
- cout << "\n";
- DisplayArray<double>( dArr, ARRAYMAXLENGTH );
- return 0;
- }</span>
c、非模板函数和模板函数共存
如果不是对模板进行实例化,比如:
Swap<double>( dArr, dBrr, ARRAYMAXLENGTH );
的调用,那么调用模板函数,如果调用形式是Swap( dArr, dBrr, ARRAYMAXLENGTH );,则优先调用非模板函数
- <span style="font-size:18px;">#include "stdafx.h"
- #include "iostream"
- using namespace std;
- #define ARRAYMAXLENGTH 4
- template<class T>
- 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<class T>
- 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<int>( arr, ARRAYMAXLENGTH );
- Swap<int>( arr, brr, ARRAYMAXLENGTH );
- cout << "\n";
- DisplayArray<int>( 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<double>( dArr, ARRAYMAXLENGTH );
- Swap( dArr, dBrr, ARRAYMAXLENGTH );
- cout << "\n";
- DisplayArray<double>( dArr, ARRAYMAXLENGTH );
- return 0;
- }</span>
限定符volatile、mutable
volatile关键字表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,可以将一个指针指向某个硬件的位置,其实包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)了能修改其中的内容。或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会发生变化。如果不将变量声明为volatile,则编译器将进行这种优化;将变量声明为volatile,相当于告诉编译器,不要进行这种优化。
mutable,可以用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改。
- <span style="font-size:18px;">#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;
- }</span>
显示指出调用约定
在C语言中,一个名称只对应一个函数,因此这很容易实现。因此,为满足内部需要,C语言编译器可能将max这样的函数名翻译成_max。这种方法被称为C语言链接性(C language linkage)。但在C++中,同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此,C++编译器执行名称纠正或名称修饰,为重载函数生成不同的函数名称。例如,可能将max2( int,int )转换成_max2_i_i,而将max2( double, double )转换成_max_d_d。这种方法称为C++语言链接。
当然,可以显示的指出调用约定:
- <span style="font-size:18px;">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 );
- }</span>
名称空间
1)未命名的名称空间
可以通过省略名称空间的名称来创建未命名的名称空间
namespace
{
int iValue;
int IAge;
}
这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称潜在作用于为:从声明点到该声明区域末尾。从这个方面看,他们与全局变量相似。不过,由于这种名称空间没有名称,因此不能显示地使用using编译指令或using声明来使它在其他位置都可用。具体地说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称,因此这种方法可以替代链接性为内部的静态变量。
- <span style="font-size:18px;">#include "stdafx.h"
- namespace
- {
- int iValue;
- int IAge;
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- iValue = 14;
- IAge = 39;
- return 0;
- }</span>
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)可查看堆栈是否为空
可以将上述描述转换为一个类声明,其中公有成员函数提供了表示堆栈操作的接口,而私有数据成员负责存储堆栈数据。
- <span style="font-size:18px;">// 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<MyType> 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;
- }</span>
使用类
1)类中一个加法操作符的重载例子
- <span style="font-size:18px;">#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;
- }</span>
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;
- <span style="font-size:18px;">#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<long>( 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;
- }</span>
4)常用的友元:重载<<操作符
一个很有用的类特性是,可以对<<操作符进行重载,使之能与cout一起来显示对象的内容。当输出time对象的时候,可以直接使用cout << time;之所以可以这样做,是因为<<是可被重载的C++操作符之一。实际上,它已经被重载很多次了。最初,他表示额含义是按位移。ostream类对该操作符进行了重载,将其转换为一个输出工具。
在重载<<的时候应使用cout对象本身(void operator<<( ostream& os, CTime& ct )),而不是他的拷贝。因此该函数按应用(而不是按值)来传递该对象。这样,表达式cout << time;将导致os成为cout的一个别名;而表达式cout << time;将导致os成为cerr的另一个别名。
- #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<long>( 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;
- }