C语言-程序环境与预处理

简介: 程序怎么诞生与运行

C语言-程序环境与预处理

程序怎么诞生与运行

基本预处理符号与指令

如何书写宏

例子一:offsetof 偏移量(结构体类型,成员变量)

例子二:二进制奇偶位互换(int)

语句型

返值型

getc()与getchar()有些编译器上是宏定义的,实际上就是接受了stdin(可以理解为键盘输入的文件指针)

宏与函数的区别与优劣

一些不常见的指令

Ⅰ.程序怎么诞生与运行

预编译(预处理)(显然,这个过程快于一切,所以后面代码中提到宏就会被替换,而不能作为变量名)

在这个过程中,编译器会将源文件的文本进行“更改”

将注释忽略,头文件内容的搬运,宏对源文件某位置内容的替换,等一系列预处理指令

这个过程是完全替代的过程,是本次博客的重点,等一下会重点讲

遇到“嵌套的情况”,编译器只要检测有文本中仍然

编译

语法错误,编译错误

在这个过程中,编译器会将你写的代码编译会汇编代码

语法分析

词法分析

语义分析

符号汇总

汇编

一般汇编不会出错

在这个过程,编译器会将汇编代码,汇编为一系列东西,如

形成符号表

汇编指令,二进制指令,形成(.o文件)(二进制文件)

链接

在这个过程,链接器会将(.o文件)结合“链接库”合并成段表,完成符号表的合并和符号表的重定位

一般是隔离编译,一起链接的(多个文件编译完,一切链接)

这就好说明为什么没有定义函数,但是调用了它,显示的是链接错误

运行

运行错误,与目标实际情况不符,说明思路有问

可以在Linux环境下去查看这个过程,通过指令


Ⅱ.基本预处理符号与指令&基本预处理符号与指令

2.1 预定义符号 (宏是替换的,可以作为数组元素个数,因为有些编译器不允许变长数组1)

这些都是系统内置的,直接可以用!

FILE //进行编译的源文件

LINE //文件当前的行号

DATE //文件被编译的日期

TIME //文件被编译的时间

STDC //如果编译器遵循ANSI C,其值为1,否则未定义

printf("file:%s line:%d\n", __FILE__, __LINE__);

2.2 #define

#define 可以定义符号,也可以定义宏,但是我理解为都是宏,因为都是替换的过程,只不过符号的话,相当于没“形参”(如果宏那边形参没用到,那就跟替换没区别),省略括号

2.2.1 书写宏

#define MAX 1000
#define reg register      //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)   //用更形象的符号来替换一种实现
#define CASE break;case     //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符!!!)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
             date:%s\ttime:%s\n" ,\
             __FILE__,__LINE__ ,    \
             __DATE__,__TIME__ )
//!!!!!宏是替换的,带分号会导致语法错误,结果错误!!!!!


宏可以通过续行符,完成一块东西的替换

记住替换!所以是原封不动的替换

所以就有一个坑

#define max 5 + 6
int main()
{
    printf("%d ",max * 6);
    return 0;
}


一定要注意这里,替换过来是 5 + 6 * 6,所以结果并不是 66,而是 41,应该是 #define max (5 + 6)


#define pow(x) x * x
int main()
{
    printf("%d ", max(5 + 6));
    return 0;
}


同样的,预处理不会对非预处理的地方进行运算,所以这里是把 5 + 6 这个整体传了过去,也就是-> 5 + 6 * 5 + 6 ,结果不是 121,而是 41,应该是#define pow(x)((x) * (x))


2.2.2 #define 的工作就是

有参考数的时候,将参数整体传到“形参”里,然后将定义的宏里对应位置替换了,然后再替换到原来的位置,最后这句指令在可能多次预处理后,删除

没参数的时候,后面的内容完美无缺地替换过去

2.2.3 例子

offsetof模拟

C/C++库函数查询网址:网址


offsetof (type,member)


返回值 type member

无 结构体类型名 成员变量名

可能你发现了,这里宏的好处体现了,没有类型,但是这里得是结构体,虽然你要怎么定义宏无所谓,但是替换过去能运行才行  

#define OFFSETOF(TYPE,MEMBER) (int)&(((TYPE*)0)->MEMBER) // -0 省略了


你可能有一个顾虑,就是这里用了0->C语言的NULL,受保护的0地址空指针(java中null为不指向任何),但是这里只是用了这个地址的性质找到成员变量的地址罢了,并没有对值进行修改,也没有把地址引用的东西怎么怎么滴,所以没关系


我的代码仓库,码云


奇数偶数位互换

就是把一个int类型的变量的值,奇数的二进制位与偶数的二进制位互换

类函数型:

#define CHANGE(N)\
int M = 0; \
int i = 0; \
for (i = 0; i < 32; i++)\
{\
if (i % 2 == 0)\
M |= (((N) >> 1) & (1 << i)); \
else \
M |= (((N) << 1) & (1 << i)); \
}\
n=M;


返回值型

#define C(N) ((0b01010101010101010101010101010101&(N))<<1)\
 +((0b10101010101010101010101010101010&(N))>>1)
//不会影响原来变量的值



Ⅲ.函数与宏的对比

属性 宏 函数

