2.C语言中的关键字

时间:2021-10-23 08:16:27

1、auto

  修饰局部变量,编译器默认所有局部变量都是用auto来修饰的,所以在程序中很少见到。

2、static

  它作用可大了,除了可以修饰变量,还可以修饰函数,修饰变量,改变其作用域和生命周期,修饰函数,改变作用域。

  (1)修饰变量:如果该变量是局部变量,那么生命周期会变化,作用域限定在本文件中被使用,如果该变量是全局变量,生命周期没变化,作用域限定在本文件中被使用。

  (2)修饰函数:该函数只能在当前文件下被调用。

3、register

  (1)寄存器变量的处理速度是最快的,适合频繁使用的变量。

  (2)寄存器变量时没有地址的,不能用取地址的方式访问。

  (3)寄存器变量不能用来做全局变量,因为全局变量的生命周期很长,贯穿整个运行周期,这样CPU的内存一直被占据,影响运行的效率,甚至会影响CPU的运行,一般定义全局寄存器变量编译器会直接报错。

  PS:上面3个可以放到一组,因为它们有共同的特点,都决定变量的属性。C语言中的变量是有属性的,auto属性的变量存储在栈中,static属性的变量存储在程序静态区中(解释了为什么用static修饰的局部变量生命周期和静态区的变量一样长),register属性的变量存储在CPU的寄存器中。

4、extern

  它的作用就是声明变量或者函数,本来应该和static放到一组,因为它也修饰变量和函数。

  (1)修饰变量:只能修饰全局变量,生命周期没变化,作用域延伸到其他文件。

  (2)修饰函数:一般不用写出来,和auto类似。

  (3)一般写在头文件中,然后文件用到时直接调用头文件。

5、if

  if语句中的零值比较总结:

  (1)bool型变量

 bool a;
 if(a)
 {
   printf("ok");
 }
 else
 {
   printf("error");
 }

  C语言中是没有bool型变量的,但是很多编译器内置了bool型变量,所以有的时候能看到像上面一样在程序中直接写出来了。实际上,一般bool型变量通常是用枚举来定义的,完整的if语句中的零值比较的示例应该是:

 typedef enum _bool
 {
    ,

 }bool;

 bool a=ture;
 if(a)
 {
    printf("ok");
 }
 else
 {
    printf("error");
 }

 (2)整形变量

 int a;
 ==a)
 {
     printf("ok");
 }
 else
 {
     printf("error");
 }

  (3)浮点型变量

#define FLOAT 0.00000001
float a=0.0;
if((-FLOAT<=a)&&(a<=FLOAT))
{
    printf("ok");
}
else
{
    printf("error");
}

  如果定义float a=5.0,相应的if语句修改为:if((5.0-FLOAT<=a) && (a<=5.0+FLOAT))。

6、switch

  它是if语句的同胞兄弟,当有单个条件,多个分值时,一般用switch。

  PS:上面两个可以放到一组,都是分支语句。从功能上讲,if可以代替switch语句,但是,switch不能代替if语句。

7、break

  既能用于循环语句,又能用于分支语句,表示终止整个循环。

8、continue

  只能用于循环语句,不能用于分支语句,表示终止本次循环,进入下次循环。

 //示例:从键盘输入3个数,求其中非负数之和
 void test()
 {
     ,sum=;
     )
     {
         scanf("%d",&input);
         count++;
         )    continue;
         sum+=input;
     }
     printf("%d\n",sum);
 }

  PS:这两个可以放到一组,相同点是都能跳出循环,不同点是程度不同。

9、do

  循环语句,先执行,后判断,循环体至少执行一次。

10、while

  循环语句,先判断,后执行,循环体可能不执行。

