前言
1 结构体
1-1 结构体定义
- C 语言提供了 结构体(struct),用来把多个不同类型的数据组合在一起。
- 结构体使用
struct 关键字定义。
- 基本语法:
c
复制代码
struct 结构体名
{
成员变量;
成员变量;
};
c
复制代码
#include <stdio.h>
struct Student
{
int id; //学号
char name[20]; //姓名
float score; //成绩
};
1-2 结构体变量
- 结构体定义完成后,就可以声明变量。声明变量有两种方法
- 先定义结构体,再定义变量
c
复制代码
struct Student
{
int id;
char name[20];
float score;
};
struct Student stu1;
- 定义时直接创建变量
c
复制代码
struct Student
{
int id;
char name[20];
float score;
} stu1, stu2;
1-3 结构体成员访问
c
复制代码
#include <stdio.h>
struct Student
{
int id;
char name[20];
float score;
};
int main()
{
struct Student stu1;
stu1.id = 1001;
stu1.score = 95.5;
printf("id = %d\n", stu1.id);
printf("score = %.2f\n", stu1.score);
return 0;
}
1-4 结构体初始化
c
复制代码
struct Student
{
int id;
char name[20];
float score;
};
int main()
{
struct Student stu = {1001, "Tom", 95.5};
printf("%d %s %.1f\n", stu.id, stu.name, stu.score);
}
1-5 结构体指针
c
复制代码
struct Student *p;
c
复制代码
(*p).id
- 同时C 语言提供了更方便的运算符
->,使得我们可以这样访问结构体指针所指的的结构体成员:
c
复制代码
p->id
c
复制代码
#include <stdio.h>
struct Student
{
int id;
float score;
};
int main()
{
struct Student stu = {1001, 90};
struct Student *p = &stu;
printf("%d\n", p->id);
printf("%.1f\n", p->score);
return 0;
}
1-6 结构体数组
- 结构体数组用于存储多个结构体变量。结构体数组也有多种定义方式:
- 先定义结构体,再定义数组(最常见)
c
复制代码
struct Student
{
int id;
float score;
};
struct Student stu[3];
- 其中每一个元素都是一个
Student 结构体,我们可以这样访问它:
c
复制代码
stu[0].id
stu[1].score
- 定义结构体时直接定义数组
c
复制代码
struct Student
{
int id;
float score;
} stu[3];
- 结构体数组也可以在定义时初始化:
c
复制代码
struct Student
{
int id;
float score;
};
struct Student stu[3] =
{
{1001, 90},
{1002, 85},
{1003, 92}
};
1-7 结构体嵌套
- 结构体中的成员也可以是另一个结构体 ,这种情况称为 结构体嵌套。
- 例如:学生信息中还包含地址信息。
c
复制代码
#include <stdio.h>
struct Address
{
char city[20];
char street[20];
};
struct Student
{
int id;
char name[20];
struct Address addr;
};
c
复制代码
Student
├─ id
├─ name
└─ addr
├─ city
└─ street
c
复制代码
stu.addr.city
stu.addr.street
1-8 链表结构
- 在很多数据结构中,结构体中经常会包含一个指向自身结构体的指针。
- 这种结构常用于实现 链表(Linked List)。
c
复制代码
struct Node
{
int data;
struct Node *next;
};
| 成员 |
含义 |
| data |
存储数据 |
| next |
指向下一个节点 |
- 链表由多个节点连接而成,每个节点通过
next 指针连接到下一个节点。
c
复制代码
node1 node2 node3
┌─────┐ ┌─────┐ ┌─────┐
│data │ │data │ │data │
│next │ ──► │next │ ──► │next │ ──► NULL
└─────┘ └─────┘ └─────┘
c
复制代码
#include <stdio.h>
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node n1, n2, n3;
n1.data = 1;
n2.data = 2;
n3.data = 3;
n1.next = &n2;
n2.next = &n3;
n3.next = NULL;
printf("%d\n", n1.data);
printf("%d\n", n1.next->data);
printf("%d\n", n2.next->data);
return 0;
}
1-9 typedef 简化结构体写法
c
复制代码
struct Student stu1;
- 如果在程序中大量使用结构体,这种写法会显得比较繁琐。
- 因此C 语言提供了
typedef 来给数据类型起别名,从而简化结构体的使用。
- 基本用法:
c
复制代码
typedef struct
{
int id;
char name[20];
float score;
} Student;
- 这样以后定义变量时就可以直接写:(这点就类似
C++的class关键字了)
c
复制代码
Student stu1;
Student stu2;
1-10 结构体大小 sizeof
- 在 C 语言中,我们可以使用
sizeof 运算符查看结构体所占用的内存大小。
c
复制代码
struct Student
{
int id;
char name[20];
float score;
};
- 例如上面这个结构体计算
sizeof(struct Student)的结果为28
| 成员 |
类型 |
大小 |
| id |
int |
4 |
| name |
char[20] |
20 |
| score |
float |
4 |
- 但在某些情况下,结构体大小 并不一定等于成员大小简单相加 ,这是因为编译器通常会进行 内存对齐(Memory Alignment)。
c
复制代码
struct Test
{
char a; //1字节
int b; //4字节
};
- 计算
sizeof(struct Student)的结果为8
- 原因是编译器为了提高 CPU 访问效率,会按照一定规则对结构体成员进行 内存对齐 ,在成员之间插入一些 填充字节(padding)。
- 内存布局可能类似这样:
c
复制代码
地址:
a | 填充 | 填充 | 填充 | b b b b
1 1 1 1 4
- 说人话就是1字节
char类型的a变量为了要对齐4字节int类型的b变量要填充3字节
- 在看一个例子:
c
复制代码
struct Example
{
char a;
char b;
int c;
};
c
复制代码
地址
a b 填充 填充 c c c c
1 1 1 1 4
2 文件定义和操作
2-0 EOF
- 在 C 语言中,
EOF 是一个宏,表示 文件结束标志(End Of File)。
- 定义在
<stdio.h> 头文件中:
c
复制代码
#define EOF (-1)
- 主要用途:
- 检查文件是否到达末尾
- 判断读取函数是否失败
2-1 文本文件与二进制文件
| 类型 |
特点 |
打开方式 |
| 文本文件(Text File) |
内容以可读字符形式存储,每行以换行符 \n 结束,可直接用文本编辑器打开 |
"r" "w" "a" 等 |
| 二进制文件(Binary File) |
内容以二进制形式存储,不可直接用文本编辑器打开,需要用程序读取 |
"rb" "wb" "ab" 等 |
2-2 FILE* fp
- 在 C 语言中,文件通过
FILE 结构体 进行操作
- 文件指针类型定义为
FILE*,用于指向打开的文件
- 典型声明:
c
复制代码
FILE *fp; // 定义一个文件指针
- 文件操作流程:
- 打开文件
fopen / fopen_s
- 读写文件
fgetc / fgets / fputc / fputs / fread / fwrite
- 定位文件
fseek / ftell
- 关闭文件
fclose
2-2 打开与关闭文件
c
复制代码
FILE *fopen(const char *filename, const char *mode);
| 模式 |
含义 |
文件必须存在 |
打开后文件内容 |
文件指针位置 |
写入行为 |
"r" |
只读文本文件 |
✅ |
不变 |
开头 |
只能读,不能写 |
"w" |
只写文本文件 |
❌ |
清空 |
开头 |
写入覆盖原文件 |
"a" |
追加文本文件 |
❌ |
不变 |
末尾 |
写入总追加到末尾 |
"r+" |
可读可写文本文件 |
✅ |
不变 |
开头 |
可读可写,写入覆盖当前位置 |
"w+" |
可读可写文本文件 |
❌ |
清空 |
开头 |
可读可写,写入覆盖 |
"a+" |
可读可写文本文件 |
❌ |
不变 |
末尾 |
写入总追加,读可用 fseek 调整位置 |
"rb" |
二进制只读 |
✅ |
不变 |
开头 |
只能读 |
"wb" |
二进制只写 |
❌ |
清空 |
开头 |
写入覆盖 |
"ab" |
二进制追加 |
❌ |
不变 |
末尾 |
写入总追加 |
"rb+" |
二进制可读可写 |
✅ |
不变 |
开头 |
可读可写,覆盖当前位置 |
"wb+" |
二进制可读可写 |
❌ |
清空 |
开头 |
可读可写,写入覆盖 |
"ab+" |
二进制可读可写追加 |
❌ |
不变 |
末尾 |
写入总追加,读可用 fseek 调整位置 |
c
复制代码
fclose(fp);
c
复制代码
#include <stdio.h>
int main()
{
FILE *fp = fopen("test.txt", "w"); // 打开文件
if(fp == NULL)
{
printf("文件打开失败\n");
return 1;
}
fprintf(fp, "Hello World!\n"); // 写入文件
fclose(fp); // 关闭文件
return 0;
}
2-3 文本文件读写
c
复制代码
fputc(char c, FILE *fp); // 写入一个字符
fputs(const char *str, FILE *fp); // 写入一个字符串
fprintf(FILE *fp, const char *format, ...); // 类似 printf
c
复制代码
fgetc(FILE *fp); // 读取一个字符
fgets(char *str, int n, FILE *fp); // 读取一行
fgetc(fp) 的返回值为 字符的 ASCII 值 或 EOF
fgets(a,10,fpn)返回值为 str 的地址 或 NULL
- 例子:
c
复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* fp= fopen("test01.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
char buf[50];
while (fgets(buf, sizeof(buf), fp) != NULL)
{
printf("%s", buf);
}
fclose(fp);
return 0;
}
2-4 格式化输入输出函数
fscanf :从文件中按指定格式读取数据,类似于 scanf,但对象是文件。
c
复制代码
fscanf(FILE *fp, const char *format, ...);
fprintf :向文件按指定格式写入数据,类似于 printf,但对象是文件。
c
复制代码
fprintf(FILE *fp, const char *format, ...);
- 示例:同时使用
fscanf 和 fprintf 进行文件的格式化读写
c
复制代码
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
// 使用 fprintf 写入格式化数据
fprintf(fp, "%d %.1f\n", 1001, 95.5);
fprintf(fp, "%d %.1f\n", 1002, 88.0);
fclose(fp);
// 使用 fscanf 读取文件
fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
int id;
float score;
while (fscanf(fp, "%d %f", &id, &score) == 2) {
printf("id=%d, score=%.1f\n", id, score);
}
fclose(fp);
return 0;
}
2-5 二进制文件的读写
c
复制代码
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
c
复制代码
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
- 其中:
ptr:数据缓冲区指针
size:每个数据块的大小
nmemb:数据块数量
fp:文件指针
- 注意 :
size * nmemb = 总共要读/写的字节数
c
复制代码
#include <stdio.h>
typedef struct
{
int id;
float score;
} Student;
int main()
{
Student stu[2] = {{1001, 90.5}, {1002, 88.0}};
FILE *fp = fopen("stu.dat", "wb");
fwrite(stu, sizeof(Student), 2, fp);
fclose(fp);
Student readStu[2];
fp = fopen("stu.dat", "rb");
fread(readStu, sizeof(Student), 2, fp);
fclose(fp);
for(int i = 0; i < 2; i++)
{
printf("id=%d score=%.1f\n", readStu[i].id, readStu[i].score);
}
return 0;
}
2-6 文件定位函数
- 文件读写时,可以使用
fseek、ftell、rewind 来定位文件位置。
fseek(FILE *fp, long offset, int whence)
- 作用:设置文件指针的位置。
- 参数 :
fp:文件指针。
offset:偏移量(以字节为单位)。
whence:起始位置,可取:
SEEK_SET:文件开头
SEEK_CUR:当前位置
SEEK_END:文件末尾
- 返回值 :
ftell(FILE *fp)
- 作用:返回当前文件指针的位置(以字节为单位)。
- 参数 :文件指针
fp。
- 返回值 :
- 成功 → 文件当前位置(long 型)
- 失败 →
-1L
rewind(FILE *fp)
- 作用 :将文件指针移到文件开头,相当于
fseek(fp, 0, SEEK_SET)。
- 参数 :文件指针
fp。
- 返回值 :无(
void)
c
复制代码
#include <stdio.h>
int main()
{
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
// fseek: 移动文件指针到第7个字符
if (fseek(fp, 6, SEEK_SET) == 0) {
char c = fgetc(fp);
printf("第7个字符: %c\n", c);
} else {
printf("fseek 失败\n");
}
// ftell: 获取当前位置
long pos = ftell(fp);
if (pos != -1L) {
printf("当前位置: %ld\n", pos);
} else {
printf("ftell 失败\n");
}
// rewind: 回到文件开头
rewind(fp);
printf("文件指针已回到开头\n");
fclose(fp);
return 0;
}
2-7 文件状态
feof(FILE *fp)
- 作用:检查文件是否到达末尾(EOF)
- 参数 :文件指针
fp。
- 返回值 :
- 文件到达末尾 → 非零值(true)
- 未到末尾 → 0(false)
c
复制代码
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
char c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
if (feof(fp)) {
printf("\n已到文件末尾\n");
}
fclose(fp);
return 0;
}
ferror(FILE *fp)
- 作用:检查文件操作是否发生错误。
- 参数 :文件指针
fp。
- 返回值 :
- 有错误 → 非零值(true)
- 无错误 → 0(false)
c
复制代码
#include <stdio.h>
int main() {
FILE *fp = fopen("nofile.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
int c = fgetc(fp);
if (ferror(fp)) {
printf("读取文件时发生错误\n");
}
fclose(fp);
return 0;
}
clearerr(FILE *fp)
- 作用:清除文件的错误标志和 EOF 标志。
- 参数 :文件指针
fp。
- 返回值 :无(
void)
c
复制代码
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
// 假设已经读取到文件末尾
while (fgetc(fp) != EOF);
if (feof(fp)) {
printf("到达文件末尾,准备清除标志\n");
clearerr(fp); // 清除 EOF 标志
}
if (!feof(fp)) {
printf("EOF 标志已清除\n");
}
fclose(fp);
return 0;
}
总结:
- 本节主要介绍了 C 语言的结构体定义与使用、结构体指针及数组、结构体嵌套和链表结构 ,以及 文件的定义、打开与关闭、文本与二进制文件的读写、格式化输入输出函数、文件定位函数及文件状态检测函数(feof、ferror、clearerr)。
- 自此本系列主要内容就结束了~
- 往期回顾:
- 如有错误,欢迎指出!