11. C语言标准函数库


C语言制定了一组使用方式通用的函数,称为C语言标准函数库,用于实现编程常用功能,标准函数库由编译器系统提供,并按功能分类存储在不同源代码文件中,调用标准库内函数时需要首先使用 #include 连接对应的源代码文件。

【终端输出函数】

输出函数用于在终端输出字符,在linux系统中终端输出操作是通过向标准输出文件写入数据实现的,每个进程都会绑定一个自己专用的标准输出文件,终端会将进程写入到此文件的数据当做字符编码显示在屏幕中。

向标准输出文件写入数据时并非立即生效,而是有一个延迟时间,当然这个延迟时间很短,进程短时间内频繁多次写入数据时,多次写入会合并为一个,若希望每次输出都立即生效,可以使用如下方式:
1.在输出数据的末尾添加换行字符。
2.程序输出数据后执行fflush(stdout)函数。
3.程序执行完毕退出,未输出的数据会立即输出。

在终端内输出数据时,回车和换行经常同时使用,linux简化了使用方式,可以使用“换行”表示“换行+回车”,但是单独使用回车时不会自动换行,此时输出的数据会覆盖本行之前显示的字符,若需要删除之前输出的一段文字,可以通过在本行输出一个回车加一段空格的方式覆盖掉原数据。

注:
1.可以修改终端的工作模式,让换行只表示换行,而非换行+回车。
2.在windows中必须同时使用换行+回车。

输出字符

putchar函数用于输出一个字符。

#include <stdio.h>
int putchar(int char)

参数char指定要输出的字符,执行成功返回输出的字符,执行失败返回-1。

putchar('a');   //输出a
putchar(10);    //输出换行

输出字符串

puts函数用于输出一个字符串,并自动在输出数据末尾添加一个换行,这一点与printf函数不同。

#include <stdio.h>
int puts(const char *str)

参数str为要输出字符串的指针,执行成功返回输出的字符串长度,执行失败返回-1。

puts("阿狸");    //数组参数自动转换为指针

转换并输出

printf函数可以在输出字符串的同时将指定数学数据转换为字符串表示形式并整合到输出字符串中。

#include <stdio.h>
int printf(const char *format, ...)   //使用了可变参数

参数format,设置输出字符串的指针,以及可变参数的类型。
可变参数,设置要转换的数据。

执行成功返回输出字符的数量,执行失败返回-1。


此函数的转换合并功能通过格式字符确定操作方式,格式字符对应要转换与合并的数据,格式字符放在参数format指向的字符串内,设置了几个格式字符就增加几个可变参数(%%除外),转换后的可变参数会合并到参数format指向的字符串中,合并位置为对应的格式字符所在位置,常用格式字符如下:

%hd,将2字节有符号整数转换为10进制表示形式的字符串,合并到参数format字符串中。
%hu,同上,操作2字节无符号整数。
%d,同上,操作4字节有符号整数。
%u,同上,操作4字节无符号整数。
%ld,同上,操作8字节有符号整数。
%lu,同上,操作8字节无符号整数。

-------------------------------------------------------------------------------------------------------

%hx,将2字节无符号整数转换为16进制表示形式的字符串,合并到参数format字符串中,字母a-f使用小写形式。
%x,同上,操作4字节无符号整数。
%lx,同上,操作8字节无符号整数。
%hX,同上,操作2字节无符号整数,字母A-F使用大写形式。
%X,同上,操作4字节无符号整数,字母A-F使用大写形式。
%lX,同上,操作8字节无符号整数,字母A-F使用大写形式。

-------------------------------------------------------------------------------------------------------

%ho,将2字节无符号整数转换为8进制表示形式的字符串,合并到参数format字符串中。
%o,同上,操作4字节无符号整数。
%lo,同上,操作8字节无符号整数。

-------------------------------------------------------------------------------------------------------

%f,将float类型浮点数转换为字符串表示形式,合并到参数format字符串中,小数部分默认显示6位,不足6位使用0填充,转换后的浮点数可能会丢失精度。
%10f,同上,指定转换后字符串的总长度(包含小数点),这里设置为总长10位,默认小数位占6个字符,整数位占3个字符,若总长度不足指定长度,则在字符串前面添加空格补足长度,若总长度超过指定长度,则使用四舍五入的方式减小长度。
%-10f,同上,添加-符号表示长度不足时在字符串后面添加空格补足长度。
%10.8f,同上,指定输出字符串总长度的同时设置小数位占用的长度,这里设置小数位占8个字符,整数位占一位,可以同时添加-符号。
%lf,同上,操作double类型浮点数。

-------------------------------------------------------------------------------------------------------

%c,将一个字符合并到输出字符串中,不进行任何转换,printf没有提供char类型数据转字符串表示形式的功能,若有此需求可以使用%d、%u格式字符代替,此时等同于使用char类型数据为4字节数据赋值,编译器会自动扩充数据长度。
%s,将一个字符串合并到输出字符串中,不进行任何转换。

-------------------------------------------------------------------------------------------------------

%p,将一个指针转换为16进制表示形式的字符串,合并到参数format字符串中,不同处理器计算机中指针长度不同,%p会自动确定指针长度,并且%p会为转换后的数据添加前缀0x。

-------------------------------------------------------------------------------------------------------

%+,若输出的有符号数为正数,则在前面添加+符号,不能单独使用,可以与%d、%hd、%ld组合使用,比如%+d,但是不能与%u组合使用。

-------------------------------------------------------------------------------------------------------

%%,输出一个%符号,此格式字符无需设置对应的可变参数。

 注:8进制、16进制的转换全部操作无符号数,用于观察一个数据每个字节的值,若为负数则显示其补码每字节的值。

