到这篇文章开始,C语言迎来了最后结束。记住这是书的结束,而不是我们的学习C语言的结束!
但我们要知道C语言的内容不仅仅是这么一点点内容而已,还有很多更加高深,更加底层的知识等着我们去学习,去了解。
C语言的底层逻辑是怎么实现的,代码的执行逻辑又是怎么样的,函数栈帧的创建和销毁,还有编译器提供一系列的库函数等等……
革命尚未成功,同志仍需努力!
计算机程序是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息
工具。说白了互联网、智能移动设备、云计算、大数据的共同基础、共同的指挥官就是程序。
简而言之,程序=算法+数据结构。
在ANSI C(C语言标准)中,存在两个不同的环境,一个是翻译环境,另一个是执行环境。
翻译环境:把程序员写的文本代码转化成机器可识别的二进制指令。
执行环境:说白了就是代码执行所需要的环境,用于代码的执行。
就比如说:文本文件、源文件(test.c)——>通过翻译环境可以转化成可执行文件,也就是二进制文件(test.exe),然后通过执行环境进行执行。
1、test.c通过翻译环境和运行环境进行,来对文件进行可执行文件。
2、翻译环境会包括编译和链接。编译是指编译器(比如VS2019、devc++等),而链接器是链接目标文件和链接库生产的可执行程序(二进制程序)
3、编译又可以分为三个阶段:预编译、编译、汇编。
其大体的过程如下:
程序的执行过程:
预定义符号都是语言内置的。
//预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include<stdio.h> int main() { //预定义符号 //__FILE__ //进行编译的源文件 //__LINE__ //文件当前的行号 //__DATE__ //文件被编译的日期 //__TIME__ //文件被编译的时间 //__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 printf("%s\n", __FILE__); printf("%d\n", __LINE__); printf("%s\n", __DATE__); printf("%s\n", __TIME__); //printf("%d\n", __STDC__);不是所有的编译器都全部按照ANSI C进行编辑的 return 0; }
#include<stdio.h>
#define name stuff//语法
#define max 100
#define reg REG
#define do_forever for(;;)
//如果要替换的标识符太长了,可以换行写,但是必须在每行的末尾加上一个反斜杠(最后一行除外)
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
int main()
{
return 0;
}
注:
- 如果要替换的标识符太长了,可以换行写,但是必须在每行的末尾加上一个反斜杠(最后一行除外)。
- 不要在语句的末尾加上分号。
先存个疑,自己可以进行思考,在代替规则里,我会进行解释。
#include<stdio.h>
#define max 30;
int main()
{
//这么写是有语法错误的
int n=0;
if (max > 0)
n -= max;
else
n += max;
return 0;
}
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
其实就是将#define定义的宏直接往文本文件里面直接进行替换。
文本文件:
#include<stdio.h> #define Add(a,b) ((a)+(b)) int main() { int a = 6; int b = 5; int sum = Add(a, b); printf("%d",sum); return 0; }
被预编译后的文件
#include<stdio.h> #define Add(a,b) ((a)+(b)) int main() { int a = 6; int b = 5; int sum = a+b; printf("%d", sum); return 0; }
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
#define的使用规则比较简单,但是需要注意的是#define定义的标识符和宏是完全的遵循替换规则的。(替换规则就是将#define定义的标识符和宏给替换掉)
注意
1、#define定义宏和标识符不是计算好了之后再从文本中找到,然后进行替换。而是先进行替换,然后才计算。
2、宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
3、当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#define的代替规则。
给标识符max进行#define定义的时候,在30后面多加了一个分号,导致回代的时候会多一个分号。而if——else没有花括号的话,默认只有一条语句,而空语句也算一条语句,导致if——else的语法不符合。
下面代码的值是多少?
#include<stdio.h> #define c a+b int main() { int a = 3; int b = 5; int ret1 = b * c; int ret2 = c * b; printf("ret1 = %d\nret2 = %d", ret1, ret2); return 0; }
c先会被替换成a+b,然后才开始进行编译。
如何把宏的参数插入到字符串中?
#define PRINT(FORMAT, VALUE) printf("the value is "#FORMAT"\n", VALUE);//使用 # ,把一个宏参数变成对应的字符串
#include<stdio.h>
int main()
{
PRINT("%d", 10);
return 0;
}
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符注:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的
#include<stdio.h>
#define CAT(v,n) v##n
int main()
{
int v10 = 100;
printf("%d", CAT(v, 10));
return 0;
}
这里的副作用不是坏的意思,而是他除了运行这一串代码后,也可能会有什么其他的变化。
例如:
副作用就像下图一样,当a++,b++执行完之后,a和b的值发生了改变。这就叫做“副作用”
#include<stdio.h> #define MAX(a,b) ((a)>(b)?(a):(b)) int main() { int a = 100; int b = 20; int max = MAX(a++, b++); printf("%d", max); return 0; }
属 性 | #define定义宏 | 函数 |
代 码 长 度 | 每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地 次使用这个函数时,都调 地方的同一份代码 |
执 行 速 度 | 更快 | 存在函数的调用和返回的 销,所以相对慢一些 |
操 作 符 优 先 级 | 宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。 | 函数参数只在函数调用的 值一次,它的结果值传递 数。表达式的求值结果更 测。 |
带 有 副 作 用 的 参 数 | 参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候 次,结果更容易控制。 |
参 数 类 型 | 宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。 | 函数的参数是与类型有关 果参数的类型不同,就需 的函数,即使他们执行的 相同的。 |
调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递 归 | 宏是不能递归的 | 函数是可以递归的 |
一般来讲,宏的用法和函数类似,没办法区分。
我们一般全用大写来表示宏名。
不全部为大写来表示函数名。
功能
移除宏定义
#include<stdio.h> #define MAX(a,b) ((a)>(b)?(a):(b)) int main() { int a = 100; int b = 20; #undef MAX//意思就是将名为MAX的宏给移除 //int max = MAX(a++, b++);这条语句将会报错,MAX无法解析的外部符号 printf("%d", max); return 0; }
许多C的编译器都提供了一种能力,可以在命令行中定义符号,用于编译。
通过条件编译,编译程序的时候,我们将一条语句进行编译和移除很方便。
当#if后面的条件语句真值为1的时候,执行后面的语句,到#endif结束。
#if 1 printf("%d", max); #endif
#if 1==1 printf("haha"); #elif 2==1 printf("hh"); #endif
判断某个宏名是否被定义
//如果SUM宏存在,执行语句;否则不执行 //第一种写法 #if defined(SUM) //语句 #endif //第二种写法 #ifdef SUM //语句 #endif
//如果SUM宏不存在,执行语句;否则不执行 //第一种写法 #if !defined(SUM) //语句 #endif //第二种写法 #ifndef SUM //语句 #endif
#include的指令就是在文件里面包含其他文件。
这个不陌生吧,包含标准输入输出文件。
这个包含语句写法有两种:#include<>和#include""。
#include<>(标准库中包含) | #include""(本地文件包含) |
会在标准库中查找,如果没有则报错 | 首先编译器会先在本地文件中进行查找,如果本地文件没有,则会在标准库中查找。如果最终没找到,则报错 |
嵌套文件包含会造成文件内容的重复。
问题解决一:
在每个头文件写的时候加上这句话。
#ifndef __TEST_H__ #define __TEST_H__ //头文件的内容 #endif //__TEST_H__
问题解决二:
在头文件的最前面写上这句话。
#pragma once
#error
#pragma
#line
……
谢谢大家的支持!