数据存储实现(C语言)
1. 数组
1.1 定义:
一组相同类型数据的有序集合。
1.2 定义数组的一般形式:
类型名 数组名[数组长度];
int a[10];
1.3 特征:
在内存中连续存放,通过数组名和下标可唯一地确定数组元素。
1.4 数组的应用操作:
通过循环进行赋值。
2. 指针
2.1 定义:
指针———内存地址,指针变量——存放内存地址的变量(大小为一个十六进制数)。
2.2 定义指针变量的一般形式:
类型名 *指针变量名;
float *p;
2.3 指针类型:
不同的指针变量所占用的存储单元长度相同(内存的地址的大小相同),但地址里存放的数据因为存放数据的类型不同所占用的存储空间长度也不同。所以,抽象的说,定义指针变量的类型,指的是其指向的变量或常量的数据类型。(不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。)
2.4 指针与数组:
数组名本身就是数组的基地址。在访问内存方面,指针是以地址作为值的变量,而数组名的值是一个特殊的固定地址。(可以把它看作指针常量,且不能改变指针常量(数组名)的值)
2.5 用指针实现内存动态分配:
2.5.1动态存储分配函数void *malloc(unsigned size):
从内存的动态存储区中分配一连续空间,其长度为size。若申请成功则返回一个指向所分配空间的起始地址;若申请不成功,则返回NULL。该函数的返回值为(void *)类型。
int *p=(int*)malloc(n*sizeof(int));//void *类型强制转换为int * , 给p分配了长度为n的内存空间,即n个int大小的内存空间。
2.5.2动态存储释放函数 void free (void *ptr):
释放由malloc函数中申请的整块内存空间,ptr为指向要释放空间的首地址。
typedef struct Node *List;//将Node *重新命名为 PtrToNode
struct Node{
int Data;//结点数据
List Next;//指向下一结点的指针
};
s=(List)malloc(sizeof(struct Node));//申请空间
free(s);//释放空间
2.6 类型定义(typedef):
typedef 原有类型名 新类型名;
typedef int ElementType;//建立数据类型的别名
2.7 指针的基本运算:
如果指针的值是某个变量的地址,就能通过指针间接访问那个变量。取地址运算符:&
和 间接访问运算符:*
。
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("Address of var variable: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("Address stored in ip variable: %p\n", ip );
/* 使用指针访问值 */
printf("Value of *ip variable: %d\n", *ip );
return 0;
}
结果:
Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20
2.8指针常量、常量指针与指针变量(待修正)
const int* p;//常量指针
int const* p;//常量指针 第一次赋值后不能被改变
//本质是指针,并且这个指针乃是一个指向常量的指针且不能通过这个指针修改所指向的变量的值
int a;
int *const p = &a; //指针常量 一定要赋值 p为一个常量,它的值为a的地址
//本质是常量,const放在指针声明操作符的右侧,表明声明的对象是一个常量
int *p;//p为指针变量
//指针变量能改指向,指针常量不能转向!
//* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变。
3. 结构体
3.1 定义:
把一些数据分量聚合成一个类型名 结构成员1;的数据类型。同时,结构又是是一个变量的集合,可以单独使用其变量成员。(与数组的不同就是结构体的元素可以不是同一类型)
3.2结构类型定义的一般类型:
struct 结构名{
类型名 结构成员1;
类型名 结构成员2;
类型名 结构成员3;
类型名 结构成员4;
……
};
//在定义结构成员时的数据类型也可以是结构类型,形成嵌套。
3.3结构体变量定义的一般类型:
struct 结构名 结构变量名;//结构变量可以在定义结构类型的同时进行定义。也可以省略结构类型,直接定义结构变量(若多个结构类型相同,可通过此结构类型直接定义多个结构变量)。
//类比int a; int b;
//struct 结构名 a;struct 结构名 b; 即同时定义了两个相同结构类型的结构变量。
//若直接 struct a;struct b; 需要重新定义两次结构类型。
3.4结构变量的使用:
3.4.1 结构成员操作符:.
使用格式:结构变量名.结构成员名
3.4.2 结构数组:
结构数组的定义:
结构类型名 数组名[数组长度];
类似于int a[10];
结构数组的赋值:
#include<stdio.h>
#include <stdlib.h>
struct paper{
int number;
int color;
};
//typedef struct paper{
// int number;
// int color;
//}papers;//通过typedef重新将paper类型定义成papers
int main(){
int i;
// papers p1{1,1};
// papers p2{2,2};
// papers p3{3,3};
// papers p4{4,4};这样赋值也可 c11标准
paper p1;
p1.number=1;
p1.color=1;
paper p2;
p2.number=2;
p2.color=2;
paper p3;
p3.number=3;
p3.color=3;
paper p4;
p4.number=4;
p4.color=4;
struct paper p[4]={p1,p2,p3,p4};//struct paper即为类型
for(i=0;i<4;i++){
printf("number=%d,color=%d\n",p[i].number,p[i].number);
}
}
对结构数组元素成员的引用格式:结构数组名[下标].结构成员名
3.4.3 结构指针:
定义:指向结构类型变量的指针
结构指针的定义:结构类型 *指针变量名;
访问结构指针:
(1) (* 结构指针变量名).结构成员名;//等于结构体变量.结构成员名
(2) 结构指针变量名->结构成员名;
4. 链表
4.1单向链表
4.1.1 组成:
表头变量head:存放链表首结点的地址
结点:数据+下个结点的地址
表尾:链表中最后一个结点,下个结点的地址部分的值为NULL
4.1.2 特征:
链表的各个结点在内存中可能不是连续存放的,具体存放位置由系统分配。
链表克服了需要预知数据大小的缺点,实现灵活的内存动态管理。
4.1.3 C语言实现:
#include<stdio.h>
#include<stdlib.h>
typedef struct Node *List;//将Node *重新命名为 PtrToNode
struct Node{
int Data;//结点数据
List Next;//指向下一结点的指针
};
//typedef PtrToNode List;//List为“单链表类型”,即指向链表结点的指针。
//给定一个单链表通常就是给定一个指向该链表表头结点的指针
int Length(List PtrL); //输入指向链表的指针 求链表长度
List Find(int/*ElementType*/ X,List PtrL); //按值x查找
List FindKth(int K,List PtrL); //按次序k查找
List Insert (int/*ElementType*/ X,int i,List PtrL); //在链表PtrL的位置i插入结点 ,数据为X
List Delete(int i,List PtrL); //对链表PtrL 删除位置为i的结点
int main(){
List l;
l=(List)malloc(sizeof(struct Node));
l->Data=1; //新建一个结点l 输入数据为1
l->Next=NULL;
l=Insert(2,1,l); //在链表l的第一个结点前插入新结点 输入数据为2
l=Insert(3,1,l); //在链表l的第一个结点前插入新结点 输入数据为3
l=Insert(4,2,l); //在链表l的第二个结点前插入新结点 输入数据为4
l=Insert(5,2,l);
l=Insert(6,2,l);
l=Insert(7,2,l);
l=Delete(3,l); //删除链表l中第三个结点
printf("链表的长度为%d\n",Length(l));
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
l=l->Next;
printf("%d\n",l->Data);
}
int Length(List PtrL){
List p=PtrL;//定义一个结构体指针 指向 开始给定的表头
int j=0;
while(p){
p=p->Next;
j++;
}
return j;
}
List Find(int X,List PtrL){
List p=PtrL;
while (p!=NULL && p->Data!=X){
p=p->Next;
}
return p;
}
List FindKth(int K,List PtrL){
List p=PtrL;
int i=1;
while (p!=NULL&&i<K){
p=p->Next;
i++;
}
if(i==K){
return p;
}else{
return NULL;
}
}
List Insert (int/*ElementType*/ X,int i,List PtrL){
List p,s;//s为待插入的结点
if(i==1){
s=(List)malloc(sizeof(struct Node));//在定义了结构体指针s后,给它分配一块内存
s->Data=X;
s->Next=PtrL;
return s;//返回新链表的表头的指针
}
p=FindKth(i-1,PtrL);
if(p==NULL){
printf("参数i错误");
return NULL;
}else{
s=(List)malloc(sizeof(struct Node));
s->Data=X;
s->Next=p->Next;
p->Next=s;
return PtrL;//返回链表的表头的指针
}
}
List Delete(int i,List PtrL){
List p,s;
if(i==1){
s=PtrL;
if(PtrL!=NULL){
PtrL=PtrL->Next;
}else{
free(s);
}
return PtrL;
}
p=FindKth(i-1,PtrL);
if(p==NULL){
printf("第%d个结点不存在",i-1);//想要删除的结点i的前一个结点i-1不存在
}else if(p->Next==NULL){
printf("第%d个结点不存在",i);//想要删除的结点i的前一个结点i-1是表尾,故i不存在
}else{
s=p->Next;
p->Next=s->Next;
free(s);//释放被删除结点
return PtrL;
}
}
4.2 双向链表
4.2.1 特征:
双向链表就是在单向链表的基础上 ,在其原有结点上增加一个指向前驱的指针。
单项链表想要找到某一结点的前一结点(前驱)需要从链头找起。而双向链表则牺牲了空间代价,减少操作的代价。
4.2.2 C语言实现(参考单向链表):
(1)结点
typedef struct *PtrToNode;
struct Node{
ElememtType Data;
PtrToNode Next;
PtrToNode Pervious;//指向前一个结点的指针
}
(2)插入
//t为将要插入的结点
//p为被插入的位置的前一结点 p->Next为被插入位置的后一结点(即插入的位置)
t->Previous=p;
t->Next=p->Next;//链接好t
p->Next->Pervious=t; //后一节点的 指向前一个结点的指针 指向t
p->Next=t; //前一结点的 指向后一个结点的指针 指向t