9.指针与链表

一、指针

1.基本介绍

在程序中,我们的数据都有其存储的地址。在程序每次的实际运行过程中,变量在物理内存中的存储位置不尽相同。不过,我们仍能够在编程时,通过一定的语句,来取得数据在内存中的地址。地址也是数据。存放地址所用的变量类型有一个特殊的名字,叫做指针

指针的大小在不同环境下有差异。在 32 32 32 位机上,地址用 32 32 32 位二进制整数表示,因此一个指针的大小为 4 4 4 字节。而 64 64 64 位机上,地址用 64 64 64 位二进制整数表示,因此一个指针的大小就变成了 8 8 8 字节。

针对不同类型的数据,指针也有不同的类型。比如,int 类型的指针,其中存储的地址对应一块大小为 32 32 32 位的空间的起始地址;有 char 类型的指针,其中存储的地址对应一块 8 8 8 位的空间的起始地址。

事实上,用户也可以声明指向指针的指针。多重指针会产生很多复杂的关系,所以在算法竞赛中使用得很少,这里仅作了解。

2.声明与使用

C/C++ 中,指针的类型为类型名后加上一个星号 *。比如,int 类型的指针的类型名即为 int*

我们可以使用 & 符号取得一个变量的地址。

要想访问指针地址所对应的空间(又称指针所指向的空间),需要对指针变量进行解引用,使用 * 符号。

int a = 123;
int* p = &a;
*p = 321;

对结构体变量也是类似。如果要访问指针指向的结构中的成员,有两种写法,但实际应用中通常使用前一种写法。

  • 使用箭头运算符 ->
  • 先对对指针进行解引用,再使用 . 成员关系运算符。
struct node
{int a;int b;
};
int main()
{node x = {1, 2}, y = {34};node* p = &x;*p = y;p->a = 5;(*p).b = 6;return 0;
}

空指针通常用 NULL 来表示,或者是 nullptr,二者是等价的。

3.指针的偏移

指针变量也可以进行加减操作(因为其实质是地址)。对于 int 型指针,每加 1 1 1,其指向的地址偏移 4 4 4 字节。同理,对于 char 型指针,每次递增,其指向的地址偏移 1 1 1 字节。

数组是存储在一块连续的空间中的,而数组名就是数组第一个元素的地址。当通过指针访问数组中的元素时,往往需要用到指针的偏移,换句话说,即通过一个基地址(数组起始的地址)加上偏移量来访问。

int main()
{int a[3] = {1, 2, 3};int* p = a;  // p 指向 a[0]*p = 4;      // {4, 2, 3}p = p + 1;   // p 指向 a[1]*p = 5;      // a: [4, 5, 3]
}

我们常用 [] 运算符来访问数组中某一指定偏移量处的元素。比如 a[3] 或者 p[4]。这种写法和对指针进行运算后再引用是等价的,即 p[4]*(p + 4) 是等价的。

4.指针在函数中的使用

默认情况下,函数仅能通过返回值,将结果返回到调用处。但是,如果某个函数希望修改外部数据,或某个结构体较大,则可以通过向其传入外部数据的地址,便得以在其中访问甚至修改外部数据。这就是函数调用的地址传递

下面的 my_swap 方法,通过接收两个 int 型的指针,在函数中使用中间变量,完成对两个 int 型变量值的交换。

void my_swap(int *a, int *b)
{int tmp;tmp = *a;*a = *b;*b = tmp;
}
int main()
{int a = 6, b = 10;my_swap(&a, &b);//a 变为 10,b 变为 6return 0;
}

C++ 中引入了引用的概念,相对于指针来说,更易用,也更安全。

二、动态分配内存

1.基本使用

程序编写时往往会涉及到动态内存分配,即,程序会在运行时,向操作系统动态地申请或归还存放数据所需的内存。当程序通过调用操作系统接口申请内存时,操作系统将返回程序所申请空间的地址。要使用这块空间,我们需要将这块空间的地址存储在指针变量中。

在 C++ 中,我们使用 new 运算符来获取一块内存,使用 delete 运算符释放某指针所指向的空间。

int* p = new int(1234);
/*
...
*/
delete p;

