章节目录

局部变量

本节阅读量:

局部变量

函数体中定义的变量称为局部变量(全局变量将在未来章节讨论):

1
2
3
4
5
6
int add(int x, int y)
{
    int z{ x + y }; // z 是局部变量

    return z;
}

函数参数通常也被认为是局部变量:

1
2
3
4
5
6
int add(int x, int y) // 函数参数x y 是局部变量
{
    int z{ x + y };

    return z;
}

本课中,将更详细了解局部变量的一些属性。


生命周期

在对象和变量简介中,讨论了变量定义(如int x),执行语句时实例化(创建)变量。与此类似,函数参数在被调用时创建和初始化,函数体中的变量在定义处创建和初始化。

例如:

1
2
3
4
5
6
int add(int x, int y) // x y 在这里被创建和初始化
{ 
    int z{ x + y }; // z 在这里被创建和初始化

    return z;
}

后续问题是,“实例化变量何时被销毁?”。局部变量在定义它的花括号内的末尾以与创建相反的顺序销毁(对于函数参数,在函数的末尾)。

1
2
3
4
5
6
int add(int x, int y)
{ 
    int z{ x + y };

    return z;
} // z, y, and x 在这里被销毁

就像人的一生被定义为出生和死亡之间一样,一个变量实例的生命周期被定义为创建和销毁之间的时间。请注意,变量的创建和销毁发生在程序运行时(称为运行时),而不是在编译时。因此,生命周期是一个运行时属性。

下面是一个稍微复杂的程序,演示名为x变量的生命周期:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>

void doSomething()
{
    std::cout << "Hello!\n";
}

int main()
{
    int x{ 0 }; // x 生命周期从这里开始

    doSomething(); // 在函数调用时,x仍然存活

    return 0;
} // x 生命周期到这里结束

上面程序中,x的生命周期从定义点运行到函数main的末尾。包括执行函数doSomething期间。


局部变量作用域

变量的作用域决定了在源代码中能看到和使用变量的位置。当变量能被看到和使用时,即它在作用域内。当变量看不见,不能被使用,即它超出作用范围。作用域是编译时属性,使用不在作用域的变量将导致编译错误。

局部变量的作用域从变量定义点开始,到定义花括号集末尾(或者对于函数参数,在函数的末尾)。确保变量不能在定义点前使用(即使编译器在定义点前创建)。在函数中定义的局部变量的作用域不在其他任何函数范围内。

下面程序,演示名为x变量的作用域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>

// x 不在 doSomething 函数内可见
void doSomething()
{
    std::cout << "Hello!\n";
}

int main()
{
    // 在这里x 不能被使用

    int x{ 0 }; // 从这里到函数末尾,x可以被使用

    doSomething();

    return 0;
} // 这里到了x 的作用域的末尾

上面程序中,变量x在定义点进入它的作用域,在main函数末尾退出。请注意,即使函数main调用了函数doSomething,变量x不在函数doSomething内可见。


作用域与生命周期

作用域与生命周期,可能会让初学者感到困惑。

变量不在作用域内,即代码中无法访问。上面示例中,x从其定义点到主函数的末尾都在作用域内。

生命周期,代表变量实例化后的实例的存活时间。局部变量实例的生命周期在其超出范围的点结束,因此局部变量在此时被销毁。

生命周期内的变量,不一定在对应代码的作用域内。

注意,并不是所有类型的变量实例都在超出作用域时被销毁。以后课程中将介绍这些示例。


另一个例子

有一个稍微复杂些的例子。记住,生命周期是一个运行时属性,作用域是一个编译时属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>

int add(int x, int y) // x y变量被创建,并且进入作用域
{
    // x y 只在add函数里,可见,并且可访问
    return x + y;
} // x y 作用域结束,并且被销毁,生命周期结束

int main()
{
    int a{ 5 }; // a 创建,初始化,进入作用域
    int b{ 6 }; // b 创建,初始化,进入作用域

    // a b 只在函数main里可见
    std::cout << add(a, b) << '\n';

    return 0;
} // a b 作用域结束,并且被销毁,生命周期结束

参数x和y在调用add函数时创建,只能在函数add中看到/使用,并在add结束时销毁。变量a和b是在函数main中创建的,只能在函数main()中看到/使用,并在main的末尾销毁。

为了增强理解,我们更详细地分析程序。按顺序发生以下情况:

  1. 执行从main的顶部开始。
  2. 创建变量a并给定值5。
  3. 创建变量b并给定值6。
  4. 使用参数值5和6调用函数add。
  5. 创建add参数x和y,并分别初始化值5和6。
  6. 计算表达式x+y以产生值11。
  7. add将值11返回调用方main。
  8. y和x被销毁。
  9. main将11打印到控制台。
  10. main将0返回到操作系统。
  11. 变量b和a被销毁。

注意,如果函数add被调用两次,则参数x和y将被创建和销毁两次。在具有许多函数和函数调用的程序中,变量经常被创建和销毁。


不同函数的同名局部变量

上面例子中,很容易看出变量a和b是与x和y不同变量。

考虑以下类似程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>

int add(int x, int y) // add.x add.y变量被创建,并且进入作用域
{
    // add.x add.y 只在add函数里,可见,并且可访问
    return x + y;
} // add.x add.y 作用域结束,并且被销毁,生命周期结束

int main()
{
    int x{ 5 }; // main.x 创建,初始化,进入作用域
    int y{ 6 }; // main.y 创建,初始化,进入作用域

    // main.x main.y 只在函数main里可见
    std::cout << add(x, y) << '\n';

    return 0;
} // main.x main.y 作用域结束,并且被销毁,生命周期结束

此例中,函数main中变量a和b的名称改为x和y。程序的编译和运行是相同的,即函数main和add都有名为x和y的变量。

首先,即使函数main和add都有名为x和y的变量,但这些变量是不同的。函数main中的x和y与函数add中的x和y没有任何关系——它们碰巧名称相同。

其次,在函数main内部时,名称x和y指的是main的局部范围变量x和y。这些变量只能在main内看到(和使用)。类似地,在函数add中,名称x和y指的是函数参数x和y,它们只能在add中看到(和使用)。

简而言之,add和main都不知道另一个函数有同名变量。因为作用域不重叠,所以编译器总是很清楚在什么时候引用了哪个x和y。

在未来,将更多地讨论局部范围和其他类型的范围。


定义局部变量的位置

在现代C++中,函数体中的局部变量,定义的位置应该接近其首次使用的位置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>

int main()
{
	std::cout << "Enter an integer: ";
	int x{}; // x 在这里定义
	std::cin >> x; // 在这里使用

	std::cout << "Enter another integer: ";
	int y{}; // y 在这里定义
	std::cin >> y; // 在这里使用

	int sum{ x + y }; // sum 在这里定义
	std::cout << "The sum is: " << sum << '\n';

	return 0;
}

上面示例中,变量都是在首次使用前定义的。


2.3 函数参数简介

上一节

2.5 为什么需要函数

下一节