数据

基本数据类型

先声明再使用
声明数据实际上有两个作用

  1. 需占内存的大小, 能表示的数据范围
  2. 允许的操作种类

整数 int

修饰词

  • short (int) %hd
  • long (int) %ld %lo %lx 要把一个较小的数看做 long,在数后面加 lL(建议使用 L)
  • long long (int) %lld
  • unsigned (int) %u

可移植类型

  • <stdint.h>
  • <inttypes.h>

浮点数 float

存储中的表示

首位作为符号位,一部分作为值(尾数)部分,另一部分作为指数部分,根据指数部分的大小使小数点移动,因此称为“浮点数”

声明形式

  • 1.14
  • 5.e-2
  • .45E-6
    • 作为一个整体,不要在其中加入空格

注:NaN 为浮点数

修饰词

  • float %f ​ 后面 8 位:127+C 最后 23 位:M-1, 丢弃小数点
  • double %lf%le%la ​ a 表示十六进制的浮点数后面 11 位:1023+C 最后 52 位:M-1,丢弃小数点
  • long double %Lf (注意是大写的 L,且只有一个)

浮点数的上溢和下溢

精度不足导致的数据丢失

字符型 char

实际上的字符是以数值形式存储的,由于输出时上下文的区别会输出字符或数值

其他

布尔值 Bool

值为 true 或 false

一般表达式的真值为 int ​ 类型
<stdbool.h> 使用 bool,值为 true 和 false

复数 Complex

虚数 Imaginary

派生数据类型

指针 *

声明/初始化

int *num; //一个指向整型的指针
float *ft[10]; //一个含有10个指针的数组,每个指针指向一个float
int board[8][9];//一个二维数组
int **ptr;//一个指向指针的指针,该指针指向一个整数
int (* rusks)[10];//一个指针,指向一个有10个元素的数组
int * oof[3][4];//一个数组,每个元素都是一个指向整数的指针
int (* uuf)[3][4];//一个指向二维数组的指针
int (* uof[3])[4];//声明一个含有三个指针的数组,每个指针指向一个含有4个整数的数组

不要把一个整数(例外:0)赋值给一个指针变量
0/NULL 常用于表示一个指针变量暂未指向一个变量
指针必须指向同类型元素,但可以这样:

int a = 5;
void * p = &a;

优先级:[]​=()​>*

操作

++/–/

自身递增/递减,移向下一个元素(如果是数组),因此指针的类型和数组才需要对应,因为要移动对应的字节数

+n/-n

向后 n 个/前 n 个元素移动

指针相减

得到两个指针相距的元素个数

*

解引用,得到指针所指地址的内容,注意不要解引用没有初始化的指针!

&

取地址,得到存放指针的地址,即指向指针的指针

指向多维数组的指针

int (*pz)[2]; //pz指向一个含2个int的数组
int *px[2]; //px是一个含有两个指针的数组,指针指向int
int pc[2][3]; //pc是一个含有两个指针的数组,每个指针指向一个含有3个int的数组

数组

定义

由数据类型相同的一系列元素组成

声明/初始化

int num[42] = {[1] = 6}; //指定位置初始化,若有特定位置被初始化则其他未操作位置都会为0
char st[114] = {'s','k','a','d','i','\0'}; //若如st已创建内容,则可省略`[]`中的数值,会为数组自动分配相应的大小
char *s[10]; //一个指向数组的指针,该数组含10个元素

数组的下标从 0 开始,到 SIZE-1
数组的名称即为一个指向数组首元素的指针
可以用负数表示下标,即往回走

变长数组(VLA)

可以先获取 n(如输入),再创建数组
因为数组需要划分一个连续的空间,所以原本会在程序运行前为数组分配空间防止它扰乱进程,新版的 C 可以在运行过程中创建数组
但还是推荐使用 malloc()

int example(int n , int m , int st[n][m]); //传参数时要把n,m放前面,或者略去所有形参,用*代替[]中的参数

多维数组

传给函数时需要以 ar[][n][m] 的格式

int num[4][2]; //num是一个拥有4个元素的数组,每一个元素都是一个含有2个int的数组,即num存着4个地址

遍历数组

int main(void)
{
	int i,n=10;
	int num[10];
	for (i = 0 ; i < n ; i++)
			printf("%d ",num[i]); //使用[]来获取数组内的值(指针表示法)
	for (i = 0 ; i < n ; i++)
			printf("%d ",*(num+i)); //使用*来获取数组内的值(数组表示法)
	return 0;
}

传参时,可以传数组,也可以传入数组的首元素的指针末元素的指针

复合字面量

相当于创建一个没有名称的数据(匿名数据),可以在给函数传数据的时候用,就可以不用再声明一个数据了

(int [2]){10,20}
int *pt1;
pt1 = (int[2]){10,20};

字符串

本质

是一个 char 类型的数组,且以 '\0' 结尾,因此必须为数组多留一位来存放 '\0'

定义

字符串常量(string constant)
  • 双引号 "" 括起来的内容,也叫字符串字面量(string literal)
  • 编译器会自动加入 \0 在末尾
  • 如果字符串字面量之间没有间隔或用空白字符分割,会视为一个
char a[50] = "hello " "world";
//等价于
char b[50] = "hello world";
char 类型数组

其他未初始化的位置默认为 \0

const char m1[40] = {'h','e','l','l','o'};  //不是一个字符串,只是一个char数组
const char m2[40] = {'h','e','l','l','o','\0'}; //结尾必须有'\0'才是字符串
const char m3[40] = "hello";  //更加简便,会自动添加'\0'
指向 char 的指针
const char * pt = "hello";
//基本等价于
const char ar[] = "hello";
数组和指针的区别
  • pt 的值可以改变(指针前后移动或指向另一个字符串),但 ar 的值不可改变(不能进行自增自减,数组名不是变量,但可以进行指针加法操作)
  • 编译器使用内存中的一个副本来表示所有完全相同的字符串字面量
char * p1 = "hello";
p1[0] = "c";
printf("hello");
printf("\n %s","hello");
//理论上会输出:
//cello
//cello
//但实际上没有输出,可能是编译器区别,所以如果打算修改字符串,就不要用指针指向字符串字面量

I/O

fgets()
gets_s

与 fgets 基本相同,但是只能从 stdin 中读取,所以只需两个参数,且会舍弃换行符
gets_s(words,STLEN)

s_gets
char *s_gets(char st[],int n) //读取一行字符串
{
	char *ret_val,*find;
	ret_val = fgets(st,n,stdin);
	if (ret_val)
	{
		find = strchr(st,'\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}
scanf()
puts()
fputs()
printf()

字符串函数

strlen()

size_t strlen(const char *_Str)

获取字符串长度,不包括 \0

strcat() / strncat()
  • char *strcat(char *__restrict__ _Dest, const char *__restrict__ _Source)
  • char *strncat(char *__restrict__ _Dest, const char *__restrict__ _Source, size_t _Count)

将 Source 拼接到 Dest 上,会将 Dest 末尾的 \0 删去,Count 指定 Source 的长度
返回 Dest

strcmp() / strncmp
  • int strcmp(const char *_Str1, const char *_Str2)
  • int strncmp(const char *_Str1, const char *_Str2, size_t _MaxCount)

比较字符串在机器排序中的先后(如 AASCII 码),相等返回 0;不相等的情况下,视为 Str1-Str2,会得到正值或负值

MaxCount 表示最多会比较到的长度

strcpy / strncpy()
  • char *strcpy(char *__restrict__ _Dest, const char *__restrict__ _Source)
  • char *strncpy(char *__restrict__ _Dest, const char *__restrict__ _Source, size_t _Count)

将 Source 的内容拷贝到 Dest 中,要注意不要溢出,返回 Dest

Count 指定 Source 拷贝的长度

sprintf()

int sprintf(char *__restrict__ _Dest, const char *__restrict__ _Format, ...)

将多个元素合成一个字符串,Dest 为目标字符串,打印结果会进入 Dest 中而不是屏幕上

strchr() / strrchr()
  • char *strchr(const char *_Str, int _Val)
    • 返回字符串中首次(包含’\0)出现 Val 的地址,不存在则返回 NULL
  • char *strrchr(const char *_Str, int _Ch)
    • 返回字符串中最后一次(包含’\0’)出现 Ch 的地址,不存在则返回 NULL
strpbrk()

char *strpbrk(const char *_Str, const char *_Control)

如果 Str 中包含 Control 中的任意字符,则函数返回指向 Str 首元素的指针;不包含则返回 NULL

strstr()

char *strstr(const char *_Str, const char *_SubStr)

返回 Str 中 SubStr 出现的首位置,未出现则返回 NULL

字符串与数值转换

atoi()atof()atol()

字符串转为 int/double/long

如果字符串是以数字开头的字母数字混合字符串,则会取开头的数字直到字母

strtol()strtoul()strtod()

字符串转为 long/unsigned long/double

可以识别和报告字符串中而的首字符是否是数字

结构

创建

struct book  //struct为关键字,book为标记
{
	char title[MAX];
	char author[MAX];
	float value;
};  //不要忘了分号

struct book library;  //声明一个book类型变量,变量名为library
struct book *pt;  //声明一个指向book的指针

//如果这个结构只需用到一次,可以:
struct  //无需结构标记
{
	char title[MAX];
	char author[MAX];
	float value;
} library;

//初始化
struct book library = {
	"Golden Age",
	"Ji Ci",
	11.45
}; //可以不用换行,仅仅是为了增加可读性

//指定初始化
struct book library = {
	.value = 11.45;  //也可以单独为其中一个成员赋值,而其他成员保持空的状态
	.title = "Golden Age",
	.author = "Ji Ci"
};  //若多次对同一个成员进行赋值,最后一次会成为最终的结果

struct book library[10] = {
	[2]={
        .foclen = 500,
        .fstop = 2,
        .brand = "Remarkata"
    }
};//为一个结构数组中的其中一个元素初始化,其他初始化为0,字符串为空

访问成员

结构成员运算符:.,相当于下标的作用

library.title

结构数组

  • struct book library[NUM] ​ 生成一个 book 类型变量的数组
  • library[count].title ​ 类似格式进行访问

嵌套结构

struct city
{
	int population;
	struct book library;
};

//访问
struct city ShangHai;
printf("%s",ShangHai.library.title);

指向结构的指针

struct book * pt;
pt = &library;  //注意,与数组不同,结构名不代表其地址
printf("%f",pt -> value);  //指针使用->来访问成员
printf("%s",(*pt).title);  //先将pt解引用得到library,再使用结构的访问成员的方式来访问,注意()是必须的,"."的优先级高于"*"

向函数传递结构的信息

传递结构本身
void show(const struct book);
……
show(libaray);  //注意结构名本身不代表结构的地址
……
void show(const struct book library)
{
	printf("%s is written by %s and values %f", library.title , library.author , library.value);
}
//使用传递结构本身可能会导致程序运行速度变慢
//而且当需要改变结构所含的值时,函数需要返回结构
传递结构指针
void show(const struct book * pt);  //pt可省略
……
show(&libaray);  //注意结构名本身不代表结构的地址
……
void show(const struct book * pt)
{
	printf("%s is written by %s and values %f", pt->title , pt->author , pt->value);
}
传递结构成员

单独的将结构的成员的值提取出来向结构传递

赋值

  • 如果两个结构类型相同,则可以直接进行赋值(不是只给地址)
  • 也可以在进行初始化的时候直接赋其另一个相同类型的值

在结构内使用指针

//需要使用malloc()为指针分配空间,不能让指针空指
struct namect
{
	char * fname;
	char * lname;
};
……
struct namect * YY;
YY -> fname = (char *)malloc(strlen(temp)+1);  //sizeof(char)(=1)可以省去
strcpy(YY -> fname , temp);  //malloc()处理字符串常规操作
……
free(YY -> fname);

结构的复合字面量

reader = (struct book) {"Arknight","Ocean Cat","10.10"}

匿名结构

struct person
{
	int id;
	struct {char first[20] ; char last[20]};
};
//访问
struct ZYY;
……
puts(ZYY.first);  //可以直接访问二级成员

伸缩型数组成员

struct flex
{
	int count;
	double scores[];  //放在最后一个,作为伸缩型数组成员
}
struct flex * pt;
pt = malloc(sizeof(struct flex) + 5 * sizeof(double));
//在struct flex(不含scores)的基础上再有一个含5个元素的double数组

不应该声明 struct flex ​ 变量,而是应该声明对应的指针,再使用 malloc() ​ 为其创建空间
使用含伸缩型数组的结构时:
1)不能直接进行拷贝(直接拷贝会略去伸缩型数组而把其他的元素拷贝),如果确实要拷贝使用 memcpy()
2)不要把结构直接传递给函数,应该传递指针
3)不要使用带有伸缩型数组的结构作为数组,或者是其他结构的成员。

保存结构到文件中

fwrite(&library , sizeof(struct book) , 1 , pbooks);
虽然使用起来比较方便,但是由于各个系统(甚至同一系统不同编译器)的二进制表示法不一定相同,所以可移植性很差

联合

创建

union hold
{
	int d;
	float f;
	double lf;
};
//联合的字节数会根据包含的最大的字节数确定
//把结构看作“和”,那就可以把联合看作“或”。联合在一个时间只能是它包含数据类型的其中一个,即只能存储一个值

使用

union hold fit;
fit.d = 1;  //fit选择int类型
fit.f = 2.0; //fit选择float类型
fit.lf = 6.4; //fit选择double类型
//模拟实现一个选择
struct company……
struct person……
//比如一辆出租车,是属于个人还是公司,对于每个从属会有不同的属性,可以用union实现一个“选择”的功能
union owner
{
	struct company companyown;
	struct person personown;
};

匿名联合

struct car_data
{
	……
	union
	{
		struct company companyown;
		struct person personown;
	};
};

枚举

创建

enum spectrum{red , orange , yellow , green , blue , violet};
enum spectrum color;
相当于把这些符号常量作为标识符(但又不完全是)来使用,称为枚举符

enum 常量

会给 enum 中的枚举符逐个赋值,默认从 0 开始递增,也可以赋值

enum spectrum {red , orange = 100 , yellow , green , blue = 1000 , violet};
//red == 0 , orange == 100 , yellow == 101 , green == 102 , blue == 1000 , violet == 1001

使用

枚举类型的目的只是为了提高程序的可读性和可维护性,但枚举类型只能在内部使用,要用字符串进行比较来确定值

enum spectrum {red , orange = 100 , yellow , green , blue = 1000 , violet};
const char * colors [] = {"red","orange"……};
……
if (strcmp(choice,colors[color]) == 0)

高级数据类型

AcWing
((20231031135627-8inw3kf ‘MOOC《数据结构》’))

typedef

示例

typedef unsigned char BYTE;  //BYTE == unsigned char
typedef char * STRING;
STRING a,b; //== char *a,*b;  如果用#define STRING char *  则会变成char * a,b;
//define 是单纯的文本替换;typedef 是打包
typedef struct complex{……} COMPLEX;
COMPLEX x;

运算符

一个表达式的操作数类型可能不相同,但其结果均具有确定的数据类型和值

优先级

优先级 运算符 描述
1 [] -> . 数组下标、结构体/联合体成员选择、结构体/联合体指针的成员选择
2 ++ – - ! ~ + (type) * & sizeof 后缀/前缀增减、负号、逻辑非、按位取反、正号、类型转换、取地址、取内容、求大小
3 * / % 乘法、除法、取模
4 + - 加法、减法
5 << >> 左移、右移
6 < <= > >= 小于、小于等于、大于、大于等于
7 == != 等于、不等于
8 & 按位与
9 ^ 按位异或
10 | 按位或
11 && 逻辑与
12 || 逻辑或
13 ? : 条件运算符(三目运算符)
14 = += -= *= /= %= &= ^= = <<= >>=
15 , 逗号运算符(从左到右顺序执行)

基本运算符

+ 加

- 减/取负

= 赋值 左值 = 右值

+= -= *= /= %=

* 乘

/ 除 整数/整数仍为整数

其他运算符

sizeof()

以字节为单位返回运算对象的大小,返回类型为 size_t
输出时要用 %zd,老版本用 %u%lu

%

求模,只能用于整数
被模数的符号决定了结果的符号
a%b 可以用 a - (a/b)*b 替换

++/–

递增/递减,前缀为先加再用;后缀为先用再加

(type)

强制类型转换,注意可能会丢失精度

&

取地址运算符

*

间接运算符,即解引用,获取一个地址上的值

关系运算符

== 等于
< 小于
<= 小于等于
> 大于
>= 大于等于
!= 不等于

逻辑运算符

&& 与

当左侧判断为 false 时,不会再判断右侧

|| 或

当左侧判断为 true 时,不会在判断右侧

! 非

a 等价于 a != 0
!a 等价于 a == 0

优先级和结合律

()
- + ++ – sizeof
* / %
+ -
< <= > >=
== !=
=

I/O

转换说明(conversion specification)

%a %A 浮点数、十六进制数、p 计数法
%d %i 有符号十进制整数
%c 单个字符
%e %E 浮点数,e 计数法(区别在于 e 的大小写)
%f 浮点数(十进制)输入必须 lf 对应 double,f 对应 float;输出时可以都用 f/lf
%g %G 根据值自动选择 %f 或 %e
%o 无符号八进制整数
%x %X 无符号十六进制整数
%p 指针
%s 字符串
%u 无符号十进制整数
%% 百分号

意义

因为计算机中的数据全部都是以一系列 01 存储,不同的转换说明将取不同的字节数,并进行不同的输出,也就是所谓的“上下文”

输入

scanf()

函数原型

int scanf(const char *__restrict__ _Format, ...)
来自 <stdio.h>

输入格式

输入格式要与 "" 中填写的格式一致,例如写了”%d,%d”,输入也要是数字,数字

返回值

返回输入成功的项的个数,可以用来判断输入是否成功

转换修饰符

* 抑制赋值,会跳过对应的输入,可以用在读取文件中的特定列
数字 最大字段宽度,输入达到最大字段宽度处停止,或第一次遇到空白字符
int main(void)
{
    char c,d;
    scanf("%3c%c", &c, &d);
    printf("%c %c\n", c, d);
    return 0;
}
//abcd
//a d

特点

1)会无视空白字符,所以要记得清除

void eatline(void)
{
	while (getchar() != '\n')
		continue;
}

2)读到与要求输入类型不同的值会停止读入,这些值就会留在缓冲区,要注意清除
3)如果带多个转换说明,会在第一个出错处停止读入输入

getchar()

函数原型

int getchar(void)

作用

从缓冲区读入一个字符,返回它的字符码,需要赋值给变量
注意当 scanf()与 getchar()混用时,要注意空白符号,scanf()会跳过空白符号,但最后一次处理过后会有空白符号残留在缓冲区中,再次使用 getchar()时要注意清除

输出

printf()

函数原型

int printf(const char *__restrict__ _Format, ...)
来自 <stdio.h>

转换修饰符

标记 见下
数字 最小字宽度,若不满足会用空格填充;若超出则会相应扩充(可以认为无效)
.数字 1)%f %e:小数点后的精度,自带四舍五入 2)%g:有效数字最大位数 3)%s:待打印字符最大数量 4)整型:待打印数字最小位数
h hh %hd %hhd h 表示 short intunsigned short int;hh 表示 signed charunsigned char
j %jd 表示 intmax_ t unint mat_t
l ll L %ld %llu %Lf 表示 (unsigned) long intunsigned long long intlong double
t %td 表示 ptrdiff_t 的值,是两个指针的差值
z %zd 表示 size_t 的值,是 sizeof 的返回类型
* %*d 用程序来指定字段宽度,需要传入两个参数,前者为字段宽度,后者为输出值

标记

- 向左对齐
+ 添加正负号
空格 根据正负在数值前添加空格或负号,保持长度一致
## 转换结果形式 %#o
0 用 0 填充字段宽度

putchar()

函数原型

int putchar(int _Ch)
来自 <stdio.h>

作用

输出一个给定字符到 stdout,只能输出字符,不能输出数值或浮点数(字符形式的数字可以)

puts()

函数原型

int puts(const char *_Str)

作用

输出给定字符串,自动添加 \n


控制语句

循环

for

for (在循环前执行 ; 在每次循环前判断 ; 在循环结束后执行)
每一个 ; 内可以用 ,(逗号运算符)来执行多条语句,也可以空着

while

while () 在每次循环前若 () 中的条件为真则执行

do {} while;

先执行命令,再判断 while() 是否满足,即保证至少有一次执行
注意有 ; 结尾

选择

if/else if/else

只会执行其中一条
else 总是与前面最近的尚无 else 配对的同一个块中的 if 配对

switch

switch ()
{
	case '':
	default:
}

视作简化版的 if,速度更快,但只接受单个字符或者数字,不接受浮点数
注意:进入一个 case 后会一直向下执行,会执行到其他 case 中去以及 default,所以要加 break ​ 来停止,可以利用这个性质叠加多个 case 达到取范围的效果

?:

(statement) ? ret_val1 : ret_val2 ​ 两个返回值必须是同一类型,向高位靠齐
任何一个表达式均具有确定的数据类型和值
多个在一起无括号,则从右向左结合


函数/多文件

函数的创建及使用

void read(char st[],int n); //函数原型(function prototype),可省略形参名称 void read(char [],int);
int main(void) //无传入参数写void
{
……
	read(st,n);  //函数调用(function call)
	return 0; // 函数返回
}
void read(char st[],int n)  //函数定义(function definition)
{
……  //变量归函数私有(具体见存储类别)
}

函数指针

创建

char (* frump)(int); //指向函数的指针,这个函数的返回类型为char
char (* frump[3])(int); //一个内含3个指针的数组,每个指针都指向范围类型为char的函数

声明时,必须声明指针指向的函数类型(函数签名,即返回类型和形参类型)

void ToUpper(char *); //无返回值,参数为char *
void (*pt) (char *); //与ToUpper()对应的指针,直接把函数名称换成指针形式就好
pt = ToUpper;

作用

可以通过为函数传递指向函数的指针来控制使用什么函数

(*pt)(str);
pt(str);

两种方法都可以
1)pt 指向 ToUpper,*pt 就是 ToUpper
2)由于函数名是指针,那么指针和函数名可以互换使用,pt(str)就是 ToUpper(str)

变量的存储类别

五种存储类别

存储类别 存储期 作用域 链接 声明方式
自动 自动 块内
寄存器 自动 块内,register
静态外部链接 静态 文件 外部 所有函数外
静态内部链接 静态 文件 内部 所有函数外,static
静态无链接 静态 块内,static

自动变量

可以使用 auto 来标识(最好不要用,会和 c++ 冲突)
进入该变量声明所在的块时变量存在,在退出该块时变量消失

寄存器变量

register 请求使用寄存器来存放变量,可以使程序运行速度更快,但不能对该变量使用地址运算符(&
而且可声明为寄存器变量的数据类型有限,因为寄存器可能没有足够大的空间

静态外部链接

使用 extern 来声明

extern int a; //如果要用别的文件的变量,则必须要加extern进行声明,声明后才可使用
int b; //定义式声明,只可初始化一次,且必须在定义时初始化,默认生成的就是具有静态外部链接的变量
int c;
int main(void)
{
	extern int b; //引用式声明,可选的声明,删去也可以
	int c; //会创建一个与外部的c不同的变量c,即块作用域中的变量会隐藏文件作用域中的同名变量
……
}

静态内部链接

static int a; //只会在当前这个源文件以及头文件中生效
int main(void)

静态无链接

使用 static 提供静态存储期

void trystatic(void) //调用该函数多次
{
	int fade = 1;
	static int stay = 1;
	printf("fade = %d , stay = %d\n",fade++,stay++);
} //结果fade不断重置,但stay只会被初始化一次

存储期

静态存储器

在程序执行期间一直存在

线程存储期

从声明到线程结束时都存在

自动存储期

程序进入定义这些变量的块时,为这些变量分配内存;退出这个块时,释放这些内存

动态分配存储期

malloc()calloc() 分配内存,用 free() 释放内存

作用域

块作用域
文件作用域

链接

外部链接:多个翻译单元(同一个项目下的所有文件)全局作用域
内部链接:一个翻译单元内(一个源文件以及其他头文件)文件作用域
无链接:一个源文件内

类型限定符

恒常性 const

const int *pt1; //pt指向一个不可被改变的指,但pt可以指向他处
int * const pt2; //pt不能被改变,但可以改变pt指向的指
const int * const pt3; //pt不能被改变切pt指向一个不可被改变的值

易变性 volatile

非变量所在的程序(代理)可以改变该变量的值

volatile int looc1;
volatile int * ploc; //ploc指向一个可以被改变的值

restrict

register ​ 请求使用寄存器来存放变量,可以使程序运行速度更快,但不能对该变量使用地址运算符(&​)
而且可声明为寄存器变量的数据类型有限,因为寄存器可能没有足够大的空间

内存分配函数

malloc()

基本使用

//void *malloc(size_t _Size)  void*代表一个任意类型的指针,所以要用强制类型变换来修改
#include <stdlib.h>
double * ptd;
ptd = (double*)malloc(10 * sizeof(double)); //创建一个含有10个double的数组

多维数组

//T9.c Enter the number of how many words you want to input,and then enter your words
int main(void)
{
    int size;
    char **pt;
    char *st;
    ……
    while (scanf("%d",&size) == 1 && size > 0)
    {
        ……
        pt = (char**)malloc(size * sizeof(char*));  //多维数组的使用
        input_words(pt,size);
        show_words(pt,size);
        puts("How many words do you wish to enter ( <1 to quit): ");
    }
    return 0;
}
void input_words(char *pt[] , int size)
{
    ……
    char temp[256];
    for (i = 0 ; i < size ; i++)
    {
        scanf("%255s",temp);
        length = strlen(temp) + 1;
        pt[i] = (char*)malloc(length * sizeof(char));  //存入字符串
        if (pt[i] == NULL)
            exit(EXIT_FAILURE);
        else
            strcpy(pt[i],temp);
    }
}
void show_words(char *pt[] , int size)
{
    int i;
    for (i = 0 ; i < size ; i++)
    {
        puts(pt[i]);
        free(pt[i]);  //注意使用完毕后要解除使用,避免内存泄漏
    }
    free(pt);
    pt = NULL;  //防止空指
}

calloc()

(void*) calloc(size,sizeof())
相当于把 *​换成,

free()

释放内存,不要忘了写

多源代码文件

头文件

自行编译的头文件
把函数原型和已定义的字符常量放在头文件中
注意重复引用的设置
数组不能重复声明

#include "myhead.h"

文件

文本模式和二进制模式

所有文件的内容都以二进制形式存储,若文件使用二进制编码的字符表示文本则为文本文件;若文件中的二进制值表示机器语言代码或数值数据则为二进制文件
读取文本文件时,阅读器会将二进制进行映射,,从而不同的阅读器打开含有相同二进制值的文件会显示不同的结果

命令行参数

写法

int main(int argc , char *argv[])

使用 cmd 命令面板或者 windowshell 运行程序时,在可执行程序后面加上字符串,则这些字符串就会被读入(以空格为分界)
" " 把用空格分开的字符串包裹,则视其为一个字符串

参数

argc 代表传入字符串的个数(此处将程序名也视为一个字符串,也就是程序名算作第一个)
argv 存储每个字符串对应的地址,只要 argv[n] 就能访问

重定向

输入

./test < data.txt (windowshell)
test < data.txt (cmd)

输出

./test > data.txt (windowshell)
test > data.txt (cmd)

标准 I/O

fopen()

函数原型

FILE *fopen(const char *__restrict__ _Filename, const char *__restrict__ _Mode)
来自 <stdio.h>
需要将 fopen 返回的 FILE 指针赋给声明的 FILE 指针,方便使用

模式(Mode)字符串

“r” 只读
“w” 只写,会删去原有数据,文件不存在则新建
“a” 只写,不会删去原有数据,可在文件末尾添加内容,文件不存在则新建
“r+” 更新,可读写
“w+” 更新,可读写,会删去原有数据,文件不存在则新建
“a+” 更新,可读写,只可以从末尾添加,不会删去原有数据,文件不存在则新建
“rb” “wb” “ab” rb+” “r+b” “wb+” “w+b” “ab+” “a+b” 以二进制模式打开
“wx” “wbx” “w+x” “wb+x” “w+bx” 如果文件已经存在或者以独占模式打开文件,则返回失败(就是一定会创建新的,如果以前存在过就不会操作,防止误删)

fclose()

函数原型

int fclose(FILE *_File)
来自 <stdio.h>

作用

关闭一个已经打开的文件,若关闭成功返回 0

getc()

函数原型

int getc(FILE *_File)
来自 <stdio.h>

作用

从文件中读取单个字符

putc()

函数原型

int putc(int _Ch, FILE *_File)
来自 <stdio.h>

作用

写入一个字符到文件中

指向标准文件的指针

标准输入 stdin 键盘
标准输出 stdout 显示器
标准错误 stderr 显示器

文本文件 I/O

fprintf()

函数原型

int fprintf(FILE *__restrict__ _File, const char *__restrict__ _Format, ...)

fscanf()

函数原型

int fscanf(FILE *__restrict__ _File, const char *__restrict__ _Format, ...)

fgets()

函数原型

char *fgets(char *__restrict__ _Buf, int _MaxCount, FILE *__restrict__ _File)

作用

从文件读取一行长度最大为 MaxCount-1(需要存放 \0)的字符串(即读到 \n 停止),会读取\n,读取成功返回该字符串指针

fputs()

函数原型

int fputs(const char *__restrict__ _Str, FILE *__restrict__ _File)

作用

向文件中输入字符串,不会额外添加 \n

二进制 I/O

fread()

函数原型

size_t fread(void *__restrict__ _DstBuf, size_t _ElementSize, size_t _Count, FILE *__restrict__ _File)

作用

与 fwrite()数据流方向相反

fwrite()

函数原型

size_t fwrite(const void *__restrict__ _Str, size_t _Size, size_t _Count, FILE *__restrict__ _File)

作用

把二进制数据写入文件
返回成功写入项的数目
Str 待写入数据块地址 void 表示数据类型不定(但都是指针)
Size 待写入数据块大小(B)
Count 待写入数据块数量
File 待写入文件

char buffer[256];
fwrite(buffer, 256, 1, fp);
double earning[10];
fwrite(earning, sizeof(double), 10, fp);

随机访问

rewind()

函数原型

void rewind(FILE *_File)

作用

返回文件开始处(比如用了 fscanf())

fseek()

函数原型

int fseek(FILE *_File, long _Offset, int _Origin)

作用

可将文件看作数组,可以在 fopen()打开的文件中移动到任意位置
fseek(待查找文件 , 偏移量 , 起始点)
从起始点移动偏移量的位置
注意偏移量需要 long 类型的数据,如果输入常数需要在数值后面加 L(因为小写的 l 与 1 较难区别)
移动正常会返回 0,错误会 -1

起始点模式
SEEK_SET 文件起始
SEEK_CUR 当前位置
SEEK_END 文件末尾

ftell()

函数原型

long ftell(FILE *_File)

作用

返回参数指向文件的当前位置距离文件开始处的字节数
适用于以二进制模式打开的文件

fgetpos()

函数原型

int fgetpos(FILE *__restrict__ _File, fpos_t *__restrict__ _Pos)

作用

因为 fseek()只能获取 long 范围内的文件长度,对于超大文件不适用,所以引入 fgetpos()以及 fpos_t
调用函数时,它把 fpost 类型的值放在 pos 指向的位置上,该值描述了文件中的当前位置距离文件开头的字节数
如果成功,函数返回 0;失败返回非 0

fsetpos()

函数原型

int fsetpos(FILE *_File, const fpos_t *_Pos)

作用

调用该函数时,使用 pos 指向位置上的 fpost 类型值来设置文件指针指向偏移该值后指定的位置
成功返回 0;失败返回非 0
在调用该函数前,应该先用 fgetpos()获取 pos

其他标准 I/O

ungetc()

函数原型

int ungetc(int _Ch, FILE *_File)

作用

ch 指定的字符放回输入流中(即下一次调用输入会获取该字符)

fflush()

函数原型

int fflush(FILE *_File)

作用

引起输出缓冲区中所有未写入数据被发送到指定输出文件中(即刷新缓冲区
有时 printf()需要输出缓冲区满才会输出,导致程序运行过程中不显示
所以需要 fflush()强制刷新缓冲区,使得缓冲区中的内容输出

setvbuf()

函数原型

int setvbuf(FILE *__restrict__ _File, char *__restrict__ _Buf, int _Mode, size_t _Size)

作用

创建一个供标准 I/O 函数替换使用的缓冲区(相当于独立开辟一个新区来操作文件)
操作成功返回 0
File 指向文件
Buf 指向创建的区域,若为 NULL,则会自行分配
Mode 选择创建模式
Size 创建的缓冲区的大小,单位为 B

Mode
_IOFBF 完全缓冲(在缓冲区满时刷新)
_IOLBF 行缓冲(缓冲区满或输入换行时刷新)
_IONBF 无缓冲

feof()

函数原型

int feof(FILE *_File)

作用

检测上一次输入是否到达文件结尾,是返回非零值;否,返回 0

ferror()

函数原型

int ferror(FILE *_File)

作用

若读写出现错误,返回非零值;否,返回 0

代码示例

//T5.c change the 13.5 programme to command-line agrument method
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 4096
void append(FILE *source , FILE *dest);
char * s_gets(char st[] , int n);
int main(int argc, char * argv[])
{
    FILE *fa , *fs;
    int files = 0;
    int count = 2;
    if (argc > 2)
    {
        if ((fa = fopen(argv[1],"a+")) == NULL)
        {
            fprintf(stderr,"Can't open %s\n",argv[1]);
            exit(EXIT_FAILURE);
        }
        if ((setvbuf(fa,NULL,_IOFBF,BUFSIZE)) != 0)
        {
            fputs("Can't create output buffer\n",stderr);
            exit(EXIT_FAILURE);
        }
        while (argv[count])
        {
            if (strcmp(argv[1],argv[count]) == 0)
                fputs("Can't append file to itself\n",stderr);
            else if ((fs = fopen(argv[count],"r")) == NULL)
                fprintf(stderr,"Can't open %s\n",argv[count]);
            else
            {
                if (setvbuf(fs,NULL, _IOFBF , BUFSIZE) != 0)
                {
                    fputs("Can't create input buffer\n",stderr);
                    continue;
                }
                append(fs,fa);
                if (ferror(fs) != 0)
                    fprintf(stderr,"Error in reading file &s.\n",argv[count]);
                if (ferror(fa) != 0)
                    fprintf(stderr,"Error in writing file %s.\n",argv[1]);
                fclose(fs);
                printf("File %s appended.\n" , argv[count]);
                count++;
                files++;
                puts("Next file");
            }
        }
    }
    printf("Done appending. %d files appended.\n",argc-2);
    rewind(fa);
    printf("%s contents: \n",argv[1]);
    while ((ch = getc(fa)) != EOF)//output the source file
        putchar(ch);
    puts("Done displaying.");
    fclose(fa);
    return 0;
}
void append(FILE *source , FILE *dest)
{
    size_t bytes;
    static char temp[BUFSIZE];
    while ((bytes = fread(temp,sizeof(char),BUFSIZE,source)) > 0)
        fwrite(temp,sizeof(char),bytes,dest);
}
char * s_gets(char st[] , int n)
{
    char * ret_val , *find;
    ret_val = fgets(st,n,stdin);
    if (ret_val)
    {
        find = strchr(st,'\n');
        if (find)
            *find = '\0';
        else
            while (getchar() != '\n')
                continue;
    }
    return ret_val;
}

需要注意的是要判断对文件的各个操作是否成功


位操作

进制

二进制


字节
八位组(8B)
高价位(7) 低阶位(0)
从低字节到高字节为 小端顺序​,相反则为 大端顺序
image

有符号整数

符号量:最高位代表符号
二进制原码
二进制反码
二进制补码

八进制

0(零) %o%#o​(显示前缀)

十进制

%d

十六进制

0x/0X %x%#x​/%#X ​(显示前缀)

C 按位运算符

按位取反~

~(11010010)
->00101101

按位与&

(10010011) & (00111101)
->00010001

掩码

MASK 0000,0011
FLAG 1001,0110
OUT 0000,0010

FLAG & MASK
MASK 对应为 1 的位,FALG 会出原本的值

关闭位(清空位)

MASK 0000,1111
FLAG 1011,0110
OUT 0000,1001

FLAG & ~(MASK)

检查位

if ((flag & mask) == mask)
只检查 mask 为 1 的位置

按位或 |

(10010011) | (00111101)
->10111111

打开位(设置位)

MASK 0000,1111
FLAG 1011,0110
OUT 1011,1111
FLAG MASK

按位异或 ^

(10010011)^(00111101)
->10101110

切换位

MASK 0000,1111
FLAG 1011,0110
OUT 1011,1001

FLAG ^ MASK
切换 MASK 对应为 1 的位

选择性切换
//切换x的后i位,可以通过if来切换指定的位置
int invert_end(int num, int bits)
{
    int x = 0;
    int bitval = 1;
    int n,i;
    scanf("%d",&n);
    for (i = 0 ; i < n ; i++)
    {
        x ^= bitval;
        bitval <<= 1;
    }
    return x;
}

移位

左移

用 0 填充空位,本身不变(需要赋值)
相当于*2(不溢出的情况下)

右移

注意操作时将至转换成 unsigned
对于 无符号类型​,用 0 填充
对于 有符号类型​,结果取决于机器,全都用 0 填充为 逻辑位移​;用符号位填充为 算术位移
相当于/2(非负且不溢出的情况下)

例: 从整数 x 的二进制表示中取出从第 p 位(最右边为第 0 位)开始的 n 位

(x>>(p+1-n)) & ~(~0<<n)
~ > <</>> > & > ^ > |

位字段

创建

struct {
	unsigned int a : 1;
	unsigned int b : 2;
	unsigned int c : 4;
	unsigned int d : 8;
} p;

通过 p.a p.b ​ 访问或赋值
要确保所赋的值不超过字段可容纳的范围
如果声明的总位数超过一个 unsigned int​(32B)会用到下一个 unsigned int ​ 的位置,但一个字段不允许跨越边界
所以要填充这样的“洞”

struct {
	unsigned int a : 1;
	unsigned int   : 2;
……
}

实例

#include <stdio.h>
#include <stdbool.h>
//style of lines
#define SOLID 0
#define DOTTED 1
#define DASHED 2
//三原色
#define BLUE 4
#define GREEN 2
#define RED 1
//mixed colors
#define BLACK 0
#define YELLOW (RED | GREEN)
#define MAGENTA (RED | BLUE)
#define CYAN (GREEN | BLUE)
#define WHITE (RED | GREEN | BLUE)
const char * colors[8] = {"black","red","green","yellow","blue","magenta","cyan","white"};
struct box_props
{
    bool opague : 1;
    unsigned int fill_color : 3;
    unsigned int : 4;
    bool show_border : 1;
    unsigned int border_color : 3;
    unsigned int border_style : 2;
    unsigned int : 2;
};
void show_setting(const struct box_props * pb);
int main(void)
{
    struct box_props box = {true , YELLOW , true , GREEN , DASHED};
    printf("Original box setting:\n");
    show_setting(&box);
    box.opague = false;
    box.fill_color = WHITE;
    box.border_color = MAGENTA;
    box.border_style = SOLID;
    printf("\nModified box setting:\n");
    show_setting(&box);
    return 0;
}
void show_setting(const struct box_props * pb)
{
    printf("Box is %s.\n",(pb -> opague == true)?"opague":"transparent");
    printf("The fill color is %s.\n",colors[pb -> fill_color]);
    printf("Border %s'\n",(pb -> show_border == true)?"shown":"not shown");
    printf("The border color is  %s.\n",colors[pb -> border_color]);
    printf("The border style is ");
    switch (pb -> border_style)
    {
        case SOLID: printf("solid.\n"); break;
        case DOTTED: printf("dotted.\n"); break;
        case DASHED: printf("dashed.\n"); break;
        default: printf("unknown type.\n"); break;
    }
}

预处理

翻译程序

映射字符

编译器把源代码中出现的字符映射到原字符集,该过程处理 多字节字符 ​ 和 三字节字符

删去实际换行

定位并删去实际输入的换行转换成一个逻辑行

划分文本

把文本划分成预处理记号序列、空白序列、注释序列,编译器会用空格代替每条注释

预处理

查找#号并进行

宏定义 #define

预处理器只会处理一行,所以指令的长度仅限一行

定义明示常量

#define a 10​​

重定义常量

有需求用 const 吧
const 会创建一个占据数据空间的变量
#define 不会占据空间,只是单纯的文本替换

#define S 2 * 3
#define S 3 * 3 //可行
#define S 2*3 //不可行,需要用#undef取消S的定义

a ​ 被称为 ​,代表值的宏称为 类对象宏​,代表函数的宏称为 类函数宏
10 ​ 所在部分称为 替换列表 ​ 或 替换体
从宏变为最终替换文本的过程称为 宏展开​​
宏不会占用内存(因为是文本的替换)

替换表达式

注意,预处理器不做计算,不对表达式求值,只进行替换

宏参数

为了避免替换后导致的运算顺序问题,建议用括号括上每个变量以及整个表达式
((X) * (X))

用宏参数创建字符串
#define PSQR(X) printf("The square of X is %d.\n",((X)*(X)));
//若X=8,输出为:The square of X is 64
#define PSQR(X) printf("The square of " #X " is %d.\n",((X)*(X))); //会进行拼接
//若X=8,输出为:The square of 8 is 64 ,字符串化
预处理黏合剂

## ​ 拥有强大的替换能力

#include <stdio.h>
#define XNAME(n) x ## n //若n=4,则宏展开结果为"x4"
#define PRINT_XN(n) printf("x" #n " = %d \n",x ## n)
int main(void)
{
    int XNAME(1) = 14; //become int x1 = 14;
    int XNAME(2) = 20; //become int x2 = 20;
    int x3 = 30;
    PRINT_XN(1);  //become printf("x1 = %d\n",x1);
    PRINT_XN(2);
    PRINT_XN(3);
    return 0;
}
/*
x1 = 14
x2 = 20
x3 = 30
*/

记号

可以把宏的替换体看作是 记号型字符串 ​ 而不是 字符型字符串
编译器会把 2*2 ​ 看成一个记号
2 * 2 ​ 看成 3 个记号
很复杂,别管了

变参宏

通过把宏参数列表中最后的参数写成省略号(即三个点 ...​),这样预定义宏 __VA_ARGS__ ​ 可用在替换部分中

#define PR(...) printf(__VA_ARGS__)
PR("A");
PR("B","C","D");
/*
ABCD
*/

注意省略号只能代替最后的宏参数

换行写法

#define OW "jjjjjjjj\  //在末尾加反斜杠,且第二行要与第一行左对齐(就是第二行顶格写)
iki"

取消宏定义#undef

#define LIMIT 42
#undef LIMIT

将取消 LIMIT ​ 的定义,使它能被重新定义为新值。
即使原来没有定义 LIMIT ​ 也可以取消定义,如果想使用一个名称,又不确定之前是否已经用过,为安全起见可以用 #undef ​ 来取消改名字的定义

定义的概念

#define LIMIT 42 //defined
#define GOOD //defined
int a; //undefined
#undef GOOD //undefined

宏的作用域从它在文件中的声明处开始,直到用 #undef ​ 取消宏或者文件结束,若宏通过头文件引入则开始位置取决于 #include ​ 的位置

条件编译

#ifdef #else #endif

#ifdef MAVIS //如果MAVIS已经被定义,则执行#else或#endif之前的所有指令(先出现哪个就停在哪里)
	#include "horse.h"
	#define STABLES 5
#else  //如果MAVIS未被定义,则执行#else和#endif之间的所有指令(可以不存在)
	#include "cow.h"
	#define STABLES 15
#endif //必须存在

可以不仅仅是写在程序开头,可以用在程序中间部分,方便调试程序

#ifdef CHECKING
	printf("aaa");
#endif

#ifndef

#ifndef CHECK
#define CHECK
//other things
#endif

#if #elif

#if SYS == 1   //#if后面跟整型常量表达式
#include "head.h"
#elif SYS == 2
#include "otherhead.h"
#else
#include "morehead.h"
#endif

defined

#if defined (YMCA)
	#include "YMCA.h"
......
#endif

文件包含 #include

#include <stdio.h>
#include "myhead.h" //优先查找当前文件夹
#include "/tools/C/Test_C/head.h" //从制定文件夹中查找

通常的习惯

可执行代码放在源代码文件中
头文件存放函数原型、常量、结构声明、类型定义

预定义宏

含义
__DATA__ 预处理日期(”Mmm dd yyyy”形式的字符串字面量,如 Oct 26 2023)
__FILE__ 表示当前源代码文件名的字符串字面量
__LINE__ 表示当前源代码文件中行号的整型常量
__STDC__ 设置为 1 时,表示实现遵循 C 标准
__STDC_HOSTED__ 本机环境设置为 1;否为 0
__STDC_VERSION__ 支持 C99 标准则设置为 199901L;支持 C11 标准,设置为 201112L
__TIME__ 翻译代码的时间(”hh:mm:ss”)
#include <stdio.h>
void why_me(void);
int main(void)
{
    printf("The file is %s\n",__FILE__);
    printf("The date is %s\n",__DATE__);
    printf("The time is %s\n",__TIME__);
    printf("The version is %ld\n",__STDC_VERSION__);
    printf("This is line %d\n",__LINE__);
    printf("This fucction is %s\n",__func__);
    printf("\n");
    why_me();
    return 0;
}
void why_me(void)
{
    printf("This function is %s\n",__func__);
    printf("This is line %d\n",__LINE__);
}
//output
The file is test.c
The date is Oct 26 2023
The time is 10:48:44
The version is 201710
This is line 10
This fucction is main
This function is why_me
This is line 21

其他指令

#line ​ 和 #error

#line ​ 重置 __LINE__ ​ 和 __FILE__ ​ 宏报告的行号和文件名

#line 1000
#line 10 "cool.c"

#error ​ 让预处理器发出一条错误信息,该消息包含指令中的文本

#if __STDC_VERSION__ != 201112L
#error Not C11
#endif

#pragma

#pragma ​ 把编译器指令放入源代码中,例如:

#pragma c9x on

用处不大

泛型选择

指没有特定类型,但是一旦指定一种类型就可以转换成指定类型的代码

_Generic(x , int: 0, float: 1, double: 2, default: 3)
//x匹配哪一项整个泛型选择表达式就是哪一个值。注以下 \ 为换行标识符
#define MYTYPE(X) _Generic((X),\
	int: "int",\
	float: "float",\
	double: "double",\
	default: "other"\
)
//MYTYPE(1) 将返回"int"
//可以使用自建的变量,这样子可操作空间将大大增加

内联函数


<stdlib.h>

rand()

生成一个随机数,但是是伪随机,可以用 srand()更改“种子”

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
    int a;
    srand((unsigned int) time(0));
    a = rand();
    for (int i = 0 ; i < 10 ; i++)
    {
        printf("%d\n",a);
        a = rand();  //若要生成1~n范围的数字,可以写成 a = rand() % n + 1;
    }
    return 0;
}

exit() atexit()

exit()会退出整个程序(main()结束时会隐式调用),可在括号内放入结束代码 EXIT_SUCCESSEXIT_FAILURE
atexit()接受一个函数指针,注册该函数,在使用 exit() ​ 退出时会调用该函数
atexit()创建对应的函数列表(至少能存放 32 个),最后调用 exit()时会执行这些函数
需要注意执行顺序与列表中的函数顺序相反(栈)
atexit()注册的函数应该不带任何参数且返回类型为 void

#include <stdlib.h>
……
	atexit(myfuct);
	exit(EXIT_FAILURE);
……

qsort()

快速排序

原型及参数

void qsort(void *_Base, size_t _NumOfElements, size_t _SizeOfElements, int (*_PtFuncCompare)(const void *, const void *))
第一个参数为指针,指向待排序数组的首元素
第二个参数时待排序项的数目
第三个参数显式指明待排序数组中每个元素的大小(因为第一个参数将指针转换为 void ​ 了,所以需要另外传入元素大小)
第四个参数为一个指向函数的指针,用于确定升序还是降序

使用例

#include <stdlib.h>
......
int mycomp(const void * p1 , const void * p2);
......
	qsort(ar,NUM,sizeof(double),mycomp);//会改变ar
......
int mycomp(const void * p1 , const void * p2)//从小到大
{
//要使用指向double的指针来访问这两个值
	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 1;
}

<limits.h>

提供了各种 int 类型的大小范围

<float.h>

提供了 float 类型的大小范围
DBL_MAX​ double 最大绝对值,大于该值为 上溢
DBL_MIN​ double 最小绝对值,小于该值为 下溢​(会表示为 0)

<math.h>

提供了各种数学运算公式

原型 描述
double acos(double x) 返回余弦值为 x 的角度(0~π)
double asin(double x) 返回正弦值为 x 的角度(-π/2~π/2)
double atan(double x) 返回正切值为 x 的角度(-π/2~π/2)
double atan2(double y , double x) 返回正切值为 y/x 的角度(-π~π)
double cos(double x) 返回 x 的余弦值,弧度
double sin(double x) 返回 x 的正弦值,弧度
double tan(double x) 返回 x 的正切值,弧度
double exp(double x) 返回 $e^x$
double log(double x) 返回 $lnx$
double log10(double x) 返回 $lgx$
double pow(double x, double y) 返回 $x^y$
double sqrt(double x) 返回 ​
double cbrt(double x) 返回 ​
double ceil(double x) 返回 $⌈x⌉$
double fabs(double x) 返回 $|x|$
double floor(double x) 返回 $[x]$ 或 $⌊x⌋$

三角问题

atan() ​ 无法区分角度相同但方向相反的线,用 atan2() ​ 可以解决这个问题
所有三角函数返回的值的单位都是弧度,视情况转换成角度

#define pi 4 * atan(1)  //the pi

类型变体

在原函数名后面加上 f ​ 或 l ​ 后缀,函数接收值的类型会发生改变
sqrtf() ​ 为 float 版本
sqrtl() ​ 为 long double 版本
使用 泛型宏 ​ 来确定使用哪个版本的函数

#define SQRT(X) _Generic((X),long double : sqrtl , default : sqrt , float : sqrtf)(X)
//SQRT(X);

<tgmath.h>

定义了泛型类型宏,如 sqrt() ​ 宏展开为 sqrtf()sqrt()sqrtl()

<complex.h>

支持复数运算
相关函数在函数名前加 c
csqrt()

<ctype.h>

提供了各种判断、处理字符的函数

字符测试函数

若符合则返回

isalnum() 字母或数字
isalpha() 字母
isblank() 标准空白字符(空格、水平制表符、换行符)或本地指定为空白的字符
iscntrl() 控制字符
isdigit() 数字
isgraph() 除空格以外的可打印字符
islower() 小写字母
isprint() 可打印字符
ispunct() 标点符号(除空格和字母和数字以外的可打印字符)
isspace() 空白字符(空格、换行符、换页符、回车符、垂直制表符、水平制表符、本地定义空白字符)
isupper() 大写字母
isxdigit() 十六进制数字符

字符映射函数

tolower() 大写改小写,原本为小写则不变
toupper() 小写改大写,原本为大写则不变

<assert.h>

assert() ​ 接受一个整型表达式作为参数,如果为非零,assert()宏就在 stderr ​ 中写入错误信息,并调用 abort() ​ 终止程序
会显示失败的测试、包含测试的文件名和行号

_Static_assert

可以在编译时检查 assert()表达式
assert()会导致正在运行的程序终止
_Static_assert()会导致程序无法通过编译
_Static_assert()接受一个整型常量表达式(和 assert()不同,给 assert()的可以是一个表达式,在程序运行时编译)和一个字符串

#include <stdio.h>
#include <limits.h>
_Static_assert(CHAR_BIT == 16, "16-bit char falselt assumed");
//接收的第一个参数需要能在编译的时候就能运行(即不需要做计算)
int main(void)
{
    puts("char is 16 bits.");
    return 0;
}

输出即为错误信息
test.c:3:1: error: static assertion failed: "16-bit char falselt assumed" _Static_assert(CHAR_BIT == 16, "16-bit char falselt assumed");

<string.h>

memcpy()和 memmove()

函数原型

void *memcpy(void *__restrict__ _Dst, const void *__restrict__ _Src, size_t _Size)
void *memmove(void *_Dst, const void *_Src, size_t _Size)

作用

两个函数的作用都是将 Source 数组的内容拷贝给 Dest 数组
不同的是 memcpy()指定的是 restrict,需要保证两个数组在内存区域中没有重叠
memmove()没有这个问题

参数

Size 需要字节数,如 10*sizeof(double)

<stdarg.h>

提供可变参数

  1. 提供一个使用省略号的函数原型(至少有一个形参和一个省略号)
  2. 在函数定义中创建一个 va_list 类型的变量
  3. 用宏把该变量初始化为一个参数列表
  4. 用宏访问参数列表
  5. 用宏完成清理工作
void f1(int n,...);//valid
int f2(const char *s , int k , ...);//valid
char f3(char c1 , ... , char c2);//invalid
oduble f4(...);//invalid

parmN ​ 描述省略号前的一个形参,传递给该形参的实际参数时省略号部分代表的参数数量

f1(2 , 200 , 400);
f1(4 , 13 , 42 , 76 , 100);
double sum(int lim, ...)
{
	va_list ap;  //声明一个存储参数的对象
	...
	va_start(ap,lim); //使用在<stdarg.h>中的宏,把参数列表拷贝到va_list类型的变量中
	//va_start(va_list , parmN);
...

访问参数列表

double tic;
int toc;
...
tic = va_arg(ap,double);
toc = va_arg(ap,int);

va_arg() ​ 接受两个参数,一个 va_list 变量和一个类型名,每次调用 va_arg()时会返回参数列表中的下一个参数。表示类型的参数指定了返回值的类型
传入的参数类型必须与宏参数的类型相匹配,否则可能报错

清理

va_end(ap)
释放动态分配用于存储参数的内存

拷贝

va_copy(ap_copy,ap_source)

使用例

#include <stdio.h>
#include <stdarg.h>
double sum(int lim , ...);
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;
    int i;
    double tot = 0;
    va_start(ap , lim);
    for (i = 0 ; i < lim ; i++)
        tot += va_arg(ap,double);
    va_end(ap);
    return tot;
}