编程环境和预处理(精选)

本文详细介绍了C语言中的预处理、编译环境,包括预处理指令如#define的使用、预定义符号、编译流程、头文件的包含规则以及宏与函数的区别。通过实例阐述了宏的定义、替换规则和条件编译的应用。
摘要由CSDN通过智能技术生成

     

目录

1。编译环境

2。运行环境

3。预处理(精选)

4。预定义符号

5。#define定义宏

题目

6。#define替换规则

7。#与##(没什么用的知识)

8。#undef

9。条件编译

10。头文件的包括

11.宏和函数的区别


      本章为C语言的最后一章了,终于要结束了吗?结束了,本文是预处理和编程环境的精选部分,也是由于特殊原因这时候才写出来,由于链表,线性表的内容比较贴近数据结构的知识,所以将会放到后面讲。总之要想了解更多的数据结构的知识,请关注这个账号。

话不多说,进入今天的内容!!!!!!!!

1。编译环境

经过日常的学习我们知道我们的代码源文件的后缀是.c的比如test.c,但是这个文件计算机是读不懂的,计算机内部需要将其转化成.exe结尾的文件,这是test.c会被转化成可执行程序test.exe。这个.exe结尾的文件是二进制文件。那这里只是体现了开始和结尾的步骤而已,请看中间的秘密。

由于VS的集成开发环境是IDE而不是gcc,所以这个编译和链接的过程是看不到的,我们也不需要知道为什么是这样。

编译 = 预处理(预编译) + 编译 + 链接,编译环境又可以分为编译+链接,那这5个部分又分别有什么作用呢?

1。预编译将.c的文件先变成.i的,编译再将.i的文件变成.s的文件,最后汇编生成.o的文件。最后生成目标文件.obj

2。所有的预处理指令都是在预编译中完成的。并且会删除注释部分,所以注释部分就不会被编译就是这个原因

3。编译中会进行语法分析,词法分析,语义分析,这些过程就是在告诉计算机要怎么执行代码。

4。汇编过程会形成符号表。如果进行词法分析就是将词法分析的结果整理成一个表格。

5。编译会进行符号汇总,再由汇编过程形成符号表,再在链接中进行符号决议和重定义。这个过程可以通过代码演示。

在add.c中写下Add函数。

在test.c中用extern引用这个函数

这个有点基础的都知道这个代码是可以实现的,就是这个ret的值一定是可以算出300的,那是怎么实现的呢?

首先add.c和test.c这两个文件同时进入预处理阶段,预处理采集到语法,语义和词义同时传入编译阶段进行符号汇总,这时由于Add和main函数都是函数所以在链接阶段形成同一类的符号表,由于test.c的Add是没有被分配空间的所以是没有地址的,记为0x000,有空间的main函数和add.c中的Add函数记为0x100,这时到链接阶段没有地址的test.c中的Add函数就会在符号决议和重定义中被舍弃。最后Add函数和main函数都有地址了,生成可执行程序test.exe,然后ret的值就被算出来了!!!

对于这个编译器cl.exe和链接器link.exe我们可以在Everything这个工具上找到:可以看到有很多

2。运行环境

其实运行环境的知识不是很重要,看得懂以上图解就行!!!

3。预处理(精选)

#define MAX 1000//预定义MAX一定是大写的为1000,不要加问号,不然会认为MAX等于1000;
int main()
{
    int a = 0;
    int e = MAX;
    printf("%d\n", MAX);
    printf("%d\n", e);
    printf("%d\n", a);
    return 0;
}

我们不仅可以将我们要定义的东西定义成数字,还可以定义成字符串:

#define MAX "hehe"
int main()
{
    //define定义标准符
    printf("%s", MAX);
    return 0;
}

这样可以正常打印hehe,但是如果这样写呢?这就形成了我们的易错点:

#define MAX hehe
int main()
{
    printf("%s", "MAX");
    return 0;
}

可能有的人会认为MAX已经被定义为hehe那打印“MAX”不就是在打印hehe了吗?,很好数学的等效替换都被你用上了,但是你打印“MAX”,计算机就会把“MAX”认为是字符串了,就不再替换了。

还可以定义成运算式:

#define M 100+2
#define STR "hehe"

int main()
{
    int a = M;
    printf("%s\n", STR);
    printf("%d\n", a);//此时M会自己相加
    return 0;
}

还可以定义成一段代码:

#define CASE break;case

 int main()
 {
     int n = 0;
     switch(n)
     {
         case 1:
         //
         CASE 2:
         //
         CASE 3:
         //
         CASE 4:
     }
     return 0;
 }

这样用可以防止程序员在写switch语句时忘记写break;是不是非常巧妙呀,但是其实不推荐这样写

4。预定义符号

VS支持的预定义符号只有4个,还有一个是VS不支持的__STDC__。

int main()
{
    printf("%s\n", __FILE__);//预定义符号,显示文件准确位置
    printf("%s\n", __TIME__);//显示编译时间
    printf("%s\n", __DATE__);//显示日期
    printf("%d\n", __STDC__);//gcc是支持ANSI C的,会等于1但是VS不支持所以会报错
    printf("%d\n", __LINE__);//显示行号
}

注意一下这个__TIME__,它打印的是你程序被编译的时间,你后面如果没有再编译,而是直接运行的话,它还是打印你之前编译的时间,而不是当前的实际时间。

学了#define后我们还可以这样写:

\t是空格,,之后的\是换行符,这个只有在预处理中可以这么用,用这个\是为了防止定义部分太长。

5。#define定义宏

