【C语言】10.static&extern,typedef,宏,条件编译,const

  • 全局变量:

    • 程序一启动就会分配空间,直到程序结束。
    • 存储位置在静态存储区
    • 多个同名的全局变量指向同一块存储空间。
    • 全局变量默认为0
    • 分类:
      • 内部变量:只能在这个文件内部访问(加上static关键字)
      • 外部变量:可以在其他文件中访问的变量,默认所有全局变量都是外部变量。(什么都不加或是有extern声明)
  • static:

    • static对局部变量的作用:

      • 延长局部变量的生命周期,从程序启动到程序退出,但是它并没有改变变量的作用域。也就是说,虽然保证它一直存在,但是该不能用的时候还是不能用。
      • 定义变量的代码在整个程序运行期间仅仅会执行一次。注意,是定义变量的代码,也就是说只会定义一次。
    • static对全局变量的作用:

      • 由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以 避免在其它源文件中引起错误。

  • extern:

    • 如果声明的时候没有写extern,那系统会自动定义这个变量,并将其初始化为0。
    • 如果声明的时候写extern了,那系统不会自动定义这个变量。
    • 一般时候用extern来声明变量是其他文件的外部变量。(?)
  • static 与 extern对函数的作用:

    • C语言默认所有函数都是外部函数,可以被其他文件访问;而内部函数只能在本文件中访问。

    • 同样地,加上static关键字可以声明和定义一个函数变为内部函数,extern则可以声明和定义一个外部函数。

    • 例:

      // 声明一个内部函数
      static int sum(int num1,int num2);
      
      // 定义一个内部函数
      static int sum(int num1,int num2)
      {
          return num1 + num2;
      }
      
      // 声明一个外部函数
      extern int sum(int num1,int num2);
      
      // 定义一个外部函数
      extern int sum(int num1,int num2)
      {
          return num1 + num2;
      }
      

  • typedef:

    • 宏定义也可以完成typedef的工作,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便且不易出错。

    • 例:

      typedef int INTEGER;
      
      typedef Integer MyInteger;
      
      typedef char NAME[20]; // 表示NAME是字符数组类型,数组长度为20。然后可用NAME 说明变量。
      NAME a; // 等价于 char a[20];
      
      typedef char * String;
      String myString = "hello";
      

      与结构体的声明定义一样,在对结构体使用typedef时,也有三种形式:

      struct Person{
          int age;
          char *name;
      };
      
      typedef struct Person PersonType; // PersonType person; person.age = ...
      
      /////////////////////////////////////////////////////////////////////
      
      typedef struct Person{
          int age;
          char *name;
      } PersonType;
      
      注意这个和结构体在定义的时候直接初始化一个值的区别,这个是由typedef关键字的,且是别名,而不是一个变量。
      
      /////////////////////////////////////////////////////////////////////
      
      typedef struct {
          int age;
          char *name;
      } PersonType;
      
      省略结构体名。
      
      /////////////////////////////////////////////////////////////////////
      
      // typedef和指向结构体的指针
      // 首先定义一个结构体并起别名 
        typedef struct { float x; float y; } Point;
      // 起别名
        typedef Point *PP;
      

      与枚举的声明定义一样,在对枚举使用typedef时,也有三种形式:

      enum Sex{
          SexMan,
          SexWoman,
          SexOther
      };
      typedef enum Sex SexType;
      
      /////////////////////////////////////////////////////////////////////
      
      typedef enum Sex{
          SexMan,
          SexWoman,
          SexOther
      } SexType;
      
      /////////////////////////////////////////////////////////////////////
      
      typedef enum{
          SexMan,
          SexWoman,
          SexOther
      } SexType;
      

      typedef和函数指针:(重要!)

      int add(int a, int b) {
          return a + b;
      }
      
      int main() {
          typedef int (*myFunction) (int, int); 
        //注意此时myFunction就是int (*myFunction) (int, int)的别名。表示这是一种指向函数的指针的类型。
          myFunction p = add; 
          p();
          return 0;
      }
      

    • 不带参数的宏定义:

      • 格式: #define 标示符 字符串(“字符串”可以是常数、表达式、格式串等。)
      • 宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令。
    • 带参数的宏定义:

      • 对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

      • #define 宏名(形参表) 字符串

      • #define average(a, b) (a+b)/2

      • 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串。

      • 带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。并且计算结果最好也括起来防止错误。

        #define Pow(a) ( (a) * (a) )
        
  • 条件编译

    • 在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。换句话说,在条件编译中,不满足条件的会直接被删去,并不会参与编译。
    • 条件编译和宏定义经常一起使用。
