C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)(下)

简介: C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)

C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)(中):https://developer.aliyun.com/article/1513279

2.5.1条件编译之常量表达式

如果常量表达式为真,参加编译。反之如果为假,则不参加编译。

代码演示:常量表达式为真

 
#include <stdio.h>
int main()
{
#if 1//非0都是真,算式也行
    printf("Hello,World!\n");
#endif
    return 0;
}

代码演示:常量表达式为假

 
#include <stdio.h>
int main()
{
#if 0
    printf("Hello,World!\n");
#endif
    return 0;
}

当然也可以用宏替换,可以表示地更清楚:

 
#include <stdio.h>
#define PRINT 1
#define DONT_PINRT 0
int main() 
{
#if PRINT
    printf("Hello,World!\n");
#endif
    return 0;
}

2.5.2多分支的条件编译

介绍:多分支的条件编译,直到常量表达式为真时才执行。

代码演示:

 
#include <stdio.h>
int main()
{
#if 1 == 2 // 假
    printf("aaaa\n");
#elif 2 == 2 // 真
    printf("bbbb\n");
#else 
    printf("cccc\n")
#endif
        return 0;
}

2.5.3条件编译判断是否被定义

定义:ifdef 和 if defined() ,ifndef 和 if !defined() 效果是一样的,用来判断是否被定义。

代码演示:

 
#include <stdio.h>
#define TEST 0
// #define TEST2 // 不定义
int main() 
{
    /* 如果TEST定义了,下面参与编译 */
    // 1
#ifdef TEST
    printf("1\n");
#endif
    // 2
#if defined(TEST)
    printf("2\n");
#endif
 
    /* 如果TEST2不定义,下面参与编译 */
    // 1
#ifndef TEST2
    printf("3\n");
#endif
    // 2
#if !defined(TEST2)
    printf("4\n");
#endif
    return 0;
}

2.5.4条件编译的嵌套

和 if 语句一样,是可以嵌套的:

 
 #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

2.6文件包含

我们已经知道,#include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。替换方式为,预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。

2.6.0头文件被包含的方式

< > 和 " " 包含头文件的本质区别:查找的策略的区别

① " " 的查找策略:先在源文件所在的目录下查找。如果该头文件未找到,

则在库函数的头文件目录下查找。(如果仍然找不到,就提示编译错误)

Linux环境 标准头文件的路径:

/usr/include

VS环境 标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include

< > 的查找策略:直接去标准路径下去查找。(如果仍然找不到,就提示编译错误)

既然如此,那么对于库文件是否也可以使用 " " 包含?

当然可以。但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

为了效率不建议这么做。

2.6.1嵌套文件的包含

头文件重复引入的情况:

comm.h 和 comm.c 是公共模块。

test1.h 和 test1.c 使用了公共模块。

test2.h 和 test2.c 使用了公共模块。

test.h 和 test.c 使用了 test1 模块和 test2 模块。

这样最终程序中就会出现两份 comm.h 的内容,这样就造成了文件内容的重复。


那么如何避免头文件的重复引入呢?

使用条件编译指令,每个头文件的开头写:

 
#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif

如果嫌麻烦,还有一种非常简单的方法:

1. 
2. #pragma once // 让头文件即使被包含多次,也只包含一份


笔试题:选自《高质量C/C++编程指南》

① 头文件中的 ifnde / define / endif 是干什么用的?

答:防止头文件被重复多次包含。

② #include <filename.h> 和 #include "filename.h" 有什么区别?

答:尖括号是包含库里面的头文件的,双引号是包含自定义头文件的。

它们在查找策略上不同,尖括号直接去库目录下查找。

而双引号是现去自定义的代码路径下查找,如果找不到头文件,则在库函数的头文件目录下查找。

注:还有其他预处理指令

 
#error
#pragma
#line
...
不做介绍,可以自己去了解。
#pragma pack()//在结构体部分介绍。

3. 笔试选择题

3.1环境

以下什么内容的作用是将源程序文件进行处理,生成一个中间文件,

编译系统将对此中间文件进行编译并生成目标代码。

A.编译预处理

B.汇编

C.生成安装文件

D.编译

解析:

题干中提到了“编译”,说明是编译的上一步,那自然是编译预处理。


3.2可执行程序的生成

由多个源文件组成的C程序,经过编辑、预处理、编译、链接等阶段会生成最终的可执行程序。

下面哪个阶段可以发现被调用的函数未定义?( )

A.预处理

B.编译

C.链接

D.执行

解析:

预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体,

所以是链接时出错的,故选C。这里附上每个步骤的具体操作方式:

预处理: 相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件

(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),

没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。


编译: 将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,

产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,

不负责寻找实体。


链接: 通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。

3.3定义变量

test.c文件中包括如下语句:

 
#define INT_PTR int*
typedef int*int_ptr;
INT_PTR a,b;
int_ptr c,d;

文件中定义的四个变量,哪个变量不是指针类型?( )

A.a

B.b

C.c

D.d

解析:

预处理的#define是查找替换,所以替换过后的语句是“int*a,b;”,

其中b只是一个int变量,如果要让b也是指针,必须写成“int *a, *b;”。

而typedef没有这个问题,c、d都是指针。故选B。


3.4预处理指令

下面哪个不是预处理指令:( )

A.#define

B.#if

C.#undef

D.#end

解析:

#define执行查找替换,#if可以区分是否编译,#undef可以反定义,

也就是取消#define宏定义的东西,#end并没有这个东西,只有#endif。


3.5预定义符号

下面哪个不是预定义符号?( )

A.__FILE__

B.__TIME__

C.__DATE__

D.__MAIN__

解析:

前三个是常用宏,分别是:打印所在文件、打印编译时间、打印编译日期。

除此之外,还有__LINE__(行号)、__FUNCTION__(函数名)等宏,而__MAIN__并不存在。


3.6宏定义1

以下代码输出什么? ( )

 
#include<stdio.h>
#define N 4
#define Y(n) ((N+2)*n) /*这种定义在编程规范中是严格禁止的*/
int main()
{
    int z = 2 * (N + Y(5 + 1));
    printf("%d", z);
    return 0;
}

A.出错

B.60

C.48

D.70

解析:

 
#include<stdio.h>
#define N 4
#define Y(n) ((N+2)*n) /*这种定义在编程规范中是严格禁止的*/
int main()
{
    //N=4  所以Y(n)=((4+2)*n)
    //所以2 * (N + Y(5 + 1)) = 2 * (4 + (4 + 2) * 5 + 1 ) = 2 * 35 = 70
    int z = 2 * (N + Y(5 + 1));
    printf("%d", z);
    return 0;
}

3.7宏定义2

下面代码执行的结果是:( )

 
#include<stdio.h>
#define A 2+2
#define B 3+3
#define C A*B
int main()
{
    printf("%d\n", C);
    return 0;
}

解析:

宏C预处理后的代码是:2+2*3+3,即2+6+3,等于11


3.8条件编译指令

下面哪个是条件编译指令( )

A.#define

B.#ifdef

C.#pragma

D.#error

解析:

A是宏定义,C是一个比较复杂的预编译语句,但跟条件肯定扯不上关系,

D是报错用的,条件编译指令包括#if、#ifdef,#ifndef,#else,#elif、#endif等。

除此之外还有#if defined(xxx)的用法。选B


3.9头文件

以下关于头文件,说法正确的是( )

A.#include,编译器寻找头文件时,会从当前编译的源文件所在的目录去找

B.#include“filename.h”,编译器寻找头文件时,会从通过编译选项指定的库目录去找

C.多个源文件同时用到的全局整数变量,它的声明和定义都放在头文件中,是好的编程习惯

D.在大型项目开发中,把所有自定义的数据类型、函数声明都放在一个头文件中,各个源文件都

只需包含这个头文件即可,省去了要写很多#include语句的麻烦,是好的编程习惯。

解析:

AB说反了,尖括号是直接去库找,双引号是先从当前目录找,再去库里找。

C选项头文件不能定义全局变量,否则如果有多个文件,那链接时会冲突。故选D。

D也不是十全十美,在大型项目的开发中,这也并不是一个很好的编程习惯,

分类放在不同的头文件并根据特点命名是更好的选择,因为这样更加方便代码的管理和维护,

就目前而言,算是一个好习惯吧。


本篇完。

C语言知识点算结束了。


a7a9f6ce42724a77a2a99564bc340994.jpg


目录
相关文章
|
存储 编译器 程序员
c语言的文件操作与文件缓冲区
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。磁盘(硬盘)上的文件是文件。但是在程序设计中,我们⼀般谈的⽂件有两种:程序文件、数据文件(从文件功能的角度来分类 的)。就比如说我们电脑中以.txt为后缀的就是文件的一种,他就是数据文件。.exe为后缀的就为程序文件。函数名功能适用范围fgetc字符输入函数所有输入流fputc字符输出函数所有输出流fgets。
731 0
|
人工智能 C语言
|
存储 小程序 C语言
【C语言程序设计——文件】文件操作(头歌实践教学平台习题)【合集】
本文介绍了C语言中的文件操作,分为两个关卡。第1关任务是将键盘输入的字符(以#结束)存入`file1.txt`并显示输出;第2关任务是从键盘输入若干行文本(每行不超过80个字符,用-1作为结束标志),写入`file2.txt`后再读取并显示。文中详细讲解了文件的打开、读取(使用`fgetc()`和`fgets()`)、写入(使用`fputc()`和`fputs()`)及关闭操作,并提供了示例代码和测试说明。
667 5
|
存储 自然语言处理 Unix
【C语言】C语言 4 个编译过程详解
编译是将源代码转换为目标代码的过程。它是在编译器的帮助下完成的。编译器检查源代码是否存在语法或结构错误,如果源代码没有错误,则生成目标代码。
944 1
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
813 1
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
1049 23
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
613 15
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1646 0
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
769 1
一文彻底搞清楚C语言的函数
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
998 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】