Linux基础|第十一篇(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)在这个定义的变量语句的前面加typedeftypedef int aaa---> aaa就是int这种类型的新名字
3)在后面的代码中定义整型变量aaa a; ---> 前面的aaa就是数据类型后面的a就是变量名

----------------------------------
#include
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)在这个定义的变量语句前面加typedeftypedef 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
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
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
#include
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
int main(int argc,char *argv[])
{
printf("helloworld!\n");
return 0;
}
编译通过--> 直接去系统中寻找stdio.h,结果找到了。
2)使用尖括号来包含自定义头文件。
#include
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)预处理。
-EStop 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)编译
-SStop after the stage of compilation proper; do not assemble.
//在编译的步骤之后就会停止了,不会进行汇编的操作
编译命令: gcc xxx.i -o xxx.s -S
编译之前: xxx.i--> C语言程序
编译之后: xxx.s--> 汇编程序(检查代码语法是否正确)
3)汇编
-cCompile 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
#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
#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
#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. 123456B.012345C.01234567D.01

五、条件编译。
1、什么是条件编译?
根据条件的真假来选择是否编译代码。
#if (1代表以下的代码参与编译,0代表以下的代码不参与编译)
xxxx;
yyyy;
#endif
#include
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
struct usr_data{
int a;
char b;
};
【Linux基础|第十一篇(C语言终篇)typedefy关键字,自定义头文件,GCC编译过程中文件转化,宏定义和条件编译详解。】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
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
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
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
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


    推荐阅读