C基础(七)文件操作

一、文件的打开和关闭

1.1 fopen函数

FILE *fopen(const char*path,const char*mode);

fopen打开文件成功则返回有效的FILE地址,失败则返回NULL
path就是指定打开文件的路径,可以是相对路径,也可以是绝对路径.

模式(mode)有以下几种:

  • r:以只读的方式打开文件,该文件必须存在,且需要读的权限;
  • rb:读写方式打开一个二进制文件,文件必须存在且需要读的权限,b选项在windows有效,windows所有文本都是\r\n结尾的,而不是\n结尾的,如果读文件的时候没有带b,那么系统会自动把\r吃掉;
  • r+: 和r类似,只是它还可以写文件(前提是文件必须存在);
  • w:打开只写文件,如果文件存在则文件长度清0,即该文件内容会消失,若文件不存在则建立文件,需要写的权限,在windows上会自动在\n前补齐\r;
  • w+:和w类似, 只是它还可以读文件;
  • wb:在Windows下有效,它可以避免系统自动在\n前添加\r字符;
  • a:以追加的形式打开只写文件,若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留),需要写的权限;
  • a+:和a类似,同时可以读和写

其中b选项是二进制的意思,可以和上面的所有模式组合使用。

注意:在windows上读写一个二级制文件,一般要加b,防止系统添加无畏的\r;但如果读写的是一个文本文件,那么不要加b,这样可以不用单独处理这个\r.
在Windows系统中,文本模式下,文件以"\r\n"代表换行,若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面追加"\r".即实际写入文件的是"\r\n".
在类unix/linux系统的文本模式下,文件是以"\n"代表换行的,所以linux系统的文本模式和二进制模式没有区别.

1.2 fclose函数

关闭打开的文件 ,和fopen配套使用,只要fopen成功返回,那就需要调用fclose释放资源。

fclose(FILE *stream);

二、读取和写入字符

2.1 getc/fgetc函数

读取字符

int getc(FILE *stream);

getc的参数是一个fopen成功打开的文件后返回的指针,getc返回的是一个char, 它的功能是以字节位单位读取文件内容, 文本文件的最后结束标记是-1,也就是一个EOF宏定义 #define EOF -1

#include <stdio.h>

int main()
{
    FILE *fp = fopen("user.txt", "r");
    char c;
    while ((c = getc(fp)) != EOF) //读取文件的每一个字符,直到末尾
    {
        printf("%c", c);
    }
    fclose(fp);
    return 0;
}

2.2 putc/fputc函数

输出字符

int putc(int c,FILE *stream);

参数1是要写入的char,参数2是fopen返回的文件指针。

#include <stdio.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("a.txt", "w");
    const char *s = "hello world";
    int i;
    for (i = 0; i < strlen(s); i++)
    {
        putc(s[i], fp); //向文件写入数据
    }
    putc('\n', fp); //添加换行符
    fclose(fp);
    return 0;
}

C语言中fgetc、fputc和getc、putc的区别是什么?

千万不要认为fgetc、fputc的f代表的是file,就是这两个函数是和文件有关的!得看看他们的函数声明,如下图:在这里插入图片描述
看到没,参数都是可以接收FILE指针的,其实那个f代表的其实是function。

fgetc和getc他们的区别并不是在他们的使用上,而是在他们的实现上!具体来说,就是带f的(fgetc、fputc)实现的时候是通过函数来实现的,而不带f(putc、getc)的,实现的时候是通过宏定义来实现的!关于他们的不同点,就拿getc和fgetc来说:

  1. getc的参数不应当是具有副作用的表达式。
  2. 因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。
  3. 调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。

2.3 案例-实现控制台输入文件名创建文件并支持写入功能

效果图如下:
在这里插入图片描述
打开目标文件
在这里插入图片描述
上代码:

#include <stdio.h>