上面的语句使用 new 运算符向操作系统申请了一块 int 大小的空间,将其中的值初始化为 1234,并声明了一个 int 型的指针 p 指向这块空间。

需要注意,当使用 new 申请的内存不再使用时,需要使用 delete 释放这块空间。不能对一块内存释放两次或以上。而对空指针 NULLnullptr 使用 delete 操作是合法的。

2.动态创建数组

也可以使用 new[] 运算符创建数组,这时 new[] 运算符会返回数组的首地址,也就是数组第一个元素的地址,我们可以用对应类型的指针存储这个地址。释放时,则需要使用 delete[] 运算符。

size_t element_cnt = 5;
int *p = new int[element_cnt];
delete[] p;

数组中元素的存储是连续的,即 p + 1 指向的是 p 的后继元素。

3.二维数组

实际使用中,二维数组的大小可能不是固定的,需要动态内存分配。动态的二维数组有三种生成方式。

(1)一维数组模拟

常见的方式是声明一个长度为 N × M N × M N×M一维数组,并通过下标 r * M + c 访问二维数组中下标为 (r, c) 的元素。

int* a = new int[N * M]; 

这种方法可以保证二维数组是连续的

(2)数组的数组

此外,亦可以根据数组的数组这一概念来进行内存的获取与使用。对于一个存放的若干数组的数组,实际上为一个存放的若干数组的首地址的数组,也就是一个存放若干指针变量的数组。

我们需要一个变量来存放这个数组的数组的首地址。这个变量便是一个指向指针的指针,有时也称作二重指针,如:

int** a = new int* [5];

接着,我们需要为每一个数组申请空间:

for (int i = 0; i < 5; i++)a[i] = new int[5];

至此,我们便完成了内存的获取。而对于这样获得的内存的释放,则需要进行一个逆向的操作:即先释放每一个数组,再释放存储这些数组首地址的数组,如:

for (int i = 0; i < 5; i++)delete[] a[i];
delete[] a;

需要注意,这样获得的二维数组,不能保证其空间是连续的。

(3)指向数组的指针

还有一种方式,需要使用到指向数组的指针

int main()
{int(*a)[5] = new int[5][5];int* p = a[2];a[2][1] = 1;delete[] a;return 0;
}

这种方式获得到的也是连续的内存,但是可以直接使用 a[n] 的形式获得到数组的第 n + 1 n+1 n+1 行的首地址,因此,使用 a[r][c] 的形式即可访问到下标为 (r, c) 的元素。

由于指向数组的指针也是一种确定的数据类型,因此除数组的第一维外,其他维度的长度均须为一个能在编译器确定的常量。不然,编译器将无法翻译如 a[n] 这样的表达式(a 为指向数组的指针)。

三、链表

1.基本介绍

链表是一种用于存储数据的数据结构,通过如链条一般的指针来连接元素。它的特点是插入与删除数据十分方便,但寻找与读取数据的表现欠佳。

链表和数组都可用于存储数据。与链表不同,数组将所有元素按次序依次存储。不同的存储结构令它们有了不同的优势:

  • 链表删除和插入数据的时间复杂度是 O ( 1 ) O(1) O(1)。寻找、读取数据的时间复杂度是 O ( n ) O(n) O(n)

  • 链表删除和插入数据的时间复杂度是 O ( n ) O(n) O(n)。寻找、读取数据的时间复杂度是 O ( 1 ) O(1) O(1)

2.链表的结构

单向链表中,每一个结点包含数据域和指针域,其中数据域用于存放数据,指针域用来连接当前结点和下一节点。单向链表需要一个头结点作为起点。

双向链表比单向链表多一个指向上一个结点的指针域。双向链表需要一个头结点和一个尾结点进行标识。

3.链表的运用

下面以双向链表为例讲解链表的构造和操作方式。

(1)定义

//指针
struct node
{ll value;node *prev, *next;
};
node *head, *tail;
void init()
{head = new node();tail = new node();head -> next = tail;tail -> prev = head;
}
//数组模拟
struct node
{ll val;node *prev, *next;
}New[maxn];
ll head, tail, tot;
int init()
{tot = 2;head = 1;tail = 2;head.next = tail;tail.prev = head;
}

