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

C++ Primer 第二遍阅读笔记(第二章)

2013-01-21 21:17 176 查看
第二章 变量和基本类型

C++ 定义了一组表示整数、浮点数、单个字符和布尔值的算术类型,另外还定义了一种称为 void 的特殊类型。void 类型没有对应的值,仅用在有限的一些情况下,通常用作无返回值函数的返回类型。

C++标准规定了每个算术类型的最小存储空间,但它并不阻止编译器使用更大的存储空间。事实上,对于int类型,几乎所有的编译器使用的存储空间都比所要求的大。



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

字符类型有两种:char 和 wchar_t。char 类型保证了有足够的空间,能够存储机器基本字符集中任何字符相应的数值,因此,char 类型通常是单个机器字节(byte)。wchar_t 类型用于扩展字符集,比如汉字和日语,这些字符集中的一些字符不能用单个 char 表示。

short、int 和 long 类型都表示整型值,存储空间的大小不同。一般, short 类型为半个机器字长,int 类型为一个机器字长,而 long 类型为一个或两个机器字长(在 32 位机器中 int 类型和 long 类型通常字长是相同的)。

整型 int、short 和 long 都默认为带符号型。要获得无符号型则必须指定该类型为 unsigned,比如 unsigned long。unsigned int 类型可以简写为 unsigned,也就是说,unsigned 后不加其他类型说明符意味着是 unsigned int 。

和其他整型不同,char 有三种不同的类型:plain char 、unsigned char 和 signed char。虽然 char 有三种不同的类型,但只有两种表示方式。可以使用 unsigned char 或 signed char 表示 char 类型。使用哪种 char 表示方式由编译器而定。

对象的类型决定对象的取值。这会引起一个疑问:当我们试着把一个超出其取值范围的值赋给一个指定类型的对象时,结果会怎样呢?答案取决于这种类型是 signed 还是 unsigned 的。对于 unsigned 类型来说,编译器必须调整越界值使其满足要求。编译器会将该值对 unsigned 类型的可能取值数目求模,然后取所得值。比如 8 位的 unsigned char,其取值范围从 0 到 255(包括 255)。如果赋给超出这个范围的值,那么编译器将会取该值对
256 求模后的值。例如,如果试图将 336 存储到 8 位的 unsigned char 中,则实际赋值为 80,因为 80 是 336 对 256 求模后的值。C++ 中,把负值赋给 unsigned 对象是完全合法的,其结果是该负数对该类型的取值个数求模后的值。所以,如果把 -1 赋给8位的 unsigned char,那么结果是 255,因为 255 是 -1 对 256 求模后的值。

类型 float、 double 和 long double 分别表示单精度浮点数、双精度浮点数和扩展精度浮点数。一般 float 类型用一个字(32 位)来表示,double 类型用两个字(64 位)来表示,long double 类型用三个或四个字(96 或 128 位)来表示。类型的取值范围决定了浮点数所含的有效数字位数。

对于实际的程序来说,float 类型精度通常是不够的——float 型只能保证 6 位有效数字,而 double 型至少可以保证 10 位有效数字,能满足大多数计算的需要。

实际上,许多人用整型进行计数。例如:程序经常计算像 vector 或数组这种数据结构的元素个数。在第三章和第四章中,我们将看到标准库定义了一组类型用于统计对象的大小。因此,当计数这些元素时使用标准库定义的类型总是正确的。其他情况下,使用 unsigned 类型比较明智,可以避免值越界导致结果为负数的可能性。

虽然 char 类型是整型,但是 char 类型通常用来存储字符而不用于计算。事实上,在某些应用中 char 类型被当作 signed 类型,在另外一些应用中则被当作 unsigned 类型,因此把 char 类型作为计算类型使用时容易出问题。

决定使用哪种浮点型就容易多了:使用 double 类型基本上不会有错。在 float 类型中隐式的精度损失是不能忽视的,而 double 类型精度代价相对于 float 类型精度代价可以忽略。事实上,有些机器上,double 类型比 float 类型的计算要快得多。long double 类型提供的精度通常没有必要,而且还需要承担额外的运行代价。