int main(int argc, char **args)
{
    if (argc < 2)
        return 0;
    FILE *fp = fopen(args[1], "w");
    if (fp)
    {
        while (1)
        {
            printf("循环开始--\n");
            char c = getchar(); //从标准控制台中每次读取一个char,包括回车符
            printf("收到%c\n", c);
            if (c == '0')
                break;
            putc(c, fp);
            printf("--循环结束\n");
        }
        fclose(fp);
    }
    return 0;
}

2.4 案例-实现控制台输入文件名读取该文件的内容

效果图如下:
在这里插入图片描述

#include <stdio.h>

int main(int argc, char **args)
{
    if (argc < 2)
        return 0;

    FILE *fp = fopen(args[1], "r"); //根据用户输入的路径读取
    if (fp)
    {
        char c;
        while ((c = getc(fp)) != EOF)
        {
            printf("%c", c);
        }
        fclose(fp);
    }
    return 0;
}

2.5 案例-实现echo的功能

效果图:
在这里插入图片描述
查看a.txt文件内容如下:
在这里插入图片描述

#include<stdio.h>
#include<string.h>

int main(int argc,char **args)
{
	if(argc <2)
		return 0;
	char *s = args[1]; //字符串内容
	char *op = args[2]; // > or >> ,注意控制台输入需要转义, \> 或者 \>\>
	FILE *fp = NULL;
	if(strcmp(">",op)==0)
		fp = fopen(args[3],"w");  //新建
	if(strcmp(">>",op)==0)
		fp = fopen(args[3],"a");  //追加
	if(fp)
	{
		int i;
		for(i = 0;i<strlen(s);i++)
		{
			putc(s[i],fp);
		}
		putc('\n',fp);//末尾添加换行
		fclose(fp);
	}

	return 0;
}

或者

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **args)
{
    if (argc != 4)
    {
        printf("Usage: test \"hello\" > a.txt");
        return -1;
    }
    char *content = args[1];
    char *op = args[2];
    char *path = args[3];
    FILE *f = NULL;
    if (strcmp(op, ">") == 0)
    {
        f = fopen(path, "w");
    }
    else if (strcmp(op, ">>") == 0)
    {
        f = fopen(path, "a");
    }
    else
    {
        printf("Unknow option:%s\n", op);
        return -1;
    }
    if (f)
    {
        int size = strlen(content);
        for (int i = 0; i <= size; i++) // 注意:这里包含了size,这样字符串末尾的'\0'也能输出
        {
            putc(content[i], f);
        }
        //putc('\n',f);//末尾添加换行
        fclose(f);
    }
    return 0;
}

2.6 案例-实现文件拷贝的命令

#include<stdio.h>

int main(int argc, char **args)
{
    if (argc != 3)
    {
        printf("Usage:test a.txt b.txt");
        return -1;
    }
    FILE *src = fopen(args[1], "r");
    FILE *dst = fopen(args[2], "w");
    if (src && dst)
    {
        char c;
        while ((c = getc(src)) != EOF)
        {
            putc(c, dst);
        }
        fclose(src);
        fclose(dst);
    }
    return 0;
}

2.7 实现文件加解密

#include <stdio.h>
#include <string.h>

int main(int argc, char **args)
{
    if (argc != 4)
    {
        printf("Usage: pwd a.txt b.txt 0\n0:加密\n1:解密\n");
        return -1;
    }
    FILE *src = fopen(args[1], "r");
    FILE *dst = fopen(args[2], "a");
    char *op = args[3]; // 这里也可以使用args[3][0]取字符来对比
    if (src && dst)
    {
        char c;
        while ((c = getc(src)) != EOF)
        {
            if (strcmp(op, "0") == 0)
            {
                // 加密
                c++;
            }
            else if (strcmp(op, "1") == 0)
            {
                // 解密
                c--;
            }
            putc(c, dst);
        }
        fclose(src);
        fclose(dst);
    }
    return 0;
}

2.8 EOF与feof函数文件结尾

