预处理器
C标准:C语言+C预处理器+C标准库
在程序执行之前查看程序
根据预处理指令把符号缩写替换成表示的内容、转换文本
包含程序所需的其他文件、选择让编译器查看哪些代码
翻译
1、把源代码中出现的字符映射到源字符集、处理多字节字符和三字符序列
2、定位 \ +Enter换行、删除换行、把物理行转换为逻辑行
printf("That's wond\
erful \n");// \ + Enter 2个物理行
printf("That's wonderful!\n"); // 一个逻辑行 可以是多个物理行
3、文本划分为预处理记号序列、空白序列、注释序列
空格替换注释、除换行符外的空白字符序列
define
预处理器指令,明示常量
#define 宏 替换体
宏:类对象宏(代表值)、类函数宏(可带参数)
宏名称中不能有空格、只能用字符数字和_、首字母不可以是数字、一般大写
替换字符串、参数列表可以有空格
圆括号括起宏的参数和替换体、可以大概率避免宏调用出错
宏展开:宏->替换文本、替换到没有宏结束
指令可以出现在源文件的任意位置、定义从指令出现到文件末尾有效
指令从#开始、到第一个换行符停止(逻辑行)
ANSI之后允许指令前中有空格、ANSI之前要求整条指令没有多余的空格
/*preproc.c--简单的预处理示例*/
#define TWO 2 // 字符常量代替数字、清楚表达数字含义
/*可以使用注释*/
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar wilde"
/*反斜杠把该定义延续到下一行、预处理开始前会把多行物理行处理为一行逻辑行、
第二行前不能有空格、要和第一行左对齐、从开始到"之间所有都算是字符串的一部分、包括空格
#define HAL "Z" :""定义字符串常量、存储在空字符'\0'结尾的数组中、预处理:字符串替换OW
#define HAL 'Z' :''定义字符常量*/
#define FOUR TWO *TWO // 宏包含宏、替换到没有宏结束、有编译器不支持嵌套
#define PX printf("X is %d. \n", x)
#define FMT "X is %d.\n" // const char *fmt = "x is %d.\n";
int x = TWO; // 预处理器找到宏实例之后替换、int x = 2;
PX; // 宏可以表示任何字符串、C表达式
x = FOUR; // x = TWO * TWO; x = 2 * 2; 预处理器只做替换、不求值
printf(FMT, x); // printf("X is %d.\n", x);
printf("%s\n", OW);
printf("TWO: OW\n"); // 双引号之间的宏不做替换 printf("%d:%s\n",TWO, OW);
#define LIMIT 20
const int LIM = 50; // C中不算常量
static int data1[LIMIT]; // 有效
//static int data2[LIM]; // 无效、非自动数组大小:整型常量表达式、枚举常量、sizeof表达式
const int LIM2 = 2 * LIMIT; // 有效
//const int LIM3 = 2 * LIM; // 无效
记号
记号:宏定义替换体中单独的词、空格分开
#define FOUR 2*2 //1个记号、记号序列:2*2
#define FOUR 2 * 2 //3个记号、记号序列:2 、*、2
记号型字符串:空格是替换体中各记号的分隔符
字符型字符串:空格是替换体的一部分
重定义常量
相同定义:替换体记号相同、顺序也相同
ANSI只有新旧定义完全相同才允许重定义
否则会出现警告或报错
#define SIX 2 * 3
#define SIX 2 * 3//相同可重定义
#define SIX 2*3//不同、3个记号和1个记号、涉及到undef
define 带参数的类函数宏
类函数宏:生成内联代码、在程序中重复替换语句、不区分变量类型、嵌套循环中使用宏有助于提高效率
函数:只有一份函数副本、调用节省空间、函数跳转恢复浪费时间
内联函数
足够多的圆括号来确保运算和结合的正确顺序
#define SQUARE(X) X *X // SQUARE:宏标识符、X:宏参数 X*X:替换列表 X可由宏调用中的符号替代
#define PR(X) printf("The result is %d. \n", X)
int x = 5;
int z;
printf("x = %d\n", x);
z = SQUARE(x);
printf("Evaluating SQUARE(x):"); // 5*5
PR(z);
z = SQUARE(2);
printf("Evaluating SQUARE(2): "); // 2*2
PR(z);
printf("Evaluating SQUARE(x+2): ");
PR(SQUARE(x + 2)); // 5+2*5+2 只做替换、不计算、可修改为#define SQUARE(x) (x)*(x)
printf("Evaluating 100/SQUARE(2): ");
PR(100 / SQUARE(2)); // 100/2*2 #define SQUARE(×)(x*×) 综合#define SQUARE(x) ((x)*(x))
printf("x is %d\n", x);
printf("Evaluating SQUARE(++x): ");
PR(SQUARE(++x)); //++x*++x 49\42 自增自减避免做宏参数
printf("After incrementing, x is %x.\n", x);
#运算符
在字符串中包含宏参数
字符串化:#x相当于"x"
#define PSQR(x) printf("The square of " #x " is %d.\n", ((x) * (x))) // #x->"x"
int y = 5;
PSQR(y); //"y"替换#x、"The square of y is %d.\n"
PSQR(2 + 4); //"2 + 4"替换#x
##运算符
##:把2个记号组成一个记号
#define XNAME(n) x##n // ##把两个记号组合成一个记号 x1
#define PRINT_XN(n) printf("x" #n " = %d\n", x##n); // #n相当于"n"、x##n相当于xn
int XNAME(1) = 14; // 变成int x1 = 14;
int XNAME(2) = 20; // 变成int x2 = 20;
int x3 = 30;
PRINT_XN(1); // 变成printf("x1 = %d\n", x1);
PRINT_XN(2); // 变成printf("x2 = %d\n", x2);
PRINT_XN(3); // 变成 printf("x3 = %d\n", x3);
变参宏 …和__VA_ARGS__
stdvar.h支持函数接收数量可变的参数
#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)
/* ...只能代替最后的宏参数、可变参数
__VA_ARGS__在替换部分代替省略号、前必须有空格、否则报错*/
double x = 48;
double y;
y = sqrt(x);
PR(1, "x = %g\n", x); // #X展开为"1"、__VA_ARGS__展开为 "x = %g\n", x printf("Message " "1" ": " "x= %g\n",x);
PR(2, "x= %.2f, y =%.4f\n", x, y); //__VA_ARGS__展开为 "x= %.2f, y =%.4f\n", x, y printf("Message " "2" ": " "x= %.2f, y =%.4f\n", x, y );
getchar();
include
把被包含文件的全部内容输入到本文件
.h:头文件、编译器创建可执行代码时需要的信息、包含:预处理指令(明示常量、宏函数)、结构声明、类型定义、函数原型,内联函数。
.c:源代码文件、可执行代码,包含头文件以使用头文件中的信息
头文件中的函数实现在name.c、函数调用在main.c、两个源代码文件需要编译和连接
#include <stdio.h>//尖括号、查找标准系统目录、部分开发环境可以配置标准系统目录
#include "hot.h"//双引号、查找当前工作目录、具体查找取决于编译器、未找到再查找标准系统目录
#include "/usr/biff/p.h"//查找/usr/biff目录
extern int status;
// 引用式声明,所有包含该头文件的文件都可使用 .c中int status;定义式声明
const static int abc = 0;
/*文件作用域,内部链接,const不会被意外修改,
static 所有包含该头文件的文件都会获得副本,再定义会报错
不需要定义式+引用式声明*/
undef
取消已定义的define指令
对于预处理器:标识符 已定义或未定义
#define 宏 的作用域,从声明处开始,到 undef或者文件结尾 结束
define在头文件时,#define的开始处取决于include指令处
//对于预处理器
#define LIMIT 1000 // LIMIT 是已定义的
#define GOOD // GOOD 是已定义的
#define A(X) ((-(X)) * (X)) // A 是已定义的
int q; // q 不是宏,因此对预处理器是未定义的
#undef GOOD // GOOD 取消定义,是未定义的,移除上面的定义,避免重复定义报错,
#undef RED // 未定义的 undef也不会出问题
条件
根据指令,执行或忽略 代码块
#ifdef (#else) #endif
判断标识符定义、否则
调试程序,根据宏选择不同的代码块
// // 示例
// #ifdef MAVIS
// #include "horse.h" //如果已经用#define定义了 MAVIS,则执行下面的指令
// #define STABLES 5
// #else
// #include "cow.h" //如果没有用#define定义MAVIS,则执行下面的指令
// #define STABLES 15
// #endif
#define JUST_CHECKING
#define LIMIT 4
int i;
int total = 0;
for (i = 1; i <= LIMIT; i++)
{
total += 2 * i * i + 1;
#ifdef JUST_CHECKING
/* 定义了执行下面,在之前没定义或者undef不会执行
调试结束后移除#define JUST_CHECKING即可去掉打印信息 */
printf("i=%d,running total =%d\n", i, total);
#endif
}
printf("Grand total=%d\n", total);
#ifndef (#else) #endif
判断标识符未定义、否则
#define SIZE 10
#ifndef SIZE
/*没有定义执行下面,定义了会跳过下面,可避免重复定义,
定义10做测试,完成后移除#define SIZE 10恢复定义100*/
#define SIZE 100
#endif
#ifndef NAMES_H
#define NAMES_H // 防止多次包含一个文件
// NAMES_H 文件内容
#endif
#if #elif
判断整型常量表达式为0假,非0真
#if defined 相当于 #ifdef
#if SYS == 1
#include "ibmpc.h"
#elif SYS == 2
#include "vax.h"
#elif SYS == 3
#include "mac.h"
#else
#include "general.h"
#endif
#if defined(IBMPC)//defined(IBMPC)定义过返回1,否则返回0
#include "ibmpc.h"
#elif defined(VAX)
#include "vax.h"
#elif defined(MAC)
#include "mac.h"
#else
#include "general.h"
#endif
预定义宏
void why_me();
// predef.c--预定义宏和预定义标识符
printf("The file is %s.\n", __FILE__);//文件
printf("The date is %s.\n", __DATE__);//预处理日期
printf("The time is %s.\n", __TIME__);//预处理时间
printf("The version is %d.\n", __STDC_VERSION__);
printf("This is line %d.\n", __LINE__);//行号
printf("This function is %s\n", __func__);//函数名,函数作用域,预定义标识符
why_me();
void why_me()
{
printf("This function is %s\n",__func__);
printf("This is line %d.\n", __LINE__);
}
#line
重置__LINE__行和__FILE__文件信息
printf("This is line %d.\nThe file is %s.\n", __LINE__, __FILE__);
#line 1000 //把当前行号重置为1000
printf("This is line %d.\nThe file is %s.\n", __LINE__,__FILE__);
#line 10 "cool.c"//把行号重置为10,把文件名重置为 cool.c
printf("This is line %d.\nThe file is %s.\n", __LINE__, __FILE__);
#error
给出错误消息
// 让预处理器发出一条错误消息,该消息包含指令中的文本。如果可能的话,编译过程应该中断
#if __STDC_VERSION__ != 201112L
#error Not C11
/* 如果编译器只支持旧标准,则会编译失败,报error为#error Not C11,
如果支持C11标准,就能成功编译。gcc -std=c11 c_study_16.c*/
#endif
#pragma
向编译器发指令
#pragma c9x on // 让编译器支持c9x
_Pragma("nonstandardtreatmenttypeB on"); // Pragma预处理器运算符,完成解字符串,相当于 #pragma nonstandardtreatmenttypeB on
_Pragma("use_bool \"true \"flase "); // 相当于#pragma use_bool "true "flase "
#define PRAGMA(X) _Pragma(#X) // _Pragma不使用 #符号,作为宏展开的一部分
#define LIMRG(X) PRAGMA(STDC CX_LIMITED_RANGE X)
LIMRG(ON);
// #define LIMRG(X) _Pragma(STDC CX_LIMITED_RANGE #X)//error 问题在于这行代码依赖字符串的串联功能,而预处理过程完成之后才会串联字符串。
泛型
没有特定类型,指定类型之后转换为指定类型的代码
泛型选择表达式,通过类型确定值
_Generic
float x;
_Generic(x, int: 0, float: 1, double: 2, default: 3);
// 表达式,类型:值,类型:值,类型:值.... 当表达式是float类型时,表达式的值即为float:1的1,没有匹配即为3
// mytype.c
#define MYTYPE(X) _Generic((X), \
int: "int", \
float: "float", \
double: "double", \
default: "other")
int d = 5;
printf("%s\n", MYTYPE(d)); // d 是int 类型
printf("%s\n", MYTYPE(2.0 * d)); // 2.0*d是double 类型
printf("%s\n", MYTYPE(3L)); // 3L 是long 类型
printf("%s\n", MYTYPE(&d)); // &d 的类型是 int *
内联函数 inline
函数调用:建立调用,传参,跳转,恢复
inline:唯一的函数说明符;把函数变成内联函数,建议尽快调用该函数;由编译器决定替换函数调用,或其他优化,或无优化
内联函数:
具有内部链接的函数,可以在多个文件定义同一个内联函数
函数定义和调用必须在同一个文件中,多个文件使用时放在头文件
定义在首次使用的文件中,相当于原型
无法获取地址,不能在调试器中显示
内容较少:太长的函数执行比调用久,没必要定义成内联
// 示例
#include <stdio.h>
inline static void eatline() // 内联函数定义/原型
{
while (getchar() != '\n')
continue;
}
int main()
{
eatline(); /* 函数调用
编译器查看内联函数定义,可能会替换为 while (getchar() != '\n') continue;*/
}
// file1.c
inline static double square(double); // inline static 而可能会优化该代码
double square(double x) { return x * x; }
int main() { double q = square(1.3); }
// file2.c
double square(double x)
{ // 普通函数定义,外部链接,文件作用域,静态存储期
return (int)(x * x); //(int)直接截断,返回值再转为double
}
void spam(double v) { double kv = square(v); }
// file3.c
inline double square(double x) // inline,可替换的外部定义
{
return (int)(x * x + 0.5);//加0.5可以在截断时获取最符合四舍五入的结果
}
void masp(double w)
{
double kw = square(w); // 可以使用本文件的内联定义和file2.c文件的外部链接定义
}
_Noreturn
C11新增第二个函数说明符
调用之后不返回主调函数,示例exit()
C库
包含对应的头文件,声明函数类型
编译或链接时,指定库
函数文档->函数头->应该包含的文件,函数返回类型,参数列表,作用
math.h
编译器没有找到数学库时
gcc rect_pol.c -lm
math.h浮点型函数接收double类型参数
float可以更快,long double损失精度
泛型,根据类型选择合适的数学函数版本
ceil 大于参数的最小整数
floor 小于参数的最大整数
round 四舍五入
#include <math.h>
#define RAD_TO_DEG (180 / (4 * atan(1)))
// 泛型平方根函数,表达式的值是函数名,带指定参数的函数的指针
#define SQRT(X) _Generic((X), \
long double: sqrtl, \
default: sqrt, \
float: sqrtf)(X)
// 泛型正弦函数,角度的单位为度,表达式的值是特定的函数调用
#define SIN(X) _Generic((X), \
long double: sinl((X) / RAD_TO_DEG), \
default: sin((X) / RAD_TO_DEG), \
float: sinf((X) / RAD_TO_DEG))
typedef struct polar_v
{
double magnitude;
double angle;
} Polar_V;//线长和角度
typedef struct rect_v
{
double x;//
double y;
} Rect_V;//坐标
Polar_V rect_to_polar(Rect_V rv);
/*rect_pol.c--把直角坐标转换为极坐标*/
Rect_V input;
Polar_V result;
puts("Enter x and y coordinates;enter q to quit:");
while (scanf("%lf %lf", &input.x, &input.y) == 2)
{
result = rect_to_polar(input);
printf("magnitude=%.2f,angle=%.2f\n",result.magnitude, result.angle);
}
puts("Bye");
// generic.c定义泛型宏
float x = 45.0f;
double xx = 45.0;
long double xxx = 45.0L;
long double y = SQRT(x);
long double yy = SQRT(xx);
long double yyy = SQRT(xxx);
printf("%.17Lf\n", y); // 匹配 float
printf("%.17Lf\n", yy); // 匹配 default
printf("%.17Lf\n", yyy); // 匹配 long double
int i = 45;
yy = SQRT(i); // 匹配 default
printf("%.17Lf\n", yyy);
yy = SQRT(i); // 匹配 long double
printf("%.17Lf\n", yy);
yyy = SIN(xxx); // 匹配 long double
printf("%.17Lf\n", yyy);
Polar_V rect_to_polar(Rect_V rv)
{
Polar_V pv;
pv.magnitude = sqrt(rv.x * rv.x + rv.y * rv.y);//线长=根号(a*a+b*b)
if (pv.magnitude == 0)
pv.angle = 0.0;
else
pv.angle = RAD_TO_DEG * atan2(rv.y, rv.x); // atan2返回弧度值,*RAD_TO_DEG转换为度
return pv;
}
tgmath.h
#include <tgmath.h> /*定义泛型类型宏
sqrtf()宏展开为sqrtf,sqrt,sqrtl 类似SQRT()*/
float x = 44.0;
double y;
y = sqrt(x); // 调用宏,所以是 sqrtf(x)
y = (sqrt)(x); // 调用函数 sqrt()
stdlib.h
rand 、srand
malloc 、free
atoi 、 atol 、 atof
strtod 、strtol
exit() atexit
#include <stdlib.h>
void sign_off(void);
void too_bad(void);
/*byebye.c-- atexit()函数指针作为参数,注册函数,退出时调用
注册的参数都会在列表中,退出之后最后添加的最先执行,最先添加的最后执行
注册的函数应该不带参数且返回void*/
int n;
atexit(sign_off); /*注册 sign off()函数 */
puts("Enter an integer:");
if (scanf("%d", &n) != 1)
{
puts("That's no integer!");
atexit(too_bad); /*注册 too bad()函数 */
exit(EXIT_FAILURE);
}
printf("%d is %s.\n", n, (n % 2 == 0) ? "even" : "odd");
/* exit()默认调用,中止整个程序,先执行atexit注册的函数,刷新输出流,关闭打开流,关闭标准函数IO创建的临时文件
控制权返回给主机,报告中止状态(EXIT_SUCCESS成功,EXIT_FAILURE失败)*/
void sign_off(void)
{
puts("Thus terminates another magnificent program from");
puts("SeeSaw Software!");
}
void too_bad(void)
{
puts("SeeSaw Software extends its heartfelt condolences");
puts("to you upon the failure of your program.");
}
qsort()
#define NUM 40
// void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
// /*快排,把数组分成两部分ab,a的值都小于b的值
// 指向数组的首元素,待排序项个数,单个元素大小,指向比较函数的指针:第一项大于第二项返回正*/
void fillarray(double ar[], int n);
void showarray(const double ar[], int n);
int mycomp(const void *pl, const void *p2);
double vals[NUM];
fillarray(vals, NUM);//随机浮点值的数组
puts("Random list:");
showarray(vals, NUM);
qsort(vals, NUM, sizeof(double), mycomp);
puts("\nSorted list:");
showarray(vals, NUM);
void fillarray(double ar[], int n)
{
int index;
for (index = 0; index < n; index++)
ar[index] = (double)rand() / ((double)rand() + 0.1);
}
void showarray(const double ar[], int n)
{//每6个一行显示
int index;
for (index = 0; index < n; index++)
{
printf("%9.4f ", ar[index]);
if (index%6 == 5)
putchar('\n');
}
if (index%6 != 0)
putchar('\n');
}
/*int (*compar)(const void *, const void *) 形式定义*/
int mycomp(const void *pl, const void *p2)
{ /*要使用指向 double 的指针来访问这两个值*/
const double *al = (const double *)pl;
const double *a2 = (const double *)p2;
if (*al < *a2)
return -1; // 从小到大的顺序排序 -1 从大到小的顺序排序 1
else if (*al == *a2)
return 0;
else
return 1;
}
// struct names
// {
// char first[40];
// char last[40];
// };
// struct names staff[100];
// qsort(staff,100,sizeof(struct names),comp);
// int comp(const void *pl,const void *p2) /* 该函数的形式必须是这样 */
// { /*得到正确类型的指针 */
// const struct names *ps1 = (const struct names *)pl;
// const struct names *ps2 = (const struct names *)p2;
// int res;
// res = strcmp(ps1->last, ps2->last); /*比较姓*/
// if (res != 0)
// return res;
// else /*如果同姓,则比较名*/
// return strcmp(ps1->first, ps2->first);
// }
assert.h
assert()
运行时检查
// #define NODEBUG //写在#include<assert.h>的前面 可以禁用文件中的assert()语句
#include <assert.h>
/*assert.c--使用assert()*/
double x, y,z;
puts("Enter a pair of numbers( 0 0 to quit):");
while (scanf("%lf%lf", &x, &y) == 2 && (x != 0 || y != 0))
{
z = x * x - y * y; /*应该用 + */
assert(z >= 0);/*整型作为参数,运行时检查
非0时在stderr中写入错误信息(显示文件名和行号),调用abort()结束程序,
相当于 if(z<0){puts("z less than 0");abort();}*/
printf("answer is %f\n", sqrt(z));
puts("Next pair of numbers:");
}
puts("Done");
_Static_assert
编译时检查
_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");
//第一个参数整型常量表达式,为0时无法编译,编译器显示第二个参数字符串
puts("char is 16 bits.");
stdarg.h
#include <stdarg.h>
double sum(int, ...);/*使用省略号的函数原型,
至少有一个形参和一个省略号,省略号必须在最后,省略号前一个形参叫parmN
void f1(int n, ...); // 有效 f1(2,200,400) 2个额外的参数
int f2(const char *s, int k, ...); // 有效 f2(4,13,117,18,23) 4个额外的参数
// char f3(char c1, ..., char c2); // 无效,省略号不在最后
// double f3(...); // 无效,没有形参
*/
// varargs.c--use variable number of arguments
double s, t;
s = sum(3, 1.1, 2.5, 13.3);
t = sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1);
printf("return value for sum(3,1.1,2.5,13.3): %g\n",s);
printf("return value for sum(6,1.1,2.1,13.1,4.1,5.1,6.1):%g\n",t);
double sum(int lim, ...)
{//va_copy
va_list ap; // 声明一个对象储存参数
double tot = 0;
int i;
va_start(ap, lim); // 把 ap 初始化为参数列表 va_start(va_list变量, parmN形参);
for (i = 0; i < lim; i++)
tot += va_arg(ap, double);
// 访问参数列表中的每一项 va_arg(va_list变量, 类型名); 参数和类型必须匹配
va_end(ap);
// 清理工作,释放内存
return tot;
}