C语言之用户自己建立的数据结构
结构体的声明和使用
结构体的声明
struct 结构体名{
...
成员表列
...
}变量名表列
结构体的使用方法
struct 结构体名 变量名 = {成员表列};
可以使用变量名.成员名 来访问某个数据
也可以使用 结构体指针->成员名来访问某个数据
结构体指针变量p用p->field或(*p).field指向结构体里面的内容
结构体变量p用p.field指向结构体里面的内容
结构体变量的使用示例
#include <stdio.h>
#include <string.h>
//根据成绩高低输出结构体
struct student{
int id;
char name[20];
float score;
};
int main(){
//定义结构体变量student1,student2,temp
struct student student1,student2,temp;
//定义结构体指针student3指向student1
struct student *student3 = &student1;
//输入id name 和score
//结构体指针用p->field或(*p).field 结构体用p.field指向结构体里面的内容
//scanf("%d%s%f",&student3->id,student3->name,&student3->score);
scanf("%d%s%f",&(*student3).id,(*student3).name,&(*student3).score);
scanf("%d%s%f",&student2.id,student2.name,&student2.score);
/**
交换算法 也可以用memcpy方法
memcpy(&temp,&student1,sizeof(student1));
memcpy(&student1,&student2,sizeof(student2));
memcpy(&student2,&temp,sizeof(temp));
**/
if(student1.score<student2.score){
temp = student1;
student1 = student2;
student2 = temp;
}
//按大小交换排序后 输出两个结构体的详细内容
printf("id=%d name=%s score=%.2f\n",student1.id,student1.name,student1.score);
printf("id=%d name=%s score=%.2f",student2.id,student2.name,student2.score);
return 0;
}
结构体数组的声明和使用
结构体数组的声明
struct student
{
int id;
char name[20];
float score;
};
struct student stu[6]= {{1001,"LI MING",86},{1002,"LI HUA",80},{1003,"LIU MING",79},
{1004,"ZHANG MING",85},{1005,"WANG MING",90},{1006,"LI LIN",87}};
实际的使用方法和数组的使用是一样的
比如:需要使用第一个成员的score变量 就使用stu[0].score来访问
结构体的拷贝可以直接使用memcpy函数进行拷贝
结构体数组的使用
#include <stdio.h>
#include <string.h>
//根据成绩高低输出学生结构体
struct student
{
int id;
char name[20];
float score;
};
int main()
{
//定义结构体数组stu
struct student stu[6]= {{1001,"LI MING",86},{1002,"LI HUA",80},{1003,"LIU MING",79},
{1004,"ZHANG MING",85},{1005,"WANG MING",90},{1006,"LI LIN",87}
};
struct student temp;
int i,j,k;
//冒泡排序
for( i = 0 ; i < 4; i ++)
{
k = i;
for(j=i+1; j<5; j++)
{
//如果后者的分数比前者高
if(stu[k].score<stu[j].score)
{
k = j;
}
}
if(k != i)
{
//交换两个结构体
temp = stu[i];
stu[i] = stu[k];
stu[k] = temp;
}
}
//按分数从大到小输出
for( i = 0 ; i < 6; i ++)
{
printf("id=%d name=%s score=%.2f\n",stu[i].id,stu[i].name,stu[i].score);
}
return 0;
}
结构体指针的声明和使用
结构体指针的声明
结构体指针就是一个指向结构体变量的指针变量
使用方法和特点与正常的数组指针是一样的
结构体指针变量p用p->field或(*p).field来指向结构体里面的内容
struct student{
int id;
char name[20];
float score;
};
struct student stu[6]= {{1001,"LI MING",86},{1002,"LI HUA",80},{1003,"LIU MING",79},
{1004,"ZHANG MING",85},{1005,"WANG MING",90},{1006,"LI LIN",87}
};
struct student *temp=stu;
结构体指针的使用
#include <stdio.h>
#include <string.h>
//根据成绩高低输出结构体
struct student{
int id;
char name[20];
float score;
};
//定义一个结构体数组
struct student stu[6]= {{1001,"LI MING",86},{1002,"LI HUA",80},{1003,"LIU MING",79},
{1004,"ZHANG MING",85},{1005,"WANG MING",90},{1006,"LI LIN",87}
};
int main(){
//用结构体指针去循环输出结构体数组中的全部内容 p->field等价于(*p).field
for(struct student *temp=stu ;temp<stu+6;temp++){
//printf("id=%d name=%s score=%.2f\n",temp->id,temp->name,temp->score);
printf("id=%d name=%s score=%.2f\n",(*temp).id,(*temp).name,(*temp).score);
}
return 0;
}
链表及相关应用
单链表及相关应用
单链表:本质上是一个含有一个结构体指针的结构体
这个结构体指针在没有后继节点的时候指向为NULL
一旦有后继节点 则该结构体指针指向后继节点的首地址
单链表判断队尾 就是该节点的指向后继节点的指针指向为NULL 这就是达到了队尾的标志 此时p->next=NULL
以下代码为单链表的基础用法 包括 单链表节点的声明/交换/遍历
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//单链表及相关应用
//定义单链表
struct student{
int id;
char name[20];
float score;
struct student *next ;
};
char *str[] = {"1st" , "2nd" , "3rd"};
int main(){
//先初始化第一个节点 next值为空
struct student stu1 = {1001,"LI MING",86,NULL};
printf("1st node is id=%d name=%s score=%.2f\n",stu1.id,stu1.name,stu1.score);
//初始化第二个节点 next值为空 使用malloc 动态分配内存给temp指向的结构体变量
struct student *temp =(struct student *) malloc(sizeof(struct student));
temp->id=1002;
strcpy(temp->name,"LI HUA");
temp->score=95;
temp->next=NULL;
//第一个节点的next值设置为第二个节点
stu1.next=temp;
printf("2nd node is id=%d name=%s score=%.2f\n",stu1.next->id,stu1.next->name,stu1.next->score);
//初始化第三个节点 next值为空
struct student *temp1 =(struct student *) malloc(sizeof(struct student));
temp1->id=1003;
strcpy(temp1->name,"LIU QIANG");
temp1->score=90;
temp1->next=NULL;
//将第一个节点的next指向的第二个节点的next值设置为第二个节点
stu1.next->next=temp1;
printf("3rd node is id=%d name=%s score=%.2f\n",stu1.next->next->id,stu1.next->next->name,stu1.next->next->score);
//交换第二个节点和第三个节点
//保存头节点地址
struct student head = stu1;
//存储两个节点的值
//拿temp2储存一个节点
struct student *temp2 =(struct student *) malloc(sizeof(struct student));
temp2 = head.next;
head.next = head.next->next;
head.next->next=temp2;
head.next->next->next=NULL;
//遍历链表
struct student *p;
int i=0;
//如果p指向NULL那就表示遍历结束 到达链表尾部
for(p=&head;p!=NULL;p=p->next,i++){
printf("%s node is id=%d name=%s score=%.2f\n",str[i],p->id,p->name,p->score);
}
return 0;
}
交换两个节点需要按照如上代码 修改两个节点的next指针的指向 还要用中间变量保存一个节点的内容
双链表及相关应用
双链表:本质上是一个含有两个结构体指针的结构体
一个结构体指针head指向前一个节点的首地址
另一个结构体指针next指向后一个节点的首地址
双链表判断队头 就是该节点的指向前一个节点的指针指向为NULL 这就是达到了队头的标志 此时p->head=NULL
双链表判断队尾 就是该节点的指向后继节点的指针指向为NULL 这就是达到了队尾的标志 此时p->next=NULL
以下代码为双链表的基础用法 包括 双链表节点的声明/交换/遍历
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//双链表及相关应用
//定义双向链表
struct student{
int id;
char name[20];
float score;
struct student *next,*head;
};
char *str[] = {"1st" , "2nd" , "3rd"};
int main(){
//先初始化第一个节点 next值为空
struct student stu1 = {1001,"LI MING",86,NULL,NULL};
printf("1st node is id=%d name=%s score=%.2f\n",stu1.id,stu1.name,stu1.score);
//初始化第二个节点 next值为空 head值也为空
struct student *temp =(struct student *) malloc(sizeof(struct student));
int i=0;
temp->id=1002;
strcpy(temp->name,"LI HUA");
temp->score=95;
temp->next=NULL;
temp->head=NULL;
//第一个节点的next值设置为第二个节点
stu1.next=temp;
stu1.next->head=&stu1;
printf("2nd node is id=%d name=%s score=%.2f\n",stu1.next->id,stu1.next->name,stu1.next->score);
//初始化第三个节点 next值为空 head值也为空
struct student *temp1 =(struct student *) malloc(sizeof(struct student));
temp1->id=1003;
strcpy(temp1->name,"LIU QIANG");
temp1->score=90;
temp1->next=NULL;
temp1->head=NULL;
//将第一个节点的next指向的第二个节点的next值设置为第二个节点
stu1.next->next=temp1;
stu1.next->next->head=stu1.next;
printf("3rd node is id=%d name=%s score=%.2f\n",stu1.next->next->id,stu1.next->next->name,stu1.next->next->score);
//交换第二个结点和第三个结点
//保存头节点地址
struct student head = stu1;
//存储两个节点的值
//拿temp储存一个节点
//依次交换head和next的值
printf("swap 2nd node with 3rd node\n");
struct student *temp2 =(struct student *) malloc(sizeof(struct student));
temp2 = head.next;
head.next = head.next->next;
head.next->head=&head;
head.next->next=temp2;
head.next->next->head=head.next;
head.next->next->next=NULL;
//遍历链表
struct student *p,*rare;
//如果p指向NULL那就表示遍历结束 到达链表尾部
printf("traversal from head to rear\n");
for(i=0,p=&head;p!=NULL;p=p->next,i++){
printf("%s node is id=%d name=%s score=%.2f\n",str[i],p->id,p->name,p->score);
if(p->next==NULL){
rare = p;
}
}
printf("traversal from rear to head\n");
for(i=0;rare!=NULL;rare=rare->head,i++){
printf("%s node is id=%d name=%s score=%.2f\n",str[i],rare->id,rare->name,rare->score);
}
return 0;
}
交换两个节点需要按照如上代码 修改两个节点的next和head指针的指向 还要用中间变量保存一个节点的内容
循环链表及相关应用
循环链表:本质上是一个含有一个结构体指针的结构体
这个一个结构体指针一般指向后一个节点的首地址
循环链表和单链表的不同之处在于 该表中最后一个结点的指针域指向头结点,整个链表形成一个环,故称循环链表。
判断循环链表为空的标准在于 head==head->next;
头节点等于头结点的后继节点
以下代码为循环链表的基础用法 包括 循环链表节点的声明/交换/遍历
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//循环链表及相关应用
//定义循环链表
struct student{
int id;
char name[20];
float score;
struct student *next;
};
char *str[] = {"1st" , "2nd" , "3rd"};
int main(){
//先初始化第一个节点 next值为空
struct student stu1 = {1001,"LI MING",86,NULL};
struct student *headnode= &stu1;
printf("1st node is id=%d name=%s score=%.2f\n",stu1.id,stu1.name,stu1.score);
//初始化第二个节点 next值为空
struct student *temp =(struct student *) malloc(sizeof(struct student));
temp->id=1002;
strcpy(temp->name,"LI HUA");
temp->score=95;
temp->next=NULL;
//第一个节点的next值设置为第二个节点
stu1.next=temp;
stu1.next->next=&stu1;
printf("2nd node is id=%d name=%s score=%.2f\n",stu1.next->id,stu1.next->name,stu1.next->score);
//初始化第三个节点 next值为空
struct student *temp1 =(struct student *) malloc(sizeof(struct student));
temp1->id=1003;
strcpy(temp1->name,"LIU QIANG");
temp1->score=90;
temp1->next=NULL;
//将第一个节点的next指向的第二个节点的next值设置为第二个节点
stu1.next->next=temp1;
stu1.next->next->next=&stu1;
printf("3rd node is id=%d name=%s score=%.2f\n",stu1.next->next->id,stu1.next->next->name,stu1.next->next->score);
//交换第二个节点和第三个节点
//保存头节点地址
struct student head = stu1;
//存储两个节点的值
//拿temp储存一个节点
struct student *temp2 =(struct student *) malloc(sizeof(struct student));
temp2 = head.next;
head.next = head.next->next;
head.next->next=temp2;
head.next->next->next=&head;
//遍历循环链表
struct student *p=&head;
int i=0;
//如果p的next指向循环链表的头那就表示遍历结束 即到达链表尾部
do{
printf("%s node is id=%d name=%s score=%.2f\n",str[i],p->id,p->name,p->score);
p=p->next;
i++;
}
while(p!=&head);
return 0;
}
链表的小结
- 链表就是含有结构体指针的一个有序的结构体串
- 链表主要分为三种 单链表/双链表/循环链表
- 因为单链表是顺序储存的 所以单链表适合顺序查找和顺序遍历
- 因为双链表是顺序储存的 所以双链表也适合顺序查找和双向的遍历
- 因为循环链表是顺序储存的 所以循环链表也适合顺序查找和顺序遍历
- 单链表和双链表本质都是差不多的 唯一需要注意的地方就是交换新增节点时的指针的修改
- 循环链表和单/双链表的区别就是 循环链表是一个环形结构,整个链表头尾相连,成环
栈及相关应用
栈的基本性质
- 栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
- 栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
- 栈具有先入后出的性质 栈内元素具有FILO(先入后出)的特性
- 在计算机中 通常用来保存程序的入口和参数 以及函数内的临时变量
- 栈有大小 栈中存放的数据数量不能超过栈中可以存放的数目 超过就会出现栈的溢出的问题 导致程序乃至系统崩溃
栈的基本应用
以下代码为栈的基础用法 包括 栈的进栈函数push()/出栈函数pop()/返回栈顶函数top()/清空栈的函数clear()
#include <stdio.h>
//用数组模拟栈 FILO 先入后出
//元素elem进栈
int push(int* a,int top,int elem){
a[++top]=elem;
return top;
}
//数据元素出栈
int pop(int * a,int top){
if (top==-1) {
printf("空栈");
return -1;
}
printf("出栈元素:%d\n",a[top]);
top--;
return top;
}
//tops方法返回栈顶元素
int tops(int * a,int top){
if (top==-1) {
printf("空栈");
return -1;
}
return a[top];
}
int main() {
int a[100];
int top=-1;
//通过push入栈 通过top查看入栈情况
top=push(a, top, 1);
printf("栈顶元素 %d\n",tops(a,top));
top=push(a, top, 2);
printf("栈顶元素 %d\n",tops(a,top));
top=push(a, top, 3);
printf("栈顶元素 %d\n",tops(a,top));
top=push(a, top, 4);
printf("栈顶元素 %d\n",tops(a,top));
//通过pop函数 按先入后出的顺序弹出入栈的元素
top=pop(a, top);
top=pop(a, top);
top=pop(a, top);
top=pop(a, top);
top=pop(a, top);
return 0;
}
队列及相关应用
队列的基本性质
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
- 进行插入操作的端称为队尾,进行删除操作的端称为队头。
- 队列具有先入先出的性质 队列内元素具有FIFO(先入先出)的特性
- 在计算机中 用于调节不同读写数据的部分的工作情况 例如:打印机依照打印序列顺序打印缓冲区的多个文件 CPU调度算法中的时间片轮转算法 就是队列在计算机方面的多种应用
- 队列是现用现申请存储区间的线性链表 因而也不存在溢出等问题。
队列的基本应用
以下代码为队列的基础用法 包括 队列的进队列函数push()/出队列函数pop()/返回队列顶函数top()/清空队列的函数clear()
#include <stdio.h>
#include <string.h>
//用数组模拟队列 FLFO 先入先出
//入队 返回队列尾部位置
int push(int *a,int rear,int data){
a[rear]=data;
rear++;
return rear;
}
//出队 返回目前的队列头位置
int pop(int *a,int front,int rear){
if(front==rear){
return 0;
}
printf("出队元素:%d\n",a[front]);
return front+1;
}
//返回队顶元素
int top(int *a,int front,int rear){
if(front==rear){
return 0;
}
else{
return a[front];
}
}
void clear(int *a,int front,int rear){
//如果 front==rear,表示队列为空
while (front!=rear) {
printf("出队元素:%d\n",a[front]);
front++;
}
//全部初始化
memset(a,'\0',100*sizeof(int));
}
int main() {
int a[100];
int front,rear;
//设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
front=rear=0;
//入队
rear=push(a, rear, 1);
rear=push(a, rear, 2);
rear=push(a, rear, 3);
rear=push(a, rear, 4);
//top 输出队列顶部元素
top(a,front,rear);
//出队方法1 pop 一次出一个
front = pop(a,front,rear);
front = pop(a,front,rear);
//出队方法2 clear 一次出完
clear(a, front, rear);
return 0;
}
共用体类型的声明和使用
共用体类型的声明
共用体类型的声明
union 共用体名{
...
成员表列
...
}变量表列
共用体的使用方法和结构体一样的
就是用共用体的变量名p p.filed指向p中的某个成员变量
共用体和结构体的区别在于
共用体共用一段内存空间 大小为该结构体内占用内存最大的成员变量的内存大小 在使用之前的最后一次赋值的内容才能启用 之前赋值的会被后续的赋值覆盖
而结构体声明时就开辟了能存放全部的成员变量的内存空间 各个成员之间的赋值和使用互不干扰
共用体类型的使用
以下代码为共用体类型的基础用法 包括 共用体类型的声明/赋值/输出等操作
#include <stdio.h>
//共用体公用一段内存空间 在使用之前的最后一次赋值的内容才能启用 之前赋值的没用
union data{
char c;
int i;
float f;
long long l;
};
int main(){
union data d;
d.c='b',d.i=97;
//此时共用体内最后一次赋值为97 ASCII码对应a
printf("c is %c and i is %d and f is %f\n",d.c,d.i,d.f);
//此时共用体大小为最大的成员的大小 long long 64位 字节数为8
printf("size of union is %d\n",sizeof(union data));
d.c='g',d.i=101;
//此时共用体内最后一次赋值为101 ASCII码对应e
printf("c is %c and i is %d and f is %f\n",d.c,d.i,d.f);
//此时共用体大小为最大的成员的大小 long long 64位 字节数为8
printf("size of union is %d",sizeof(union data));
return 0;
}
枚举类型的声明和使用
枚举类型的声明
枚举类型的声明
enum 枚举名{
枚举元素列表
}
比如 enum weekdays{mon,tue,wed,thu,fri,sat,sun};
可以定义一系列合法的枚举类型变量并赋值
enum weekdays{mon,tue,wed,thu,fri,sat,sun};
enum weekdays weekend,weekday;
weekend = mon ;//mon是枚举类型中定义的枚举元素 赋值合法
weekday = sunday ;//sunday不是枚举类型中定义的枚举元素 赋值不合法
枚举类型需要注意的内容和特点:
- 枚举能够使代码更清晰,更优雅,能清辨的进行循环或者遍历
- 枚举使代码更易于维护,能限定变量能使用的值域和范围
- 枚举类型的值并不属于任何基础数据类型,不能直接输出到printf语句中,需要转换后才能输出
枚举类型的使用
以下代码为枚举类型的基础用法 包括 枚举类型的声明/赋值/输出等操作
#include <stdio.h>
//枚举相关操作
//从星期六和星期天选一天休息日 和从星期一到星期五选一天非休息日 输出所有可能的排列
// 排列数C52 = 10 所以一共有十种排列
//声明枚举类型weekdays 储存非字符型数据 使用枚举类型主要方便遍历
enum weekdays{mon,tue,wed,thu,fri,sat,sun};
enum Num{};
int main(){
//定义枚举类型变量
enum weekdays weekend,weekday ,p;
int n=0;
//使用两个枚举类型变量 作为循环的相关参数
for(weekend=sat;weekend<=sun;weekend++){
for(weekday=mon;weekday<=fri;weekday++){
if(weekend!=weekday){
n++;
printf("case %d ",n);
for(int i=0;i<2;i++){
switch(i){
case 0:p=weekend,printf(" weekend is ");break;
case 1:p=weekday,printf(" weekday is ");break;
default:break;
}
//枚举类型数据不能直接输出,只能转化后再输出
switch(p){
case sun:printf("%s ","SUNDAY");break;
case mon:printf("%s ","MONDAY");break;
case tue:printf("%s ","TUESDAY");break;
case wed:printf("%s ","WEDNESDAY");break;
case thu:printf("%s ","THURSDAY");break;
case fri:printf("%s ","FRIDAY");break;
case sat:printf("%s ","SATURDAY");break;
default:break;
}
}
printf("\n");
}
}
}
return 0;
}
使用typedef声明新类型名
typedef的定义
既可以简单的用typedef声明一个新类型名来代替原来的数据类型
typedef char Line[81];Line text, secondline;
typedef char* PCHAR;PCHAR pa, pb;
也可以使用typedef来代替一个复杂的数据类型
typedef struct tagPOINT
{
int x;
int y;
}point;
point p2 ={1,2};
//而不用使用 struct tagPOINT p2 = {1,2};来定义
- 使用typedef 可以杜绝声明变量时可能遇到的错误
- 用typedef只是对已经存在的类型增加一个类型名,而没有创造新的数据类型
- 用typedef与#define 宏定义的效果类似 但是不一样 #define是预编译时期进行文本替换 而typedef只是简单的为数据类型取个别名而已
typedef的使用
以下代码为typedef的基础用法
#include <stdio.h>
//typedef 关键字可以自行定义数据类型
//效果和#define类似 但是不是预编译时期进行文本替换
struct tagPOINT1
{
int x;
int y;
};
//使用typedef修饰结构体 必须加上别名 不加就不通过编译
typedef struct tagPOINT
{
int x;
int y;
}point;
int main(){
typedef char* PCHAR;
//char *pa,pb;可能会以为定义了pa pb两个字符指针 容易错误
//PCHAR pa, pb;就不会出错
PCHAR pa, pb;
//正常情况初始化 结构体 需要多输一个struct
struct tagPOINT1 p1 = {1,2};
//使用typedef 每次可以减少一个struct
point p2 ={1,2};
printf("%d\n",sizeof(point));
//避免重复定义复杂变量
//不用每次都声明一个字符数组
typedef char Line[81];
Line text, secondline;
gets(text);
puts(text);
return 0;
}