第十一篇(C语言终篇)typedefy关键字,自定义头文件,GCC编译过程中文件转化,宏定义和条件编译详解。

一、typedef关键词。
1、什么是typedef,它是干嘛用的?
typedef其实就是type + define,作用就是给一种数据类型(基本数据类型/非基本数据类型)取一个别名。
例如:
给int这种类型取一个新的别名叫aaa               --> 很少给基本数据类型取新的名字  0.00001%
给struct person这种类型取一个新的别名person    --> 大部分时候都是给复杂类型取别名  --> 好处: 定义变量与普通变量一样   99.999%

struct person   --> 新的别名: person
再定义变量: person a;

总结:简化复杂的数据类型。

2、如何使用typedef给类型取新的别名?

例子1: 给int这种类型取一个新的别名叫aaa
1)在代码中定义一个整型变量,变量名以类型的新名来命名。       int aaa         ---> aaa就是整型变量的变量名
2)在这个定义的变量语句的前面加typedef                    typedef int aaa    ---> aaa就是int这种类型的新名字
3)在后面的代码中定义整型变量                                aaa a;          ---> 前面的aaa就是数据类型  后面的a就是变量名
  
----------------------------------
#include <stdio.h>

typedef int aaa;

int main(int argc,char *argv[])
{
    aaa a = 10;
    printf("a = %d\n",a);
    
    return 0;
}
----------------------------------

例子2: 给struct person这种类型取新名叫person。
struct person{
    char name[10];
    int age;
};

1)在代码中定义一个结构体变量,变量名以类型的新名来命名。    struct person person        --> person就是结构体变量的变量名。
2)在这个定义的变量语句前面加typedef                      typedef struct person person  --> person就是struct person的新别名。
3)在后面的代码中定义结构体变量                             person ggy;                 --> 前面person就是数据类型  ggy就是结构体变量名


3、已知typedef取别名的代码,求出是谁替代谁?

例子1: 已知typedef int aaa;  请问是谁替代谁?

1)先将typedef去掉                            int aaa
2)剩余的语句中肯定有"数据类型 + 变量名"       数据类型:int   变量名: aaa
3)结果就是该变量名替代了数据类型              aaa 替代了 int

例子2: 已知 typedef void (*sighandler_t)(int)  请问是谁替代谁?

1)先将typedef去掉                            void (*sighandler_t)(int)
2)剩余的语句中肯定有"数据类型 + 变量名"       数据类型:void (*)(int)   变量名:sighandler_t
3)结果就是该变量名替代了数据类型              sighandler_t 替代了   void (*)(int)


-------------------------------
void func(int a)
void(*p)(int) = func;   嫌弃void(*)(int)太复杂,想给void(*)(int)取一个新的别名叫 sighandler_t

typedef void(*sighandler_t)(int)
以前: void (*p)(int)
现在: sighandler_t p
-------------------------------


补充关于结构体的笔记:
-------------------------------------------
struct person{
    char name[20];
    int age;
};

struct person   --> 结构体的数据类型
struct person a --> 定义一个结构体变量
struct person *p--> 定义一个结构体指针    

--------------------------------------------

struct person{
    char name[20];
    int age;
}a;   --> 定义一个结构体变量

--------------------------------------------

struct person{
    char name[20];
    int age;
};

typedef struct person person;    //将struct person这种数据类型取一个新的别名叫person

等价于

typedef struct person{
    char name[20];
    int age;
}person,*ptype;          ---> 给struct person取一个新的别名叫person
             ---> 给struct person*取一个新的别名叫ptype

二、自定义头文件。
1、自定义头文件一定要写的吗?
不一定。

2、什么时候需要写?
当声明的内容比较多的时候,就会将声明的内容写入到文件中。

3、可以写到自定义头文件的内容有哪些?
1)系统的头文件: #include <stdio.h>
2)结构体声明:
struct person{
    char name[10];
    int age;
};

3)函数声明: void func(int a,int b);
4)宏定义:#define OK 0

