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整型。

C++ Primer Chapter3

15 4 月, 2010

1、使用using声明可以在不需要加前缀namespace_name::的情况下访问命名空间中的名字。using声明的形式如下:

using namespace::name;    // 如using std::cout;

有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。用#include包含文件时,相当于头文件中的文本将成为我们编写的文件的一部分。如果在头文件中放置了using声明,就相当于在包含该头文件的每个程序中都放置了同一using声明,不论该程序是否需要using声明。

通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。

2、string IO操作getline接受两个参数:一个输入流对象和一个string对象。

while(getline(cin, line))

string类类型和许多其他库类型都定义了一些配套类型。通过这些配套类型,库类型的使用就能与机器无关。size_type就是这些配套类型中的一种。它定义为与unsigned型(unsigned int或unsigned long)具有相同的含义,而且可以保证足够大能够存储任意string对象的长度。为了使用由string类型定义的size_type类型,程序员必须加上作用域操作符来说明所使用的size_type类型是由string类定义的。

任何存储string的size操作结果的变量必须为string::size_type类型。特别重要的是,不要把size的返回值赋给一个int变量。string自己负责size_type一定能容纳的最大长度,且unsigned型所能表示的最大正数值比对应的signed型要大一倍。

3、C标准库头文件命名形式为name.h,而C++版本则命名为cname,少了后缀.h而在头文件名前加了c。c表示这个头文件源自C标准库。因此,cctype与ctype.h文件的内容是一样的,只是采用了更适合C++程序的形式。特别地,cname头文件中定义的名字都定义在命名空间std内,而.h版本中的名字却不是这样。

4、vector不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector类型的每一种都指定了其保存元素的类型。因此vector<int>和vector<string>都是数据类型。

vector中的对象是没有命名的。就像string中的字符对象、数组中的对象。

必须是已存在的元素才能用下标操作符([])进行索引。通过下标操作进行赋值时,不会添加任何元素。

由end操作返回的迭代器指向vector的“末端元素的下一个”。通常成为超出末端迭代器,表明它指向了一个不存在的元素。如果vector为空,begin返回的迭代器与end返回的迭代器相同。

每种容器类型还定义了一种名为const_iterator的类型,该类型只能用于读取容器内元素,但不能改变其值。使用const_iterator类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。

不要把const_iterator对象与const的iterator对象混淆起来。声明一个const迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值。

5、类似于vector,bitset类是一种类模板;而与vector不一样的是bitset类型对象的区别仅在其长度而不在其类型。在定义bitset时,要明确bitset含有多少位,须在尖括号内给出它的长度值:

bitset<32> bitvec;

C++ Primer Chapter2

15 4 月, 2010

1、C++定义了一组表示整数、浮点数、单个字符和布尔值的算术类型,另外还定义了一种称为void的特殊类型。

算术类型的存储空间依机器而定。C++标准规定了每个算术类型的最小存储空间。

通常将8位的块作为一个字节,32位或4个字节作为一个“字(word)”。一般short类型为半个机器字(word)长,int类型为一个机器字长,而long类型为一个或两个机器字长(在32位机器中int类型和long类型通常字长是相同的)。

表示整数、字符和布尔值的算术类型合称为整形。

2、对于unsigned类型来说,编译器必须调整越界值使其满足要求。编译器会将该值对unsigned类型的可能取值数目求模,然后取所得值。

当将超过取值范围的值赋给signed类型时,由编译器决定实际赋的值。在实际操作中,很多的编译器处理signed类型的方式和unsigned类型类似。

3、字面值常量。称之为字面值是因为只能用它的值称呼它,称之为常量是因为它的值不能修改。只有内置类型存在字面值,没有类类型的字面值。

字面值整数常量的类型默认为int或long类型(没有short类型的字面值常量)。

默认的浮点字面值常量为double类型。

为兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字符。

两个相邻的仅由空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值。

std::cout << “a multi-line ”
“string literal ”
“using concatenation”
<< std::endl;

4、C++是一门静态类型语言,在编译时会作类型检查。

变量提供了程序可以操作的有名字的存储区。