11、for

  循环语句,先判断,后执行。

  PS:这3个可以放到一组,都是循环语句。

 //示例:求1到n的和
 void test()       //do
 {
     ,sum=;
     scanf("%d",&n);
     )
     {
         do
         {
             sum=sum+n;
             n--;
         }while(n);
         printf("the sum is %d\n",sum);
     }
     else
     {
         printf("please input a number greater than 1");
     }
 }
 void test2()   //while
 {
     ,sum=;
     scanf("%d",&n);
     )
     {
         while(n)
         {
             sum=sum+n;
             n--;
         }
         printf("the sum is %d\n",sum);
     }
     else
     {
         printf("please input a number greater than 1");
     }
 }
 void test3()    //for
 {
     ,n=,sum=;
     scanf("%d",&n);
     )
     {
         ;i--)
         {
             sum=sum+n;
             n--;
         }
         printf("the sum is %d\n",sum);
     }
     else
     {
         printf("please input a number greater than 1");
     }
 }

12、sizeof

  它是编译器内置的一个指示符,并不是一个函数,用于计算各个数据类型所占的内存大小。  

13、void

  (1)void的作用:修饰函数的返回值和参数,如果一个函数没有返回值和参数,就用void声明。

  (2)不允许有void类型的变量,但允许有void*类型的变量。当void*修饰变量时,void*类型变量作为左值用于接收任意类型的指针变量,void*类型变量作为右值赋给其他指针的时候需要进行强制类型装换。

  (3)拓展:在学习线性表的时候,接触到了void*函数,里面用它进行数据封装,这也是产品级代码经常使用的方法。

  PS:void用来修饰函数,void*用来修饰函数和变量。

14、scanf

  scanf语句的作用是等待接收键盘上输入的数据,特点是阻塞式,只有当键盘输入相应的字符之后才能接着执行后面的语句。

15、printf

  (1)作用:打印输出数据,做练习和调试的时候经常用到。

  (2)printf中常用的格式符及含义

格式符 功能
%d 以带符号的10进制形式输出整数(正数不输出正号+)
%u 以不带符号的10进制形式输出整数
%o 以不带符号的8进制形式输出整数
%x 以不带符号的16进制形式输出整数
%c 输出一个字符
%s 输出一个或者多个字符
%f 以小数形式输出单、双精度数,默认输出6位小数
%e 以标准指数形式输出单、双精度数,数字部分小数位数为6位

  (3)printf函数的入栈问题

  原则是先从右边至左计算,然后从左至右打印。理解下面的代码为什么是这个结果。

 void test()
 {
     ;
     printf("%d %d %d\n",a++,a,a++);       //2 3 1
 }
 void test2()
 {
     ;
     printf("%d %d %d\n",a++,a++,a++);     //3 2 1
 }
 void test3()
 {
     ;
     printf("%d %d %d\n",a++,++a,a);       //2 3 3
 }
 void test4()
 {
     int a;
     a=;
     printf("%d %d %d\n",a++,++a,a++);     //3 4 1
     printf("%d %d %d\n",a++,++a,a);       //5 6 6
 }

16、const

  (1)首先弄清楚一点,在标准C中,const定义的不是一个常亮,它定义的是一个只读的变量。本质上只对编译器有用,告诉编译器这个变量不能作为左值,例如下面的代码编译时会报错。 

 void test()
 {
     ;
     printf("%d\n",cc);
     cc=3;                    //函数会在这行报错,因为 cc用const修饰了,代表只读,不能作为左值。
     printf("%d\n",cc);
 }

  (2)在上面讲到用const修饰的变量只对编译器有用,换句话说就是在运行时无用,还是可以同过指针修改变量的值。(const修饰数组与修饰变量类似)

 void test2()
 {
     ;
     int *p=(int *) &cc;     //注意强制类型转换
     printf("%d\n",cc);
     *p=;
     printf("%d\n",cc);
 }

  (3)const修饰指针。要领:左数右指。当const出现在*的左边时,指针指向的数据不能改变,当const出现在*右边时,指针本身(指针指向的地址值)不能改变,当const出现在*的两边时,指针指向的数据和指针本身(指针指向的地址值)都不能改变。

