济宁网站建设公司,用wordpress videopro,苏州网站设计公司有哪些,电子商务主要是什么文章目录 一、宏和函数的对比二、#和##运算符2.1 #运算符2.2 ##运算符 三、#undef四、命令行定义五、条件编译六、头文件的包含1. 头文件包含的方式2. 嵌套文件包含 上期回顾#xff1a;
【C语言基础】#xff1a;预处理详解(一) 一、宏和函数的对比
宏通常被应有于执行简单… 文章目录 一、宏和函数的对比二、#和##运算符2.1 #运算符2.2 ##运算符 三、#undef四、命令行定义五、条件编译六、头文件的包含1. 头文件包含的方式2. 嵌套文件包含 上期回顾
【C语言基础】预处理详解(一) 一、宏和函数的对比
宏通常被应有于执行简单的运算。 比如在两个数中找出较大的⼀个时写成下面的宏更有优势⼀些。
#define MAX(x, y) ((x) (y) ? (x) : (y))用函数来完成 调用函数执行运算函数返回 使用函数来完成任务就要经历这三个步骤而这三个步骤都需要一定的时间开销对于一些简单的运算这无疑是不太好的。 用宏来完成 对于简单的运算宏只有执行运算的时间开销这个效率明显比函数要高得多。 小结
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于 来比较的类型。宏的参数是与类型无关的。
利用宏的执行速度短那是不是以后就只用宏了呢这明显是不明智的函数也有着宏所没有的优点 3. 每次使用宏的时候⼀份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。 4. 宏是没法调试的。 5. 宏由于类型无关也就不够严谨。双刃剑 6. 宏可能会带来运算符优先级的问题导致程容易出现错。
宏也有函数做不到的功能例如宏的参数可以出现类型函数就不可以 【示例】利用宏来实现malloc函数
#includestdio.h
#define Malloc(n, type) (type*)malloc(n*sizeof(type))
int main()
{// int* p (int*)malloc(5 * sizeof(int));int* prt Malloc(5, int);return 0;
}当我们将5和int传入到Malloc是那么n就是5type就是int也就是有一个参数是类型宏是可以实现的但函数可以实现预处理之后替换的结果就是(int*)malloc(5 * sizeof(int))。
宏和函数的对比
二、#和##运算符
2.1 #运算符
#运算符是一个预处理器运算符用于字符串化Stringification。当你在宏定义中使用 # 运算符时它会将宏的参数转换为一个字符串字面量。这意味着当宏被展开时参数的值会被放在双引号中成为字符串的一部分。
【示例铺垫】
#includestdio.h
int main()
{printf(hello world\n);printf(helloworld\n);return 0;
}C语言会将两个字符串看成一个字符串。
#includestdio.h
int main()
{int a 1;printf(the value of a is %d\n, a);int b 20;printf(the value of b is %d\n, b);float f 5.6f;printf(the value of f is %f\n, f);return 0;
}可以看到这几个打印的只有这两个地方有所差异那我们可以利用宏来实现这个功能。
【示例】
#define Print(n, format) printf(the value of #n is format \n, n)
int main()
{int a 1;Print(a, %d);int b 20;Print(b, %d);float f 5.6f;Print(f, %f);return 0;
}可以发现结果其实是一样的这里的#运算符的作用就是将n转化成n例如#a就是将a转换成a。 利用前面的那个铺垫两个字符串可以看成一个字符串。
注意使用 # 运算符时应确保宏参数两侧有空格或其他非字母数字字符否则可能会导致字符串化不正确。例如#define NUM 42 和 #define NUM_ 42 会产生不同的结果因为第一个定义会将 NUM 字符串化而第二个定义会将 NUM_ 字符串化并且由于 42 紧跟在 NUM_ 后面它可能会成为字符串的一部分导致预处理错误。
2.2 ##运算符
在C语言中## 是预处理器的标记粘贴运算符。这个运算符可以将两个标识符拼接成一个更长的标识符。当预处理器遇到使用 ## 的宏定义时它会将 ## 符号左边和右边的任何合法标识符或宏名称拼接在一起创建一个新的标识符。
【示例铺垫】求较大值
// 求整数较大值
int int_max(int x, int y)
{return x y ? x : y;
}
// 求浮点数较大值
float float_max(float a, float b)
{return a b ? a : b;
}这样写显得有点繁琐因为求不同的数据类型就要写不同的函数这时候就可以动态创建宏名称
#includestdio.h
// \为续航符
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{\
return (xy?x:y);\
}
// 使用宏定义不同的函数
GENERIC_MAX(int)
GENERIC_MAX(float)
int main()
{int m1 int_max(5, 6);printf(%d\n, m1);float m2 float_max(5.6f, 3.4f);printf(%f\n, m2);return 0;
}预处理之后可以更加明显的看到这之间的变化 注意 由于 ## 运算符是在预处理阶段进行的因此它不能用于运行时的代码拼接。确保在使用 ## 运算符时左右两边的标识符是明确的否则可能会导致编译错误或者不可预期的行为。##运算符可以与 # 字符串化运算符结合使用创建更加复杂的宏定义。 命名约定 一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分⼆者。 那我们平时的⼀个习惯是
把宏名全部大写函数名不要全部大写
三、#undef
#undef是一个预处理器指令用于取消已经定义的宏。当预处理器遇到 #undef指令时它会移除指定宏的定义使得宏名不再代表之前定义的文本。
#undef 指令通常用于以下情况
防止宏名冲突如果在不同的头文件中定义了相同的宏名或者在修改代码时需要改变宏的定义可以使用 #undef 来确保宏的最新定义是有效的。条件编译在条件编译块中可能需要根据某些条件取消宏的定义这时可以使用 #undef。清理宏定义在某些复杂的宏定义中可能需要在宏展开后清理宏定义以防止宏名被错误地使用。
使用方法
// 只需要提供要取消定义的宏名即可
#undef macro_name【示例】
#define MAX 100#undef MAXprintf(%d\n, MAX); // 这里会引发错误因为MAX已不再定义四、命令行定义
在C语言编程中命令行定义指的是通过编译器的命令行参数来定义宏或者设置编译时的选项。这种方法允许开发者在不修改源代码的情况下动态地改变编译过程和生成的程序的行为。
定义宏 大多数C语言编译器允许使用命令行参数来定义宏。在GCC和Clang等编译器中可以使用 -D 选项来定义宏。
【示例】命令行定义
#includestdio.h
int main()
{int arr[SZ];// SZ未定义for (int i 0; i SZ; i){arr[i] i 1;}for (int i 0; i SZ; i){printf(%d , arr[i]);}return 0;
}五、条件编译
条件编译是C语言预处理器提供的一项功能它允许根据预处理器指令的特定条件来包含或排除代码块。这意味着在编译时只有满足特定条件的代码才会被编译器处理其他不满足条件的代码将被忽略。这对于根据不同的平台、操作系统或编译时的配置来编译不同的代码非常有用。
条件编译主要使用以下预处理器指令
#ifdef如果定义了某个宏则编译#ifdef和#endif之间的代码块。#ifndef如果未定义某个宏则编译#ifndef和#endif之间的代码块。#if如果给定的表达式为真非零则编译#if和#endif之间的代码块。#elif如果前面的#if或#elif条件不满足并且当前#elif表达式为真则编译#elif和#endif之间的代码块。#else如果前面的所有#if和#elif条件都不满足则编译#else和#endif之间的代码块。#endif结束条件编译块。
【示例1】 调试性的代码删除可惜保留又碍事所以我们可以选择性的编译。
#includestdio.h
#define __DEBUG__
int main()
{int arr[10] { 0 };for (int i 0; i 10; i){arr[i] i 1;
#ifdef DEBUGprintf(%d , arr[i]); //为了观察数组是否赋值成功。
#endif // DEBUG}return 0;
}【示例2】#if 常量表达式
#includestdio.h
int main()
{
#if 0printf(hello world);
#endifreturn 0;
}预处理后可以发现当不满足条件时这里是不参与编译的
【示例3】多分支的条件编译
#includestdio.h
#define M 1
int main()
{
#if M 0printf(hehe\n);
#elif M 1printf(haha\n);
#elif M 2printf(heihei\n);
#endifreturn 0;
}注意最后都要以 #endif 结束。
【示例4】判断是否被定义
#includestdio.h
int main()
{
#if defined(MAX)// 定义了执行没定义不执行printf(NO);
#endif#if !defined(MAX)// 没定义执行定义了不执行printf(YES);
#endifreturn 0;
}其实条件编译是非常常见的比如在头文件里面就会经常使用条件编译以下是头文件stdio.h的部分条件编译
六、头文件的包含
1. 头文件包含的方式
在C语言中头文件的包含方式主要有两种直接包含和间接包含。这两种方式都是为了在当前文件中引入其他文件中定义的函数、变量、类型声明等以便在当前文件中使用它们。
直接包含 直接包含是指在源文件或头文件中使用预处理器指令 #include 直接引入另一个文件。这是最常见的包含方式可以确保所需的声明和定义在当前编译单元中可用。
#include stdio.h编译器会在标准库的路径中搜索这些文件。这些路径通常是编译器安装时预设的包括了所有标准库文件的位置。尖括号通常用于包含C标准库的头文件。
查找策略先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件⼀样在标准位置查找头文件。如果找不到就提示编译错误。
2. 嵌套文件包含
我们已经知道 #include 指令可以使另外⼀个文件被编译。就像它实际出现于 #include 指令的地方⼀样。 这种替换的方式很简单预处理器先删除这条指令并用包含文件的内容替换。 ⼀个头文件被包含10次那就实际被编译10次如果重复包含对编译的压力就比较大。
test.h头文件
void test();struct Stu
{int id;char name[20];
};如果直接这样写test.c文件中将test.h包含5次那么test.h文件的内容将会被拷贝5份在test.c中。 如果test.h 文件比较大这样预处理后代码量会剧增。如果工程比较大有公共使用的头文件被大家都能使用又不做任何的处理那么后果真的不堪设想。
解决办法 每个头文件的开头写 test.h头文件
#ifndef __TEST_H__
#define __TEST_H__
void test();struct Stu
{int id;char name[20];
};
#endif或者#pragma once
#pragma once
void test();struct Stu
{int id;char name[20];
};就可以避免头文件的重复引入。