枚举
枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。
枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
如何使用枚举
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
可以在定义枚举类型时改变枚举元素的值:
enum season {spring, summer = 3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5
枚举变量的定义
1.先定义枚举类型,再定义枚举变量
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2.定义枚举类型的同时定义枚举变量
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}day;
3.省略枚举名称,直接定义枚举变量
enum
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}day;
应用实例
#include <stdio.h>
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;
printf("%d", day);
return 0;
}
结构体
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组
};
struct stu{
char *name;
int num;
int age;
char group;
float score;
};
stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。
stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。
也可以在定义结构体的同时定义结构体变量:
struct stu{
char *name;
int num;
int age;
char group;
float score;
}stu1, stu2;
成员的获取和赋值
#include <stdio.h>
int main(){
struct{
char *name;
int num;
int age;
char group;
float score;
} stu1;
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 136.5;
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.lf\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
return 0;
}
除了可以对成员进行逐一赋值,也可以在定义时整体赋值
struct stu{
char *name;
int num;
int age;
char group;
float score;
}stu1 = {"Tom", 12, 18, 'A', 136.5};
结构体数组
# include <stdio.h>
# include <string.h>
struct
{
char name[20];
int age;
char sex;
char num[20];
}stu[2];
int main(void)
{
int i = 0;
struct stu[2] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}} , {"小七", 23, 'F', "Z1207022"}, {"小欣", 20, 'F', "Z1207015"}, {"小天", 19, 'M', "Z1207024"}};
for(i; i < 5; i++)
{
printf("%s %d %c %s\n",stu[i].name, stu[i].age,stu[i].sex, stu[i].num);
}
return 0;
}
结构体指针
一个结构体变量的指针就是该结构体变量所占据内存段的起始地址。
可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。
同时指针变量也可以用来指向结构体数组中的元素。
结构体指针变量说明的一般形式为:
struct 结构名 *结构指针变量名
例如,在前面的例题中定义了stu这个结构,如果要说明一个指向stu的指针变量pstu,可写为
struct stu *pstu;
当然也可以在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构体指针变量也必须要先赋值才能使用
赋值就是把结构变量首地址赋予该指针变量,不能把结构名赋予该指针变量。
如果boy是被说明为stu类型的结构变量,则:
pstu = &boy;是正确的
pstu = &stu;是错误的
因为结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,编译系统并不对他们分配内存空间,只有当某变量被说明为这种类型的结构时,才对该变量存储内存空间。
所以以上 pstu = &stu;是错误的,不可能去取一个结构名的首地址。有了结构指针变量,就更方便地访问结构变量的各个成员。
其访问的一般形式为:
(*结构指针变量).成员名
或为:
结构指针变量->成员名
例如:(*pstu).num
或者:pstu->num
文件
文件操作 | 函数 |
---|---|
文件的打开操作 | fopen 打开一个文件 |
文件的关闭操作 | fclose 关闭一个文件 |
文件的读写操作 | fgetc 从文件中读取一个字符 |
fputc 写一个字符到文件中去 | |
fgets 从文件中读取一个字符串 | |
fputs 写一个字符串到文件中去 | |
fprintf 往文件中写格式化数据 | |
fscanf 格式化读取文件中数据 | |
fread 以二进制形式读取文件中的数据 | |
fwrite 以二进制形式写数据到文件中去 | |
getw 以二进制形式读取一个整数 | |
putw 以二进制形式存贮一个整数 | |
文件状态检查函数 | feof 文件结束 |
ferror 文件读/写出错 | |
clearerr 清除文件错误标志 | |
ftell 了解文件指针的当前位置 | |
文件定位函数 | rewind 反绕 |
fseek 随机定位 |
fopen()函数
使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen(const char *filename, const char *mode);
filename 是字符串,用来命名文件,访问模式 mode 的值可以根据需要选择
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。程序会从文件的开头写入内容。如果文件存在,则会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
fclose()函数
int fclose(FILE *fp);
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
fgetc()函数
int fgetc(FILE *fp)
从fp指向的文件中,读入一个字符,同时将读写位置指针指向下一个字符
fputc()函数
int fputc(int c,FILE*fp)
将字符数据c输出到fp所指的文件中去,同时将读写位置指针指向下一个写入位置
fget()函数
char* fgets(char*s,int n,FILE *fp)
从fp所指文件读n-1个字符送入s指向的内存区,并在最后加一个‘\0’ 若读入n-1字符前遇换行符或文件尾(EOF)即结束
fput()函数
int fputs(char*s,FILE\*fp)
将存放在s中的字符串写到文字指针fp所指向的文件中去,同时将读写位置指针向前移动字符串长度个字节。不会将字符串结尾符‘\0’写入文件,也不会自动向文件写入换行符。
fscanf()函数
int fscanf(FILE *fp,const char\* format,[&a,&b,...])
链表
1.什么是链表
链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。
链表中每个数据的存储都由以下两部分组成:
-
数据元素本身,其所在的区域称为数据域;
-
指向直接后继元素的指针,所在的区域称为指针域;
图一
图 1所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 2所示:
图二
因此,链表中每个节点的具体实现,需要使用 C 语言中的结构体,具体实现代码为:
struct Node{
int data; //代表数据域
struct Node * next; //代表指针域,指向直接后继元素
}; node//node为节点名,每个节点都是一个 node 结构体
2.创建一个链表
创建多个存储数据的节点,在创建的过程中,要随时与其前驱节点建立逻辑关系,例如,创建一个存储 {1,2,3,4}
且无头节点的链表,C 语言实现代码如下:
node * initLink() {
int i;
node * p = NULL;//创建头指针
node * temp = (node*)malloc(sizeof(node));//创建首元节点
//首元节点先初始化
temp->elem = 1;
temp->next = NULL;
p = temp;//头指针指向首元节点
//从第二个节点开始创建
for (i = 2; i < 5; i++) {
//创建一个新节点并初始化
node *a = (node*)malloc(sizeof(node));
a->elem = i;
a->next = NULL;
//将temp节点与新建立的a节点建立逻辑关系
temp->next = a;
//指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
temp = temp->next;
}
//返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表
return p;
}
3.对链表的一些基本操作
对链表的一些基本操作,包括对链表中数据的添加、删除、查找(遍历)和更改。这些对链表的操作实现均建立在已创建好链表的基础上。
一.链表增添元素
向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:
1.插入到链表的头部(头节点之后),作为首元节点;
2.插入到链表中间的某个位置;
3.插入到链表的最末端,作为链表中最后一个数据元素;
虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:
- 将新结点的 next 指针指向插入位置后的结点;
- 将插入位置前结点的 next 指针指向插入结点;
例如,我们在链表 {1,2,3,4}
的基础上分别实现在头部、中间部位、尾部插入新元素 5,其实现过程如图1所示:
图一
从图中可以看出,虽然新元素的插入位置不同,但实现插入操作的方法是一致的,都是先执行步骤 1 ,再执行步骤 2。链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,会导致插入位置后续的部分链表丢失,无法再实现步骤 1。
下面,我们可以尝试编写 C 语言代码来实现链表插入元素的操作:
//p为原链表,elem表示新数据元素,add表示新元素要插入的位置
node * insertElem(node * p, int elem, int add) {
node * temp = p;//创建临时结点temp
node * c = NULL;
int i = 0;
//首先找到要插入位置的上一个结点
for (i = 1; i < add; i++) {
if (temp == NULL) {
printf("插入位置无效\n");
return p;
}
temp = temp->next;
}
//创建插入结点c
c = (node*)malloc(sizeof(node));
c->elem = elem;
//向链表中插入结点
c->next = temp->next;
temp->next = c;
return p;
}
二.链表删除元素
从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,但作为一名合格的程序员,要对存储空间负责,对不再利用的存储空间要及时释放。因此,从链表中删除数据元素需要进行以下 2 步操作:
- 将结点从链表中摘下来;
- 手动释放掉结点,回收被结点占用的存储空间;
其中,从链表上摘除某节点的实现非常简单,只需找到该节点的直接前驱节点 temp,执行一行程序:
temp->next=temp->next->next;
例如,从存有 {1,2,3,4}
的链表中删除元素 3,则此代码的执行效果如图 2 所示:
因此,链表删除元素的 C 语言实现如下所示:
//p为原链表,add为要删除元素的值
node * delElem(node * p, int add) {
node * temp = p;
node * del = NULL;
int i = 0;
//temp指向被删除结点的上一个结点
for (i = 1; i < add; i++) {
temp = temp->next;
}
del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
free(del);//手动释放该结点,防止内存泄漏
return p;
}
三.链表查找元素
在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL
(比对失败的标志)。
因此,链表中查找特定数据元素的 C 语言实现代码为:
//p为原链表,elem表示被查找元素、
int selectElem(node * p, int elem) {
//新建一个指针t,初始化为头指针 p
node * t = p;
int i = 1;
//由于头节点的存在,因此while中的判断为t->next
while (t->next) {
t = t->next;
if (t->elem == elem) {
return i;
}
i++;
}
//程序执行至此处,表示查找失败
return -1;
}
四.链表更新
更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
直接给出链表中更新数据元素的 C 语言实现代码:
//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
node *amendElem(node * p, int add, int newElem) {
int i = 0;
node * temp = p;
temp = temp->next;//在遍历之前,temp指向首元结点
//遍历到被删除结点
for (i = 1; i < add; i++) {
temp = temp->next;
}
temp->elem = newElem;
return p;
}
4.关于更多
除了链表,当然更多的其他的线性表,如顺序表,静态链表,动态链表,循环链表,双向链表,双向循环链表