17、volatile

  (1)作用:编译器警告指示字--告诉编译器必须每次去内存中取变量值,不要做优化。

  (2)理解:在C语言中,如果定义的变量没有被当做左值(都是出现在程序语句的右边)使用,编译器就会认为它的值不会被改变, “聪明”的对其优化(能加快程序运行效率),每次默认对它取所赋的右值。举个例子:

 ;
 ;
 ;
 a=result;
 delay();
 b=result;

  上面的代码这样写没问,但是在嵌入式系统中,如果延时的200ms里出现一个中断,要把result的值(内存中的值)改变为5,如果不加volatile修饰int result=1,系统优化后,b的值还会是1,就得不到我们预期的值,出现不可预计的错误。而加上volatile,表示每次都去内存中去取值,这样中断后我们就能得到预期的值。

  (3)思考
  思考一:const和volatile是否可以同时修饰一个变量?
  能。关键搞清楚 编译期 和 运行期 的关系。编译期就是编译器将源代码转化为汇编再到机器代码的过程。运行期就是实际的机器代码在CPU执行的过程。const在编译的时候起作用,它告诉编译器它修饰的变量是只读的,不能而出现在赋值符号左边。实际运行的时候就不是编译器所能管的了。volatile在运行的时候起作用,保证其修饰的变量不被优化,每次每次读取它都会在内存中取值。

  思考二:const volatile int i = 0; 这个时候i具有什么属性?编译器如何处理这个变量?
  属性:在编译期,i是一个只读变量,不能而出现在赋值符号左边。在运行期,i不会被优化,每次读取它都会在内存中取值。

  处理:如果i出现在赋值符号左边,编译报错。

18、struct

  (1)结构体的出现时为了解决数组的不足之处,它是一种构造数据类型。结构体使用总结如下:

  1.1 结构体初始化--分为完全初始化和部分初始化

