2.1详解变量的定义、初始化与赋值

这一节我们将继续《1.3变量初探》的内容。

在前的课程中我们谈到,变量其实就是一块内存空间的名称。简要地说,计算机拥有可供程序使用的随机存取存储器(RAM),当一个变量被定义时,一部分内存就会被预留给这个变量。

内存的最小单位是二进制数字(binary digit,bit,比特),0或者1。你可以把bit想象成传统的电灯开关——要么是关的(0),要么是开的(1),不存在两者之间的状态。如果你要查看一个随机的内存片段,你可能看到…011010100101010…或者它们的组合。内存按照称为“地址”的连续单元进行组织,就像可以通过门牌号找到一条街上的某户人家,内存地址使我们能够找到和访问一块特定内存的内容。但在现代计算机中,并没有给每一个bit都分配地址,最小的可寻址内存单元称为字节(byte),包含8个bit。

下图展示了一些连续的内存地址以及相应字节的数据。内存地址

因为计算机中的所有数据实际上就是位序列,所以我们需要以一些有意义的方式来解释内存中的内容,“数据类型”就是用来告诉我们以何种方式解释内存中的内容的。从前面的课程你应该已经了解到一种数据类型:整型。当我们把一个变量声明为整型时,就是在告诉编译器“这个变量地址所对应的内存空间中的数据应当被解释为一整个数字”。

当我们把一个值赋给一种数据类型的变量时,编译器和CPU就会按照这种数据类型将该数据编码为合适的位序列存放于内存中,当你提出访问请求时,再将位序列“重组”返回给你。

除了整型数据,C++中还有很多种数据类型,我们稍后会一一讲到。

基本数据类型

C++内建了对一些特定数据类型的支持,它们被称为基本数据类型(C++标准规范中称为fundamental data types),也常不正式地称为basic types、primitive types或者built-in types。

分类 数据类型 含义 举例
布尔型 bool true or false true
字符型 char, wchar_t, char16_t, char32_t a single ASCII character ‘c’ char16_t, char32_t 仅C++11支持
浮点型 float, double, long double a number with a decimal 3.14159
整型 short, int, long, long long a whole number 64 long long 仅C99/C++11支持
void void 空类型 n/a

变量定义

在C++基础一章中,我们已经学会如何定义一个整型变量:

int nVarName;   // int为数据类型,nVarName为变量名

定义其他类型的变量也是一样的:

type varName;   // type为数据类型如int、bool,varName为变量名

举例如下,定义了五种数据类型的变量:

bool bValue;

char chValue;

int nValue;

float fValue;

double dValue;

需要注意的是void有其特殊用法,不可用来定义变量:

void vValue;   // 错误!

变量初始化

变量定义后,可以立即给它一个值,我们称这为“变量初始化”,简称“初始化”。

C++中提供了两种初始化方法,第一种为“复制初始化”(copy initialization):

int nValue = 5; // 复制初始化

注意此处的等号只是语法的一部分,与变量创建完成后用来赋值的符号含义不同。

第二种称为“直接初始化”(direct initialization):

int nValue(5); // 直接初始化

直接初始化从形式上来看非常像函数调用,编译器会持续跟踪哪些是变量名哪些是函数名以便能够正确处理。

对于一些数据类型来说,使用直接初始化效果比复制初始化要好,当我们涉及到类时,还会了解到直接初始化的其他优点。而且,直接初始化有助于我们区分初始化和赋值,因此,我们建议使用直接初始化而不是复制初始化。

注:推荐使用直接初始化

C++11中的统一初始化

随着C++的不断发展,复制初始化和直接初始化仅能适用于某些类型的变量,例如这两种初始化方法都不能用来初始化列表,为了提供一种适用于所有数据类型的初始化机制,C++11新增了一种初始化形式:统一初始化

int value{5};

使用空括号初始化变量称为“缺省初始化”,缺省初始化会将变量初始化为0(或者为空,如果对于某种数据类型来说空更合适):

int value{}; // 默认初始化为0

统一初始化有一个好处,不允许收缩类型转换(即隐式转换,例如浮点型转整型),这意味着,如果你试图以统一初始化的方式,用一个不能安全保存的数据来初始化变量,编译器将会报错或警告。例如:

int value{4.5}; // 错误:整型变量不能安全保存非整型数据

也有地方称“统一初始化”为“列表初始化”,但我们更倾向于使用“统一初始化”,因为不管是初始化还是变量都不必是列表。

注:如果你的编译器兼容C++11,请使用“统一初始化”。

变量赋值

如果在一个变量定义之后才给他一个值,则称为“拷贝赋值”,简称赋值:

int nValue;

nValue = 5; // 拷贝赋值

C++中是没有直接或者统一赋值的。

未初始化变量

未被初始化的变量称为“未初始化变量”,在你为其分配合法值之前,未初始化变量的值是垃圾值,参看《1.3变量初探》

注:尽量对变量进行初始化,要不就在定义之后尽快对其赋值。

定义多个变量

可以在同一语句中定义多个同种类型的变量,变量之间使用逗号隔开,下面两段代码是一样的:

int a, b;
int a;
int b;

同样可以在同一个语句中初始化多个变量:

int a = 5, b = 6;

int c(7), d(8);

int e{9}, f{10};

新人常会犯下面三个错误

1、给同一语句中的每个变量都定义类型,编译器通常会提示你修复这个错误:

int a, int b; // 错误

int a, b; // 正确

2、在同一行定义多种类型变量,这是不允许的。不同类型的变量必须在不同语句中定义,编译器同样会提示你这个错误。

int a, double b; // 错误

int a; double b; // 正确

// 正确且推荐(易于阅读)
int a;
double b;

3、试图用一个初始化语句初始化多个变量,这个错误是非常危险的。

int a, b = 5; // 错误,a未被初始化

int a= 5, b= 5; // 正确

上面的代码中,变量a未被初始化,并且编译器很可能不会报错,而你误认为a、b均被初始化为5,这可能导致程序的间歇性崩溃和散发性的结果。

记住这个错误最好的方法是想象一下直接初始化和统一初始化的情形:

int a, b(5); // 错误,明显不协调

int c, d{5}; // 错误,明显不协调

这样看起来似乎就很明显了,5仅被分配给了b。

因为在同一行定义和初始化多个变量常会引起错误,所以我们建议只在不初始化任何变量时在同一行定义多个变量。

注:避免在同一语句中定义多个变量如果要对其中的变量进行初始化。

在何处定义变量

老式的C编译器强制用户在函数最开始定义所有的变量:

int main()
{
    // 在开头定义所有变量
    int x;
    int y;

    // 然后编写其他代码
    std::cout << "Enter a number: ";
    std::cin >> x;

    std::cout << "Enter another number: ";
    std::cin >> y;

    std::cout << "The sum is: " << x + y << std::endl;

    return 0;
}

这种方式现在已经过时了,在C++中不需要在函数一开始定义所有变量,可以随用随定义,优秀的编码风格是在尽量靠近第一次使用变量的地方定义变量

int main()
{
    std::cout << "Enter a number: ";
    int x; // 下一行需要使用x,所以在此处定义
    std::cin >> x; // first use of x

    std::cout << "Enter another number: ";
    int y; // 此时才需要y,所以在此处才定义y
    std::cin >> y; // first use of y

    std::cout << "The sum is: " << x + y << std::endl;

    return 0;
}

这样做有非常多的好处。

首先,何处使用何处定义可以明了地看出变量的意义。如果在函数一开头定义一个变量,我们必须浏览整个函数找出这个变量在何处第一次使用才能看得出它的含义,在上面的例子中我们在输入输出语句之间定义x可以很明显的看出x是用于输入输出的。

其次,仅在需要的地方定义变量,告诉我们这个变量不会影响到定义上面的任何内容,使我们的程序易于理解并且减少了鼠标的滚动。

最后,这种方式减少了无意中留下一个未初始化变量的可能性,因为我们可以定义然后立刻赋给它我们需要的值。

大多数情况下,我们可以在第一使用这个变量的前一行定义这个变量,但偶尔你也会遇到一种情况使得这种做法变得不可取(由于性能原因)或不可行(变量将被破坏并且以后需要用到),在以后的章节中我们会看到这样的案例。

注:尽量在第一次使用的地方定义变量。