文章目录
C语言第十一天课程笔记
每一天的笔记包含如下内容:
- 当天授课内容安排
- 课堂重点内容笔记
- 课后思考题
1. 内容安排
第一节课: 按字符读写文件
第二节课: 判断文件结束(EOF、feof函数)、实现文件拷贝
第三节课: 行文件读写(fputs、fgets、strtok、strchr)
第四节课: 块文件读写(fread、fwrite)
第五节课: 随机文件读写(fseek、fread、rewind、fwrite)
第六节课: 案例-登陆读写账号和密码
数据展示(客户端) 业务逻辑(功能实现) 数据持久化(数据存储)
- printf scanf 实现 cmd 命令行客户端的交互,数据展示。QT第三方C++库,GUI库。
- C语言的流程控制、函数语法,实现业务逻辑。 C++面向语言泛型。系统编程(Linux系统)
- 文件操作: 数据持久化。数据库。
行业结合。
2. 字符文件读写
-
打开和关闭文件
-
FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。
声明FILE结构体类型的信息包含在头文件“stdio.h”中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。
-
fopen
#include <stdio.h> FILE* fopen(const char * filename, const char * mode); 功能:打开文件 参数: filename:需要打开的文件名,根据需要加上路径 mode:打开文件的模式设置 返回值: 成功:文件指针 失败:NULL
-
fclose
#include <stdio.h> int fclose(FILE * stream); 功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。 参数: stream:文件指针 返回值: 成功:0 失败:-1
-
-
文件打开模式
打开模式 含义 r或rb 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) w或wb 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a或ab 以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 r+或rb+ 以可读、可写的方式打开文件(不创建新文件) r+或rb+ 以可读、可写的方式打开文件(不创建新文件) w+或wb+ 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) a+或ab+ 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件 -
文件关闭
-
打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
-
一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败
-
如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。
-
-
文件字符读写
-
fputc
#include <stdio.h> int fputc(int ch, FILE * stream); 功能:将ch转换为unsigned char后写入stream指定的文件中 参数: ch:需要写入文件的字符 stream:文件指针 返回值: 成功:成功写入文件的字符 失败:返回-1
-
fgetc
#include <stdio.h> int fgetc(FILE * stream); 功能:从stream指定的文件中读取一个字符 参数: stream:文件指针 返回值: 成功:返回读取到的字符 失败:-1
-
-
示例代码:
- 文件打开与关闭
// 1. 文件打开关闭 void test01() { // r 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) // 下面写法不推荐 // fopen("C:\\Users\\cpp\\Documents\\codes\\day12\\day12\\demo1.txt", "r"); // 大部分系统都支持下面的路径写法(绝对路径) // fopen("C:/Users/cpp/Documents/codes/day12/day12/demo1.txt", "r"); // 相对路径 FILE *fp = fopen("./demo1.txt", "r"); // 返回的 FILE 指针,指向了一块内存,这块内存就存储了当前打开文件的信息 // FILE 指针指向的空间是由系统帮我们申请的 // 文件打开失败,返回 NULL if (NULL == fp) { printf("文件打开失败!\n"); return; } // 关闭文件 // 如果打开文件没有关闭,当程序结束的时候,文件也被关闭。 // 文件使用完毕,要及时释放。 // 1. fp 指向的内存就会被释放. // 2. 刷新缓冲区,会将缓冲区中的文件内容写入到磁盘中 // 3. 对于程序,可打开的文件数量有上限的。 fclose(fp); fp = NULL; }
3. 文件结束
-
EOF
在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。 #define EOF (-1)
-
feof 函数
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
函数声明:
#include <stdio.h> int feof(FILE * stream); 功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置内容。 参数: stream:文件指针 返回值: 非0值:已经到文件结尾 0:没有到文件结尾
案例代码:
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
// 1. 字符文件读写 fputc fgetc
void test01()
{
// 1. 文件打开
FILE *fp = fopen("./demo2.txt", "r");
if (NULL == fp)
{
printf("文件打开失败!\n");
return;
}
// 2. 读取文件内容
#if 0
char c = fgetc(fp);
printf("c = %c\n", c);
c = fgetc(fp);
printf("c = %c\n", c);
c = fgetc(fp);
printf("c = %c\n", c);
// 文本文件的结尾符号就是 -1
c = fgetc(fp);
printf("c = %d\n", c);
#endif
char ch = 0;
// end of file
while ((ch = fgetc(fp)) != EOF)
{
printf("%c", ch);
}
// 文件关闭
fclose(fp);
fp = NULL;
}
void test02()
{
FILE *fp = fopen("./demo2.txt", "r");
if (NULL == fp)
{
printf("文件打开失败!\n");
return;
}
// 2. 读取文件内容
char contents[128] = { 0 };
int index = 0;
while ((contents[index] = fgetc(fp)) != EOF)
{
++index;
}
// 3.
printf("contents = %s\n", contents);
// 文件关闭
fclose(fp);
fp = NULL;
}
void test03()
{
// w 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
FILE *fp = fopen("./demo3.txt", "w");
if (NULL == fp)
{
printf("文件打开失败!\n");
return;
}
char *s = "hello world";
// 写入文件内容 fputc
for (int i = 0; i < strlen(s); ++i)
{
fputc(s[i], fp);
}
// 关闭文件
fclose(fp);
fp = NULL;
printf("文件写入成功!\n");
}
// 2. EOF和feof函数
// feof 适用于文本文件、二进制文件(先读再判断是否结束)
// EOF 适用于文本文件
void testp4()
{
FILE *fp = fopen("./demo2.txt", "r");
if (NULL == fp)
{
printf("文件打开失败!\n");
return;
}
// 2. 读取文件内容
char ch = 0;
// "abc[-1]"
while (1)
{
ch = fgetc(fp);
// 如果 feof 函数返回 true, 表示文件结束
if(feof(fp))
{
break;
}
printf("%c %d\n", ch, ch);
}
// 文件关闭
fclose(fp);
fp = NULL;
}
int main()
{
testp4();
system("pause");
return EXIT_SUCCESS;
}
#endif
4. 实现文件拷贝
实现思路:
- 输入待拷贝的文件路径.
- 输入拷贝的目的地路径.
- 打开两个文件:
- 待拷贝文件: 以读的方式打开
- 目的地文件: 以写的方式打开
- 文件拷贝
- 从待拷贝文件中读取一个字符
- 将该字符写入到目的地文件
- 关闭两个文件。
void test()
{
// 1. 获得文件路径
char file_src[128] = { 0 };
char file_dst[128] = { 0 };
printf("请输入待拷贝文件路径:");
scanf("%s", file_src);
printf("请输入目的地文件路径:");
scanf("%s", file_dst);
// 2. 打开文件
FILE *fp_read = fopen(file_src, "r");
if (NULL == fp_read)
{
printf("文件: %s 打开失败!\n", file_src);
return;
}
FILE * fp_write = fopen(file_dst, "w");
if (NULL == fp_write)
{
// 关闭前面打开的文件
fclose(fp_read);
fp_read = NULL;
printf("文件: %s 打开失败!\n", file_dst);
return;
}
// 3. 拷贝实现
char ch = 0;
// 从待拷贝文件中读取内容
while ((ch = fgetc(fp_read)) != EOF)
{
// 将读取的内容写入到目的地文件中
fputc(ch, fp_write);
}
// 4. 关闭文件
fclose(fp_read);
fp_read = NULL;
fclose(fp_write);
fp_write = NULL;
printf("%s 拷贝到 %s 成功!\n", file_src, file_dst);
}
5. 行文件读写
-
fputs
#include <stdio.h> int fputs(const char * str, FILE * stream); 功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0' 不写入文件。 参数: str:字符串 stream:文件指针 返回值: 成功:0 失败:-1
-
fgets
#include <stdio.h> char * fgets(char * str, int size, FILE * stream); 功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。 参数: str:字符串 size:指定最大读取字符串的长度(size - 1) stream:文件指针 返回值: 成功:成功读取的字符串 读到文件尾或出错: NULL
-
案例
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> // 1. 把内容写入到文件中 void test01() { // 1. 打开文件 FILE *fp = fopen("./demo6.txt", "w"); if (NULL == fp) { return; } // 2. 写文件 #if 0 char *s = "hello world"; // 从 s 字符串第一个字符开始到 \0 之前的内容写入到文件中 fputs(s, fp); #endif // 字符串指针数组 char *name[] = { "Obama\n", "Trump\n", "Cliton\n", "Bush\n", "Polly\n", "Smith\n" }; for (int i = 0; i < sizeof(name)/ sizeof(name[0]); ++i) { fputs(name[i], fp); } // 3. 关闭文件 fclose(fp); fp = NULL; } // 2. 从文件中读取出来 void test02() { // 1. 打开文件 FILE *fp = fopen("./demo6.txt", "r"); if (NULL == fp) { return; } // 2. 读文件 while(1) { // 申请空间,用于存储文件内容 char line[128] = { 0 }; // 从文件中一次读取一行 fgets(line, 128, fp); // 判断文件是否结束 if (feof(fp)) { break; } // 打印文件内容 printf("%s", line); } // 3. 关闭文件 fclose(fp); fp = NULL; } // 统计文件 demo6.txt 文件中有多少行 int file_count() { // 1. 打开文件 FILE *fp = fopen("./demo6.txt", "r"); if (NULL == fp) { return; } // 2. 读文件 int cnt = 0; // 记录文件行数 while (1) { // 申请空间,用于存储文件内容 char line[128] = { 0 }; // 从文件中一次读取一行 fgets(line, 128, fp); // 判断文件是否结束 if (feof(fp)) { break; } // 累加文件行数 ++cnt; } // 3. 关闭文件 fclose(fp); fp = NULL; // 返回文件行数 return cnt; } void test03() { // 1. 获取文件行数 int line_cnt = file_count(); printf("line_cnt = %d\n", line_cnt); // 2. 根据文件行数开辟字符串指针数组 char **lines = malloc(sizeof(char*) * line_cnt); // 3. 再次从头按行读取文件内容,根据内容长度开辟空间,并存储内容 FILE *fp = fopen("./demo6.txt", "r"); if (NULL == fp) { return; } // 读取文件内容 int idx = 0; while (1) { char content[128] = { 0 }; // 读取每一行数据 fgets(content, 128, fp); if (feof(fp)) { break; } // 计算当前字符串的长度 int len = strlen(content) + 1; // 给字符串指针开辟空间 lines[idx] = malloc(len); // 将 content 中的数据拷贝到新开辟的堆空间中 strcpy(lines[idx], content); // 累加下标 ++idx; } fclose(fp); fp = NULL; // 4. 打印文件内容 for (int i = 0; i < line_cnt; ++i) { printf("%s", lines[i]); } // 5. 释放堆内存空间 for (int i = 0; i < line_cnt; ++i) { if (lines[i] != NULL) { free(lines[i]); lines[i] = NULL; } } free(lines); lines = NULL; }
案例:
// 1. strtok
void test01()
{
char s[] = "aa#bb#cc";
// 1. 第一次传入第一个参数 char* 字符串
char *p = strtok(s, "#");
printf("p = %s\n", p);
if (s[2] == '\0')
{
printf("第一个#号被替换成了\\0\n");
}
// 2. 第二个以后就不需要传入,第一个参数写 NULL
p = strtok(NULL, "#");
printf("p = %s\n", p);
if (s[5] == '\0')
{
printf("第一个#号被替换成了\\0\n");
}
p = strtok(NULL, "#");
printf("p = %s\n", p);
p = strtok(NULL, "#");
printf("p = %s\n", p);
// 注意: strtok 所分割的字符串必须可写
}
// 2. strchr strstr
// strchr 在字符串查找指定字符,返回第一个匹配的字符地址. 查找失败返回 NULL
// strstr 在字符串中查找子串,返回子串首字符的地址,查找失败返回NULL
void test02()
{
char *s = "abcdef";
// 如果找到,返回该字符的地址
// 如果找不到,返回空指针
char *pos = strchr(s, 'g');
if (pos != NULL)
{
printf("pos = %c\n", *pos);
}
else
{
printf("找不到!\n");
}
}
void test03()
{
char *s = "abcdefg";
char *pos = strstr(s, "def");
// 保存子串的内存
char buf[32] = { 0 };
if (pos != NULL)
{
printf("pos = %s\n", pos);
// 从字符串中将查找到的子串,扣出来
strncpy(buf, pos, 3);
}
else
{
printf("查找失败!\n");
}
printf("buf = %s\n", buf);
}
void test04()
{
FILE *fp = fopen("./demo7.txt", "r");
if (NULL == fp)
{
return;
}
// 1. 按行读内容 10+20
while (1)
{
char line[64] = { 0 };
fgets(line, 64, fp);
// 判断是否文件结束
if (feof(fp))
{
break;
}
// 2. 查找+号的位置
char *p = strchr(line, '+');
// 3. +号左侧的内容抠出来,就是左操作数
char left[32] = { 0 };
// 在数组中,两个指针相减可以获得他们之间相差元素的个数
strncpy(left, line, p - line);
// 4. +号右面的内容抠出来,就是右操作数
char right[32] = { 0 };
// 拷贝完的字符串,最后一个字符是\n
// 需要将最有一个字符 \n 换成 \0
strcpy(right, p + 1);
// 123\n\0
right[strlen(right) - 1] = '\0';
// 5. 使用 atoi 函数,将字符串转换成数字
int l = atoi(left);
int r = atoi(right);
// 6. 计算并输出结果
printf("%d+%d=%d\n", l, r, l + r);
}
fclose(fp);
fp = NULL;
}
6. 格式化文件读写
printf sprintf fprintf
-
fprintf
#include <stdio.h> int fprintf(FILE * stream, const char * format, ...); 功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。 参数: stream:已经打开的文件 format:字符串格式,用法和printf()一样 返回值: 成功:实际写入文件的字符个数 失败:-1
-
fscanf
#include <stdio.h> int fscanf(FILE * stream, const char * format, ...); 功能:从输入流(stream)中读入数据,存储到 argument 中,遇到空格和换行时结束。 参数: stream:已经打开的文件 format:字符串格式,用法和scanf()一样 返回值: 成功:参数数目,成功转换的值的个数 失败: - 1
-
案例代码
void test01() { FILE *fp = fopen("./demo8.txt", "w"); if (NULL == fp) { return; } fprintf(fp, "%d+%d\n", 10, 20); fprintf(fp, "%d+%d\n", 20, 30); fprintf(fp, "%d+%d\n", 30, 40); fprintf(fp, "%d+%d\n", 40, 50); fprintf(fp, "%d+%d\n", 50, 60); fclose(fp); fp = NULL; } void test02() { FILE *fp = fopen("./demo8.txt", "r"); if (NULL == fp) { return; } // fscanf 碰到换行符或者空格就会终止匹配 int a = 0; int b = 0; while (1) { fscanf(fp, "%d+%d", &a, &b); if (feof(fp)) { break; } printf("%d+%d=%d\n", a, b, a + b); } fclose(fp); }
7. 块文件读写
-
fwrite
#include <stdio.h> size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:以数据块的方式给文件写入内容 参数: ptr:准备写入文件数据的地址 size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小 nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb stream:已经打开的文件指针 返回值: 成功:实际成功写入文件数据的块数目,此值和nmemb相等 失败:0
-
fread
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:以数据块的方式从文件中读取内容 参数: ptr:存放读取出来数据的内存空间 size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小 nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb stream:已经打开的文件指针 返回值: 成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。 失败:0
-
案例:结构体数据读写文件
单个结构体变量读写:
struct Person { char name[32]; int age; }; void test01() { // 创建了结构体变量 struct Person person = { "Obama", 50 }; // 数据写入到文件中,都是二进制 // 变量在内存中都是以二进制的方式存储 // 将变量在内存中的字节,一个一个的写到文件中 FILE *fp = fopen("./demo9.txt", "wb"); if (NULL == fp) { return; } // fputc fputs fprintf 一般都用于文本文件读写 // fread fwrite 一般用于二进制文件读写 fwrite(&person, sizeof(struct Person), 1, fp); fclose(fp); fp = NULL; } void test02() { FILE *fp = fopen("./demo9.txt", "rb"); if (NULL == fp) { return; } // 申请Person大小的空间 struct Person p = {0}; printf("Name:%s Age:%d\n", p.name, p.age); fread(&p, sizeof(struct Person), 1, fp); printf("Name:%s Age:%d\n", p.name, p.age); fclose(fp); fp = NULL; }
结构体数组读写:
struct Person { char name[32]; int age; }; void test03() { // 创建Person数组,并且将数组存储到文件中 struct Person ps[] = { {"Obama", 99}, {"Trump", 89}, {"Clinton", 100} }; FILE *fp = fopen("./demo10.txt", "wb"); if (NULL == fp) { return; } // 将数组写入到文件中 fwrite(ps, sizeof(struct Person), 3, fp); fclose(fp); fp = NULL; } void test04() { FILE *fp = fopen("./demo10.txt", "rb"); if (NULL == fp) { return; } // 从文件中读取结构体数组 struct Person ps[3]; fread(ps, sizeof(struct Person), 3, fp); // 打印内容 for (int i =0; i < 3; ++i) { printf("Name:%s Age:%d\n", ps[i].name, ps[i].age); } fclose(fp); fp = NULL; }
8. 随机文件读写
-
fseek
#include <stdio.h> int fseek(FILE *stream, long offset, int whence); 功能:移动文件流(文件光标)的读写位置。 参数: stream:已经打开的文件指针 offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。 whence:其取值如下: SEEK_SET:从文件开头移动offset个字节 SEEK_CUR:从当前位置移动offset个字节 SEEK_END:从文件末尾移动offset个字节 返回值: 成功:0 失败:-1
fseek(fp, -10, SEEK_END);
-
ftell
#include <stdio.h> long ftell(FILE *stream); 功能:获取文件流(文件光标)的读写位置。 参数: stream:已经打开的文件指针 返回值: 成功:当前文件流(文件光标)的读写位置 失败:-1
-
rewind
#include <stdio.h> void rewind(FILE *stream); 功能:把文件流(文件光标)的读写位置移动到文件开头。 参数: stream:已经打开的文件指针 返回值: 无返回值
-
案例:随机读写结构体
struct Person { char name[32]; int age; }; void test01() { // 创建Person数组,并且将数组存储到文件中 struct Person ps[] = { { "Obama", 99 }, { "Trump", 89 }, { "Clinton", 100 } }; FILE *fp = fopen("./demo11.txt", "wb"); if (NULL == fp) { return; } // 将数组写入到文件中 fwrite(ps, sizeof(struct Person), 3, fp); fclose(fp); fp = NULL; } void test02() { FILE *fp = fopen("./demo11.txt", "rb"); if (NULL == fp) { return; } // 1. 移动指针到第二个 Person 数据首地址 long pos = ftell(fp); printf("当前文件指针位置:%ld\n", pos); fseek(fp, sizeof(struct Person), SEEK_SET); // 创建变量保存数据 struct Person person = {0}; fread(&person, sizeof(struct Person), 1, fp); printf("Name:%s Age:%d\n", person.name, person.age); // 将文件指针从指向 cliton 转回指向 trump int offset = sizeof(struct Person); fseek(fp, -offset, SEEK_CUR); printf("Name:%s Age:%d\n", person.name, person.age); // 2. 继续读取下一个数据 fread(&person, sizeof(struct Person), 1, fp); printf("Name:%s Age:%d\n", person.name, person.age); // 3. 可以将文件指针移动到开始位置 // fseek(fp, 0, SEEK_SET); rewind(fp); fread(&person, sizeof(struct Person), 1, fp); printf("Name:%s Age:%d\n", person.name, person.age); fclose(fp); fp = NULL; } void test03() { // 获得文件大小 FILE *fp = fopen("./demo11.txt", "rb"); if (NULL == fp) { return; } // 将文件指针移动到文件末尾 fseek(fp, 0, SEEK_END); long file_size = ftell(fp); printf("文件大小是:%ld %u\n", file_size, sizeof(struct Person)); fclose(fp); fp = NULL; }
9. 文件读写案例-登陆功能实现
- 文件存储用户名密码
- 用户输入用户名和密码
C语言第十二天课程笔记
每一天的笔记包含如下内容:
- 当天授课内容安排
- 课堂重点内容笔记
- 课后思考题
1. 内容安排
第一节课: 堆区指针使用注意、goto语句
第二节课: 电话本(数据结构定义、添加、显示)
第三节课: 电话本(删除、修改、数据存储)
第四节课: 函数封装、分文件编写
第五节课: 课程总回顾
第六节课: 课程总回顾
2. 堆区指针使用注意
- 不能 free 指向非堆的内存.
- 堆指针在释放之前禁止重新赋值.
- 堆内存释放之后仍然使用堆指针.
- 堆指针地址修改之后无法再 free.
- malloc(0) 时,不建议使用该空间
// 1. 不能 free 指向非堆的内存[常犯].
int g_a = 100;
void test01()
{
// 1. 栈变量
int a = 10;
int *p = &a;
// free(p);
// 2. 字符串常量
char *s = "hello world";
// free(s);
// 3. 全局变量
int *gp = &g_a;
free(gp);
}
// 2. 堆指针在释放之前禁止重新赋值【常犯】.
void test02()
{
int *p = (int *)malloc(sizeof(int));
*p = 100;
p = (int *)malloc(sizeof(int));
}
// 3. 堆内存释放之后仍然使用堆指针[常犯].
void test03()
{
int *p = (int *)malloc(sizeof(int));
*p = 100;
free(p);
// 内存回收,使用权已经不归我们了,
// 虽然没有报错,原则不能再使用
// 既然已经分析出错误,至于结果是什么并不重要
*p = 200;
printf("*p = %d\n", *p);
}
// 4. 堆指针地址修改之后无法再 free[常犯].
void test04()
{
int *p = (int *)malloc(sizeof(int) * 10);
int *p_temp = p;
p_temp = p_temp + 1;
// p = p + 1;
// free 的时候一定要拿到空间的首地址
// free 的参数是无类型指针
// 我们有没有告诉 free 我们的堆空间多大?
// 编译器在首地址附近会存储堆空间大小
// 我们虽然申请了40字节,但实际上,申请的内存大于40,可能是44,多出来的4个字节存储堆空间大小.
free(p);
}
// 5. malloc(0) 时,不建议使用该空间
void test05()
{
int len = 0;
int *p = malloc(len);
if (NULL == p)
{
return;
}
// *p = 100; // 没有报错
// memcpy(p, "hello", 3);
printf("p = %p\n", p);
free(p);
}
3. 电话本数据结构
- 数据展示:字符界面
- 业务功能:C语言语法、数据类型
- 数据存储: 文件
一个项目数据是核心. 数据结构. 电话本:
- 联系人信息(姓名、电话).
- 联系人的数组. 联系人当前的数量.
分析的思路转换为具体代码实现:
#define MAX_CONTACTS 100
// 联系人
struct Contact
{
char name[32];
char tele[64];
};
struct Book
{
// 存储联系人信息
struct Contact contacts[MAX_CONTACTS];
// 记录当前电话本中存储了多少个联系人
int size;
};
4. 分析业务流程
- 显示操作菜单
- 获得用户输入的操作
- 根据用户输入做相应的操作
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_CONTACTS 100
// 操作常量
enum { ADD = 1, REMOVE, EDIT, SEARCH, EXIT };
// 联系人
struct Contact
{
char name[32];
char tele[64];
};
struct Book
{
// 存储联系人信息
struct Contact contacts[MAX_CONTACTS];
// 记录当前电话本中存储了多少个联系人
int size;
};
void test()
{
// 创建保存电话本信息的变量
struct Book book;
memset(&book, 0, sizeof(struct Book));
/*************电话本开始工作****************/
while (1)
{
// 1. 显示操作菜单
printf("--------------------------------\n");
printf("电话本系统v1.0\n");
printf("--------------------------------\n");
printf("1. 添加联系人\n");
printf("2. 删除联系人\n");
printf("3. 修改联系人\n");
printf("4. 查找联系人\n");
printf("5. 退出电话本\n");
printf("--------------------------------\n");
// 2. 获得用户输入的操作
printf("请输入您的操作:");
int flag = -1;
// scanf: 返回0表示读取失败,输入了非整数字符. 非0: 正常
int ret = scanf("%d", &flag);
if (!ret)
{
// 清空缓冲区
char buf[128] = { 0 };
fgets(buf, 128, stdin);
printf("请不要输入非法字符, 有效字符为 1-5.\n");
continue;
}
// 3. 根据用户输入做相应的操作
switch (flag)
{
case ADD:
// 添加联系人
system("cls"); // 清屏
printf("添加联系人!\n");
break;
case REMOVE:
// 删除联系人
system("cls"); // 清屏
printf("删除联系人!\n");
break;
case EDIT:
// 修改联系人
system("cls"); // 清屏
printf("修改联系人!\n");
break;
case SEARCH:
// 查找联系人
system("cls"); // 清屏
printf("查找联系人!\n");
break;
case EXIT:
// 退出系统
system("cls"); // 清屏
printf("欢迎再次使用《电话本v1.0》!\n");
system("pause"); // 等待
exit(0);
break;
default:
printf("您输入的操作暂不支持, 请期待下一个v1.1版本!\n");
break;
}
}
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
#endif
5. 添加联系人
- 获得用户输入的联系人姓名、联系人电话
- 将获得的数据存储 size 下标的位置
- size 累加 1 个
- 提示添加成功
/*1. 获得用户输入的联系人姓名、联系人电话*/
printf("请输入联系人姓名:");
char name[32] = { 0 };
scanf("%s", name);
printf("请输入联系人电话:");
char tele[64] = { 0 };
scanf("%s", tele);
/*2. 将获得的数据存储 size 下标的位置*/
strcpy(book.contacts[book.size].name, name);
strcpy(book.contacts[book.size].tele, tele);
/*3. size 累加 1 个*/
++book.size;
/*4. 提示添加成功*/
printf("联系人【%s】添加成功!\n", name);
6. 显示联系人
- 遍历 book.contacts 数组
- 遍历不需要遍历所有,只需要遍历 size 个即可.
system("cls"); // 清屏
printf("--------------------------------\n");
printf("姓名\t电话\n");
printf("--------------------------------\n");
for (int i = 0; i < book.size; ++i)
{
printf("%s\t%s\n", book.contacts[i].name, book.contacts[i].tele);
}
printf("--------------------------------\n");
7. 修改联系人
-
输入要修改的联系人姓名
-
通过姓名找到联系人的结构体
-
输入修改之后的新的电话号码
-
新的电话号码重新复制到对应联系人的 tele 字符串里
/*1. 输入要修改的联系人姓名*/
printf("请输入要修改的联系人姓名:");
char edit_name[32] = { 0 };
scanf("%s", edit_name);
/*2. 通过姓名找到联系人的结构体*/
int flag = -1; // 假设联系人不存在
for (int i = 0; i < book.size; ++i)
{
if (0 == strcmp(book.contacts[i].name, edit_name))
{
flag = 0;
/*3. 输入修改之后的新的电话号码*/
printf("您原来的电话号码是[%s], 您要修改为:", book.contacts[i].tele);
char new_tele[64] = { 0 };
scanf("%s", new_tele);
/*4. 新的电话号码重新复制到对应联系人的 tele 字符串里*/
strcpy(book.contacts[i].tele, new_tele);
printf("联系人[%s]的电话号码修改成功!\n", book.contacts[i].name);
break;
}
}
// 判断联系人是否存在
if (flag == -1)
{
printf("联系人[%s]信息不存在!\n", edit_name);
}
8. 删除联系人
- 输入要删除的联系人
- 根据联系人姓名查找该联系人的信息
- 要删除联系人.(将要删除元素后面的所有元素向前移动一个位置)
/*1. 输入要删除的联系人*/
printf("请输入您要删除的联系人姓名:");
char del_name[32] = { 0 };
scanf("%s", del_name);
/*2. 根据联系人姓名查找该联系人的信息*/
int del_idx = -1; // 记录要删除联系人的下标
for (int i = 0; i < book.size; ++i)
{
if (strcmp(book.contacts[i].name, del_name) == 0)
{
del_idx = i;
break;
}
}
/*3. 要删除联系人.*/
if (-1 == del_idx)
{
printf("您要删除的联系人[%s]不存在!\n", del_name);
}
else
{
for (int i = del_idx; i < book.size - 1; ++i)
{
// 将要删除元素后面的所有元素向前移动一个位置
book.contacts[i] = book.contacts[i + 1];
}
}
/*4. 更新联系人数目*/
book.size--;
printf("联系人[%s]已被删除!\n", del_name);
9. 查找联系人
- 获得要查找联系人的姓名
- 遍历数组,将联系人的电话打印出来
/*
1. 获得要查找联系人的姓名
2. 遍历数组,将联系人的电话打印出来
*/
printf("请输入要查找的联系人姓名:");
char search_name[32] = { 0 };
scanf("%s", search_name);
int search_flag = -1;
for (int i = 0; i < book.size; ++i)
{
if (strcmp(book.contacts[i].name, search_name) == 0)
{
printf("您查找的联系人[%s]的电话号码是:%s\n", search_name, book.contacts[i].tele);
search_flag = 0;
break;
}
}
if (-1 == search_flag)
{
printf("您要查找的联系人[%s]不存在!\n", search_name);
}
10. 退出电话本
- 先要保存数据到文件:
contacts.data
- 退出系统
系统启动时加载数据:
FILE *fp = fopen(file_path, "rb");
if (NULL == fp)
{
printf("加载数据时, 文件[%s]打开失败!\n", file_path);
return;
}
fread(&book, sizeof(struct Book), 1, fp);
fclose(fp);
fp = NULL;
系统退出时存储数据:
/*
1. 先要保存数据到文件: `contacts.data`
2. 退出系统
*/
FILE *fp = fopen(file_path, "wb");
if (NULL == fp)
{
printf("文件[%s]打开失败!\n", file_path);
return;
}
// 将联系人数据写入到文件中
fwrite(&book, sizeof(struct Book), 1, fp);
fclose(fp);
fp = NULL;
printf("欢迎再次使用《电话本v1.0》!\n");
system("pause"); // 等待
exit(0);
💗💗💗
print("如果文章对你有用,请点个赞呗O(∩_∩)O~")
System.out.println("如果文章对你有用,请点个赞呗O(∩_∩)O~");
cout<<"如果文章对你有用,请点个赞呗O(∩_∩)O~"<<endl;
💗💗💗