#include <stdio.h>
int main()
{char a = 65;printf("变量a %%c 输出:%c\n", a);      //输出A,65为A的编码printf("变量a %%d 输出:%d\n\n", a);    //输出65,将变量a转换为字符串表示形式int b = 10;printf("变量b的十进制:%d\n", b);printf("变量b的八进制:%o\n", b);printf("变量b的十六进制:0x%X\n\n", b);float c = 3.1415926;printf("圆周率:%f\n", c);printf("圆周率:%10f\n", c);printf("圆周率:%-10f\n", c);printf("圆周率:%-10.5f\n\n", c);/* 设置多个格式字符,分别对应一个可变参数,绑定关系为各自的出现顺序 */printf("变量a:%c\n""变量b:%d\n""变量c:%f\n",a, b, c);return 0;
}

线程安全输出函数

以上终端输出函数没有考虑线程安全问题,多线程程序同时进行输出时将会混乱,可以使用以下线程安全函数。

但是实际上多线程程序一般只会使用一个线程负责输出操作,所以非线程安全输出函数依然广泛使用。

fputs函数

fputs用于向文件写入一个字符串,可以设置其向标准输出文件写入数据,从而进行终端输出操作,同时它也可以用于操作普通文件。

#include <stdio.h>
int fputs(const char *str, FILE *stream)

参数str,指定要写入字符串的指针。
参数stream,指定要操作的文件,赋值为stdout则操作标准输出文件,赋值为stderr则操作标准错误文件,操作普通文件时需要自行打开文件,打开文件函数参考之后的章节。

执行成功返回大于0的正数,执行失败返回-1。

#include <stdio.h>
int main()
{fputs("阿狸\n", stdout);return 0;
}

fprintf函数

fprintf用于向文件写入数据,可以设置其向标准输出文件写入数据,从而进行终端输出操作,使用方式类似printf。

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...)    //使用了可变参数

参数stream,指定要操作的文件,赋值为stdout则操作标准输出文件,赋值为stderr则操作标准错误文件。
参数format,指定要输出的数据,可以使用格式字符,同printf函数。

执行成功返回写入数据的长度,执行失败返回-1。

#include <stdio.h>
int main()
{int a = 9;fprintf(stdout, "变量a的值为:%d\n", a);return 0;
}


【终端输入函数】

用户在键盘中按下按键后会向CPU发送按键对应的字符编码,Linux为每个进程绑定一个自己专用的标准输入文件,用于存储用户向本进程输入的数据,用户的输入以换行结尾,换行字符也会保存到标准输入文件内,进程可以使用终端输入函数读取这些数据。

执行终端输入函数时,会有以下几种情况:
1.若标准输入文件内没有数据,则进程进入暂停等待状态,直到有数据后恢复执行。
2.若标准输入文件内有数据,则读取指定数量的数据。
3.成功读取数据后,若没有读完,则剩余数据等待下次被读取,不会被丢弃。

输入一个字节

getchar函数在标准输入文件读取一个字节的数据并返回,用户输入的换行字符也会被读取,不会被丢弃,若读取出错则返回-1。

#include <stdio.h>
int getchar()
#include <stdio.h>
int main()
{int a = getchar();printf("你输入的第一个字节编码是:%d\n", a);return 0;
}

清空标准输入文件

当用户输入多个字节时,getchar无法一次读完,剩余数据会在下次执行输入函数时读取,若程序不想使用剩余数据,就需要在每次读取后清空剩余数据,在Windows中可以执行 fflush(stdin) 函数清空输入数据,但此函数在linux中不生效,在linux中可以使用循环语句不断读取标准输入文件内的一个字节,直到读取到换行字符为止,读取到的数据废弃不用。

#include <stdio.h>
int main()
{char a,b,x=0;printf("请输入第一个字符\n");a = getchar();    //读取一个字节,此时标准输入文件中至少会剩余一个换行字符/* 清空标准输入文件,10为换行编码 */while(x != 10){x = getchar();}x = 0;printf("请输入第二个字符\n");b = getchar();printf("你输入的两个字符分别是:%c/%c\n", a, b);/* 使用完毕,继续清空 */while(x != 10){x = getchar();}x = 0;return 0;
}

输入一个字符串

#include <stdio.h>
char * gets(char *str)
char * gets_s(char *str, int size)
char * fgets(char *str, int size, FILE *stream)

参数str,设置接收数据的字符串地址。
参数size,设置每次最大的读取字节量,不应超过参数str指向字符串的长度,因为每次读取都会在字符串末尾添加一个空字符,所以单次读取最大有效字符数量为size减1。
参数stream,设置读取的文件,赋值为stdin则读取标准输入文件。

读取成功返回参数str,读取失败返回0。

gets在标准输入文件内读取一个字符串,读取到换行字符为止,同时读取的数据不保留换行,gets不能设置最大的读取字节量,当用户输入的数据长度超过接收数据的数组长度时,将会发生数组越界访问,所以不推荐使用gets。

gets_s可以设置每次最多读取的字节量,但并非所有编译器都支持此函数,注重通用性的程序代码应该使用fgets。

fgets是读写文件函数,将参数stream设置为stdin将会读取标准输入文件,从而实现终端输入功能,fgets读取数据时会保留换行,这一点与gets、gets_s不同,另外fgets是线程安全的,而前两者不是。

#include <stdio.h>
int main()
{char a[30];fgets(a, 30, stdin);puts(a);return 0;
}

转换并输入

scanf函数可以在读取字符编码时转换为对应的数学数据表示形式。

#include <stdio.h>
int scanf(const char *format, ...)    //可变参数均为指针

参数format,设置一个字符串指针,用于存储格式字符,格式字符用于设置转换功能,每个格式字符对应一个读取的字符串,并且绑定一个可变参数,读取到的字符串转换后赋值给可变参数、或者直接赋值给可变参数,注意可变参数均为变量指针,而非变量。

执行成功返回成功赋值的可变参数个数,执行失败返回-1。

scanf的格式字符与printf函数对应,功能相反,但是注意%c、%s格式字符,两者都不会进行转换,若需转换可以在读取数据之后使用字符串转换函数snprintf,另外两者的使用需要注意以下情况:

%c,会读取用户输入的换行字符,而其它格式字符不会读取换行,并且不能使用%d代替实现转换功能,这一点与printf不同,printf转换并输出一个字符时,可以使用%d代替,此时单字节数据会自动扩充长度为4字节,而在scanf中输入一个字符并转换时,若使用%d代替,并且使用一个单字节数据接收,则scanf进行赋值时会额外多操作3个字节,这些内存单元可能正在被其它变量使用,这将出现错误。

%s,可以限制每次读取的字节数量,防止数组越界访问,比如%10s,表示最多读取10个字节的数据赋值给可变参数指向的数组,没有读取完的数据保存在标准输入文件内,等待下次被读取。

#include <stdio.h>
int main()
{char a[100];printf("请输入一个字符串\n");scanf("%99s", &a);            //最大输入长度为数组长度减1,保留末尾空字符位置printf("你输入的是:%s\n", a);return 0;
}

输入多个数据

若需要一次为多个变量输入数据,需要设置多个格式字符,默认情况下每个格式字符对应的字符串以换行结尾,用户每输入一个数据就需要按一次enter键,scanf支持自行设置字符串分割符号,此时scanf将会以此符号分割用户输入的字符串,并赋值给多个变量。

#include <stdio.h>
int main()
{int a,b;printf("请输入两个整数,数据以/符号分割\n");scanf("%d/%d", &a, &b);    //在格式字符中间添加/符号,scanf会以此符号作为输入字符串的分割符printf("你输入的数据为:%d/%d\n", a, b);return 0;
}

输入错误数据

若用户输入的数据与格式字符设置的数据类型不同,则输入的数据不会读取,而是继续存放在标准输入文件内,scanf也不会为可变参数指向的数据赋值,并直接返回0。

比如在scanf函数中使用%d格式字符,而用户却输入了英文字母、符号,此时输入数据会继续保存在标准输入文件内,等待之后程序使用scanf函数并设置%s、%c格式字符读取,或者使用其它输入函数读取,但是此时输入功能会产生一个问题,若程序一直要求输入数字,那标准输入文件内剩余的字符就会一直存在,scanf函数就会一直立即返回0,为了防止以上情况,需要在每次输入数据后清空标准输入文件。

#include <stdio.h>
int main()
{int a=0, b=0;    //存储用户输入数据int result;      //存储scanf返回值char x=0;        //清空标准输入文件使用printf("请输入第一个整数\n");do{result = scanf("%d", &a);/* 每次读取后清空 */while(x != 10){x = getchar();}x = 0;if(result == 0){printf("输入错误,请重新输入\n");}else{break;}}while(1);printf("请输入第二个整数\n");do{result = scanf("%d", &b);/* 每次读取后清空 */while(x != 10){x = getchar();}x = 0;if(result == 0){printf("输入错误,请重新输入\n");}else{break;}}while(1);printf("两个数据相加结果为:%d\n", a+b);return 0;
}

线程安全输入函数

常用的线程安全输入函数有fgets、fscanf,fgets之前已经讲过,fscanf是scanf的线程安全版本,原型如下:

#include <stdio.h>
int fscanf(FILE *stream, char *format, ...)

fscanf用于读写文件,参数stream指定要读取的文件,赋值为stdin则读取标准输入文件。

#include <stdio.h>
int main()
{int a=0, b=0;fscanf(stdin, "%d%d", &a, &b);return 0;
}


【读写文件函数】

创建、打开文件

文件需要首先进行打开操作才能读写数据,打开文件时需要设置进程对此文件的读写权限。

#include <stdio.h>
FILE * fopen(char* filename, char* mode)

参数filename,设置文件路径。

参数mode,设置文件的创建、打开方式,参数有固定的值,常用值如下:
r,以只读模式打开,若文件不存在则返回0。
r+,以读写模式打开,若文件不存在则返回0。
a,以只写模式打开,若文件存在,则向文件内容末尾写入数据,若文件不存在,则创建文件并打开。
a+,以读写模式打开,若文件存在,则向文件内容末尾写入数据,若文件不存在,则创建文件并打开。
w,以只写模式打开,若文件存在,则清空文件原有内容并打开,若文件不存在,则创建文件并打开。
w+,以读写模式打开,若文件存在,则清空文件原有内容并打开,若文件不存在,则创建文件并打开。

执行成功返回一个FILE结构体实例指针,执行失败返回0。

FILE结构体用于存储进程打开的一个文件属性,比如文件打开方式、文件读写位置、等等属性,文件打开之后会自动创建一个FILE实例并绑定,程序通过FILE实例确定要操作的文件。

关闭文件

打开的文件不再使用后需要执行关闭文件操作,释放对此文件占用的读写权限,并删除FILE实例。

关闭一个文件

#include <stdio.h>
int fclose(FILE * stream)

参数stream指定打开的文件,执行成功返回0,执行失败返回-1。

关闭所有文件

#include <stdio.h>
int fcloseall()

本进程打开的所有普通文件都将会关闭,在关闭之前会首先将所有写入操作完成,fcloseall总是返回0。

写入数据

写入一个字节

#include <stdio.h>
int fputc(int c, FILE *stream)

参数c,指定要写入的数据,虽然类型为int,但是在执行写入操作时会转换为char类型,赋值不应该超过255。
参数stream,指定要写入的文件。

执行成功返回参数c,执行失败返回-1。

