C语言的指针链表的原理和各类操作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言的指针链表的原理和各类操作相关的知识,希望对你有一定的参考价值。

第三博客,重点


心得体会:

堂上要讲授许多关于c语言的语法规则,听起来十分枯燥无味,也不容易记住,死记硬背是不可取的。然而要使用c语言这个工具解决实际问题,又必须掌握它。通过多次上机练习,对于语法知识有了感性的认识,加深对它的理解,在理解的基础上就会自然而然地掌握c语言的语法规定。对于一些内容自己认为在课堂上听懂了,但上机实践中会发现原来理解的偏差,这是由于大部分学生是初次接触程序设计,缺乏程序设计的实践所致。学习c语言不能停留在学习它的语法规则,而是利用学到的知识编写c语言程序,解决实际问题。即把c语言作为工具,描述解决实际问题的步骤,由计算机帮助我们解题。只有通过上机才能检验自己是否掌握c语言、自己编写的程序是否能够正确地解题。

 

 

一、指针

 1、运用指针

   什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量。从指针指向的内存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如intlongdouble。指针也可以是void类型、NULL指针和未初始化指针。

   根据出现的位置不同,操作符 * 既可以用来声明一个指针变量,也可以用作指针的取值。当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值。&是地址操作符,用来引用一个内存地址。通过在变量名字前使用&操作符,我们可以得到该变量的内存地址。

例如:

#include<stdio.h>

int main()

