C -- C预处理器和C库

C预处理器和C库

一、预处理器指令

  1. #include
//引用系统库,在系统目录中查找相应库
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

//引用自定义库,在当前目录下查找相应头文件
#include "MY_HEAD.h"
  1. #define
//定义明示常量
#define SIZE 100

//定义宏函数,多层括号防止宏展开时出意外
#define MIN(X,Y) ( (X) < (Y) ? (X) : (Y) )
  1. #undef
//取消先前定义的宏
#define JUST_CHECK
#undef JUST_CHECK

//可使用预处理器指令测试程序、测试完毕取消宏定义跳过测试代码,保持程序中测试代码不删除,以便以后的调试
#defined JUST_CHECK
//添加#undef JUST_CHECK 或者直接注释掉宏定义

#ifdef JUST_CHECK
/* code for test */
#endif
  1. #ifdef、#else、#endif
/*可类比if-else选择分支(仅针对宏)	*/
--------------------------------------------------
//#ifdef与#endif
#ifdef MARCO
...//执行相应语句
#endif
--------------------------------------------------
//#ifdef与#else与#endif
#ifdef MACRO
...//执行相应语句
#else
...//执行相应语句
#endif
  1. #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
  1. #ifndef
//如果没定义...则执行相关操作
//通常可用于防止头文件被重复包含造成多次定义错误
//假设我们定义一个my_head.h,可如下操作
#ifndef _MY_HEAD
#define _MY_HEAD
...//宏、宏函数、结构、类型等部分code
#endif
  1. #error
//由预处理器发出一条错误信息
#if __STDC_VERSION__ != 201112L
#error not C11
#endif
  1. #line
//重置当前预定义行号信息以及当前文件名
#line 10 "cool.c"		
//预定义宏行号修改为10,预定义宏文件名改为cool.c
  1. #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

标准规定:

  1. 具有内部链接的函数可以成为内联函数;
  2. 必须在首次使用的地方即定义,定义即相当于原型;

因此我们可以同时使用函数说明符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>
  1. _Noreturn void exit(void);
    控制权返回主机环境,终止整个程序。
  2. atexit( void (*fp) (void) );
    atexit()以函数名做为参数,注册函数列表中的函数,当调用exit()时会执行这些函数,列表至少可存放32个函数,后添加的函数在exit()时先执行。通常atexit()注册的函数执行一些清理任务。
  3. 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
未完待续

发布了18 篇原创文章 · 获赞 4 · 访问量 849

猜你喜欢

转载自blog.csdn.net/GuoningningPro/article/details/103474172
今日推荐