程序怎么才知道是否已经到达文件末尾了呢?EOF代表文件结尾,如果已经是文件的末尾,那么feof函数返回true,而EOF的值就是-1,在读文本文件的时候可以判断读到的字符是否是EOF或者调用feof返回true则表示读到文件的末尾了, 但是判断二进制文件则必须使用feof函数来判断. 因为字符的ASCII码不可能是EOF(-1), 但是二进制文件都是二进制数是可能出现-1的,所以二进制文件只能使用feof函数来判断。

int feof(FILE *stream);  

feof函数的滞后性
feof函数每次都是判断FILE里面的一个标记,只有当执行fgets、getc、fread函数的时候才会更新FILE内的标记,所以feof函数有滞后性。也就是说在fgets、getc、fread函数前调用feof的结果是上一次的结果。
下面看一个错误的示例

#include <stdio.h>

int main()
{
    FILE *fp = fopen("a.txt", "r");
    if (fp)
    {
        char ch;
        while (!feof(fp)) //feof函数需要在fgetc调用后才是最新的,所以当fgetc读到末尾的时候,此时feof判断还是上次的状态,也就是没有到末尾,这样就会多循环一次
        {
            ch = fgetc(fp);//当多循环一次的时候就会把EOF结束符也读取了.这样输出的结果就有问题了.
            printf("%c", ch);
        }
        fclose(fp);
        fp = NULL;
    }
    else
    {
        printf("打开文件失败");
    }
    return 0;
}

在这里插入图片描述
从输出结果可以看到多输出了一个字符.
正确的方式是这样的

#include <stdio.h>

int main()
{
    FILE *fp = fopen("a.txt", "r");
    if (fp)
    {
        char ch;
        while (1)
        {
            ch = fgetc(fp);
            if (feof(fp)) //改成在这里判断
                break;
            printf("%c", ch);
        }
        fclose(fp);
        fp = NULL;
    }
    else
    {
        printf("打开文件失败");
    }
    return 0;
}

或者这样

#include <stdio.h>

int main()
{
    FILE *fp = fopen("a.txt", "r");
    if (fp)
    {
        char ch;
        while ((ch = fgetc(fp)) != EOF)
        {
            printf("%c", ch);
        }
        fclose(fp);
        fp = NULL;
    }
    else
    {
        printf("打开文件失败");
    }
    return 0;
}

三、读取和写入一行文本

3.1 fprintf和fscanf函数

这2函数都是通过FILE *来对文本文件(字符)进行行的读写(每次都是操作数据),但不能用于操作二进制的文件(可执行程序,音乐等)。

int fscanf(FILE *stream, const char *format, ...); // 和sscanf功能一样,不同点是从文件中取解析
int fprintf(FILE *stream, const char *format, ...);// 和sprintf功能一样,不同点是向文件中输出

3.2 案例-将用户控制台输入的内容打印到文件中

#include<stdio.h>
#include<string.h>

int main()
{
	FILE *wf = fopen("a.txt","w");
	char buf[1024] = {0};
	while(1)
	{
		scanf("%s",buf);
		if(strcmp("exit",buf) ==0)
			break;
		fprintf(wf,"%s\n",buf);//按指定格式输出到文件中
	}
	fclose(wf);
	return 0;
}

3.3 fgets和fputs函数

这2个函数可以配套使用,一次读写一个字符串内容.

char *fgets(char *s, int size, FILE *stream); //每次读取一行字符串,包括换行符
int fputs(const char *s, FILE *stream); //每次输出一行字符串

下面实现从控制台输入内容到文件中保存

#include <stdio.h>
#include <string.h>

int main(int argc, char **args)
{
    if (argc < 2)
        return 0;
    FILE *wf = fopen(args[1], "w");
    while (1)
    {
        char buf[1024] = {0};
        fgets(buf, sizeof(buf), stdin); //从控制台读取数据到buf中,注意fgets还可以从文件中读取,第三个参数改成FILE *即可
        if (strncmp(buf, "exit", 4) == 0)
            break;

        fputs(buf, wf); //写到文件中
    }
    fclose(wf);

    return 0;
}

3.4 案例-实现文件拷贝

#include<stdio.h>

