前言
线性表(Liner List)
线性表:是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n = 0时,线性表是一个空表。
特点:一对一,除第一个唯一前驱,除最后一个有唯一后继.
a1–>a2–>a3–>a4
数据结构三要素:逻辑结构,存储结构,运算.
线性表是一种逻辑结构,有两种存储结构:顺序表和链表.
基本操作
- 创建,销毁
- 增加元素
- 删除元素
- 按位查找
- 按值查找
常用操作
- 判断表空
- 求表长
- 打印表
整个项目的构建
编程软件采用vscode,采用cmake管理多文件,不需要手动编译.
关于具体vscode的讲解.建议查看b站视频.
https://www.bilibili.com/video/BV13K411M78v?p=2&spm_id_from=pageDriver
顺序表(静态分配)
静态分配,元素为结构体的顺序表
定义
我们取元素为结构体,写一个复杂一点点的,注意比较结构体相同的时候,不能用==.
#define MAXSIZE 10
//这里的写法也可以typedef struct Student{} Student;一个意思
typedef struct{
int number; //序号
string name; //姓名
}Student;
//定义一个静态,元素为字符的顺序表,最大元素10
typedef struct
{
int length; //当前表长
Student data[MAXSIZE] ; //数据
}Seqlist;
初始化
典型错误
//初始化
void initList(Seqlist & l){
l.length=0;
}
- 由于内存中的脏数据,假如不初始化,强行访问可能产生意想不到的效果.
我们强制打印一下,明显不是我们想要的结果
void test(){
Seqlist l;
initList(l);
//insertListByOrder(l,1,'a');
//printList(l);
for(int i=0;i<10;i++){
cout << "第" <<i <<"个" <<endl;
cout << "名字是:"<<l.data[i].name << endl;
cout << "序号是:" <<l.data[i].number << endl;
}
}
完善后的如下:
void initList(Seqlist & l){
for(int i=0;i<Maxsize;i++){
l.data[i].number=0;
l.data[i].name="";
}
l.length=0;
}
静态分配没必要销毁.
逻辑上长度为0即可,通过声明分配的空间,函数结束会自动回收.
增
注意健壮性,判断位置与表满,
尤其注意插入位置是1到表长+1
由于需要修改顺序表,注意采用&
bool insertList(Seqlist & l,int n,int number,string name){
//插入位置检查
if(n<1||n>l.length+1){
cout <<"插入位置不合法" <<endl;
return false;
}
//表满检查
if(l.length>=MAXSIZE){
cout <<"表已满" <<endl;
return false;
}
/* //从最后开始往后移一个位置
for(int i=l.length-1;i>=n-1;i--){
l.data[i+1]=l.data[i];
}
l.data[n-1]=e;
l.length++;
return true; */
for(int i=l.length;i>=n;i--){
l.data[i].name=l.data[i-1].name;
l.data[i].number=l.data[i-1].number;
}
l.data[n-1].name=name;
l.data[n-1].number=number;
l.length++;
return true;
}
调用代码,注意返回是bool,所以可以判断是否插入成功,printList()
在后面有解释
if(insertList(l,1,0,"Alice")){
cout <<"insert success" <<endl;
}
//insertList(l,1,0,"Alice");
insertList(l,2,1,"Baker");
insertList(l,3,2,"Chris");
printList(l);
删
注意有多个返回值,所以通过指针修改
删除位置的合法性判断,1到表长
bool deleteListByOrder(Seqlist & l,int n,int &number,string & name){
if(n<1||n>l.length){
cout <<"删除位置不合法" <<endl;
return false;
}
number=l.data[n-1].number;
name=l.data[n-1].name;
for(int i=n;i<l.length;i++){
l.data[i-1].name=l.data[i].name;
l.data[i-1].number=l.data[i].number;
}
l.length--;
return true;
}
调用
string name;
int number;
if(deleteListByOrder(l,2,number,name)){
cout <<"删除的学生序号是" <<number <<endl;
cout <<"删除的学生名字是" <<name <<endl;
}
printList(l);
查
// 按位序查找元素,由于返回序号,和名字,通过引用修改.
void findElementByOrder(Seqlist l,int n,int & number,string & name);
// 按内容---姓名查找位置,第一次出现,没有返回-1
int findElementByValue(Seqlist l,string name);
//按内容---结构体查找位置
int findStructByValue(Seqlist l,Student student);
- 注意判断结构体相同的方法
- 由于返回值有两个,用引用,或者指针返回。
- 判断结构体相同的方法在后面,注意不能用==
void findElementByOrder(Seqlist l,int n,int &number,string & name){
if(n<1||n>l.length){
cout <<"查找位置不合法" <<endl;
return;
}
number=l.data[n-1].number;
name=l.data[n-1].name;
}
int findElementByValue(Seqlist l,string name){
for(int i=0;i<l.length;i++){
if(l.data[i].name==name){
return i+1;
}
}
return -1;
}
int findStructByValue(Seqlist l,Student student){
for(int i=0;i<l.length;i++){
if(isSameStudent(l.data[i],student)){
return i+1;
}
}
return -1;
}
调用
int n=2;
string name2="";
int number2;
findElementByOrder(l,n,number2,name2);
cout << "第" <<n <<"个位置的序号是" << number2 <<endl;
cout << "第" <<n <<"个位置的名字是" << name2 <<endl;
cout <<"Alice的位序是" <<findElementByValue( l,"Alice") <<endl;
Student a;
a.number=0;
a.name="Alice";
if(findStructByValue(l,a)){
cout <<"位序是" <<findStructByValue(l,a) <<endl;
}
工具
//打印一下顺序表
void printList(Seqlist l);
//表长
int lengthOfList(Seqlist l);
//判断表空
bool isEmpty(Seqlist l);
//判断结构体相同
bool isSameStudent(Student a,Student b);
void printList(Seqlist l){
for (int i=0;i<l.length;i++){
cout <<"第" << i+1 <<"个学生序号是 " << l.data[i].number <<endl;
cout <<"第" << i+1 <<"个学生名字是 " << l.data[i].name <<endl;
}
}
int lengthOfList(Seqlist l){
return l.length ;
}
bool isEmpty(Seqlist l){
if(l.length!=0){
return false;
}
return true;
}
bool isSameStudent(Student a,Student b){
if(a.name==b.name&&a.number==b.number){
return true;
}
return false;
}
源程序如有必要,留言.
typedef与struct
struct Stu {
int data;
string name;
}Stu;
声明一个结构体
使用的时候,struct Stu a;
struct {
int data;
string name;
}Stu;
声明一个结构体变量
使用的时候,Stu.data=3;Stu.name="abc";
这两个的结果相同,都是将结构体变量重命名
使用的时候,Stu a;
typedef struct Stu {
int data;
string name;
}Stu;
typedef struct {
int data;
string name;
}Stu;
我们将那个结构体重命名,至于它的名字,我们不在乎了
但是在c++中,也可以不需要typedef
使用结构体的时候,不需要struct
struct Stu {
int data;
string name;
};
Stu a;
a.data=5;
a.name="all";
指针与引用
我们可以把引用理解成变量的别名。定义一个引用的时候,程序把该引用和它的初始值绑定在一起,而不是拷贝它。计算机必须在声明r的同时就要对它初始化,并且,r一经声明,就不可以再和其它对象绑定在一起了。
引用自知乎匿名用户
1.引用和指针,在内存中都是占用4个字节(32bits系统中)的存储空间。指针和引用存放的都是被引用对象的地址,引用必须在定义的同时进行初始化。
2.指针常量本身(以p为例)允许寻址,即&p返回指针常量(常变量)本身的地址,被引用对象用*p表示;引用变量本身(以r为例)不允许寻址,&r返回的是被引用对象的地址,而不是变量r的地址(r的地址由编译器掌握,程序员无法直接对它进行存取),被引用对象直接用r表示。
3.凡是使用了引用变量的代码,都可以转换成使用指针常量的对应形式的代码,只不过书写形式上要繁琐一些。反过来,由于对引用变量使用方式上的限制,使用指针常量能够实现的功能,却不一定能够用引用来实现。
4.一些其他不同:
引用使用时无需解引用(*),指针需要解引用;
“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
引用不能为空,指针可以为空;
指针和引用的自增(++)运算意义不一样;引用自增被引用对象的值,指针自增内存地址。
引用自知乎RainMan
- 引用只是c++语法糖,可以看作编译器自动完成取地址、解引用的常量指针
- 引用区别于指针的特性都是编译器约束完成的,一旦编译成汇编就喝指针一样
- 引用由编译器保证初始化,使用起来较为方便(如不用检查空指针等)
- 引用没有顶层const即
int & const
,因为引用本身就不可变,所以在加顶层const也没有意义; 但是可以有底层const即const int&
,这表示引用所引用的对象本身是常量
int b=999;
int& r=b;
//通过r访问变量
cout<<"引用的值"<<r<<endl;
int* p=&b;
cout<<"指针的值"<<*p<<endl;
int c=1010;
//r仍然指向b,但是现在b的值是1010
r=c;
cout<<"引用的值"<<r<<endl;
cout<<"修改后的b"<<b<<endl;
int d=2020;
p=&d;
cout<<"指针的值"<<*p<<endl;
int a;
int* p1 = NULL; //p1叫空指针,空指针指的是不指向任何实体的指针,其值为NULL就表示空指针
p1 = &a; //此时p1当然就不是空指针了
void* p2; //p2并非叫空指针,它只是未初始化,指向void的指针与是否空指针无关
p2 = NULL; //p2此时才叫空指针
p2 = &a; //p2不是空指针
类似地,空引用指的是没有对其它实体进行引用的引用,由于C++规定引用必须初始化,所以不存在空引用,而指针在这方面是自由的,当它被赋值NULL时,才成为空指针。
int * p1 = NULL; //p1是一个空指针
int & r = *p1; //虽然p1是一个空指针,但r并非空引用,r有引用,只不过r引用的东西是无效的,所以r是一个无效引用,不仅对r的使用属于未定义行为,而且*p1本身就属于未定义行为。
“不存在的实体”与“尚未引用其它实体的引用”不是一回事。
无论引用还是指针都存在两个概念,“引用本身/被引用的实体”和“指针本身/被指向的实体”。
空引用/空指针指的是指针或引用本身为空,而非被指向或被引用的实体为空。
引用或指针本身为空称为尚未引用/指向其它实体,被指向或被引用的实体为空称为不存在的实体。
引用自 https://bbs.csdn.net/topics/390538538?page=2