参考: 里科《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 );
实例+流程说明
- 先在顶层调用一次setjmp,将程序状态信息保存到跳转缓冲区,此时setjmp返回值是0
- 某段代码或调用的函数中调用了longjmp,其中value的值用于在顶层中区分报错
- 在顶层中再次调用setjmp,根据value值进入不同的流程
- 顶层返回后,跳转缓冲区失效,不能再调用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的影响
- locale可能向正在执行的程序所用的字符集增加字符,(但可能不会改变现存字符的含义) isalpha、islower、isspace和isupper可能比之前包括更多字符
- 正在使用的字符集的对照序列可能会改变
- 打印的方向可能会改变
- printf和scanf函数家族使用locale定义的小数点
- strftime所产生的日期和时间格式也会改变