int main(int argc,char **args)
{
	if(argc < 3)
		return 0;
	FILE *rf = fopen(args[1],"r");
	FILE *wf = fopen(args[2],"w");

	if(rf)
	{
		while(!feof(rf))//只要没有到文件末尾,循环就继续
		{
			char buf[1024] = {0};
			fgets(buf,sizeof(buf),rf);//从文件中读取
			fputs(buf,wf);
		}
		fclose(rf);
		fclose(wf);
	}

	return 0;
}

3.5 在文件拷贝的基础是增加加解密操作

#include<stdio.h>

//加密
void encode(char *s)
{
	int len =0;
	while(s[len])
	{
		s[len++]++; //将字符串每个char移动一位达到加密
	}
}

//解密
void decode(char *s)
{
	int len=0;
	while(s[len])
	{
		s[len++]--;//将字符串每个char退一位达到解密
	}
}


int main(int argc,char **args)
{
	if(argc < 3)
		return 0;
	FILE *rf = fopen(args[1],"r");
	FILE *wf = fopen(args[2],"w");
	char op = args[3][0]; //加解密操作符

	if(rf)
	{
		while(!feof(rf))//只要没有到文件末尾,循环就继续
		{
			char buf[1024] = {0};
			fgets(buf,sizeof(buf),rf);//从文件中读取
			if(op == '0')
				encode(buf);
			else
				decode(buf);
			//写到目标文件
			fputs(buf,wf);
		}
		fclose(rf);
		fclose(wf);
	}

	return 0;
}

3.6 案例-实现大数据排序

假设有一个文件,里面的数据全是0~255的随机数,总大小有1G多, 如何能够快速的将文件内的数据进行排序呢?
思路:首先使用冒泡排序是行不通的,因为栈内存有限,数组的大小不能定义的太大,而且效率也比较低. 所以得换一种思路, 由于数据全是0~255的数据且有1G多,可见里面会有大量的重复数据,因此可以定义一个255个元素的数组,只需要记录每个随机数出现的次数即可,而数组下标就是随机数的排序.统计完成后就自动完成排序了.

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>

void makeRand(char *path,int count) //生成随机数
{
	FILE * wf = fopen(path,"w");
	int t = (int)time(NULL);
	srand(t);
	int i;
	for(i=0;i<count;i++)
	{
		int i = rand()%256;
		char buf[4] = {0};
		sprintf(buf,"%d\n",i);//将随机数存到数组
		fputs(buf,wf);//然后将数组写到文件
	}
	fclose(wf);
}


int main(int argc,char **args) //对文件内容进行排序
{
	if(argc <2 )
		return 0;

	char *op = args[2];
	if(strcmp("make",op) ==0)
		makeRand(args[1],atoi(args[3])); //生成随机数
	else
	{
		int arr[256] = {0}; //存放排序后的
		FILE *rf = fopen(args[1],"r");
		while(!feof(rf))
		{
			char buf[4]={0};
			fgets(buf,sizeof(buf),rf); //从文件读随机到字符数组中
			int i = atoi(buf);//字符串转数字
			arr[i]++; //统计出现的次数并且自动排序了,下标就是随机数,从0~255自动排序了
		}
		fclose(rf);

		//将排序后结果覆盖源文件
		FILE *wf = fopen(args[1],"w");
		int i,j;
		for(i = 0;i < 256;i++) //最多就256种随机数,i代表随机数
		{
			for(j= 0 ;j<arr[i];j++) //对每一种随机数输出j次
			{
				//数字转字符串
				char buf[4] = {0};
				sprintf(buf,"%d\n",i);
				//输出到文件
				fputs(buf,wf);
			}
		}
		fclose(wf);
	}
	return 0;
}

3.7 案例-实现读取源文件中的四则运算, 读取完成后需要把结果填上并更新源文件

例如源文件内容如下:

2+2
2*3
3/3
5-3

执行完毕后,需要将源文件修改为如下:

2+2=4
2*3=6
3/3=1
5-3=2

