C day17 C库 (一)


任何语言刚出来的时候都没有配套的标准库。但是它的社区不断壮大和发展,会逐渐形成一些约定俗成的东西,然后随着语言的普及和应用范围扩大,会成立专门的标准委员会,把之前社区的一些标准统一起来,在他们的基础上建立官方的标准。

对于C语言,这个社区是UNIX的C开发者,这个官方委员会是ANSI C。

标准库里面全是函数,包括I/O函数,字符函数,字符串函数,一些数学函数,内存管理函数,等等等等。

本文我们就学习一些标准的库函数。

数学库

清一色的double返回值类型和参数类型
在这里插入图片描述
在这里插入图片描述

示例 直角坐标转极坐标

我写了一个错误的程序,即指针的陷阱,程序只是警告了input和result,po指针没有初始化,然后后面运行起来也错误退出了。

#include <stdio.h>
#include <math.h>
//atan(1)=pi/4,所以这个结果是57度左右,用于弧度制和度数的转换
#define REC_TO_DEG 180/(4*atan(1))
typedef struct polar{
    double magnitude;
    double angle;
}Polar;

typedef struct rect{
    double x;
    double y;
}Rect;

Polar *rect_to_polar(Rect *);

int main()
{
    Rect *input;
    Polar *result;

    printf("Enter the length and width of the rectangle: (enter q to quit)\n");
    while(scanf("%lf, %lf", &input->x, &input->y)==2)
    {
        result = rect_to_polar(input);
        printf("The magnitude is %.2lf\nThe angle is %.2lf\n", result->magnitude, result->angle);
        printf("Enter the length and width of the rectangle: (enter q to quit)\n");
    }
    puts("Bye!");
    return 0;
}

Polar *rect_to_polar(Rect *re)
{
    Polar *po;

    po->magnitude = sqrt(re->x * re->x + re->y * re->y);
    po->angle = atan2(re->y, re->x) * REC_TO_DEG;

    return po;
}

改为传结构体的函数,程序对了。

#include <stdio.h>
#include <math.h>
//atan(1)=pi/4,所以这个结果是57度左右,用于弧度制和度数的转换
#define REC_TO_DEG 180/(4*atan(1))
typedef struct polar{
    double magnitude;
    double angle;
}Polar;

typedef struct rect{
    double x;
    double y;
}Rect;

Polar rect_to_polar(Rect);

int main()
{
    Rect input;
    Polar result;

    printf("Enter the length and width of the rectangle: (enter q to quit)\n");
    //我竟然又一次在scanf上栽了大跟头!!scanf("%lf %lf", &input.x, &input.y)中我写的scanf("%lf, %lf", &input.x, &input.y),多个逗号!!怎么都进不去while
    while(scanf("%lf %lf", &input.x, &input.y)==2)
    {
        result = rect_to_polar(input);
        printf("The magnitude is %.2f\nThe angle is %.2f\n", result.magnitude, result.angle);
        printf("Enter the length and width of the rectangle: (enter q to quit)\n");
    }
    puts("Bye!");
    return 0;
}

Polar rect_to_polar(Rect re)
{
    Polar po;

    po.magnitude = sqrt(re.x * re.x + re.y * re.y);
    po.angle = atan2(re.y, re.x) * REC_TO_DEG;

    return po;
}
Enter the length and width of the rectangle: (enter q to quit)
10 10
The magnitude is 14.14
The angle is 45.00
Enter the length and width of the rectangle: (enter q to quit)
-12 -5
The magnitude is 13.00
The angle is -157.38
Enter the length and width of the rectangle: (enter q to quit)
q
Bye!

在while循环首部加点代码拒绝负值输入

if(input.x<0 || input.y<0)
        {puts("Please enter positive data!");
        printf("Enter the length and width of the rectangle: (enter q to quit)\n");
        continue;}
Enter the length and width of the rectangle: (enter q to quit)
-12 3
Please enter positive data!
Enter the length and width of the rectangle: (enter q to quit)
1 1
The magnitude is 1.41
The angle is 45.00

数学库函数的类型变体

可以说是很贴心了
在这里插入图片描述
示例

//generic.c
#include <stdio.h>
#include <math.h>
#define RAD_TO_DEG (180/(4*atanl(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))

