c学习笔记 20210406 c预处理库和c库

_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

该头文件为函数提供了类似变参宏的功能。
须按如下步骤:

  1. 提供一个使用省略号的函数原型
  2. 在函数定义中创建一个va_list类型的变量
  3. 用宏把该变量初始化为一个参数列表
  4. 用宏访问参数列表
  5. 用宏完成清理工作
    这种函数原型至少有一个形参和一个省略号
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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值