思路: 需要先读取源文件,读取每一行字符串后然后通过sscanf读取改字符串,取出每个操作变量和操作符进行四则运算并保存结果,接着使用sprintf将运算带结果的表达式输出到一个临时的字符数组中,然后在堆中动态分配内存创建字符串将每一个临时字符数组追加到字符串中,最后在将该字符串覆盖源文件即可。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int doCale(int i, int j, char c)
{
    int result = 0;
    switch (c)
    {
    case '+':
        result = i + j;
        break;
    case '-':
        result = i - j;
        break;
    case '*':
        result = i * j;
        break;
    case '/':
        result = i / j;
        break;
    }

    return result;
}

int main()
{
    FILE *rf = fopen("a.txt", "r");
    int lineCount = 1; // 记录行数
    char *s = NULL;    // 动态更新的结果
    while (1)
    {
        // 源文件每次读取一行的结果
        char buf[16] = {0};
        fgets(buf, sizeof(buf), rf); // 读到最后一行,feof不会立即返回true
        printf("读取的第%d行字符串:%s", lineCount, buf);

        // 解析出表达式的值
        int i, j;
        char c;
        sscanf(buf, "%d%c%d", &i, &c, &j);
        // 计算结果
        int result = doCale(i, j, c);
        // 包含该结果的一行数据
        char result_buf[16];
        sprintf(result_buf, "%d%c%d=%d\n", i, c, j, result);
        printf("运算表达式:%s", result_buf);
        // 动态申请堆空间
        s = (char *)realloc(s, sizeof(result_buf) * lineCount);
        // 将结果追加到s中
        strcat(s, result_buf);
        lineCount++;
        if (feof(rf))
        {
            printf("\n读取到文件末尾\n");
            break; // 已经到了最后一行,再调feof才会返回true
        }
    }
    // 关闭源文件
    fclose(rf);

    // 下面开始更新源文件
    FILE *wf = fopen("a.txt", "w");
    fputs(s, wf); //输出s中的内容

    fclose(wf);
    // 释放空间
    free(s);
    return 0;
}

运行结果如下:
在这里插入图片描述

3.8 案例-给定格式的文本内容,取出里面年龄第二大的人的姓名

例如文本内容如下:

姓名=aaa,年龄=20
姓名=bbbb,年龄=18 
姓名=ccc,年龄=30 
姓名=ddd,年龄=19 

实现代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 用于封装每一行数据的结构体
struct man
{
    char *name;
    int age;
};

int main()
{
    struct man *p = NULL;           // 定义结构体指针
    FILE *rf = fopen("a.txt", "r"); // 读取目标文件
    int index = 1;
    while (1)
    {
        char buf[1024];
        fgets(buf, sizeof(buf), rf); // 读取每一行数据,存到buf中
        if (feof(rf))
            break;
        // char name[100];
        // int age =0;
        // sscanf(buf,"姓名=%s,年龄=%d",name,&age);直接这样解析会有问题,它会把=号后面的都匹配成姓名了
        // printf("name=%s,age=%d\n",name,age);//输出的name=后面所有内容,而age=0,显然sscanf无法解析这种格式

        // 动态分配内存
        p = realloc(p, sizeof(struct man) * index);

        // 先按逗号分割每一行的字符串
        char *s;
        s = strtok(buf, ","); // 得到逗号前的  姓名=xxx ,s指针此时是指向'姓' 这个字符的地址
        s = &s[7];            // 取出等号后的名字, s[7]表示定位到第7个字符,然后通过&取它的地址重新赋值给s, 这样s指针指向的首地址就变成了=号后的第一个字符的地址了, "姓名=" 刚好占7个字节
                              // name成员是指针变量,需要先初始化
        p[index - 1].name = malloc(strlen(s) + 1);
        strcpy(p[index - 1].name, s);

        s = strtok(NULL, ",");
        s = &s[7];                  // 取出年龄,将s指向第7个字节的地址, "年龄="刚好也占7个字节
        p[index - 1].age = atoi(s); // 取出的年龄是字符串类型,需要转成int

        index++;
    }

    // 关闭文件
    fclose(rf);

    //给结构体排序,这里采用冒泡排序
    int i, j;
    int size = index - 1;
    for (i = 0; i < size; i++)
    {
        for (j = 0; j < size - i - 1; j++)
        {
            if (p[j].age > p[j + 1].age)
            {
                struct man tmp = p[j];
                p[j] = p[j + 1];
                p[j + 1] = tmp;
            }
        }
    }

    // 取出年龄第二大的人的姓名
    printf("年龄第二大的name=%s\n", p[size - 2].name);

    // 释放内存
    for (i = 0; i < size; i++)
    {
        free(p[i].name);
    }

    free(p);

    return 0;
}

