参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
2.1 基本内置类型(P30)
C++ 定义的基本类型包括算术类型(arithmetic type)和空类型(void),其中算术类型包括字符、整型、布尔值和浮点数。
2.1.1 算术类型(P30)
算术类型分为整型(包括字符、布尔值)和浮点型。
char
类型实际上会表现为 signed char
或 unsigned char
,具体是哪种由编译器决定。
如何选择类型?
- 数值不可能为负时,选用无符号类型。
- 默认使用
int
进行整型运算,使用double
进行浮点型运算。
2.1.2 类型转换(P32)
常见的类型转换:
- 把非布尔算数值赋给布尔类型时,初始值为 0 则结果为
false
,否则结果为true
;把布尔类型赋值给非布尔类型时,初始值为false
则结果为 0 ,初始值为true
则结果为 1 。 - 把浮点数赋值给整型,仅保留小数点前的部分;把整型赋值给浮点数,小数部分记为 0 。
- 当赋给无符号类型超过其表示范围的值时,其实际结果要取模;当赋给有符号类型超过其表示范围的值时,其结果未定义。
由于 C++ 并未对
int
类型的尺寸做出规定,所以我们不应把int
的尺寸看作一个固定值。
当无符号类型和有符号类型混用时,一般会将有符号类型转换为无符号类型。
这部分最好结合数的机器表示、补码等知识进行理解。
2.1.3 字面值常量
整型字面量默认为十进制带符号数,特别地,以 0
开头的整型字面量代表八进制数,以 0x
或 0X
开头的整型字面量代表十六进制数。八进制和十六进制字面值可能是带符号的,也可能是无符号的。
20 /* 十进制 */ 024 /* 八进制 */ 0x14 /* 十六进制 */
严格地说,字面值不会是负数,形如 -42
的字面值应看作对字面值 42
取负值。
浮点字面值:
3.14 3.14e0 0. 0E0 .001
由单引号括起来的单个字符为 char
型字面值,双引号括起来的字符为字符串型字面值。字符串以空字符结尾(‘\0’)。
如果两个字符串型字面值位置紧邻或仅由空格、缩进和换行符分隔时,则它们实际上是一个整体:
// 多行书写
cout << "hello"
"world";
泛化的转义序列:\x
后紧跟若干十六进制数字,\
后紧跟 1~3 个八进制数字:
\12 (换行符) \xc (换行符)
指定字面值的类型:
练习
int month = 09; // 编译报错
double d = 1024f; // 编译报错,因为1024为整型字面量,不能有f后缀
2.2 变量(P38)
变量提供一个有名字的、可操作的存储空间,其数据类型决定占用空间的大小、布局方式、值的范围、能参与的运算类型。
2.2.1 变量定义(P38)
变量定义的基本形式:类型说明符,随后紧跟一个或多个变量名(用逗号分隔)。
int a = 0, b, c = 0;
int a = b = 1; // 错误
初始化
当变量再创建时获得了初值,就称这个变量被初始化了。用于变量初始化的值可以是任意复杂的表达式。
在 C++11 后,允许统一用花括号初始化变量:
int a{0};
int b[4]{1, 2, 3, 4};
这种初始化方式会在初值存在丢失信息的风险时进行报错。
默认初始化
对内置类型的变量,如果在函数体外定义,则初始化为 0 ;如果在函数体内定义,则不会被初始化,未被初始化的变量的值是未定义的。
绝大多数类无须显示初始化,这样的类提供了一个合适的默认值,如 string
类默认空串。
2.2.2 变量声明和定义的关系(P41)
变量声明的作用是告诉编译器某个实体的存在;定义的作用是在程序中为实体分配内存。
定义变量的同时也就声明了这个变量,如果仅仅想要声明一个变量,要使用 extern
关键字,并且不能显式初始化。
变量只能被定义一次,可以被声明多次。
2.2.3 标识符(P43)
2.2.4 名字的作用域(P43)
程序中的每个名字都会指向一个特定的实体。
作用域是程序的一部分,大部分作用域用花括号分隔。
名字的有效区域始于名字的声明语句,结束于声明语句所在的作用域末端。
作用域能够嵌套,内层作用域能够访问外层作用域的名字,也能重新定义外层作用域已有的名字。
2.3 复合类型(P45)
声明语句的通用描述:一条声明语句由一个基本数据类型和紧随其后的一个声明符(declarator)列表组成。每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型。
2.3.1 引用(P45)
引用(reference)为对象起了一个别名,引用并非对象,也无法令引用重新绑定对象,因此引用必须被初始化。
int a = 0;
int &b = a;
对引用的所有操作都是在与之绑定的对象上进行的。由于引用本身并不是对象,所以不能对引用进行引用。
一般情况下,引用的类型必须和绑定的对象严格匹配,且只能绑定在对象上。
2.3.2 指针(P47)
指针是指向另一种类型的复合类型,指针本身是一个对象,允许赋值和拷贝,无须在定义时赋初值。
获取对象的地址
取地址符 &
由于引用不是对象,所以不能定义指向引用的指针。
一般而言,指针的类型要和指向的对象严格匹配。
指针值
指针的值是有效的,当且仅当以下情况:
-
指向一个对象
-
指向紧邻对象所占空间的下一个位置
可以用来判断是否越界
-
空指针
利用指针访问对象
如果指针指向了一个对象,则可以使用解引用符 *
来访问该对象。对指针解引用会得到指针指向的对象。
空指针
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;
nullptr
是一种特殊类型的字面值,可以被转换成任意类型的指针类型。NULL
是一种预处理变量,会被预处理器处理为 0 。新标准下最好使用 nullptr
并避免使用 NULL
。
int *p = 0x12345678; //错误,因为0x12345678会被当成int型字面量
赋值和指针
赋值永远改变的是等号左边的对象。
其他指针操作
指针可以用在条件表达式中;两个相同类型的指针可以比较
void*
指针
void*
指针是一种特殊的指针,可以存放任意对象的地址。我们不能直接操作 void*
指针所指向的对象。
练习
2.23 给定指针 p
,可以判断它是否指向了一个合法的对象吗?
答:个人感觉其实不可以的。首先,我们无法确定指针 p
是否有效。再者,即使 p
有效,它仍有可能是指向紧邻某对象的下一个位置。
2.3.3 理解复合类型的声明(P51)
在同一条定义语句中,基本数据类型只有一个,但声明符的形式可以有很多:
int *p, a = 0, &b = a;
指向指针的引用
int i = 0;
int *p = &i;
int *&a = p;
要理解变量 a
的类型,我们可以从右向左阅读 a
的定义,与 a
紧邻的符号(&
)对 a
有最直接的影响,所以 a
是一个引用,声明符的其余部分确定了 a
引用的类型。所以,a
是 int
指针的引用。
int *p, a = 0, &b = a;
### 指向指针的引用
```cpp
int i = 0;
int *p = &i;
int *&a = p;
要理解变量 a
的类型,我们可以从右向左阅读 a
的定义,与 a
紧邻的符号(&
)对 a
有最直接的影响,所以 a
是一个引用,声明符的其余部分确定了 a
引用的类型。所以,a
是 int
指针的引用。