以 0(零)开头的字面值整数常量表示八进制,以 0x 或 0X 开头的表示十六进制。

通过在数值后面加 L 或者 l(字母“l”大写或小写)指定常量为 long 类型。定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。

类似地,可通过在数值后面加 U 或 u 定义 unsigned 类型。同时加 L 和 U 就能够得到 unsigned long 类型的字面值常量。但其后缀不能有空格:



通常可以用十进制或者科学计数法来表示浮点字面值常量。使用科学计数法时,指数用 E 或者 e 表示。默认的浮点字面值常量为 double 类型。在数值的后面加上 F 或 f 表示单精度。同样加上 L 或者 l 表示扩展精度(再次提醒,不提倡使用小写字母l)。下面每一组字面值表示相同的值:



这些字面值都是 char 类型的。在字符字面值前加 L 就能够得到 wchar_t 类型的宽字符字面值。如: L'a'

转义字符都以反斜线符号开始,C++ 语言中定义了如下转义字符:



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

'A' 表示单个字符 A,然而"A"表示包含字母A 和空字符两个字符的字符串。

也存在宽字符串字面值,一样在前面加“L”,如L"a wide string literal" 宽字符串字面值是一串常量宽字符,同样以一个宽空字符结束。

两个相邻的仅由空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值。这使得多行书写长字符串字面值变得简单:



处理长字符串有一个更基本的(但不常使用)方法,这个方法依赖于很少使用的程序格式化特性:在一行的末尾加一反斜线符号可将此行和下一行当作同一行处理。

C++ 的格式非常自由。特别是有一些地方不能插入空格,其中之一是在单词中间。特别是不能在单词中间断开一行。但可以通过使用反斜线符号巧妙实现:



左值:左值可以出现在赋值语句的左边或右边。

右值:右值只能出现在赋值的右边,不能出现在赋值语句的左边。

在本书中,我们遵循更为通用的用法,即对象是内存中具有类型的区域。我们可以自由地使用对象描述程序中可操作的大部分数据,而不管这些数据是内置类型还是类类型,是有名字的还是没名字的,是可读的还是可写的。

变量名,即变量的标识符,可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++ 中的标识符都是大小写敏感的。语言本身并没有限制变量名的长度,但考虑到将会阅读和/或修改我们的代码的其他人,变量名不应太长。

C++ 保留了一组词用作该语言的关键字。关键字不能用作程序的标识符。



C++ 还保留了一些词用作各种操作符的替代名。这些替代名用于支持某些不支持标准C++操作符号集的字符集。它们也不能用作标识符。



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

变量命名习惯

变量名一般用小写字母。例如,通常会写成 index,而不写成 Index 或 INDEX。

标识符应使用能帮助记忆的名字,也就是说,能够提示其在程序中的用法的名字,如 on_loan 或 salary。

包含多个词的标识符书写为在每个词之间添加一个下划线,或者每个内嵌的词的第一个字母都大写。例如通常会写成 student_loan
或 studentLoan,而不写成studentloan。

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

int ival(1024); // direct-initialization

int ival = 1024; // copy-initialization

虽然在本书到目前为止还没有清楚说明,但是在 C++ 中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

现在我们只需知道,直接初始化语法更灵活且效率更高。

我做错的一道题:判断:double salary = wage = 9999.99; 错,同一定义语句中不同变量的初始化应分别进行。改为double salary = 9999.99, wage = 9999.99

内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。除了用作赋值操作符的左操作数,未初始化变量用作任何其他用途都是没有定义的。

建议每个内置类型的对象都要初始化。虽然这样做并不总是必需的,但是会更加容易和安全,除非你确定忽略初始化式不会带来风险。

大多数类都提供了默认构造函数。如果类具有默认构造函数,那么就可以在定义该类的变量时不用显式地初始化变量。

