C-study(十六)

预处理器

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值