• C复习-标准函数库:数值计算+字符串转换+日期+信号处理+locale


    参考: 里科《C和指针》


    整型函数 stdlib.h

    1)算术

    labs是对long int取绝对值

    /的运算结果是没有精确定义的,使用div好些。ldiv是用于long int,返回的是ldiv_t

    div_t x = div(30, -7);
    // quot是商,rem是余数
    printf("%d, %d", x.quot, x.rem); // -4, 2
    
    • 1
    • 2
    • 3

    2)随机数

    rand和srand可以产生伪随机数(是通过计算产生的,可能重复出现,不是真正的随机数)

    // 返回一个属于[0, RAND_MAX]的(RAND_MAX >= 32767)
    // 如果要缩小随机数的范围,可以将返回结果取模,然后通过加减偏移量来调整
    int rand( void ); 
    
    // 通过设置seed,初始化随机数发生器
    // 可以用每天的时间:srand( (unsigned int)time( 0 ) );
    void srand( unsigned int seed );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3)字符串转换

    如果string包含前导空白字符和非法后缀字符,会被跳过。

    对于strtol和strtoul,如果base=0,那么任何在程序中用于书写整数字面值的形式都能被接受(比如0x2a和0377),否则base应在2~36中。A-Z分别被解释为10-35。

    如果string中不包含一个合法的数值,返回0;如果被转换的值无法表示,函数会在errno中存储ERANGE,并返回一个值(strtol:值太大+负数,返回LONG_MIN;值太大+正数,返回LONG_MAX。strtoul:值太大,返回ULONG_MAX)。

    int atoi( char const *string ); // base=10
    long int atol( char const *string ); // long型,base=10
    // unused是指向转换值后面第一个字符位置的指针
    long int strtol( char const *string, char **unused, int base );
    unsigned long int strtoul( char const *string, char **unused, int base );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    浮点型函数 math.h

    返回值和绝大多数参数都是double

    如果一个函数的参数不在该函数的定义域内,称为定义域错误domain error。如果一个函数的结果值过大或过小,称为范围错误range error。

    浮点表示形式

    frexp将value拆成指数exponent和小数fraction,使得

    f r a c t i o n ∗ 2 e x p o n e n t = v a l u e fraction * 2^{exponent}=value fraction2exponent=value

    其中fraction∈[0.5, 1),exponent是整数。frexp返回的是fraction。将这个fraction和exponent输入ldexp就能还原value,所以如果要在浮点格式不兼容的机器间传递,应该用这两个函数。

    modf把一个浮点值分成整数和小数两部分,每个部分跟原值的符号一致。整数以double类型存储在ipart指向的位置,小数部分直接返回。

    double frexp( double value, int *exponent );
    double ldexp( double fraction, int exponent );
    
    double modf( double value, double *ipart );
    
    • 1
    • 2
    • 3
    • 4

    pow(x, y)计算的是 x y x^{y} xy,但是因为计算时用到对数,所以x不能为负

    floor和ceil返回的是整数值,但是以double形式返回,因为这个范围大。

    fabs返回绝对值。

    fmod(x, y)返回x/y的余数,但是商被限制为是整数

    字符串转换stdlib.h

    类似atoi,如果string包含前导空白字符和非法后缀字符,会被跳过。

    unused也是指向字符串中被转换的值后面的第一个字符的指针。如果unused≠NULL,就存储这个指针。

    如果这两个函数的string都没有合法数值字符,返回0;如果转换值太大或太小,函数会在errno中存储ERANGE,如果值太大,函数返回HUGE_VAL,如果太小,返回0。

    double atof( char const *string );
    double strtod( char const *string, char **unused );
    
    • 1
    • 2

    日期和时间函数time.h

    clock函数

    clock返回从程序开始执行起,处理器消耗的时间,是一个近似值,通常是处理器时钟滴答的次数,要转换成秒,需要除以常量CLOCKS_PER_SEC。

    // 如果机器无法提供处理器时间,或者时间值太大无法用clock_t表示,则返回-1
    clock_t clock(void ); 
    
    • 1
    • 2

    time函数

    time返回当天的日期和时间。如果returned_value≠NULL,也会存储返回值。如果返回无法转换为time_t,函数返回-1。标准没有规定时间的编码方式,一种常见的返回形式是返回一个时刻开始到现在的秒数,例如MS-DOS和UNIX中,计算的是1970年1月1日0:0:0到现在的秒数。time_t一般被定义为有符号的32位量。

    调用两次time再相减,以判断程序段运行时间是不好的,因为标准没有要求函数的结果用秒表示,应该用difftime。

    time_t time( time_t *returned_value);
    
    • 1

    time_t的使用

    ctime的参数time_value是指向time_t的指针,返回的是指向字符串的指针,格式是:

    Fri Nov 17 09:04:40 2023\n\0
    
    • 1

    标准没有规定存储这个字符串的内存类型,但是很多编译器使用一个静态数组存储,因此下一次调用ctime时,字符串会被覆盖。

    difftime计算time1-time2,并将结果转化为秒,返回的是double。

    char *ctime( time_t const *time_value );
    double difftime( time_t time1, time_t time2 );
    
    // 转换为世界协调时间UTC(格林尼治标准时间)
    struct tm *gmtime( time_t const *time_value );
    // 转换为当地时间
    struct tm *localtime( time_t const *time_value );
    
    // 转换结果与ctime一致,很可能ctime=asctime(localtime(time_value))
    char *asctime( struct tm const *tm_ptr );
    
    time_t cur = time(NULL);
    time_t* ptr = &cur;
    char str[100] = {};
    char* p2 = str;
    p2 = ctime(ptr);
    printf("%c", *p2);
    while (*p2++ != '\0') {
        printf("%c", *p2);
    }
    //Fri Nov 17 09:04:40 2023
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    tm的字段

    类型+名称范围说明
    int tm_sec0-61考虑闰秒
    int tm_min0-59分钟
    int tm_hour0-23
    int tm_mday1-31
    int tm_mon0-111月之后的月数
    int tm_year≥01900之后的年数
    int tm_wday0-6星期天之后的天数
    int tm_yday0-3651月1日后的天数
    int tm_isdat夏令时标志

    strftime是格式化tm,如果转换结果的长度小于maxsize,那么转换结果会被复制到string中,strftime返回字符串的长度,否则函数返回-1,且string的内容未定义。

    size_t strftime( char *string, size_t maxsize, char const *format, \
        struct tm const *tm_ptr );
    
    • 1
    • 2

    strftime格式代码

    代码含义
    %%一个%字符
    %a %A一个星期的某天,%a以当地的星期几的简写形式表示,%A是全写
    %b %B月份,%b简写,%B全写
    %c日期和时间,使用%x%X
    %d一个月的第几天0-31
    %H, %I小时,%H是(00-23),%I是(00-12)
    %J一年的第几天(001-366)
    %m月数01-12
    %M分钟00-59
    %PAM或PM
    %S秒00-61
    %U, %W一年中的第几星期(00-53),%U以星期日为第一天,%W是以星期一为第一天
    %w一星期的第几天,星期日是第0天
    %x日期,使用本地的日期格式
    %X时间,使用本地的时间格式
    %y当前的年份,后两位
    %Y年份的全写,四位
    %Z时区的缩写,没有则为空

    mktime用于把tm转会time_t

    time_t mktime( struct tm *tm_ptr );
    
    • 1

    非本地跳转setjmp.h

    setjmp和longjmp提供了一种类似goto语句的机制,如果有深层嵌套的函数调用链,一旦出错可以立刻返回顶层,而不必像每一层返回一个错误flag。

    int setjmp( jmp_buf state );
    // value必须是非0值
    void longjmp( jmp_buf state, int value );
    
    • 1
    • 2
    • 3

    实例+流程说明

    1. 先在顶层调用一次setjmp,将程序状态信息保存到跳转缓冲区,此时setjmp返回值是0
    2. 某段代码或调用的函数中调用了longjmp,其中value的值用于在顶层中区分报错
    3. 在顶层中再次调用setjmp,根据value值进入不同的流程
    4. 顶层返回后,跳转缓冲区失效,不能再调用longjmp
    #include 
    
    jmp_buf restart;
    int main()
    {
        int value;
        // 创建一个longjmp调用后执行流恢复执行的地点
        // 此时restart被初始化了,value=0
        // setjmp将程序的状态信息保存到跳转缓冲区
        // main就是我的顶层函数
        value = setjmp(restart);
        
        // 此时某段代码调用了longjmp:
        // longjmp( restart, 1 );
        
        // 这里检查setjmp的返回值,也就是longjmp的参数value的值
        switch (setjmp(restart)) {
            default:
                // longjmp被调用,致命错误
                fputs("Fatal error.\n", stderr);
                break;
            case 1:
                // longjmp被调用,小错误
                fputs("Invalid operation.\n", stderr);
                // 继续处理
                break;
            case 0:
                // 没有问题,或者没调用longjmp,那么正常处理
    						// codes
                break;
        }
        // 当顶层函数(调用setjmp的)返回时,跳转缓冲区的状态信息会失效,所以
        // 此后不能再调用longjmp
        return value == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    信号signal.h

    信号表示一种事件,可能不是程序引发的,如果程序没有安排如何处理一个特定的信号,那么程序应该有缺省反应(标准未定义,编译器一般是终止程序)。程序可以调用signal函数,或者忽略这个信号,或者设置一个信号处理函数(signal handler)。

    如果要提升可移植性,必须在需要时才使用信号,并且不违反规则。

    标准定义了一些信号,但是编译器未必全都实现,也可能扩展。

    前四个信号是同步的,是程序内部发生的,如果使用相同的数据运行,错误可以复现。SIGINT和SIGTERM是异步的,是程序外部产生的,一般是用户出发的。

    SIGINT在大多数机器中都是当用户试图中断程序时发生,一般是为其定义一个信号处理函数,以执行一些维护工作并在程序退出前保存数据。

    SIGTERM是请求终止的信号,没有信号处理函数。

    信号含义
    SIGABRT程序请求异常终止。abort函数引起
    SIGFPE发生一个算术错误。取决于编译器
    SIGILL检测到非法指令。
    SIGSEGV检测到对内存的非法访问
    SIGINT收到一个交互性注意信号
    SIGTERM收到一个终止程序的请求

    raise可以显式地引发一个信号,发的信号是同步的

    int raise( int sig );
    
    • 1

    signal函数

    void ( *signal( int sig, void ( *handler )( int ) ) )( int );
    
    • 1

    先看参数:

    // sig是上面所示的信号之一,handler是信号处理函数,是一个函数指针,它指向的函数
    // 接受一个int参数且没有返回值
    signal( int sig, void ( *handler )( int ) )
    
    • 1
    • 2
    • 3

    此时函数改成下面这样,即signal是一个函数,返回一个函数指针,这个函数接受一个int参数,没有返回值。

    void ( *signal() )( int );
    
    • 1

    事实上,signal返回一个指向该信号以前的处理函数的指针,通过保存这个值,可以为信号设置一个处理函数,并在未来恢复成先前的处理函数。如果调用signal失败(比如信号代码非法),函数将返回SIG_ERR值(定义在signal.h中)。

    signal.h还定义了SIG_DFL和SIG_IGN,都可以作为signal函数的第二个参数,前者表示恢复对该信号的缺省反应,后者使该信号被忽略。

    信号处理函数

    当一个已经设置了信号处理函数的信号发生时,系统首先恢复对该信号的缺省行为(为了避免信号处理函数内部也发生这个信号导致的无限循环,也可能“阻塞”信号),随后,信号处理函数被调用,信号代码作为参数传递给函数。

    标准表示信号处理函数可以通过调用exit终止程序。用于处理除SIGABRT外所有信号的处理函数也可以通过调用abort终止程序,但因为这两个函数是库函数,所以如果是处理异步信号(来自程序外部的)可能无法正常运行,不过最终会终止。

    信号处理函数只能访问有限的静态变量(为一个类型为volatile sig_atomic_t的静态变量赋值),为了保证安全,信号处理函数应该只做赋值然后返回,程序的剩余部分则定期检查变量的值,以确认是否有信号发生。

    信号处理函数返回后继续执行程序,但是如果信号是SIGFPE是不行的,因为计算无法完成,因此返回的效果未定义。

    如果希望捕捉将来同类型的信号,那从当前这个信号的处理函数返回前要调用signal函数重新设置信号处理函数,否则只有第一个信号会被捕捉,后面的信号会用缺省反应处理。

    sig_atomic_t定义了一种CPU可以以原子方式访问的数据类型。比如一个16位的机器可以以原子方式访问一个16位的整数,但是访问32位整数需要两个操作。在访问非原子数据的中间步骤时如果产生信号,可能导致不一致的结果,所以在信号处理函数里要限制到原子方式,不可分割。

    volatile是告诉编译器,这个变量在相邻的语句中可能变化所以不能随便优化程序。

    如果value在第二次判断前被修改了,那么输出结果可能跟编译器的优化版本不一样。声明为volatile之后就不会有这个自动优化了。

    if( value )
    		printf("True\n");
    else
    		printf("False\n");
    if( value )
    		printf("True\n");
    else
    		printf("False\n");
    
    // 编译器的自动优化
    if( value ) {
    		printf("True\n");
    		printf("True\n");
    }
    else {
    		printf("False\n");
    		printf("False\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印可变参数列表

    需要包含stdio.h和stdarg.h。这些函数与对应的标准函数相同,只是使用了可变参数列表。在调用之前,arg需要用va_start进行初始化。不需要调用va_end。

    int vprintf( char const *format, va_list arg );
    int vfprintf( FILE *stream, char const *format, va_list arg );
    int vsprintf( char *buffer, char const *format, va_list arg );
    
    • 1
    • 2
    • 3

    与执行环境交互的函数

    终止执行stdlib.h

    abort用于不正常地终止一个正在执行的程序,会引发SIGABRT信号,可以设置信号处理程序(可以不终止程序)。

    atexit可以把一些函数注册为退出函数。退出函数不接受参数,在程序正常终止或调用了exit或main函数返回时,退出函数被调用。退出函数里不能调用exit,会死循环。

    exit是正常终止程序。如果程序以main返回一个值结束,那么其效果相当于把这个值作为参数调用exit。

    当exit被调用时,所有被atexit注册的退出函数将按它们注册顺序的反序依次调用,然后所有用于流的缓冲区被刷新,所有打开的文件被关闭,用tmpfile创建的文件被删除,然后退出状态返回给宿主环境,程序停止执行。

    void abort( void );
    void atexit( void (func)( void ) );
    void exit( int status );
    
    • 1
    • 2
    • 3

    断言assert.h

    断言是声明某个东西应该会真。被执行时,测试expression,如果是假(0),就向stderr打印一条信息并终止程序;如果是真(非0),则不打印任何东西,程序继续执行。

    assert只适用于验证必须为真的表达式。

    void assert(int expression);
    
    • 1

    当程序被测试完成后,可以在编译时通过定义NDEBUG消除所有断言。可以使用-DNDEBUG编译器命令行选项,或者在assert.h被包含前增加定义:

    #define NDEBUG
    
    • 1

    NDEBUG被定义后,预处理器将丢弃所有的断言。

    获取环境变量值stdlib.h

    环境是一个由编译器定义的名字/值对列表,是os维护的。getenv可以查找一个特定的名字,找到的话返回指向其值的指针,且不能修改这个值;如果找不到,返回NULL指针。

    char *getenv( char const *name );
    
    • 1

    执行系统命令stdlib.h

    system的返回值因编译器而异。但如果command==NULL,就是询问命令处理器是否实际存在,如果存在,返回非0值,否则返回0。

    void system( char const *command );
    
    • 1

    排序和查找stdlib.h

    qsort是升序排序。base是需要排序的数组,el_size是每个元素的长度(字符),最后一个是函数指针,是用来比大小的(第1个参数大于第2个返回大于0的整数值,等于返回0,否则返回小于0的整数值)。

    void qsort( void *base, size_t n_element, size_t el_size,
    		 int (*compare)( void const *, void const * ) );
    
    /***实例****/
    typedef struct {
    	char key[10];
    	int other_data;
    } Record;
    
    /*
    * 比较函数:只比较关键字的值
    */
    int r_compare( void const *a, void const *b ) {
    	// 需要强制转换指针类型
    	return strcmp( ((Record *)a)->key, ((Record*)b)->key);
    }
    
    int main()
    {
    	Record array[50];
    	qsort( array, 50, sizeof( Record ), r_compare );
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    bsearch是在一个排好序的数组中用二分法查找元素。key是要找的值。如果找到了,返回指向那个元素的指针,找不到返回NULL。key的类型必须与数组元素的类型一致,如果数组中是struct,必须创建一个同样的struct,只填比较函数检查的字段就可以。

    void *bsearch( void const *key, void const *base, size_t n_elements, 
    		size_t el_size, int (*compare)(void const *, void const *) );
    
    // Record和r_compare如上所示
    
    int main() {
    	Record array[50];
    	Record key;
    	Record *ans;
      // 填充记录+排序
    	strcpy(key.key, "value");
      // 注意key是指针!
    	ans = bsearch(&key, array, 50, sizeof( Record ), r_compare);	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    locale

    locale是一组特定的参数,每个国家可能不相同。缺省是”C”locale,编译器也可以自定义。修改locale可能影响库函数的运行方式。

    如果参数locale==NULL,函数将返回一个指向给定类型的、当前locale的名字的指针,这个值可以保存,并在后续的setlocale中使用,以便恢复原来的locale。如果locale≠NULL,就是修改了,成功后返回新locale的值,否则返回NULL,修改无效。

    char *setlocale( int category, char const *locale );
    
    • 1

    category可选的值:

    含义
    LC_ALL整个locale
    LC_COLLATE对照序列,影响strcoll和strxfrm函数
    LC_CTYPE定义在ctype.h中的函数所使用的字符类型分类信息
    LC_MONETARY格式化货币值时使用的字符
    LC_NUMERIC格式化非货币值时使用的字符,同时修改格式化输入输出函数、字符串转换函数所使用的小数点符号
    LC_TIME影响strftime

    localeconv函数用于获得根据当前的locale对货币值和非货币值进行合适的格式化所需要的信息。只提供信息。lconv结构包含两种参数:字符和指针。如果字符参数为CHAR_MAX,表示当前不可用,如果是字符指针参数,如果指向空字符串,说明不可用。

    struct lconv *localeconv( void );
    
    • 1

    数值格式化

    字段和类型含义
    char *decimal_point用作小数点的字符,不能为空
    char *thousands_sep用作分隔小数点左边各组数字的符号
    char *grouping用来指定小数点左边多少个数字一组。CHAR_MAX表示剩余数字不分组,0表示前面的值适用于数值中剩余的各组数字
    // 典型的北美格式。1234567.89 -> 1,234,567.89
    decimal_point = "."
    thousands_sep = ","
    grouping = "\3"
    
    // grouping是从小数点往左去的,所以先分4个
    // 2125551234 -> 212-555-1234
    grouping = "\4\3"
    thousands_sep = "-"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    货币格式化

    格式化本地货币值的参数

    字段和类型含义取值举例
    char *currency_symbol本地货币符号“$”
    char *mon_decimal_point小数点字符“.”
    char *mon_thousands_sep分隔小数点左边各组数字的字符“,”
    char *mon_grouping小数点左侧分组情况“\3”
    char *positive_sign用于提示非负值的字符串“”
    char *negative_sign用于提示负值“CR”
    char frac_digits小数点右边的数字个数‘\2’
    char p_cs_precedes如果currency_symbol出现在一个非负值之前,值为1,如果出现在后面,值为0‘\1’
    char n_cs_precedes如果currency_symbol出现在一个负值之前,值为1,如果出现在后面,值为0‘\1’
    char p_sep_by_space如果currency_symbol和非负值之间用1个空格分隔,为1,否则为0‘\0’
    char n_sep_by_space如果currency_symbol和负值之间用1个空格分隔,为1,否则为0‘\0’
    char p_sign_posn提示positive_sign出现在一个非负值的位置:0是货币符号和值两边的括号;1 正号出现在货币符号和值之前;2 正号出现在货币符号和值之后;3 正号紧邻货币符号之前;4 正号紧随货币符号之后‘\1’
    char n_sign_posn提示negative_sign出现在一个负值中的位置,取值类似p_sign_posn‘\2’

    当按照国际化的用途格式化货币值时,字符串int-curr_symbol代替currency_symbol,字符int_frac_digits代替frac_digits。国际货币符号根据ISO 4217:1987标准形成,即前三个字符是字母形式的货币符号,第四个字符用于分隔符号和值。

    使用上面的参数,1234567890 → $1 234 567 890.00 -1234567890 → $1 234 567 890.00CR

    如果设置n_sign_posn=’\0’ 负值变成 ($1 234 567 890.00CR)

    一台机器的字符集的对照序列是固定的,当需要使用一个非缺省的对照序列时,需要指定。

    strcoll对两个根据当前locale的LC_COLLATE指定的字符串进行比较,返回一个大于、等于、小于0的值表示s1大于、等于、小于s2。这个比较可能比strcmp计算更多,可以使用strxfrm减少计算量。

    strxfrm把根据当前locale解释的s2转换为不依赖locale的字符串,存到s1。转换完再用strcmp比较,跟不转换直接用strcoll的效果一致。

    int strcoll( char const *s1, char const *s2 );
    size_t strxfrm( char *s1, char const *s2, size_t size );
    
    • 1
    • 2

    改变locale的影响

    1. locale可能向正在执行的程序所用的字符集增加字符,(但可能不会改变现存字符的含义) isalpha、islower、isspace和isupper可能比之前包括更多字符
    2. 正在使用的字符集的对照序列可能会改变
    3. 打印的方向可能会改变
    4. printf和scanf函数家族使用locale定义的小数点
    5. strftime所产生的日期和时间格式也会改变
  • 相关阅读:
    基于springboot实现招聘信息管理系统项目【项目源码+论文说明】计算机毕业设计
    win禁用shift切换输入法
    leetcode.622. 设计循环队列(Design Circular Queue)
    初次使用Ubuntu18.04遇到的问题——笔记4 (Ubuntu18.04+Anaconda+Pycharm+Pytorch)
    基于Openwrt系统架构,实现应用与驱动的实例。
    全网最全的知识库管理工具综合评测和推荐:FlowUs、Baklib、简道云
    jsp70077网络办公OA系统
    功率谱分析笔记-------脑电相关
    LDA(Fisher)线性判别分析
    公路施工机械技术突破和创新后的实施影响
  • 原文地址:https://blog.csdn.net/pxy7896/article/details/134467325