标准格式:#define MIN(x) ((x) + (x))

 #define SQUARE(X) (X)*(X)
 //宏的参数不运算,直接替换到宏的体内
 //1. 宏的参数中如果有操作符,和宏的内容中的操作符
 //因为运算符有优先级的问题,可能导致运算顺序不达预期
 //所以容易产生问题
 //2. 宏在书写的时候,给参数都带上括号,不要吝啬括号

以下例子说明为什么不要吝啬括号:

#define MAX(x) (x) + (x)
#define MIN(x) ((x) + (x))//
int main()
{
    int a = 3;
    int r = 0;
    int c = 0;
    r = 3 * MAX(a);
    c = 3 * MIN(a);
    printf("%d\n", r);
    printf("%d\n", c);
    return 0;
}

有的同学肯定会认为最后算出来,r和c的值会一样,那事实是如此吗?

如果没有最外层的括号,这就有了运算优先级的问题了,c等于18很容易得到的就是3*((3)+ (3)),而r是这样算的 r = 3 * 3 + 3 = 12,由于乘号的优先级大于加号。

那根据宏的运算特性题目会怎么出呢?

题目

//写一个宏,求2个整数的较大值
 #define MAX(x,y) ((x)>(y)?(x):(y))
 int main()
 {
     int a = 0;
     int b = 0;
     scanf("%d %d", &a, &b);// 3 5
     int m = MAX(a++, b++);
     //int m = ((a++)>(b++)?(a++):(b++));

     printf("m = %d\n", m);
     printf("a = %d\n", a);
     printf("b = %d\n", b);

     return 0;
 }

如果a = 3,b = 5的话那编译的结果会是多少呢?

首先宏的定义格式非常完整,所以坑点一定不会在宏上,那就是这个++了。其实我们也称a++这种后置加加为带副作用的宏参数。

由于是后置++所以是先代入再++,这里就有先后之分了(循环语句没有),由于a比b小所以,m = :后面的b++但是由于a和b在前面已经加加过了,所以m = b++,b还要再加加,所以b就相当于++了两次,所以b = 7,a 也相当于加了两次,所以a = 4,但是由于m = b++,又是一个后置++,先代入再加加,b在第2次++还没进行时就待入m了,所以m = 6。

6。#define替换规则

7。#与##(没什么用的知识)

由于两个字符串有自动连接的特点,所以如果要大印两个字符串就需要把两个字符串都用“”引起来。

#的作用是将一个宏参数变成对应的字符串。

#define PRINT(val, format)  printf("the value of "#val" is " format"\n", val)//把宏的参数变成对应的字符串加#后要再加一个“”
 //
 int main()
 {
     int a = 10;
     PRINT(a, "%d");

     int b = 20;
     PRINT(b, "%d");

     float f = 3.5f;
     PRINT(f, "%f");

     return 0;
 }

宏可以大印不同形式的数字,但是原本应该是the value of val,但是如果这样写的话,这个val就无法被翻译成参数的val,因为系统会默认这一整行是一个字符串。所以#的作用的得以显现,"#val"就会加上“”表示是字符串。

##的作用:

我们换个更直观的例子:

#define GENERIC_MAX(type) \
type type##_max(type x, type y)\ //如果没加##计算机就会认为type_max是一个整体,
{\
    return (x > y ? x : y); \
}

 /*GENERIC_MAX(int)
 GENERIC_MAX(float)
 GENERIC_MAX(double)*/

 int main()
 {
     int m = int_max(3, 5);
     printf("%d\n", m);

     return 0;
 }

这个例子是利用宏来实现比较函数,type type##_max(type x, type y),如果没加##计算机就会认为 type_max是一个整体,是一个字符串,使得type不会被替换。

8。#undef

9。条件编译

这个常量表达式只能是  参数 =常数,M==a这种不是常量表达式。

对于多分支的条件编译:

#define a 2
int main()
{
#if  a == 1
    printf("%s", "hehe");
#elif a == 2
    printf("%s", "haha");//有遇到匹配的会亮
#elif a == 6
    printf("%s", "hihi");
#else
    printf("%s", "jiji");
#endif
    return 0;
}
 和选择语句不同的一点是,如果#else 之前已经有匹配项了,这个#else就不会运行了。

3。判断是否被定义。

#define MAX 0
int main()
{
    #if defined(MAX)
        printf("hehe\n");
    #endif

    #ifdef MAX//缩写
        printf("haha\n");
    #endif

#ifdef MAX是#if defined(MAX)的缩写。

#if !defined(symbol) 的缩写是  #ifndef symbol

4。嵌套定义

嵌套定义的实质就是以上三种的组合。

10。头文件的包括

首先我们从写代码的第一天就可以看到#include<stdio.h>这个头文件是C语言标准库里的头文件,而我们在编写扫雷游戏是有这么一个头文件#include"test.h",那这两个头文件包括方式有什么区别呢?

如果这个头文件是在标准库里那就是用<>,如果这个头文件是自己创建的,也就是标准库里没有的,那就是用“”。

就是说,如果头文件是用<>包括的,计算机就会直接在库里面找,如果是用""包括的计算机会先在当前文件中找,等到找不到了再去库里面找。所以所有的在C语言标准库里的头文件也可以用“”引起来,只是在找的时候比较慢。#include<stdio.h>  ==  #include"stdio.h"

为了防止重复包括头文件,这里有两种防止重复包括的方式:

#pragma once//防止头文件包含多次


#ifndef __TEST_H__
#define __TEST_H__
#endif//防止重复包括

由于计算机会去.h的文件中找有没有这个头文件,所以这两种方式都应写在.h文件中。

11。宏和函数的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值