這是我在博客園的第一篇博文,自目前是在准備跨專業考軟件工程研究生的考研汪一只。就用博客記錄自己的一點學習感悟吧。由於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本身的值的“功勞”
