C入门系列:第八章 C预处理器和C库

C入门系列:第一章 基本类型

C入门系列:第二章 字符串

C入门系列:第三章 函数

C入门系列:第四章 数组和指针

C入门系列:第五章 数据存储类别和内存管理

C入门系列:第六章 文件输入/输出

C入门系列:第七章 结构和其他数据形式

C入门系列:第八章 C预处理器和C库

1 #define

#define LIMIT 20
const int SIZE = 50;
static int data1[LIMIT]// 有效
static int data2[SIZE]; // 无效
const int LIM2 = 2 * LIMIT; // 有效
const int LIM3 = 2 * SIZE; // 无效

#include<stdio.h>
#include SQUARE1(X) X*X  // x+2*x+2
#include SQUARE2(X) (X)*(X) // (x+2)*(x+2)
#define PR(X) printf("The result is %d", X);

int main(void) {
	int x = 5;
	int z;

	z = SQUARE1(x); // 使用预定义的数据替换表达式	
	PR(z);
	PR(SQUARE1(x + 2)); 
	PR(SQUARE2(x + 2));
}	

2 #运算符

#include<stdio.h>
#define PSQR(x) printf("The square of " #x " is %d\n", ((x)*(x)));

int main(void) {
	int x = 5;
	PSQR(x); // #x代表按变量名输出:the square of x is 5
}

3 ##运算符

#include<stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void) {
	// int x1 = 14
	int XNAME(1) = 14;
	// printf("x1 = %d", x1);
	PRINT_XN(1);
}

4 变参宏…和__VA_ARGS__

... 参数输入的字符串会在 __VA_ARGS__ 完整替换,可输入多个参数。... 参数只能代替最后的宏参数。

#define WRONG(X, ..., Y) #X #__VA_ARGS__ #y // 错误

#include<stdio.h>
#include<math.h>
#define PR(X, ...) printf("Message " #X ": "__VA_ARGS__);

int main(void) {
	double x = 5;
	double y;

	y = sqrt(x);
	// Message 1: x = 5;
	PR(1, "x = %g\n", x);

	// Message 2: x = 5.00, y = 10.0000
	PR(2, "x = %.2f, y = %.4f\n", x, y);
}

5 #include

#include<stdio.h> // <>告诉预处理器从标准系统目录中查找头文件
#include "hot.h"  // ""告诉预处理首先在当前目录或指定目录查找头文件,如果没有再从标准系统目录查找
#include "/usr/biff/p.h"

头文件引入程序编译运行注意事项:

  • 两个源代码文件 name_st.cuseheader.c 都使用了 names_st 类型结构,所以必须包含 names_st.h 头文件

  • 必须编译和链接 names_st.cuseheader.c 源代码文件

  • 声明和指令放在 names_st.h 头文件中,函数定义放在 names_st.c 源代码文件中

names_st.h
#include<string.h>
#define SLEN 32

struct names_st {
	char first[SLEN];
	char last[SLEN];
};

typedef struct names_st names;

// 函数原型声明
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);

name_st.c
#include<stdio.h>
#include "names_st.h"

// 函数定义
void get_names(names * pn) {
	printf("Please enter your first name:");
	s_gets(pn->first, SLEN);

	printf("Please enter your last name:");
	s_gets(pn->last, SLEN);
}

void show_names(const names * pn) {
	printf("%s %s", pn->first, pn->last);
}

useheader.c // 编译时链接name_st.c
#include<stdio.h>
#incluce "names_st.h"

int main(void) {
	names candidate;

	get_names(&candidate);
	show_names(&candidate);
	return 0;
}

6 #undef

指令 #undef 用于取消已定义的#define指令。如果想使用一个名称,又不确定之前是否已经用过,为安全起见,可使用#undef指令取消该名字的定义。

#define LIMIT 400
#undef LIMIT // 取消了#define的定义,可以重新定义LIMIT

7 #ifdef、#else、#endif

条件编译指令,使用这些指令告诉编译器根据编译时的条件执行或忽略信息块。

指令 #ifdef 表示是否已经定义,要与 #endif 配合使用结束指令判断。

#include<stdio.h>
#define JUST_CHECKING
#deinfe LIMIT 4

int main(void) {
	int i;
	int total = 0;
	
	for (i = 1; i <= LIMIT; i++) {
		total += 2 * i*i + 1;
// 如果JUST_CHECKING没有定义,则执行下面语句块
#ifdef JUST_CHECKING 
		printf("i=%d, running total=%d\n", i, total);
#endif
	}
	printf("Grand total = %d\n", total);

	return 0;
}

8 #ifndef

指令 #ifndef 表示是否未定义,可以和 #else#endif 配合使用。#ifndef 通常用于防止多次包含一个文件。