int main()
{
    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);
    printf("%.17Lf\n", yy);
    printf("%.17Lf\n", yyy);

    int i = 45;
    yy = SQRT(i);//由于泛型宏没有定义Int类型,所以就用了default的double类型,
    //所以输出和double的一样
    printf("%.17Lf\n", yy);
    yyy = SIN(xxx);
    printf("%.17Lf\n", yyy);

    return 0;
}
6.70820393249936909
6.70820393249936942
6.70820393249936909
6.70820393249936942
0.70710678118654752

tgmath.h

type generic
所以是泛型类型的意思
这个头文件是用于给math.h中的所有返回值和参数类型为double的函数定义泛型类型宏,就和上面示例程序中的泛型宏一样的。

每一个math.h中的函数,在tgmath.h中都有一个同名的泛型类型宏,比如sqrt()函数就有一个sqrt()宏。
在这里插入图片描述
在这里插入图片描述
示例

#include <tgmath.h>
#include <stdio.h>

int main()
{
    float x = 44.0;
    double y;
    y = sqrt(x);//调用sqrt()宏,实际上使用sqrtf()函数
    printf("y = %.18f\n", y);
    y = (sqrt)(x);//调用sqrt()函数
    printf("y = %.18f\n", y);

    return 0;
}

按理说,第一个应该不如第二个准确呀,不知道为啥结果是一样的

y = 6.633249580710799620
y = 6.633249580710799620

通用工具库

在这里插入图片描述

exit() atexit()

这两个函数是处理程序退出的,exit()我们已经用过,它的参数是整型,0表示程序正常退出,1,2, 3等表示各种错误退出。也可以使用宏,EXIT_FAILURE,EXIT_SUCCESS等宏和使用数字0,1等的效果一样。

atexit()提供一个注册函数的功能,它并不会导致程序立刻退出,而是接受一个函数指针作为参数,比如函数名,因为函数名就是函数代码的地址,然后把该函数注册上,等到调用exit()时执行这些函数,但是执行函数的顺序是和注册顺序相反的,估计是因为注册的函数指针存储在栈里的。注意,不管是显式调用exit(自己在程序中写)还是隐式调用(main函数结束时隐式调用),注册的函数都会执行。

ANSI C规定最多可以注册32个函数。但是注册的函数必须没有参数也没有返回值

exit()函数的工作不少啊。还要执行这么多收尾清理工作。
在这里插入图片描述

Enter an integer:
a
That's no integer!
SeeSaw Software extends its heartfelt condolences
to you upon the failure of your program.
Thus terminates another magnificent program
from SeeSaw Software!
Enter an integer:
13
13 is odd.
Thus terminates another magnificent program
from SeeSaw Software!

快速排序qsort()

qsort()用于排序数组的数据对象。1962年开发的算法。是一种分治。
在这里插入图片描述
第一个参数是数组首地址,第二个参数是待排序的项数,第三个是数据类型所占用的字节数,第四个是函数指针,一般是要用到的比较函数

C允许任何类型指针被转换为void指针,也可以把void指针转换为任何类型

示例1 qsort()排序double数组

//qsort()快速排序
#include <stdio.h>
#include <stdlib.h>
#define NUM 10
int mycomp(const void *, const void *);
void fillarray(double [], int);
void showarray(const double [], int);
int main()
{
    double d[NUM];

    fillarray(d, NUM);
    puts("Random version:");
    showarray(d, NUM);
    qsort(d, NUM, sizeof(double), mycomp);//
    puts("Sorted version:");
    showarray(d, NUM);

    return 0;
}

void fillarray(double ar[], int n)
{
    int i;

    for(i = 0; i < n; i++)
    {
        ar[i] = rand() / (rand() + .1);
    }
}

void showarray(const double ar[], int n)
{
    int i;

    for(i = 0;i < n; i++)
    {
        printf("%9.4f ", ar[i]);
        if(i % 6 == 5)//每6个一行
            putchar('\n');
    }
    if(i % 6 != 0)
        putchar('\n');
}

int mycomp(const void *p1, const void *p2)
{
    const double *a1 = (const double *)p1;//把void指针强制转换为double指针
    const double *a2 = (const double *)p2;

    if(*a1 < *a2)
        return -1;
    else if(*a1 == *a2)
        return 0;
    else
        return 1;
}
Random version:
   0.0022    0.2390    1.2191    0.3910    1.1021    0.2027
   1.3835   20.2830    0.2508    0.8880
