C++/C知识点:指针与链表

知识点:指针

指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。 

指针描述了数据在内存中的位置,标示了一个占据存储空间实体,在这一段空间起始位置的相对距离值。在 C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组函数等占据存储空间的实体。

计算机中,所有的数据都是存放在存储器中的,不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址。比如:int 类型占 4 个字节,char 类型占 1 个字节等。系统在内存中,为变量分配存储空间的首个字节单元的地址,称之为该变量的地址。地址用来标识每一个存储单元,方便用户对存储单元中的数据进行正确的访问。在高级语言中地址形象地称为指针。 

地址与指针

指针相对于一个内存单元来说,指的是单元的地址,该单元的内容里面存放的是数据。在 C 语言中,允许用指针变量来存放指针,因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

变量及其定义

指针变量是存放一个内存地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量。定义指针变量的一般形式为:类型说明符*变量名。 

类型说明符表示指针变量所指向变量的数据类型;*表示这是一个指针变量;变量名表示定义的指针变量名,其值是一个地址,例如:char*p1;表示 p1 是一个指针变量,它的值是某个字符变量的地址。 

变量与其指向的对象的关联

#include <stdio.h>  
//测试环境: Windows 10 Pro x86(32位)
//打印的变量(i或者p)的地址可能每次都不一样
int main(int argc,char** argv)
{
    int i = 0x12345678;         
    int* p = &i;              
 
    printf("0x%08x \n", i);      //打印结果: 0x12345678
    printf("0x%08x \n", &i);     //打印结果: 0x004ffed0
    printf("%d \n", sizeof(i));  //打印结果: 4
 
    printf("0x%08x \n", p);      //打印结果: 0x004ffed0
    printf("0x%08x \n", &p);     //打印结果: 0x004ffecc
    printf("%d \n", sizeof(p));  //打印结果: 4
  
    // 指针变量p指向的对象是变量i

    // 1.指向的对象变量i的地址是&i(0x004ffed0)
    // 2.指向的对象变量i的类型是int
    // 3.指向的对象变量i的内容是0x12345678
    // 4.指针变量p本身的地址是&p(0x004ffecc)
    // 5.指针变量p本身的类型是int*
    // 6.指针变量p本身的内容是&i(0x004ffed0)
  
    return 0;
}

从示例代码打印结果可以看出,指针变量p存储的内容就是其所指向的变量i的地址。指针变量p本身也是一个变量,也需要占据存储空间(32位系统下占4个字节),同样也有地址。

运算符

  • & 取址运算符

功能:返回变量的内存地址(升一级) 

  • * 取值运算符

功能:访问指针指向的变量(降一级) 

指针运算

  • 指针±数值:p±n等价于p±n*c 。

  • 指针-指针:结果为两指针所指向地址间数据的个数。

  • 指针 比较 指针:产生的结果为 0(假)和 1(真)。

 知识点:链表

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 与其直接后继数据元素 之间的逻辑关系,对数据元素 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。由这两部分信息组成一个"结点"(如概述旁的图所示),表示线性表中一个数据元素。线性表的链式存储表示,有一个缺点就是要找一个数,必须要从头开始找起,十分麻烦。

根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。