代码长度 每次出现宏都会替换可能会大大增加代码长度 调用函数就可以了,长度就那么多

执行速度 快,“就是走下去一路没有阻碍,不用开辟空间然后各种操作” 需要给函数开辟空间,调用完需要销毁,这样就要花时间

操作符优先级 没进行运算,是替换,所以容易出错 运算结果直接传参

带有副作用的表达式参数 可能会一错再错,因为可能如果替换很多次,这个副作用就更大了!(副作用是指,这个表达式会影响变量的值,例如,i++ , i = 1) 只会副作用一次,就是把运算结果传过去就好

参数类型 无顾虑 C中没有java的重载,C特别要顾虑类型,不然就会导致结果不对,甚至报错

调试 替换了之后,按 F11 不能查看情况 按 F11 可查看函数情况,容易找到错误的源头

递归 无递归 有递归,一些必须递归写的(如,汉诺塔)只有函数能写

Ⅳ.其他的预处理指令

#undef 将 #define 定义的内容取消掉,具体操作就是将这一指令以后的的宏

“ # 与 ## ”

"#"就是将宏里面的符 # 连接的参数(只有宏形参可以)转化为字符串

"##"就是连接两个左右符号,然后当作新符号

这里可以当作是预处理之前的预处理,在宏把形参替换后,他们就开始改变宏定义的文本了

这里有一个小技巧,就是两个字符串之间会直接连接,例如,“abc”“cde”-> “abccde” 两个相邻双引号融合

int i = 10;

#define PRINT(FORMAT, VALUE)\

printf("the value of " #VALUE "is "FORMAT "\n", VALUE);

...

PRINT("%d", i+3);//产生了什么效果?


通过这个宏,我们实现了打印变量名的要求!


#define ADD_TO_SUM(num, value) \
sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.
//当然如果你这里的5如果是变量,它并不会把值代入进去,
//例如 x ,它会说 numx += 10


♥条件编译

就是通过一些处理,让此次运行与调试不经过一些代码段

1.

#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

2.多个分支的条件编译

#if 常量表达式(例如 1 > 2)
//...
#elif 常量表达式
//...
#else
//...
#endif

3.判断是否被定义

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif



如 #if 后为真,则保留,假则删除

必须有#endif 否则不知道删除与保留多少

#if defined 与#if !defined + 宏,(这个是在代码运行前,所以不应该写任何变量)意思是如果被定义就保留

等价于#ifdef 与#ifndef

#elif,支持分支,有elif defined 没有elifdef,因为defined + 宏 相当于一个预处理的真假判断式!

♥头文件包含,#include,

当然include很常见,但是放在这里讲更方便,因为include,可以包含宏定义

原理

用 < > 会直接到库里面找头文件 #include<stdio.h>

用 “ ” 会先从你的相对文件路径(也可以绝对路径的文件名)下找有没有这个头文件,否则到库里找,如果找不到就报错 #include "filename"

然后将文件内的内容复制粘贴过来,当然宏也要替换掉

头文件包含嵌套重复

就是头文件可能在以后包含的时候被多次包含,并且头文件里可能也包含了其他头文件,等等,如果每次包含都会增加代码长度,那就得不偿失了


所以结合刚才的知识,有了这个解决方法

#ifndef __TEST_H__//第一次可以包含,第二次将跳过
#define __TEST_H__
//头文件的内容
//头文件的内容
//头文件的内容
#endif  //__TEST_H__


一些IDE2,在建立头文件是时候会自动添加:>

#pragma once
一样能解决问题
♥其他的
#error
#pragma
#line
...

不做介绍,自己去了解吧。


不过 #pragma , 刚才用到了,还有个用处

#pragma pack(4) ->结构体的默认对齐数

文章到此结束!谢谢观看 —>内容参考【比特科技】

可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆!


这是我的代码仓库!(在马拉圈的22.12里)代码仓库


邮箱:2040484356@qq.com


并不是会变长的数组,是以变量作为数组长度的数组 ↩︎


集成开发环境 ↩︎


目录
相关文章
|
27天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
27天前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
59 4
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
57 1
|
27天前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
38 2
|
27天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
37 1
|
1月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
29 2
|
2月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
C语言
C语言及程序设计初步例程-4 C语言程序初体验
贺老师教学链接  C语言及程序设计初步 本课讲解 让程序会计算:求a和b两个数之和 #include &lt;stdio.h&gt; int main( ) { int a,b,sum; scanf("%d %d", &amp;a, &amp;b); sum=a+b; printf("%d\n", sum); return 0; } 用户界面友好(或罗
1092 0
|
C语言 数据处理
《C语言及程序设计》实践项目——C语言程序初体验
返回:贺老师课程教学链接  C语言及程序设计初步   【项目1-输出点阵图】编一个程序,用你的姓名读音首字母,组成类似的趣图提示:printf("……\n");语句会输出双引号中的内容,'\n'完成换行[参考解答]【项目2-完成简单计算】(1)编程序,输入长方形的两边长a和b,输出长方形的周长和面积 提示:边长可以是整数也可以是小数;实现乘法的运算符是*[参考解答] (2)编程序,输入两个电
1274 0
|
24天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
48 10