跳转至

其他库函数


stdarg.h 可变长度实参

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);

stdarg.h提供了一种工具可以让我们自行编写的函数具有可变长的参数列表(varying number of arguments of varying types)。stdarg.h定义了一种va_list类型和三种宏,名为va_start, va_arg, va_end, 可以把这些宏看成是带有上述原型的函数。

书中使用了此例进行讲解:

int max_int(int n, ...)    // n must be at least 1
{
    va_list ap;
    int i, current, largest;

    va_start(ap, n);
    largest = va_arg(ap, int);

    for (i = 1; i < n; ++i) {
        current = va_arg(ap, int);
        if (current > largest)
            largest = current;
    }

    va_end(ap);
    return largest;
}

函数的第一个实参n说明了跟随其后的其他参数的数量。

形参列表中的...符号表示可变数量的参数。带有可变参数的函数必须至少有一个“正常的”形参,在最后一个正常的参数后边始终会有省略号出现在参数列表的末尾。

va_list ap声明了一个变量,使得函数可以访问到可变参数。

va_start(ap, n)指出了实参列表中可变长度开始的位置。

va_arg(ap, int)把获取当前的可变参数,然后自动前进到下一个可变参数处。int说明希望此参数是int类型的。

函数返回前,使用语句va_end(ap)进行清扫。

调用带有可变实参列表的函数

调用带有可变实参列表的函数是一个固有的风险提议。

这里主要的难点就是带有可变实参列表的函数很难确定传递过来的参数的数量或类型。所以必须把这个信息传递给函数,并且函数假设知道了这个信息。上述max_int函数依靠第一个实参来指明跟随其后的其他参数的数量,并且它还设定参数是int类型的。

另一个问题就是不得不处理NULL作为参数的情况,具体见书本。

v...printf类函数

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);

不同于printf等函数,v...printf类函数具有固定数量的实参,每个v...printf类函数的最后一个实参都是一个va_list型值。这个类型的值意味着此函数可以由带有可变实参列表的函数调用。

实际上,v...printf类函数主要用于编写“包装”函数。包装函数接收可变数量的实参,并稍后把这些参数传递给v...printf类函数(通过va_list)。

这种包装函数的核心内容是:

  • va_start(ap, arg)

  • 把ap传递给v...printf

  • va_end(ap)

stdlib.h 通用的实用工具

字符串转换函数

int atoi(const char *nptr);
long long atoll(const char *nptr);

long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);

伪随机生成函数

伪随机数的生成方法是:

  • 先设置一个随机种子(srand)

  • 调用rand函数根据随机种子生成一个伪随机数

如果每次程序运行的随机种子都一样,那么rand出来的数就会一样。因此通常采用当前时间戳作为随机种子(但如果两次启动间隔不足一秒,时间戳也是一样滴)。

void srand(unsigned int seed);
int rand(void);

与环境的通信

与外部通信的标准库函数可以:

  • 为操作系统返回一个程序结束的状态码

  • 获取环境变量

  • 执行操作系统的命令

返回状态码

在main中执行return语句,即返回了一个状态码给操作系统;或者在程序的任意处执行exit函数,也可以终止程序并返回一个状态码给操作系统。

exit是正常性质的结束程序,可以清理程序打开的资源。

atexit函数还可以注册一个函数,在程序正常结束前,执行这个注册函数。可以注册多个atexit函数,调用顺序和注册顺序一致。

void exit(int status);
int atexit(void (*function)(void));

获取环境变量

环境变量是一组存放到静态存储区的字符串,描述了操作系统的环境,比如PATH。使用getenv就可以获取它的值。

char *getenv(const char *name);

执行命令

主要是通过system函数执行一个外部的命令。system函数返回该命令的终止状态码。

int system(const char *command);

搜索和排序工具

用于搜索的工具是:bsearch(实现为二分查找),用于排序的工具是:qsort(实现为快速排序)。

void qsort(void *base, size_t nmemb, size_t size,
                  int(*compar)(const void *, const void *));

void *bsearch(const void *key, const void *base,
                     size_t nmemb, size_t size,
                     int (*compar)(const void *, const void *));

整数算术运算函数

abs求绝对值

函数原型:

int abs(int j);

div求除法运算结果

函数原型:

div_t div(int numerator, int denominator);

结果是第一个实参除以第二个实参。结果是一个div_t类型,它包含了商和余数,定义如下:

typedef struct
{
    int quot;    /* Quotient.  */
    int rem;     /* Remainder. */
} div_t;

但第二个实参一定不能为0,不然就会出现段错误。因此判断除数是否合法的责任就交给了程序员。

time.h 日期和时间

标准库提供了三种表示时间的类型:

  1. clock_t:按照“时钟滴答”进行测量的时间值

  2. time_t:日历时间(时间戳),由于这个类型在不同平台下定义不同(unsigned int or long),因此输出的时候应当做一个强制转换。

  3. struct tm:分解时间,一种适合人类理解的时间格式

clock_t和time_t是支持算术运算的,但是它们具体是整型还是浮点型并没有被C标准说明。但struct tm的类型定义很清楚:

成员 说明
tm_sec 分后的秒,[0, 61],允许两个额外的闰秒
tm_min 时后面的分,[0, 59]
tm_hour 午夜后的时,0到23
tm_mday 月份中的第几天,[1,31]
tm_mon 一月份以后的月,[0,11]
tm_year 从1900年开始的年
tm_wday 星期日以后的天,[0,6]
tm_yday 一月一日后的天,[0,365]
tm_isdst 夏令时标记,夏令时有效为正数,否则为0,如果未知,可为-1

时钟滴答

clock_t clock(void);

clock函数返回处理器的时间(时钟滴答),即程序开始运行到执行到此的消耗的时间。但它的单位不是秒,为了将它转换成秒,可以给它除以宏CLOCK_PER_SEC。

clock_t不能表示日期,只是善于表示时间区间(两个clock_t相减获得比较精准的时间差)。

(clock() - start_clock) / (double)CLOCK_PER_SEC

加double强制转换的理由是,标准C没有指明宏CLOCK_PER_SEC的类型,也没有说明clock_t的类型,所以必须用强制转换明确一下类型。

日历时间

time_t time(time_t *t);
double difftime(time_t time1, time_t time0);

time用来获取时间戳(UNIX从1970年为纪元),difftime获取两个时间戳的间隔,但这种计算间隔的方式没有用clock_t计算精准。

分解时间

time_t mktime(struct tm *tm);
struct tm *localtime(const time_t *timep);
char *asctime(const struct tm *tm);
  • mktime将分解时间转换成日历时间(时间戳)。但它有一个很好得到地方,除了转换成日历时间,它还会先修正分解时间,如果分解时间中的某些值不正确的话。修正的规则就是“进位”,把溢出的时间补给高位的时间。比如tm_mday超过了31,那么tm_mon就会增加至少1。可以利用这一个修正的规则来计算未来的日期。见代码案例。

  • localtime根据日历时间,获得本地的分解时间

  • asctime获取分解时间的字符串格式,末尾还会有一个换行符

时间转换函数

有 ctime strftime 等。