深圳宝安网站设计,深圳宝安股票,ui设计网站成品图片,小程序商店开发文章目录一.程序翻译环境和执行环境1.ANSI C 标准2.程序的翻译环境和执行环境二.程序编译和链接1.翻译环境2.编译本身的几个阶段3.运行环境三.预处理1.预定义符号2.#define#xff08;1#xff09;#define定义标识符#xff08;2#xff09;#define定义宏#xff08;31#define定义标识符2#define定义宏3#define替换规则3.#和##1#2##4.#undef5.带副作用的宏参数6.宏和函数对比7.命名约定四.命令行编译五.条件编译1.条件编译常量表达式2.多分支的条件编译3.条件编译是否被定义4.条件编译的嵌套六.文件包含1.头文件被包含的方式2.嵌套文件的包含一.程序翻译环境和执行环境
1.ANSI C 标准
ANSI C是由美国国家标准协会ANSI及国际化标准组织ISO推出的关于C语言的标准。ANSI C 主要标准化了现存的实现 同时增加了一些来自 C 的内容 主要是函数原型 并支持多国字符集 包括备受争议的三字符序列。 ANSI C 几乎被所有广泛使用的编译器所支持且多数C代码是在ANSI C基础上写的。
2.程序的翻译环境和执行环境
ANSI C 的任何一种实现中存在两种不同的环境:
翻译环境在该环境中源代码被转换为可执行的机器指令。执行环境用于实际执行代码。
二.程序编译和链接
1.翻译环境 组成一个程序的每个源文件(.c)通过编译过程分别转换成目标代码(.obj)每个目标文件由链接器捆绑在一起形成一个单一而完整的可执行程序。链接器同时也会引入标准C库函数中任何被该程序所用到的函数且可以搜索程序员个人的程序库将其需要的函数也链接到程序中。
举个例子test.c、add.c、minu.c
2.编译本身的几个阶段
举个例子 ① sum.c
int global_val 2021;
void print(const char* string) {printf(%s\n, string);
}② test.c
#include stdio.hint main(void) {extern void print(char* string);extern int global_val;printf(%d\n, global_val);printf(Hello,World!\n);return 0;
}编译阶段为 解析图如下
3.运行环境
程序执行过程
程序必须载入内存中。在有操作系统的环境中程序的载入一般由操作系统完成。在独立环境中程序的载入必须手工安排也可能是通过可执行代码置入只读内存来完成。程序的执行便开始。接着便调用 main 函数。开始执行程序代码这个时候程序将使用一个运行时堆栈stack内存函数的局部变量和返回地址。程序同时也可以使用静态staic内存存储与静态内存中的变量在整个执行过程中一直保留他们的值。终止程序。正常终止 main 函数也有可能是意外终止。
三.预处理
1.预定义符号
1.__FILE__ //进行变异的源文件
2.__LINE__ //文件当前的行号
3.__DATE__ //文件被编译的日期
4.__TIME__ //文件被编译的时间
5.__STDC__ //如果编译器遵循ANSI C其值为1否则未定义
6.__FUNCTION__ //返回所在函数的函数名在预处理阶段被处理的已经定义好的符号为预定义符号。这些符号是可以直接使用的是在C语言中已经内置好的。
注意值得注意的是__ 为两个下划线 用法演示
#include stdio.hint main(void) {printf(%s\n, __FILE__); // 返回使用行代码所在的源文件名包括路径printf(%d\n, __LINE__); // 返回行号printf(%s\n, __DATE__); // 返回程序被编译的日期printf(%s\n, __TIME__); // 返回程序被编译的时间printf(%s\n, __FUNCTION__); // 返回所在函数的函数名return 0;
}运行结果
那么这些预定义符号有什么用
如果一个工程特别复杂这时去调试时可能会无从下手。所以需要代码在运行的过程中记录一些日志信息通过日志信息分析程序哪里出了问题再进行排查就如同瓮中捉鳖。
2.#define
1#define定义标识符
#define NAME stuff用法演示
#include stdio.h#define TIMES 100int main(void) {int t TIMES;printf(%d\n, t);return 0;
}运行结果100 在预处理阶段会把 TIMES 替换为 100。预处理结束后 int t TIMES 就没有TIMES 了会变为 int t 100。
// 预处理前
int t TIMES;
// 预处理后
int t 100;当然了 #define 定义的符号可不仅仅只有数字还可以用来做很多事比如
1.#define REG register //给关键字register创建一个简短的名字
2.#define DEAD_LOOP for(;;) //用更形象的符号来替换一种实现① #define REG register给关键字 register创建一个简短的名字
#define REG registerint main(void) {register int num 0;REG int num 0; // 这里REG就等于registerreturn 0;
}② #define DEAD_LOOP for(;;用更形象的符号来替换一种实现
#define DEAD_LOOP for(;;)int main(void) {DEAD_LOOP // 预处理后替换为 for(;;); ; // 循环体循环的是一条空语句DEAD_LOOP; // 那么可以这么写这个分号就是循环体循环的是一个空语句return 0;
}③ #define CASE break;case 在写case语句的时候自动字上break很巧妙的偷懒
#define CASE break;case // 在写case语句的时候自动字上breakint main(void) {int n 0;//switch (n) {// case 1:// break;// case 2:// break;// case 3:// break;//}switch (n) {case 1: // 第一个case不能替换CASE 2: // 相当于 break; case 2:CASE 3: // 相当于 break; case 3:}return 0;
}有个细节再前面 #define 定义标识符时为什么末尾没有加上分号呢
#define TIMES 100;
#define TIMES 100这是因为分号也会被当作替换内容替换到文本当中可能会导致出现错误
#define _CRT_SECURE_NO_WARNINGS 1#include stdio.h#define TIMES 100;int main(void) {int a, b;if (a 10)b TIMES; // b 100;;else //else没有匹配对象b -TIMES; // b 100;;return 0;
}所以在 #define 定义标识符时尽量不要在末尾加分号必须加的情况除外
2#define定义宏
#define NAME(parament-list) stuff#define 机制允许把参数替换到文本中这种实现通常被称为宏macro或 定义宏define macroparament-list 是一个由逗号隔开的符号表他们可能出现在 stuff 中。
注意
参数列表的左括号必须与 name 紧邻。如果两者之间由任何空白存在参数列表就会将其解释为 stuff 的一部分。
用法演示3*39
#include stdio.h#define SQUARE(X) X*Xint main(void) {printf(%d\n, SQUARE(3)); // printf(%d\n, 3 * 3);return 0;
}那么(31) 的结果是什么
#include stdio.h#define SQUARE(X) X*Xint main(void) {printf(%d\n, SQUARE(31));return 0;
}运行结果7 这是因为替换是在预处理阶段时替换表达式真正计算出结果是在运行时计算。所以先替换 31*317 如果想获得 31 相乘也就是得到 4×4 16 的结果我们需要给他们添加括号
#include stdio.h// 整体再括一个括号严谨
#define SQUARE(X) ((X)*(X))int main(void) {printf(%d\n, SQUARE(31));return 0;
}另外整体再套一个括号让代码更加严谨防止产生不必要的错误。比如我希望得到 10* DOUBLE可能会得到以下情况
#include stdio.h#define DOUBLE(X) (X)(X)int main(void) {printf(%d\n, 10 * DOUBLE(31));// printf(%d\n, 10 * (4) (4)); // 我们本意是想得到80但是结果为44因为整体没带括号return 0;
}*所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号可以有效避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料地相互作用。*不要吝啬括号
3#define替换规则
在程序中扩展 #define 定义符号或宏时需要涉及的步骤如下
检查在调用宏时首先对参数进行检查看看是否包含任何由 #define 定义的符号。如果包含它们首先被替换。替换替换文本随后被插入到程序中原来的文本位置。对于宏函数名被它们的值替换。再次扫描最后再次对结果文件进行扫描看看是否包含任何由 #define 定义的符号。如果包含就重复上述处理过程。
注意事项
宏参数 和 #define 定义中可以出现 #define 定义的变量。但是对于宏绝对不能出现递归当预处理器搜索 #define 定义的符号的时候字符串常量的内容并不被搜索。
3.#和##
我们知道宏是把参数替换到文本中。那么如何把参数插入到字符串中呢 比如这种情况使用函数是根本做不到的
void print(int x) {printf(变量的值是%d\n, ) 函数根本做不到
}int main(void) {int a 10;// 打印内容变量a的值是10print(a);int b 20;// 打印内容变量b的值是20print(b);int c 30;// 打印内容变量c的值是30print(c);return 0;
}这种情况就可以用 宏 来实现。
1#
# //把一个宏参数变成对应的字符串#把一个宏参数变成对应的字符串。
使用 # 解决上面的问题
#include stdio.h
#define PRINT(X) printf(变量#X的值是%d\n, X);
// #X 就会变成 X内容所定义的字符串int main(void) {// 打印内容变量a的值是10int a 10;PRINT(a); // printf(变量a的值是%d\n, a);// 打印内容变量b的值是20int b 20;PRINT(b); // printf(变量b的值是%d\n, b);// 打印内容变量c的值是30int c 30;PRINT(c); // printf(变量c的值是%d\n, c);return 0;
}运行结果
改进让程序不仅仅支持打印整数还可以打印其他类型的数比如浮点数
#include stdio.h
#define PRINT(X, FORMAT) printf(变量#X的值是 FORMAT\n, X);int main(void) {// 打印内容变量a的值是10int a 10;PRINT(a, %d);// 打印内容变量f的值是5.5float f 5.5f;PRINT(f, %.1f); //printf(变量f的值是 %.1f\n, f);return 0;
}运行结果
2##
## //把位于它两边的符号合并成一个符号##可以把位于它两边的符号融合成一个符号。它允许宏定义从分离的文本片段创建标识符。
用法演示
#include stdio.h#define CAT(X,Y) X##Yint main(void) {int vs2003 100;printf(%d\n, CAT(vs, 2003)); // printf(%d\n, vs2003);return 0;
}运行结果 ##也可以将多个符号合成一个符号比如 X##Y##Z
4.#undef
#undef NAME //移除一个宏定义用于移除一个宏定义。
用法演示用完 M 之后移除该定义
#include stdio.h#define M 100int main(void) {int a M;printf(%d\n, M);
#undef M // 移除宏定义return 0;
}5.带副作用的宏参数
什么是副作用 副作用就是表达式求值的时候出现的永久性效果例如
//不带有副作用
x 1;
//带有副作用
x; int a 1;
//不带有副作用
int b a 1; //b2, a1
//带有副作用
int b a; //b2, a2当宏参数在宏的定义中出现超过一次的情况下如果参数带有副作用那么在使用这个宏的时候就可能出现危险导致不可预料的后果。这种带有副作用的宏参数如果传到宏体内这种副作用会一直延续到宏体内。
举个例子
#include stdio.h#define MAX(X,Y) ((X)(Y)?(X):(Y))int main(void) {int a 5;int b 8;int m MAX(a, b);printf(m %d\n, m);printf(a%d, b%d\n, a, b);return 0;
}运行结果 所以写宏的时候尽量避免使用这种带副作用的参数。
6.宏和函数对比
举个例子在两数中找较大值 ① 用宏
#include stdio.h#define MAX(X,Y) ((X)(Y)?(X):(Y))int main(void) {int a 10;int b 20;int m MAX(a, b); // int m ((a)(b) ? (a):(b))printf(%d\n, m);return 0;
}② 用函数
#include stdio.hint Max(int x, int y) {return x y ? x : y;
}int main(void) {int a 10;int b 20;int m Max(a, b);printf(%d\n, m);return 0;
}那么宏和函数那种更好呢
答案是宏
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多所以宏比函数在程序的规模和速度方面更胜一筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之宏可以适用于整型、长整型、浮点型等可以用于比较的类型。因为宏是类型无关的。
当然宏也有劣势的地方
每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。宏不能调试。宏由于类型无关因为没有类型检查所以不够严谨。宏可能会带来运算符优先级的问题导致程容易出现错。
宏有时候可以做函数做不到的事情。比如宏的参数可以出现类型但是函数做不到
#include stdio.h
#include stdlib.h#define MALLOC(num, type) (type*)malloc(num*sizeof(type))int main(void) {// 原本的写法malloc(10*sizeof(int));// 但我想这么写malloc(10, int);int* p MALLOC(10, int); // (int*)malloc(10*sizeof(int))...return 0;
}所以如果一个运算的逻辑足够简单建议使用宏。反之如果一个运算的逻辑足够复杂建议使用函数。
7.命名约定
命名约定一般来讲函数的宏的使用语法很相似所以语言本身没法帮我们区分二者。约定俗成的一个习惯是 宏名全部大写函数名不要全部大写。
四.命令行编译
什么是命令行编译
在编译的时候通过命令行的方式对其进行相关的定义叫做命令行编译。
许多C的编译器提供的一种能力允许在命令行中定义符号。用于启动编译过程。当我们根据同一个源文件要编译出不同的一个程序的不同版本的时可以用到这种特性增加灵活性。
比如假如某个程序中声明了一个某个长度的数组假如机器甲内存有限我们需要一个很小的数据但是机器丙的内存较大我们需要一个大点的数组。
#include stdio.hint main() {int arr[ARR_SIZE];int i 0;for (i 0; i ARR_SIZE; i) {arr[i] i;}for (i 0; i ARR_SIZE; i) {printf(%d , arr[i]);}printf(\n);return 0;
}gcc 环境下测试VS 里面不太好演示 gcc -D ARRAY_SIZE10 programe.c 五.条件编译
在编译一个程序时通过条件编译指令将一条语句一组语句编译或者放弃是很方便的。
调试用的代码删除了可惜保留了又碍事。我们就可以使用条件编译来选择性地编译
#include stdio.h#define __DEBUG__ // 就像一个开关一样int main(void)
{int arr[10] {0};int i 0;for (i 0; i 10; i) {arr[i] i;#ifdef __DEBUG__ // 因为__DEBUG__被定义了所以为真printf(%d , arr[i]); // 就打印数组 #endif // 包尾}return 0;
}运行结果1 2 3 4 5 6 7 8 9 10
如果不想用了就把 #define DEBUG 注释掉
#include stdio.h// #define __DEBUG__ // 关int main(void)
{int arr[10] {0};int i 0;for (i 0; i 10; i) {arr[i] i;#ifdef __DEBUG__ // 此时ifdef为假printf(%d , arr[i]); #endif}return 0;
}1.条件编译常量表达式
#if 常量表达式……
#endif如果常量表达式为真参加编译。反之如果为假则不参加编译。
用法演示常量表达式为真
#include stdio.hint main(void) {
#if 1printf(Hello,World!\n);
#endifreturn 0;
}2.多分支的条件编译
#if 常量表达式……
#else if 常量表达式……
#else……
#endif多分支的条件编译直到常量表达式为真时才执行。
用法演示
#include stdio.hint main(void) {
#if 1 2 // 假printf(rose\n);
#elif 2 2 // 真printf(you jump\n);
#else printf(i jump\n)
#endifreturn 0;
}运行结果you jump
3.条件编译是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbolifdef 和 if defined() ifndef 和 if !defined() 效果是一样的用来判断是否被定义。
用法演示
#include stdio.h#define TEST 0
// #define TEST2 // 不定义int main(void) {
/* 如果TEST定义了下面参与编译 */
// 1
#ifdef TESTprintf(1\n);
#endif// 2
#if defined(TEST)printf(2\n);
#endif/* 如果TEST2不定义下面参与编译 */
// 1
#ifndef TEST2printf(3\n);
#endif// 2
#if !defined(TEST2)printf(4\n);
#endifreturn 0;
}运行结果
4.条件编译的嵌套
和 if 语句一样是可以嵌套的
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif六.文件包含
我们已经知道#include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。替换方式为预处理器先删除这条指令并用包含文件的内容替换。这样一个源文件被包含10次那就实际被编译10次。
1.头文件被包含的方式
#include filename
#include filename.h和 包含头文件的本质区别查找的策略的区别 的查找策略先在源文件所在的工程目录下查找。如果该头文件未找到则在库函数的头文件目录下查找。如果仍然找不到就提示编译错误 的查找策略直接去标准路径下去查找。如果仍然找不到就提示编译错误
既然如此那么对于库文件是否也可以使用 包含 答案是可以的。但是这样做查找的效率就低些当然这样也不容易区分是库文件还是本地文件了。为了效率不建议这么做。
2.嵌套文件的包含
头文件被重复包含的情况
comm.h 和 comm.c 是公共模块。test1.h 和 test1.c 使用了公共模块。test2.h 和 test2.c 使用了公共模块。test.h 和 test.c 使用了 test1 模块和 test2 模块。
这样最终程序中就会出现多份 comm.h 的内容会造成文件内容的重复。 那么如何避免头文件的重复引入呢 使用条件编译指令每个头文件的开头写
#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif还有一种非常简单的方法
#pragma once // 让头文件即使被包含多次也只编译一份————————————————————————————————— 本篇到此结束码文不易还请多多支持