{

   int*ptr;   // 声明一个int指针

   int val =1;  // 声明一个int

   ptr =&val;  // 为指针分配一个int值的引用

   int deref =*ptr;  // 对指针进行取值,打印存储在指针地址中的内容

  printf("deref地址=%ld,=%d\n",ptr, deref);

   第2行,我们通过*操作符声明了一个int指针。接着我们声明了一个int变量并赋值为1。然后我们用int变量的地址初始化我们的int指针。接下来对int指针取值,用变量的内存地址初始化int指针。最终,我们打印输出变量值,内容为1

   第6行的&val是一个引用。在val变量声明并初始化内存之后,通过在变量名之前使用地址操作符&我们可以直接引用变量的内存地址。

   第8行,我们再一次使用*操作符来对该指针取值,可直接获得指针指向的内存地址中的数据。由于指针声明的类型是int,所以取到的值是指针指向的内存地址存储的int值。

   这里可以把指针、引用和值的关系类比为信封、邮箱地址和房子。一个指针就好像是一个信封,我们可以在上面填写邮寄地址。一个引用(地址)就像是一个邮件地址,它是实际的地址。取值就像是地址对应的房子。我们可以把信封上的地址擦掉,写上另外一个我们想要的地址,但这个行为对房子没有任何影响。

  2、指针和数组

    C语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象。与之相反,指针用来存储单个内存地址。数组和指针不是同一种结构因此不可以互相转换。而数组变量指向了数组的第一个元素的内存地址。

例如:

#include<stdio.h>

int main()

{

         int myarray[4] = {1,2,3,0};

    int *ptr = myarray;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

}

总结:

指向指针的指针,可以这样理解,首先指向指针的指针可以把他看成一种特殊的变量,既然是变量就可以存储不同的元素,比如整形变量int aa可以存储2,3,4这种普通的整型数据,只要将值付给a就行了,但指向指针的指针所存的元素比较特殊,存放的元素一般是存放地址的指针变量,比如我有三个指针变量,int *p1,*p2,*p3,那么我可以定义一个特殊的变量 int **p,我可以将p1的地址付给p,也可以将p2的地址付给p.比如p=&p1,(类似于int a,int *t,t=&a),那么p就代表了他所指向的变量p1或者p2的地址,而p所指向的变量是一个指针变量,*p代表着这个指针变量里面的值(注意值实际上是一个地址),**p代表着它所指向的指针变量的内容(地址)所指向的存储单元的内容(数值)。

二、链表

   链表是一种数据结构 ,其最大的的好处就是能够为数据分配动态内存,就不用像一开始那样先为系统分配一个都不知道够不够用的空间来存贮学生的信息。

   链表,首先可以细分为一小块一小块的结构体变量,这一小块一小块的结构体变量在链表中是首尾相连的顾名思义 就像一条铁链一样 而这每一小块的结构体变量中又可以从大方向地分成两个部分, 其中一个部分就是——涵盖着该结构体变量里的所有信息,另一个部分就是链接每块结构体变量的部分——指针。

例如:

typedefstruct node

{

char name[20];

struct node *link;

}stud;

   这样就定义了一个单链表的结构,其中char name[20]是一个用来存储姓名的字符型数组,指针*link是一个用来存储其直接后继的指针。定义好了链表的结构之后,只要在程序运行的时候在数据域中存储适当的数据,如有后继结点,则把链域指向其直接后继,若没有,则置为NULL

 

三、指针和数组的区别

  1.声明的区别

   指针:exterenint * x;

   数组:externint[] y[];

  2.指针是保存数据的地址。

   数组是保存数据。

  3.指针是用于的动态的数据结构。

   数组是用于储存固定的数目且数据类型相同的数据结构。

   数组一经定义,其基址和大小便固定了,在该数组的有效使用范围内是不可变的;

   但是指针则具有很强的动态特征,可以动态地指向任一该类型(定义决定)变量,这也就决定了它 有更大的灵活性。

  4.数组是开辟一块连续的内存空间,数组本身的标示符代表整个数组。

   指针则是只分配一个指针大小的内存,并可把它的值指向某个有效的内存空间。

  5.指针是一个变量,可以被赋值,变量的值是另外一个变量的地址。那么,既然指针是变量,那么指    针必然有自己的存储空间,只不过是该存储空间内的值是一个地址值,而不是别的内容。

数组名仅仅是一个符号,不是变量,不可以被赋值,它没有自己的存储空间。

  6.运算速度上的差异。一般来说,用指针要快些,因为在实际的运算中,总是把数组下标表示通过存储映象函数转换为指针表示,按其地址访问内存,这种转换要进行乘法和加法的运算。

  7.数组具有较好的可读性,指针具有更强的灵活性。一般,对某些多维数组中非连续的元素的随机访问用下标表示比较方便,当按递增(减)顺序访问数组时,使用指针快捷而且方便。

  8.访问方式:指针是间接访问,首先取得指针的内容作为地址,再去该地址访问数据;

数组是直接访问,数组名即是地址。

 

四、学生姓名管理系统

#include <stdio.h>

#include <conio.h>

#include <string.h>

#include <stdlib.h>

#define N 3

typedef struct node

{

   charname[20];

   struct node*link;

}stud;

stud * creat(int n) /*建立单链表的函数*/

{

   stud*p,*h,*s;

   int i;

   if((h=(stud*)malloc(sizeof(stud)))==NULL)

   {

     printf("不能分配内存空间!");

      exit(0);

   }

  h->name[0]=‘\0‘;

  h->link=NULL;

   p=h;

  for(i=0;i<N;i++)

   {

     if((s=(stud *) malloc(sizeof(stud)))==NULL)

     {

       printf("不能分配内存空间!");

        exit(0);

     }

    p->link=s;

    printf("请输入第%d个人的姓名:",i+1);

    scanf("%s",s->name);

    s->link=NULL;

     p=s;

   }

   return(h);

}

stud * search(stud *h,char *x) /*查找函数*/

{

   stud *p;

   char *y;

   p=h->link;

  while(p!=NULL)

   {

    y=p->name;

    if(strcmp(y,x)==0)

      return(p);

     elsep=p->link;

   }

   if(p==NULL)

     printf("没有查找到该数据!");

}

 

stud * search2(stud *h,char*x)

/*另一个查找函数,返回的是上一个查找函数的直接前驱结点的指针,

h为表头指针,x为指向要查找的姓名的指针

其实此函数的算法与上面的查找算法是一样的,只是多了一个指针s,并且s总是指向指针p所指向的结点的直接前驱,

结果返回s即是要查找的结点的前一个结点*/

{

   stud *p,*s;

   char *y;

   p=h->link;

   s=h;

  while(p!=NULL)

   {

    y=p->name;

    if(strcmp(y,x)==0)

      return(s);

     else

     {

      p=p->link;

       s=s->link;

     }

   }

   if(p==NULL)

   printf("没有查找到该数据!");

}

void insert(stud *p) /*插入函数,在指针p后插入*/

{

   charstuname[20];

   stud *s; /*指针s是保存新结点地址的*/

   if((s= (stud*) malloc(sizeof(stud)))==NULL)

   {

    printf("不能分配内存空间!");

     exit(0);

   }

   printf("请输入你要插入的人的姓名:");

  scanf("%s",stuname);

  strcpy(s->name,stuname); /*把指针stuname所指向的数组元素拷贝给新结点的数据域*/

  s->link=p->link; /*把新结点的链域指向原来p结点的后继结点*/

   p->link=s;/*p结点的链域指向新结点*/

}

 

void del(stud *x,stud *y) /*删除函数,其中y为要删除的结点的指针,x为要删除的结点的前一个结点的指针*/

{

  stud *s;

  s=y;

 x->link=y->link;

  free(s);

}

 

 

void print(stud *h)

{

   stud *p;

   p=h->link;

   printf("数据信息为:\n");

  while(p!=NULL)

   {

    printf("%s \n",&*(p->name));

    p=p->link;

   }

}

 

 

void quit()

{

  exit(0);

}

void menu(void)

{

   system("cls");

   printf("\t\t\t单链表C语言实现实例\n");

   printf("\t\t|----------------|\n");

   printf("\t\t| |\n");

   printf("\t\t| [1] 建 立 新 表 |\n");

   printf("\t\t| [2] 查 找 数 据 |\n");

   printf("\t\t| [3] 插 入 数 据 |\n");

   printf("\t\t| [4] 删 除 数 据 |\n");

   printf("\t\t| [5] 打 印 数 据 |\n");

   printf("\t\t| [6] 退 出 |\n");

   printf("\t\t| |\n");

   printf("\t\t| 如未建立新表,请先建立! |\n");

   printf("\t\t| |\n");

   printf("\t\t|----------------|\n");

   printf("\t\t 请输入你的选项(1-6):");

}

main()

{

    int choose;

    stud*head,*searchpoint,*forepoint;

    charfullname[20];

 

    while(1)

    {

      menu();

     scanf("%d",&choose);

     switch(choose)

      {

        case 1:

          head=creat(N);

          break;

        case 2:

                      printf("输入你所要查找的人的姓名:");

          scanf("%s",fullname);

          searchpoint=search(head,fullname);

          printf("你所查找的人的姓名为:%s",*&searchpoint->name);

          printf("\n按回车键回到主菜单。");

          getchar();getchar();

          break;

        case 3:printf("输入你要在哪个人后面插入:");

          scanf("%s",fullname);

          searchpoint=search(head,fullname);

          printf("你所查找的人的姓名为:%s",*&searchpoint->name);

          insert(searchpoint);

          print(head);

          printf("\n按回车键回到主菜单。");

          getchar();getchar();

          break;

        case 4:

                      print(head);

          printf("\n输入你所要删除的人的姓名:");

          scanf("%s",fullname);

          searchpoint=search(head,fullname);

          forepoint=search2(head,fullname);

          del(forepoint,searchpoint);

          break;

        case 5:

                      print(head);

          printf("\n按回车键回到主菜单。");

          getchar();getchar();

          break;

        case6:quit();

          break;

        default:

                      printf("你输入了非法字符!按回车键回到主菜单。");

          system("cls");

           menu();

           getchar();

      }

   }

}

指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环, 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时, 指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要多编程,上机调试。只要作到这些,指针也是不难掌握的。

       指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K(ASCII码为十进制数 75)C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。

 

 

      既然指针变量的值是一个地址, 那么这个地址不仅可以是变量的地址, 也可以是其它数据结构的地址。在一个指针变量中存放一

      个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址, 也就找到了该数组或函数。这样一来, 凡是出现数组,函数的地方都可以用一个指针变量来表示, 只要该指针变量中赋予数组或函数的首地址即可。这样做, 将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中, 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构, 而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址, 它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。


以上是关于C语言的指针链表的原理和各类操作的主要内容,如果未能解决你的问题,请参考以下文章

C语言链表各类操作详解

c语言 双向链表的简单操作-创建插入删除

求c语言大神,指针指向链表的数据如何进行运算和比较?这样写语法是否错误呢?

用C语言建立一个顺序存储的线性表并实现线性表的插入和删除操作

C语言提升

单链表的基本操作实现