(2)插入

//指针
void insert(node *p, ll val)
{node q = new node();q -> value = val;p -> next -> prev = q;q -> next = p -> next;p -> next = q;q -> prev = p;
}
//数组
void insert(ll p, ll val)
{ll q = ++tot;New[q].value = val;New[New[p].next].prev = q;New[q].next = New[p].next;New[p].next = q;New[q].prev = p;
}

(3)删除

//指针
void remove(node *p)
{p->prev->next = p -> next;p->next->prev = p -> prev;delete p;
}
//数组
void remove(ll p)
{New[New[p].prev].next = New[p].next;New[New[p].next].prev = New[p].prev;
}

(4)回收

//指针
void recycle()
{while(head != tail){head = head -> next;delete head -> prev;}delete tail;
}
//数组
void recycle()
{memset(New,0,sizeof(New));head = tail = tot = 0;
}

四、作业

P1996 约瑟夫问题

B3631 单向链表

P1160 队列安排

POJ 3784

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/56774.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode 单词规律

即判断给定的模式字符串&#xff08;pattern&#xff09;和单词字符串&#xff08;s&#xff09;是否遵循相同的对应规则。具体来说&#xff0c;就是要判断 pattern 中的字符与 s 中的单词是否存在一一对应的关系&#xff0c;即双射&#xff08;bijection&#xff09;。 算法思…

【解决办法】git clone报错unable to access ‘xxx‘: SSL certificate problem

git clone 是 Git 版本控制系统中的一个基本命令&#xff0c;用于从远程仓库复制一个完整的版本库到本地。这个命令不仅复制远程仓库中的所有文件&#xff0c;还复制仓库的历史记录&#xff0c;使得你可以在本地进行版本控制操作&#xff0c;如提交&#xff08;commit&#xff…

Electron+Vue实现两种方式的截屏功能

本次介绍的截屏功能一共有两种分别是在electron环境中与非electron环境中 非electron环境 这个环境下会有一些限制&#xff1a; 1.只能截浏览器中的画面 2.如果里面有iframe或者base64的图片会加载不出来&#xff08;这个会有解决办法&#xff09; yarn add -D js-web-scree…

Java爬虫:获取商品评论数据的高效工具

在电子商务的激烈竞争中&#xff0c;商品评论作为消费者购买决策的重要参考&#xff0c;对于商家来说具有极高的价值。它不仅能够帮助商家了解消费者的需求和反馈&#xff0c;还能作为改进产品和服务的依据。Java爬虫技术&#xff0c;以其稳健性和高效性&#xff0c;成为了获取…

基于Spring Cloud的电商系统设计与实现——用户与商品模块的研究(上)

操作系统&#xff1a;Windows Java开发包&#xff1a;JDK1.8 项目管理工具&#xff1a;Maven3.6.0 项目开发工具&#xff1a;IntelliJIDEA 数据库&#xff1a;MySQL Spring Cloud版本&#xff1a;Finchley.SR2 Spring Boot版本&#xff1a;2.0.6.RELEASE 目录 用户模块—user-…

YOLO系列入门:1、YOLO V11环境搭建

YOLO了解 yolo检测原理 yolo是目标检测模型&#xff0c;目标检测包含物体分类、位置预测两个内容。目前yolo的开发公司官网为&#xff1a;https://docs.ultralytics.com/zh截止到目前2024年10月&#xff0c;最新的是yolo11。关于YOLO的介绍可以参考这篇文章&#xff1a;https…

[Javase]封装、继承、多态与异常处理

文章目录 一、前言二、封装1、封装的思想2、封装代码层面的体现 三、继承1、继承的概念和好处2、继承代码层面的体现 四、多态1、多态的概念2、多态的好处和三要素2、多态代码层面的体现 五、异常处理1、try-catch-finally结构详解2、throw\throws 一、前言 本文章适合有一定面…

10.15.2024刷华为OD C题型(二)

10.15.2024刷华为OD C题型&#xff08;二&#xff09; 密码输入检测智能成绩表 如果是目标院校150分能过&#xff0c;而且这道题是两百分的话我就阿弥陀佛了。 这类简单类型的字符串处理题目一看就有思路&#xff0c;起码能做&#xff0c;遇到那种稍微加点数学的&#xff0c;感…

