线性表是典型的线性结构,用顺序存储的方式实现ADT List,并在此基础上实现顺序表的输入输出以及插入删除。
[实验目的]
- 认识符号常量和宏定义。
- 学习编写头文件。
- 掌握线性表的顺序存储结构。
- 实现顺序表的基本操作。
[实验内容及要求]
- 回顾课堂上老师的讲述,将课本第10页上的预定义常量和类型封装成一个头文件“predef.h”,供以后的每次实验使用。
- 参照课本第22页,实现顺序表的存储结构SqList,封装在头文件“sqlist.h”中。
- 参照课件(或者课本),实现顺序表的基本操作:求表长度、构造一个空顺序表、获取某个元素的值、插入新元素、删除某个元素等,封装在头文件“sqlist.h”中。
- 在源文件“sqlist_try.cpp”中,调用基本操作实现功能函数——顺序表的批量输入:依次输入n个数据元素存放在顺序表中(n由用户在程序运行时输入)。
- 在源文件“sqlist_try.cpp”中,调用基本操作实现功能函数——顺序表的批量输出:依次输出当前顺序表中的所有数据元素。
- 在源文件“sqlist_try.cpp”中,在主函数中调用上述功能函数和基本操作:批量向顺序表中输入数据元素,尝试插入和删除某个数据元素,输出顺序表中的所有数据元素并观察每次操作的结果是否正确。
[测试数据](示例)
- 1 2 3 4 5 6 7 8 9 10
- 12 32 65 9 21
- 98 5 87 66 4 7 54
- 101 6 88 76 5 89
- 10 109 87 78 86 5 61
- ……
- ……
[程序流程图](主要功能模块)
[实验过程与结果](抓图和分析)
主测试函数:
测试输出:
[实验体会](随笔)
一、容量问题
主要是对于顺序表功能和操作代码的健壮性需要慎重考虑,如在插入元素或批量写入元素时,如果遇到顺序表容量不过够的问题,需要扩大空间
if (L.length + 1 > L.listsize) // 当前存储空间已满,增加分配
{
ElemType* newbase = (ElemType*)realloc(L.elem, (L.listsize + LISTINCREAMENT) * sizeof(ElemType));
if (!newbase) exit(OVERFLOW); // 存储分配失败
L.elem = newbase; // 新基址
L.listsize += LISTINCREAMENT; // 增加存储容量
}
二、越界访问
要注意好元素移动时的下标位置,不要越界访问。
[思考]
(1)在程序设计语言中,原子类型和结构类型之间有什么区别与联系?
一、区别:
1. 定义:
原子类型:是指不可再分的数据类型,通常由编程语言直接提供,如整型(int)、浮点型(float)、字符型(char)等。
结构类型:是由多个原子类型或其他结构类型组合而成的复合数据类型,如数组(array)、结构体(struct)、联合体(union)、类(class)等。
2. 表示方式:
原子类型:通常在内存中以固定大小的空间表示,例如一个整型可能占用4个字节。
结构类型:由多个成员组成,每个成员可以是原子类型或其他结构类型,它们在内存中的布局通常是连续的,但整体大小可能不固定。
3. 操作:
原子类型:通常只能进行基本的算术运算、关系运算和逻辑运算。
结构类型:除了可以包含原子类型的操作外,还可以定义更复杂的操作,如函数、方法等。
4. 内存分配:
原子类型:变量通常在栈上分配内存。
结构类型:可能需要在堆上分配内存,特别是当结构类型的大小较大或者需要动态管理时。
5. 用途:
原子类型:用于表示程序中的简单数据,如计数器、坐标点等。
结构类型:用于表示更复杂的数据结构,如链表节点、用户定义的数据模型等。
二、联系:
1. 组合:结构类型可以包含原子类型作为其成员,原子类型是构成结构类型的基础。
2. 操作一致性:尽管结构类型和原子类型的操作不同,但在某些情况下,结构类型可以提供与原子类型类似的操作接口,以提供一致性。
3. 内存管理:无论是原子类型还是结构类型,都需要合理管理内存,避免内存泄漏或越界访问。
4. 类型转换:在某些编程语言中,可以将结构类型转换为原子类型(通常是通过指针或引用),反之亦然。
5. 抽象层次:原子类型通常位于较低层次的抽象,而结构类型则提供更高层次的抽象,但它们都是编程语言中不可或缺的部分。
理解这两种类型的区别与联系对于程序设计来说非常重要,它们分别适用于不同的编程场景,合理使用可以有效地解决各种问题。
(2)在实现顺序表的存储结构SqList及其基本操作的过程中,有什么困难和疑问?
可能会出现越界访问的问题,或是内存空间不足溢出的问题,应该让代码提高健壮性,保障用户的正常使用。
最后将全部代码放出,欢迎大家一起讨论思考!
sqlist_try.cpp
#include <stdio.h>
#include "sqlist.h"
/*
author: Electronic_Rain
date: 2024/09/28
*/
int main()
{
SqList L;
InitList_Sq(L); // 初始化顺序表
ListWriting_Sq(L); // 批量写入顺序表
ListShow_Sq(L); // 显示顺序表
ListInsertElem_Sq(L, 3, 10); // 插入元素
ListShow_Sq(L); // 显示顺序表
ListDeleteElem_Sq(L, 1); // 删除元素
ListShow_Sq(L); // 显示顺序表
int e = GetElem_Sq(L, 3); // 获取第三个元素
std::cout << e << std::endl;
system("pause");
return 0;
}
predef.h
#pragma once
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define Status int
sqlist.h
#pragma once
#include "predef.h"
#include <malloc.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
// -----线性表的动态分配顺序表存储结构----- //
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量
#define LISTINCREAMENT 10 // 线性表存储空间的分配增量
#define ElemType int // 设定线性表中存储数据类型
typedef struct {
ElemType* elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量
}SqList;
// 初始化顺序表
Status InitList_Sq(SqList& L)
{
L.elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L.elem) exit(OVERFLOW); // 存储分配失败
L.length = 0; // 空表长度为0
L.listsize = LIST_INIT_SIZE; // 初始存储容量
return OK;
} // InitList_Sq
// 返回顺序表元素个数
int ListLength_Sq(const SqList L)
{
return L.length; //返回表长
} // ListLength_Sq
// 获取某个元素的值
ElemType GetElem_Sq(const SqList L, int pos)
{
return L.elem[pos - 1]; //返回pos-1位置元素
} // GetElem_Sq
// 插入新元素
Status ListInsertElem_Sq(SqList& L, int pos, ElemType ele)
{
if (pos < 1 || pos > L.length + 1) return ERROR; // pos值不合理
if (L.length + 1 > L.listsize) // 当前存储空间已满,增加分配
{
ElemType* newbase = (ElemType*)realloc(L.elem, (L.listsize + LISTINCREAMENT) * sizeof(ElemType));
if (!newbase) exit(OVERFLOW); // 存储分配失败
L.elem = newbase; // 新基址
L.listsize += LISTINCREAMENT; // 增加存储容量
}
for (int i = L.length; i >= pos; i--)
{
L.elem[i] = L.elem[i - 1]; // 将后面数字向后移位
}
L.elem[pos - 1] = ele; // 在pos-1位置赋值ele
L.length++; // 表长加一
return OK;
} // ListInsertElem_Sq
// 删除某个元素
Status ListDeleteElem_Sq(SqList& L, int pos)
{
if (pos < 1 || pos > L.length) return ERROR; // pos值不合理
for (int i = pos - 1; i < L.length - 1; i++)
{
L.elem[i] = L.elem[i + 1]; // 将元素前置一位
}
L.length--; // 表长减一
return OK;
} // ListDeleteElem_Sq
// 判断顺序表是否为空
Status ListIsEmpty_Sq(const SqList L)
{
return L.length == 0 ? OK : FALSE; // 表为空返回OK,不为空返回FALSE
} // ListIsEmpty_Sq
// 显示顺序表
void ListShow_Sq(const SqList L)
{
if (ListIsEmpty_Sq(L)) printf("表中无元素\n"); // 表为空
printf("顺序表中元素为:");
for (int i = 0; i < L.length; i++)
{
if (i == L.length - 1)
{
std::cout << L.elem[i] << std::endl;
return;
}
std::cout << L.elem[i] << ",";
}
} // ListShow_Sq
// 在顺序表后添加一个元素
Status ListPushBack_Sq(SqList& L, ElemType x)
{
if (L.length + 1 > L.listsize) // 当前存储空间已满,增加分配
{
ElemType* newbase = (ElemType*)realloc(L.elem, (L.listsize + LISTINCREAMENT) * sizeof(ElemType));
if (!newbase) exit(OVERFLOW); // 存储分配失败
L.elem = newbase; // 新基址
L.listsize += LISTINCREAMENT; // 增加存储容量
}
L.elem[L.length] = x;
L.length++;
return OK;
} // ListPushBack_Sq
// 输入顺序表数据
Status ListWriting_Sq(SqList& L)
{
ElemType x;
int n;
std::cout << "请输入顺序表中元素个数:";
std::cin >> n;
int count = 0;
while (n--)
{
count++;
std::cout << "请输入顺序表中第" << count << "位元素:";
std::cin >> x;
ListPushBack_Sq(L, x);
}
return OK;
} // ListWriting_Sq