探秘C++系列——变量的那些事儿


变量是所有编程语言中一个非常基础的概念,但是其背后也有一些深究的知识点。本篇文章就和大家聊一聊C++中的变量。


什么是变量

变量就是保存数据内存地址的名称。
比如定义int a=1; a就是变量的名字,我们定义了这个变量后,系统就会为这个变量分配一个内存地址,然后就可以通过这个地址将相应的数据写到内存中。因为计算机是通过一段地址来寻找要操作的数据,或者是往里面写入数据,但是地址通常是一段又长又奇怪的东西,如果让我们程序员去记每一个数据的地址名,那也太反人类了吧?于是像C++还有各种高级语言就有了变量这个概念。我们可以为数据的内存地址起个我们自己喜欢的名字,这样后续就方便我们使用了。
对于C++程序员,“变量”和“对象”一般可以互换使用。


变量的初始化

定义时可以为一个或多个变量赋初始值,即 初始化(initialization)
如 int a=1; 定义了一个变量a,将它的值初始化为1。两件事写在一行里。
此外 int a (1)这种初始化方式也行。但是C++11中有更好的方式,待会儿会介绍。
需要注意的是:初始化不等于 赋值(assignment)。初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,再用一个新值来替代。

//赋值
int a;
a=1;

用花括号初始化变量称为列表初始化(list initialization)。这是C++11的新特性。当用于内置类型的变量时,如果使用了列表初始化并且初始值存在丢失信息的风险,则编译器会报错。这在类型转换中比较常见。什么意思呢?我们来举个例子:

double ld=3.1415926536;
int a{
    
    ld}, b = {
    
    ld};    // error: 转换未执行,因为存在丢失信息的危险     
int c(ld), d = ld;      // ok: 转换执行,且确实丢失了部分值

变量的默认初始化

如果定义变量时未指定初值,则变量被 默认初始化(default initialized)
1)定义于任何函数体之外的变量被初始化为0,如全局变量。

2)函数体内部的变量将不被 初始化(uninitialized)。定义于函数体内的内置类型对象如果没有初始化,则其值未定义,使用没有初始化的变量是一种错误的编程行为且很难调试!!!
可能会有很诡异的现象!!!
可能变量在没有初始化和赋值前,里面有以前的数据,这些数据是杂乱的,所以在没有初始化就使用变量,出现的结果也是无意义的。出现任何结果也是可能的
所以有的时候如果能确定自己程序的逻辑基本没有问题,但是结果不是自己期望的,不妨检查一下变量是否已经初始化,或者没给它赋值就拿去用了。
我之前在做C++算法题的时候,经常因为这点得到了错误的结果。调式程序一看,原来某个变量的初始值不是0。这就是使用未初始化的变量可能出现的后果。
经测试,在dev c++中使用未初始化的变量程序会正常运行,但是有潜在的风险。 而在Visual Studio 2019中,直接会给你警告
Visual Studio 2019

3)以上说的变量是指普通变量。对于静态变量,就是定义时在最前面加static的变量,无论是在函数外部还是内部,都默认初始化0。

4)类的对象如果没有显式初始化,则其值由类的代码确定。


全局变量、静态变量、局部变量

在这里插入图片描述

1)全局变量,静态全局变量,静态局部变量都是存储在静态存储区(全局数据区)。这是一种静态存储。就是变量定义的时候,就分配了一定的内存单元。既然储存在什么地方是事先(程序运行前,也就是编译期)定好的,那么就干脆给它一个默认值为0,反正也不影响程序运行期间的效率。
而局部变量是在栈上开辟空间的。每次函数调用时动态地在栈上产生。这是一种动态存储为了提高效率,系统把初始化变量的权利交给了程序员自己,不会为我们赋初值。但是如果程序员忘了初始化变量或给它重新赋值,系统给变量分配的栈空间上可能原本就有其他的值,造成了隐患。所以说局部变量的默认值是不确定的。

