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