C预处理器和C库
一、预处理器指令
- #include
//引用系统库,在系统目录中查找相应库
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
//引用自定义库,在当前目录下查找相应头文件
#include "MY_HEAD.h"
- #define
//定义明示常量
#define SIZE 100
//定义宏函数,多层括号防止宏展开时出意外
#define MIN(X,Y) ( (X) < (Y) ? (X) : (Y) )
- #undef
//取消先前定义的宏
#define JUST_CHECK
#undef JUST_CHECK
//可使用预处理器指令测试程序、测试完毕取消宏定义跳过测试代码,保持程序中测试代码不删除,以便以后的调试
#defined JUST_CHECK
//添加#undef JUST_CHECK 或者直接注释掉宏定义
#ifdef JUST_CHECK
/* code for test */
#endif
- #ifdef、#else、#endif
/*可类比if-else选择分支(仅针对宏) */
--------------------------------------------------
//#ifdef与#endif
#ifdef MARCO
...//执行相应语句
#endif
--------------------------------------------------
//#ifdef与#else与#endif
#ifdef MACRO
...//执行相应语句
#else
...//执行相应语句
#endif
- #if、#elif以及defined运算符
//拓展的选择分支预处理器指令(后可跟整型常量表达式)
--------------------------------------------------
//根据#if后的语句选择执行相应的语句
#if SYS == 1
#include "win.h"
#elif SYS == 2
#include "mac.h"
#else
#include "other.h"
#endif
//结合使用defined预处理运算符
#if defined (IBMPC)
#include "ibmpc.h"
#elif defined (VAX)
#include "vax.h"
#elif defined (MAC)
#include "mac.h"
#else
#include "general.h"
#endif
- #ifndef
//如果没定义...则执行相关操作
//通常可用于防止头文件被重复包含造成多次定义错误
//假设我们定义一个my_head.h,可如下操作
#ifndef _MY_HEAD
#define _MY_HEAD
...//宏、宏函数、结构、类型等部分code
#endif
- #error
//由预处理器发出一条错误信息
#if __STDC_VERSION__ != 201112L
#error not C11
#endif
- #line
//重置当前预定义行号信息以及当前文件名
#line 10 "cool.c"
//预定义宏行号修改为10,预定义宏文件名改为cool.c
- #pragma指令 以及_Pragma运算符
//编译指示#pragma,修改编译设置,将编译器指令放入源代码中
//让编译器支持c9x
#pragma c9x on
//等价方式
_Pragma("c9x on")
//_Pragma运算符完成解“字符串”工作
_Pragma("c9x on")
#pragma c9x on
二、 泛型选择(C11)
//类似于switch语句,switch用值匹配标签,而_Generic用类型匹配标签
//例1:
_Generic(x,int: 0,float: 1,double: 2,default: 3)
//例2:
#define MYTYPE(X) _Generic((X),
int: "int",\
float: "float",\
double: "double",\
default: "other"\
)
//例3:结合函数,判断类型,对应类型标签,使用相应类型函数等,tgmath.h中就定义了泛型类型宏
#include <math.h> //使用sin()、sinl()、sinf()
#define SIN(X) _Generic((X),
long double: sinl(X),\
float: sinf(X),\
default: sin(X)
)
三、内联函数(C99)
通常函数调用有一定的开销,函数的调用过程包括建立调用、传递参数、跳转到函数代码并返回。使用宏使代码内联可以避免这样的开销。函数通常省代码空间,但是特定情况调用过程时间可能耗费较大;宏函数通常省时间,无需跳转又返回此等麻烦,但是代码空间耗费大,如果使用很多次宏函数,那么便嵌入很多宏函数代码,空间耗费巨大。
C99提供另一种方法:内联函数(inline function)
标准规定:
- 具有内部链接的函数可以成为内联函数;
- 必须在首次使用的地方即定义,定义即相当于原型;
因此我们可以同时使用函数说明符inline和存储类别说明符static;如果省略static,那么对于编译器来说inline定义被视为可替换的外部定义,可能内联,也可能这就是个外部链接定义的普通函数。
#include <stdio.h>
//内联函数定义/原型
inline static void eatline(void)
{
while(getchar() != '\n')
continue;
}
int main(void)
{
...
eatline();
...
}
一般我们不在头文件中放置可执行代码,内联函数是个特例。由于内联函数具有内部链接,所以在多个文件中定义同一个内联函数不会有什么问题。
注意:内联函数应该比较短小。把较长或带深层循环或递归的函数变成内联并未节约时间,因为执行函数体的时间比调用时间长得多。这么看来似乎内联函数也没什么大用处?。。。
不过到目前为止我们有三种定义函数的方式:
- 普通函数(无函数说明符)
- 宏函数
- 内联函数(使用inline static说明)
四、_Noreturn函数(C11)
C11新增了第二个函数说明符_Noreturn(第一个是C99的inline),表明函数调用完成后不返回主调函数。exit()是_Noreturn函数的一个示例。与void类型函数不同,void类型函数执行完毕返回主调函数,只是不返回值。
_Noreturn函数说明符目的是告知用户和编译器,这个特殊的函数不会把控制返回主调程序,告知用户避免滥用该函数,通知编译器可优化一些代码。
现在我们有四种定义函数的方式:
- 普通函数(无函数说明符)
- 宏函数
- 内联函数(使用inline static说明)
- 无跳转函数(使用_Noreturn说明)
五、C库
关于一些库的其他函数。
-
<math.h>
数学库,一些数学计算领域的函数,不多说;需使用相应版本函数。 -
<tgmath.h>
泛型类型宏,由泛型选择相应版本的计算函数,如定义sqrt()宏展开为sqrtf()、sqrtl()或sqrt()函数。
//特殊之处
#include <tgmath.h>
...
float x = 9.9;
double y;
y = sqrt(x); //调用宏,因而是sqrtf(x)
y = (sqrt)(x); //调用函数sqrt()
y = (*sqrt)(x); //仍是调用函数sqrt()
- <stdlib.h>
- _Noreturn void exit(void);
控制权返回主机环境,终止整个程序。 - atexit( void (*fp) (void) );
atexit()以函数名做为参数,注册函数列表中的函数,当调用exit()时会执行这些函数,列表至少可存放32个函数,后添加的函数在exit()时先执行。通常atexit()注册的函数执行一些清理任务。 - qsort(),快速排序算法在C中的实现。
//函数原型
void qsort( void * base, size_t nmemb,size_t size,int (*cmp)(const void *, const void *) );
//使用案例
#include <stdio.h>
#include <stdlib.h>
int mycmp(const void *, const void *); //mycmp原型
int main(void)
{
//以nums数组为例
int nums[10] = {4,5,3,4,2,1,7,8,9,5};
qsort(nums,10,sizeof(int),mycmp); //调用qsort()排序
}
int mycmp(const void * p1, const void * p2) //mycmp定义
{
int ret;
//显式强制类型转换
const int * ps1 = (const int *) p1;
const int * ps2 = (const int *) p2;
//返回值1/-1/0 用于提供给qsort排序
if(*ps1 > *ps2)
ret = 1;
else if(*ps1 < *ps2)
ret = -1;
else
ret = 0;
return ret;
}
关于qsort()就到这,改天回来实现一下快速排序的算法。
- <assert.h>
断言库。
assert(),运行时断言一个条件表达式或逻辑表达式成立,若断言失败即条件不满足,则会在运行过程中报错。
_Static_assert(),(相较于运行时报错,编译时提示错误更有意义)编译时检查参数语句(要是整型常量表达式,即可以在编译器求值判断),若断言失败,编译时提示第二个参数(字符串) - <string.h>
字符串库有很多关于字符串处理的函数。我们之前学过,字符串拷贝strcpy不能拷贝数组,如果要拷贝数组得一个元素一个元素拷贝。其实<string.h>中有针对任意数组类型的拷贝。
void * memcpy(void * restrict s1, const void * restrict s2,size_t n);
void * memmove(void * s1, const void * s2, size_t n);
//从函数原型的角度来看,memcpy前两个参数不知道数组的元素类型,因此它移动拷贝的其实是字节,也就是说应用时通常如下;
memcpy(target,values, sizeof(/*元素类型*/) * NUM );
memcpy()、memmove(),其实都可以完成任意类型数组拷贝指定字节。从他们函数原型来看,仅有restrict限定符的区别,即我们应用memcpy时更要小心,确保拷贝的两个区域不重叠!
- <stdarg.h>
可变参库提供一个定义可变参数函数的定义方法。
有时间再回来写着一部分。
2019.12.10
by liuxin
未完待续