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);    //输出100
    return 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与b
	printf("%d\n", result);              //相同,输出0
	
	result = memcmp(&a[0], &c[0], 5);    //比较a与c
	printf("%d\n", result);              //大于,输出大于0的正数
	
	result = memcmp(&c[0], &a[0], 5);    //比较c与a
	printf("%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.mfbz.cn/a/434174.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

操作教程|使用MeterSphere对恒生UFX系统进行压力测试

恒生UFX&#xff08;United Finance Exchange&#xff0c;统一金融交换&#xff09;系统&#xff08;以下简称为“UFX系统”&#xff09;&#xff0c;是一款帮助证券公司统一管理外部接入客户的系统&#xff0c;该系统整体上覆盖了期货、证券、基金、银行、信托、海外业务等各类…

【CSP试题回顾】201604-1-折点计数

CSP-201604-1-折点计数 解题代码 #include <iostream> #include <vector> #include <algorithm> using namespace std;int n, pointSum;int main() {cin >> n;vector<int>myData(n);for (int i 0; i < n; i){cin >> myData[i];}// 统…

LinkedList集合源码分析

LinkedList集合源码分析 文章目录 LinkedList集合源码分析一、字段分析二、构造函数分析三、方法分析四、总结 看到实现了Deque 就要联想到这个数据结构肯定是属于双端队列了。Queue 表示队列&#xff0c;Deque表示双端队列。 一、字段分析 LinkedList 字段很少&#xff0c;就…

Python影像分析-根据卫星图像估算土壤湿度

在这篇文章中,我将讨论用于估计土壤湿度的最流行的模型,称为梯形模型。该模型源自卫星,这些卫星以一致的比例和分辨率提供地表温度和植被指数,例如归一化植被指数 (NDVI)。” 目录 🌟简介📥下载 Sentinel-3 表面温度和 NDVI 图像📉绘制 NDVI 与 LST(梯形空间)的图�…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

【C++专栏】C++入门 | 命名空间、输入输出、缺省参数

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;C专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家 点赞&#x1f44d;收藏⭐评论✍ C入门 | 命名空间、输入输出、缺省参数 文章编号&#xff1a;C入门 / 0…

基于stm32的电压采样与质量检测系统

150基于stm32的电压采样与质量检测系统[proteus仿真] 电压检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于stm32的电压采样与质量检测系统 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xf…

【递归搜索回溯专栏】专题一递归:汉诺塔

本专栏内容为&#xff1a;递归&#xff0c;搜索与回溯算法专栏。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;递归搜索回溯专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代…

java性能调优面试,程序员Java视频

前言 很多人在打算自学Java的时候或许都没有思考过Java的应用方向&#xff0c;市场需要什么样的人才&#xff0c;企业对你有什么要求等等一系列问题&#xff1b;或许你只听说这个行业薪资高…然后懵懵懂懂的上路&#xff0c;不得要害。 对于零基础来学习Java&#xff0c;你或…

Go 简单设计和实现可扩展、高性能的泛型本地缓存

相信大家对于缓存这个词都不陌生&#xff0c;但凡追求高性能的业务场景&#xff0c;一般都会使用缓存&#xff0c;它可以提高数据的检索速度&#xff0c;减少数据库的压力。缓存大体分为两类&#xff1a;本地缓存和分布式缓存&#xff08;如 Redis&#xff09;。本地缓存适用于…

正向代理和反向代理区别

正向代理和反向代理的区别&#xff1a; 特点正向代理反向代理位置位于客户端和目标服务器之间位于目标服务器和客户端之间代理对象代理服务器代表客户端发送请求到目标服务器代理服务器代表目标服务器接收客户端的请求配置客户端需要手动配置代理服务器客户端不需要知道代理服…

阻塞队列、生产者消费者模型、阻塞队列的模拟实现等干货

文章目录 &#x1f490;生产者消费者模型&#x1f490;模拟实现阻塞队列&#x1f4a1;注意点一&#x1f4a1;注意点二 阻塞队列是一种“特殊”的数据结构&#xff0c;但是也遵循队列的“先进先出”特性&#xff0c;它的特殊在于&#xff1a; 阻塞队列的两个特性&#xff1a; 1…

Java精品项目--第7期基于SpringBoot的驾校后台管理系统的设计分析与实现

项目使用技术栈 JavaSpringBootMavenMySQLMyBatisShiroHTMLVue.js&#xff08;非前后端分离&#xff09;… 项目截图

vue3组件通信有哪几种方式?

文章目录 一、父子通信1、props2、模板引用ref和defineExpose 二、跨层级传递数据provid和inject 一、父子通信 1、props 父组件中给子组件绑定属性子组件内通过props选项接收 子传父&#xff0c;通过defineEmits,先声明事件&#xff0c;再emit触发 2、模板引用ref和define…

LabVIEW高精度天线自动测试系统

LabVIEW高精度天线自动测试系统 系统是一个集成了LabVIEW软件的自动化天线测试平台&#xff0c;提高天线性能测试的精度与效率。系统通过远程控制测试仪表&#xff0c;实现了数据采集、方向图绘制、参数计算等功能&#xff0c;特别适用于对天线辐射特性的精确测量。 在天线的…

HashMap 源码解读

文章目录 一、什么是HashMap HashMap 是一种快速的查找并且插入、删除性能都良好的一种 K/V键值对的数据结构&#xff0c;key唯一&#xff0c;value允许重复它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是非线程安全的。 二、HashMap的数据结…

使用MockJS模拟数据,如何获取入参?

场景描述 在使用MockJS进行模拟数据的时候&#xff0c;会遇到一种场景&#xff0c;当参数1时&#xff0c;展示A类数据&#xff0c;当参数B时&#xff0c;展示B类数据&#xff0c;为了实现这场景&#xff0c;那就要在模拟数据时拿到请求参数&#xff1f; 实现逻辑 mock方法的…

【详识C语言】自定义类型之三:联合

本章重点 联合 联合类型的定义 联合的特点 联合大小的计算 联合&#xff08;共用体&#xff09; 联合类型的定义 联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09;…

【自然语言处理】【大模型】BitNet:用1-bit Transformer训练LLM

BitNet&#xff1a;用1-bit Transformer训练LLM 《BitNet: Scaling 1-bit Transformers for Large Language Models》 论文地址&#xff1a;https://arxiv.org/pdf/2310.11453.pdf 相关博客 【自然语言处理】【大模型】BitNet&#xff1a;用1-bit Transformer训练LLM 【自然语言…

OBS插件开发(二)推流实时曲线

不发视频了&#xff0c;截个图算了&#xff0c;嫌麻烦 1&#xff0c;自定义QWidget图表绘制 &#xff0c;动态更新 2&#xff0c;OBS直播帧率&#xff0c;码率监控 3&#xff0c;主要用于前端推流状况可视化&#xff0c;异常报警&#xff0c;及时性&#xff0c;无人值守直播
最新文章