我所知道的数组&我所知道的链表(上)

引言:

       近些日子准备用空余时间对基础的数据结构与常用的算法做一个整理,目前规划了一些章节,后续章节会陆续补充。

        当然,由于个人水平有限,所整理内容难免会出现谬误,欢迎各位大神不吝指正。

一、数组篇

1.1 什么是数组?

       数组,可以理解成定义在计算机内存中具有固定长度且数据类型相同的一组数据。而且数组在计算机内存中,有一个非常重要的物理特性,数组中相邻的数据元素在内存中也是物理相邻的。为了验证数组的这些特性,编写了如下代码输出数组各个元素在内存中分配的地址:

int Myarray[5] = {1,2,3,4,5};//数组的常见定义形式(不要纠结于教科书中过于严苛的定义形式)
	cout << &Myarray[0] << '\n' << &Myarray[1] << '\n' << &Myarray[2] << '\n' << &Myarray[3] << '\n' << &Myarray[4] <<'\n';

        使用VS编译器运行,结果如下(这个水印怎么去掉呢?):

        图片中则显示了程序运行的结果。从上到下分别对应这Myarray数组中的五个元素在内存中分配的地址。而两两相邻元素的地址相差4,刚好是int类型的变量所占用的内存空间(int类型占用空间为32位,4个字节)。

        由此,可以推断出数组在内存中的存储形式为(请原谅我的灵魂画笔以及扭曲的字体):

          第一个元素的首地址0x0055FDD0 ,第二个元素地址为0x0055FDD4,,元素地址依次递增,到第五个元素地址为0x0055FDE0。由此证明了数组的物理存储特性。因为数组的这一特性,数组元素修改起来非常容易,可以直接修改数组某一个元素的数值(注意:这一点和链表有明显差异)。当然,数组元素的赋值也因此变得非常简单,赋值也可以看成是一种修改数组元素数值的操作。

1.2数组的使用

       在C语言环境中,数组需要自己定义数据类型、数组大小以及数组中的内容。另外建议数组中的元素在定义时候所有元素初始化为0。C语言中有一点需要特别注意,数组中第一个元素为数组索引值为0的元素,而非索引值为1的元素。这一点在按索引值赋值时候需要特别注意。即给数组中第一个元素赋值或者取数组第一个元素,需要将其索引值写为0,类似a[0]这种。由此,数组索引值大小是一定小于数组大小的。当数组大小为N时,数组索引值最大为N-1,不存在数组a[N]这个元素,这样会使数组操作越界。

       在C++语言中,除了可以正常按照C语言中数组的使用方法来操作之外,C++还额外提供了非常接近数组结构体的vector容器给用户使用。vector可以理解为一种“增强版”数组,个人认为,增强的最主要的地方是它可以动态扩容(普通数组一旦定义完成之后,数组大小是不能再改动的)。关于vector容器的使用,可以参见一些专门介绍C++容器的文章。

二、链表篇

2.1什么是链表

       链表是一种有别于数组的数据结构。它和数组的区别在于,链表中的元素在计算机内存中的存储位置在物理上是不相邻的。链表中元素之间的联系是:链表中上一个元素会存储下一个元素在内存中的地址,根据地址去索引下一个元素。链表中的元素通常被称为链表的结点。

        而之所以将链表和数组安排在一起,是因为链表克服了数组一个"痛点"(有互联网味了么),即数组不能动态拓展或者缩小。链表则可以根据要存储的数据量随意拓展、缩小。链表结构通常定义为如下形式(先从单链表搞起来):

typedef struct LinkNode    //创建一个结构体,命名为LinkNode
{                          //结构体的创建方法有很多种,但是都要包含链表数据以及下一个元素地址
	int val;              //关键元素1:链表要存储的数据类型
	LinkNode *next;       //关键元素2:下一个链表元素的地址,该地址可以为空,代表该元素为链表末元素
};

       按照如上代码可以定义一个简单的链表结构体,可以看出,该链表包含数据类型为整形的数据元素,以及linknode同类型链表下一个结点地址值。该值会存储在next变量中。可以用如下代码证明链表的这一特性。在定义结构体时,采用了上面结构体的定义方式。

LinkNode *testNode1, *testNode2;//声明两个链表结点,分别放在链表首位以及第二位
	testNode1 = new LinkNode;//C++中需要使用new来为创建的链表结点申请内存空间
	testNode2 = new LinkNode;
	testNode1->val = 10;
	testNode1->next = testNode2;
	testNode2->val = 20;
	testNode2->next = NULL;
	cout << "链表元素依次为:" << testNode1->val << '\n' << testNode2->val << '\n';//依次打印输出链表值
	cout << "链表第二个结点地址为:" << &(testNode1->next) << '\n';                
	cout << "链表第二个结点地址所存储的数值" << *(&(testNode1->next)->val);

      运行结果为:

 

 为了更加清晰的了解内存中发生的事情,添加断点,将链表中的结点添加到监视器中:

     从显示结果以及添加断点结果,都可以看出,链表结点之间是通过地址逻辑,连接在一起。而且每一块使用new申请的内存,都被初始化为了LinkNode结构体类型。在示例代码中,使用了new,在编写正式程序时,new申请的内存空间在不使用之后,应该及时使用delete释放掉。根据以上分析,用一个三个结点的链表举例,可以判断出链表在内存中的存储结构应该是如下图所示:

         为了不丢失链表,一般都是会存储链表的头结点。即初始地址存储的链表结点。链表的所有操作都可以基于该结点完成。

         在介绍完单链表的基本结构之后,会继续介绍链表的其他常见形式:双向链表、循环链表等内容,此外还会介绍链表常用的操作:链表建立、链表插入、链表删除、链表反转等等。

        以上即为第一部分所有内容,欢迎大家批评讨论。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值