四、获取文件的属性

使用stat函数可以获取文件的属性,包括文件的建立时间,大小等等,需要导入3个头文件,<sys/stat.h>,<sys/types.h>,<unistd.h>

int stat(const char * _Filename, struct stat * _Stat)

参数1:代表文件名
参数2:struct stat结构体

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	struct stat st;
	stat("a.txt",&st);
	int size = st.st_size; //得到文件的大小
	long time = st.st_atime; //得到最后一次访问的时间
	printf("size=%d,time=%ld\n",size,time);

	return 0;
}

输出结果如下:

size=87,time=1644801698

五、读写文本和二进制

使用fwrite和fread这2个函数可以以二进制形式操作文件,不局限于文本文件

size_t fread (void *buffer, size_t size, size_t count, FILE *stream) ;
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

参数1:任意类型的指针变量
参数2:每个单位的大小占多少个字节,通过sizeof获取类型占用的字节数
参数3:一共读写多少个单位
参数4:fopen返回的文件指针
只要参数2*参数3的大小等于参数1所指的内存区域大小一样就可以.
返回成功读/写的单位个数, 会受到参数2的影响,当参数2为1的时候,可以认为返回的值是读取/写入的字节数

5.1 fwrite函数

用于写二进制的数据

#include <stdio.h>

int main()
{
    FILE *wf = fopen("a.dat", "w");
    int a = 10;
    int size = fwrite(&a, sizeof(a), 1, wf); //向文件写出一个10,对应的二进制是0b1010
    fclose(wf);
    printf("size=%d\n", size); // size =1, 表示写了 1个sizeof(a)

    return 0;
}

而生层的文件大小就是4个字节.
在这里插入图片描述

5.2 fread函数

用于读二进制文件,参数和fwrite一样,只不过是变成读了.

#include <stdio.h>

int main()
{
    FILE *rf = fopen("a.dat", "r");
    int a;
    int size = fread(&a, sizeof(a), 1, rf);

    //输出十进制数
    printf("a=%d,size=%d\n", a, size); // a=10,size=1,表示读了1个sizeof(a)

    fclose(rf);

    return 0;
}

5.3 案例-用fread/fwrite实现逐个二进制数拷贝

#include<stdio.h>

int main(int argc,char **args)
{
	if(argc < 2)
		return 0;
	FILE *rf = fopen(args[1],"rb");  //b选项表示二进制方式操作
	if(rf == NULL)
		return 0;
	FILE *wf = fopen(args[2],"wb");  //b选项表示二进制方式操作
	if(wf == NULL)
		return 0;

	while(1)
	{
		char c = 0;
		fread(&c,1,1,rf); //每次读一个字节
		if(feof(rf))
			break;
		fwrite(&c,1,1,wf); //每次读一个字节
	}
	fclose(rf);
	fclose(wf);
	return 0;
}

上面的代码不仅可以用于操作二进制文件,也可以操作普通文本文件

5.4 案例-使用fread/fwrite实现二进制数批量拷贝

#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>

#define MAX 64*1024 //64k 单次拷贝最大的字节数

