【C语言进阶】程序编译&预处理详解_编译器预处理需要开辟空间吗

做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。

别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。


(1)Python所有方向的学习路线(新版)

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

最近我才对这些路线做了一下新的更新,知识体系更全面了。

在这里插入图片描述

(2)Python学习视频

包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

在这里插入图片描述

(3)100多个练手项目

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

在这里插入图片描述

(4)200多本电子书

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。

基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。

(5)Python知识点汇总

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。

在这里插入图片描述

(6)其他资料

还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

在这里插入图片描述

这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

在ANSIC的任何一种实现中,存在两个不同的环境:

  • 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。机器只认识0&1的二进制指令。
  • 第2种是执行环境,它用于实际执行代码。

图解1:

图解2:


①翻译环境

图解:

  • 组成一个程序的每个源文件通过编译过程分别转换成目标代码 (object code)
  • 每个目标文件由链接器**(linker)**捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库, 将其需要的函数也链接到程序中。

②运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须 由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同 时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

③C语言程序的编译名词+步骤详解

名词详解

源文件:.c为后缀的文件,如 test.c,game.c

头文件:.h为后缀的文件,如 game.h

目标文件:****.obj为后缀的文件,由源文件编译后生成,如 test.obj

**链接库:**库是写好的现有的,成熟的,可以复用的代码。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。

  • windows上对应的是.lib.dll
  • linux上对应的是.a.so

**静态库:**是因为在链接阶段,会将汇编生成的目标文件 .o 与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

链接库:

编译器:

链接器:

编译步骤详解

编译阶段分为以下三步骤:

预处理、编译、汇编

预处理阶段:

头文件的包含
#define定义的符号和宏的替换
注释的删除(所以我们要大胆写注释!不会影响程序的运行和性能!)
**注:**这些都是文本操作

编译阶段:

把c语言代码转换为汇编代码
语法分析、词法分析、语义分析、符号汇总

汇编阶段:

将汇编语言转换为机器语言
生成符号表

链接阶段:

把多个目标文件(.obj(windows) / .o(Linux))和链接库进行链接
合并段表
符号表的合并和重定位

二、预处理详解

  • C语言允许在源程序中加入一些“预处理指令”(preprocessing directive), 以改进程序设计环境,提高编程效率。
  • 这些预处理指令是由C标准建议的, 但它不是C语言本身的组成部分,不能用C编译系统直接对它们进行编译(因为编译程序不能识别它们)。
  • 必须在对程序进行正式编译(包括词法和语法分析、代码生成、优化等)之前,先对程序中这些特殊的指令进行“预处理”(preprocess, 也称“编译预处理”或“预编译”)。
  • 把预处理指令转换成相应的程序段,它们和程序中的其他部分组成真正的C语言程序, 对预处理指令进行的预处理工作,是由称为C预处理器(preprocessor)的程序负责处理的。
  • 在预处理阶段,预处理器把程序中的注释全部删除; 对预处理指令进行处理, 如把#include指令指定的头文件(如stdio.h)的内容复制到#include指令处; 对#define指令,进行指定的字符替换(如将程序中的符号常量用指定的字符串代替), 同时删去预处理指令。

①预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

代码演示:

#include <stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __FUNCTION__);
	//printf("%d\n", __STDC__);
	//因为 VS2019 不遵循 ANSI C,该符号未定义,所以进行了注释
	return 0;
}

执行结果:

写一个日志文件:

代码如下:

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	FILE* pf = fopen("log.txt", "w");
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
		fprintf(pf, "file:%s line:%d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	fclose(pf);
	pf = NULL;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

执行结果:

日志展示:


②预处理指令

  • #define定义标识符
语法:
#define name stuff
举例:
#define MAX 10
#define reg register
  • #define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

例1:

#include <stdio.h>
#define SQUARE(x) x*x//求x的平方
int main()
{
	int ret = SQUARE(5);
	//相当于int ret = 5*5;
	printf("%d\n", ret);//结果为25
	return 0;
}

执行结果:


例2:

#include <stdio.h>
#define SQUARE(x) x*x
int main()
{
    int ret = SQUARE(4+1);
    printf("%d\n", ret);
    return 0;
}

执行结果:


思考:为什么例1跟例2的结果会不一致呢?

  • 例2中,当传入的宏参数为4+1时,宏完成的是替换,它不会先把4+1的值算出来再进行替换,而是直接替换,所以传入4+1替换后相当于:int ret = 4+1*4+1 = 9
  • *的优先级高于+,所以这样算出的结果就为9了。

为了避免这种情况的发生,用宏实现求一个数的平方应该这样:

#define SQUARE(x) ((x)*(x))

这里将 (x)*(x) 整体再用括号括起来的原因也是一样的,都是为了避免在使用宏时,因操作符的优先级问题而导致不可预料的后果。

注:在使用#define定义宏时,不要吝啬括号,该加括号的地方就要加上

  • #define的替换规则

在程序中替换 #define 定义的宏和标识符时,会涉及几个步骤

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

例:

#include <stdio.h>
#define SQUARE(x) ((x)*(x)*100)
int main()
{
    int ret = SQUARE(3);
    printf("%d\n", ret);
    return 0;
}

等价于:

#include <stdio.h>
int main()
{
	int ret = ((3)*(3)*100);
	printf("%d\n", ret);
	return 0;
}

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

如:

#define FAC(x) (x)*FAC(x-1)//error
//不能出现如上所述的代码

#include <stdio.h>
#define MAX 100
int main()
{
	printf("MAX = %d\n", MAX);//结果为MAX = 100
	return 0;
}
//代码字符串中的MAX不会被替换为100,而字符串外的MAX会被替换
  • #undef

#undef可以移除一个#define定义的标识符或宏。

例:

#include <stdio.h>
#define MAX 100
int main()
{
	printf("%d\n", MAX);//正常使用
#undef MAX
	printf("%d\n", MAX);//报错,MAX未定义
}


③宏和函数的对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

#define MAX(a,b) ((a)>(b)?(a):(b))

那为什么不用下面这个函数来实现这个功能呢?

int Max(int a, int b)
{
	return a > b ? a : b;
}

原因:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。

而且,宏有时候可以做到函数做不到的事情。

例如,宏的参数可以出现类型,但是函数却不可以。我们使用malloc函数开辟内存空间时,可能会觉得代码太多。

例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int *p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		printf("p开辟失败\n");
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

此时我们就可以通过宏来使我们用malloc开辟空间时,只用传入开辟的类型和该类型的元素个数即可。

例:

#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	int* p2 = MALLOC(10, int);
	if (p2 == NULL)
	{
		printf("p2开辟失败\n");
		return 1;
	}
	free(p2);
	p2 = NULL;
	return 0;
}

当然和宏相比函数也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

对比图

命定约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:把宏名全部大写 函数名不要全部大写


④#和##的介绍

#的作用

这里所说的#并不是#define和#include中的#,这里所说的#的作用是:把一个宏参数变成对应的字符串

我们首先需要知道:字符串是有自动连接的特点的

例:

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值