对于非线性的链表,可以参见相关的其他数据结构,例如树、图。另外有一种基于多个线性链表的数据结构:跳表,插入、删除和查找等基本操作的速度可以达到O(平衡二叉树一样。

其中存储数据元素信息的域称作数据域(设域名为data),存储直接后继存储位置的域称为指针域(设域名为next)。指针域中存储的信息又称做指针或链。

由分别表示,,…,的N 个结点依次相链构成的链表,称为线性表的链式存储表示,由于此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表。

#include<stdio.h>
#include<stdlib.h>
#include<iostream.h>

usingnamespacestd;

structNode
{
intdata;//数据域
structNode*next;//指针域
};

/*
Create
*函数功能:创建链表.
*输入:各节点的data
*返回值:指针head
*/
Node*Create()
{
intn=0;
Node*head,*p1,*p2;
p1=p2=newNode;
cin>>p1->data;
head=NULL;
while(p1->data!=0)
{
if(n==0)
{
head=p1;
}
else
p2->next=p1;
p2=p1;
p1=newNode;
cin>>p1->data;
n++;
}
p2->next=NULL;
returnhead;
}

/*
insert
*函数功能:在链表中插入元素.
*输入:head链表头指针,p新元素插入位置,x新元素中的数据域内容
*返回值:无
*/
voidinsert(Node*head,intp,intx)
{
Node*tmp=head;//for循环是为了防止插入位置超出了链表长度
for(inti=0;i<p;i++)
{
if(tmp==NULL)
return;
if(i<p-1)
tmp=tmp->next;
}
Node*tmp2=newNode;
tmp2->data=x;
tmp2->next=tmp->next;
tmp->next=tmp2;
}

/*
del
*函数功能:删除链表中的元素
*输入:head链表头指针,p被删除元素位置
*返回值:被删除元素中的数据域.如果删除失败返回-1
*/
intdel(Node*head,intp)
{
Node*tmp=head;
for(inti=0;i<p;i++)
{
if(tmp==NULL)
return-1;
if(i<p-1)
tmp=tmp->next;
}
intret=tmp->next->data;
tmp->next=tmp->next->next;
returnret;
}

voidprint(Node*head)
{
for(Node*tmp=head;tmp!=NULL;tmp=tmp->next)
printf("%d",tmp->data);
printf("\n");
}

intmain()
{
Node*head;
head=newNode;
head->data=-1;
head->next=NULL;
return0;
}
例子
#include<iostream>
#defineNULL0
structstudent
{
longnum;
structstudent*next;
};
intmain()
{
inti,n;
student*p=(structstudent*)malloc(sizeof(structstudent));
student*q=p;
printf("输入几个值");
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&(q->num));
q->next=(structstudent*)malloc(sizeof(structstudent));
q=q->next;
}
printf("值第几个");
intrank;
scanf("%d%d",&(q->num),&rank);
student*w=p;
for(i=1;i<rank-1;i++)
{
w=w->next;
}
q->next=w->next;
w->next=q;
for(i=1;i<=n+1;i++)
{
printf("%d",p->num);
p=p->next;
}
return0;
}//指针后移麻烦链表形式循环链表

循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。

双向链表

双向链表其实是单链表的改进。

当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。

经典例题

Problem A: 猴子选大王

Description

有n只猴子围成一圈,编号为1-n,打算从中选出一个大王。经过协商,决定选大王的规则如下:从第一只猴子开始循环报数,数到k的猴子出圈,然后从下一只猴子继续报数出圈......最后剩下来的那只猴子就是大王。

Input

一行两下整数n和k,用一空格隔开,2<=n<=1000,2<=k<=10^9

Output

n只猴子依次出圈的编号,中间用一空格隔开。

Problem B: 夏令营旗手

Description

我们将举办一个夏令营,其中一个有趣的活动:挑选本次夏令营的旗手。
规则如下:将N个人排成一排,编号为1-N。从第一个人开始正向报数,报到M的人出列,再从下一个人开始继续报数,报到M的人出列。按某个方向报数到尾部时,再反方向继续报数。如此进行下去,直到剩下一人为止,这个人就是本次夏令营的旗手。
小明非常想成为旗手,请你编一个程序帮助他实现愿望,并输出小明的编号。

Input

一行两个整数N和M,2<=N,M<=300,两个数之间用一个空格隔开。

Output

一行一个整数,表明小明在队列中的编号。

有用的视频

【C语言】发明链表的人真是太有才啦_哔哩哔哩_bilibili

【图解数据结构】【链表1】先来看看结构体和指针吧,这都搞不明白,学个p链表_哔哩哔哩_bilibili

链表 数据结构与算法,完整代码动画版,附在线数据结构交互式工具_哔哩哔哩_bilibili

参考文献

指针(编程语言中的一个对象)_百度百科

链表_百度百科

  • 36
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值