2024-2025-1 20241314 《计算机基础与程序设计》第十四周学习总结
作业信息
这个作业属于哪个课程 | <班级的链接>(如2024-2025-1-计算机基础与程序设计) |
---|---|
这个作业要求在哪里 | <作业要求的链接>2024-2025-1计算机基础与程序设计第十四周作业 |
作业正文 | 正文 |
教材学习内容总结
第十三章:文件操作
一、文件概述
- 文件的概念
- 文本文件:例如一个简单的文本文件
example.txt
,内容是“Hello, World!”。在文本文件中,每个字符都按照字符编码(如ASCII码)存储。在ASCII码中,字符‘H’对应的十进制值是72,‘e’是101等等。当我们用文本编辑器打开这个文件时,编辑器会根据字符编码将这些数字转换为对应的字符显示出来。 - 二进制文件:以存储整数数组为例。如果有一个整数数组
int arr[] = {1, 2, 3, 4, 5}
,在二进制文件中,这些整数会按照它们在内存中的存储形式(在大多数系统中,int
类型占4个字节)直接存储到文件中。这种存储方式对于计算机处理数据效率较高,但对于人类来说,打开文件看到的是一堆乱码。
- 文本文件:例如一个简单的文本文件
- 文件指针
- 例如,定义一个文件指针
FILE *fp;
。这个指针变量将用于指向我们要操作的文件相关的结构体信息。就好像这个指针是一个“把手”,通过它可以找到文件的各种属性以及对文件进行读写等操作。
- 例如,定义一个文件指针
二、文件的打开与关闭
- fopen函数
- 示例:
#include <stdio.h>
int main()
{
FILE *fp;
// 尝试以只读方式打开文件
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 文件操作...
fclose(fp);
return 0;
}
在这个例子中,程序试图打开一个名为example.txt
的文件用于只读。如果fp
的值为NULL
,说明文件打开失败,通过perror
函数打印出错误信息。如果打开成功,就可以对文件进行后续操作,最后使用fclose
函数关闭文件。
- fclose函数
- 继续上面的例子,
fclose(fp);
语句用于关闭已经打开的文件。如果文件在写入操作后没有正确关闭,可能会导致数据丢失。例如,如果正在向文件写入数据,数据可能还在缓冲区中,没有真正写入磁盘,关闭文件会强制将缓冲区的数据写入磁盘。
- 继续上面的例子,
三、文件的读写
- 字符读写函数
- fgetc函数:
#include <stdio.h>
int main()
{
FILE *fp;
int ch;
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 从文件中读取一个字符
ch = fgetc(fp);
if (ch!= EOF)
{
putchar(ch);
}
fclose(fp);
return 0;
}
在这个程序中,首先打开文件example.txt
。然后使用fgetc
函数从文件中读取一个字符,将其存储在变量ch
中。如果ch
不等于EOF
(文件结束标志),就使用putchar
函数将这个字符输出到控制台。
- fputc函数:
#include <stdio.h>
int main()
{
FILE *fp;
char ch = 'A';
fp = fopen("new_file.txt", "w");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 将字符写入文件
fputc(ch, fp);
fclose(fp);
return 0;
}
这里创建了一个新文件new_file.txt
(如果文件已存在,内容会被清空),然后使用fputc
函数将字符‘A’写入这个文件。
- 字符串读写函数
- fgets函数:
#include <stdio.h>
int main()
{
FILE *fp;
char str[100];
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 从文件中读取一行字符串
fgets(str, 100, fp);
printf("%s", str);
fclose(fp);
return 0;
}
假设`example.txt`中有多行内容,这个程序会从文件中读取最多99个字符(因为要留一个位置给字符串结束符`\0`)到字符数组`str`中。读取会在遇到换行符`\n`、读取了99个字符或者遇到文件末尾`EOF`时停止,然后将读取到的字符串输出到控制台。
- **fputs函数**:
```c
#include <stdio.h>
int main()
{
FILE *fp;
char str[] = "This is a test string.";
fp = fopen("new_file.txt", "a");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 将字符串写入文件
fputs(str, fp);
fclose(fp);
return 0;
}
程序打开文件new_file.txt
用于追加内容。然后使用fputs
函数将字符串str
的内容写入文件。如果文件原来有内容,新内容会添加在文件末尾。
- 格式化读写函数
- fscanf函数:
假设data.txt
文件内容为“1 2.5”,下面的程序读取文件中的整数和浮点数。
- fscanf函数:
#include <stdio.h>
int main()
{
FILE *fp;
int num;
float fnum;
fp = fopen("data.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 从文件中按照格式读取数据
fscanf(fp, "%d %f", &num, &fnum);
printf("读取的整数为:%d,浮点数为:%f\n", num, fnum);
fclose(fp);
return 0;
}
- **fprintf函数**:
#include <stdio.h>
int main()
{
FILE *fp;
int num = 10;
float fnum = 3.14;
fp = fopen("output.txt", "w");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 将数据按照格式写入文件
fprintf(fp, "整数为:%d,浮点数为:%f", num, fnum);
fclose(fp);
return 0;
}
这个程序创建一个新文件output.txt
,并将整数num
和浮点数fnum
按照指定的格式写入文件。
四、文件的定位
- rewind函数
#include <stdio.h>
int main()
{
FILE *fp;
char ch;
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 读取一个字符
ch = fgetc(fp);
if (ch!= EOF)
{
putchar(ch);
}
// 将文件指针重新定位到文件开头
rewind(fp);
// 再次读取一个字符
ch = fgetc(fp);
if (ch!= EOF)
{
putchar(ch);
}
fclose(fp);
return 0;
}
在这个例子中,首先从文件中读取一个字符并输出。然后使用rewind
函数将文件指针重新定位到文件开头,再次读取一个字符并输出,这样可以看到文件内容被重新读取的过程。
- fseek函数
#include <stdio.h>
int main()
{
FILE *fp;
char ch;
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("文件打开失败");
return 1;
}
// 将文件指针从文件开头向后移动3个字节
fseek(fp, 3L, SEEK_SET);
// 读取一个字符
ch = fgetc(fp);
if (ch!= EOF)
{
putchar(ch);
}
fclose(fp);
return 0;
}
假设example.txt
中有足够的内容,这个程序将文件指针从文件开头向后移动3个字节,然后读取并输出一个字符。这可以用于跳过文件开头的一些内容或者定位到文件中的特定位置进行读取。
第十四章:其他高级主题
一、动态内存分配
- malloc函数、calloc函数和realloc函数
- malloc函数:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p;
// 分配可以存储5个整数的内存空间
p = (int *)malloc(5 * sizeof(int));
if (p == NULL)
{
perror("内存分配失败");
return 1;
}
for (int i = 0; i < 5; i++)
{
p[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
这个程序首先使用malloc
函数分配了可以存储5个整数的内存空间。如果分配成功,p
将指向这块内存空间的起始地址。然后通过数组的方式给每个元素赋值,并输出这些元素的值。最后使用free
函数释放分配的内存,避免内存泄漏。
- calloc函数:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p;
// 分配可以存储5个整数的内存空间,并初始化为0
p = (int *)calloc(5, sizeof(int));
if (p == NULL)
{
perror("内存分配失败");
return 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
与malloc
函数不同,calloc
函数在分配内存后会将内存空间初始化为0。在这个程序中,分配了可以存储5个整数的内存空间,然后直接输出这些元素的值,可以看到它们都是0。
- realloc函数:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p;
// 先分配可以存储5个整数的内存空间
p = (int *)malloc(5 * sizeof(int));
if (p == NULL)
{
perror("内存分配失败");
return 1;
}
for (int i = 0; i < 5; i++)
{
p[i] = i;
}
// 重新分配内存空间,使其可以存储10个整数
p = (int *)realloc(p, 10 * sizeof(int));
if (p == NULL)
{
perror("内存重新分配失败");
return 1;
}
for (int i = 5; i < 10; i++)
{
p[i] = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
程序首先使用malloc
函数分配了可以存储5个整数的内存空间,然后使用realloc
函数将其重新分配为可以存储10个整数的内存空间。接着给新增加的元素赋值,并输出所有元素的值。最后释放内存。
二、命令行参数
- main函数的参数
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("请在命令行输入参数\n");
return 1;
}
printf("程序名:%s\n", argv[0]);
for (int i = 1; i < argc; i++)
{
printf("参数 %d:%s\n", i, argv[i]);
}
return 0;
}
假设这个程序名为test
。如果在命令行输入test arg1 arg2
,argc
的值为3。argv[0]
指向字符串"test"
(程序名),argv[1]
指向"arg1"
,argv[2]
指向"arg2"
。程序会先检查是否有足够的参数,如果有,就输出程序名和各个参数。这可以用于实现根据不同的命令行参数执行不同的功能,比如一个文件处理程序可以根据命令行输入的文件名来处理不同的文件。
三、预处理指令的深入应用
- 宏定义的高级用法
- 带参数的宏定义:
#include <stdio.h>
#define MAX(a, b) ((a) > (b)? (a) : (b))
int main()
{
int x = 5;
int y = 3;
int max_value = MAX(x, y);
printf("较大的值是:%d\n", max_value);
return 0;
}
这个程序定义了一个带参数的宏MAX
,用于比较两个数并返回较大的值。在main
函数中,通过MAX(x, y)
调用这个宏,预处理器会将其替换为((x) > (y)? (x) : (y))
,从而实现比较两个数大小的功能。
- 条件编译指令:
#include <stdio.h>
#define DEBUG
int main()
{
#ifdef DEBUG
printf("程序处于调试模式\n");
#endif
return 0;
}
- 在这个程序中,定义了
DEBUG
宏。因为#ifdef DEBUG
条件成立,所以printf
语句会被编译并执行。如果没有定义DEBUG
宏,这部分代码将不会被编译,这对于在调试阶段输出一些调试信息,而在正式发布时不输出这些信息很有用。
教材学习中的问题和解决过程
指针数组,数组指针,函数指针,指针函数的定义与区别
- 指针数组
- 定义:指针数组是一个数组,其元素为指针类型。也就是说,数组中的每个元素都存放着一个地址。其定义形式一般为
数据类型 *数组名[数组大小];
。例如int *p[5];
,这里定义了一个名为p
的指针数组,它可以存放5个指向int
类型数据的指针。 - 示例:
- 定义:指针数组是一个数组,其元素为指针类型。也就是说,数组中的每个元素都存放着一个地址。其定义形式一般为
#include <stdio.h>
int main()
{
int a = 1, b = 2, c = 3, d = 4, e = 5;
int *p[5];
p[0] = &a;
p[1] = &b;
p[2] = &c;
p[3] = &d;
p[4] = &e;
for (int i = 0; i < 5; i++)
{
printf("%d ", *(p[i]));
}
return 0;
}
- 在这个例子中,首先定义了5个整数`a`、`b`、`c`、`d`、`e`,然后定义了一个指针数组`p`。通过`p[i] = &变量名`的方式,将每个变量的地址存放在指针数组`p`的元素中。最后,通过`*(p[i])`的方式访问每个变量的值并打印出来。
- 数组指针
- 定义:数组指针是一个指针,它指向一个数组。定义形式为
数据类型 (*指针变量名)[数组大小];
。例如int (*q)[5];
,这里q
是一个指针,它指向一个包含5个int
类型元素的数组。 - 示例:
- 定义:数组指针是一个指针,它指向一个数组。定义形式为
#include <stdio.h>
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
int (*q)[5];
q = &arr;
for (int i = 0; i < 5; i++)
{
printf("%d ", (*q)[i]);
}
return 0;
}
- 在这个例子中,首先定义了一个数组`arr`和一个数组指针`q`。然后将数组`arr`的地址赋给`q`(注意是`&arr`而不是`arr`)。最后通过`(*q)[i]`的方式访问数组中的元素并打印出来。这种方式是先解引用指针`q`得到指向的数组,再通过索引`i`访问数组中的元素。
- 函数指针
- 定义:函数指针是指向函数的指针。函数在内存中也有自己的地址,函数指针可以保存这个地址,从而可以通过函数指针来调用函数。其定义形式为
返回值类型 (*指针变量名)(参数列表);
。例如int (*func_ptr)(int, int);
定义了一个函数指针func_ptr
,它可以指向一个返回值为int
,参数为两个int
类型的函数。 - 示例:
- 定义:函数指针是指向函数的指针。函数在内存中也有自己的地址,函数指针可以保存这个地址,从而可以通过函数指针来调用函数。其定义形式为
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*func_ptr)(int, int);
func_ptr = add;
int result = func_ptr(3, 5);
printf("结果是:%d\n", result);
return 0;
}
- 在这个例子中,首先定义了一个函数`add`,用于计算两个整数的和。然后在`main`函数中定义了一个函数指针`func_ptr`,并将`add`函数的地址赋给它(通过`func_ptr = add;`)。最后通过`func_ptr(3, 5)`来调用`add`函数,就好像`func_ptr`就是`add`函数一样,得到计算结果并打印出来。
- 指针函数
- 定义:指针函数是一个函数,其返回值是一个指针。定义形式为
数据类型 *函数名(参数列表);
。例如int *func(int a);
定义了一个函数func
,它接受一个int
类型的参数a
,并且返回一个指向int
类型数据的指针。 - 示例:
- 定义:指针函数是一个函数,其返回值是一个指针。定义形式为
#include <stdio.h>
#include <stdlib.h>
int *create_array(int size)
{
int *arr = (int *)malloc(size * sizeof(int));
if (arr == NULL)
{
return NULL;
}
for (int i = 0; i < size; i++)
{
arr[i] = i;
}
return arr;
}
int main()
{
int *p;
p = create_array(5);
if (p!= NULL)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
free(p);
}
return 0;
}
- 在这个例子中,`create_array`函数是一个指针函数,它接受一个整数`size`作为参数,用于分配一个包含`size`个`int`类型元素的数组空间。在函数内部,通过`malloc`函数分配内存,给数组元素赋值后,返回这个数组的首地址(即指向`int`类型的指针)。在`main`函数中,通过`p = create_array(5);`调用这个指针函数,得到返回的指针,然后可以通过这个指针访问数组元素。最后使用`free`函数释放内存。
- 区别总结
- 指针数组:重点在于它是一个数组,数组元素是指针。可以用于存储多个同类型的指针,比如存储多个字符串的首地址等情况。
- 数组指针:重点是一个指针,它指向一个数组。在处理二维数组或者需要将数组作为参数传递给函数并且在函数内部需要以数组的形式操作数据时比较有用。
- 函数指针:主要用于指向函数,通过函数指针可以实现函数回调等功能,比如在库函数或者事件驱动编程中很常见。
- 指针函数:本质是一个函数,其特点是返回值为指针。通常用于需要在函数内部动态分配内存并且返回这个内存空间地址的情况。
基于AI的学习