4、自定义头文件的格式是什么?
1)头文件文件格式:xxxx.h   (my_head.h)
2)为了防止重复声明,一般都会在头文件的开头写:
#ifndef _MY_HEAD_H_     //如果没有定义过_MY_HEAD_H_
#define _MY_HEAD_H_     //那么就定义_MY_HEAD_H_

3)将你需要声明的头文件、结构体、宏定义、函数声明全部放在中间。

#include <stdio.h>
struct person{
    char name[10];
    int age;
};

void func(int a,int b);
#define OK 0

4)在头文件结束时,必须写上这个:
#endif

5)只需要在.c文件中包含头文件即可。
#include "my_head.h"


------------------------------my_head.h-----------------------------
#ifndef _MY_HEAD_H_
#define _MY_HEAD_H_

#include <stdio.h>
#include <string.h>

struct person{
    char name[10];
    int age;
};

#define OK 0

void func(int x);

#endif

-----------------------------test.c-----------------------------
#include "my_head.h"

void func(int x)
{
    x = 10;
    printf("x = %d\n",x);
    return;
}

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    
    char A[10];
    strcpy(A,"hello");
    
    func(100);
    
    struct person a;
    strcpy(a.name,"ggy");
    
    return 0;
}

5、使用""与<>来包含头文件有什么区别?

双引号"" : 先去指定的路径下寻找该头文件,如果找不到,则会去系统中寻找该头文件,如果找不到,则提示头文件没有找到。
尖括号<> : 直接去系统中寻找头文件,如果找不到,则提示头文件没有找到。

1)使用尖括号来包含系统头文件。

#include <stdio.h>

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    return 0;
}

编译通过  --> 直接去系统中寻找stdio.h,结果找到了。

2)使用尖括号来包含自定义头文件。

#include <my_head.h>

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    return 0;
}

编译出错: fatal error: my_head.h: No such file or directory
    //因为尖括号会直接去系统中寻找,所以找不到。

如果一定要这样写,还有解决办法吗?
有,就是在编译时候使用"-I"选项。

gcc test.c -o test -I .  (在编译的时候,就会先去当前目录寻找,然后才在系统中寻找)


3)使用双引号来包含系统头文件。
#include "stdio.h"

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    return 0;
}

编译通过: 先去当前目录找(#include "stdio.h"等价于 #include "./stdio.h",./就是当前目录)
       结果找不到,就会继续去系统中寻找,结果找到了。  


4)使用双引号来包含自定义头文件。

#include "my_head.h"

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    return 0;
}

编译通过,先去当前目录寻找,结果找到了。


三、gcc编译器编译过程。
1、使用gcc编译器一步到位: gcc xxx.c -o xxx

           gcc
    xxx.c        ------->       xxx
  高级语言                    目标程序(二进制文件)

2、使用gcc编译器细分每一个编译步骤,可以分为4个步骤:

         预处理              编译              汇编            链接
   xxx.c    ------>   xxx.i    ----->   xxx.s    ----->  xxx.o   ----->  xxxx
  高级语言                                                             目标程序(二进制文件)

3、那么这些步骤是如何处理的?
查看gcc用法: man gcc

1)预处理。
-E  Stop after the preprocessing stage; do not run the compiler proper.
    //在预处理之后,就会停止了,不会进行编译的步骤。

编译命令: gcc xxx.c -o xxx.i -E

预处理之前: xxx.c    --> C语言程序
预处理之后: xxx.i    --> C语言程序(只是将xxx.c中的头文件展开,宏定义替换,条件编译)

2)编译
-S  Stop after the stage of compilation proper; do not assemble.
    //在编译的步骤之后就会停止了,不会进行汇编的操作

编译命令: gcc xxx.i -o xxx.s -S

编译之前: xxx.i     --> C语言程序
编译之后: xxx.s     --> 汇编程序   (检查代码语法是否正确)

3)汇编
-c  Compile or assemble the source files, but do not link.
    //在汇编之后就会停止,不会进行链接