int main(int argc,char **args)
{
	if(argc < 2)
		return 0;
	FILE *rf = fopen(args[1],"rb");
	if(rf == NULL)
		return 0;
	FILE *wf = fopen(args[2],"wb");
	if(wf == NULL)
		return 0;

	int count =0;//统计拷贝的次数

	//得到文件大小
	struct stat st;
	stat(args[1],&st);
	long size = st.st_size;

	if(size >= MAX)
		size = MAX;
	//定义缓冲区
	char* buf = malloc(size);
	while(!feof(rf))
	{
		int len = fread(buf,1,size,rf); //返回读取了多少个单元, 每个单元是一个字节
		fwrite(buf,1,len,wf); //读多少单元就写多少单元
		count++;
	}
	fclose(rf);
	fclose(wf);
	free(buf);
	printf("copy count=%d\n",count);

	return 0;
}

5.5 读写结构体数据

结构体类型的数据其实是二进制数据

#include <stdio.h>

struct student
{
    char name[30];
    int age;
};

int main()
{
    // 创建一个结构体数组
    struct student st[3] = {{"abc", 12}, {"tom", 20}, {"jack", 34}};
    FILE *wf = fopen("st.dat", "w");
    // 把结构体数据写到文件
    fwrite(st, sizeof(struct student), 3, wf);
    // 如果要写单个结构体,可以这样
    fwrite(&st[0], sizeof(struct student), 1, wf);
    fclose(wf);

    //从文件中读取结构体
    FILE *rf = fopen("st.dat", "r");
    struct student buf[4]; //创建结构体数组
    fread(buf, sizeof(struct student), 4, rf);
    fclose(rf);

    int i;
    for (i = 0; i < 4; i++)
        printf("name=%s,age=%d\n", buf[i].name, buf[i].age);

    return 0;
}

注意:
fwrite是可以写二进制数据的,因此可以将结构体输出到文件中, 这里特别需要注意如果结构体内有指针成员,那么其实保存到文件的只是指针成员指向的地址,而不是堆中的数据,所以要小心,遇到这种情况应该单独定义输出的格式,或者能用char数组替代的就不要用char*了.

六、其他辅助函数的使用

6.1 fseek函数

该函数用于设置文件指针stream的位置。

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

第一个参数stream为文件指针
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET,表示的意思如下:
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾

如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。

实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。

#include <stdio.h>

int main()
{
    char a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    FILE *wf = fopen("a.dat", "w");
    fwrite(a, 1, sizeof(a), wf);
    fclose(wf);

    FILE *rf = fopen("a.dat", "r");
    fseek(rf, 2, SEEK_SET); //从文件开始位置向后移动2个字节,此时rf的指针指向数字3

    char buf[2];
    fread(buf, 1, sizeof(buf), rf);
    printf("%d,%d\n", buf[0], buf[1]); // 3,4,此时rf的指针指向数字5

    fseek(rf, -2, SEEK_CUR); // 从当前位置回退2个字节,上面fread的时候curr指向了5的位置,所以回退2位又来到了3的位置了
    fread(buf, 1, sizeof(buf), rf);
    printf("%d,%d\n", buf[0], buf[1]); // 3,4 因为FILE指针回退了2位

    fseek(rf, 0, SEEK_SET); // 回到文件开始位置
    fread(buf, 1, sizeof(buf), rf);
    printf("%d,%d\n", buf[0], buf[1]); // 1,2

    fseek(rf, 0, SEEK_END); //移动到文件末尾
    printf("%d,%d\n", buf[0], buf[1]); // 1,2这个输出其实是buf上次的数据没有清空,其实是读不到内容的
    fclose(rf);
    return 0;
}

使用fseek可以快速的生成一个大文件

#include <stdio.h>

int main()
{
    FILE *wf = fopen("a.dat", "w");
    //  int i;
    //	for(i=0;i<1000000;i++)
    //	{
    //		char a = 0;
    //		fwrite(&a,1,sizeof(a),wf);
    //	}

    //可以利用fseek函数实现
    fseek(wf, 9999999, SEEK_SET);
    char a = 0;
    fwrite(&a, 1, 1, wf);
    fclose(wf);
    return 0;
}

结果如下:
在这里插入图片描述

6.2 ftell函数

函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。

FILE *fp = fopen("a.txt","r");
long len = ftell(fp) 