【从零开始的LeetCode-算法】3099. 哈沙德数

如果一个整数能够被其各个数位上的数字之和整除&#xff0c;则称之为 哈沙德数&#xff08;Harshad number&#xff09;。给你一个整数 x 。如果 x 是 哈沙德数 &#xff0c;则返回 x 各个数位上的数字之和&#xff0c;否则&#xff0c;返回 -1 。 示例 1&#xff1a; 输入&am…

MySQL增删改进阶

目录 1.数据库约束 1.1约束类型 1.2 not null约束 1.3 unique&#xff1a;唯一约束 1.4 default&#xff1a;默认约束 1.5 primary key&#xff1a;主键约束 1.6 foreign key:外键约束 1.7 check约束&#xff08;了解&#xff09; 2.表的设计 3.新增&#xff08;进阶&…

刷题训练之多源 BFS

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握多源 BFS算法。 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷…

C++(stack和queue)

1. stack的介绍、使用和实现 1.1 stack的介绍 stl里的stack其实和数据结构内的stack和前面数据结构的栈不能说百分百一样&#xff0c;但也有百分之90是一样的&#xff0c;他们的特性都是LIFO&#xff08;last in first out&#xff09;先进后出的原则&#xff0c;前面有类似的…

VideoCLIP-XL:推进视频CLIP模型对长描述的理解

摘要 对比语言-图像预训练&#xff08;CLIP&#xff09;已被广泛研究并应用于众多领域。然而&#xff0c;预训练过程中对简短摘要文本的重视阻碍了CLIP理解长描述的能力。在视频方面&#xff0c;这个问题尤为严重&#xff0c;因为视频通常包含大量详细内容。在本文中&#xff…

如何看一个flutter项目的具体flutter版本

查看pubspec.lock文件 这个项目实际运行的就是 flutter 3.16.6 版本的

Leetcode 1489. 找到最小生成树里的关键边和伪关键边

1.题目基本信息 1.1.题目描述 给你一个 n 个点的带权无向连通图&#xff0c;节点编号为 0 到 n-1 &#xff0c;同时还有一个数组 edges &#xff0c;其中 edges[i] [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一…

MFC扩展库BCGControlBar Pro v35.1新版亮点:改进网格控件性能

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v35.1已全新发布了&#xff0c;这个版本改进网格控件的性能、增强工具栏编辑器功能等。 …

【puppeteer】wvp-puppeteer制作 过程

目录 最后的结论 制作windows&ubuntu的docker 重启桌面上的docker 命令重启 通过 Docker Desktop 图形界面重启 制作centos docker 测试 参考文档 最后的结论 ubuntu && windows 使用 dualvenregistry:5000/wvp-puppeteer:1.0 centos7 使用&#xff1a;…

通过OpenCV实现 Lucas-Kanade 算法

目录 简介 Lucas-Kanade 光流算法 实现步骤 1. 导入所需库 2. 视频捕捉与初始化 3. 设置特征点参数 4. 创建掩模 5. 光流估计循环 6. 释放资源 结论 简介 在计算机视觉领域&#xff0c;光流估计是一种追踪物体运动的技术。它通过比较连续帧之间的像素强度变化来估计图…

第6篇:无线与移动网络

目录 引言 6.1 无线网络的基础概念 6.2 无线局域网&#xff08;WLAN&#xff09;与IEEE 802.11 6.3 蓝牙与无线个域网&#xff08;WPAN&#xff09; 6.4 无线城域网&#xff08;WMAN&#xff09;与WiMax 6.5 ZigBee与智能家居 6.6 移动蜂窝网络&#xff08;3G/4G/5G&…

【Linux】总线-设备-驱动模型

背景 前面&#xff0c;我们介绍了写驱动代码的一些常规步骤&#xff0c;并且也写了最基本的驱动代码&#xff0c;但是那些代码存在着问题&#xff0c;我们将硬件的信息都写进了驱动里了&#xff0c;如果我们在杂项设备驱动中控制led&#xff0c;那么会在硬件操作接口中包含硬件…