声明与定义分离Tips变量能且仅能被定义一次但是可以被多次声明。为了支持分离式编译C将定义和声明区分开。其中声明规定了变量的类型和名字定义除此功能外还会申请存储空间并可能为变量赋一个初始值。extern如果想声明一个变量而非定义它就使用关键字extern并且不要显式地初始化变量12externinti;// 声明i而非定义iexterninti 1;// 定义i, 这样做抵消了extern的作用static当我们在C/C用static修饰变量或函数时主要有三种用途局部静态变量外部静态变量/函数类内静态数据成员/成员函数其中第三种只有C中有我们后续在面向对象程序设计中再探讨这里只讨论静态局部/全局变量。1. 静态局部变量在局部变量前面加上static说明符就构成静态局部变量例如123// 声明局部静态变量staticinta;staticintarray[5] {1, 2, 3, 4, 5};静态局部变量在函数内定义但不像自动变量那样当函数被调用时就存在调用结束就消失静态变量的生存期为整个源程序静态变量的生存期虽然为整个源程序但是作用域与自动变量相同即只能在定义该变量的函数内使用该变量退出函数后虽然变量还存在但不能够使用它对基本类型的静态局部变量如果在声明时未赋初始值则系统自动赋0值而对普通局部变量不赋初始值那么它的值是不确定的根据静态局部变量的特点它的生存期为整个源程序在离开定义它的函数作用域但再次调用定义它的函数时它又可继续使用而且保存了前次被调用后留下的值。因此当多次调用一个函数且要求在调用之间保留某些变量的值时可考虑采用静态局部变量虽然用全局变量也可以达到上述目的但全局变量有时会造成意外的副作用因此最好采用局部静态变量。例如1234567891011121314151617181920#include iostreamvoidfoo() {intj 0;// 普通局部变量staticintk 0;// 静态局部变量j;k;printf(j:%d, k:%d\n, j, k);}intmain(void){for(inti 1; i 5; i) {foo();}}// 输出:j:1, k:1j:1, k:2j:1, k:3j:1, k:4j:1, k:52. 静态全局变量C废弃用匿名命名空间替代Tips对于全局变量不管是否被static修饰它的存储区域都是在静态存储区生存期为整个源程序。只不过加上static后限制这个全局变量的作用域只能在定义该变量的源文件内。全局变量外部变量的声明之前加上static就构成了静态的全局变量全局变量本身就是静态存储变量静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同这两者的区别在于非静态全局变量的作用域是整个源程序。当一个源程序由多个源程序组成时非静态的全局变量在各个源文件中都是有效的而静态全局变量则限制了其作用域即只在定义该变量的源文件内有效在同一源程序的其他源文件中不能使用它。这种在文件中进行静态声明的做法是从C语言继承而来的在C语言中声明为static的全局变量在其所在的文件外不可见。这种做法已经被C标准取消了现在的替代做法是使用匿名命名空间。匿名命名空间指关键字namespace后紧跟花括号括起来的一系列声明语句具有如下特点在匿名命名空间内定义的变量具有静态生命周期匿名空间在某个给定的文件内可以不连续但是不能跨越多个文件每个文件定义自己的匿名命名空间不同文件匿名命名空间中定义的名字对应不同实体如果在一个头文件中定义了匿名命名空间则该命名空间内定义的名字在每个包含该头文件的文件中对应不同实体123namespace{inti;// 匿名命名空间内定义的变量具有静态生命周期, 作用域仅限于当前文件}3. 总结static这个说明符在不同地方所起的作用域是不同的比如把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期把全局变量改变为静态变量后是改变了它的作用域限制了它的使用范围。auto1. C98中auto用法C11已废弃C98 auto用于声明变量为自动变量拥有自动的生命周期C11已经删除了该用法取而代之的是“变量的自动类型推断方法”。1234// c 98:inta 10;// 拥有自动生命期autointb 20;// 拥有自动生命期(C11编译不过)staticintc 30;// 延长了生命期C11新标准引入了auto类型说明符让编译器通过初始值来自动推断变量类型这意味着通过auto定义的变量必须有初始值。123// c 11:inta 10;auto auto_a a;// 自动类型推断为int类型2. auto会去除变量的引用语义当引用对象作为初始值时真正参与初始化的是引用对象的值此时编译器会以引用对象的类型作为auto推算的类型12345intmain(void) {inti 10;intri i;auto auto_i ri;// 去除引用语义, 自动推断为int}如果希望推断出来的auto类型包含引用语义我们需要用明确指出1234intmain(void) {inti 10;auto auto_i i;// 加上引用语义, 自动推断为int}3. auto忽略顶层constauto一般会忽略掉顶层const同时底层const会被保留下来1234567891011intmain(void) {constintci 10;// 常量intauto auto_ci ci;// auto_ci被推断为int类型auto_ci 20;// 正确: auto_ci非常量constintcr ci;// cr是指向常量int的常量引用auto auto_cr cr;// auto_cr被推断为int类型: 去除了引用语义 去除了顶层constauto_cr 20;// 正确: auto_cr非常量constint*cp ci;// cp是指向常量int(底层)的常量指针(顶层)auto auto_cp cp;// auto_cp被推断为const int*类型(指向常量int的指针): 去除了顶层const 保留底层const// *auto_cp 10; // 错误: 不能修改auto_cp指向的常量}如果希望推断出来的auto类型是一个顶层const我们需要通过const关键字明确指出12345intmain(void) {constintci 10;// 常量intconstauto auto_ci ci;// auto_ci被推断为const int类型// auto_ci 20; // 错误: auto_ci是一个常量, 禁止修改}const有时我们希望定义一个不能被改变值的变量可以使用关键字const对变量类型加以限定。1. const对象必须初始化因为const对象一经创建后其值就不能再改变所以const对象必须初始化但是初始值可以是任意复杂的表达式123constinti get_size();// 正确: 运行时初始化constintj 42;// 正确: 编译时初始化constintk;// 错误: k是一个未经初始化的常量2. 默认情况下const仅在文件内有效举个例子我们在编译时初始化一个const对象1constinti 10;编译器会在编译过程把用到该变量的地方都替换为对应的值。为了执行这个替换编译器必须知道变量的初始值如果程序包含多个文件那么每个用了这个const对象的文件都必须得能访问到它的初始值才行即每个文件都要定义const对象。为了避免对同一变量的重复定义当多个文件中出现同名的const对象时其实等同于在不同文件中分别定义了独立的变量。1234567/** 下面是合法的, 不存在变量i重复定义问题*/// foo.cppconstinti 10;// bar.cppconstinti 5;如果想在多个文件之间共享const对象那么必须在变量的定义之前添加extern关键字1234567891011/** 下面是合法的, main.cpp和foo.cpp中的const int对象是同一个*/// foo.cppexternconstinti 10;// main.cpp#include iostreamintmain(void) {externinti;std::cout i: i std::endl;}3. 允许常量引用绑定非常量对象、字面值甚至一般表达式一般而言引用的类型必须与其所引用对象的类型一致但是有两个例外初始化常量引用时允许用任意表达式作为初始值只要该表达式的结果能转换成引用类型即可允许为一个常量引用绑定非常量的对象、字面值甚至是一个一般表达式如下可以将基类的指针或引用绑定到派生类对象上后续面向对象章节再探讨1234inti 10;constintri1 i;// 合法: 绑定到非常量对象constintri2 100;// 合法: 绑定到字面值constintri3 1 1;// 合法: 绑定到一般表达式4. 顶层const与底层const指针本身是一个对象因此指针本身是不是常量与指针所指对象是不是常量是两个独立的问题前者被称为顶层const后者被称为底层const。Tips指针类型既可以是顶层const也可以是底层const其他类型要么是顶层常量要么是底层常量。顶层const用于表示任意的对象是常量包括算数类型、类和指针等底层const用于表示引用和指针等复合类型的基本类型部分是否是常量。12345inti 10;int*constp1 i;// 顶层const: 不能改变p1的值constint*p2 i;// 底层const: 不能通过p2改变i的值constint*constp3 i;// 底层const 顶层constconstintr1 i;// 底层const: 不能通过r1改变i的值constexprC11引入了常量表达式constexpr的概念指的是值不会改变并且在编译期间就能得到计算结果的表达式。123constinti 10;// 常量表达式constintj i 1;// 常量表达式constintk size();// 仅当size()是一个constexpr函数时才是常量表达式, 运行时才能获得具体值就不是常量表达式在一个复杂系统中我们很难分辨一个初始值是否是常量表达式通过constexpr关键字声明一个变量我们可以让编译器来验证变量的值是否是一个常量表达式。1. 字面值是常量表达式算术类型、引用和指针都属于字面值类型自定义类则不属于字面值类型因此也无法被定义为constexpr。Tips尽管指针和引用都能被定义成constexpr但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr、0或者是存储于某个固定地址中的对象。2. constexpr是对指针的限制在constexpr声明中定义了一个指针限定符constexpr仅对指针有效与指针所指对象无关12constint*pi1 nullptr;// 底层const: pi1是指向整型常量的普通指针constexprint*pi2 nullptr;// 顶层const: pi2是指向整型的常量指针我们也可以让constexpr指针指向常量12constexprinti 10;constexprconstint*pi i;// 顶层const 底层const到此这篇关于[c]变量声明与定义的规则详解的文章就介绍到这了,希望对大家有帮助