条件运算符
语法:exp1 ? exp2 : exp3;
—exp1是条件表达式
—如果结果为真,返回exp2
—如果结果为假,返回exp3
数组
类型 数组名[元素个数]
数组不能动态定义,以下是错误示范
#include
//错误操作
int main()
{
int n;
printf("请输入字符的个数:");
scanf("%d,&n");
int a[n];/*括号里面只能是常量,或常量表达式*/
......
return 0;
}
字符串
输入字符前最好加上一行 getchar();
每一个字符串最后都隐藏一个’ \0
’
二维数组
类型 数组名[常量表达式] [常量表达式]
前行后列
在内存中其实仍是以线性的方式进行存储
二维数组的初始化也能偷懒,让编译器根据元素的数量计算数组长度,但只有第一维的元素个数可以不写,其他维度必须写上:
int a[][4] = {
{
1,2,3,4},{
1,2,3,4},{
1,2,3,4}};
指针
指针变量存放的是指针,地址通常称之为指针,普通变量存放的是数据
类型名 *指针变量名
char *pa; // 定义一个指向字符型的指针变量
int *pb; // 定义一个指向整型的指针变量
取地址运算符和取值运算符
如果需要获取某个变量的地址,可以使用取地址运算符**(&)**:
char *pa = &a;
int *pb = &f;
如果需要访问指针变量指向的数据,可以使用取值运算符**(*)**:
printf("%c, %d\n", *pa, *pb);
取值运算符与指针定义符一样,属于符号的重用
int main(){
char a = 'F';
int f = 123;
char *pa = &a;
int *pb = &f;
printf("a = %c\n", *pa);
printf("f = %d\n", *pb);
//直接访问
//a = 'c';
//b += 1;
//间接访问
*pa = 'c';
*pb += 1;
//所以取值运算符又叫间接运算符
printf("now, a = %c\n", *pa);
printf("now, f = %d\n", *pb);
printf("sizeof pa = %d\n", sizeof(pa));
printf("sizeof pb = %d\n", sizeof(pb));
printf("the addr of a is: %p\n", pa);
printf("the addr of b is: %p\n", pb);
return 0;
}
/*输出
a = F
f = 123
now, a = c
now, f = 124
sizeof pa = 4
sizeof pb = 4
the addr of a is: 0xbf9eff17
the addr of b is: 0xbf9eff10
*/
避免访问未初始化的指针
//错误示范
#include
int main(){
int *a;
*a = 123;
return 0;
}
指针与数组
int main(){
int a;
int *p = &a;
printf("请输入一个整数:");
scanf("%d",&a);
printf("a = %d\n", a);
printf("请重新输入一个整数:");
scanf("%d", p); // 用指针来间接访问a
printf("a = %d\n", a)
return 0;
}
/*输出
请输入一个整数:3
a = 3
请重新输入一个整数:5
a = 5
*/
数组名就是数组第一个元素的地址
int main(){
char str(128);
printf("请输入鱼C的域名:");
scanf("%s",str);
printf("域名是:%s\n",str);
printf("str 的地址是:%p\n", str);
printf("str 的地址是:%p\n", &str[0]);
return 0;
}
/*输出
请输入鱼C的域名:fishc.com
域名是:fishc.com
str 的地址是:0xbfb91630
str 的地址是:0xbfb91630
*/
int main(){
char a[] = "FishC";
int b[5] = {
1, 2, 3, 4, 5};
float c[5] = {
1.1, 2.2, 3.3, 4.4, 5.5};
double d[5] = {
1.1, 2.2, 3.3, 4.4, 5.5};
printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);
return 0;
}
/*输出
a[0] -> 0xbf96c4aa, a[1] -> 0xbf96c4ab, a[2] -> 0xbf96c4ac
b[0] -> 0xbf96c494, b[1] -> 0xbf96c498, b[2] -> 0xbf96c49c
c[0] -> 0xbf96c480, c[1] -> 0xbf96c484, c[2] -> 0xbf96c488
d[0] -> 0xbf96c458, d[1] -> 0xbf96c460, d[2] -> 0xbf96c468
*/
如果用一个指针指向数组,应该怎么办呢?
char *p;
p = a; // 语句1
p = &a[0]; // 语句2
指针的运算
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素
int main(){
char a[] = "FishC";
char *p = a;
//p+1并不是简单地将地址加1,而是指向数组地下一个元素
printf("*p = %c, *(p+1) = %c, *(p+2) = %c\n", *p, *(p+1), *(p+2));
//数组名本身指向数组的第一个元素
printf("*b = %d, *(b+1) = %d, *(b+2) = %d\n", *b, *(b+1), *(b+2));
return 0;
}
/*输出
*p =F, *(p+1) = i, *(p+2) = s
*b =1, *(b+1) = 2, *(b+2) = 3
*/
对比标准的下标法访问数组元素,这种使用指针进行间接访问的方法叫做指针法
#include
#include
int main(){
char *str = "I love FishC.com";
int i, length;
length = strlen(str);
for (i = 0; i < length; i++){
printf("%c",str[i]);
}
printf("\n");
return 0;
}
/*输出
I love FishC.com
*/
指针和数组的区别
数组名只是一个地址,而指针是一个左值
int main(){
char str[] = "I love FishC.com";
chat *target = str;
int count = 0;
//报错:while(*str++ != '\0')
while(*target++ != '\0')
{
count++;
}
printf("总共有%d个字符!\n", count);
return 0;
}
指针数组:int *p1[5]
指针数组是一个数组,每个数组元素存放一个指针变量
int main(){
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int *p1[5] = {
&a, &b, &c, &d, &e};
int i;
for (i = 0; i < 5; i++){
printf("%d\n", *p1[i]);
}
return 0;
}
/*
1
2
3
4
5
*/
int main(){
char *p1[5] = {
"让编程改变世界",
"just do it",
"god",
"damn",
"GG"
}
int i;
for (i = 0; i < 5; i++){
printf("%s\n", p1[i]);//注意没有星号
}
return 0;
}
/*
让编程改变世界
just do it
god
damn
GG
*/
数组指针:int (*p2)[5]
数组指针是一个指针,他指向的是一个数组
int main(){
int temp[5] = {
1, 2, 3, 4, 5};
int *p = temp;//指向数组第一个元素的地址
int (*p2)[5] = &temp;//利用数组指针指向数组
int i;
for (i = 0; i < 5; i++){
printf("%d", *(p + i));
printf("%d\n", *(*p2 + i));
}
return 0;
}
/*
11
22
33
44
55
*/
二维数组与指针
int main(){
int array[4][5] = {
0};
printf("sizeof int: %d\n", sizeof(int));
printf("array: %p\n", array);
printf("array + 1: %p\n", array + 1);
return 0;
}
/*输出
sizeof int: 4
array: 0xbfc34320
array + 1: 0xbfc34334(证明了的确是4行5列)
*/
多维数组实际上就是一维数组的线性扩展:
*(array+i) == array[i]
*(*(array+i)+j) == array[i][j]
*(*(*(array+i)+j)+k) == array[i][j][k]
int main(){
int array[4][5] = {
0};
int i, j, k = 0;
for (i = 0; i < 4; i++){
for (j = 0; j < 5; j++){
array[i][j] = k++;
}
}
printf("*(array+1): %p\n", *(array + 1));
printf("array[1]: %p\n", array[1]);
printf("&array[1][0]: %p\n", array[1][0]);
printf("**(array+1): %d\n", **(array+1));
printf("*(*(array+1)+3): %d\n", *(*(array+1)+3));
printf("array[1][3]: %d\n", array[1][3]);
return 0;
}
/*输出
*(array+1): 0xbfaa1518
array[1]: 0xbfaa1518
&array[1][0]: 0xbfaa1518
**(array+1): 5
*(*(array+1)+3): 8
array[1][3]: 8
*/
初始化二维数组是可以偷懒的:
int array[2][3] = { {0, 1, 2}, {3, 4, 5}};
可以写成
int array[][3] = { {0, 1, 2}, {3, 4, 5}};
定义一个数组指针是这样的:
int (*p)[3];
int main(){
int array[2][3] = {
{
0, 1, 2}, {
3, 4, 5}};
int (*p)[3] = array;
printf("**(p+1): %d\n", **(p+1));
printf("**(array+1): %d\n", **(array+1));
printf("array[1][0]: %d\n", array[1][0]));
printf("*(*(p+1)+2): %d\n", *(*(p+1)+2));
printf("*(*(array+1)+2): %d\n", *(*(array+1)+2));
printf("array[1][2]: %d\n", array[1][2]));
return 0;
}
/*输出
**(p+1): 3
**(array+1): 3
array[1][0]: 3
*(*(p+1)+2): 5
*(*(array+1)+2): 5
array[1][2]: 5
void指针
void指针我们把它称之为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给void指针。
int main(){
int num = 1024;
int *pi = #
char *ps = "FishC";
void *pv;
pv = pi;
printf("pi:%p, pv:%p\n", pi, pv);
printf("*pv:%d\n", *(int *)pv);
pv = ps;
printf("ps:%p, pv:%p\n", ps, pv);
printf("*pv:%s\n", (char *)pv);
return 0;
}
/*输出
pi:0xbfd8c3c0, pv:0xbfd8c3c0
*pv:1024
ps:0x8048504, pv:0x8048504
*pv:FishC
*/
NULL指针
#define NULL ((void *)0)
当你还不清楚要将指针初始化到什么地址时,请将它初始化NULL;在对指针进行解引用时,先检查该指针是否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。
int main(){
int *p1;
int *p2 = NULL;
printf("%d\n", *p1);
printf("%d\n", *p2);
return 0;
}
/*输出
30265115
报错
*/
NULL用于指针和对象,表示控制,指向一个不被使用的地址;而'\0'
表示字符串的结尾
指向指针的指针
int main(){
int num = 520;
int *p = #
int **pp = &p;
printf("num: %d\n",num);
printf("*p: %d\n", *p);
printf("**p: %d\n", **pp);
printf("&p: %p, pp: %p\n", &p, pp);
printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp);
return 0
}
/*输出
num: 520
*p: 520
**p: 520
&p: 0xbfea9384, pp:0xbfea9384
&num: 0xbfea9388, p: 0xbfea9388, *pp: 0xbfea9388
*/
指针数组和指向指针的指针
int main(){
char *cBooks[] = {
"1",
"2",
"3",
"4",
"5",
"6",
};
chae **byFishC;
char **jiayu[4];
int i;
byFishC = &cBooks[5];
jiayu[0] = &cBooks[0];
jiayu[1] = &cBooks[1];
jiayu[2] = &cBooks[2];
jiayu[3] = &cBooks[3];
printf("甲鱼出版的书有: %s\n", *byFishC);
printf("甲鱼喜欢的书有: \n");
for (i = 0; i < 4; i++){
printf("%s\n", *jiayu[i]);
}
return 0;
}
/*输出
甲鱼出版的书有:6
甲鱼喜欢的书有:
1
2
3
4
*/
至少有两个好处:
——避免重复分配内存
——只需要进行一处修改
代码的灵活性和安全性都有了显著的提高
数组指针和二维数组
int main(){
int array[10] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = array;
int i;
for (i = 0; i < 10; i++){
printf("%d\n", *(p + i));
}
return 0;
}
/*输出
0
1
2
...
9
*/
int main(){
int array[3][4] = {
{
0, 1, 2, 3},
{
4, 5, 6, 7},
{
8, 9, 10, 11}
}
int **p = array;
int i, j;
printf("p: %p, array: %p\n", p, array);
printf("p+1: %p, array+1: %p\n", p+1, array+1);
return 0;
}
/*输出
p:0xbfab14a4, array: 0xbfab14a4
p+1:0xbfab14a8, array+1: 0xbfab14b4
*/
int main(){
int array[3][4] = {
{
0, 1, 2, 3},
{
4, 5, 6, 7},
{
8, 9, 10, 11}
}
int (*p)[4] = array;
int i, j;
for (i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
printf("%2d", *(*(p+i)+j));
}
printf("\n");
}
return 0;
}
/*输出
0 1 2 3
4 5 6 7
8 9 10 11
*/
常量的表示
(1)520, ‘a’, 3.14
(2)#define PRICE 520
#define A 'a'
#define PI 3.14
(3)还可以用const关键字修饰:
——const int PRICE = 520;
——const char A = 'a';
——const float PI = 3.14;
指向常量的指针
int main(){
int num = 520;
const int cnum = 880;
const int *pc = &cnum;
printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
printf("*pc: %d, pc: %p\n", *pc, pc);
/*无法通过解引用的方法改变cnum的值,会报错
*p = 1024;
*/
pc = #
/*无法通过解引用的方法改变num的值,会报错
*p = 1024;
*/
printf("num: %d, &num: %p\n", num, &num);
printf("*pc: %d, pc: %p\n", *pc, pc);
//但是可以通过修改num的值来实现
num = 1024;
printf("*pc: %d, pc: %p\n", *pc, pc);
return 0;
}
/*输出
cnum: 880, &cnum: 0xbffe63e4
*pc: 880, pc: 0xbffe63e4
num: 520, &num: 0xbf0e5d8
*pc: 520, pc: 0xbf0e5d8
*pc: 1024, pc: 0xbf0e5d8
*/
总结
——指针可以修改为指向不同的常量
——指针可以修改为指向不同的变量
——可以通过解引用来读取指针指向的数据
——不可以通过解引用修改指针指向的数据
int main(){
int num = 520;
const int cnum = 880;
int * const p = #
*p = 1024;
printf("*p: %d\n", *p);
/*错误,常量指针不可被修改
p = &cnum;
printf("*p: %d\n", *p);
*/
return 0;
}
/*输出
*p = 1024
*/
指向非常量的常量指针:
——指针自身不可以被修改
——指针指向的值可以被修改
指向常量的常量指针:
——指针自身不可以被修改
——指针指向的值也不可以被修改
指向"指向常量的常量指针"的指针
int main(){
int num = 520;
const int cnum = 880;
const int * const p = #
const int * const *pp = &p;
printf("pp: %p, &p: %p\n", pp, &p);
printf("*pp: %p, p: %p, &num: %p\n", *pp, p, &num);
printf("**pp: %d, p: %d, num: %d\n", *pp, *p, num);
return 0;
}
/*输出
pp: 0xbfda9440, &p: 0xbfda9440
*pp: 0xbfda9444, p: 0xbfda9444,&num: 0xbfda9444
**pp: 520, *p: 520, num: 520
*/
void print_C();//函数声明
void print_C(){
printf(" ###### \n");
printf("## ##\n");
printf("## \n");
printf("## \n");
printf("## \n");
printf("## ##\n");
printf(" ###### \n");
}
int main(){
print_C();
return 0;
}
函数的定义
类型名 函数名(参数列表){
函数体
}
函数的参数和返回值
编写一个函数sum, 由用户输入参数n, 计算1+2+3+…+(n-1)+n的结果并返回
int sum(int);
//int sum(int n);两种写法皆可,不写形参可以但类型一定要写
int sum(int n){
int i,sum = 0;
for (i = 0; i <= n; i++ ){
sum += i;
}
return sum;
}
传值与传址
void swap(int *x, int *y);
void swap(int *x, int *y){
int temp;
printf("In swap, 互换前: x = %d, y = %d\n", *x, *y);
temp = *x;
*x = *y;
*y = temp;
printf("In swap, 互换后: x = %d, y = %d\n", *x, *y);
}
int main(){
int x = 3, y = 5;
printf("In main, 互换前: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("In main, 互换后: x = %d, y = %d\n", x, y);
return 0;
}
/*输出
In main,互换前:x = 3, y = 5
In swap,互换前:x = 3, y = 5
In swap,互换后:x = 5, y = 3
In main,互换后:x = 5, y = 3
*/
传数组
void get_array(int a[10]);
void get_array(int a[10]){
int i;
a[5] = 520;
for (i = 0; i < 10; i++){
printf("a[%d] = %d\n", i, a[i]);
}
}
int main(){
int a[10] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
get_array(a);
for (i = 0; i < 10; i++){
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
/*输出
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 520
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 0
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 520
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 0
*/
//说明并不存在将整个数组作为参数传入的形式,只是接收了地址
void get_array(int a[10]);
void get_array(int b[10]){
printf("sizeof b: %d\n", sizeof(b));
}
int main(){
int a[10] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
printf("sizeof a: %d\n", sizeof(a));
get_array(a);
return 0;
}
/*输出
sizeof a:40
sizeof b: 4
*/
#include
int sum(int n, ...);
int sum(int n, ...){
int i, sum = 0;
va_list vap;
va_start(vap, n);
for (i = 0; i < n; i++){
sum += va_arg(vap, int);
}
va_end(vap);
return sum;
}
int main(){
int result;
result = sum(3, 1, 2, 3);
printf("result = %d\n", result);
return 0;
}
/*输出
result = 6
*/
指针函数
使用指针变量作为函数的返回值,就是指针函数
char *getWord(char);
char *getWord(char c){
switch (c){
case 'A': return "Apple";
case 'B': return "Banana";
case 'C': return "Cat";
case 'D': return "Dog";
default: return "None";
}
}
int main(){
char input;
printf("请输入一个字母:");
scanf("%c", &input);
printf("%s\n", getWord(input));
return 0;
}
不要返回局部变量的指针
//错误示范
char *getWord(char);
char *getWord(char c){
char str1[] = "Apple";
char str2[] = "Banana";
char str3[] = "Cat";
char str4[] = "Dog";
char str5[] = "None";
switch (c){
case 'A': return str1;
case 'B': return str2;
case 'C': return str3;
case 'D': return str4;
default: return str5;
}
}
int main(){
char input;
printf("请输入一个字母:");
scanf("%c", &input);
printf("%s\n", getWord(input));
return 0;
}
指针函数 ——> int *p();
函数指针 ——> int (*p)();
int square(int);
int square(int num){
return num * num;
}
int main(){
int num;
int (*fp)(int);
printf("请输入一个整数:");
scanf("%d", &num);
fp = square;//写成'fp = &square'也可,但因为变量名等于地址所以可省略
printf("%d * %d = %d\n", num, num, (*fp)(num));
return 0;
}
int add(int, int);
int sub(int, int);
int calc(int (*fp)(int, int), int, int);
int add(int num1, int num2){
return num1 + num2;
}
int sub(int num1, int num2){
return num1 - num2;
}
int calc(int (*fp)(int, int),int num1, int num2){
return (*fp)(num1, num2);
}
int main(){
printf("3 + 5 =%d\n",calc(add, 3, 5));
printf("3 - 5 =%d\n",calc(sub, 3, 5));
return 0;
}
函数指针作为返回值
比如这个函数的名字叫select,它本身有两个参数,返回值是一个函数指针,这函数指针也有两个参数,并且其返回值为整型。
int add(int, int);
int sub(int, int);
int calc(int (*fp)(int, int), int, int);
int (*select(char op))(int, int)
int add(int num1, int num2){
return num1 +num2;
}
int sub(int num1, int num2){
return num1 - num2;
}
int calc(int (*fp)(int, int), int num1, int num2){
return (*fp)(num1, num2);
}
int (*select(char op))(int, int){
switch(op){
case '+': return add;
case '-': return sub;
}
}
int main(){
int num1, num2;
char op;
int (*fp)(int, int);
printf("请输入一个式子:");
scanf("%d%c%d", &num1, &op, &num2);
fp = select(op);
printf("%d %c %d = %d\n", num1, op, num2, clac(fp, num1, num2));
return 0;
}
不同函数的变量无法相互访问
在函数里边定义的,我们叫局部变量;在函数外边定义的,我们叫外部变量,也叫全局变量。
void a();
void b();
void c();
int count = 0;
void a(){
count++;
}
void b(){
count++;
}
void c(){
count++;
}
int main(){
a();
b();
c();
printf("count = %d\n", count);
return 0;
}
/*输出
count = 3
*/
如果不对全局变量进行初始化,那么它会自动初始化为0;
如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是在函数中屏蔽全局变量(也就是说在这个函数中,全局变量不起作用)。
int a, b = 520;
void func();
void func(){
int b;
a = 880;
b = 120;
printf("In func, a = %d, b = %d\n", a, b);
}
int main(){
printf("In main, a = %d, b = %d\n", a, b);
func;
printf("In main, a = %d, b = %d\n", a, b);
return 0;
}
/*输出
In main, a = 0, b = 520
In func, a = 880, b = 120
In main, a = 880, b = 520
*/
不要大量的使用全局变量:
①使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义时候开始,直到程序退出才被释放。
②污染命名空间,虽然局部变量会屏蔽全局变量,但这样一来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围。
③提高了程序的耦合性,当代码过长时,容易忘记全局变量被哪些函数修改过
作用域
当变量被定义在程序的不同位置时,它的作用范围是不一样的,这个作用范围就是我们所说的作用域
C语言编译器可以确认4种不同类型的作用域:
①代码块作用域 ②文件作用域 ③原型作用域 ④函数作用域
代码块作用域(block scope)
在代码块中定义的变量,具有代码块作用域。作用范围是从变量定义的位置开始,到标志该代码块结束的右大括号}
处
尽管函数的形式参数不在大括号内定义,但其同样具有代码块作用域,隶属于包含函数体的代码块。
int main(){
int i = 100; //i1
{
int i = 110; //i2
{
int i = 120; //i3
printf("i = %d\n", i);
}
{
printf("i = %d\n", i);
int i = 130;
printf("i = %d\n", i);
}
printf("i = %d\n", i);
}
printf("i = %d\n", i);
return 0;
}
/*输出
i = 120
i = 110
i = 130
i = 110
i = 100
*/
文件作用域
任何在代码块之外声明的标识符都具有文件作用域,作用范围是从他们的声明位置开始,到文件的结尾处都是可以访问的。
另外,函数名也具有文件作用域,因为函数名本身也是在代码块之外。
void func(void);
int main(){
extern int count;//如果没有这句会报错,因为count的定义在外部
func();
count++;
print("In main, count = %d\n", count);
return 0;
}
int count;
void func(void){
count++;
printf("In func, count = %d\n", count);
}
原型作用域
原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数的名字(但参数类型是必须要写上的),其实函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然,这样做没有任何意义)
函数作用域
函数作用域只适用于goto语句的标签,作用将goto语句限制在同一个函数内部,以及防止出现同名标签。
定义和声明
当一个变量被定义的时候,编译器为变量声请内存空间并填充一些值。
当一个变量被声明的时候,编译器就知道该变量被定义在其他地方。
声明是通知编译器该变量名及相关的类型已存在,不需要再为此申请内存空间。
局部变量既是定义又是声明。
定义只能来一次,否则就叫做重复定义某个同名变量;而声明可以有很多次。
链接属性
external
(外部的):
——多个文件中声明的同名标识符表示同一个实体
internal
(内部的):
——单个文件中声明的同名标识符表示同一个实体
none
(无):
——声明的同名标识符被当成独立不同的实体
只有具备文件作用域的标识符才能拥有external或internal的链接属性,其他作用域的标识符都是none属性。
默认情况下,具备文件作用域的标识符拥有external属性。也就是说该标识符允许跨文件访问;对于external属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。
使用static关键字可以使得原先拥有external属性的标识符变为internal属性。这里有两点需要注意:
——使用static关键字修改链接属性,只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)
——链接属性只能修改一次,也就是说一旦将标识符的链接属性变为internal,就无法变回external了
生存期
C语言的变量拥有两种生存期:
——静态存储期(static storage duration):
具有文件作用域的变量属于静态存储期,函数也属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放
——自动存储期(automatic storge duration):
具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。
存储类型
存储类型其实是指存储变量值的内存类型,C语言提供了五种不同的存储类型:
①auto ②register ③static ④extern ⑤typedef
自动变量
在代码块中声明的变量默认的存储类型就是自动变量,使用关键字auto来描述:
int main(){
//由于这是默认的存储类型,所以不写auto是完全没问题的。
auto int i, j, k;
return 0;
}
寄存器变量(register)
将一个变量声明为寄存器变量,那么该变量就有可能被存放于CPU中。
寄存器变量和自动变量在很多方面的是一样的,他们都拥有代码作用域,自动存储期和空连接属性。
不过这里有一点需要注意的是:当你将变量声明为寄存器变量,那么你就没办法通过取值运算符获得该变量的地址
静态局部变量(static)
使用static来声明局部变量,那么就可以将局部变量指定为静态局部变量
static使得局部变量具有静态存储期,所以它的生存期与全局变量一样,直到程序结束才释放。
void func(void);
void func(void){
static int count = 0;
//若去掉static则全是0
pritnf("count = %d\n", count);
count ++;
}
int main(){
int i;
for (i = 0; i < 10; i++){
func();
}
return 0;
}
/*输出
count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
*/
static和extern
作用于文件作用域的static和extern,static关键字使得默认具有external链接属性的标识符变成internal链接属性,而extern关键字是用于告诉编译器这个变量或函数在别的地方已经定义过了,先去别的地方找找,不要着急报错。