2.1 初识声明,类型与对象
在 C++ 中,声明可以将一个标识符与一个类型或者对象联系起来。
为了理解声明的作用,这里我们也简要介绍一下类型和对象的概念。
类型与对象
在 C++ 中,内存的基本单位是字节。一个对象占据内存中的一块区域,也即多个连续的字节(可能是0个)。
并且,对象具有一个类型。对象的类型决定了对象的值的集合,以及对象上可以进行的操作。
当我们声明一个对象时,需要指定对象的类型;通过声明,我们创造了这个对象。
举例而言,在关键字中提到了 int
,这个关键字表示的是整数类型。整数类型的对象具有整数值,并具有整数运算的行为。
假设我们已经有整数类型的对象 a
、b
和c
,我们可以对a
、b
进行加法运算,然后把结果赋值给 c
:
c = a + b
由于 a
、b
和c
都是整数类型的对象,所以这个计算符合整数加法的规则。例如,如果 a
和 b
的值分别是 1
和 2
,那么 c
的被赋予的值将是 3
。
而如果 a
或者 b
不是整数类型,那么这个计算的规则就不一定是整数加法了。
抽象用语的内存
在这里,“内存”并不是具体的指个人电脑的内存条提供的RAM存储器,而是一个抽象机的描述用语。例如,在寄存器上的一个字节,对于 C++ 而言也是内存里的一个字节,实现可以根据对象的使用情况编译为需要的具体调度形式。在高等编程语言中,这样的抽象将语言和硬件实现分离,使得语言可以适用于不同的硬件平台,程序员也无需阅读硬件手册来编写程序。
对象与变量
很多时候,人们会用“变量”来描述类似“对象”的概念。在 C++ 的术语中,变量不一定是对象。
在日常交流环境中,这两个术语是可以互换的。本文为了保持必要的严谨性,会尽量正确使用“对象”和“变量”这两个术语。
简单声明
在 C++ 中,声明的形式是:类型 标识符;
或者 类型 标识符 初始化器;
。不要忘了最后的分号 ;
,这个分号表示声明的结束。
初始化器有很多形式,在这一章节中我们只介绍最简单的形式,即 = 字面量
。
举例而言,下面的代码声明了一个整数类型的对象 a
:
int a;
上面这个声明的作用是创造一个整数类型的对象,将标识符 a
与这个对象联系起来。这样,我们就可以使用 a
来表示一个整数类型的对象。
在声明的同时,我们也可以对对象进行初始化。例如:
int b = 42;
这个声明的作用是创造一个整数类型的对象,将标识符 b
与这个对象联系起来,并且将 b
初始化为整数值 42
。
用语与惯例
在这里,技术性来说,int
是一个标识符,并且是一个关键字。在这里表示某一种特定的整数类型;a
和 b
是标识符,这个标识符与特定的对象绑定。
但在现实生活中,人们并不会用如此严格的语言来描述这些概念,不会费劲的说什么“标识符a
所绑定的的一个标识符int
所代指的类型的对象” 。
因为 int
就代表这个类型,a
就代表这个对象。所以,这里人们通常直接说 int
类型,a
对象,b
对象,这样的描述在交流中更有效率也更容易理解。
不确定值
以没有初始化器的形式声明一个对象的时候,对象被默认初始化,此时这个对象被初始化为一个不确定的值。例如:
int c; // c 的值是不确定的
这个不确定值可能是内存中的垃圾值,也可能是编译器生成的特殊值,没有可以确定的性质(注意:随机性也是一种性质,程序员不能确定这个值具有随机性)。
如果一个对象的值是不确定的,使用其值会导致错误,例如:
int c;
int d = c;// 错误:c 的值是不确定的
不确定值、默认初始化与用户自定义类型
不确定值并不是一个随机值,它没有确定的随机性质。数学的来讲,随机数应当具有一定的熵,而不确定值可以一直是0
,并在更换了电脑显示器之后一直是111445
。程序员不能确定这个值具有什么性质,因此不能使用这个值。
默认初始化的规则对于用户自定义类型会有所不同,技术性地说,这里的不确定值规则仅限内置类型的默认初始化。对于更复杂类型的默认初始化,我们会在后面的章节中介绍。
初识类型
为了方便这一章的理解,以及避免引入过多的概念造成理解困难,这里介绍几种基本的类型。
int
是整数类型,此类型的对象可以存储整数值。例如:42
、-42
这样的字面量都是整数值(注意没有后缀),可以用这样的字面量来初始化整数类型的对象。- C++ 规定
int
类型的对象至少占用2个字节。在常见的机器上,int
类型的对象占用4个字节。
- C++ 规定
bool
是布尔类型,此类型对象可以存储逻辑值。例如:true
、false
这样的字面量都是布尔值,可以用这样的字面量来初始化布尔类型的对象。- C++ 没有规定
bool
具体占用多少字节。在常见的机器上,bool
类型的对象占用1个字节。
- C++ 没有规定
char
是字符类型,此类型对象可以存储字符。例如:'a'
、'b'
这样的字面量都是字符值,可以用这样的字面量来初始化字符类型的对象。- C++ 规定
char
类型的对象占用1个字节。
- C++ 规定
“整数类型”指什么?
“整数类型”这一说法有时候也会指代所有具有整数性质的类型,这里提到的 int
、 bool
和 char
都属于具有整数性质的类型。在本节中,为了方便表述,使用“整数类型”这一说法指代 int
类型。而在后面的章节中,则倾向直接使用“ int
类型”这一说法。
初识转换
当需要的类型和提供的类型不一致时,C++ 会尝试进行一些简单的转换。简单转换不是术语,只是 C++ 的转换规则中的一部分,为了描述需要,这里专门提出来一部分。
例如,当我们需要一个整数类型的对象,但是提供的是一个字符类型的对象时,C++ 会尝试将字符类型转换为整数类型。
int a = 'a';
这个声明的作用是将标识符 a
与整数类型联系起来,并且将 a
初始化为字符 'a'
以 ASCII 编码对应的整数值(即 97
)。
目标类型 | 提供类型 | 提供的值 | 得到的值 |
---|---|---|---|
int | bool | true | 1 |
int | bool | false | 0 |
bool | int | 0 | false |
bool | int | 任意非零值 | true |
bool | char | \0 | false |
bool | char | 非\0 值 | true |
char | bool | true | '\1' |
char | bool | false | '\0' |
int 与 char 的转换
int
与 char
显然有着不同的大小,更小的 char
的值无法与 int
的值产生对应,因此需要一定的规则来进行转换。
假设 int
的大小是2字节,1字节的比特数是8,那么 int
的值范围是[-2^15, 2^15-1]
,也即[-32768, 32767]
。此外,再假设 char
是有符号的,那么 char
的值范围是[-2^7, 2^7-1]
,也即[-128, 127]
。
C++ 规定,int
使用补码表示,因此,int
的-1
表示为 0b1111111111111111
。
当从 char
转换到 int
时,char
的值会被扩展到 int
的大小,此时会发生符号扩展。例如,char
的值-1
,其比特为0b11111111
。会被扩展为int
的值-1
(0b1111111111111111
),而不是255
(0b0000000011111111
)。
当从 int
转换到 char
时,int
的值会被截断到 char
的大小,此时会按照字节宽度取余。例如,int
的值193
,其比特为0b0000000110000001
。会被截断为char
的值 -63
(0b10000001
)。
这里为了方便表述,假设了 int
为2字节,否则在描述比特的时候就会写出过多的0和1。对于常见的4字节 int
的情况,这里的规则是通用的,只是数值范围会有所不同。
此外,在特定实现中,char
可能是无符号的,这会导致 0b0000000110000001
被截断为 193
,而不是 -63
,但比特位上的结果是一样的 0b10000001
。