除了关键字,C++标准还保留了一组标识符用于标准库。标识符不能包含两个连续的下划线(__id),也不能以下划线开头后面紧跟一个大写字母(_Id)。有些标识符(在函数外定义的标识符)不能以下划线开头(_)。

5、C++支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等号,直接初始化则是把初始化式放在括号中:

int ival = 1024   // 复制初始化

int ival(1024);    // 直接初始化

对内置类型来说,复制初始化和直接初始化几乎没有差别。而对类类型对象,直接初始化语法更灵活且效率更高。

在C++中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

6、内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成0,在函数体里定义的内置类型变量不进行自动初始化。

7、变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。

声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。可以通过使用extern关键字声明变量名而不定义它。

如果声明有初始化式,那么它可被当作是定义,及时声明标记为extern。(只有当extern声明位于函数外部时,才可以含有初始化式。)

8、const:因为常量在定义后就不能被修改,所以定义时必须初始化。参考资料:CONST

非const变量默认为extern。要使const变量能够在其他的文件中访问,必须显示地指定它为extern。(只有变量在声明定义时指定了extern,才能在其他文件中使用extern声明该变量后直接使用。)

9、引用就是对象的另一个名字。

引用是一种复合类型。复合类型是指用其他类型定义的类型。在引用的情况下,每一种引用类型都“关联到”某一其他类型。不能定义引用类型的引用,但可以定义任何其他类型的引用。

当引用初始化后,只要该引用存在,它就保持绑定到初始化时指定的对象。不可能将引用绑定到另一个对象。

const引用是指向const对象的引用。即引用对象不能修改。

非const引用只能绑定到与该引用同类型的对象。const引用则可以绑定到不同但相关的类型的对象或绑定到右值。

10、常量表达式是编译器在编译时就能够计算出结果的整型表达式。

枚举成员值可以是不唯一的。

11、用class和struct关键字定义类的唯一差别在于默认访问级别:默认情况下,struct的成员为public,而class的成员为private。

12、因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。有三个例外:头文件可以定义类、值在编译时就已知道的const对象和inline函数。

回想一下,const变量默认时是定义该变量的文件的局部变量。正如我们现在所看到的,这样设置默认情况的原因在于允许const变量定义在头文件中。

当该const变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式来替换对这些const变量的使用。所以,在实践中不会有任何存储空间用于存储常量表达式初始化的const变量。参考资料:const

13、预处理器用指定的头文件的内容替代每个#include。

如果头文件名括在尖括号(<>)里,那么认为该头文件是标准头文件。编译器将会在预定义的位置集查找该头文件。

14、所谓魔数和魔字符串是指在代码中出现但没有解释的数字常量或字符串常量。

C++ Primer Chapter1

14 4 月, 2010

1、操作系统通过调用main函数来执行程序,(嵌入启动函数,操作系统调用启动函数。在启动函数中进行C/C++运行时库的初始化等工作,最后调用main函数执行程序。参考资料:C运行时库),main函数则执行组成自己的语句并返回一个值给操作系统。

操作系统通过main函数返回的值来确定程序是否成功执行完毕。返回0值表示程序成功执行完毕。返回值的作用:如在一个程序(A)中调用另一个程序(B)处理任务,程序A通过程序B的返回值判断任务执行情况,并及时处理。参考资料:main函数返回值作用

main函数是(唯一)被操作系统显式调用的函数。

2、定义函数必须指定4个元素:返回类型、函数名、圆括号内的形参表(可能为空)和函数体。

main函数的返回值必须是int型。这是标准规定。参考资料:C语言之main函数返回值问题分析

3、在大多数系统中,main函数的返回值是一个状态指示器。返回值0往往表示main函数成功执行完毕。任何其他非零的返回值都有操作系统定义的含义。通常非零返回值表明有错误出现。每一种操作系统都有自己的方式告诉用户main函数返回什么内容。

访问main函数的返回值的方式和系统有关。不论UNIX还是Windows系统,执行程序后,必须发出一个适当的echo命令。UNIX系统中,通过键入如下命令获取状态:

$ echo $?

要在Windows系统下查看状态,键入

C:\directory> echo %ERRORLEVEL%(显示程序main函数的返回值)

4、调用GNU编译器的默认命令是g++:

$ g++ prog1.cc -o prog1

这里的$是系统提示符。这个命令产生一个名为prog1或prog1.exe的可执行文件。在UNIX系统下,可执行文件没有后缀;-o prog1是编译器参数以及用来存放可执行文件的文件名。如果省略-o prog1,那么编译器在UNIX系统下产生名为a.out而在Windows下产生名为a.exe的可执行文件。

微软编译器采用命令cl(compile-link 编译-链接)来调用:

C:\directory> cl -GX prog1.cpp

cl是调用编译器的命令,-GX是一个选项,该选项在使用命令行界面编译程序时是必需的。

5、流是指要从某种IO设备上读入或写出的字符序列。属于“流”试图说明字符是随着时间顺序生成或消耗的。(流就是一种序列。)

6、标准库定义了4个IO对象:istream的cin、ostream的cout、cerr、clog。

一般情况下,系统将这些对象与执行程序的窗口联系起来。这样,当我们从cin读入时,数据从执行程序的窗口读入,当写到cout、cerr或clog时,输出写至同一窗口。

7、当操作符是输出操作符(<<)时,结果是左操作数的值。也就是说,输出操作返回的值是输出流本身。

endl是一个特殊值,称为操纵符,将它写入输出流时,具有输出换行的效果,并刷新与设备相关联的缓冲区。通过刷新缓冲区,用户可立即看到写入到流中的输出。

8、在写C++程序时,大部分出现空格符的地方可用换行符代替。这条规则的一个例外是字符串字面值中的空格符不能用换行符替代。另一个例外是空格符不允许出现在预处理指示中。

9、定义变量时,应该给变量赋初始值,除非确定将变量用于其他意图之前会覆盖这个初值。如果不能保证读取变量之前重置变量,就应该初始化变量。

10、当注释跨多行时,最好能直观地指明每一行都是注释。我们的风格是在注释的每一行以星号开始,指明整个范围是多行注释的一部分。如:

//
//
//
/*
 *
 */

太多注释混入程序代码可能会使代码难以理解,通常最好是将一个注释块放在所解释代码的上方。

临时忽略一段代码更好的方法,是用编辑器在要忽略的每一行代码前面插入单行注释。

11、for(int val = 1; val <= 10; ++val)退出for循环后,变量val不可再访问,循环终止后使用val是不可能的。然后不是所有的编译器都有这一要求。

12、while(std::cin >> value)当我们使用istream对象作为条件,结果是测试流的状态。如果流是有效的(也就是说,如果读入下一个输入是可能的)那么测试成功。遇到文件结束符或遇到无效输入时,如读取了一个不是整数的值,则istream对象是无效的。处于无效状态的istream对象将导致条件失败。

13、操作系统使用不同的值作为文件结束符。Windows系统下我们通过键入control-z,来输入文件结束符。Unix系统中,包括Mac OS-X机器,通常用control-d。

14、标准库的头文件用尖括号<>括起来,非标准库的头文件用双括号””括起来。

15、点操作符通过它的左操作数取得右操作数。点操作符仅应用于类类型的对象:左操作数必须是类类型的对象,右操作数必须指定该类型的成员。与大多数其他操作符不同,点操作符的右操作数不是对象或值,而是成员的名字。

16、缓冲区 一段用来存放数据的存储区域。IO设备通常存储输入(或输出)到缓冲区,并独立于程序动作对缓冲区进行读写。输出缓冲区通常必须显式刷新以强制输出缓冲区内容。默认情况下,读cin会刷新cout;当程序正常结束时,cout也被刷新。

cerr 默认情况下,输出cerr不带缓冲。

clog 默认情况下,写到clog时是带缓冲的。

操纵符 在读或写时“操纵”流本身的对象,如std::endl。

未初始化变量 类类型没有未初始化变量。没有指定初始值的类类型变量由类定义(即构造函数)初始化。