技巧:当fseek移动到文件末尾,使用ftell就可以得到文件的大小.

#include <stdio.h>

int main(int argc, char **args)
{
    if (argc < 2)
        return 0;
    FILE *rf = fopen(args[1], "r");
    if (rf)
    {
        fseek(rf, 0, SEEK_END); // 先移动到文件末尾
        int len = ftell(rf);    // 然后再获取指针位置, 这样就得到了文件的大小了.
        printf("file len is:%d\n", len);
    }

    return 0;
}

6.3 fflush函数

c语言的所有文件操作函数都是缓冲区函数,仅当缓冲区满 了或者调用了fclose函数之后才会将内存数据写到磁盘中,fflush函数可以将缓冲区中任何未写入的数据写入文件中。成功返回0,失败返回EOF。

int fflush(FILE * _File);

由于fflush是实时的将缓冲区内容写到磁盘,所以不要大量的使用.

6.4 remove函数

remove函数删除指定文件

int remove(const char *_Filename);

参数Filename为指定的要删除的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔

6.5 rename函数

rename函数将指定文件改名

int rename(const char *_OldFilename,const char *_NewFilename);

参数1:_OldFilename为指定的要修改的文件名.
参数2:_NewFilename为修改后的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。

七、综合案例-实现一个学生信息录入和查询程序

效果图如下:
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct student
{
    char name[30];
    int age;
};

int insert()
{
    FILE *wf = fopen("st.dat", "a");
    struct student st;
    if (wf == NULL)
        return 0;
    while (1)
    {
        printf("please input name:");
        scanf("%s", st.name);
        if (strcmp("exit", st.name) == 0)
            break;

        printf("please input age:");
        scanf("%d", &st.age);

        fwrite(&st, sizeof(struct student), 1, wf); //写到文件
    }

    fclose(wf);
}

int search()
{
    FILE *rf = fopen("st.dat", "r");
    if (rf == NULL)
        return 0;

    struct student st;
    char success = 0;
    printf("请输出要查询的姓名,输入all查找所有:");
    char name[30];
    scanf("%s", name);

    while (1)
    {
        fread(&st, sizeof(struct student), 1, rf); //从文件中读
        if (feof(rf))
            break;
        if (strcmp("all", name) == 0)
        {
            printf("name=%s,age=%d\n", st.name, st.age);
            success = 1;
        }
        else if (strcmp(st.name, name) == 0)
        {
            printf("name=%s,age=%d\n", st.name, st.age);
            success = 1;
            break;
        }
    }
    if (!success)
        printf("not found\n");

    fclose(rf);
}

int delete ()
{
    FILE *rf = fopen("st.dat", "r");
    if (rf == NULL)
        return 0;

    fseek(rf, 0, SEEK_END);                          //移动到文件末尾
    int fileLenght = ftell(rf);                      //获取文件总长度
    int count = fileLenght / sizeof(struct student); //计算有多少个结构体
    fseek(rf, 0, SEEK_SET);                          //重置指针位置

    struct student *st;                          //创建结构体指针
    st = malloc(count * sizeof(struct student)); //一次性分配内存

    printf("请输入要删除的姓名:");
    char name[30];
    scanf("%s", name);

    while (1)
    {
        fread(st, sizeof(struct student), count, rf); //从文件中读
        if (feof(rf))
            break;
    }
    fclose(rf);

    FILE *wf = fopen("st.dat", "w");
    int i;
    for (i = 0; i < count; i++)
    {
        if (strcmp(st[i].name, name) == 0)
            continue;
        fwrite(&st[i], sizeof(struct student), 1, wf);
    }
    fclose(wf);
    free(st);
}

int main(int argc, char **args)
{
    if (argc < 2)
    {
        printf("请输入第二个参数, 1表示新增,2表示查询,3表示删除\n");
        return 0;
    }
    else if (args[1][0] == '1')
        insert();
    else if (args[1][0] == '2')
        search();
    else if (args[1][0] == '3')
        delete ();
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值