Sorted version:
   0.0022    0.2027    0.2390    0.2508    0.3910    0.8880
   1.1021    1.2191    1.3835   20.2830

其中生成随机数的代码可以用点强制转换

ar[i] = (double)rand() / ((double)rand() + .1);

注意一定要把指向void的指针强制转换为指向double的指针
把NUM改为100

Random version:
   0.0022    0.2390    1.2191    0.3910    1.1021    0.2027
   1.3835   20.2830    0.2508    0.8880    2.2179   25.4866
   0.0236    0.9308    0.9911    0.2507    1.2802    0.0939
   0.9760    1.7217    1.2054    1.0326    3.7892    1.9635
   4.1137    0.9241    0.9971    1.5582    0.8955   35.3798
   4.0579   12.0460    0.0096    1.0109    0.8506    1.1529
   2.3614    1.5876    0.4825    6.8749    1.5550    1.2070
   1.5841    0.6733    0.5295    0.5223    2.1748    0.2571
   7.7412    1.3862    0.1553    0.1564    2.2864    6.0609
   1.4149    0.1747    1.4117    1.0756    1.7194    0.3087
   1.8048    0.4916    0.9026    1.5189    0.3345   23.7805
   0.7798    4.0716    1.0303    1.5883    1.2580    2.6729
   1.6775    3.3007    1.3030    0.2574    2.1110    0.5627
   0.0276   11.3359    2.7378    1.3451    3.3237    6.8286
   0.1479    0.3337    0.8506    0.1913    1.1788    0.8033
   1.7492    0.0265    0.9456    1.8215   38.8075    0.2503
   1.0963    7.3549    2.1626    2.3978
Sorted version:
   0.0022    0.0096    0.0236    0.0265    0.0276    0.0939
   0.1479    0.1553    0.1564    0.1747    0.1913    0.2027
   0.2390    0.2503    0.2507    0.2508    0.2571    0.2574
   0.3087    0.3337    0.3345    0.3910    0.4825    0.4916
   0.5223    0.5295    0.5627    0.6733    0.7798    0.8033
   0.8506    0.8506    0.8880    0.8955    0.9026    0.9241
   0.9308    0.9456    0.9760    0.9911    0.9971    1.0109
   1.0303    1.0326    1.0756    1.0963    1.1021    1.1529
   1.1788    1.2054    1.2070    1.2191    1.2580    1.2802
   1.3030    1.3451    1.3835    1.3862    1.4117    1.4149
   1.5189    1.5550    1.5582    1.5841    1.5876    1.5883
   1.6775    1.7194    1.7217    1.7492    1.8048    1.8215
   1.9635    2.1110    2.1626    2.1748    2.2179    2.2864
   2.3614    2.3978    2.6729    2.7378    3.3007    3.3237
   3.7892    4.0579    4.0716    4.1137    6.0609    6.8286
   6.8749    7.3549    7.7412   11.3359   12.0460   20.2830
  23.7805   25.4866   35.3798   38.8075

在这里插入图片描述

这个程序要想具有通用性,即任何类型都可以用,那就必须在比较函数中把void指针强制转换为你需要的类型的指针,这个例子是double,你也可以转换为int,float,等等,所以只需要改变比较函数就可以使得qsort函数通用啦。难怪要把比较函数作为参数传进去呢。

在这里插入图片描述

即:
C里面这样就OK

const double *a1 = p1;
const double *a2 = p2;

c++必须强制转换

const double *a1 = (const double *)p1;
const double *a2 = (const double *)p2;

示例2 qsort()排序结构字符数组

用一个结构数组存储人名,包括姓和名,改变qsort使用的比较函数,来对结构数组的人名排序

写了一个小时,不断出错,有忘记写qsort调用,忘记包含stdlib头文件,fillarray函数调用忘写第二个参数,输入名字给结构体还把变量i绕了一会儿

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM 3
#define LEN 20
typedef struct names{
    char first[LEN];
    char last[LEN];
}names;
int str_comp(const void *, const void *);
void fillarray(names [], int);
void showarray(const names [], int);
char *s_gets(char *st, int n);

int main()
{
    names name[NUM];

    fillarray(name, NUM);
    puts("Random version:");
    showarray(name, NUM);
    puts("Sorted version:");
    qsort(name, NUM, sizeof(names), str_comp);
    showarray(name, NUM);

    return 0;
}

