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

  • 全局变量:

    • 次第③运维就会分配空间,直到程序截至。
    • 仓储地方在静态存储区
    • 三个同名的全局变量指向一律块存储空间。
    • 大局变量暗中认可为0
    • 分类:
      • 其间变量:只能在那个文件之中访问(加上static关键字)
      • 外部变量:可以在其余文件中访问的变量,暗中同意持有全局变量都以表面变量。(什么都不加或是有extern评释)
  • static:

    • static对有的变量的功能:

      • 延长部分变量的生命周期,从程序运行到程序退出,可是它并不曾改观变量的功效域。相当于说,固然保障它一直留存,但是该无法用的时候依旧不恐怕用。
      • 概念变量的代码在方方面面程序运行期间单单会举行五回。注意,是概念变量的代码,约等于说只会定义一回。
    • static对全局变量的作用:

      • 鉴于静态全局变量的意义域局限于2个源文件内,只可以为该源文件内的函数公用,由此得以
        幸免在别的源文件中引起错误。

  • extern:

    • 假定评释的时候没有写extern,那系统会自行定义这一个变量,并将其发轫化为0。
    • 比方注脚的时候写extern了,那系统不会活动定义这么些变量。
    • 相似时候用extern来声称变量是别的文件的外部变量。(?)
  • static 与 extern对函数的成效:

    • C语言默许全体函数都是外部函数,可以被其余文件访问;而内部函数只可以在本文件中走访。

    • 一律地,加上static关键字可以申明和定义1个函数变为内部函数,extern则足以注解和定义1个外表函数。

    • 例:

      // 声明一个内部函数
      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 <stdio.h>
    
    #endif /* defined(__xxxxxx__x__) */
    

    譬如说这是xxxxx里面的x.h文件,当其他文本导入这些文件的时候(#include),约等于复制那么些文件里的始末。如若导入了三个文件不小心重复导入了同多个,那么像上边那样写是未曾难点的。因为预处理指令会首先检查是不是定义了

    __xxxxxx__x__
    

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

    那就是说只要下次际遇(那时候都是在预编译的时候做到的),它照旧检查是还是不是定义了

    __xxxxxx__x__
    

    本次因为刚刚大家早就定义了,那么它就会直接为止那一个命令,所以不会另行导入相同的头文件。

  • 假设出现重复导入循环(比如a.h文件里导入了b.h,而b.h文件里导入了a.h),化解格局:

    比方a.h表明了2个add方法,b.h里面申明了三个minus方法,不过此时再次引用循环,编译器会报错,那么可以在b.h里面不再导入a.h就不会现出循环导入的难点。然而本身又想在b.h里面访问a.h里面的add方法,不过此时b.h里面并从未导入a.h,那么大家就平素将add方法的宣示复制到b.h里面即可。

  • const

    const的机能紧借使多少个:

    1. 节省空间,制止不要求的内存分配。(斯维夫特中提出使用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 标识指向和值都不大概改。