您的位置:首页 > 编程语言 > C语言/C++

<<C++Primer PLus 第五版>>读书笔记1

2012-08-27 16:17 411 查看
处理第一个问题:

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的头文件:<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那样)

#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<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;
}


如果试图打开一个不存在的文件,这将导致后面使用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 );
	}


如果愿意,可以使用其它方法来确定循环种终止的真正原因:

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";
	}






数组作为函数参数

当数组作为函数参数时,将数组的地址作为参数可以节省复制整个数组所需的时机和内存

验证一下,函数中数组的地址与外层调用时,函数的地址是一样的,并且在函数中

#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<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;
}


a、重载的函数模板

需要多个对不同类型使用同一种算法的函数时,可使用模板。不过,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载函数模板定义。和常规重载一样,被重载的模板的函数特征为( T&, T& )。而新模板的特征为( T[], T[], int ),最后一个参数的类型为具体类型( int ),而不是通用类型。并非所有的模板参数都必须是模板参数类型。

#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;
}




b、模板的显示具体化

对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。

显示具体化的圆形和定义应以template<>打头,并通过名称来指出类型。

具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。

#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;
}


c、非模板函数和模板函数共存

如果不是对模板进行实例化,比如:

Swap<double>( dArr, dBrr, ARRAYMAXLENGTH );

的调用,那么调用模板函数,如果调用形式是Swap( dArr, dBrr, ARRAYMAXLENGTH );,则优先调用非模板函数

#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;
}



限定符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<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;
}



使用类

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<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;
}


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<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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: