C语言库函数
1. stdio库
1.1 printf函数
printf
格式化输出符:
int a = 3;
float b = 3.14;
double c = 5.2;
char s1[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
char *s2 = "world";
printf("%d %f %f\n", a, b, c);
printf("%s %s\n", s1, s2);
printf("%c\n", s1[3]);
printf("%u %o %x", -1, 10, 20);
/* 运行结果:
3 3.140000 5.200000
hello world
l
4294967295 12 14
*/
printf
格式化输出符修饰符:
uploading-image-843664.png
标记符:
double pi = 3.1415926;
printf("%5.2f\n", pi);
printf("%.4s\n", s2);
int d = 100;
printf("%#o %#x", d, d);
char *s2 = "abcdef";
size_t e = sizeof(s2);
printf("%zu", e);
/* 运行结果:
3.14
worl
0144 0x64
7
*/
1.2 scanf函数
scanf
格式化输入符:
scanf
格式化输入符修饰符:
- scanf_s函数
int scanf_s(const char * restrict format, . . . );
scanf_s函数输入字符串或字符时,即格式化字符串中有%s、%c
时,需要指定字符串缓冲区大小或字符字节大小
char s[5];
scanf_s("%s", s, sizeof(s));
1.3 字符的输入输出
-
字符输入:
int getchar(void)
:从stdin读取一个字符int fgetc(FILE *stream)
:从输入流stream中读取一个字符int getc(FILE *stream)
:从输入流stream中读取一个字符 -
字符输出:
int putchar(int c)
:向stdout输出一个字符int fputc(int c, FILE *stream)
:向输出流stream中输出一个字符int putc(int c, FILE *stream)
:向输出流stream中输出一个字符
1.4 文件操作
- 打开文件
FILE *fopen(const char * restrict filename, const char * restrict mode)
参数:filename: 文件名(可能包含文件路径)
windows下文件名中文件路径含有'\'时,要注意C语言会把它看做转义序列的开始标志,需要使用'\\'或'/'表示文件路径:
fopen("C:\\project\\test.txt", 'r');
fopen("C:/project/test.txt", 'r');
mode:
返回值:fopen函数返回一个文件指针,通常可以把它存在一个变量中:
FILE *fp;
fp = fopen("C:/project/test.txt", 'r');
- 关闭文件:
int fclose(FILE *stream)
参数:stream:文件指针;
返回值:关闭成功,返回0;关闭失败,返回EOF。
1.5 文件格式化输入输出
- 向文件中写入:
int fprintf(FILE * restrict stream, const char * restrict format, ...);
参数:stream:文件指针;format:格式化输出字符串;
返回值:返回写入的字符数。
- 从文件中读入:
int fscanf(FILE * restrict stream, const char * restrict format, ...);
参数:stream:文件指针;format:格式化输入字符串;
返回值:返回读入的数据个数;
1.6 文件的行输入输出
- 行的输出:
int fputs(const char * restrict s, FILE * restrict stream)
:向输出流stream中写入一行字符串s
int puts(const char *s)
:向标准输出流stdout中写入一行字符串s
返回值:若写入失败,返回EOF;否则返回一个非负的数。
- 行的输入:
char *fgets(char * restrict s, int n, FILE * restrict stream)
:从输入流stream中读取一行到字符串s中,字符串最大长度为n;(会丢弃换行符)
char *gets(char *s)
:从标准输入流中读取一行到字符串s中;(会丢弃换行符)
返回值:若读取失败,返回空指针;否则返回读取到的字符串
1.7 字符串的格式化输入输出
- 字符串的输出:
int sprintf(char * restrict s, const char * restrict format, ...)
:把参数根据格式化字符串写入字符数组s中(会向字符串s末尾添加空字符'\0')
int snprintf(char *restrict s, size_t n, const char * restrict format, ...)
:把参数根据格式化字符串写入字符串s中,写入字符串s的字符数不会超过n-1(会向字符串s末尾添加空字符'\0')
int snprintf_s(char *restrict s, size_t n, size_t count, const char * restrict format, ...)
:把参数根据格式化字符串写入字符串s中,n是字符数组s的大小,count是当参数根据格式化字符串得到的字符串长度超过了缓冲区大小n之后,为避免缓冲区溢出,会自动截取前count个字符
返回值:返回格式化后字符串s的长度(不包括空字符'\0')
#define MAX_DATA_LEN 12;
char date[MAX_DATE_LEN];
int ret = sprintf(date, "%d/%d/%d", 2023, 5, 25);
// date: "2023/5/25"
int ret = snprintf(date, MAX_DATE_LEN, "%d/%d/%d", 2023, 5, 25);
// date: "2023/5/25"
int ret = snprintf_s(date, MAX_DATE_LEN, MAX_DATE_LEN - 1, "%d/%d/%d", 2023, 5, 25)
// date: "2023/5/25"
- 字符串的输入:
int sscanf(const char * restrict s, const char * restrict format, ... );
:从字符串s中根据格式化字符串读取并格式化数据到各参数中
int sscanf_s(const char * restrict s, const char * restrict format, ... );
:从字符串s中根据格式化字符串读取并格式化数据到各参数中,若格式化字符串format中包含了%s、%S、%c、%C或括号表达式(例如%[a-d])时,函数中需要传入字符串参数的长度大小,且长度大小必须小于等于字符串s的实际大小
#define MAX_DATA_LEN 20;
#define S_LEN 10;
char date[MAX_DATE_LEN] = "2023/5/25";
int year, month, day;
sscanf(date, "%d/%d/%d", &year, &month, &day);
// year = 2023; month = 5; day = 25
char s[S_LEN];
sscanf_s(date1, "%s", s, S_LEN);
// s = "2023/5/25"
返回值:成功则返回读取的参数个数,失败则返回EOF;
2. string库
2.1 字符串拷贝
char *strcpy(char *s1, const char *s2);
:把字符串s2复制给字符串s1
返回值:返回字符串s1
errno_t strcpy_s(char* strDest, size_t destMax, const char* strSrc)
:把字符串strSrc复制给字符串strDest,字符串strDest的缓冲区大小为destMax,若strSrc的长度大于destMax,则会截断strSrc的前destMax - 1 个字符复制给strDest,,保证strDest以空字符结尾
返回值:成功时返回0,否则返回非0
注意:利用赋值运算符'='来把字符串复制到字符数组中是不行的!数组名不能出现在赋值运算符的左侧!
char str1 = "abcd";
char str2[10];
str2 = str1; // 错误!
str2 = "abcd"; // 错误!
char str2[10] = "abcd"; // 允许,只能初始化时赋值!
char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
:把字符串s2复制给字符串s1,复制的最大字符个数为n,若s2的字符串长度大于s1字符数组长度,会导致字符串s1没有空字符'\0'终止
返回值:返回字符串s1
#define BUFF_LEN 10
char s1[BUFF_LEN];
char s2= "abcd";
char *ret;
ret = strcpy(s1, s2);
errno_t ret1;
ret1 = strcpy_s(s1, s2, BUFF_LEN);
ret = strncpy(s1, s2, sizeof(s1) - 1);
s1[sizeof(s1) - 1] = '\0'; // 保证有空字符结尾
2.2 字符串长度
size_t strlen(const char *s);
:返回字符串s的长度(字符个数),不包括空字符
size_t strnlen(const char *s, size_t maxlen)
:若字符串s的实际长度小于等于maxlen,则返回字符串字符个数;否则,返回maxlen
#define MAXLEN 10
char *s1 = "abcd";
char s2[MAXLEN] = "ABCD";
size_t len1 = strlen(s1);
size_t len2 = strlen(s2, MAXLEN);
2.3 字符串连接
char *strcat(char *s1, const char *s2);
:把字符串s2的内容追加到字符串s1的末尾
返回值:返回字符串s1
errno_t strcat_s(char* strDest, size_t destMax, const char* strSrc)
:把字符串strSrc追加到字符串strDest末尾,若追加之后的字符串strDest长度大于destMax,则会截断strDest的前destMax - 1个字符,保证strDest以空字符结尾
返回值:成功则返回0,否则返回非0
#define MAX_BUFF_LEN 20
char s1[MAX_BUFF_LEN] = "1234";
char *s2 = "abcd"
char *ret;
ret = strcat(s1, s2);
errno_t ret1;
ret1 = strcat_s(s1, MAX_BUFF_LEN, s2);
2.4 字符串比较
int strcmp(const char *s1, const char *s2);
:比较字符串s1和s2大小(按字典序)
int strncmp(const char *s1, const char *s2, size_t n);
:比较字符串s1和s2大小,最多比较前n个字节
返回值:若s1 < s2,返回负数;若s1 = s2,返回0;若s1 > s2,返回正数
s1 = "abc";
s2 = "abd";
s3 = "abc";
s4 = "aba";
int ret1, ret2, ret3;
ret1 = strcmp(s1, s2);
// ret1 < 0;
ret2 = strcmp(s1, s3);
// ret2 = 0;
ret3 = strcmp(s1, s4);
// ret3 > 0;
2.5 字符串搜索
2.5.1 字符搜索
char *strchr(const char *s, int c);
:在字符串s中搜索字符c
返回值:返回一个指向字符串s中出现的第一个字符c
char *strrchr(const char *s, int c);
:在字符串s中反向搜索字符c
char s1 = "abcdefgabc";
char *p1, *p2, *p3;
p1 = strchr(s1, 'a'); // 查找第一个字符a的位置
p1 = strchr(p1 + 1, 'a') // 查找第二个字符a的位置
p2 = strrchr(s1, 'a'); // 搜索最后一个字符a的位置
2.5.2 子串搜索
char *strstr(const char *s1, const char *s2);
:在字符串s1中搜索字符串s2
返回值:若找到,则返回字符串s2第一次在字符串s1中出现的位置的指针;若找不到,则返回空指针
char *s1 = "abcdabcd";
char *s2 = "bcd";
char *p;
p = strstr(s1, s2);
// p指向s1中的第一个字符b
char *strtok(char * restrict s1, const char * restrict s2);
:将字符串s1以字符串s2进行分割
调用strtok(s1, s2)会在s1中搜索不包含在s2中的非空字符序列。strtok函数会在记号末尾的字符后面存储一个空字符作为标记,然后返回一个指针指向记号的首字符。strtok函数最有用的特点是以后可以调用strtok函数在同一字符串中搜索更多的记号。调用strtok(NULL, s2)就可以继续上一次的strtok函数调用。和上一次调用一样,strtok函数会用一个空字符来标记新的记号的末尾,然后返回一个指向新记号的首字符的指针。这个过程可以持续进行,直到strtok函数返回空指针,这表明找不到符合要求的记号。
char date[] = "April 28,1998"; // 在"April与28之间有制表符\t分割"
char *p = NULL;
p = strtok(date, '\t'); // 第一次调用,将字符串date以制表符进行分割,此时p指向字符A
p = strtok(NULL, "\t,") // 第二次调用,从上次调用结束的位置继续调用,即把字符串"28,1998"按制表符和逗号进行分割,此时p指向数字2
p = strtok(NULL, '\t'); // 第三次调用,从上次调用结束的位置继续调用,即把字符串"1998"按制表符进行分割,此时p指向数字1
// strtok经典用法:
char s[] = "This is - a test - string";
const char *splits = "-";
char *p;
p = strtok(s, splites);
while(p != NULL)
{
printf("%s\n", p);
p = strtok(NULL, splits);
}
/* 执行结果:
This is
a test
string
*/
strtok由于会记住上次调用的结果,因此该函数是不可重入的,在多线程中使用不安全。
char* strtok_s(char* str, const char* delimiters, char** next_token);
:将字符串str按照字符串delimiters中的字符进行分割,并且把分割之后的字符串写入到next_token中,这样就是线程安全函数了。
char str[] = "This is - a test - string";
const char *splits = "-";
char *next_token = NULL;
char *token;
token = strtok_s(str, splits, &next_token); // 注意是&next_token
while(token != NULL)
{
printf("%s\n", token);
token = strtok_s(NULL, splits, &next_token);
}
2.6 内存操作函数
2.6.1 内存拷贝
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
:从源缓冲区s2处复制n个字节的数据到目的缓冲区s1中
当源缓冲区与目的缓冲区相同时,memcpy函数此时的行为是未定义的!
当目的缓冲区大小小于n时,将会发生缓冲区溢出!
errno_t memcpy_s(void* dest, size_t destMax, const void* src, size_t count)
:从源缓冲区src中复制count个字节的数据到目的缓冲区dest中,目的缓冲区中允许修改的字节大小为destMax
若在运行时检测到以下错误将导致整个目标范围[dest,dest + destMAX)被清零(如果dest和destMAX都有效):
- dest或者src是空指针;
- destMax或者count大于RSIZE_MAX;
- count大于destMax;(会发生缓冲区溢出)
返回值:成功复制则返回0,否则返回非0值
int arr[3] = {1, 2, 3};
int *p = malloc(3 * sizeof(int));
memcpy(p, arr, 3 * sizeof(int));
#define BUFF_LEN 10;
int arr1[BUFF_LEN];
errno_t ret;
ret = memcpy_s(arr1, BUFF_LEN * sizeof(int), arr, 3 * sizeof(int));
// ret = 0
char src[] = "aaaaaaaaaa";
char dest[] = "xyxyxyxyxy";
ret = memcpy_s(dest, sizeof(dest), src, 5);
// dest[] = "aaaaayxyxy", ret = 0
ret = memcpy_s(dest, 5, src, 10) // count 大于 destMax,将会把[dest, dest + destMax)区间内的字节清零
// dest[] = "\0\0\0\0\0yxyxy", ret = 22 (ret为非零随机值)
2.6.2 内存初始化
void *memset(void *s, int c, size_t n);
:将缓冲区s的n个字节全部初始化为字符c
#define BUFF_SIZE 40
int *buffer = malloc(BUFFSIZE);
if(buffer == NULL)
{
printf("malloc memory failed!\n");
return;
}
memset(buffer, 0, sizeof(buffer));
errno_t memset_s(void* dest, size_t destMax, int c, size_t count)
:将缓冲区dest的count个字节全部初始化为字符c,缓冲区dest中允许修改的字节大小为destMax
返回值:成功复制则返回0,否则返回非0值
若在运行时检测到以下错误,将在目的地范围[dest, dest + destMax]的每个字节初始化为字符c(如果dest和destMax都有效的话):
- dest是一个空指针
- destMax或count大于RSIZE_MAX
- count大于destMax(会出现缓冲区溢出)
char str[] = "ababababab";
errno_t ret;
ret = memset_s(str, sizeof(str), 'b', 5);
// str = "bbbbbbabab" ret = 0
ret = memset_s(str, 5, 'a', 10); // count大于destMax,将会把[dest, dest + destMax]中的字节初始化为字符a
// str = "aaaaababab" ret = 22
3. math库
3.1 三角函数
double acos(double x);
double asin(double x);
double atan(double x);
double atan2(double y, double x);
double cos(double x);
double sin(double x);
double tan(double x);
3.2 指数函数和对数函数
double exp(double x);
:e的x次幂
double log(double x);
:ln(x)
double log10(double x);
:lg(x)
3.3 幂函数
double pow(double x, double y);
:x的y次幂
double sqrt(double x);
:计算x的平方根
3.4 绝对值函数
double fabs(double x);
:x的绝对值
注意:C语言中没有定义最大值max和最小值min函数!
4. ctype库
4.1 字符分类
4.2 字符大小写转换
int tolower(int c);
int toupper(int c);
对于这两个函数,如果所传参数不是字母,那么将返回原始字符,不加任何改变。
5. stdlib库
5.1 数值转换
double atof(const char *nptr);
:将字符串转换成double
int atoi(const char *nptr);
:将字符串转换成int
long int atol(const char *nptr);
:将字符串转换成long int
5.2 整数算数运算
int abs(int j);
:返回一个int类型整数的绝对值
div_t div(int numer, int denom)
:用参数numer除以参数denom,返回一个div_t类型,div_t是一个含有商成员quot和余数成员rem的结构体;
ans = div(5, 2);
printf("商:%d, 余数:%d", ans.quot, ans.rem);
5.3 随机序列生成
int rand(void);
:生成随机数,返回一个0~RAND_MAX之间的数
void srand(unsigned int seed);
:基于种子seed值生成特定的伪随机序列
5.4 搜索和排序
void *bsearch(const void *key,
const void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *));
在有序数组中搜索一个特定的值key
参数:key:要搜索的值
base:指向要搜索的数组
nmemb:数组中元素的数量
size:数组中每个元素的大小(字节)
compar:指向比较函数的指针,数组的内容应根据compar所对应的比较函数升序排序
返回值:如果查找成功,该函数返回一个指向数组中匹配元素的指针,否则返回空指针。
bsearch只能用于搜索有序数值,因此在进行搜索前需要对数组进行排序!
#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b)
{
int numa = *(int*)a;
int numb = *(int*)b;
return numa - numb;
}
int main()
{
int values[] = {30, 50, 40, 20, 10};
qsort(values, sizeof(values)/sizeof(values[0]), sizeof(int), compare);
int key = 50;
int *res;
res = bsearch(&key, values, sizeof(values)/sizeof(values[0]), sizeof(int), compare);
if(res != NULL){
printf("%d Found!\n", *res);
} else {
printf("Not Found!\n");
}
return 0;
}
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
参数:base必须指向数组中的第一个元素(如果只是对数组的一段区域进行排序,那么要使base指向这段区域的第一个元素)。在一般情况下,base就是数组的名字;
nmemb是要排序元素的数量(不一定是数组中元素的数量);
size是每个数组元素的大小,用字节来衡量;
compar是指向比较函数的指针;
如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的前面
如果compar返回值等于0(== 0),那么p1所指向元素与p2所指向元素的顺序不确定
如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的后面
注意compar比较函数的两个形参是const void *
类型,在调用compar函数时传入的实参也必须转换成const void *
型,在compar函数内部会将const void *
类型转换成实际类型。
一个通用的compar函数可以写成这样:
int compare (const void * a, const void * b)
{
if ( *(MyType*)a < *(MyType*)b ) return -1;
if ( *(MyType*)a == *(MyType*)b ) return 0;
if ( *(MyType*)a > *(MyType*)b ) return 1;
}
//升序排序
int compare (const void * a, const void * b)
{
return ( *(int*)a - *(int*)b );
}
//降序排列
int compare (const void * a, const void * b)
{
return ( *(int*)b - *(int*)a );
}
- int数组排序
#include <stdio.h>
#include <stdlib.h>
// 升序
int compare(const void * a, const void * b)
{
return (*(int*)a - *(int*)b);
}
int main()
{
int values[] = {30, 50, 40, 20, 10};
qsort(values, sizeof(values)/sizeof(values[0]), sizeof(int), compare);
for(int i = 0; i < sizeof(values)/sizeof(values[0]); i++)
printf("%d ", values[i]);
return 0;
}
- 结构体排序
#include <stdio.h>
#include <stdlib.h>
typedef struct{
char name[30];
int Chinese;
int Math;
int English;
}student;
int compare(const void * a, const void * b)
{
student* pa = (student*)a;
student* pb = (student*)b;
int suma = pa->Chinese + pa->Math + pa->English;
int sumb = pb->Chinese + pb->Math + pb->English;
return sumb - suma;
}
int main()
{
student students[7] = {
{"周",97,68,45},
{"吴",100,32,88},
{"郑",78,88,78},
{"王",87,90,89},
{"赵",87,77,66},
{"钱",59,68,98},
{"孙",62,73,89}
};
qsort(students, sizeof(students)/sizeof(student), sizeof(student), compare);
for(int i = 0; i < 7; i++)
{
printf("%s%4d%4d%4d", students[i].name, students[i].Chinese, students[i].Math, students[i].English);
printf(" sum:%4d\n", students[i].Chinese + students[i].Math + students[i].English);
}
return 0;
}
- 字符串指针数组排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare(const void * a, const void * b)
{
char *stra = *(char**)a;
char *strb = *(char**)b;
return strcmp(stra, strb);
}
int main()
{
char *arr[5] = {"i", "love", "c", "language", "programming"};
qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(char *), compare);
for(int i = 0; i < 5; i++)
{
printf("%s ", arr[i]);
}
return 0;
}
向qsort传入arr之后,qsort将arr理解为指向数组中第一个元素的指针,对字符串指针数组进行排序时,排序的其实是字符串char *
,因此qsort的形式参数a和b实际上是指向字符串的指针即char **
- 字符串二维数组排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare(const void* a, const void* b)
{
char *stra = (char *)a;
char *strb = (char *)b;
return strcmp(stra, strb);
}
int main()
{
char arr[5][16] = {"i", "love", "c", "language", "programming"};
qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), compare);
for(int i = 0; i < 5; i++)
{
printf("%s ", arr[i]);
}
return 0;
}
arr传入qsort函数,qsort函数将arr理解为指向数组第一个元素的指针,arr的第一个元素是arr[0][0]
,所以形式参数a和b指的是指向"a[i][0]
"的指针,a[i][0]是字符char,所以a和b指的是char *