Archive for the ‘书籍’ Category

C++ Primer Chapter8

星期五, 23 4 月, 2010

1、IO对象不可复制或赋值:只有支持复制的元素类型可以存储在vector或其他容器类型里;形参或返回类型也不能为流类型。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用。

2、IO标准库的条件状态

strm::iostate 机器相关的整形名,由各个iostream类定义,用于定义条件状态

strm::badbit strm::iostate类型的值,用于指出被破坏的流

strm::failbit strm::iostate类型的值,用于指出失败的IO操作

strm::eofbit strm::iostate类型的值,用于指出流已经到达文件结束符

s.eof() 如果设置了流s的eofbit值,则该函数返回true

s.fail() 如果设置了流s的failbit值,则该函数返回true

s.bad() 如果设置了流s的badbit值,则该函数返回true

s.good() 如果流s处于有效状态,则该函数返回true

s.clear() 将流s中的所有状态值都重设为有效状态

s.clear(flag) 将流s中的某个指定条件状态设置为有效。flag的类型是strm::iostate

s.setstate(flag) 给流s添加指定条件。flag的类型是strm::iostate

s.rdstate() 返回流s的当前条件,返回值类型为strm::iostate

badbit标志着系统级的故障,如无法恢复的读写错误。如果出现了这类错误,则该流通常就不能再继续使用了。如果出现的是可恢复的错误,如在希望获得数值型数据时输入了字符,此时则设置failbit标志,这种导致设置failbit的问题通常是可以修正的。eofbit是在遇到文件结束符时设置的,此时同时还设置了failbit。

3、下面几种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:

(1)程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区。

(2)在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。

(3)用操纵符显示地刷新缓冲区,例如行结束符endl。

(4)在每次输出操作执行完后,用unitbuf操纵符设置流的内部状态,从而清空缓冲区。

(5)可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。

4、除了endl操纵符,C++语言还提供了另外两个类似的操纵符。第一个是经常使用的flush,用于刷新流,但不在输出中添加任何字符。第二个则是比较少用的ends,这个操纵符在缓冲区中插入空字符null,然后刷新它:

cout << “hi!” << flush;

cout << “hi!” << ends;

cout << “hi!” << endl;

如果需要刷新所有输出,最好使用unitbuf操纵符。这个操纵符在每次执行完写操作后都刷新流:

cout << unitbuf << “first” << “second” << nounitbuf;

等价于:

cout << “first” << “second” << flush;

nounitbuf操纵符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。

5、tie函数可用istream或ostream对象调用,使用一个指向ostream对象的指针形参。调用tie函数时,将实参流绑在调用该函数的对象上。如果一个流调用tie函数将其本身绑在传递给tie的ostream实参对象上,则该流上的任何IO操作都会刷新实参所关联的缓冲区。一个ostream对象每次只能与一个istream对象绑在一起。如果在调用tie函数时传递实参0,则打破该流上已存在的捆绑。

6、由于历史原因,IO标准库使用C风格字符串而不是C++ string类型的字符串作为文件名。在创建fstream对象时,如果调用open或使用文件名作初始化式,需要传递的实参应为C风格字符串,而不是标准库string对象。程序常常从标准输入获得文件名。通常,比较好的方法是将文件名读入string对象,而不是C风格字符数组。假设要使用的文件名保存在string对象中,则可调用c_str成员获取C风格字符串。

7、如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用close关闭前一个文件,并调用clear清除该流的状态。

8、文件模式

in 打开文件做读操作

out 打开文件做写操作

app 在每次写之前找到文件尾

ate 打开文件后立即将文件定位在文件尾

trunc 打开文件时清空已存在的文件流

binary 以二进制模式进行IO操作

9、stringsteam提供转换和/或格式化

C++ Primer Chapter7

星期五, 23 4 月, 2010

1、函数由函数名以及一组操作数类型唯一地表示。C++使用调用操作符(即一对圆括号)实现函数的调用。正如其他操作符一样,调用操作符需要操作数并产生一个结果。调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参。

2、形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。

函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本(形参复制实参的值)。如果函数将新指针赋值给形参,主调函数使用的实参指针的值没有改变。事实上被复制的指针只影响对指针所指对象的赋值,即如果函数形参是非const类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值。

可以将指向const对象的指针初始化为指向非const对象,但不可以让指向非const对象的指针指向const对象。

在调用函数时,如果该函数使用非引用的非const形参,则既可给该函数传递const实参也可传递非const的实参。因为初始化复制了初始化式的值,所以可以用const对象初始化非const对象,反之亦然。

3、从C语言背景转到C++的程序员习惯通过传递指针来实现对实参的访问。在C++中,使用引用形参则更安全和自然。

