考研C语言复习01(小甲鱼版本)

考研C语言复习01(小甲鱼版本)

条件运算符

语法:exp1 ? exp2 : exp3;

—exp1是条件表达式

—如果结果为真,返回exp2

—如果结果为假,返回exp3


数组

类型 数组名[元素个数]

数组不能动态定义,以下是错误示范

#include <stdio.h>
//错误操作
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 <stdio.h>
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 <stdio.h>
#include <string.h>

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 = &num;
    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 = &num;
    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;
    /*无法通过解引用的方法改变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 = &num;
    
    *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 = &num;
    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<stdarg.h>
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关键字是用于告诉编译器这个变量或函数在别的地方已经定义过了,先去别的地方找找,不要着急报错。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值