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

参考: 里科《C和指针》


整型函数 stdlib.h

1)算术

labs是对long int取绝对值。

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

c 复制代码
div_t x = div(30, -7);
// quot是商,rem是余数
printf("%d, %d", x.quot, x.rem); // -4, 2

2)随机数

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

c 复制代码
// 返回一个属于[0, RAND_MAX]的(RAND_MAX >= 32767)
// 如果要缩小随机数的范围,可以将返回结果取模,然后通过加减偏移量来调整
int rand( void ); 

// 通过设置seed,初始化随机数发生器
// 可以用每天的时间:srand( (unsigned int)time( 0 ) );
void srand( unsigned int seed );

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)。

c 复制代码
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 );

浮点型函数 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 fraction∗2exponent=value

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

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

c 复制代码
double frexp( double value, int *exponent );
double ldexp( double fraction, int exponent );

double modf( double value, double *ipart );

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。

c 复制代码
double atof( char const *string );
double strtod( char const *string, char **unused );

日期和时间函数time.h

clock函数

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

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

time函数

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

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

c 复制代码
time_t time( time_t *returned_value);

time_t的使用

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

shell 复制代码
Fri Nov 17 09:04:40 2023\n\0

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

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

c 复制代码
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

tm的字段

类型+名称 范围 说明
int tm_sec 0-61 考虑闰秒
int tm_min 0-59 分钟
int tm_hour 0-23
int tm_mday 1-31
int tm_mon 0-11 1月之后的月数
int tm_year ≥0 1900之后的年数
int tm_wday 0-6 星期天之后的天数
int tm_yday 0-365 1月1日后的天数
int tm_isdat 夏令时标志

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

c 复制代码
size_t strftime( char *string, size_t maxsize, char const *format, \
    struct tm const *tm_ptr );

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
%P AM或PM
%S 秒00-61
%U, %W 一年中的第几星期(00-53),%U以星期日为第一天,%W是以星期一为第一天
%w 一星期的第几天,星期日是第0天
%x 日期,使用本地的日期格式
%X 时间,使用本地的时间格式
%y 当前的年份,后两位
%Y 年份的全写,四位
%Z 时区的缩写,没有则为空

mktime用于把tm转会time_t

c 复制代码
time_t mktime( struct tm *tm_ptr );

非本地跳转setjmp.h

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

c 复制代码
int setjmp( jmp_buf state );
// value必须是非0值
void longjmp( jmp_buf state, int value );

实例+流程说明

  1. 先在顶层调用一次setjmp,将程序状态信息保存到跳转缓冲区,此时setjmp返回值是0
  2. 某段代码或调用的函数中调用了longjmp,其中value的值用于在顶层中区分报错
  3. 在顶层中再次调用setjmp,根据value值进入不同的流程
  4. 顶层返回后,跳转缓冲区失效,不能再调用longjmp
c 复制代码
#include <setjmp.h>

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;
}

信号signal.h

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

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

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

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

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

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

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

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

c 复制代码
int raise( int sig );

signal函数

c 复制代码
void ( *signal( int sig, void ( *handler )( int ) ) )( int );

先看参数:

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

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

c 复制代码
void ( *signal() )( int );

事实上,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之后就不会有这个自动优化了。

c 复制代码
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");
}

打印可变参数列表

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

c 复制代码
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 );

与执行环境交互的函数

终止执行stdlib.h

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

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

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

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

c 复制代码
void abort( void );
void atexit( void (func)( void ) );
void exit( int status );

断言assert.h

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

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

c 复制代码
void assert(int expression);

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

c 复制代码
#define NDEBUG

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

获取环境变量值stdlib.h

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

c 复制代码
char *getenv( char const *name );

执行系统命令stdlib.h

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

c 复制代码
void system( char const *command );

排序和查找stdlib.h

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

c 复制代码
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;
}

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

c 复制代码
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;
}

locale

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

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

c 复制代码
char *setlocale( int category, char const *locale );

category可选的值:

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

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

c 复制代码
struct lconv *localeconv( void );

数值格式化

字段和类型 含义
char *decimal_point 用作小数点的字符,不能为空
char *thousands_sep 用作分隔小数点左边各组数字的符号
char *grouping 用来指定小数点左边多少个数字一组。CHAR_MAX表示剩余数字不分组,0表示前面的值适用于数值中剩余的各组数字
c 复制代码
// 典型的北美格式。1234567.89 -> 1,234,567.89
decimal_point = "."
thousands_sep = ","
grouping = "\3"

// grouping是从小数点往左去的,所以先分4个
// 2125551234 -> 212-555-1234
grouping = "\4\3"
thousands_sep = "-"

货币格式化

格式化本地货币值的参数

字段和类型 含义 取值举例
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的效果一致。

c 复制代码
int strcoll( char const *s1, char const *s2 );
size_t strxfrm( char *s1, char const *s2, size_t size );

改变locale的影响

  1. locale可能向正在执行的程序所用的字符集增加字符,(但可能不会改变现存字符的含义) isalpha、islower、isspace和isupper可能比之前包括更多字符
  2. 正在使用的字符集的对照序列可能会改变
  3. 打印的方向可能会改变
  4. printf和scanf函数家族使用locale定义的小数点
  5. strftime所产生的日期和时间格式也会改变
相关推荐
悲伤小伞36 分钟前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
佳心饼干-4 小时前
C语言-09内存管理
c语言·算法
物联网牛七七4 小时前
4、数据结构与算法解析(C语言版)--栈
c语言·栈操作
F-2H6 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
余额不足1213810 小时前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
罗伯特祥11 小时前
C调用gnuplot绘图的方法
c语言·plot
嵌入式科普12 小时前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
lqqjuly14 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++
2401_8582861115 小时前
115.【C语言】数据结构之排序(希尔排序)
c语言·开发语言·数据结构·算法·排序算法
2401_8582861116 小时前
109.【C语言】数据结构之求二叉树的高度
c语言·开发语言·数据结构·算法