names.h

#ifndef NAMES_H_ // 使用#ifndef可以避免被重复预定义
#define NAMES_H_

#define SLEN 32

struct names_st {
	char first[SLEN];
	char last[SLEN];
};

typedef struct names_st names;

void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);

#endif

doubincl.c

#include<stdio.h>
#includde "names.h"
#include 'names.h" // 不小心包含了两次头文件

9 #if、#elif

// #if判断条件是否成立
#if SYS == 1
#include "libm.h"
#endif

#if SYS == 1
	#include "ibmpc.h"
#elif SYS == 2
	#include "vax.h"
#elif SYS == 3
	#include "mac.h"
#else
	#include "general.h"
#endif

10 预定义的宏

在这里插入图片描述

以上的宏都是在标准库预先定义好的,可以直接使用。

11 #line、#error

指令 #line 重置 __LINE____FILE__ 宏报告的行号和文件名。

#line 100 // 把当前行号重置为100
#line 10 "cool.c" // 把行号重置为10,把文件名重置为cool.c

指令 #error 让预处理器发出一条错误消息,该消息包含指令中的而文件。如果可能的话,编译过程应该中断。

#if __STDC__VERSION__ != 201112L
#error NOT C11
#endif

编译时会显示#error的提示

12 #pragma

指令 #pragma 可以修改编译器设置支持某些编译指令集。C99还提供了 _Pragma 预处理器运算符,该运算符把字符串转换成普通的编译指示。

#pragma c9x on // 开发C99时标准被称为C9X,让编译器支持C9X

_Pragma("nonstandardtreatmenttypeB on")
等价于下面的指令
#pragma nonstandardtreatmenttypeB on
由于该运算符不适用#符号,所以可以把它作为宏展开的一部分
#define PRAGMA(X) _Pragma(#X)
#define LIMRG(X) PRAGMA(STD CX_LIMITED_RANGE X)
然后使用类似代码:
LIMRG( ON )

定义上没问题,但实际上无法正常运行:
	#define LIMRG(X) PRAGMA(STD CX_LIMITED_RANGE #X)
问题在于这行代码依赖字符串的串联功能,而预处理过程完成之后才会串联字符串。

_Pragma运算符完成“解字符串”的工作,即把字符串中的转义序列转换成它所代表的字符,因此
_Pragma("use_bool \"true \"false")
变成了
#pragma use_bool "true "false

13 数学库

在这里插入图片描述

数学库的函数使用需要引入头文件 math.h

14 exit()和atexit()

  • atexit()

atexit() 函数需要把退出时要调用的函数地址(一般是函数名)传递给 atexit() 作为参数即可。atexit() 会注册传入的函数,当调用 exit() 时就会调用 atexit() 所注册的函数。ANSI保证,最少可以注册32个函数,执行顺序与注册顺序相反。

被注册的函数应该不带任何参数且返回类型为 void。通常,这些函数会执行一些清理任务,例如根棍监视程序的文件或重置环境变量。

注意,main()执行结束时也会隐式调用exit()。

  • exit()

exit() 可以终止程序,可传递对应的int数值表示程序执行情况,比如 exit(0) 表示成功终止。在main()以外的函数调用exit()也会终止整个程序。

15 string.h库中的memcpy()和memov()

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmov(void * s1, const void *s2, size_t n);

这两个函数都从 s2 指向的位置拷贝n字节到 s1 指向的位置,而且都返回 s1 的值。所不同的是,memcpy() 的参数带关键字 restrict,即memcpy() 假设两个内存区域之间没有重叠;而 memmov()不做这样的假设。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define SIZE 10
void show_array(const int ar[], int n);
// 如果编译器不支持C11的_Static_assert断言,可以注释掉
_Static_assert(sizeof(double) == 2 * sizeof(int), "double not twice int size");

int main() {
	int values[SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int target[SIZE];
	double curious[SIZE / 2] = {2.0, 2.0e5, 2.0e10, 2.0e20, 2.0e30};

	puts("memcpy() used:");
	puts("value (original data):");
	show_array(values, SIZE);
	// 将values数组拷贝到target数组
	memcpy(target, values, SIZE * sizeof(int)); 
	puts("target (copy of values):");
		show_array(target, SIZE);

	puts("\nUsing memmov() with overlapping ranges:");
	memmov(values + 2, values, 5 * sizeof(int));
	put("values -- elements 0-4 copied to 2-6:");
	show_array(values, SIZE);

	puts("\nUsing memcpy() to copy double to int:");
	memcpy(target, curious, (SIZE / 2) * sizeof(double));
	puts("target --5 double into 10 int positions:");
	show_array(target, SIZE / 2);
	show_array(target + 5, SIZE / 2);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值