如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用。

非const引用形参只能与完全同类型的非const对象关联。应该将不修改相应实参的形参定义为const引用。如果将这样的形参定义为非const引用,则毫无必要的限制了该函数的使用。如:

int incr(int &val);

short v1 = 0;

int v2 = incr(v1);    // int &只能绑定int对象

应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活。这样的形参既不能用const对象初始化,也不能用字面值(常量)或产生右值的表达式实参(临时变量)初始化。

4、使用数组形参或指针形参传递数组,编译器只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组实参的大小与形参的大小是否匹配。

&arr两边的圆括号是必需的,因为下标操作符具有更高的优先级:

f(int &arr[10])    // error: arr is an array of references

f(int (&arr)[10])    // ok: arr is a reference to an array of 10 ints

5、C++中的省略符形参是为了编译使用了varargs的C语言程序。关于如何使用varargs,请查阅所用C语言编译器的文档。对于C++程序,只能将简单数据类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制。

6、为了主函数main的返回值独立于机器,cstdlib头文件定义了两个预处理变量,分别表示程序运行成功和失败:

#include <cstdlib>

int main()

{

    if(some_failure)

        return EXIT_FAILURE;

    else

        return EXIT_SUCCESS;

}

7、当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。理解返回引用至关重要的是:千万不能返回局部变量的引用。

返回引用的函数返回一个左值(变量的别名,可以赋值)。

8、默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。

9、只有当定义它的函数被调用时才存在的对象称为自动对象。static局部对象一旦被创建,在程序结束前都不会被撤销。

10、内联函数应该在头文件中定义,这一点不同于其他函数。

编译器隐式地将在类内定义的成员函数当作内联函数。

内联说明对于编译器来说只是一个建议,编译器可以选择忽略这个建议。

11、每个成员函数都有一个额外的、隐含的形参this。

const改变了隐含的this形参的类型(Object *const this变成了const Object *const this)。用这种方式使用const的函数称为常量成员函数。由于this是指向const对象的指针,const成员函数不能修改调用该函数的对象。

const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的。

12、在冒号和花括号之间的代码成为构造函数的初始化列表。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。

Object: iMin(0), iMax(100) {}    // Object构造函数

13、由编译器创建的默认构造函数通常成为合成的默认构造函数,它将依据如同变量初始化的规则初始化类中所有成员。对于具有类类型的成员,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,处于其他任何目的对未初始化成员的使用都没有定义。

14、允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。

C++ Primer Chapter6

星期二, 20 4 月, 2010

1、switch表达式必须产生一个整数结果,case标号必须是整型常量表达式。如果两个case标号具有相同的值,同样也会导致编译时的错误。

case 3.14:    // 不是整型

case iVal:    // 不是常量

2、对于switch结构,只能在它的最后一个case标号或default标号后面定义变量。制定这个规则是为了避免出现代码跳过变量的定义和初始化的情况。

3、在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程。

4、可以在for语句的init-statement中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型。

5、goto语句不能跨越变量的定义语句向前跳转;如果确实需要在goto和其跳转对应的标号之间定义变量,则定义必须放在一个块语句中。

6、与其他块语句一样,try块引入局部作用域,在try块中声明的变量,包括catch子句中声明的变量,不能在try外面引用。

7、可使用NDEBUG预处理变量实现有条件的调试代码:

#ifndef NDEBUG

// Error

#endif

8、预处理器还定义了其余四种在调试时非常有用的常量:

__FILE__    // 文件名

__LINE__    // 当前行号

__DATE__    // 文件被编译的日期

__TIME__    // 文件被编译的时间

9、与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用assert来测试“不可能发生”的条件。

C++ Primer Chapter5

星期一, 19 4 月, 2010

1、表达式由一个或多个操作数通过操作符组合而成。每个表达式都会产生一个结果。如果表达式中没有操作符,则其结果就是操作数本身的值。对于含有操作符的表达式,它的值通过对操作数做指定操作获得。除了特殊用法外,表达式的结果是右值,可以读取该结果值,但是不允许对它进行赋值。

2、如果两个操作数都是负数,求模操作的结果则为负数(或零);如果只有一个操作数为负数,除法操作和求模操作的结果取决于机器。

3、对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。

4、赋值操作符的左操作数必须是非const的左值。数组名是不可修改的左值:因此数组不可用作赋值操作的目标。

与下标和解引用操作符一样,赋值操作符也返回左值。

5、点操作符用户获取类类型对象的成员。

6、sizeof操作符的作用是返回一个对象或类姓名的长度,返回值的类型为size_t,长度的单位是字节。参考资料:sizeof

7、逗号表达式的结果是其最右边表达式的值。

8、只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果。

