1.3变量、初始化与赋值初探

变量

一个例如x=5的语句意义已经显而易见:将5分配给x,那么我们要问了,x是什么?x就是我们所说的变量。

C++中,变量的实质是一块用于存储信息的内存区域的名称,你可以把它想成一个邮箱,或者一间小屋,可以存放或取回信息。所有的电脑都有内存,称为随机存取存储器(RAM,random access memory),供程序使用。当定义一个变量时,一块内存就会被留出来使用。

这一节,我们暂时只考虑整型变量,即一个拥有整数值的变量。通常我们使用一个声明语句来定义变量,下面的例子定义了一个整型变量。

int x;

当CPU执行这个语句时,RAM中的一块内存就会被预留出来(称为实例化instantiation)。比如,变量x被分配的内存位置为140,那么每当程序在表达式或者语句中看到x时,就会知道要去140位置寻找x的值。

对变量的最常见操作是赋值,赋值需要使用赋值操作符,即大家熟知的“=”符号。例如:

x = 5;

当CPU执行这个语句时,它被翻译为“将5放到内存区域140中”。

我们的程序接下来会用std::cout把这个值打印到屏幕上。

std::cout << x;  // 把x的值打印到屏幕上

左值与右值

C++中变量是一种左值(l-value),左值是一种可寻址的值(a value that has an address (in memory),看英文意义更明了)。由于所有的变量都有地址,所以所有的变量都是左值,称为左值是因为左值是唯一能够放在赋值操作符左边的值。做赋值运算时,赋值运算符的左边必须是一个左值。因此,语句5 = 6;将产生一个一个编译错误,因为5不是一个左值。5没有相应的内存区域,因此没有什么可以被分配给它。5就是5,它的值不能被重新分配。当一个值被分配给左值时,左值所在内存地址的值就会被重写。

左值对应的为右值,右值是指任何可以被分配给左值的值,右值的计算值通常为一个单一数字。右值可以是数字5(值为5),或者一个变量x(值为最后被分配的值),或者一个表达式x+2(值为x加2)。下面是几个展示右值是如何求值得例子。

int y;      // 定义整型变量y
y = 4;      // 4的值为4,被分配给y
y = 2 + 5;  // 2 + 5的值为7,被分配给y,之前的y值被覆盖
int x;      // 定义整型变量x
x = y;      // y的值为7,被分配给x
x = x;      // x的值为7,被分配给x(无用的语句)
x = x + 1;  // x+1的值为8,被分配给x,所以最终y的值为7,x的值为8

我们来看一下上面的最后一个语句,这句是最会造成困惑的。

x = x + 1 ;

这个语句中,x被使用在两种环境中,在赋值运算符“=”左边,作为左值(在内存中有相应地址的变量),在“=”右边,作为右值,被用来产生一个具体值(此例中为7)。即:

x = 7 + 1;

这样看就很明显了,将8分配给x。

程序员是不经常谈论左值右值得,所以也没有必要过于深究。记住关键的就好,等号左边的值必须能够代表一段内存地址(比如变量),右边的值必须有特定的计算值。

赋值与初始化

初学者会遇到两个经常混淆的概念,赋值和初始化

变量被定义后,可能会通过“=”号被分配一个值(赋值)。

int x;
x = 5; //此为赋值

C++允许在定义变量的同时给予其一初始值,这称为初始化

int x = 5;

显而易见,变量在初始化之前必须定义。

虽然这两个概念在本质上相似的,并且达成相似的目标。但在未来的课程中,我们会遇到某些类型大的变量只允许初始化,不允许赋值,所在区分一下两者还是有意义的

未初始化的变量

与一些编程语言不同,C/C++在声明变量的同时不会自动给其赋值(比如赋0值)(这是出于性能的考量),所以当一个变量被分配到内存中的某个区域时,其默认值将该区域已有的任何可能的值,一个未被赋值的变量称为未初始化变量。

:某些编译器,比如Visual Studio,在调试模式下会自动初始化内存中的内容,在发布模式下则不会。

未初始化的变量可能产生一些有趣的(意料不到的)现象,我们来看下面这个小程序:

// #include "stdafx.h" // Visual Studio用户无需注释掉此代码
#include <iostream>
int main()
{
    int x;
    // 危险的操作,因为x未初始化
    std::cout << x;
    return 0;
}

此例中计算机会将一些闲置的内存分配给x,然后将该区域存放的值发送给屏幕并打印出来。但是打印出的值是什么呢?答案是谁都不知道。每次运行程序时结果都可能不同,作者用Visual Studio 2013运行的结果,一次是7177728,下一次5277592。

如果你想亲自运行这个程序有两点需要注意:

  • 确保使用的是release调试模式,(参看6a解决方案配置),否则结果可能是你所用的编译器初始化的结果(Visual Studio使用-858993460初始化)。
  • 如果编译器不允许你运行这个程序并且提示变量未初始化,可以尝试更改为这个程序:
    // #include "stdafx.h" // Visual Studio用户无需注释掉这行代码
    #include <iostream>
    
    void doNothing(const int &x)
    {
    
    }
    
    int main()
    {
          int x;
    
          // 让编译器误认为x被使用了
          doNothing(x);
    
          std::cout << x;
    }

    使用未初始化的变量是新手常犯的错误之一,不幸的是这也是今后调试程序的挑战之一(因为如果未初始化的变量恰好被分配到一块有合理值比如0的内存区域,程序可能就会正常运行)。

幸运的是,许多现代编译器在编译时检测到一个未初始化的变量时,就会给出相应的警告。例如,在Visual Studio 2005中编译上述程序时会给出如下警告:

c:vc2005projectstesttesttest.cpp(11) : warning C4700: uninitialized local variable ‘x’ used

一个很好的经验法则是在定义的同时就进行初始化,这会确保你的变量始终有一个一致的值,程序出错时也便于调试。

规则:始终记得初始化你的变量。

小测验

下面的程序会打印出什么值?

int x = 5;
x = x - 2;
std::cout << x << std::endl; // #1

int y = x;
std::cout << y << std::endl; // #2

std::cout << x + y << std::endl; // #3

std::cout << x << std::endl; // #4

int z;
std::cout << z << std::endl; // #5

 答案

  1. 3
  2. 3
  3. 6
  4. 3
  5. 不确定的

转载请参看关于博客页面相关要求。