块、局部变量的scope、duration
在讨论变量时,将范围和持续时间的概念分开是有用的。变量的范围确定变量可访问的位置。变量的持续时间决定了它的创建和销毁位置。这两个概念经常联系在一起。
用{.......}
括起来的程序块,内部定义的变量,作用范围也仅限本程序块,嵌套程序块可以引用外部块的变量。
#include <iostream>
int main()
{ // 外部块
int apples(5); // apples变量在外部块有效
if (apples >= 5) // 这个apples是外部块的变量
{ // 进入内部块
int apples; // 这个变量是内部块的变量,此时外部块的apples不起作用
// the outer block apples is temporarily hidden
apples = 10; // 这个不对外部那个apples起作用
std::cout << apples << '\n'; // print value of nested block apples
} // 内部块的变量销毁
// apples now refers to the outer block apples again
std::cout << apples << '\n'; // prints value of outer block apples
return 0;
} // 外部块的变量销毁
程序输出:
10
5
这段代码充分体现了块的概念,还有局部变量的作用范围和生命周期。
但是应该尽量避免这种情况
函数参数
虽然函数参数没有在属于函数的块内定义,但在大多数情况下,它们可以被认为具有函数定义的块的范围。
全局变量
全局变量声明在文件的顶部,在库文件之下,但在任何代码之上。
对于块内的局部变量会屏蔽全局变量的情况:
①对于全局变量,在命名时前面加上 g_
进行区分
①可以使用::
符号,进行声明。比如:
#include <iostream>
int value(5); // 全局变量
int main()
{
int value = 7; // 局部变量,全局变量被遮盖
++value; // 此处是局部变量
--(::value); // 此处是全局变量
std::cout << "local value: " << value << "\n";
std::cout << "global value: " << ::value << "\n";
return 0;
} // 局部变量被销毁
链接
之前讨论全局变量和局部变量,多是限定于同一个cpp文件内,但对于一个项目存在多个cpp项目,同一个变量在不同文件之间的关系和范围这些性质,就是变量的第三个属性:链接。
内部变量:具有内部链接的变量可以在定义的文件中的任何位置使用,但不能在它们存在的文件之外引用。使用关键字static
外部变量:具有外部链接的变量既可以在它们定义的文件中使用,也可以在其他文件中使用。使用关键字extern
示例:
static int g_x; //内部变量,只能在定义的文件里使用
extern double g_y(9.8); //外部变量,可以在其他文件里使用
在函数外部声明的非const变量默认为外部变量。
在函数外部声明的const变量默认为内部变量。
请注意:,如果要定义未初始化的非const全局变量,请不要使用extern关键字,否则C ++会认为您正在尝试为变量进行前向声明。
所以关键字extern
就可以表示这个变量可以进行外部链接,也可以表示声明,使用外部变量。
如果变量forward语句声明在函数之外,则它适用于整个文件。如果变量转发声明在函数内声明,则它仅适用于该块。
global.cpp:
int g_x; // 默认外部变量
extern int g_y(2); // 这个extern其实并不需要
main.cpp:
extern int g_x; // 前向声明,该变量本文件可以用
int main()
{
extern int g_y; // 前向声明,该变量只能在本函数使用,并且函数内但在声明前也是不能用的
g_x = 5;
std::cout << g_y; // should print 2
return 0;
}
该规则只对cpp
文件生效,对在.h
中定义的static等要小心,因为#include
意思是把头文件包含到代码中,所以对于包含头文件的cpp代码来说,可以使用该头文件里的局部变量。
函数链接性
函数具有与变量相同,都有链接属性。
函数始终默认为外部链接,但可以通过static关键字设置为内部链接
函数转发声明不需要extern关键字。编译器能够通过是否提供函数体来判断您是在定义函数还是函数原型。
全局变量的弊端
1、任何调用全局变量的函数都可以更改全局变量,如果有很多调用的函数,在某些位置值被改变得并非我们所期望的,这本身就是风险。
2、如果出现bug,全局变量离出现问题的地方可能很远,不利于定位问题。
所以推荐局部变量尽量定义到他们使用的地方,方便查找
3、全局变量会降低使用它的程序的模块化,因为使用全局变量的代码块并不能够完全放心地被调用,会受到全局变量的变化而可能会运行出不同的结果。
如果一定要使用全局变量,尽量做到:
①以g_
前缀来命名变量,比如即使变量是gravity
,也命名为g_gravity
。
②对于要使用全局变量的函数,把全局变量作为函数参数传递进去,这样可以提高代码的模块性。
③把全局变量定义为static
类型,禁止除了本文件以外的文件直接引用,然后通过全局函数把这个参数传递出去。
不使用这种:
double g_gravity (9.8);
使用这种方式:
static double g_gravity (9.8); // 只能本文件用
double getGravity() // 通过可以任意引用的函数传递变量
{
return g_gravity;
}
Static duration variables
(不知道怎么翻译这个变量……)
static关键字
应用于全局变量时,将全局变量定义为具有内部链接,这意味着该变量无法导出到其他文件。
static关键字
应用于局部变量时,将局部变量定义为具有静态持续时间,这意味着该变量将仅创建一次,并且在程序结束之前不会被销毁。
相当于给局部变量的生命时间从块开始创建块结束销毁,延长到整个程序的时间。
#include <iostream>
void incrementAndPrint()
{
int value = 1; // 函数调用时变量开始创建
++value;
std::cout << value << '\n'; // 函数结束时变量销毁
}
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
所以这段程序将不断打印出:
2
2
2
如果加了static
关键字:
#include <iostream>
void incrementAndPrint()
{
static int value = 1; // 函数调用时变量开始创建,只生效一次
++value;
std::cout << value << '\n'; // 函数结束时变量并不销毁,只不过不可用
}
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
所以这段程序将不断打印出:
2
3
4
时间、范围、链接 汇总
项目 | Value | s |
---|---|---|
电脑 | $1600 | jj |
手机 | $12 | |
导管 | $1 |
类型 | 例 | 范围 | 持续时间 | 连锁 | 笔记 |
---|---|---|---|---|---|
局部变量 | int x; | block范围 | 自动持续时间 | 没有联系 | |
静态局部变量 | static int s_x; | block范围 | 静态持续时间 | 没有联系 | |
动态变量 | int * x = new int; | block范围 | 动态持续时间 | 没有联系 | |
函数参数 | void foo(int x) | block范围 | 自动持续时间 | 没有联系 | |
外部非const全局变量 | int g_x; | 文件范围 | 静态持续时间 | 外部联系 | 初始化或未初始化 |
内部非const全局变量 | static int g_x; | 文件范围 | 静态持续时间 | 内部联系 | 初始化或未初始化 |
内部const全局变量 | const int g_x(1); | 文件范围 | 静态持续时间 | 内部联系 | 必须初始化 |
外部const全局变量 | extern const int g_x(1); | 文件范围 | 静态持续时间 | 外部联系 | 必须初始化 |
类型 | 例 | 笔记 |
---|---|---|
函数转发声明 | void foo(int x); | 只有原型,没有功能体 |
非const全局变量前向声明 | extern int g_x; | 必须是未初始化的 |
Const全局变量前向声明 | extern const int g_x; | 必须是未初始化的 |
命名空间 namespace
作用:程序庞大起来之后,为了避免引起命名冲突
使用方法:
namespace hello
{
//whatever you want write
//定义函数、变量等等,比如:
int fuck{5};
}
想使用namespace
里面的东西,通过标识符::
。比如hello::fuck
。
可以命名空间里面在嵌套一个命名空间,多重引用,比如:hello::world::fuck
这种形式。也可以给多重引用的命名空间起一个别名:
namespace Foo
{
namespace Goo
{
const int g_x = 5;
}
}
namespace Boo = Foo::Goo; // Boo now refers to Foo::Goo
int main()
{
std::cout << Boo::g_x; // This is really Foo::Goo::g_x
return 0;
}
对于标准库,通常都需要通过std::
来引用,比如std::cout
。
对于opencv这样的第三方库,如果使用里面的函数都需要cv::function
这样的方式调用,那么有点麻烦,可以通过:
using namespace cv;
这样的方式,这样以后就可以直接使用函数名了,但是之所以使用命名空间就是为了避免命名冲突,using namespace
又把命名空间的作用取消了,所以应该谨慎使用。
当在块内使用的,他的作用范围仅限于块内。
除了cv和std这种的可以using namespace,应该限制使用。