-----------------------------------纸上得来终觉浅,绝知此事要躬行-----------------------------------
1.前言
本篇是在通讯录(C语言实现)-CSDN博客基础上增加了动态内存存储以及文件写入保存与读取的优化。
1.1 动态内存管理
malloc(unsigned int size[申请空间大小]):用于申请一片连续的内存空间,返回值是所申请内存空间的首地址,申请不成功返回NULL。
calloc(unsigned int num[申请空间个数],unsigned int size[每个空间所占大小]):和malloc类似,与malloc不同的是calloc在申请一片连续的内存空间后并对这篇内存空间进行初始化为“0”,返回值同样是所申请内存空间首地址,申请不成功返回NULL。
realloc(void *mem_address[需要改变内存首地址], unsigned int newsize[新内存大小]):改变空间大小,用于增加或者减小内存空间,realloc在执行过程中是在在内存中找一片和改变的新内存大小一样大且连续的内存空间,可能地址与需要改变的首地址相同也可能不同,然后将内容给复制粘贴到新地址的一片空间,返回值同样是所申请内存空间首地址,申请不成功返回NULL。
1.2 文件操作
1.2.1文件的打开
FILE * fopen ( const char * filename, const char * mode );
参数介绍:
const char * filename:为要打开文件的相对地址或者绝对地址。const char * mode:文件指针。
返回值:
打开成功返回文件首地址,失败返回NULL。
文件的打开方式:
文件使用方式 含义 如果指定的文件不在 “ r ” (只读) 为了输入数据,打开一个已经存在的文本文件 出错 “ w ” (只写) 为了输出数据,打开一个文本文件 建立一个新文件 “ a ” (追加) 向文本文件尾部添加数据 建立一个新文件 “ rb ”(只读) 为了输入数据,打开一个二进制文件 出错 “ wb ”(只写) 为了输出数据,打开一个二进制文件 建立一个新文件 “ ab ”(追加) 向二进制文件尾部添加数据 出错 “ r+ ”(读写) 为了读和写,打开一个文本文件 出错 “ w+ ”(读写) 为了读和写,建立一个新的文件 建立一个新文件 “ a+ ”(读写) 打开一个文件,在文件尾部进行读写 建立一个新文件 “ rb+ ”(读写) 为了读和写,打开一个二进制文件 出错 “ wb+ ”(读写) 为了读和写,新建一个新的二进制文件 建立一个新文件 “ ab+ ”(读写) 打开一个二进制文件,在文件尾部进行读和写 建立一个新文件
1.3 文件的顺序读写
功能 函数名 适用于 字符输入函数 fgetc 所有输入流 字符输出函数 fputc 所有输入流 文本行输入函数 fgets 所有输入流 文本行输出函数 fputs 所有输入流 格式化输入函数 fscanf 所有输入流 格式化输出函数 fprintf 所有输入流 二进制输入 fread 文件流 二进制输出 fwrite 文件流
1.3.1 fgetc函数
int fgetc ( FILE * stream );
功能:读取文件单个字符。
参数介绍:stream 为指向想要读取字符的文件的地址的指针。
返回值:
读取的字符的ASCII值,读取失败返回EOF(-1)。
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
//读文件
char ch;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
fclose(pf);
printf("\n")
pf = NULL;
}
1.3.2 fputc函数
int fputc ( int character , FILE * stream );
功能:往文件写入单个字符。
参数介绍:
int character:为想要写入的单个字符。
FILE * stream:为指向想要读取字符的文件的地址的指针。
返回值:
读取的字符的ASCII值,读取失败返回EOF(-1)。
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
//写文件
fputc('a', pf);
fclose(pf);
pf = NULL;
}
1.3.3 fgets函数
char * fgets ( char * str, int num, FILE * stream );
功能:读取文件中一串字符
参数介绍:
char * str:从文件中读取的字符串的接受数组首地址或者指针;
int num:为读取字符串的个数;
FILE * stream:为指向想要读取文件的地址的指针。
返回值:
读取成功返回字符串首地址,读取失败返回NULL。
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen::");
}
char arr[20];
fgets(arr,20, pf);
printf("%s", arr);
fclose(pf);
pf = NULL;
}
1.3.4 fputs函数
int fputs ( const char * str, FILE * stream );
功能:写入文件一串字符。
参数介绍:
const char * str:为想要写入的字符串;
FILE * stream:为指向写入字符串的文件的地址的指针。
返回值:
成功返回写入字符个数,失败返回EOF(-1)。
int main()
{
FILE* pf = fopen("text.txt", "a");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
//写一行
fputs("hello world\n", pf);
fclose(pf);
pf = NULL;
}
1.3.5 fscanf函数
int fscanf ( FILE * stream, const char * format, ... );
功能:格式化的方式读取文件中的数据。
参数介绍:FILE * stream:为指向写入格式化数据的文件的地址的指针;
const char * format:为写入数据的格式(例:%s、%d……);
返回值:
成功返回从文件读取的字符个数,失败返回EOF(-1)。
int main()
{
struct s s1;
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen::");
}
fscanf(pf, "%d %s", &s1.a, s1.arr);
printf("%d %s", s1.a, s1.arr);
fclose(pf);
pf = NULL;
}
1.3.6 fprintf函数
int fprintf ( FILE * stream, const char * format, ... );
功能:将格式化变量的数据写入文件。
参数介绍:FILE * stream:为指向写入格式化数据的文件的地址的指针;
const char * format:为写入数据的格式(例:%s、%d……)。
返回值:
成功返回写入字符的个数,失败返回EOF(-1)。
int main()
{
struct s s1 = { 1,"abcdef" };
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen::");
}
fprintf(pf, "%d %s", s1.a, s1.arr);
fclose(pf);
pf = NULL;
}
1.3.7 fread函数
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
功能:对文件进行二进制的读取数据。
参数介绍:void * ptr:为指向存储要读取到文件的数据的指针;
size_t size:为要读取的数据的总大小(单位:字节);
size_t count:为最多读几个 size 大小为单位的数据(单位:字节);
FILE * stream:为指向读取数据的文件的地址的指针。
返回值:
成功返回读入字符的个数,失败返回EOF(-1)。
int main()
{
struct s s1=;
FILE* pf = fopen("text.txt", "rb");
if (pf == NULL)
{
perror("fopen");
}
//二进制读取
fread(&s1, sizeof(struct s), 1, pf);
printf("%d %s", s1.a, s1.arr);
fclose(pf);
pf = NULL;
}
1.3.8 fwrite函数
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
功能:对文件进行二进制的写入数据。
参数介绍:const void * ptr:为指向存储要写入到文件的数据的指针;
size_t size:为要写入的数据的总大小(单位:字节);
size_t count:为最多写几个 size 大小为单位的数据(单位:字节);
FILE * stream:为指向写入数据的文件的地址的指针。
返回值:成功返回写入字符的个数,失败返回EOF(-1)。
int main()
{
struct s s1={129,"abcdef"};
FILE* pf = fopen("text.txt", "wb");
if (pf == NULL)
{
perror("fopen::");
}
//二进制写入
fwrite(&s1, sizeof(struct s),1, pf);
fclose(pf);
pf = NULL;
}
1.3.9 sprintf 函数 和 sscanf 函数
int sprintf ( char * str, const char * format, ... );
功能:
把一个格式化的数据写到字符串中,本质是把一个格式化的数据换成字符串
参数介绍:
char * str:为指向将格式化数据写入到字符串的地址的指针;
const char * format:为写入数据的格式(例:%s、%d……)。
返回值:
成功返回写入字符串长度,失败返回一个负数。
int sscanf ( const char * s, const char * format, ...);
功能:
把一个字符串读出一个格式化的数据。
参数介绍:const char * s:为指向存储字符串的地址的指针;
const char * format:为写入数据的格式(例:%s、%d……)。
返回值:
成功返回读入字符串长度,失败返回一个负数。
int main()
{
struct s s1={1,"wangzhengfeng"};
struct s s2 = { 0 };
char buf[50]={0};
sprintf(buf, "%d %s", s1.a, s1.arr); //s1-->buf
printf("%s\n", buf);
sscanf(buf, "%d %s", &s2.a, s2.arr);//buf-->s2
printf("%s %d\n", s2.arr,s2.a);
}
1.4 文件的关闭
int fclose ( FILE * stream );
参数介绍:
FILE * stream:FILE*的文件指针
返回值:
关闭成功返回0,失败返回EOF(-1)。
1.5 文件的随机存取
1.5.1 fseek函数
int fseek ( FILE * stream, long int offset, int origin );
功能:根据文件指针的位置和偏移量来定位文件指针。
参数介绍:FILE * stream:为指向文件的地址的指针;
long int offset:为文件指针的偏移量;
int origin:为文件指针偏移量的相对起始地址。
int main()
{
FILE* pf = fopen("text.txt", "r");
///feek
int n=ftell(pf);
printf("%d", n);
fseek(pf, 2, SEEK_SET);//起始偏移2字节
char ch=getc(pf);
printf("%c", ch);
fseek(pf, 2, SEEK_CUR);//指针所在位置偏移两字节
ch=getc(pf);
printf("%c", ch);
n=ftell(pf);
printf("%d", n);
fseek(pf, -1, SEEK_END);//末尾
ch = getc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
}
1.5.2 ftell函数
long int ftell ( FILE * stream );
功能:
返回文件指针相对于起始位置的偏移量。
参数介绍:
FILE * stream:为指向文件的地址的指针。
返回值:
相对起始位置偏移量,起始位置偏移量为0。
1.5.3 rwind函数
void rewind ( FILE * stream );
功能:
让文件指针的位置回到文件的起始位置。
参数介绍:
FILE * stream :为指向文件的地址的指针。
2.动态内存管理版本
在原基础上的改动。
1.结构体
typedef struct Contact
{
Infopeople *data;
int conunt;
int capacity;
}Contact;
2.初始化
int InitContact(Contact* c) {
c->conunt = 0;
c->data = (Infopeople*)calloc(DEFAULTVALUE, sizeof(Infopeople));
if (NULL == c->data)
{
printf("%s\n", strerror(errno));
return 1;
}
c->capacity = DEFAULTVALUE;
return 0;
}
3. 添加联系人
void Addpeople(Contact * c)
{
Inc_Capatity(c);
printf("请输入姓名:>\n");
scanf("%s", c->data[c->conunt].name);
printf("请输入年龄:>\n");
scanf("%d", &(c->data[c->conunt].age));
printf("请输入性别:>\n");
scanf("%s", c->data[c->conunt].sex);
printf("请输入电话:>\n");
scanf("%s", c->data[c->conunt].telephone);
printf("请输入地址:>\n");
scanf("%s", c->data[c->conunt].address);
printf("增加成功\n");
c->conunt++;
}
4.增加容量
void Inc_Capatity(Contact* c)
{
if (c->conunt == c->capacity)
{
Infopeople* ptr = (Infopeople*)realloc(c->data, (INC_CAP + c->capacity) * sizeof(Infopeople));
if (ptr == NULL)
{
printf("Addpeople::%s\n", strerror(errno));
return;
}
c->data = ptr;
c->capacity += INC_CAP;
ptr = NULL;
printf("增容成功\n");
}
}
5.释放内存
void DestroyContact(Contact* c) {
free(c->data);
c->data = NULL;
c = NULL;
}
3.动态内存管理+文件操作版本
3.1 初始化
3.1.1 文本录入版本的初始化
int InitContact(Contact* c) {
c->conunt = 0;
c->data = (Infopeople*)calloc(DEFAULTVALUE, sizeof(Infopeople));
if (NULL == c->data)
{
printf("%s\n", strerror(errno));
return 1;
}
c->capacity = DEFAULTVALUE-1;
//加载联系人信息
FILE* pfread = fopen("text.txt", "r");
assert(pfread);
if (NULL == pfread)
{
perror("InitContact");
return;
}
int ret = 1;
while (ret != -1)
{
Inc_Capatity(c);
ret = fscanf(pfread, "%s %d %s %s %s",
c->data[c->conunt].name,
&(c->data[c->conunt].age),
c->data[c->conunt].sex,
c->data[c->conunt].telephone,
c->data[c->conunt].address);
if (ret != -1)
{
c->conunt++;
}
}
fclose(pfread);
pfread = NULL;
return 0;
}
文本录入版本写入文件
void SaveContact(Contact* c)
{
assert(c);
FILE* pfwrite = fopen("text.txt", "w");
if (NULL == pfwrite)
{
perror("SaveContact");
return;
}
for (int i = 0; i < c->conunt; i++)
{
fprintf(pfwrite, "%s %d %s %s %s\n",
c->data[i].name,
c->data[i].age,
c->data[i].sex,
c->data[i].telephone,
c->data[i].address);
}
fclose(pfwrite);
pfwrite = NULL;
}
3.1.2 二进制录入版本
int InitContact(Contact* c) {
c->conunt = 0;
c->data = (Infopeople*)calloc(DEFAULTVALUE, sizeof(Infopeople));
if (NULL == c->data)
{
printf("%s\n", strerror(errno));
return 1;
}
c->capacity = DEFAULTVALUE-1;
//加载联系人信息
//二进制文本录入
FILE* pfread = fopen("text.txt", "rb");
assert(pfread);
if (NULL == pfread)
{
perror("InitContact");
return;
}
Infopeople tmp = { 0 };
while (fread(&tmp, sizeof(Infopeople), 1, pfread))
{
Inc_Capatity(c);
c->data[c->conunt] = tmp;
c->conunt++;
}
fclose(pfread);
pfread = NULL;
return 0;
}
二进制录入版本写入文件
void SaveContact(Contact* c)
{
assert(c);
//二进制版本
FILE* pfwrite = fopen("text.txt", "wb");
if (NULL == pfwrite)
{
perror("SaveContact");
return;
}
for (int i = 0; i < c->conunt; i++)
{
fwrite((c->data) + i, 1, sizeof(Infopeople), pfwrite);
}
fclose(pfwrite);
pfwrite = NULL;
}
4.结语
在动态内存空间的开辟和文件操作中存在诸多细节,比如我在写文件基于动态内存的文件操作通讯录的时候在每次开辟新空间开辟到4个的时候,第五个就会出现卡死现象,导致输入文件内的内容全部乱码,最后发现是初始化的时候需要先判断预置的空间是否能存储文件中读入的内存,如果不够则需要先开辟新的内存供给存储。
感谢大家观看,一起进步~~~~😆😆😆