void fillarray(names name[], int n)
{
    int i = 0;

    puts("Enter the first name:");
    while(i < n && s_gets(name[i].first, LEN) != NULL)
    {
        puts("and then the last name:");
        if(s_gets(name[i].last, LEN) == NULL)
            {puts("Enter the last name again:");
            s_gets(name[i].last, LEN);}
        i++;
        if(i < n)
            puts("Enter the next first name:");
    }
}

void showarray(const names name[], int n)
{
    int i;

    for(i = 0; i < n; i++)
        printf("%20s %20s\n", name[i].first, name[i].last);
}

int str_comp(const void *p1, const void *p2)
{
    const names *a1 = (const names *)p1;
    const names *a2 = (const names *)p2;

    if(strcmp(a1->first, a2->first) < 0)
        return -1;
    else if(strcmp(a1->first, a2->first) > 0)
        return 1;
    else if(strcmp(a1->last, a2->last) < 0)
        return -1;
    else if(strcmp(a1->last, a2->last) > 0)
        return 1;
    else if(strcmp(a1->last, a2->last) == 0)
        return 0;
}

char * s_gets(char * st, int n)
{
    char * ret_val;
    int i;

    ret_val = fgets(st, n, stdin);
    if(ret_val != NULL)//等效于if(ret_val),即fgets没有遇到文件结尾啥的,正常读取了一个字符串
    {
        //逐个检查输入字符
        i=0;
        while(st[i]!='\n' && st[i]!='\0')
            i++;
        if(st[i]=='\n')
            st[i] = '\0';
        else
            while(getchar() != '\n')
                continue;

    }
    return ret_val;
}
Enter the first name:
Mary
and then the last name:
Galler
Enter the next first name:
Amy
and then the last name:
Green
Enter the next first name:
Amy
and then the last name:
White
Random version:
                Mary               Galler
                 Amy                Green
                 Amy                White
Sorted version:
                 Amy                Green
                 Amy                White
                Mary               Galler

比较函数还可以更简单:

int str_comp(const void *p1, const void *p2)
{
    const names *a1 = (const names *)p1;
    const names *a2 = (const names *)p2;

    int res;

    res = strcmp(a1->first, a2->first);
    if(res)
        return res;
    else
        return strcmp(a1->last, a2->last);
}

断言库 assert.h

这是一个用于辅助调试程序的小型库。

assert,断言,主张的意思,不要觉得这个词很生涩就以为这个东西很难,实际上简直简单死了,它很像一个if判断语句,如果条件为假就做什么什么。

运行时检查和中止程序

它不是函数,而是,它的参数是一个条件表达式或者逻辑表达式,程序运行时,如果这个表达式的结果是假,就会调用原型在stdlib.h中的abort()函数中止程序的运行,并清晰地输出程序出错的行数和文件名,如果条件为真,就什么事都不做。

#include <assert.h>
#include <math.h>
#include <stdio.h>

int main()
{
    double a, b, z;
    puts("Enter a pair of numbers:(0 0 to quit)");
    while(scanf("%lf %lf", &a, &b) == 2 && (a!=0 || b!=0))
    {
        z = a*a - b*b;
        assert(z>=0);
        printf("The square of the sum of these two numbers is: %.2f\n", sqrt(z));
        puts("Enter another pair of numbers:(0 0 to quit)");
    }
    puts("Bye!");
    return 0;
}
Enter a pair of numbers:(0 0 to quit)
5 3
The square of the sum of these two numbers is: 4.00
Enter another pair of numbers:(0 0 to quit)
10 8
The square of the sum of these two numbers is: 6.00
Enter another pair of numbers:(0 0 to quit)
3 4
Assertion failed: z>=0, file C:\Users\wulimmya\Documents\MyCCode\MyC\main.c, line 13

如果你觉得自己的程序很正确,不用担心bug,不想用assert()宏了,也不用一行行删掉相关代码,只需要在包含assert.h前面定义一个NDEBUG宏就好了

no debug,不要帮我找bug,禁用所有assert语句

所以虽然assert有一点像if语句,但是好处有两个:一是清晰定位错误行号和文件;二是方便禁用,一句宏就搞定

#define NDEBUG
#include <assert.h>
#include <math.h>
#include <stdio.h>

