当*遇到&——简析数据结构中的引用
这是我在博客园的第一篇博文,自目前是在准备跨专业考软件工程研究生的考研汪一只。就用博客记录自己的一点学习感悟吧。由于408的四门课我只用考一门数据结构,我就把自己学习数据结构的学习感悟整理一下。我不是大牛,只是爬行在IT路上的一只小蚂蚁,希望我的博文能为在校大学生的和自学计算机基础知识的人们提供一点帮助。第一篇博文我谈谈指针和引用吧。
在决心跨专业考研之前,我的计算机底子只有C语言和一点数据库的知识。好在C语言是大一趁着学公共课的时候趴在《C Primer plus》上学的,才让我有了底气款专业考研。
刚开始接触数据结构时候,用的也是严蔚敏的教材,那时候我对变量前面加&的概念只有“取地址”一个作用,后来学习C++的时候才知道引用的概念,虽然严蔚敏的教材第一章就提过这本书用了引用的知识,但最初学习数据结构的时候对引用一点概念也没有,走了不少弯路。不过倒是《数据结构与算法分析——C语言描述》这本经典的教材倒是没有涉及引用的知识,别的不说吧,为了应付考研,也是把数据结构学了下来了。
- C++ primer上对引用的定义:引用(reference)为对象齐了另外一个名字,引用类型引用另外一种类型。
很抽象是吧。这么用这么一种直观的方法理解(可能不是很严谨,如果大神发现我的博文有错误请轻拍):我们知道如果把主函数里的一个变量以值传递的方式传入一个函数,无论在函数里如何操作这个变量,再函数执行完成后,都不会影响主函数里的值。
如果想通过指针就通过函数修改一个变量的值,除了建立一个指针指向一个变量,然后在函数调用参数列表中传入该指针,在函数中如果修改这个指针指向的地址的值,那么在函数执行结束后,这个变量的值也会被修改。
在举例说明引用之前,先定义使用的数据类型
typedef struct LNode
{
ElemType data;
struct LNode* next;//尽管有的编译器在省略struct的时候不会报错,但由于定义都写到这个的是,LNode的类型的声明还没完成,不要去掉struct
}LNode,*LinkList;
这里有同学可能不明白又LNode又*LinkList是怎么回事,我们不如把这个声明改成这样
typedef struct LNode
{
ElemType data;
struct LNode* next;//尽管有的编译器在省略struct的时候不会报错,但由于定义都写到这个的是,LNode的类型的声明还没完成,不要去掉struct
}LNode;
typedef struct LNode* LinkList;
也就是说,LinkList等价于LNode*
如果不通过指针就修改一个变量的值呢?答案是可以的,在函数调用参数列表中传入想修改的变量的引用,同样可以在函数执行完成后修改这个变量的值,以下通过一个例子说明
- 将链表中的第index个元素保存到主函数的定义的变量x中,函数返回执行的状态码OK(符号常量,定义为1)或ERROR(符号常量,定义为0)
int Find( LNode* L , int index , int &x )
{
int i = 1;
LNode* p = L->next;
while( i < index )
{
if( NULL == p )
return ERROR;//链表长度小于index-1
i++;
p=p->next;
}
if( NULL == p )
return ERROR;//链表长度等于index-1
x=p->data;
return OK;
}
执行结果可以参看博文末尾的截图
由于函数的返回值要返回函数的状态码,因要要用别的变量记录第index个位置的元素的值。这个函数把这个值传递给x,由于函数参数列表里使用了x的引用,在函数执行完成后,第index的值会被保存在x中。&的作用不是取地址,而是表示一个整型变量的引用。这个函数如果使用指针,应该这么改写:
int Find( LNode* L , int index , int* px )//px指向一个int类型的变量
{
int i = 1;
LNode* p = L->next;
while( i < index )
{
if( NULL == p )
return ERROR;//链表长度小于index-1
i++;
p=p->next;
}
if( NULL == p )
return ERROR;//链表长度等于index-1
*px=p->data;
return OK;
}
一个int类型的变量的引用比较好理解,而指针本身作为变量的时候,也可以被引用,看下面一个例子:
- L1和L2是两个带头结点的单链表,其中元素递增有序。将L1和L2归并成一个按元素值非递减有序的链表L3,L3由L1和L2中的结点组成,L3的头结点使用L1的头结点
void Merge( LNode* A , LNode* B ,LNode* &C )
{
LNode* p1 = A->next;
LNode* p2 = B->next;
LNode* p;
C = A;//就是这一步修改了C的值,也就是修改了C所指向的地址
p = C;
while( NULL != p1 && NULL != p2 )
{
if( p1->data < p2->data )
{
p->next = p1;
p = p1;
p1 = p1->next;
}
else
{
p->next = p2;
p = p2;
p2 = p2->next;
}
}
if( NULL == p1 )
p->next = p2;
if( NULL == p2 )
p->next = p1;
free( B );
return;
}
对函数参数列表中第三项,看到又有*又有&,初学者可能就犯迷糊了,我学C语言的时候老师说“&和*放一起的时候可以理解相互抵消了”。但在这里,应该这么理解
LNode* &C是对LNode*类型的变量C的引用,即是一个指向LNode类型的指针变量的引用
牢记“指针也是一种类型的变量”,既然是变量,就可以被引用
不是说“通过地址传递可以修改一个变量吗?那修改L3的时候,既然传递的是指向LNode类型的指针,为什么还要使用指针的引用?”
注意题目中要求L3的头结点使用L1的头结点,也就是是要修改L3自身的值,而不是修改L3里的data域或者next域的值。如果是修改data域或者next域的值,确实仅在函数中传递指针变量即可。
最后我们用VS2010运行我们的程序,首先附上完整的运行代码
#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#define ElemType int
#define ERROR 0
#define OK 1
typedef struct LNode
{
ElemType data;
struct LNode* next;
}LNode,*LinkList;
//typedef struct LNode* LinkList;
void Insert( LinkList L , int A[] , int n )//尾插法将一个数组中的元素依次导入
{
int i;
LNode* pNow = L;
for( i = 0 ; i < n ; i++ )
{
LNode* pNew = (LNode*)malloc(sizeof(LNode));
pNew->data = A[ i ];
pNew->next = NULL;
pNow->next = pNew;
pNow = pNew;
}
return;
}
void PrintList( LinkList L )//在屏幕上打印一个链表
{
LNode* p = L->next;
int flag = 0;
while( NULL != p )
{
if( 0 == flag )
flag = 1;
else
printf(" ");
printf( "%d" , p->data );
p = p->next;
}
return;
}
void Merge( LNode* A , LNode* B ,LNode* &C )
//可以改写为void Merge( LinkList A , LinkList B , LinkList &C )
{
LNode* p1 = A->next;
LNode* p2 = B->next;
LNode* p;
C = A;//就是这一步修改了C的值,也就是修改了C所指向的地址
p = C;
while( NULL != p1 && NULL != p2 )
{
if( p1->data < p2->data )
{
p->next = p1;
p = p1;
p1 = p1->next;
}
else
{
p->next = p2;
p = p2;
p2 = p2->next;
}
}
if( NULL == p1 )
p->next = p2;
if( NULL == p2 )
p->next = p1;
free( B );
return;
}
int Find( LNode* L , int index , int &x )
{
int i = 1;
LNode* p = L->next;
while( i < index )
{
if( NULL == p )
return ERROR;//链表长度小于index-1
i++;
p=p->next;
}
if( NULL == p )
return ERROR;//链表长度等于index-1
x=p->data;
return OK;
}
int main()
{
LinkList L1 = (LNode*)malloc(sizeof(LNode));
LinkList L2 = (LNode*)malloc(sizeof(LNode));
LinkList L3 = (LNode*)malloc(sizeof(LNode));
printf("L1的地址是:%p\n",L1);
printf("L2的地址是:%p\n",L3);
printf("L3的地址是:%p\n",L3);
int A1[ 5 ] = {1,3,5,7,9};
int B1[ 4 ] = {2,4,6,8};
Insert( L1 , A1 , 5 );
Insert( L2 , B1 , 4 );
printf("L1:");
PrintList( L1 );
printf("\n");
printf("L2:");
PrintList( L2 );
printf("\n");
Merge( L1 , L2 , L3 );
printf("归并后的链表为:");
PrintList( L3 );
printf("\n");
int x = -1;
Find( L3 , 5 , x );
printf("L3链表中第5个元素为:%d\n" , x );
printf("\n");
printf("L1的地址是:%p\n",L1);
printf("L2的地址是:%p\n",L2);
printf("L3的地址是:%p\n",L3);
return 0;
}
大家可以看到,在执行完Merge函数后,L3和L1指向了同一个地址,这正是L3的引用改变了L3本身的值的“功劳”