预处理和程序的编程(跑路人笔记3)

简介: 预处理和程序的编程(跑路人笔记)

预处理

预处理定义符号

__FILE__ //进行编译的源文件


__LINE__ //文件当前的行号


__DATE__ //文件被编译的日期


__TIME__ //文件被编译的时间


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


这些符号都是语言内置的无需定义


image.png


最后__STDC__并不是所有编译器都定义了的至少在VS2019上未被定义.


#define

#define有本质上是将文本内容在编译时进行替换,又因为他可以替换参数所以就出现了宏


注意点:


#define有替换规则:


先替换参数的#define


随后插入文本将宏参数名所替换


最后对结果文件进行扫描看是否还有


宏定义格式:#define name( parament-list ) stuff


宏定义时不能吝啬括号


name和左括号直接不能有空隙如#define MAX(a,b) MAX不能和左括号有空隙


宏不可以递归 不可以一个宏套自己.


使用是宏最好不要使用带有副作用的宏参数如x++


宏一般比函数速度快一般简单的功能使用宏来实现较好


有时我们可以通过宏来实现函数无法实现的功能


宏无法调试因为在编译期间就被替换掉了


如使用宏PRINT打印无论浮点型或者整形还可以实时将我们要打印的变量名加入其中.通过#和##来实现(目录中有)


#define替换文本

语法:#define name stuff


举一个例子


#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__ )



我们C语言的程序员内部有默契,我们把#define定义的一般做全大写函数命名一般不是全大写一般是首字母大写或其他部分大写,当然我们宏定义假做成函数也会没有全大写.


#define定义宏

#define允许把参数替换到文本中,这种实现通常被称为宏,或定义宏


#define name(由逗号隔开的符号) stuff


举例:


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

image.png


值得注意的是:


还有上面的宏其实有一个很大的弊病


我们用下面的代码来说明:


#include<stdio.h>
#define SQUARE(x) x * x
int main()
{
  printf("%d", SQUARE(3+2));
  return 0;
}


image.png


这并不是我们想要的答案我们想得到5*5可是这个宏定义给的式子却给了我们11这是因为我们在定义宏时没有考虑到运算的优先级


首先我们的宏在编译阶段会直接和代码替换本次的宏就将printf函数内容进行了替换使SQUARE(3+2)替换成了3+2*3+2这样我们就得到了11的值


所以这也提醒我们在定义宏的时候一定不要吝啬我们的括号只需稍加修改我们的宏变为如下:


#include<stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
  printf("%d", SQUARE(3+2));
  return 0;
}



就不会出现以上情况了.


注意:


宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。


当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。


#和##

介绍这俩之前我们需要先知道


char* p = "hello ""bit\n";
  printf("hello"" bit\n");
  printf("%s\n", p);



他们的打印结果都是"hello bit"这是因为字符串有自动连接的特点


首先介绍这步用到的知识点


字符串的自动连接

#放在字符串之间自动将变量转换成了字符串(放在宏定义里)

##将两个符号合成为一个符号

介绍一下#操作直接上例子:


#define PRINT(x) printf("The "#x" value is %d",x)
int main()
{
  int a = 10;
  PRINT(a);
  return 0;
}



image.png


这样define里的#x和"x"一样.


很适合偷懒


包括打印其他类型也就只是多个参数


#define PRINT(x,y) printf("The "#x" value is" #y,x)


##的作用把两边的符号合成一个符号直接上例子:


#define ADD_TO_SUM(num, value) sum##num += value
int main()
{ 
  int sum1 = 20;
  int sum2 = 10;
  ADD_TO_SUM(1, 20);
  printf("%d", sum1);
  return 0; 
}


image.png



这样我们就在sum1直接加了20或把1改完2就是将sum2加20


带有副作用的宏参数

x+1;//不带副作用x++;//带有副作用

1

不要对宏使用带有副作用的参数


如下例

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
  int x = 5;
  int y = 8;
  int z = MAX(x++, y++);
  printf("x=%d y=%d z=%d\n", x, y, z);
  return 0;
}