2)全局变量和全局静态变量的区别
二者都是储存在静态存储区。
但是全局变量的作用域是整个项目。它只需要在一个源文件中定义就可以作用于所有的源文件,其它不包括全局变量定义的文件需要用extern关键字再次声明这个全局变量。
而静态全局变量的作用域是定义它的程序文件。不能作用于项目里的其它文件

3)静态局部变量和(动态)局部变量的区别
它们的作用域相同,都是只能在定义自己函数内部使用。但内存释放周期不同(生命期)。动态局部变量当函数执行结束后,就释放内存。静态局部变量在程序运行期间一直存在。它只能被初始化一次。什么是只能被初始化一次呢?来看下面这个程序:

int fun(){
    
    
	static int num=0;
	return ++num;
}
int main(){
    
    
	for(int i=0;i<10;i++){
    
    
		cout<<fun()<<" ";
	}
	return 0;
}

输出结果:1 2 3 4 5 6 7 8 9 10
注意看,我在main函数里循环调用fun()函数,虽然fun()函数里有个初始化静态局部变量num的操作,但是每次循环不会把num的值重置为0,而是保存了num自增后的值。这是因为num还在内存中,不会因为退出函数被销毁。所以只有第一次调用的时候才初始化一次,并且函数执行完后再次调用相同的函数会沿用这个静态局部变量的值。

总结
局部变量->静态变量:改变了它的存储方式和生命期
全局静态变量-> 全局变量:改变了它的作用域

变量的声明和定义

为了支持分离式编程,C++需要把声明和定义区分开。
声明(declaration) 告诉编译器有这个变量存在(使得名字为程序所知)。一个文件如果想使用其他地方定义的名字,则必须先包含对那个名字的声明。
定义(definition) 负责创建与名字相关联的实体。定义除了告诉编译器有这变量存在之外,还为变量分配空间,进行了初始化

如果想声明一个变量而不定义它,就在变量名前添加关键字 extern,并且不要显式地初始化变量。

extern int i; // 声明但不定义 i
int j;      // 声明并定义 j
extern int k=0 //定义

extern语句如果包含了初始值就不再是声明了,而变成了定义。

变量能且只能被定义一次,但是可以被声明多次。

如果要在多个文件中使用同一个变量,就必须将声明和定义分开。此时变量的定义必须出现且只能出现在一个文件中,其他使用该变量的文件必须对其进行声明,但绝对不能重复定义。

变量名字的作用域

{ } 是一个块作用域
定义在函数体之外的名字拥有 全局作用域(global scope)。声明之后,该名字在整个程序范围内都可使用。最好在第一次使用变量时再去定义它。这样做更容易找到变量的定义位置,并且也可以赋给它一个比较合理的初始值。

作用域中一旦声明了某个名字,在它所嵌套着的所有作用域中都能访问该名字。同时,允许在内层作用域中重新定义外层作用域已有的名字,此时内层作用域中新定义的名字将屏蔽外层作用域的名字

int main() {
    
    
	int num = 1;
	{
    
    
		int num = 2;
		cout << num << endl;
	}
	cout << num << endl;
	return 0;
}
//输出结果:
2
1

当全局变量和局部变量重名时,可以用作用域操作符 :: 来访问全局变量。因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,会向全局作用域发出请求获取作用域操作符右侧名字对应的变量。这种方法可以用于当作用域中存在和全局变量名字相同的局部变量时,访问全局变量
来看示例:

int num;
int main() {
    
    
	int num = 1;
	{
    
    
		int num = 2;
		cout << num << endl; 
		//注:在此代码块中无法直接访问外层值为1的num,因为重新定义了num
		// ::num只能访问此方法之外的全局变量
	}
	cout << num << endl;
	cout << ::num << endl;  //访问全局变量的num
	return 0;
}
//输出:
2
1
0

猜你喜欢

转载自blog.csdn.net/qq_46044366/article/details/119602816