C入门系列:第一章 基本类型
C入门系列:第二章 字符串
C入门系列:第三章 函数
C入门系列:第四章 数组和指针
C入门系列:第五章 数据存储类别和内存管理
C入门系列:第六章 文件输入/输出
C入门系列:第七章 结构和其他数据形式
C入门系列:第八章 C预处理器和C库
... 参数输入的字符串会在 __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); }头文件引入程序编译运行注意事项:
两个源代码文件 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; }指令 #undef 用于取消已定义的#define指令。如果想使用一个名称,又不确定之前是否已经用过,为安全起见,可使用#undef指令取消该名字的定义。
#define LIMIT 400 #undef LIMIT // 取消了#define的定义,可以重新定义LIMIT条件编译指令,使用这些指令告诉编译器根据编译时的条件执行或忽略信息块。
指令 #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; }指令 #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" // 不小心包含了两次头文件以上的宏都是在标准库预先定义好的,可以直接使用。
指令 #line 重置 __LINE__ 和 __FILE__ 宏报告的行号和文件名。
#line 100 // 把当前行号重置为100 #line 10 "cool.c" // 把行号重置为10,把文件名重置为cool.c指令 #error 让预处理器发出一条错误消息,该消息包含指令中的而文件。如果可能的话,编译过程应该中断。
#if __STDC__VERSION__ != 201112L #error NOT C11 #endif 编译时会显示#error的提示指令 #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数学库的函数使用需要引入头文件 math.h
atexit() 函数需要把退出时要调用的函数地址(一般是函数名)传递给 atexit() 作为参数即可。atexit() 会注册传入的函数,当调用 exit() 时就会调用 atexit() 所注册的函数。ANSI保证,最少可以注册32个函数,执行顺序与注册顺序相反。
被注册的函数应该不带任何参数且返回类型为 void。通常,这些函数会执行一些清理任务,例如根棍监视程序的文件或重置环境变量。
注意,main()执行结束时也会隐式调用exit()。
exit()exit() 可以终止程序,可传递对应的int数值表示程序执行情况,比如 exit(0) 表示成功终止。在main()以外的函数调用exit()也会终止整个程序。
这两个函数都从 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; }