《C++ Primer 4th》读书摘要
基本上所有的语言都要提供下列特征:
• 内置数据类型,如整型、字符型等。
• 表达式和语句:表达式和语句用于操纵上述类型的值。
• 变量:程序员可以使用变量对所用的对象命名。
• 控制结构:如 if 或 while,程序员可以使用控制结构有条件地执行或重复执行一组动作。
• 函数:程序员可以使用函数把行为抽象成可调用的计算单元。
大多数现代程序语言都采用两种方式扩充上述基本特征集:允许程序员通过自定义数据类型扩展该语言;提供一组库例程,这些例程定义了一些并非内置在语言中的实用函数和数据类型。
C++ 是静态类型(statically typed)语言,在编译时执行类型检查。
类型是所有程序的基础。类型告诉我们数据代表什么意思以及可以对数据执行哪些操作。
C++ 语言定义了几种基本类型:字符型、整型、浮点型等。C++ 还提供了可用于自定义数据类型的机制,标准库正是利用这些机制定义了许多更复杂的类型,比如可变长字符串 string、vector 等。此外,我们还能修改已有的类型以
形成复合类型。
C++标准规定了每个算术类型的最小存储空间,但它并不阻止编译器使用更大的存储空间。
类型 |
含义 |
最小存储空间 |
bool |
boolean |
NA |
char |
character |
8 bits |
wchar_t |
wide character |
16 bits |
short |
short integer |
16 bits |
int |
integer |
16 bits |
long |
long integer |
32 bits |
float |
single-precision floating-point |
6 significant digits |
double |
double-precision floating-point |
10 significant digits |
long double |
extended-precision floating-point |
10 significant digits |
表示整数、字符和布尔值的算术类型合称为整型
让存储具有结构的最基本方法是用块(chunk)处理存储。大部分计算机都使用特定位数的块来处理存储,块的位数一般是 2 的幂,因为这样可以一次处理 8、16 或 32 位。大多数计算机将存储器中的每一个字节和一个称为地址的数关联起来。要让地址为 736425 的字节具有意义,必须要知道存储在该地址的值的类型。
在 32 位机器中 int 类型和 long 类型通常字长是相同的。可以将算术类型的任何值赋给 bool对象。0 值算术类型代表 false,任何非 0 的值都代表 true。
unsigned 后不加其他类型说明符意味着是 unsigned int 。符号位为1,值就为负数;符号位为 0,值就为 0 或正数。
字面值常量。称之为字面值是因为只能用它的值称呼它,称之为常量是因为它的值不能修改。每个字面值都有相应的类型
我们能将值20定义成下列三种形式中的任意一种:
20 // decimal
024 // octal
0x14 // hexadecimal
以 0(零)开头的字面值整数常量表示八进制,以 0x 或 0X 开头的表示十六进制。
定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。可通过在数值后面加 U 或 u 定义 unsigned 类型。没有 short 类型的字面值常量。
128u /* unsigned */ |
1024UL /* unsigned long*/ |
1L /* long */ |
8Lu /* unsigned long*/ |
在字符字面值前加 L 就能够得到 wchar_t类型的宽字符字面值。如:L'a'
不可打印字符和特殊字符都用转义字符书写。转义字符都以反斜线符号开始,C++ 语言中定义了如下转义字符:
换行符 |
\n |
水平制表符 |
\t |
纵向制表符 |
\v |
退格符 |
\b |
回车符 |
\r |
进纸符 |
\f |
报警(响铃)符 |
\a |
反斜线 |
\\ |
疑问号 |
\? |
单引号 |
\' |
双引号 |
\" |
字符串字面值常量用双引号括起来的零个或者多个字符表示。不可打印字符表示成相应的转义字符。为了兼容 C 语言,C++ 中所有的字符串字面值都由编译器自动在末尾添加一个空字符。
两个相邻的仅由空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值。这使得多行书写长字符串字面值变得简单。 在一行的末尾加一反斜线符号可将此行和下一行当作同一行处理,后继行行首的任何空格和制表符都是字符串字面值的一部分。
变量提供了程序可以操作的有名字的存储区。
1. 左值(发音为 ell-value):左值可以出现在赋值语句的左边或右边。
2. 右值(发音为 are-value):右值只能出现在赋值的右边,不能出现在赋值语句的左边。
一般而言,对象就是内存中具有类型的区域。
C++ 中的标识符都是大小写敏感的。标识符不能包含两个连续的下划线,也不能以下划线开头后面紧跟一个大写字母。有些标识符(在函数外定义的标识符)不能以下划线开头。
变量命名习惯
• 变量名一般用小写字母。例如,通常会写成 index,而不写成 Index 或INDEX。
• 标识符应使用能帮助记忆的名字,也就是说,能够提示其在程序中的用法的名字,如 on_loan 或 salary。
• 包含多个词的标识符书写为在每个词之间添加一个下划线,或者每个内嵌的词的第一个字母都大写。例如通常会写成 student_loan 或studentLoan,而不写成 studentloan。
C++ 支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中,C++ 中初始化和赋值是两种不同的操作:
int ival(); // direct-initialization int ival = ; // copy-initialization
当定义没有初始化式的变量时,系统有时候会帮我们初始化变量。这时,系统提供什么样的值取决于变量的类型,也取决于变量定义的位置。内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。
未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态。当被解释成整型值时,任何位模式都是合法的值——虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃。可能的结果是导致程序错误执行和/或错误计算。
建议每个内置类型的对象都要初始化。虽然这样做并不总是必需的,但是会更加容易和安全,除非你确定忽略初始化式不会带来风险。
变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。
声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。可以通过使用extern 关键字声明变量名而不定义它。
extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。只有当 extern 声明位于函数外部时,才可以含有初始化式。任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
extern int i; // declares but does not define i int i; // declares and defines i
我们可以把一个非 const 变更定义在一个文件中,假设已经做了合适的声明,就可在另外的文件中使用这个变量:
// file_1.cc int counter; // definition // file_2.cc extern int counter; // uses counter from file_1 ++counter; // increments counter defined in file_1
与其他变量不同,除非特别说明,在全局作用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定 const 变更为 extern,就可以在整个程序中访问 const 对象:
// file_1.cc // defines and initializes a const that is accessible to other files extern const int bufSize = fcn(); // file_2.cc extern const int bufSize; // uses bufSize from file_1 // uses bufSize defined in file_1 for (int index = ; index != bufSize; ++index) // ...
引用是一种复合类型,通过在变量名前添加“&”符号来定义。复合类型是指用其他类型定义的类型。引用只是它绑定的对象的另一名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象
int ival = ; int &refVal = ival; // ok: refVal refers to ival int &refVal2; // error: a reference must be initialized int &refVal3 = ; // error: initializer must be an object
const 引用是指向 const 对象的引用:
const int ival = ; const int &refVal = ival; // ok: both reference and object are const int &ref2 = ival; // error: non const reference to a const object
非 const 引用只能绑定到与该引用同类型的对象。const 引用则可以绑定到不同但相关的类型的对象或绑定到右值。
typedef 可以用来定义类型的同义词:
typedef double wages; // wages is a synonym for double
枚举的定义包括关键字 enum,其后是一个可选的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员列表。枚举成员值可以是不唯一的。
// point2d is 2, point2w is 3, point3d is 3, point3w is 4 enum Points { point2d = , point2w,point3d = , point3w };
每个类都定义了一个接口和一个实现。接口由使用该类的代码需要执行的操作组成。实现一般包括该类所需要的数据。实现还包括定义该类需要的但又不供一般性使用的函数。类定义以关键字 class 开始,其后是该类的名字标识符。类体位于花括号里面。花括号后面必须要跟一个分号。
定义变量和定义数据成员存在非常重要的区别:一般不能把类成员的初始化作为其定义的一部分。当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数的特殊成员函数控制初始化。
C++ 支持另一个关键字 struct,它也可以定义类类型。用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。
一般类定义都会放入头文件。头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、extern 变量的声明和函数的声明。因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数
设计头文件时,应使其可以多次包含在同一源文件中,这一点很重要。我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。使得头文件安全的通用做法,是使用预处理器定义头文件保护符。头文件保护符用于避免在已经见到头文件的情况下重新处理该头文件的内容。
预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理器指示不同。#define 指示接受一个名字并定义该名字为预处理器变量。#ifndef 指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现 #endif。可以使用这些设施来预防多次包含同一头文件:
#ifndef SALESITEM_H #define SALESITEM_H // Definition of Sales_itemclass and related functions goes here #endif