有些类类型没有默认构造函数。对于这些类型来说,每个定义都必须提供显式的初始化式。没有初始值是根本不可能定义这种类型的变量的。

为了让多个文件访问相同的变量,C++ 区分了声明和定义。可以通过使用extern关键字声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。

extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。

任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。

在函数内定义一个与函数可能会用到的全局变量同名的局部变量总是不好的。局部变量最好使用不同的名字。

通常把一个对象定义在它首次使用的地方是一个很好的办法。

与其他变量不同,除非特别说明,在全局作用域声明的const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定 const 变更为 extern,就可以在整个程序中访问
const 对象。

非 const 变量默认为 extern。要使 const 变量能够在其他的文件中访问,必须地指定它为 extern。

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

const 引用是指向 const 对象的引用:



可以读取但不能修改 refVal ,因此,任何对 refVal 的赋值都是不合法的。这个限制有其意义:不能直接对 ival 赋值,因此不能通过使用 refVal 来修改 ival。

同理,用 ival 初始化 ref2 也是不合法的:ref2 是普通的非 const 引用,因此可以用来修改 ref2 指向的对象的值。通过 ref2 对 ival 赋值会导致修改 const 对象的值。为阻止这样的修改,需要规定将普通的引用绑定到 const 对象是不合法的。

const 引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:



同样的初始化对于非 const 引用却是不合法的,而且会导致编译时错误。

非 const 引用只能绑定到与该引用同类型的对象。

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

typedef 可以用来定义类型的同义词。

可以为一个或多个枚举成员提供初始值,用来初始化枚举成员的值必须是一个常量表达式。常量表达式是编译器在编译时就能够计算出结果的整型表达式。

每个类都定义了一个接口和一个实现。接口由使用该类的代码需要执行的操作组成。实现一般包括该类所需要的数据。实现还包括定义该类需要的但又不供一般性使用的函数。

定义类时,通常先定义该类的接口,即该类所提供的操作。通过这些操作,可以决定该类完成其功能所需要的数据,以及是否需要定义一些函数来支持该类的实现。

接口:由某种类型支持的操作。设计良好的类分离了接口和实现,在类的 public 部分定义接口,private 部分定义实现。数据成员一般是实现的一部分。当函数成员是期望该类型的使用者使用的操作时,函数成员就是接口的一部分(因此为 public);当函数成员执行类所需要的、非一般性使用的操作时,函数成员就是实现的一部分。

实现:定义数据和操作的类成员(通常为 private),这些数据和操作并非为使用该类型的代码所用。例如,istream 和 ostream 类管理的 IO 缓冲区是它们的实现的一部分,但并不允许这些类的使用者直接访问。

类也可以包含 0 个到多个 private 或 public 访问标号。访问标号控制类的成员在类外部是否可访问。使用该类的代码可能只能访问 public 成员。

定义变量和定义数据成员存在非常重要的区别:一般不能把类成员的初始化作为其定义的一部分。当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数的特殊成员函数控制初始化。

如果使用 class 关键字来定义类,那么定义在第一个访问标号前的任何成员都隐式指定为 private;如果使用 struct 关键字,那么这些成员都是 public。使用 class 还是 struct 关键字来定义类,仅仅影响默认的初始访问级别。

由多个文件组成的程序需要一种方法连接名字的使用和声明,在 C++ 中这是通过头文件实现的。



你需要检查所用编译器的用户手册,了解如何编译和执行由多个源文件组成的程序。

因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。

对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的
const 对象和 inline 函数。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。

一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当 const 整型变量通过常量表达式自我初始化时,这个 const 整型变量就可能是常量表达式。而 const 变量要成为常量表达式,初始化式必须为编译器可见。为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的 const 变量定义在头文件中。那样的话,无论该 const 变量何时使用,编译器都能够看见其初始化式。

我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。使得头文件安全的通用做法,是使用预处理器定义头文件保护符。头文件保护符用于避免在已经见到头文件的情况下重新处理该头文件的内容。

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