//1.1 完全初始化--定义结构体的同时定义变量
#if 0
#include<stdio.h>
struct student{
    char *name1;
    ];
    int age;
    char c;               //定义一个字符
    float height;
}stu={,'M',1.75};
//字符串常量初始化这一项会有警告,故实践中,完全初始化字符串时用数组的形式。
int main()
{
    printf("姓名: %s\n",stu.name1);
    printf("姓名: %s\n",stu.name2);
    printf("年龄: %d\n",stu.age);
    printf("age = %c\n",stu.c);
    printf("身高: %.2f\n",stu.height);
    ;
}
#endif
//1.2 完全初始化--先定义结构体,再定义变量
#if 0
#include<stdio.h>
struct student{
    char *name1;
    ];
    int age;
    char c;               //定义一个字符
    float height;
};
,'M',1.75};
//字符串常量初始化这一项会有警告,故实践中,完全初始化字符串时用数组的形式。
int main()
{
    printf("姓名: %s\n",stu.name1);
    printf("姓名: %s\n",stu.name2);
    printf("年龄: %d\n",stu.age);
    printf("age = %c\n",stu.c);
    printf("身高: %.2f\n",stu.height);
    ;
}
#endif
//2.1 部分初始化--定义结构体的同时定义变量
#if 0
#include<stdio.h>
#include<string.h>
struct student
{
    char *name1;          //定义字符串常亮
    ];       //定义字符串变量
    int age;              //定义整形数
    char c;               //定义一个字符
    float height;         //定义一个浮点数
}stu;
int main()
{
    stu.name1="pual"; //字符串常量初始化 --这样子初始化在结构体部分初始化中也会有警告,故实践中,部分初始化字符串时用数组的形式。
    strcpy(stu.name2,"wade");       //字符串变量初始化
    stu.age=;               //整形数初始化
    stu.c='m';                //字符初始化
    stu.height=1.72;          //浮点数初始化
    printf("姓名: %s\n",stu.name1);
    printf("姓名: %s\n",stu.name2);
    printf("age = %d\n",stu.age);
    printf("age = %c\n",stu.c);
    printf(" %.2f\n",stu.height);
    ;
}
#endif
//2.2 部分初始化--先定义结构体,再定义变量
#if 1
#include<stdio.h>
#include<string.h>
struct student
{
    char *name1;          //定义字符串常亮
    ];       //定义字符串变量
    int age;              //定义整形数
    char c;               //定义一个字符
    float height;         //定义一个浮点数
};
struct student stu;
int main()
{
    stu.name1="pual"; //字符串常量初始化 --这样子初始化在结构体部分初始化中也会有警告,故实践中,部分初始化字符串时用数组的形式。
    strcpy(stu.name2,"wade");       //字符串变量初始化
    stu.age=;               //整形数初始化
    stu.c='m';                //字符初始化
    stu.height=1.72;          //浮点数初始化
    printf("姓名: %s\n",stu.name1);
    printf("姓名: %s\n",stu.name2);
    printf("age = %d\n",stu.age);
    printf("age = %c\n",stu.c);
    printf(" %.2f\n",stu.height);
    ;
}
#endif
/*“特别注意”:    stu.name="abc";
                    stu.age=15;
                    stu.height=1.72;
    这些部分初始化只能写在函数里面,不能写在主函数外面,如3
    //3
    #include<stdio.h>
    struct student
    {
        char *name;
        int age;
        float height;
    } stu;
    stu.name="abc";
    stu.age=15;
    stu.height=1.72;
    int main()
    {
        printf("姓名: %s\n",stu.name);
        printf("age = %d\n",stu.age);
        printf(" %.2f\n",stu.height);
        return 0;
    }
    为什么写在外面不行呢?总结如下:
    我写了几个简单程序:
    (a)#include<stdio.h>
         int i=1;
         void main()
         {
             printf("%d\n",i);
         }
     这是正常的,相当于完全初始化。
    (b)#include<stdio.h>
         int i;

         void main()
         {
             i=1;
             printf("%d\n",i);
         }

         #include<stdio.h>
         int i;
         void test()
         {
            i=1;
             printf("%d\n",i);
         }
         int main()
         {
            test();
            return 0;
         }
     这也是正常的,相当于部分初始化。
    (c)#include<stdio.h>
         int i;
         i=1;
         void main()
         {
             printf("%d\n",i);
         }
     这是不正常的,相当于3。
    总的来说,在定义一个全局变量以后,要么在定义的时候就赋初值(如1或a),要么在主函数里面再复制(2或b),千万不能先定义,然后再赋值(如3或c)
 还可以这样理解,i=1;是一条语句,放在主函数外面,不能执行到。
*/

  1.2 结构体不允许对本身的递归定义,即结构体内部不能包含结构体本身,但是可以包含别的结构体。

struct date
    {
        int year;
        int month;
        int day;
    };
struct student
    {
        int age;
        struct date birthday;
    };
void test()
{
    ,{,,}};   // {2015,1,1}这个括号可加可不加,加了思路更清晰
    printf("%d\n",stu.age);
    printf("%d\n",stu.birthday.year);
    printf("%d\n",stu.birthday.month);
    printf("%d\n",stu.birthday.day);
}

  1.3 结构体与函数--作为函数参数

//修改结构体成员变量的值--比较test1和test2的差别
struct Person { int age1; int age2; }; void change1(struct Person p1) { p1.age1=; } void test1(void) { }; printf("%d\n",person1.age1); change1(person1); printf("%d\n",person1.age1); } void change2(struct Person* p2) { (*p2).age2=; } void test2(void) { struct Person person2; person2.age2=; printf("%d\n",person2.age2); change2(&person2); printf("%d\n",person2.age2); }

  1.4 结构体与数组

struct student
    {
        int age;
        char *name;
        float height;
    }a[]={{,,"lmj",1.90}};
void test(void)
{
    printf(].age);
    printf(].name);
    printf(].height);
    printf(].age);
    printf(].name);
    printf(].height);
}

  1.5 结构体与指针 

struct Person
{
    int age;
    ];
};
void test()
{
    ,};
    struct Person *p;
    p=&person;
    printf("%d\n",person.age);
    printf("%d\n",(*p).age);
    printf("%d\n",p->age);
    printf(]);
}

  (2)思考:空结构体占多大内存?这是C语言的灰色地带,不同的编译器下面不同,在gcc编译器下面,占用0个字节,a和b的地址相同。在g++下面运行,占用1个字节,a和b的地址相差1。

