结构体和链表手搓

192 阅读6分钟

结构体

什么是结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

结构体怎么声明

struct 结构体名字
{
    成员变量
};
//这个;不能少写

比如 声明一个学生

struct student
{
    char name[20];
    int age;
    char sex[5];
    char id[20];
}; //分号不能少

结构体变量定义和初始化

以下是各种定义和初始化的方式

struct Point
{
    int x;
    int y;
}p1;                //声明类型的同时定义变量p1

struct Point p2;    //定义结构体变量p2

                    //初始化:定义变量的同时赋初值。
struct Point p3= {1, 2};

struct Stu          //类型声明
{
    char name[15];       //名字
    int age;             //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
    int data;
    struct Point p;
    struct Node*next;
}n1={10, {4,5}, NULL};  //结构体嵌套初始化

struct Node n2 = {20, {5,6}, NULL};//结构体嵌套初始化

typedef和define

define就是替换 不受作用域限制 全都会替换 typedef是起别名 受作用域限制

简化代码

使用typedef可以简化我们的代码

这是原本的代码

struct student
{
    char name[20];
    int age;
    char sex[5];
    char id[20];
}; //分号不能少

struct student s1;
//定义结构体s1
struct student *ss1 = &s1;
//定义结构体指针指向s1

使用typedef后,可以简化为

typedef struct student {
    char name[20];
    int age;
    char sex[5];
    char id[20];
} stu, *ss;

stu s1 = {"A", 2, "man", "2"};  	// 使用别名 stu 声明变量
ss p = &s1;  						// 使用别名 ss 声明指针,并指向 s1

访问成员变量

用.和->来访问 通过点运算符(.)来访问结构体的成员变量。对于指向结构体的指针,则使用箭头运算符(->)

#include <stdio.h>

typedef struct Point {
    int x;
    int y;
}po, *ppo;

int main() {
    po p;
    p.x = 10;  // 通过点运算符访问成员
    p.y = 20;

    printf("Point: (%d, %d)\n", p.x, p.y);

    ppo ptr = &p;
    printf("Point via pointer: (%d, %d)\n", ptr->x, ptr->y);  // 通过箭头运算符访问

    return 0;
}

动态内存开辟

malloc函数,用于在堆(heap)上动态分配指定大小的内存块。其功能是向系统请求一定大小的内存,成功分配后返回该内存块的首地址。

void* malloc(size_t size);

对于返回值:需要将它强制转换成具体的类型,如 int* 或 Stu* 。(转换成你需要的类型

int *ptr = (int *)malloc(sizeof(int));  // 分配一个整数大小的内存
if (ptr == NULL) {  // 检查内存是否分配成功
    printf("内存分配失败\n");
    return 1;
}
free(ptr);

那对于上面的结构体Student呢? 我们可以这么写

typedef struct student {
    char name[20];
    int age;
    char sex[5];
    char id[20];
} stu, *ss;

// 使用 malloc 分配一个 struct student 的空间
ss sp = (ss)malloc(sizeof(stu));
free(sp);

补充:堆? 栈?

相信同学们在学习地址的时候都接触过这两个概念,那他们是什么,一种数据结构吗,他们存在在哪里,有什么优点,为啥变量是在栈上创建,开辟空间是在堆上?

  • 栈是一种后进先出(LIFO)的数据结构,用来存储局部变量、函数参数和函数调用的返回地址。
  • 自动管理:栈上的内存由编译器自动分配和释放,当函数执行完毕,所有在栈上分配的变量会自动销毁。
  • 访问速度快:由于栈内存按顺序存储,并通过指针快速访问,栈的操作比堆快。
  • 内存限制较小:栈的大小有限,不能用于存储大块数据。

  • 堆是一个内存池,用于动态分配内存。这些内存块可以在程序运行过程中按需分配,并且需要程序员手动释放
  • 灵活但慢:堆适用于存储动态大小的数据(如链表、树),但由于手动管理,操作速度比栈慢
  • 内存泄漏风险:如果分配的内存没有正确释放, 会造成内存泄漏占用系统资源。

(对于什么是内存池,感兴趣的可以去了解了解

线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

在这里插入图片描述

基本结构

// 顺序表的动态存储
typedef struct SeqList
{
    int* array;     // 指向动态开辟的数组
    size_t size ;   // 有效数据个数
    size_t capicity ;// 容量空间的大小
}SeqList;

顺序表的不足

  1. 中间/头部的插入删除,时间复杂度为O(N),全都要移动

  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

那可以不可以尽量避免这些问题

链表

什么是链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 请添加图片描述节点之间在物理结构上,可能连续,可能不连续 (一般上面的一个完整的方块,就是一个节点(Node)

分类(了解即可 没啥大用

  1. 单向/双向

请添加图片描述

  1. 是否有头节点

请添加图片描述

  1. 是否循环(即是否头尾相连 请添加图片描述 ///////////////////////////////////分界线/////////////////////////////////////////

但一般我们只用这两种: 无头单向不循环链表 一般作为其他数据结构的子结构

带头双向循环链表用起来更方便 请添加图片描述

基本结构

我们有两种搓的方式:用数组或者动态开辟内存的方式

动态开辟内存

完整代码

数组手搓

因为动态开辟对于做题可能比较麻烦 所以我们来介绍用多个数组来模拟实现链表的方式

基本结构的区别就是 将数据和指针分别存在两个数组中,我们用相同的下标来表示同一个节点

一定要画图 一定要画图 尤其是针对插入和删除 画图帮助理解

完整代码