- 链接
变量具有一种链接,分为外部链接( external linkage)、内部链接(internal linkage)、空链接(no linkage)
具有文件作用域的变量可能有外部链接或内部链接,具有块作用域或函数原型作用域的有空链接。外部链接表示可以在多个文件程序的任何地方使用,内部链接表示可以在一个文件的任何地方使用,通过关键字static区分!
int giant = 5; // external, file scope
static int dodgers = 3; // internal, file scope
int main(){...}
giant可以在属于同一程序的其他文件中使用,但加static只能在本文件中使用
- 存储时期
变量具有一种存储时期,分为静态存储时期、自动存储时期。if 一个变量具有静态存储时期,它在程序执行期间会一直存在,要用常量来初始化。文件作用域的变量具有静态存储时期,static关键字表明链接类型而不是存储时期!自动存储期表示程序进入定义这些变量的代码块时为变量分配内存,退出时释放内存。
C使用作用域、链接、存储时期组合起来定义了5中存储类:自动、寄存器、具有代码块作用域的静态、具有外部链接的静态、具有内部链接的静态
存储类 | 时期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 空 | 代码块内 |
寄存器 | 自动 | 代码块 | 空 | 代码块内使用register关键字 |
ext链接静态 | 静态 | 文件 | 外部 | 在所有函数外 |
内部链接静态 | 静态 | 文件 | 内部 | 在所有函数外用static关键字 |
空链接静态 | 静态 | 代码块 | 空 | 代码块内使用static关键字 |
自动变量可显式地加auto
if luckily, 寄存器变量可以存到寄存器或高速内存中,这取决于编译器根据当时的情况是否给你寄存器使用。需要显式地使用register关键字.
具有file scope的变量一定是静态存储的。代码块里的变量也可以静态但需要显式static一下,函数结束后这种变量还会存在,下次调用会延续值,静态变量不初始化的话,则会系统为它赋0
函数参数不能用static
void fun(static int n); // illegal
- 具有外部链接的静态变量
在函数外定义,定义的时候不加关键字。如果声明一个定义在别的文件中的外部链接静态变量就需要加extern关键字!
int Errupt;
double Up[100];
extern char Coal; // 这个Coal定义在别的文件中了
void next(void);
int main(){
extern int Errupt; // 可以不写,写表示我这里会用到
extern double Up[]; // 可以不写,数组大小不写也可,因为定义过了
...}
如果在main中用了int Errupt就会覆盖静态变量Errupt,使代码块的变量在main中起作用instead of 全局变量errupt
外部变量初始化
只能用常量表达式,不能=2*x这样,可以=sizeof (int)
- 具有内部链接的静态变量
在函数外使用static定义
static int volume = 1;
int main(){...}
内部链接决定它只在本文件中使用
extern只是用来声明文件作用域的变量,不会改变变量的链接,所以可以用在内部链接的静态变量上
int traveler = 1;
static int stayhome = 1;
int main(){
extern int traveler;
extern int stayhome;
- 存储类说明符
5个:auto, register, static, extern, typedef
register的变量不能使用地址。请求将变量存到寄存器中
static说明符对具有代码块作用域的变量声明时,使它具有静态存储时期。对具有文件作用域的变量声明时,使它具有内部链接
extern说明符表明已经在别处定义了这个变量
函数也有存储类之分,默认情况下函数是外部的,可以是静态的,决定了其他文件能不能调用它们。
double gamma(); // 默认为extern的
static double beta();
extern double delta(); // 其他文件定义了delta,现在来声明的
使用static主要为了创建一个模块所私有的函数,避免命名冲突
随机函数和静态变量
ANSI C的随机函数是可移植的,在不同机器上有确定的随机序列。(编译器有内置的随机函数rand(),需要头文件stdlib.h)
rand()产生的是0到RAND_MAX范围内的整数,可以取模来产生小范围
引入自己写的头文件的时候,用""告诉编译器在本地寻找头文件而不是在标准头文件的地方寻找。引入头文件,那里有声明函数和变量,于是主函数那个文件就不用声明了
malloc() & free(),需要头文件stdlib.h
malloc的参数是所需内存字节数,返回指向第一个字节的地址,如果找不到空间就返回NULL
double * ptd;
ptd = (double *)malloc(30*sizeof(double));
其中cast部分 (double *)在C是可选的在C++是必须的
申请了30个double的空间,由ptd指向第一个double空间, 可以用ptd[index]
于是创建数组的三个方法为:(1)声明时用常量表达式指定维度(2)用变量表达式声明变长数组(3)声明个指针,用malloc
free()的参数是一个malloc返回的指针,不能用free来释放数组分配的内存
calloc()
long * newmem;
newmem = (long *)calloc(100, sizeof(long));
calloc有2个参数,第一个是单元格数量,第二个是单元格大小。需要cast
calloc会把内存块的比特位都置为0,也不知道有什么影响
free() 可以释放calloc分配的内存
变长数组是自动存储的,不需要手动释放。动态内存分配需要free
malloc创建的数组可以让别的函数使用,只要返回指针供别的函数使用,使用之后可以free,free的地址一样就可以不必要同一个指针
变量由类型和存储类表征。C90增加了两个属性:不变性和易变性,由关键字const, volatile声明,C99增加了restrict来方便编译,这三个是受限类型。
类型限定词const
关键字const使变量不可通过赋值、自增自减来修改
const int price;
price = 12; // illegal
const int price = 12; // legal
const int factor[3] = {0.8, 0.6, 0.5}; // legal
在指针和参量中使用const
const float * pf; // 指向的值是不变的,但pf可变可以指向别的值
float const * pf; // 同上
float * const pf; // 指针不可变,总是指向同一个地址
const float * const pf; // 都不可变
// const在*左边的都是不可修改指向的值,在右边是不能修改指针
为防止函数传递数组时,被误修改数组的值,可以用const修饰参数
void display(const int array[], int limit);
void display(const int * array, int limit); // equal
对全局变量使用const
全局变量会暴露数据,在程序任何地方都可以修改。如果加上const就可以了。在文件之间共享数据的方法:
(1)在一个文件中定义,在另一个中声明
(2)在头文件中定义,其他文件中引入
//file.h
static const double PI = 3.14159;
如果不加static会在每个包含了头文件的文件中都有同一表示符的定义声明,ANSI不支持这样。加上static使每个文件有了一个数据拷贝。缺点是复制数据,如果常量是很大的数组就有点问题。
类型限定词volatile
volatile告诉编译器,这个变量可能被其他代理改变除了这个程序外。语法同const。没有const,说明这个变量不会被其他并行程序更改,就可以让编译器进行代码优化(比如把变量放到cache中)。一个变量可以同时有const 和volatile,前后顺序不重要,程序不修改它,但是程序以外的代理可能修改。
- restrict
也是提供编译器优化的,restrict只修饰指针,且这个指针是访问数据的唯一且初始的方式,所以指针对数据的多次操作可以合并(优化). restrict修饰的指针是指向那个区域的唯一的指针
int * restrict ptr = (int *)malloc(10*sizeof(int));
void * memcpy (void * restrict s1, const void * restrict s2, size_t n); // void指针可以cast成其他类型
C99新语法
void f(int * const a1, int * restrict a2, int n); //old
void f(int a1[const], int a2[restrict], int n); // new