声明
声明的语法
一般地,声明具有下列形式:
声明说明符 声明符;
声明说明符(declaration specifier)描述声明的变量或函数的性质。
声明符(declarator)给出了它们的名字,并且可以提供关于其性质的额外信息。
声明说明符分为以下 4 大类:
- 存储类型
存储类型一共有4种:auto、static、extern 和 register。 - 类型限定符
C89只有两种类型限定符:const 和 volatile;C99还有一个限定符 restrict; - 类型说明符
包括:(void、char、short、int、long、float、double、signed、unsigned、struct、union、enum) - 函数说明符
C99 新增:inline
在声明中最多可以出现一种存储类型;如果存储类型存在,则必须把它放置在最前面。
声明可以包含零个或多个类型限定符。
类型限定符和类型说明符必须跟随在存储类型后边,但是两者的顺序没有限制。
存储类型
变量的性质
C程序中的每个变量都具有以下 3 个性质。
- 存储期限。变量的存储期限决定了为变量预留和内存被释放的时间。
- 具有自动存储期限的变量在所属块被执行时获得内存单元,并在块终止时释放内存单元,从而会导致变量失去值。
- 具有静态存储期限的变量在程序运行期间占有同一个的存储单元,也就允许变量无限期地保留它的值。
- 作用域。变量的作用域是指可以引用变量的那部分程序文本。
- 块作用域:变量从声明的地方一直到所在块的末尾都是可见的。
- 文件作用域:变量从声明的地方一直到所在文件的末尾都是可见的。
- 原型作用域:它只适用于在函数原型中声明的参数名,在原型中(与函数的定义不同),参数的名字可以省略。
- 函数作用域:它只适用于语句标签,语句标签用于 goto 语句。基本上,函数作用域可以简化为一条规则:一个函数中的所有语句标签必须唯一。
- 链接属性。变量的链接确定了程序的不同部分可以共享此变量的范围。
- 具有外部链接的变量可以被程序中的几个(或许全部)文件共享。
- 具有内部链接的变量只能属于单独一个文件,但是此文件中的函数可以共享这个变量。
- 无链接的变量属于单独一个函数,而且根本不能被共享。
变量的默认存储期限、作用域和链接都依赖于变量声明的位置:
- 在块(包括函数体)内部声明的变量具有自动存储期限、块作用域,并且无链接。
- 在程序的最外层(任意块外部)声明的变量具有静态存储期限、文件作用域和外部链接。
int i; //具有静态存储期限、文件作用域和外部链接
void fun(void)
{int j; //具有自动存储期限、块作用域,并且无链接
}
可以通过指定明确的存储类型(auto、static、extern 和 register)来改变变量的性质。
auto存储类型
auto 存储类型只对属于块(用花括号括起来的多个语句)的变量有效。
auto 变量具有自动存储期限、块作用域,并且无链接。
auto 存储类型几乎从来不用明确地指明,因为对于在块内部声明的变量,它是默认的。
static存储类型
static 存储类型可以用于全部变量,而无需考虑变量声明的位置。
但是,作用于块外部声明的变量和块内部声明的变量时会有不同的效果:
- 当用在块外部时,static 说明变量具有内部链接。
- 当用在块内部时,static 把变量的存储期限从自动的变成了静态的。
int i; //具有静态存储期限、文件作用域和内部链接
void fun(void)
{int j; //具有静态存储期限、块作用域和无链接
}
static 变量 和 auto 变量的比较:
- 块内的 static 变量只在程序执行前进行一次初始化;而 auto 变量则会在每次出现时进行初始化。
- 每次调用函数时,它都会获得一组新的 auto 变量;但是,如果函数含有 static 变量,那么此函数的全部调用都可以共享这个 static 变量。
- 虽然函数不应该返回指向 auto 变量的指针,但是函数可以返回指向 static 变量的指针。
extern存储类型
extern 存储类型使几个源文件可以共享同一个变量。
变量的 extern 声明不是定义,它只是提示编译器需要访问定义在别处的变量。
file1.c
int a = 3,b = 4;//在file1.c中定义(初始化)file2.c
extern int a,b; //在file2.c中声明
int main()
{printf("%d,%d\n",a,b);
}
extern 声明的变量始终具有静态存储期限,其作用域依赖于声明的位置。
在大多数情况下,变量会定义在另一个文件中,并且具有外部链接。
register存储类型
声明变量具有 register 存储类型就要求编译器把变量存储在寄存器中,而不是像其他变量一样保留在内存中。
函数的存储类型
和变量声明一样,函数声明的(和定义)也可以包括存储类型,但是选项只有 extern 和 static。
在函数声明开始处的单词 extern 说明函数具有外部链接,也就是允许其他文件调用此函数。
如果不指明函数的存储类型,那么会假设函数具有外部链接。
在函数声明开始处的单词 static 说明函数具有内部链接,也就是说只能在定义函数的文件内部调用此函数。
类型限定符
const 限定符
任何变量的声明都可以使用 const 限定符限定,该限定符指定变量的值不能被修改。
const 对象必须声明的同时初始化。
const int buffsize = 255;
初始值可以是任意复杂表达式:
int i = 42;
const int ci = i;
const int j = get_size();
默认状态下,const 对象仅在文件内有效。
如果想在多个文件之间共享 const 对象,在变量的定义和声明之前都添加 extern 关键字。
文件1:
extern const int buffsize = fcn(); //该常量能被其他文件访问
文件2:
extern const int buffsize; //使用其它文件的常量
指向常量的指针
要想存放常量的地址,只能使用指向常量的指针(pinter to const)。
指向常量的指针不能用于改变其所指对象的值。
const double pi = 3.14;
const double* ptr = π
允许指向常量的指针指向一个非常量的对象:
double pi = 3.14;
const double* cptr = π //不能通过*cptr改变pi的值
常量指针
允许把指针本身定位常量。
常量指针(const pointer)必须初始化,而且一旦初始化完成,它的值就不能再改变了。
把 * 放在 const 关键字之前用以说明指针是一个常量。
int errNumb = 0;
int* const curErr = &errNumb; //cuErr将一直指向errNumb
指向常量的常量指针
const double pi = 3.14159;
const double* const pip = π //指向常量对象的常量指针。
受限指针
在C99中,关键字 restrict 可以出现在指针的声明中:
int* restrict p;
用 restrict 声明的指针叫做受限指针(restricted pointer)。
这样做的目的是,如果指针 p 指向的对象在之后需要修改,那么该对象不会允许通过除指针 p 之外的任何方式访问。
其他访问对象的方式包括让另一个指针指向同一个对象,或者让指针 p 指向命名变量。
初始化
具有静态存储期限的变量的初始化必须是常量。
具有静态存储期限的变量默认情况的值为零。
具有动态存储期限的变量的初始化必须可以是变量也可以是常量。
具有动态存储期限的变量没有默认的初始值。
包含在花括号中的数组、结构或联合的初始化必须只包含常量表达式。
自动类型的结构或联合的初始化可以是另外一个结构或联合。