编译命令: gcc xxx.s -o xxx.o -c

汇编之前: xxx.s    --> 汇编程序
汇编之后: xxx.o    --> 二进制文件

4)链接
没有选项。

编译命令: gcc xxx.o -o xxx

链接之前:    --> 二进制文件
链接之后:    --> 二进制文件


四、宏定义。   --> 你永远要记住,宏定义只是一个替换。
1、什么是宏定义?
其实宏定义与枚举类型非常相似,都是可以使得某一个常量变得有意义。
宏定义除了可以替换int类型的数据之外,还可以替换字符/字符串/浮点型数据。

2、宏定义在什么时候发生替换的?
预处理阶段发生替换,不是在运行阶段。

3、宏定义怎么写?
1)无参宏
#define OK 0     ---> OK等价于0
#define HELLO 'x'---> HELLO等价于'x'
#define KKK "hello"  --> KKK等价于"hello"

-------------------预处理之前------------------
#include <stdio.h>

#define KKK 'x'

int main(int argc,char *argv[])
{
    printf("KKK = %c\n",KKK);
    
    return 0;
}
------------------预处理之后-------------------

stdio.h这个头文件展开
int main(int argc,char *argv[])
{
        printf("KKK = %c\n",'x');   --> 很明显,预处理阶段已经实现替换
        return 0;
}

2)带参宏   --> 你使用该宏时候需要传递参数

#include <stdio.h>

#define KKK 'x'  //无参宏
#define XYZ(a,b) a*b  //带参宏

int main(int argc,char *argv[])
{
    printf("KKK = %c\n",KKK);
    
    int ret = XYZ(10,20);     
    printf("ret = %d\n",ret); //200
    
    int ret = XYZ(10+10,20);     
    printf("ret = %d\n",ret); //210
    
    int ret = XYZ((10+10),20);     
    printf("ret = %d\n",ret); //400
    return 0;
}

 
请问以下程序结果是什么?  C
#include <stdio.h>

#define N 2
#define M N+1
#define NUM (M+1)*M/2                       

int main(int argc,char *argv[])
{
    int i = 0;
    while(i < NUM)
    {
        printf("%d",i++);
    }
    printf("\n");
    return 0;
}

A. 123456   B.012345   C.01234567  D.01


五、条件编译。
1、什么是条件编译?
根据条件的真假来选择是否编译代码。

#if (1代表以下的代码参与编译,0代表以下的代码不参与编译)
    xxxx;
    yyyy;
#endif

#include <stdio.h>

int func(int a,int b)
{
    printf("helloworld!\n");
    return a + b;
}

int main()
{
    int r;
    r = func(10,20);

#if 1    
    printf("r = %d\n",r);
#endif
    
    return 0;
}

2、条件编译使用场景。
在调试代码时,某段代码用于输出一些值来观察情况下,一般都是使用到条件编译。

#if 1
    printf("xxxx");
    printf("yyyy");
    printf("zzzz");
#endif
      
3、关于预处理阶段的几个注意事项。
1)头文件展开/宏定义/条件编译,占用的是预处理时间,不会占用运行时间。
2)宏定义既可以使用大写字母,也可以使用小写字母。
3)由于头文件中有函数声明,所以头文件必须在函数调用之前必须包含。
4)如果在同一行代码中出现了多个预处理的语句,只会执行第一个预处理的语句。

六、拆分多个.c文件。
1、先写好源文件。
----------------------------project.c----------------------------

#include <stdio.h>

struct usr_data{
    int a;
    char b;
};

int u = 0;

#define OK 0

void fun1_A();
void fun1_B();
void fun2_A();

void fun1_A()
{
    printf("this is fun1_A !\n");
}

void fun1_B()
{
    printf("this is fun1_B !\n");
}

void fun2_A()
{
    printf("this is fun2_A !\n");
}