image.png


不然总会带来我们不想要的结果因为y进行了两次++而x进行了一次++ z得到的是y++的值也就是9当然我们知道宏MAX是什么可以轻松反推但是未来大型项目中这样搞不知会出现什么bug


undef

移除宏定义


#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。



命令行定义^ 2

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。


例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)


image.png


这样我们就直接将M定义成了100直接就加入了代码中


条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。


比如:


那些我们为了调试而编写的代码,删除可惜但是保留住又十分碍事.


常见的条件编译指令(使用时均包含在main函数中):


1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
2.多个分支的条件编译
#if 常量表达式
 //...
#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


后面跟常量表达式为真就进入为假就跳过(除了0都是真)所以有时也被当做注释使用(为了装逼=-=)


#define __DEBUG__ 1 
#if __DEBUG__ 
//.. 
#endif



只要这个__DEBUG__为非零就进入


#if defined(symbol)//symbol被定义了就进入
#ifdef symbol//同上
#if !defined(symbol)//没有被定义就进入
#ifndef symbol//同上



可以嵌套使用


文件包含

我们可以使用#include来使另一个文件被编译就行他实际就在#include的位置一样


这种替换的方式很简单:


预处理器先删除这条指令,并用包含文件的内容替换。


这样一个源文件被包含10次,那就实际被编译10次


包含方式

分两种


#include"Add.h"


这种包含方式我们的编译器会现在源文件的目录下查找如果没找到编译器就会想查找库函数头文件一样寻找.还没找到就报错


#include<stdio.h>


直接就在库函数的头文件找,找不到就报错


嵌套文件的包含

有时我们会不小心多次嵌套自己的头文件或者一个头文件被多次嵌套使用就浪费了内存,这时我们只需在文件开头写到


#pragma once

1


#ifndef __TEST_H__

#define __TEST_H__

//头文件的内容

#endif   //__TEST_H_


结尾

C进阶就基本算是完成了,等我吧文件和通讯录更出来就算完成了C语言的进阶,后续会先C深剖再数据结构.数据结构穿插着题目进行.


本次测试均在Linux环境下进行。 ↩︎

相关文章
|
6月前
|
编译器 Linux C语言
【C语言航路】第十五站:程序环境和预处理(下)
【C语言航路】第十五站:程序环境和预处理(上)
40 0
|
6月前
|
存储 自然语言处理 编译器
【C语言航路】第十五站:程序环境和预处理(上)
【C语言航路】第十五站:程序环境和预处理
42 0
|
7月前
|
算法 程序员 编译器
当程序遇上困难:程序调试的艺术(VS)
当程序遇上困难:程序调试的艺术(VS)
45 0
|
10月前
|
人工智能 算法 Java
关于“Python进高考”,我有句呵呵不知当讲不当讲
如果你是需要用编程来解决问题,那么用 Python 做便是了。用它是因为它适合,跟它火不火无关。
|
11月前
|
编译器 C语言
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(1)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。 1.1程序翻译中的的编译和链接
|
11月前
|
编译器
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(2)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。
|
API C语言 开发者
【精选】太阳系八大行星运转轨迹程序,C语言,源代码分享
太阳系行星运行轨道图,C语言,源代码分享
209 0
【精选】太阳系八大行星运转轨迹程序,C语言,源代码分享
|
uml 开发者 Windows
推荐5款冷门小工具,看一看有没有你喜欢的?
每个人的电脑中都会安装很多软件,可能还保留着很多不为人知的冷门软件。不过虽然冷门,但绝不意味着低能,相反很多冷门软件的功能十分出色。闲话少说,接下来我就给大家推荐5款冷门小工具,看一看有没有你喜欢的。
147 0
推荐5款冷门小工具,看一看有没有你喜欢的?
|
算法 C语言 Python
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。
|
程序员 编译器 C语言
程序员内功心法之程序环境和预处理(2)
程序员内功心法之程序环境和预处理(2)
146 0
程序员内功心法之程序环境和预处理(2)