_Noreturn函数(c11)
c99时, inline是唯一的函数说明符, c11新增第二个函数说明符_Noreturn, 表明调用完成后函数不返回主调函数, exit()函数是_Noreturn函数的一个示例, 该函数不会把控制返回主调函数, 告诉用户以免滥用该函数, 通知编译器可优化一些代码
C库
访问C库
其取决于实现, 需了解当前系统的 一般情况
自动访问
一些系统只需编译程序, 就可使用常见的库函数
文件包含
可以通过#include 指令包含定义宏参数的文件, 通常类似的宏都放在合适名称的头文件中
库包含
在编译或链接程序的某些阶段, 可能需要在指定库选项, 库选项告诉系统去何处查找函数代码
数学库
数学库math.h包含许多有用的数学函数
三角问题
#include <stdio.h>
#include <math.h>
#define RAD_TO_DEG (180 / (4 * atan(1)))
typedef struct polar_v {
double magnitude;
double angle;
}Polar_V;
typedef struct rect_v {
double x;
double y;
}Rect_V;
Polar_V rect_to_polar(Rect_V);
int main(void)
{
Rect_V input;
Polar_V result;
puts("Enter x and y coordinates; enter q to quit:");
while (scanf("%lf %lf", &input.x, &input.y) == 2)
{
result = rect_to_polar(input);
printf("magnitude = %0.2f, angle= %0.2f\n", result.magnitude, result.angle);
}
puts("bye.");
return 0;
}
Polar_V rect_to_polar(Rect_V rv)
{
Polar_V pv;
pv.magnitude = sqrt(rv.x * rv.x + rv.y * rv.y);
if (pv.magnitude == 0)
pv.angle = 0.0;
else
pv.angle = RAD_TO_DEG * atan2(rv.y, rv.x);
return pv;
}
如果编译时出现
Undefined: _sqrt
或
‘sqrt’: unresolved external
等其他类似的消息, 需使用-lm标记(flag)指示链接库搜索数学库
UNIX: cc rect_pol.c -lm
Linux: gcc rect_pol.c -lm
类型变体
c标准为float和long double类型提供了标准函数, 即在原函数名之后加上f或l后缀
利用c11新增的泛型表达式定义一个泛型宏, 可根据参数类型选择最合适的数学函数版本
#include <stdio.h>
#include <math.h>
#define RAD_TO_DEG (180 / (4 * atan(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(void)
{
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);
printf("%.17Lf\n", yy);
yyy = SIN(xxx);
printf("%.17Lf\n", yyy);
return 0;
}
SIN()的定义,每个带标号的值都是函数调用, 用传入SIN()的参数代替X
SQRT() 中-Generic表达式的值是一个指向函数的指针, _Generic表达式之后跟着的(X), 因此, 这是一个带指定参数的函数指针。
tgmath.h 库(c99)
c99 提供的该头文件定义了泛型类型宏, 其内部的泛型类型宏与原来double版本的函数名同名。
complex.h头文件声明了与复数运算相关的函数, 有
csqrtf(), csqrt()和 csqrtl(), 如果提供这些支持, 那么tgmath.h中的sqrt()宏也能展开为相应的复数平方根函数。
如果包含了tgmath.h库, 要调用sqrt()函数而不是宏, 则应加上圆括号
#include <tgmath.h>
...
float x = 44.0;
double y;
y = sqrt(x); // 宏
y = (sqrt) (x); // 调用函数
还可使用(*sqrt)(x) 来调用sqrt()函数。
通用工具库
通用工具库stdlib.h包含各种函数, 包括随机数生成器, 查找和排序函数, 转换函数和内存管理函数。
exit()和 atexit()函数
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
void sign_off(void);
void too_bad(void);
int main(void)
{
int n;
atexit(sign_off);
puts("Enter an integer:");
if (scanf("%d", &n) != 1)
{
puts("That's no integer!");
atexit(too_bad);
exit(EXIT_FAILURE);
}
printf("%d is %s.\n", n, (n % 2 == 0) ? "even" : "odd");
return 0;
}
void sign_off(void)
{
puts("Thus terminates another magnificent program from");
puts("SeeSaw Software!");
}
void too_bad(void)
{
puts("SeeSaw Software extends its heartfelt condolences");
puts("to you upon the failure of your program.");
}
atexit()函数用法
这个函数使用函数指针,将退出时要调用的函数地址传递给atexit()函数即可, 随后atexit()注册这些函数, 在调用exit()时执行, 其执行顺序与列表中函数顺序相反。
main()结束时会隐性调用exit().
exit()函数用法
exit()执行完atexit()后, 刷新所有输出流, 关闭所有打开的流, 关闭由标准io函数tmpfile()创建的临时文件, 向主机报告终止状态, 通常UNIX用0表成功终止, 非0值表终止失败。
ANSI C 中, 非递归的main()中使用exit等价于使用关键字return, 同时, 在main()以外使用exit()也会终止整个程序。
qsort()函数
即快速排序算法, 原型如下
void qsort(void *base, size_t nmemb, size_t size, int (*conpar)(const void *, const void *));
第一个参数是指向待排序数组的首元素的指针, 第二个参数是待排序项的数量, 第三个参数指明待排序数组中每个元素的大小, 第四个参数是一个比较函数的指针, 用于确定排序的顺序。
如果要求由小至大排序, 则比较函数应当第一项的值比第二项大时返回正数,相等返回0, 比第二项小返回负数。
#include <stdio.h>
#include <stdlib.h>
#define NUM 40
void fillarray(double ar[], int n);
void showarray(const double ar[], int n);
int mycomp(const void* p1, const void* p2);
int main(void)
{
double vals[NUM];
fillarray(vals, NUM);
puts("Random list:");
showarray(vals, NUM);
qsort(vals, NUM, sizeof(double), mycomp);
puts("\nSorted list:");
showarray(vals, NUM);
return 0;
}
void fillarray(double ar[], int n)
{
int index;
for (index = 0; index < n; index++)
{
ar[index] = (double)rand() / ((double)rand() + 0.1);
}
}
void showarray(const double ar[], int n)
{
int index;
for (index = 0; index < n; index++)
{
printf("%9.4f", ar[index]);
if (index % 6 == 5)
putchar('\n');
}
if (index % 6 != 0)
putchar('\n');
}
int mycomp(const void* p1, const void* p2)
{
const double* a1 = (const double*)p1;
const double* a2 = (const double*)p2;
if (*a1 < *a2)
return -1;
else if (*a1 == *a2)
return 0;
else
return 2;
}
mycomp()函数
因为传入的数据为指向void, 若想进行解引用并比较数值大小, 必须在比较函数内部声明两个类型正确的指针, 并初始化他们。
** c++ 要求把void类型指针付给任何类型的变量的指针时必须进行强制类型转换 **
断言库
assert.h头文件支持的断言库是一个辅助调试程序的小型库, 由assert()宏组成, 接受一个整型表达式作为参数, 求值为假(零), 就在stderr流中写入一条错误信息, 并调用abort(),通常assert()的参数是一个条件表达式或逻辑表达式, 如果assert终止了程序, 其首先会显示失败的测试, 包含测试的文件名和行号。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
int main(void)
{
double x, y, z;
puts("Enter a pair of numbers (0 0 to quit):");
while (scanf("%lf%lf", &x, &y) == 2 && (x != 0 || y != 0))
{
z = x * x - y * y;
assert(z >= 0);
printf("answer is %f\n", sqrt(z));
puts("Next pair of numbers: ");
}
puts("Done");
return 0;
}
使用assert()不仅能自动标识文件和出问题的行号, 如果认为已经排除bug, 可将该宏定义写在包含assert.h函数之前来禁用所有的assert()语句。
_Static_assert(c11)
c11新增的_Static_assert声明可以在编译时检查assert()表达式, 其第一个参数是整型常量表达式, 第二个参数是一个字符串, 如果第一个表达式求值为0(或false), 编译器显示字符串, 不编译程序。
#include <stdio.h>
#include <limits.h>
_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");
int main(void)
{
puts("char is 16 bits.");
return 0;
}
string.h 库中的memcpy()和memmove()
这两个函数用于拷贝任意类型的数组,原型如下:
void *memcpy(void *restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
memcpy假设这两个数组没有重叠,memmove不做该假设, 其拷贝过程类似于先把所有字节拷贝到一个临时缓冲区, 再拷贝至目标数组。
两函数的第三个参数是数组所占的** 字节数**。
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
void show_array(const int ar[], int n);
_Static_assert(sizeof(double) == 2 * sizeof(int), "double mot twice int size");
int main(void)
{
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, 5.0e30 };
puts("memcpy() used:");
puts("values (Original data):");
show_array(values, SIZE);
memcpy(target, values, SIZE * sizeof(int));
puts("target (copy to 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, SIZE / 2);
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]);
putchar('\n');
}
memcpy只负责原样拷贝, 不管类型转换。
可变参数: stdarg.h
该头文件为函数提供了类似变参宏的功能。
须按如下步骤:
- 提供一个使用省略号的函数原型
- 在函数定义中创建一个va_list类型的变量
- 用宏把该变量初始化为一个参数列表
- 用宏访问参数列表
- 用宏完成清理工作
这种函数原型至少有一个形参和一个省略号
void f1(int n, ...);
int f2(const char * s, int k ,...);
省略号之前的一个形参, 标准中用parmN这个术语来描述该形参,传递给该形参的实际参数应该是省略号部分代表的参数数量。
例如
f1(2, 200, 400);
f1(4, 13, 117, 18, 23);
接下来, 声明在stdarg.h中的va_list类型代表一种储存形参对应的形参列表中省略号部分的数据对象。变参函数定义起始部分:
double sum(int lim, ...)
{
va_list ap;
随后函数将使用stdarg.h 中的va_start()宏, 将参数列表拷贝到va_list类型的变量中, 该宏有两个参数:
va_list 类型变量和parmN形参
va_start(ap, lim);
下一步:访问参数列表内容, 使用另一个宏va_arg(), 该宏接受两个参数:一个va_list类型变量和一个类型名。第一次调用时返回参数列表第一项, 第二次返回第二项, 以此类推。表示类型的参数指定了返回值的类型
double tic;
int toc;
...
tic = va_arg(ap, double);
toc = va_arg(ap, int);
最后要使用va_end()宏完成清理
va_end(ap);
va_arg()不提供退回之前参数的方法, c99新增了va_copy()宏以处理该情况。接受两个va_list类型参数, 将第二个参数拷贝给第一个参数。
va_list ap;
va_list apcopy;
double
double tic;
int toc;
...
va_start(ap, lim);
va_copy(apcopy, ap);
tic = va_arg(ap, double);
toc = va_arg(ap, int);
#include <stdio.h>
#include <stdarg.h>
double sum(int, ...);
int main(void)
{
double s, t;
s = sum(3, 1.1, 2.5, 13.3);
t = sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1);
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.1, 5.1, 6.1):%g\n", t);
return 0;
}
double sum(int lim, ...)
{
va_list ap;
double tot = 0;
int i;
va_start(ap, lim);
for (i = 0; i < lim; i++)
tot += va_arg(ap, double);
va_end(ap);
return tot;
}