C程序都有哪些组成部分呢 C程序是由一个或者多个扩展名为.c的源代码文件和若干个扩展名为.h的头文件组成。在实际项目中我们又是如何安排各个源文件呢。本文将逐一展开介绍一.C源代码文件的组成1、预处理指令以#开头的行预处理命令机制只在“单个编译单元”内生效不同.c文件预处理命令的“记忆”是独立的。1#include作用就是把include后面的文件的内容再此处展开文件中的内容原样复制到此处头文件的写法有两种,都是头文件的包含命令都是为了找到相应的文件#includexxx.h//表示在系统的标准头文件路径下面去搜索这个头文件#includexxx.h//表示先在当前工程路径下去搜索没有搜索到再去标准路径下搜索2#define将程序中所有的宏名替换为要替换的内容3#pragma#pragma指令有各种各样的用途#pragmaonce确保头文件只被包含一次防止多重包含#pragmawarning用于控制编译器的警告信息#pragmapack用于控制内存对齐方式4条件编译条件编译如果条件成立则编译如果条件不成立则不编译#endif:条件编译结束符#ifndef宏标识符一般于define一起出现用来防止头文件重复编译#ifndef宏标识符#define宏标识符内容1#endif上面伪代码的含义是如果宏标识符没有被定义则定义宏标识符并且编译“内容1”如果宏标识符被定义了编译则跳过#endif#ifdef宏标识符#includestdio.h#defineDEBUG_MODEvoidtest(){#ifdefDEBUG_MODEprintf(调试时生效\n);#endifprintf(后续为业务代码\n);};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);test();return0;}用来判断宏是否被定义如果定义了DEBUG_MODE就编译printf“调试时生效”直到遇到endif结束如果没有定义编译跳过#endif即本代码中就不会打印输出调试生效。可用于开发时打印调试信息发布时一键关闭。运行后结果#if #elif #else符合则编译#if表达式1或宏标识符内容1#elif表达式2或宏标识符内容2#else内容3#endifif表达式1成立执行“内容1”如果表达式2成立则执行“内容2”否则执行“内容3”直到遇到endif类似if语句只不过是变为编译代码#includestdio.h#defineVERSION2//版本号1社区版2专业版intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);#ifVERSION1printf(社区版功能\n);#elifVERSION2printf(专业版功能\n);#elseprintf(其他版本功能\n);#endifreturn0;}上面代码实现了一套代码适配不同版本、不同平台或不同硬件2、全局声明1对象的定义分配存储空间可能同时提供初始值变量、数组、指针、函数都是对象说明函数本身是可以在任何地方使用的作用域是全局。2对象的声明声明就是告诉编译器存在某个对象类型、名字但不分配存储空间保证编译能通过。详细解析可参见https://blog.csdn.net/qq_44801116/article/details/163165159变量、数组、指针的声明externinta;externcharb[5];externdouble*p;函数的声明voidxxx(inta);//本文件内的声明externintyyy(inta);//组合类型(struct\union)的声明structs{inta;intb;}二、.h头文件的组成头文件或叫声明文件命名一般是xxx.h(.h为后缀)类似循环结构一样可以减轻程序的编程负担。用来声明代码的接口让多个.c源文件可以共享函数、变量、宏等定义核心作用是分离声明与实现避免代码重复说明后缀改成其他也可以不影响但是不建议那样搞。1、头文件的格式标准头文件有固定结构最核心的是头文件保护宏防止重复包含报错一般只包含各类对象的声明和条件编译格式如下声明放.h实现放.c分离接口和代码//1、头文件保护宏#ifndef宏标识符//如果未定义该宏#define宏标识符//定义宏//头文件核心内容声明区/* 1、宏定义#define 2、类型声明结构体、枚举、共用体、typedef 3、函数声明只写函数原型不写函数体 4、全局变量声明必须加extern 5、其他头文件包含#include 6、内联函数static inline可写实现 *///这里只写声明不写具体实现// 结束保护宏#endif2、示例tools.h用来声明代码的接口tools.c来具体实现16_test_c.c源文件可以共享函数、变量、宏等定义tools.h//头文件保护宏防止重复包含#ifndefTOOL_H#defineTOOL_H//1、包含其他需要的头文件#includestdio.h//2、定义声明区#defineMAX_NUM100//常量宏#defineADD(a,b)(ab)//函数宏externintglobal_num;typedefenum{SUCCESS0,ERROR1}Status;typedefstruct{intid;charname[20];}Student;//函数声明intget_max(inta,intb);//只写原型不写函数体voidprint_student(Student stu);staticinlinevoidhello(){printf(Hello from header\n);}//内联函数可以写在头文件#endiftools.c#includetools.hintglobal_num0;//函数实现需要写函数体intget_max(inta,intb){returnab?a:b;}voidprint_student(Student stu){printf(ID:%d,姓名%s\n,stu.id,stu.name);}16_test_c.c#includetools.hintmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);printf(项目中定义的最大值是%d\n,MAX_NUM);Student stu{47,张老三};print_student(stu);intmax_valget_max(10,20);printf(最大值是%d\n,max_val);hello();return0;}运行结果3、头文件的核心规则1必须加头文件保护宏#ifndef、#define、#endif否则重复包含会编译错误2函数、全局变量只在头文件中声明在.c文件实现3全局变量必须加extern否则会导致重复定义4不要在头文件中写普通函数的实现否则会报多重定义错误说明.h和.c文件名不强制必须同名两个文件的绑定关系是通过#include。但是为了可读性、方便维护建议还是一一对应。声明是可以重复出现的定义不可以定义作用域一样同一个作用域内不能定义两个一样的名字eg头文件中有static int a;在多个.c文件中都include .h文件对于a的定义是允许的因为a的作用域被限制在当前.c文件中。三、模块化设计把一个大项目进行任务细分分成多个模块.c文件来进行开发1、文件的编译过程阶段主要工作预处理1、把#号开头的行进行处理2、gcc -E demo.c -o demo.i;3、预处理完成之后demo.i文件还是C代码文件编译1、把C语言文件编译成汇编文件2、gcc -S demo.i -o demo.s;3、汇编之后demo.s文件就是汇编文件汇编1、把汇编文件编译成机器码2、gcc -C demo.s -o demo.o;3、汇编完成后demo.o就是机器码文件了链接1、把所有的.o文件链接到一起2、gcc demo1.c demo2.c… -o demo;3、最后demo这个文件就是可执行文件**说明**用到的所有c文件都要进行编译。2、代码模块化的好处1代码可读性高一个模块只负责一个功能.h文件就是模块的说明书只看头文件就直到这个模块提供哪些函数、数据类型不用区看复杂的实现代码别人阅读代码不用通读全部代码只看对应模块即可。2便于维护和修改代码修改模块内部代码只要对外接口.h里函数原型不变其他调用该模块的代码完全不用动bug定位方便出问题就锁定对应模块不用再整个大文件里找错误3代码复用写好的模块可以直接拿到别的项目复用4适合多人团队协作开发不同开发人员负责不同模块大家只需要约定好头文件的接口互不干扰并行开发不会相互修改对方的实现代码5减少编译时间修改某个模块只需要重新编译这个模块对应的.c文件不需要编译全部代码大项目中效果非常明显Makefile、CMake就是利用模块化实现增量编译6信息隐藏封装思想模块把内部细节藏在.c里面只把必要接口暴露在.h//内部函数用static修饰只能本.c内部使用外部无法调用。外部使用者只能使用头文件公开的函数不能随意访问模块内部细节降低错误风险7降低全局命名污染模块内部函数、变量用static限定作用域不会和别的文件里同名符号冲突8方便单元测试模块独立可以单独对这个模块写测试代码单独测试模块的函数功能不用跑完整的主程序。