随笔小记-C++ 隐式初始化和显示初始化,switch下的声明和赋值
在C++primer里面这个程序:
case ture:
string file_name; //报错:控制流绕过一个隐式初始化
int ival = 0; //报错:控制流绕过一个显示初始化
int jval; //正确:没有初始化
break;
case false:
jval = next_num(); //正确:给jval赋值
if(file_name.empty()) //file_name在作用域内,但是没有被初始化
...
有时候会有一些疑惑为什么file_name会报错,ival会报错,下面的jval又会正确,为什么会这样,我们现在慢慢的解答。
C++存储区
首先看一下C++中的几个存储区:
- 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
- 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 。
- 常量区:常量字符串就是放在这里的。 程序结束后由系统释放 。
- 程序代码区:存放函数体的二进制代码。
在上述的几个存储区域中,如果定义在全局区的变量没有被用户初始化的话,编译器会自动将其初始化为0。
这里要非常注意定义两个字而不是声明。
再来看一下定义和声明的区别:从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。既然声明都不分配内存,所以自然也不可能被编译器自动初始化为0了。
结论:一些全局变量(不管用没用static修饰)或者是使用static中修饰的局部变量在定义的时候都会被编译器自动初始化为0,而在声明的时候任何变量都不会被编译器自动初始化。如static int num;如果放在函数中的任何位置都会被隐式的初始化为0,但是如果是在类的声明中这样写就不会有值。
声明和定义
变量声明和变量定义
- 变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
- 变量声明:用于向程序表明变量的类型和名字。
- 定义也是声明,extern声明不是定义
注意:变量在使用前就要被定义或者声明。 在一个程序中,变量只能定义一次,却可以声明多次。 定义分配存储空间,而声明不会。
C++程序通常由许多文件组成,为了让多个文件访问相同的变量,C++区分了声明和定义。
变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。在程序中,变量有且仅有一个定义。
声明(declaration)用于向程序表明变量的类型和名字。定义也是声明:当定义变量的时候我们声明了它的类型和名字。可以通过使用extern声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。
extern声明不是定义,也不分配存储空间。事实上它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern。
任何在多文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
如何清晰的区分变量声明和定义
extern通知编译器变量在其他地方被定义
extern int i; //声明,不是定义
int i; //声明,也是定义,未初始化
如果声明有初始化式,就被当作定义,即使前面加了extern。 只有当extern声明位于函数外部时,才可以被初始化。
extern double pi=3.141592654; //定义
函数的声明和定义区别比较简单,带有{ }的就是定义,否则就是声明。
除非有extern关键字,否则都是变量的定义。
extern int i; //声明
int i; //定义
测试
测试定义的全局和局部的int,double, char, bool,string类型的变量:
#include<iostream>
using namespace std;
int i_temp;
double d_temp;
char c_temp;
string s_temp;
bool b_temp;
int main()
{
int n, m;
string s ;
string str = "abc";
m = 10;
// m = n;
//str = s;
char ch;
int i_temp1;
double d_temp1;
char c_temp1;
string s_temp1;
bool b_temp1;
i_temp = i_temp1;
d_temp = d_temp1;
c_temp = c_temp1;
b_temp = b_temp1;
s_temp = s_temp1;
cout << i_temp << ' ' << d_temp << ' ' << c_temp /*<< s_temp */<< ' ' << b_temp<< ' '<< c_temp << endl;
// cout << s << endl;
cout << "00000000000000" << endl;
return 0;
}
发现全局变量int,double, bool,char 分别初始化为0, 0, false,‘0’,可以参与运算输出;但string初始化为”“。注意这时string不可以输出,但是可以赋值,都已经被隐式初始化。可能是因为是”“,输出时会显示错误。我们把其赋值给已经初始化的string不会报错。
注意:“ ”里面是没有字符的,显示是好的,发布出去就变成了里面有一个空格符,里面是空的。
但是在局部变量时,int,double, bool,char 类型变量被随机分配一个相应类型的数字(没有被初始化),但是该数字不可以参与运算赋值输出,当我们进行赋值和输出时,编译器会显示没有初始化;string类型仍然被隐式初始化为”“,可以赋值。
switch分支变量声明
我们先看这样一个程序:
switch(a)
{
case 0: int b=1;cout<<b<<endl;break;
case 1: cout<<b<<endl;break;
default:break;
}
对于一个局部变量,它的作用域为它所定义的地方到它所在的语句块结束为止,那么对于变量b,它所在的最小语句块为switch{}块,那么也就说在case 0后面的部分,在case 0中声明的变量都是可见的(注意:在case 0无法访问之后定义到的变量)。考虑这样一种情况,当a的值为1,那么程序就跳到case 1执行,此时b虽然可以访问,但是跳过了它的初始化过程。而如果在定义变量的同时进行了初始化,表明程序员希望初始化这个变量,但是此时跳过了该变量的初始化,就可能导致程序出现程序员无法意料的情况,因此编译器为了避免跳过这样的初始化而造成无法预料的结果,就对该语句进行报错。
switch(a)
{
case 0: int b;b=0;cout<<b<<endl;break;
case 1: cout<<b<<endl;break;
default: break;
}
//不会报错,编译会有warming提示变量b没有初始化。
switch(a)
{
case 0: break;
default: int b=1;cout<<b<<endl;break;
}
这段代码没有报错。因为如果执行case 0,变量b没有进行初始化,但是由于在case 0部分b是不可见的,因此不会对程序造成任何影响,而如果执行default分支,则b会被初始化,因此程序没有报错。此程序不会跳过初始化,所以不会报错。
变量作用域:
作用域:是指变量作用的范围;
局部变量和静态局部变量,其作用域为定义处到所在代码块结束(一个{}表示一个代码块)。
全局变量:作用域为定义处到所在源文件结束,但是可以使用extern关键字来扩充其作用域(可以在其他源文件使用)。当使用static关键字修饰时,将其作用域限定在其所在源文件,这样和其他源文件中同名变量不会冲突。(用static修饰的全局变量成为静态全局变量)。
生存期
全局变量(包括静态全局变量),静态局部变量都是从定义处开始到程序结束。
普通局部变量,从定义处开始到该语句块结束。
回看程序
现在我们在看开头的程序,编译器为了避免跳过这样的初始化而造成无法预料的结果,所以在前面case定义初始化的变量时编译器会报错,不管是显示初始化,还是隐式初始化(string类型定义后,会隐式初始化),在case 0声明的变量,在后面的case仍然在的作用域内,jval为变量赋值就不会报错。
参考博客: