11-C语言修饰符和预处理指

全局变量和局部变量

  • 局部变量

    • 局部变量就是从开始定义到遇见return 或者遇到}为止
    • 存储位置在内存的栈区中
    • 特点:
      • 相同作用域内不可以定义同名变量,
      • 不同作用域可以定义同名变量,但是内部变量会覆盖外面定义的变量
  • 全局变量

    • 全局变量的作用范围就是从定义的那行开始到文件结束
    • 存储在静态存储区
    • 特点: 多个同名的全局变量指向同一块存储空间

auto 和 register 关键字

  • auto 关键字只能用于修饰局部变量的; 默认就是 auto
  • register: 告诉编译器,将局部变量存储到cpu寄存器下
  • auto 和 register共同点都是修饰局部变量的
  • 好处:
    • auto : 特点就是告诉编译器,局部变量离开作用域自动释放
    • register好处就是访问的速度回更快;

static 关键字

  • static对局部变量的作用

    • 如果利用static修饰局部变量,那么会将局部变量的存储区域从栈去移动到静态区

    • 静态区只有程序结束才会释放

    • 没有用static修饰的变量

    #include 
    void test();
    int main()
    {
        test();
        test();
        test();
        return 0;
    }
    
    
    void test(){
        int num = 0;
        num++;
        printf("num = %i\n", num);  // 局部变量的时候输出1  1  1
    }
    
    
    • 用static修饰过的
    #include 
    void test();
    int main()
    {
        test();
        test();
        test();
        return 0;
    }
    
    
    void test(){
        static int num = 0;
        num++;
        printf("num = %i\n", num);  // 1  2  3
    }
    
    
  • static 对全局变量的作用

    • 全局变量默认是所有文件都可以使用
    • 但是如果想要让某个函数在一个文件内使用,那么就可以使用static进行修饰
// main.c 下的文件, 引入了a.h这个头文件
#include 
#include "a.h"
int main()
{
    int res1 = sum(10, 20);
    printf("res1 = %i\n", res1);  // 如果函数被Static修饰,则外部文件不能访问报错
    int res2 = minus(10, 20);
    printf("res2 = %i\n", res2);
    return 0;
}

// a.c 下的文件
static int sum(int num1, int num2){
    return num1 + num2;
}
int minus(int num1, int num2){
    return num1 - num2;
}

  • static 对函数的作用就是上面的代码
    默认情况下, 所有的函数都是外部函数(默认有extern, 但是一般都省略不写)
    所以 static修饰函数,就是可以定义一个内部使用函数,不会被外界污染;

  • 注意点: 如果只有函数声明添加了static 与 extern, 而定义中没有添加static与
    extern, 那么 static的作用无效
    ;

extern 关键字

  • extern对局部变量的作用

    • extern 用于声明一个变量,声明变量并不会开辟存储空间
    • extern 一般用于全局变量,不能用于局部变量
  • extern对全局变量的作用

    • 用于声明一个全局变量, 应为全局变量时从定义开始的哪一行才能进行使用
    • 那么使用extern关键字声明这个变量,那么在文件中可以放心使用
    • 没有使用extern 声明变量
    #include 
    
    int main()
    {
        num = 666;
        printf("num = %i\n", num);  // 错误 全局变量定义在使用之后
        return 0;
    }
    
    int num;
    
    • 使用extern 声明变量
    #include 
    extern int num;
    int main()
    {
        num = 666;
        printf("num = %i\n", num);  // 666
        return 0;
    }
    
    int num;
    

编译处理过程

  • 源文件 --> 预处理 --> 汇编 --> 二进制 --> 和系统的二进制文件进行链接 --> 可执行程序

预处理

  • 预处理指令
    • 文件包含 include
    • 宏定义 #define
    • 条件编译 #if #else #endif

宏定义

  • 宏定义会在预处理的时候, 用宏定义对应的值来替换宏

不带参数的宏定义

  • 格式: #define 标识符 字符串

    • 其中的"#"表示这是一条预处理命令.凡是以"#" 开头的均为预处理命令.
    • "define"为宏定义命令.
    • "标识符"为所定义的宏名.
    • "字符串" 可以是常数,表达式,格式串
    #define COUNT 5
    
    • 应用场景: 定义一个宏修改URL, 只需要改一个宏定义就好
  • 注意点:

    • 1.宏名一般用大写字母,以便与变量名区别开来.
    • 2.宏定义的后面不要写分号,因为宏定义是单纯的替换,会用宏的值来替换宏的名称
    • 3.宏定义分为两种,一种是带参数的宏定义,另一种是不带参数的宏定义
    • 定义一个宏时可以引用已经定义的宏名;
    #define R  3.0
    #define PI 3.14
    #define L  2*PI*R
    #define S  PI*R*R
    

带参数的宏定义

  • 格式: #define 宏名(形参表) 字符串
#define SUM(a, b) a+b
  • 注意点:
    • 宏定义无论有没有参数,都是单纯的替换
    #include 
    int sum(int a, int b){
        return a + b;
    }
    
    #define SUM(a,b) a+b
    
    int main()
    {
        int num1 = 10, num2 = 20;
         
        // int res = SUM(num1, num2);
        // int res = a + b;
        // int res = num1 + num2;
        int res = SUM(num1, num2);
        printf("res = %i\n", res);
        return 0;
    }
    
    
    • 宏的值的每个参数都需要加上(),宏的值的结果也需要加上();
      • 只有一个参数时
      #include 
      #define SUM(a) a*a   // 写法有点问题
      int main()
      {
          int num = 5;
          int res = SUM(num);  // 虽然正确输出
          printf("res = %i\n", res);
          return 0;
      }
      
      
      • 如果宏定义的时候不用小括号括住引发结果错误
      #include 
      #define SUM(a) a*a
      int main()
      {
      
          // int res = a * a;
          // int res = 3 + 3 * 3 + 3;
          int res = SUM(3 + 3); // 15  错误输出
          printf("res = %i\n", res);
          return 0;
      }
      
      
      • 只给宏的值加上括号还是容易引发输出错误
      #include 
      #define SUM(a) (a)+(a)
      int main()
      {
          
          // int res = (a) * (a);
          //int res = (3 + 3) + (3 + 3) / (2 + 2)  + (2 + 2);
          int res = SUM(3 + 3) / SUM(2 + 2);  // 输出11
      
          printf("res = %i\n", res);
          return 0;
      }
      
      • 正确写法: 宏的值的每个参数都需要加上(),宏的值的结果也需要加上()
      #include 
      #define SUM(a) ((a)+(a))
      int main()
      {
      
          // int res = ((a) * (a));
          //int res = ((3 + 3) + (3 + 3)) / ((2 + 2)  + (2 + 2));
          int res = SUM(3 + 3) / SUM(2 + 2);  // 输出1
          // 宏的值的每个参数都需要加上(),宏的值的结果也需要加上()
          printf("res = %i\n", res);
          return 0;
      }
      
  • 在企业开发中使用函数还是使用宏定义
    • 如果函数特别简单,一句话能搞定,那么可以使用宏定义,宏定义的效率更高
    • 宏定义在预处理时候就执行了,而函数是在运行的时候才会执行
    • 宏定义只会做简单的替换,而函数还需要分配内存空间

宏定义的作用域

  • 宏定义的作用域和全局变量很像, 但是宏定义可以提前结束
// 宏定义
#define COUNT 666

// 用来提前结束宏定义
#undef COUNT   
  • 作用域是从定义的那一行开始, 直到文件末尾;
  • 可以通过 #undef 宏名 来提前结束宏的作用域

条件编译

  • 条件编译和if else语句很像,但是也有区别
  • 区别:
    • 1.if else语句是在程序运行的时候执行的, 而#if #else #endif 是在预处理的时候执行的
    • 2.if else语句所有的代码都会编译到程序中, 而#if #else #endif中满足条件的语句才会编译到程序中
  • if 和 #else的三种写法 和 if else写法类似

    • 第一种:
    #if 0 == score
        printf("0分\n");
    #endif
    
    • 第二种:
    #if 0 == socre
        printf("0分\n");
    #else 
        printf("100分");
    #endif
    
    • 第三种:
    #define SCORE 100
    #if 100 == SCORE
        printf("100分\n");
    #elif 60 <= SCORE
        printf("及格了\n");
    #else
        printf("不及格\n");
    #endif
    
  • 注意点:
    • 条件编译中不能获取变量的值
    • 因为变量时程序执行的时候才有的,而条件编译时预处理的时候就执行了
    • 君生我未生 我生君已老
    • 条件编译一般是配合宏定义来使用, 因为宏定义和条件编译都是在预处理的时候执行的
  • 应用的小例子
#define DEBUG 0
#if DEBUG == 1
#define URL "127.0.0.1"
#else
#define URL "www.it666.com"
#endif
// 判断有没有定义名称叫做SUM的宏
#ifndef SUM
// 如果没有定义定义就会进入到这里面,然后定义一个叫做SUM的宏
#define SUM
int sum (int num1, int num2);
#endif

文件包含 #include

  • include <>

  • <>会先从编译器的环境中查找对应的文件,如果没有再从系统的环境中查找对应的文件

  • include "xxx"

  • "xxx" 会先从当前项目环境中查找对应的文件,如果没有再从编译器的环境中查找对应的文件,如果还没有再从系统环境中查找对应的文件

  • include作用: 将指定文件中的代码原封不动的拷贝到#include的位置

  • inclued执行时间: 预处理的时候执行的

  • 注意点:

    • 已知函数的定义不可以重复,但是函数的声明可以重复定义
    • 但是如果重复声明函数,或重复导入.h文件会影响编译器的编译效率
    • 所以在C语言中有专门解决重复导入的方法
    // 判断有没有定义名称叫做SUM的宏
    #ifndef SUM
    // 如果没有定义定义就会进入到这里面,然后定义一个叫做SUM的宏
    #define SUM
    int sum (int num1, int num2);
    #endif
    
    • 特别注意:不要出现循环包含, 例如A的头文件包含B文件,B文件又包含A文件

typedef 关键字

  • C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取 "别名";
  • 注意点:typedef相当于给人起了一个外号;所以typedef不是定义一个新的数据类型,而是定义一个新的名称
  • 格式: typedef 原类型名 新类型名;
    • 其中原类型名中含有定义部分,新类型名一般用大写表示,以便区别.
typedef int INTEGER
INTEGER a;  // 等价于int a;

  • 1.给普通指针起别名, 简化,更容易理解
typedef char* String;
String name = "cww";
printf("name = %s\n", name);

  • 2.给结构体起别名
    • 2.1先定义结构体类型,再给结构体类型起别名
    struct Person{
        char *name;
        int age;
    };
    typedef struct Person ps;
    ps p = {"cww", 18};
    printf("name = %s\n", p.name);  // cww
    
    
    • 2.2定义结构体类型的同时, 给结构体类型起别名
    typedef struct Person{
        char *name;
        int age;
    } ps;
    
    ps p = {"cww", 18};
    printf("name = %s\n", p.name);  // cww
    
    
    • 2.3定义结构体类型的同时,给结构体类型起别名,并且省略原有类型的名称
    typedef struct{
        char *name;
        int age;
    } Person;
    // struct Person p = {"cww", 18};
    Person p = {"cww", 18};
    printf("name = %s\n", p.name);
    
    • 给共用体\枚举起别名,格式和结构体一模一样
  • 3.给指向函数的指针起别名
    • 如果给指向函数的指针起别名, 那么指针名称就是别名
// 常规写法
int (*funcP)(int , int);
funcP = ∑

int (*funcP2)(int , int);
funcP2 = −

// 注意点:如果给指向函数的指针起别名, 那么指针名称就是别名
typedef int (*funcP)(int , int);
funcP p1 = ∑
funcP p2 = −

int sum(int num1, int num2){
    return num1 + num2;
}

int minus(int num1, int num2){
    return num1 - num2;
}

  • typedef和#define 注意点: 如果要给数据类型起别名, 那么一律用typedef即可;

const 关键字

  • const是一个类型修饰符
    • 使用const修饰变量则可以让变量的值不能改变
  • 注意点:
    • const可以写在数据类型的前面,也可以写在数据类型的后面;

    • 对于基本数据类型来说,写在数据类型的前面和后面没有任何区别

    #include 
    
    int main()
    {
        // int num = 10; // 不加const, num是一个变量,可以被修改
    
        // 只要在变量前面加上const, 那么这个变量保存的数据就不会修改了
        // const int num = 10;
        int const num = 10;
        printf("num = %i\n", num);
        num = 666;  // 变量前面加上const ,则变量的值不会修改, 报错
        printf("num = %i\n", num);
        return 0;
    }
    
    
    • 但是对于指针来说, 写在数据类型的前面和后面就有区别了

const 和指针

  • const 如果写在指针数据类型左边和右边(即const写在星号的左边的), 那么代表指针指向的内存空间不能修改, 但是指针的指向可以修改;
#include 

int main()
{
  // 指针和const
    int num = 666;
    // 定义一个指针
    // int *p;
    // const int *p;  // const 写在数据类型的左边
    int const *p;   // const 写在数据类型的右边
    p = #
    printf("*p = %i\n", *p);

    // *p = 888;  // const 修饰的变量 不可以修改内存空间的值
    printf("*p = %i\n", *p);

    int value = 777;
    p = &value;  // 重新给p赋予value的地址
    printf("*p = %i\n", *p);  // 输出777

    return 0;
}
  • const 如果写在指针星号的右边, 那么代表指针指向的内存空间可以修改,但是指针的指向不可以修改;
#include 

int main()
{

    // 指针和const
    int num = 666;
    
    int * const p = #
//    p = #
    printf("*p = %i\n", *p);  // 666
    *p = 888;
    printf("*p = %i\n", *p);  // 888

    int value = 777;
    // p = &value;   // 报错, const 写在星号的右边,则指针的指向不可以修改
    printf("*p = %i\n", *p);

    return 0;
}

你可能感兴趣的:(11-C语言修饰符和预处理指)