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

    xiaoxiao2024-12-09  64

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

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

    C入门系列:第三章 函数

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

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

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

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

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

    文章目录

    1 #define2 #运算符3 ##运算符4 变参宏...和__VA_ARGS__5 #include6 #undef7 #ifdef、#else、#endif8 #ifndef9 #if、#elif10 预定义的宏11 #line、#error12 #pragma13 数学库14 exit()和atexit()15 string.h库中的memcpy()和memov()

    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.c 和 useheader.c 都使用了 names_st 类型结构,所以必须包含 names_st.h 头文件

    必须编译和链接 names_st.c 和 useheader.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)