9、如果不提供显示初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。

如果其他动态对象一样,const动态对象也是使用删除指针来释放的。

10、对于包含signed和unsigned int型的表达式,其转换可能出乎我们的意料。表达式中的signed型数值会被转换为unsigned型。

11、不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或sizeof操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。参考资料:C++ Primer Chapter4

12、命名的强制类型转换符号的一般形式如下:

cast-name<type>(expression)

其中cast-name为static_cast、dynamic_cast、const_cast和reinterpret_cast之一,type为转换的目标类型,而expression则是被强制转换的值。强制转换的类型指定了在expression上执行某种特定类型的转换。

dynamic_cast支持运行时识别指针或引用所指向的对象。

const_cast将转换掉表达式的const性质。只有使用const_cast才能将const性质转换掉。在这种情况下,试图使用其他三种形式的转换都会导致编译时的错误。类似地,除了添加或删除const特性,用const_cast符来执行其他任何类型转换,都会引起编译错误。

static_cast编译器隐式执行的任何类型转换都可以由它显示完成。(尝试转换一个对象的类型到另一个无关的类型将失败。)

reinterpret_cast通常为操作数的位模式提供较低层次的重新解释。(总是成功的“欺骗”编译器:转换一个对象的类型到另一个无关的类型。)

参考资料:reinterpret_cast

static_cast和reinterpret_cast的区别

reinterpret_cast的本质(转)

C++ Primer Chapter4

星期一, 19 4 月, 2010

1、数组是由类型名、标识符和维数组成的复合数据类型,是一种存储单一数据类型对象的容器,其中每个对象都没有单独的名字。

没有所有元素都是引用的数组。

数组的维数必须用值大于等于1的常量表达式。此常量表达式只能包含整型字面值常量(如10)、枚举常量或者常量表达式初始化的整型const对象。非const对象以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数。

除非显示地提供元素初值,否则内置类型的局部数组的元素没有初始化。

显示初始化的数组不需要指定数组的维数值(不完整类型),编译器会根据列出的元素个数来确定数组的长度。

2、指针的概念很简单:指针用于指向对象。具体来说,指针保存的是另一个对象的地址。

标准翻译版:一个指针类型可能来自于一个函数类型、一个对象类型或一个不完整的类型,以上统称为被引用类型。一个指针类型描述了一个对象,它的值提供了到一个被引用类型实体的引用。

参考资料:解密数组名本质

数组与指针的艺术——深入探索C/C++数组与指针的奥秘

若指针保存0值,表明它不指向任何对象。(把int型变量赋给指针是非法的,尽管此int型变量的值可能为0。)

如果必须分开定义指针和其所指向的对象,则将指针初始化为0。因为编译器可检查测出0值的指针,程序可判断该指针并未指向一个对象。

3、指针和引用有两个重要区别:第一个区别在于引用总指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。

4、在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针(左值转换成右值)。当数组名作为数组对象定义的标识符、初始化器以及作为sizeof、&的操作数时,代表数组对象本身。

数组名不是指针。数组名的内涵在于其指代实体是一种数据结构(数组);数组名的外延在于其可以转换为指向其指代实体的指针;指向数组的指针则是一种变量类型,仅仅意味着数组的存放地址。

5、使用下标访问数组时,实际上是使用下标访问指针。

C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。而计算数组超出末端的位置之后或数组首地址之前的地址都是不合法的。

C++语言强制要求指向const对象的指针也必须具有const特性:

const typename *ptr;    // 指向typename类型const对象的指针

typename *const ptr;    // const指针

不能使用指向const对象的指针修改基础对象,然而如果该指针指向的是一个非const对象,可用其他方法修改其所指的对象。

如果把指向const的指针理解为“自以为指向const的指针”,这可能会对理解有所帮助。

6、typedef string *pstring;

const pstring cstr;

声明const pstring时,const修饰的是pstring的类型,这是一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的cosnt指针,这个定义等价于:

string *const cstr;

7、字符串字面值的类型就是const char类型的数组。

C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组是合法的。用new动态创建长度为0的数组时,new返回有效的非零指针。该指针与new返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。

c_str返回的数组(const char [])并不保证一定是有效的,接下来对st2的操作有可能会改变st2的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制c_str函数返回的数组。

8、严格地说,C++中没有多维数组,通常所指的多维数组其实就是数组的数组。

多维数组转换而成的指针类型应该是指向第一个内层数组的指针。

9、typedef int int_array[4];

int_array *ip = ia;    // int_array指针指向包含四个int元素的数组一行

10、ptrdiff_t与size_t类型一样,也是一种与机器相关的类型,在cstddef头文件中定义。size_t是unsigned类型,而ptrdiff_t则是signed整型。