struct test
{
};
void Test(void)
{
    struct test a;
    struct test b;
    printf("%d\n",sizeof(struct test));
    printf("%d, %x\n",sizeof(a), &a);
    printf("%d, %x\n",sizeof(b), &b);
}

  (3)由结构体实现柔性数组。 柔性数组的概念:数组大小待定的数组。因为C语言中结构体的最后一个元素可以是大未知的数组,所以可以根据这个特性由结构体产生柔性数组。

//柔性数组的大小是4,也就是 int array[];占用0个字节
typedef struct { int length; int a[]; }FlexibleArray; void test(void) { int i; FlexibleArray* space=(FlexibleArray*)malloc(); //siezeof(int)*8 决定柔性数组的大小 (*space).length=; ;i<(*space).length;i++) { (*space).a[i]=i+; printf("%d\n",(*space).a[i]); } free(space); }

  

19、union

  (1)用法和结构体类似,初始化的时候也可以分完全初始化和部分初始化。但是完全初始化的时候只能写一个初始化值,它结构体不同,它虽然定义了几个变量,实际上每次只有一个变量起作用,

所以完全初始化的时候只能共用一个,实际应用不用完全初始化。

//会报错--[Error] too many initializers for 'name' ,只能初始化一个
union name
{
    char c;
    int a;
    int b;
}d={,};

//正确方式
union name
{
    char c;
    int a;
    int b;
}d={'H'};

  (2)union和struct比较

  相同点:都可以定义多个成员,且定义方式类似。

  不同点:结构体大小由所有成员决定,等于所有成员所占大小之和,而union的大小由最大的成员的大小决定。 结构体所有成员都有自己的内存空间,而union所有成员共享同一块大小的内存,一次只能使用其中的一个成员。

  (3)对某一个成员赋值,会覆盖其他成员的值。

  (4)union的使用受操作系统的大小端的影响。小端模式:低位存低字节,高位存高字节。大端模式:低位存高字节,高位存低字节。下面程序中,如果操作系统是小端模式,打印结果为1,如果操作系统是大端模式,打印结果为0。

#include <stdio.h>
union Test
{
    int i;
    char j;
};
int test(void);
int main()
{
    ;
    m=test();
    printf("%d\n",m);
    ;
}
int test(void)
{
    union Test a;
    a.i = ;
    return a.j;
}

  反过来,可以用union验证操作系统的大小端。上面程序中,如果打印结果为1,那么操作系统是小端模式。如果打印结果为0,操作系统是大端模式。

20、枚举

  (1)枚举与宏定义有关系,它是宏定义的一种优化,现实中两种都有人用。但是用枚举的话有两个好处:第一,阅读代码时更清晰,用枚举列举定义,一看这几个就是一伙的。第二,人非圣贤,万一程序中将变量赋值了一个N,用宏定义定义的话,会执行:default:break;  而用枚举的话,直接报错,我们能迅速找到错误。举个例子:

#include <stdio.h>
#if 0
#define SUN    0
#define MON    1
#define TUE    2
#define WEN    3
#define THR    4
#define FRI    5
#define SAT    6
#define N 10
#endif
#if 1
enum week
{
    SUN,MON,TUE,WEN,THR,FRI,SAT,
};
#endif
int main(void)
{
    enum week today;        // 使用enum week类型,来定义一个枚举变量today
    today = SAT;
    switch (today)
    {
        case MON:printf("hao kun a.\n");break;
        case TUE:printf("2\n");break;
        case WEN:printf("3.\n");break;
        case THR:printf("4\n");break;
        case FRI:printf("5.\n");break;
        case SAT:printf("6\n");break;
        case SUN:printf("ha ha.\n");break;
        default:break;
    }
    ;
}

  (2)枚举定义类似于结构体和共用体,但初始化方式不同。默认第一个枚举常量为0,其他枚举常量在前一个值的基础上一次加1。

  (3)枚举类型和#define的区别:①宏常量只是简单的进行值替换,无类型信息,不是真正意义上的常量,枚举常量是一种特定类型的常量。②#define宏常量无法被调试,枚举常量可以。

21、typedef

  (1)作用:给各种数据类型起一个别名。

  (2)用法归纳

  2.1 给基本数据类型起别名

void test(void)
{
    typedef int Integer;
    typedef Integer MyInteger;
    typedef unsigned int UInteger;
    ;
    Integer b = ;
    UInteger c = ;
    MyInteger d = ;
    printf("%d\n",a);
    printf("%d\n",b);
    printf("%d\n",c);
    printf("%d\n",d);
}

  2.2 给指针类型起别名

void test1(void)
{
    char* s = "pual";
    printf("%s\n",s);
    typedef char* String;
    String s1 = "wade";
    printf("%s\n",s1);
}

  2.3 给结构体起别名

void test2(void)
{
    typedef struct
    {
        float x;
        float y;
    }Point;
    Point p = {,};
    printf("%f\n",p.x);
    printf("%f\n",p.y);
}

  2.4 给结构体指针起别名

//先给结构体起一个别名,再给指向结构体的指针起一个别名
void test3_1(void)
{
    typedef struct
    {
        float x;
        float y;
    }Point;
    typedef Point* PP;
    Point point = {10.0f, 20.0f};
    PP pp = &point;
    printf("x=%f, y=%f\n", pp->x, pp->y);
    printf("x=%f, y=%f\n", (*pp).x,(*pp).y);
    printf("x=%f, y=%f\n", point.x, point.y);
}
//直接给指向结构体的指针起一个别名
void test3_2(void)
 {
    typedef struct Point {
       float x;
       float y;
   }* PP;
    struct Point point={1.0f, 2.0f};
    PP pp = &point;
    printf("x=%f, y=%f\n", pp->x, pp->y);
    printf("x=%f, y=%f\n", (*pp).x,(*pp).y);
    printf("x=%f, y=%f\n", point.x, point.y);
}

  2.5 给枚举起别名(类似于结构体)

void test4(void)
{
    typedef enum
    {
        spring,
        summer,
        autumn=,
        winter,       //最后一个逗号可加可不加
    }Season;
    Season s1 = spring;
    Season s2 = autumn;
    Season s3 = winter;
    printf("%d\n",s1);
    printf("%d\n",s2);
    printf("%d\n",s3);
    printf("%d\n",winter);
}

  2.6 给指向函数的指针定义一个别名

int sum(int a, int b)
{
    int c = a + b;
    printf("%d+%d=%d\n", a, b, c);
    return c;
}
//不用typedef起别名时的指向函数的指针
void test5_1(void)
{
    int (*p)(int, int);
    ;
    p = sum;
    receive_sum=(*p)(, );
    printf("%d\n",receive_sum);
}
//给指向函数的指针定义一个别名(这个跟之前的不一样,在这里SumPoint就是别名--int (* )(int,int) )
void test5_2(void)
{
    typedef int (*SumPoint)(int,int);
    ;
    SumPoint p = sum;
    receive_sum=(*p)(, );
    printf("%d\n",receive_sum);
}

  2.7 给数组类型起一个别名

//数组类型由元素类型和数组大小共同决定
//标准:typedef type(name)[size];
//举例:typedef int(INT5)[5];
void test6(void)
{
    typedef ];
    AINT5 a={,,,,};  //这样就定义就等价与int a[5];
    printf(]);
    printf(]);
} 

  (3)给一种数据类型起一个别名最好用typedef,而不用宏定义。理由如下:

    typedef char* String1;   //typedef是数据类型替换
    #define String2 char*    //宏定义纯粹是字符串替换

    String1 s1,s2;
    //相当于
    char* s1;
    char* s2; 

    String2 s3,s4;
    //相当于
    char* s3;
    char  s4;

最后一个goto,尽量不用,因为它有很大的副作用,可能导致一些本该执行的语句被跳过!