目录
一.线性表
1.1 线性表的概念
1.2线性表的种类
1.2.1 静态线性表
1.2.2 线性表的动态存储
二动态顺序表的操作
2.1. 定义结构体与函数
2.2 初始化
2.2.1实参和形参的区别
2.2.2 用实参改变形参
题外话 int *p和int* p的区别
2.3 销毁
2.4 尾插
2.4.1 首先要判满,如果满了要扩容
注意这个方法有一个缺点,就是如果初始空间就是0,插入要扩容的话,0*2=0扩容不了,所以我们有两种解决办法
方法一:在结构体里将capacity和size初始化。都给一点空间
方法二:用三目运算符
扩容:用realloc
原地扩容:
异地扩容:
那么realloc可以对psl->a进行扩容吗?:是可以的。
具体实现步骤
2.5 头插法
2.6 尾删法
2.6.1 尾删法的注意事项
2.6.2 尾插法的步骤
温柔检查:
暴力检查:断言assert
2.7 指定位置的插入
2.8 制定位置的删除
逻辑地址和物理地址相同的线性结构,一般用数组储存。
是定长的线性表。在实际中不是很重要,给多了会浪费,给少了会溢出。
//线性表的静态存储
#define N 7
typedef int SLDataType;
typedef struct SeqList{
SLDataType array[N];
size_t size;
}SeqList;
先开辟一个空间,如果空间不够了在扩容。一般扩容是选择二倍扩容。
在头文件里定义结构体
#pragma once
typedf int SLDataType; //定义一个数据类型
typedf struct SeqList{
SLDataType* a;
int size; //有效数据
int capacity; //空间容量
}SL;
void SLInit(SL* psl);
void SLDestory(SL* psl);
函数声明在头文件.h,定义函数在.c文件
形参是实参的拷贝,所以形参的改变是不会改变实参的值
我们初始化的目的是想通过形参改变实参
如果想让形参改变实参,所以我们应该用指针变量来指向地址
int *p:的意思是解引用操作符,通过地址找到地址所指的对象
如 *p=20;是通过p所指的地址,取改变他的值
int *p = &20; 这里指的是p指针指向20这个元素的地址
int* p;是说明 p是一个指针变量
SeqList.c是用来实现函数,测试类Test.c是用一个例子去执行函数
测试类
void SLDestory(SL* psl) {
if (psl->a != NULL) {
free(psl->a);
psl->a = 0;
psl->size = 0;
psl->capacity = 0;
}
}
那么什么时候为满呢结论是当size和capacity相等的时候为满
if(psl->size==psl->capacity)
int newCapacity = psl->capacity*2
typedf struct SeqList{
SLDataType* a;
int size = 4; //有效数据
int capacity = 8; //空间容量
}SL;
int newcapacity = psl->capacity==0?4 : capacity*2 //如果capacity为0将空间设置为4,不为0扩2倍。
如果后面的空间没有被占用,就可以原地扩容
如果后面的空间被占用,那么就选择异地扩容。
注意,当你选择异地扩容时,系统会自动free掉源空间,不需要我们手动free。
易错点:为什么要用tmp不接收的原因是:怕万一扩容失败但是用原指针会导致赔了夫人又折兵,原地址也被覆盖。所以用tmp接收,在赋值给psl->。
当psl->a=0,capacity=0时,会现将capacity赋值为4,但是psl->任为0。
第一步:先编写头文件
头文件的用处是定义结构体和声明函数,函数的形参可以访问结构体成员,形参为指针可以改变实参的值。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
typedef int SLDataType; //定义数据类型SLDataType来代替int,当要修改数据类型时只用修改int计即可
typedef struct SeqList {
SLDataType* a;
int size;
int capacity;
}SL; //结构体别名
void SLInit(SL* psl); //定义函数名,定义psl指针,可用psl形参来访问结构体成员。
void SLDestory(SL* psl);
void SLPrint(SL* psl);
void SLPushBack(SL* psl, SLDataType x);
第二步,编写.c文件
.c文件的作用时:将头文件定义函数,具体实现出来。
尾插法是用psl->a[a->size] =x插入数据
先判满,若capacity=0,则赋初值,不为0,则扩两倍
扩容,用realloc扩容(结构体类型*)realloc(数组的地址,sizeof(结构体类型)* 新容量)
定义一个新SLDataType结构体类型变量tmp接收新空间的地址,的原因是防止扩充失败,导致原地址丢失。
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void SLInit(SL* psl) {
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void SLDestory(SL* psl) {
if (psl->a != NULL) {
free(psl->a);
psl->a = 0;
psl->size = 0;
psl->capacity = 0;
}
}
void SLPushBack(SL* psl,SLDataType x){
if (psl->size == psl->capacity) { //当空间满了,即数据个数等于容量
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;//当容量为0的时候扩容也为0,所以当capacity==0时给他赋初值4个空间,不为0扩容两倍
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL) { //如果扩容失败返回realloc fail
perror("realloc fail");
return 0;
}
psl->a = tmp; //用tmp接收的原因是,如果ralloc开辟失败,用psl->a接收会导致原始地址丢失
psl->capacity = newcapacity;
}
psl->a[psl->size] = x;
psl->size++;
}
void SLPrint(SL* psl) {
for (int i = 0; i < psl->size; i++) {
printf("%d ", psl->a[i]);
}
printf("\n");
}
第三步编写测试类Test
测试类的作用是,对函数的实现,进行数据测试。
用结构题别名SL定义sl类的作用是,SL只是一个框架,sl是具体的类,用来测试。
#define _CRT_SECURE_NO_WARNINGS
#include
#include"SeqList.h"
void TestL1() {
SL sl; //SL是结构体名,sl是创建一个类,用来测试
SLInit(&sl);
SLPushBack(&sl, 1); //插入数据
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
}
int main() {
TestL1();
}
我们先将空间扩容封装一个函数。
void SLCheckCapacity(SL* psl) {
assert(psl);
if (psl->size == psl->capacity) { //当空间满了,即数据个数等于容量
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;//当容量为0的时候扩容也为0,所以当capacity==0时给他赋初值4个空间,不为0扩容两倍
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL) { //如果扩容失败返回realloc fail
perror("realloc fail");
return 0;
}
psl->a = tmp; //用tmp接收的原因是,如果ralloc开辟失败,用psl->a接收会导致原始地址丢失
psl->capacity = newcapacity;
}
}
然后在进行头插,顺序表头插,要n-1个数据依次向后移动。
void SLPushFront(SL* psl, SLDataType x) {
assert(psl);
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= 0) {
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
(1)在尾删时不用将尾喉一个元素设置为-1;
(2)直接将size--
(3)直接size--的优势就是,size是有效数据的个数,size--,后在插入原最后一个数据被覆盖了,不用担心没有删掉的情况。因此不用free()
(4)在内存中free()不能只free()一块内存。
(1)先判空,不判空的话,再次插入数据会缺失数据
if(psl->size==0){
return;
}
assert(psl->size>0),为真不报错,为假报错。
assert(psl->size>0)
断言的头文件是include
void SLPopBack(SL* psl) {
assert(psl);
/*if (psl->size == 0) {
return;
}*/
assert(psl->size> 0);
psl->size--;
}
先异常判断,一个是指针不为空。
assert(psl)
一个是插入位置不能越界。
assert(pos>=0&&pos<=psl->size)
完整代码。
头文件
void SLInsert(SL* psl, int pos, SLDataType x);
.c文件
//指定下标位置插入
void SLInsert(SL* psl, int pos, SLDataType x) {
assert(psl);
assert(pos >= 0 && pos <= psl->size);
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= pos) {
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
测试以及结果
第一步异常判断,一个是指针不为空
一个是要删除的位置必须在【0,psl -> size-1)的位置
void SLErase(SL* psl, int pos) {
assert(psl);
assert(pos>=0&&possize);
int begin = pos + 1;
while (begin < psl->size) {
psl->a[begin - 1] = psl->a[begin];
++begin;
}
psl->size--;
}