#define SCORE 67
#if SCORE > 90
    printf("优秀\n");
#else
    printf("不及格\n");
#endif

// 条件编译后面的条件表达式中不能识别变量,它里面只能识别常量和宏定义

#if 条件1
  ...code1...
 #elif 条件2
  ...code2...
 #else
  ...code3...
 #endif

#define SCORE 67
#if SCORE > 90
    printf("优秀\n");
#elif SCORE > 60
    printf("良好\n");
#else
    printf("不及格\n");
#endif

  • 注意,条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的。
  • 一定不要忘记在最后加上 #endif !
  • ifndef 条件编译指令 与 上面用法类似,不过是条件相反的。
  • 使用条件编译指令调试 bug

    #define DEBUG1 0
    #if DEBUG1 == 0 //format是格式控制,##表示可以没有参数,__VA_ARGS__表示变量 #define Log(format,...) printf(format,## __VA_ARGS__)
    #else
    #define Log(format,...)
    #endif
    
    void test(){
        Log("xxxxx");
    }
    int main(int argc, const char * argv[]) {
        Log("%d\n",10);
        return 0;
    }
    

    这种场景用Log代替printf,在调试的时候我们只需要改变宏定义的值,就可以灵活的控制输出的语句。比如把DEBUG1设置为0,变为调试状态,那么所有的printf就会替代Log输出一些信息;把DEBUG1设置为1,则关闭调试状态,则会将Log代替为空,则不会有输出。

    这里,‘...’指可变参数。这类宏在被调用时,##表示成零个或多个参数,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体(macro body)中,那些符号序列集合将代替里面 的VA_ARGS标识符。

  • 或者用条件指令在调试阶段设置一些默认值,比如账号密码,等结束调试只需要改变宏命令就可以关闭这个功能。

  • 在C语言中防止重复导入头文件的问题:

    #ifndef __xxxxxx__x__
    #define __xxxxxx__x__
    
    #include 
    
    #endif /* defined(__xxxxxx__x__) */
    

    比如这是xxxxx里面的x.h文件,当别的文件导入这个文件的时候(#include),相当于复制这个文件里的内容。如果导入了多个文件不小心重复导入了同一个,那么像上面这样写是没有问题的。因为预处理指令会首先检查是否定义了

    __xxxxxx__x__
    

    如果没有定义,那么就定义,并且导入stdio.h文件。

    那么如果下次遇到(这时候都是在预编译的时候完成的),它仍然检查是否定义了

    __xxxxxx__x__
    

    这次因为刚才我们已经定义了,那么它就会直接结束这个指令,所以不会重复导入相同的头文件。

  • 如果出现重复导入循环(比如a.h文件里导入了b.h,而b.h文件里导入了a.h),解决方法:

    如果a.h声明了一个add方法,b.h里面声明了一个minus方法,但是此时重复引用循环,编译器会报错,那么可以在b.h里面不再导入a.h就不会出现循环导入的问题。但是我又想在b.h里面访问a.h里面的add方法,但是此时b.h里面并没有导入a.h,那么我们就直接将add方法的声明复制到b.h里面即可。

  • const

    const的作用主要是两个:

    1. 节省空间,避免不必要的内存分配。(Swift中建议使用let也是这个原因?)

      #define PI 3.14159 //常量宏
      const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ...... double i=Pi; //此时为Pi分配内存,以后不再分配!
      double I=PI; //编译期间进行宏替换,分配内存
      double j=Pi; //没有内存分配
      double J=PI; //再进行宏替换,又一次分配内存! const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
      

      编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

    2. 更加安全。

    使用:

    1. 在变量名前或变量名后。

      int const x = 2;

      const int x = 2;

    2. 注意用const修饰指针:

      • const int *A; //const修饰指针,A可变,A指向的值不能被修改

      • int const *A; //const修饰指向的对象,A可变,A指向的对象不可变

      • int *const A; //const修饰指针A, A不可变,A指向的对象可变

      • const int *const A;//指针A和A指向的对象都不可变

      先看“*”的位置

      如果const 在 *的左侧 表示值不能修改,但是指向可以改。

      如果const 在 *的右侧 表示指向不能改,但是值可以改

      如果在“*”的两侧都有const 标识指向和值都不能改。

你可能感兴趣的:(【C语言】10.static&extern,typedef,宏,条件编译,const)