#include <stdio.h>
int main()
{FILE * fp = fopen("/home/ali/a.txt", "a");if(fp != 0){int result = fputc('a', fp);if(result != -1){printf("文件写入成功\n");}else{printf("文件写入失败\n");}fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

写入一个数组

fwrite函数用于将一个数组写入文件。

#include <stdio.h>
size_t fwrite(void *ptr, size_t size, size_t n, FILE *stream)

参数ptr,指定要写入数组的地址。
参数size,指定数组元素长度,单位字节。
参数n,指定数组元素个数。
参数stream,指定要写入的文件。

返回值为成功写入数组元素的个数,若全部写入则返回值等于参数n,若只写入一部分则返回值小于参数n,若写入出错返回0。

#include <stdio.h>
int main()
{FILE * fp;size_t size;int a[5] = {1,2,3,4,5}; fp = fopen("/home/ali/a.db", "a");if(fp != 0){size = fwrite(&a, 4, 5, fp);printf("写入元素数量:%d\n", size);fclose(fp);}else{printf("文件打开失败\n");}return 0;
}


若要写入一个结构体,可以假设结构体包含在一个数组中,数组只有这一个结构体元素,注意结构体的实际长度需要计入地址对齐占用的存储单元,另外结构体中的字符串成员所有元素都会写入,包括末尾空字符。

#include <stdio.h>
int main()
{FILE * fp;size_t size;struct{char ali[20];char xyy[20];} zoo = {"阿狸", "喜羊羊"};fp = fopen("/home/ali/a.txt", "a");if(fp != 0){size = fwrite(&zoo, sizeof(zoo), 1, fp);printf("写入元素数量:%d\n", size);fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

写入字符串

fputs用于将一个字符串写入文件,写入数据不包含有效字符之后的空字符。

#include <stdio.h>
int fputs(const char *str, FILE *stream)

参数str,指定要写入字符串的指针。
参数stream,指定要操作的文件。

执行成功返回大于0的正数,执行失败返回-1。

#include <stdio.h>
int main()
{FILE * fp;int result;char a[] = "阿狸";fp = fopen("/home/ali/a.txt", "a");if(fp != 0){result = fputs(a, fp);fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

转换并写入

此函数在终端输出函数一节中使用过,可以将数学数据转换为字符串表示形式再写入,转换方式使用格式字符确定。

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...)    //可变参数

参数stream,设置要操作的文件。
参数format,设置要写入的字符串,可以使用格式字符。

执行成功返回写入的字节数,执行失败返回-1。

#include <stdio.h>
int main()
{FILE * fp = fopen("/home/ali/a.txt", "a");if(fp != 0){int result = fprintf(fp, "阿狸的年龄为:%d岁", 8);printf("写入结果:%d\n", result);fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

读写缓冲

向文件写入数据时并非立即执行写入操作,而是过一段时间执行,此段时间内若再次发生写入行为则会与之前的写入合并为一个,将短期内的多个写入行为合并为一个可以减少写入次数、减少存储碎片、增加执行效率,若写入操作需要立即执行,可以在写入数据后执行fflush函数。

#include <stdio.h>
int fflush(FILE *stream)

参数stream设置要操作的文件,设置为stdout则操作标准输出文件,执行成功返回0, 执行错误返回-1。

读取数据

读取一个字节

#include <stdio.h>
int fgetc(FILE *stream)

参数stream指定要读取文件,执行成功返回读取到的单字节数据,若已经读完所有数据或者因错误导致读取失败返回-1。

#include <stdio.h>
int main()
{FILE * fp = fopen("/home/ali/a.txt", "r");if(fp != 0){char a = fgetc(fp);if(a != -1){printf("读取到的数据:%c\n", a);}else{printf("读取数据失败\n");}fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

读取一个数组

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t n, FILE *stream)

参数ptr,指定存储数据的数组。
参数size,指定数组元素长度,单位字节。
参数n,指定数组元素个数。
参数stream,指定要读取的文件。

执行成功后返回成功赋值的元素个数,若读取到整个数组长度的数据则返回值等于参数n,若只读取到一部分数据则返回值小于参数n,若读取失败或已经读取到文件末尾返回0。

注:文件中应该存储相同类型的数据,否则读取数据会混乱。

#include <stdio.h>
int main()
{FILE * fp = fopen("/home/ali/a.db", "r");if(fp != 0){int a[100] = {0};size_t size = fread(a, 4, 5, fp);    //只读取5个数据if(size != 0){for(int i = 0; i < size; i++){printf("读取到的数据:%d\n", a[i]);}}else{printf("读取数据失败\n");}fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

读取字符串

fgets用于在文件中读取一个字符串,此函数在终端输入函数中介绍过,读取标准输入文件时若没有数据可读则会进入暂停等待状态,而读取普通文件时没有数据可读会直接返回0。

#include <stdio.h>
char * fgets(char *str, int size, FILE *stream)

参数str,指定接收数据的字符型数组。
参数size,指定数组的长度,单位为字节,每次最大读取量不会超过size指定的长度,因为每次都需要在字符串末尾添加一个空字符,所以单次最大读取有效字符数量为size减1。
参数stream,指定要读取的文件。

读取成功返回参数str,读取失败或已经读取到文件末尾返回0。

读取细节说明:
1.若文件内数据总长度低于参数size指定的长度,则实际读取到的字节量要小于size。
2.读取数据时遇到换行字符即终止,也就是每次最多读取一行,换行本身会保留在读取内容中,若换行之后还有数据,则剩余数据下次读取。

#include <stdio.h>
int main()
{FILE * fp;char * result;char a[100] = {0};fp = fopen("/home/ali/a.txt", "r");if(fp != 0){result = fgets(&a[0], 100 ,fp);if(result != 0){printf("读取到的字符串:%s\n", a);}else{printf("读取文件出错\n");}fclose(fp);}else{printf("文件打开失败\n");}return 0;
}

区分读写出错与读取为空

某些读写函数在读写出错时会返回-1,同时遇到文件为空、读取到文件末尾这类非错误型原因时也会返回-1,可以使用以下函数区分这两种情况。

#include <stdio.h>
int feof(FILE *fp)      //查询读写函数是否因非错误型原因而返回-1,是的话此函数返回非0值(一般为1),否则返回0,返回值可以直接当做布尔值使用
int ferror(FILE *fp)    //查询读写函数是否因读写错误而返回-1,是的话此函数返回非0值,否则返回0
#include <stdio.h>
int main()
{FILE * fp = fopen("/home/ali/a.txt", "w+");    //清空并打开,此时必定读取数据失败if(fp != 0){char result = fgetc(fp);if(result == -1){printf("读取数据失败\n");if(feof(fp)) printf("失败原因:文件为空\n");else printf("失败原因:读写函数出错\n");}fclose(fp);}else printf("文件打开失败\n");return 0;
}

读写位置

进程打开文件后会返回一个FILE实例,FILE内部使用一个变量记录读写文件时操作的文件内部地址,读写位置默认为0(若以追加数据方式打开文件,写入数据函数会首先将写入位置设置为文件末尾,之后执行写入操作),每次成功读写数据后读写位置都会自动增加,方便下次读写之后的位置。

FILE实例使用一个变量记录读写两种操作的位置,若一次打开的文件同时进行读写操作将导致混乱,所以一个打开的文件尽量只进行读或写一种操作,也就是使用r、a、w方式打开文件,若程序需要同时读写同一个文件,可以分别使用只读和只写两种方式打开文件。

修改读写位置

为了更灵活的进行读写操作,标准库提供了修改读写位置函数。

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence)

参数stream,设置操作的文件。
参数offset,设置读写位置增加的数据,若要减小则设置为负数。
参数whence,设置从哪个位置开始增加offset,可以设置为如下值:
SEEK_CUR,在当前读写位置增加。
SEEK_SET,在文件内容起始处增加。
SEEK_END,在文件内容末尾处增加。

执行成功返回0,执行失败返回-1。

#include <stdio.h>
int main()
{FILE * fp;char getcres;int seekres;fp = fopen("/home/ali/a.txt", "r");if(fp != 0){/* 读取第一个字节 */getcres = fgetc(fp);printf("第一次读取结果:%c\n", getcres);/* 跳过当前位置两个字节 */seekres = fseek(fp, 2, SEEK_CUR);printf("修改读写位置结果:%d\n", seekres);/* 读取第四个字节 */getcres = fgetc(fp);printf("第二次读取结果:%c\n", getcres);fclose(fp);}else{printf("打开文件失败\n");}return 0;
}

查询读写位置

long ftell(FILE *stream)

参数stream设置要操作的文件,返回值为查询到的位置,执行出错返回-1。


【字符串操作函数】

字符串查询

查询字符串有效长度

#include <string.h>
size_t strlen(const char *str)

参数str设置要查询的字符串,返回值为查询结果,长度不包括末尾空字符。

#include <stdio.h>
#include <string.h>
int main()
{char a[20] = "阿狸";printf("长度%d字节\n", strlen(a));    //返回6,utf8字符集中文字符长度最少3个字节return 0;
}

查询指定字符第一次出现的位置

#include <string.h>
char * strstr(char *haystack, const char *needle)

参数haystack,设置要查询的字符串,可以设置为一个字符串的中间下标地址,从而跳过某些元素不查询。
参数needle,设置查询使用的数据,此参数指向一个字符串,末尾需要有空字符。

strstr函数查询haystack字符串中是否包含needle字符串,若包含则返回needle数据第一次出现的下标地址,否则返回0。

#include <stdio.h>
#include <string.h>
int main()
{char a[20] = "abcdefg";char *result = strstr(a, "cd");if(result != 0){printf("查询到的地址为:%p\n""查询到的字符串为:%s\n",result, result);}else{printf("查询失败\n");}return 0;
}

若只是查询某个字符第一次出现的下标,也可以自己实现,代码很简单。

字符串转换

数学数据转字符串

#include <stdio.h>
int sprintf(char *str, const char *format, ...)                  //不限制数组越界访问
int snprintf(char *str, size_t size, const char *format, ...)    //限制数组越界访问

参数str,存储转换后的字符串。
参数size,指定str的长度,存储转换后的字符串时以此长度为准,最大有效字符长度为size减1,以保证末尾有空字符。
参数format,存储带有格式字符的字符串,格式字符参考printf。

执行成功返回转换后的有效字符总数,不包含末尾空字符,执行失败返回一个负数。

注:返回值不是参数str字符串赋值后的有效字符数量,若str长度不够,snprintf函数会将转换后的字符丢弃一部分,但是返回值依然为已经成功转换的有效字符总数,可以通过返回值确定转换后的字符串是否因长度过大而丢弃一部分。

#include <stdio.h>
int main()
{char a[5];int result = snprintf(&a[0], 5, "%d", 123456789);printf("转换后的字符串为:%s\n""snprintf返回值为:%d\n",a, result);return 0;
}

字符串转数学数据

#include <stdlib.h>
int atoi(const char *nptr)

参数nptr指定要转换的字符串,返回值为转换后的数据,若字符串为空、或者字符串不是表示十进制数学数据的字符串则返回0,若转换出错则返回-1。

#include <stdio.h>
#include <stdlib.h>
int main()
{printf("%d\n", atoi("99")+1);    //输出100return 0;
}

字符串修改

数组不支持整体修改,只能修改单个元素,这种限制对字符串来说很不方便,字符串经常有整体修改的需求,标准库提供了strcpy、strncpy函数将一个字符串的有效字符复制到另一个字符串中。

#include <string.h>
char * strcpy(char *dest, const char *src)               //不限制数组越界访问
char * strncpy(char *dest, const char *src, size_t n)    //限制数组越界访问

参数dest,设置接收数据的字符串,可以设置为字符串有效字符中间的地址,实现修改中间数据。
参数src,设置提供数据的字符串。
参数n,设置最多复制参数src字符串中的多少字节到参数dest字符串中,若src的有效字符长度小于n,则复制字节量以有效字符为准,n只是限制最大复制字节量,而不是固定复制n指定的字节量。

返回值为参数dest。

注:完成复制后,strncpy会在dest字符串的有效字符之后添加空字符,所以参数n的最大值应该为dest字符串总长度减1,否则可能无法添加空字符。

#include <stdio.h>
#include <string.h>
int main()
{char name[50] = "阿狸";strncpy(&name[0], "喜羊羊", 49);printf("%s\n", name);return 0;
}

字符串连接

#include <string.h>
char * strcat(char *dest, const char *src)               //不限制数组越界访问
char * strncat(char *dest, const char *src, size_t n)    //限制数组越界访问

参数dest,设置接收数据的字符串。
参数src,设置提供数据的字符串,此字符串将会复制到dest字符串有效字符之后。
参数n,设置最多复制参数src字符串中的多少字节到参数dest字符串中,若src有效字符长度小于n,则以有效字符长度为准。

返回值为参数dest。

注:完成连接后,strncat会在dest字符串的有效字符之后添加空字符,所以参数n的最大值应该为dest字符串总长度减1,否则可能无法添加空字符。

#include <stdio.h>
#include <string.h>
int main()
{char ali[50] = "阿狸";char xyy[50] = "喜羊羊";strncat(ali, xyy, 49);printf("%s\n", ali);return 0;
}

字符串分割

程序有时候需要将多个字符串存储在一个字符数组中,此时需要在多个字符串中间添加分割符,之后通过分割符拆分字符串,strtok函数用于实现分割,分割符一般设置为单字节,否则容易引发混乱。

#include <string.h>
char * strtok(char *str, const char *delim)

参数str,设置需要分割的字符串。
参数delim,设置分割使用的字符,delim指向一个字符串,末尾应该有空字符。

若参数str指向的字符串没有指定分割符,则返回参数str。
若参数str指向的字符串存在指定分割符,则返回此次查询到的分割符所在地址,并将str字符串中此次查询到的分割符设置为空字符编码,需要多次分割时多次执行此函数即可,再次执行strtok时参数str赋值为0。
若已分割完成则返回0。

注意:要分割的字符串没有指定的分割符时返回参数str,要分割的字符串有指定的分割符时第一次分割后也会返回参数str,所以需要首先保证参数str指向的字符串中存在指定的分割符,否则代码容易混乱。

#include <stdio.h>
#include <string.h>
int main()
{char name[100] = "阿狸/桃子/喜羊羊/美羊羊";char *result = strtok(name, "/");while(result != 0){printf("%s\n", result);result = strtok(0, "/");}/* 再次输出name,输出“阿狸”,/符号被转换成了空字符,之后的字符不再使用,需要人为指定数组下标调用之后的字符 */printf("字符串name:%s\n", name);return 0;
}

线程安全版

strtok并非线程安全的,为此标准库提供了线程安全版的strtok_r。

#include <string.h>
char *strtok_r(char *str, const char *delim, char **saveptr)

参数saveptr保存剩余未分割的字符串下标地址,下次继续调用此函数时,使用参数saveptr确定未分割的字符串下标。

#include <stdio.h>
#include <string.h>
int main()
{char name[100] = "阿狸/桃子/喜羊羊/美羊羊";char *strp;char *result;result = strtok_r(name, "/", &strp);while(result != 0){printf("%s\n", result);result = strtok_r(0, "/", &strp);}return 0;
}

字符串比较

比较两个字符串有效字符是否相同。

#include <string.h>
int strcmp(const char *s1, const char *s2)
int strncmp(const char *s1, const char *s2, size_t n)

参数n指定最多比较多少个字节,若空字符的位置小于此参数,则以空字符位置为准。

若两个字符串相同则返回0,若两个字符串不同则返回非0值,非0值并不是固定值,而是第一对不同字节的减法结果,若s1大于s2则返回正数,若s1小于s2则返回负数。

#include <stdio.h>
#include <string.h>
int main()
{char s1[20] = "阿狸";char s2[20] = "阿狸";char s3[20] = "桃子";int a = strcmp(s1, s2);int b = strcmp(s1, s3);printf("第一次比较结果%d\n""第二次比较结果%d\n",a, b);return 0;
}


【内存操作函数】

内存单元赋值

memset函数将一段内存中的每个单元赋值为指定值。

#include <string.h>
void * memset(void *s, int c, size_t n)

参数s,设置要操作的内存单元起始地址。
参数c,设置要写入的数据。
参数n,设置要操作的内存单元数量。

返回值为参数s。

#include <stdio.h>
#include <string.h>
int main()
{int a[10];memset(&a[0], 0, 40);return 0;
}

内存单元复制

memcpy函数将一段内存单元中的数据复制到另一段内存单元中。

#include <string.h>
void * memcpy(void *s1, const void *s2, size_t n)

参数s1,设置接收数据的内存单元起始地址。
参数s2,设置读取数据的内存单元起始地址。
参数n,设置要复制的字节数量。

返回值为参数s1。

#include <stdio.h>
#include <string.h>
int main()
{int a[5] = {1,2,3,4,5};int b[5] = {0};memcpy(&b[0], &a[0], 20);for(int i = 0; i < 5; i++){printf("%d\n", b[i]);}return 0;
}

内存单元比较

memcmp函数用于比较两段内存单元中的数据是否相同。

#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n)

参数s1,设置参与比较的第一段内存起始地址。
参数s2,设置参与比较的第二段内存起始地址。
参数n,设置参与比较的两段内存字节数量。

若参与比较的两段内存数据相同则返回0,若s1小于s2则返回一个负数,若s1大于s2则返回一个大于0的正数。

#include <stdio.h>
#include <string.h>
int main()
{char a[5] = {5,0,0,0,0};char b[5] = {5,0,0,0,0};char c[5] = {0,0,0,0,5};int result;result = memcmp(&a[0], &b[0], 5);    //比较a与bprintf("%d\n", result);              //相同,输出0result = memcmp(&a[0], &c[0], 5);    //比较a与cprintf("%d\n", result);              //大于,输出大于0的正数result = memcmp(&c[0], &a[0], 5);    //比较c与aprintf("%d\n", result);              //小于,输出负数return 0;
}

内存单元查询

#include <string.h>
void * memchr(const void *s, unsigned char c, size_t n)     //地址递增查询
void * memrchr(const void *s, unsigned char c, size_t n)    //地址递减查询

参数s,设置查询内存单元的起始地址。
参数c,设置查询的单字节数据。
参数n,设置查询内存单元的数量。

两个函数都会从参数s指定的地址处开始查询,每次查询一个字节,直到查询到指定数据或者查询完参数n指定的长度,若查询到指定数据则返回此数据的地址,没有查询到则返回0。

#include <stdio.h>
#include <string.h>
int main()
{char a[5] = {1,2,3,4,5};void * b = memchr(&a[0], 3, 5);if(b != 0){printf("查询到的地址:%lu\n", b);printf("查询到的数据:%d\n", *(char*)b);}else{printf("查询失败\n");}return 0;
}


【申请内存函数】

程序有时候需要在执行期间临时向操作系统申请内存使用,比如需要使用一个执行期间才能确定长度的数组,长度有可能是200字节,也有可能是20000字节,如果我们直接按照最大长度提前创建数组当然能解决问题,但是太过于浪费内存,为此操作系统提供了程序执行期间临时申请内存的功能。

申请内存

#include <stdlib.h>
void * malloc(size_t size)

参数size设置申请的内存长度,单位字节,申请成功返回内存起始地址,申请失败返回0。

#include <stdio.h>
#include <stdlib.h>
int main()
{char * str;str = (char*)malloc(1000);if (str != 0){printf("内存申请成功\n");//......free(str);    //使用完毕后需要释放内存}return 0;
}

释放内存

申请的内存使用完毕后需要使用free函数释放对内存的占用权,将内存的使用权交还给操作系统,若没有释放,则程序执行完毕后操作系统自动收回。

释放内存注意事项:
1.同一段内存禁止多次释放。
2.若释放的内存地址为0,则free不会进行任何操作。
3.若释放的内存地址为非0的非法值,则操作系统会限制执行,程序会执行出错并退出。
4.内存释放应该及时,有些程序会循环申请、释放内存。

为数组申请内存

calloc函数可以很方便的为一个数组申请内存,并且会将申请内存的每个字节赋值为0,而malloc不保证申请到的内存单元全部为0。

#include <stdlib.h>
void * calloc(size_t nr, size_t size)

参数nr,设置数组元素数量。
参数size,设置数组元素长度,calloc会计算两个参数相乘,计算结果作为申请内存长度。

#include <stdio.h>
#include <stdlib.h>
int main()
{int * p1 = (int*)calloc(100, 4);    //申请100个长度4字节的内存if (p1 != 0){printf("内存申请成功\n");//......free(p1);}return 0;
}

修改申请内存长度

若增加长度,则重新分配一段内存空间。
若减小长度,则在原有内存空间中释放一部分内存单元。

#include <stdlib.h>
void * realloc(void * ptr, size_t size)

参数prt,指定修改的内存起始地址。
参数size,指定修改后的长度。

修改成功返回新的内存起始地址,修改失败返回0。

#include <stdio.h>
#include <stdlib.h>
int main()
{int *p1, *p2;p1 = (int *)malloc(100);if(p1 != 0){printf("内存申请成功,内存地址为:%p\n", p1);p2 = (int *)realloc(p1, 200);if(p2 != 0){printf("内存修改成功,内存地址为:%p\n", p2);p1 = 0;}free(p2);}return 0;
}

从进程使用的栈空间分配内存

alloca函数返回进程使用栈空间中的一段内存地址,操作系统会将本进程所用栈空间中暂时不使用的一部分空间分配给此函数,申请的栈空间内存不使用后无需使用 free 释放,因为这段内存会一直属于此进程,直到进程终止。

栈空间内存的优势是读写速度快,常用于临时保存一个字符串,但是注意不要申请长度过大的内存,否则可能超出进程栈空间容量。

#include <alloca.h>
void * alloca(size_t size)

参数size指定申请内存的长度,申请成功返回内存起始地址,申请失败返回0。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/724757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tensorflow2.0笔记 - 常见激活函数sigmoid,tanh和relu

本笔记主要记录常见的三个激活函数sigmoid&#xff0c;tanh和relu&#xff0c;关于激活函数详细的描述&#xff0c;可以参考这里&#xff1a; 详解激活函数&#xff08;Sigmoid/Tanh/ReLU/Leaky ReLu等&#xff09; - 知乎 import tensorflow as tf import numpy as nptf.__ve…

『python爬虫』ip代理池使用 协采云 账密模式(保姆级图文)

目录 实现效果实现思路代码示例总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 实现效果 在官网原版demo基础上小改了一下,修正了接口错误(把2023改成2024就可以了),原版demo只能测试单个ip,我这里批量测试所有…

为什么MySQL中多表联查效率低,连接查询实现的原理是什么?

MySQL中多表联查效率低的原因主要涉及到以下几个方面&#xff1a; 数据量大: 当多个表通过连接查询时&#xff0c;如果这些表的数据量很大&#xff0c;那么查询就需要处理更多的数据&#xff0c;这自然会降低查询效率。 连接操作复杂性: 连接查询需要对参与连接的每个表中的数…

从零学习Linux操作系统 第三十二部分 ansible中剧本的应用

一、什么是playbook及playbook的组成 1.Playbook的功能 playbook 是由一个或多个play组成的列表 Playboot 文件使用YAML来写的 play就是一个个模块用列表的方式体现出来 playbook的语法是用YAML的预防进行书写的 2.YAML 简介 是一种表达资料序列的格式&#xff0c;类似XM…

【从零开始学GIS再到精通GIS】专题图制作-地图渲染-地图整饰

本篇主要介绍如何在gis中进行专题图制作-地图渲染-地图整饰&#xff1b;示例数据下载链接该网站更新了很多有关地理的数据。 1 数据准备&#xff1a;点、线、面等矢量数据、栅格数据的准备等&#xff08;下一更会详细介绍数据处理等方面的内容&#xff09;&#xff1b; 2 加载…

记录一则 线上域名证书更新及cdn证书更新

本篇为阿里云免费证书更新记录。 登录阿里云账号 搜索数字证书管理服务管理控制台 点击创建证书 输入你的域名 填写相关信息&#xff08;注&#xff1a;域名验证方式选择文件验证&#xff09; 等待审核通过&#xff08;时间不久&#xff0c;一般为半小时内&#xff09; …

Vue2高级篇

Vue高级 Vue生命周期 生命周期又称为生命周期回调函数、生命周期函数、生命周期钩子, 是Vue在运行过程中的关键时刻帮我们调用的一些指函数, 生命周期函数名字不可修改, 其中的this指向的是vm或组件实例对象. 常用的生命周期钩子: mounted: 发送ajax请求、启动定时器、绑定…

【Web安全】SQL各类注入与绕过

【Web安全】SQL各类注入与绕过 【Web安全靶场】sqli-labs-master 1-20 BASIC-Injection 【Web安全靶场】sqli-labs-master 21-37 Advanced-Injection 【Web安全靶场】sqli-labs-master 38-53 Stacked-Injections 【Web安全靶场】sqli-labs-master 54-65 Challenges 与62关二…

python并发编程:IO模型

一 IO模型 二 network IO 再说一下IO发生时涉及的对象和步骤。对于一个network IO \(这里我们以read举例\)&#xff0c;它会涉及到两个系统对象&#xff0c;一个是调用这个IO的process \(or thread\)&#xff0c;另一个就是系统内核\(kernel\)。当一个read操作发生时&#xff…

无代理方式实现VMware的迁移?详细解析

在当今数字化时代&#xff0c;数据的安全性和可用性对于企业至关重要。尤其是在VMware转变订阅策略后&#xff0c;原本永久订阅的产品转变为以年付费订阅的形式&#xff0c;导致客户不得不支付更多的费用&#xff0c;大幅增加了成本。同时&#xff0c;客户也对VMware未来发展前…

k8s-kubeapps图形化管理 21

结合harbor仓库 由于kubeapps不读取hosts解析&#xff0c;因此需要添加本地仓库域名解析&#xff08;dns解析&#xff09; 更改context为全局模式 添加repo仓库 复制ca证书 添加成功 图形化部署 更新部署应用版本 再次进行部署 上传nginx 每隔十分钟会自动进行刷新 在本地仓库…

人人都写过的6个bug

大家好&#xff0c;我是知微。 程序员写bug几乎是家常便饭&#xff0c;也是我们每个人成长过程中难以避免的一部分。 为了缓解这份“尴尬”&#xff0c;今天想和大家分享一些曾经都会遇到过的bug&#xff0c;让我们一起来看看这些“经典之作”。 1、数组越界 #include <…

Python爬虫:http和https介绍及请求

HTTP和HTTPS 学习目标&#xff1a; 记忆 http、https的概念和区别记忆 浏览器发送http请求的过程记忆 http请求头的形式记忆 http响应头的形式了解 http响应状态码 1 为什么要复习http和https 在发送请求&#xff0c;获取响应的过程中 就是发送http或https的请求&#xff0c…

DMA 链表模式(LLI)深度解析

在进行一次 DMA 读或者写的时候&#xff0c;可以配置多个链表&#xff0c;从而当一个链表的数据传输完成时&#xff0c;会跳到下一个链表的起始地址&#xff0c;并继续传输数据&#xff0c;直到链表的下一个地址为 0。如果 DMA 使能了完成中断&#xff0c;则当 DMA 发送或者接收…

程序计数器介绍

程序计数器是计算机处理器中的寄存器&#xff0c;它包含当前正在执行的指令的地址(位置)。当每个指令被获取&#xff0c;程序计数器的存储地址加一。在每个指令被获取之后&#xff0c;程序计数器指向顺序中的下一个指令。当计算机重启或复位时&#xff0c;程序计数器通常恢复到…

java微服务技术选型,Java学习的三个终极问题及学习路线规划

前言 在网络技术中基于浏览器的B/S结构无论在PC端还是手机端都充当着至关重要的角色。 PC端自不必说&#xff0c;手机中很多应用虽然是以APP的形式存在&#xff0c;但它采用的还是B/S结构。如今日头条、微信的朋友圈等&#xff0c;这些应用在内部封装了浏览器&#xff0c;后端…

宠物的异味,用空气净化器可以解决吗?宠物空气净化器品牌推荐

养猫的人都了解&#xff0c;一个养猫家庭的环境卫生和气味问题与主人的关系密切相关。主人的勤劳程度和对卫生的重视程度直接影响着家中的气味。尽管主人通常会经常更换猫砂&#xff0c;但有时候仍然会存在一些难闻的气味。事实上&#xff0c;忙碌的猫主人可能会因为没有足够的…

MySQL Strict Mode is not set for database connection ‘default‘

在使用 DJango 框架执行迁移文件的命令时&#xff0c;可以看到出现如下警告&#xff1a; (ll_env) D:\workspace\workspace-mengll\learning-log>python manage.py migrate System check identified some issues: WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set …

【yolov8自带脚本划分数据集】yolov8自己数据集训练

1. 命令 ultralytics.data.utils.autosplit(pathDATASETS_DIR / coco8/images, weights(0.9, 0.1, 0.0), annotated_onlyFalse)from ultralytics.data.utils import autosplitautosplit( path"path/to/images",weights(0.9, 0.1, 0.0), # (train, validation, test)…

利用Python爬取高德地图全国地铁站点信息

利用Python中的requests库进行地铁站点信息的获取,同时将数据保存在本机excel中 # 首先引入所需要的包 import requests from bs4 import BeautifulSoup import pandas as pd import json# 发送 GET 请求获取网页内容 url http://map.amap.com/subway/index.html response r…