int main(int argc,char *argv[])
{
    //1. 先实现第一个功能
    fun1_A();
    fun1_B();
    
    //2. 再实现第二个功能
    fun2_A();
    
    printf("OK = %d\n",OK);
    
    struct usr_data k;
    k.a = 10;
    k.b = 'x';
    
    return 0;
}
-------------------------------------------------------------------

2、先写头文件,将系统头文件,宏定义,结构体声明,函数声明全部写入到头文件中。

---------------------------my_head.h-----------------------
#ifndef _MY_HEAD_H_
#define _MY_HEAD_H_

#include <stdio.h>

struct usr_data{
    int a;
    char b;
};

#define OK 0

void fun1_A();
void fun1_B();
void fun2_A();

#endif

----------------------------project.c----------------------------


int u = 0;

void fun1_A()
{
    printf("this is fun1_A !\n");
}

void fun1_B()
{
    printf("this is fun1_B !\n");
}

void fun2_A()
{
    printf("this is fun2_A !\n");
}

int main(int argc,char *argv[])
{
    //1. 先实现第一个功能
    fun1_A();
    fun1_B();
    
    //2. 再实现第二个功能
    fun2_A();
    
    printf("OK = %d\n",OK);
    
    struct usr_data k;
    k.a = 10;
    k.b = 'x';
    
    return 0;
}

3、按功能来拆分.c文件。
1)将功能1的全部函数实现过程,全部剪切到fun1.c中。
2)将功能2的全部函数实现过程,全部剪切到fun2.c中。

-------------------------my_head.h---------------------
#ifndef _MY_HEAD_H_
#define _MY_HEAD_H_

#include <stdio.h>

struct usr_data{
    int a;
    char b;
};

#define OK 0

void fun1_A();
void fun1_B();
void fun2_A();

#endif


-------------------------fun1.c-----------------------

void fun1_A()
{
    printf("this is fun1_A !\n");
}

void fun1_B()
{
    printf("this is fun1_B !\n");
}

-------------------------fun2.c-----------------------

void fun2_A()
{
    printf("this is fun2_A !\n");
}

------------------------project.c---------------------

int u = 0;

int main(int argc,char *argv[])
{
    //1. 先实现第一个功能
    fun1_A();
    fun1_B();
    
    //2. 再实现第二个功能
    fun2_A();
    
    printf("OK = %d\n",OK);
    
    struct usr_data k;
    k.a = 10;
    k.b = 'x';
    
    return 0;
}

4、现在,所有的.c文件,都要包含自己写的那个头文件

-------------------------my_head.h---------------------
#ifndef _MY_HEAD_H_
#define _MY_HEAD_H_

#include <stdio.h>

struct usr_data{
    int a;
    char b;
};

#define OK 0

void fun1_A();
void fun1_B();
void fun2_A();

#endif


-------------------------fun1.c-----------------------
#include "my_head.h"

void fun1_A()
{
    printf("this is fun1_A !\n");
}

void fun1_B()
{
    printf("this is fun1_B !\n");
}

-------------------------fun2.c-----------------------

#include "my_head.h"

void fun2_A()
{
    printf("this is fun2_A !\n");
}

------------------------project.c---------------------
#include "my_head.h"

int u = 0;

int main(int argc,char *argv[])
{
    //1. 先实现第一个功能
    fun1_A();
    fun1_B();
    
    //2. 再实现第二个功能
    fun2_A();
    
    printf("OK = %d\n",OK);
    
    struct usr_data k;
    k.a = 10;
    k.b = 'x';
    
    return 0;
}


5、在头文件中声明一个句话,声明这个全局变量是外部变量。

#ifndef _MY_HEAD_H_
#define _MY_HEAD_H_

#include <stdio.h>

struct usr_data{
    int a;
    char b;
};

#define OK 0

void fun1_A();
void fun1_B();
void fun2_A();

extern int u;   //写了这句话之后,所有的.c文件都可以使用这个全局变量。

#endif

6、编译工程。
gcc project.c fun1.c fun2.c -o project

7、运行。
   ./project


 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值