int main()
{
    double a, b, z;
    puts("Enter a pair of numbers:(0 0 to quit)");
    while(scanf("%lf %lf", &a, &b) == 2 && (a!=0 || b!=0))
    {
        z = a*a - b*b;
        assert(z>=0);
        printf("The square of the sum of these two numbers is: %.2f\n", sqrt(z));
        puts("Enter another pair of numbers:(0 0 to quit)");
    }
    puts("Bye!");
    return 0;
}

程序没被中止,而是输出了错误结果,负数进sqrt,得到not a number

Enter a pair of numbers:(0 0 to quit)
3 4
The square of the sum of these two numbers is: nan

_Static_assert编译时检查,阻止程序通过编译

_Static_assert()被当做声明。

_Static_assert()有两个参数,第一个也是条件表达式或逻辑表达式,但是必须是常量表达式,即不需要在运行时才能确定真假,第二个参数是字符串。
如果第一个参数为假,就输出字符串并阻止通过编译。

使用这个的好处是:可以快速查出错误,在编译时就发现问题,而assert()要到程序编译了运行了才报错,相对而言就很没有效率了。

所以对于常量表达式,我们选择用_Static_assert(),对于有变量的表达式,我们就只好用assert()了

#include <stdio.h>
#include <limits.h>
_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");//放在函数内部也可以

int main()
{
    puts("char is 16 bits.");

    return 0;
}

在这里插入图片描述

memcpy() memmove() 把数组给数组赋值

memcpy()没有重叠
memmove()有重叠

//memcpy()  memmove()
#include <stdio.h>
#include <string.h>//memcpy()  memmove()的原型
#include <stdlib.h>
#define SIZE 10
void show_array(const int ar[], int n);

_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("values (original data):");
    show_array(values, SIZE);
    memcpy(target, values, SIZE * sizeof(int));
    puts("target (copy of values)");
    show_array(target, SIZE);

    puts("\nUsing memmove() with overlapping ranges:");
    memmove(values + 2, values, 5*sizeof(int));
    puts("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 doubles into 10 int positions:");
    show_array(target + 5, SIZE / 2);

    return 0;
}

void show_array(const int ar[], int n)
{
    int i;

    for(i = 0;i < n;i++)
    {
        printf("%d ", ar[i]);
        if(i % 5 == 4)
            putchar('\n');
    }
    if(i % 5!=0)
        putchar('\n');
}
memcpy() used:
values (original data):
1 2 3 4 5
6 7 8 9 10
target (copy of values)
1 2 3 4 5
6 7 8 9 10

Using memmove() with overlapping ranges:
values -- elements 0-4 copied to 2-6:
1 2 1 2 3
4 5 8 9 10

Using memcpy() to copy double to int:
target -- 5 doubles into 10 int positions:
1108516959 2025163840 1143320349 966823146 1178156633

参数数目可变的函数

在这里插入图片描述

注意:省略号用英语句号输入,不是省略号(数字1旁边的那个点)

//参数数目可变的函数
#include <stdio.h>
#include <stdarg.h>
double sum(int, ...);

int main()
{
    double s, t;

    s = sum(3, 1.1, 2.5, 13.3);
    t = sum(6, 1.1, 2.1, 13.1, 4.2, 5.3, 6.3);
    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.2, 5.3, 6.3): %g\n", t);

    return 0;
}

double sum(int n, ...)
{
    va_list ap;//储存参数
    double tot = 0;
    int i;

    va_start(ap, n);
    //ap初始化为参数列表,va_start()宏把参数列表拷贝到va_list类型的变量ap中,
    //va_start()宏的第一个参数是va_list变量,第二个参数是parmN形参
    //parmN形参:省略号左边的倒数第一个形参,用于说明省略号中的参数个数
    for(i = 0;i < n;i++)
    {
        tot += va_arg(ap, double);//调用va_arg()宏访问参数列表
        //第一个参数是va_list变量,第二个参数是类型名
        //第一次调用返回省略号中的第一个参数,以此类推,不需要循环变量的辅助!
        //注意传入的类型名必须和宏参数的类型匹配,这里没有任何隐式类型转换
    }
    va_end(ap);//调用va_end()宏做清理工作,即释放动态分配给ap用于存储参数列表的内存,
    //只接受一个va_list变量
    //调用va_end后,必须重新va_start初始化ap,即重新动态分配内存,才可以再次使用变量ap了
    return tot;
}
return value for sum(3, 1.1, 2.5, 13.3): 16.9
return value for sum(6, 1.1, 2.1, 13.1, 4.2, 5.3, 6.3): 32.1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值