1. 程式人生 > >【洛穀日報#75】淺談C++指標

【洛穀日報#75】淺談C++指標

放入我的部落格食用效果更佳(有很多oi學習資料)

1.指標基礎

1.引用

C++有一個東西叫引用,引用相當於給物件(如:變數)起了另一個名字,引用必須用物件初始化,一旦初始化,引用就會和初始化其的物件繫結在一起,就是說引用的值就是被引用的物件的值,引用的值被修改時被引用的物件也會被修改,但不能定義引用的引用,因為引用不是物件,引用定義方式:型別 &引用名1=物件名1

比如:

int i=0;
int &x=i,y=i,z=i;

只有x是引用且與i繫結,y和z都只是一個初值為0的int型別變數。

我們往往可以使用引用型別來簡化程式碼或者節省空間複雜度。引用有些地方需要注意可以看下面程式碼:

int &ref=10;      //錯誤,引用初始值必須是物件
double x=1.78;    
int &ref2=x;      //錯誤,ref2的引用型別與x不匹配
double &p=x;      //正確
p=3.14;

如果去除錯誤項就執行上述程式碼,x的值就會變成3.14。

2.基本的指標

然後C++裡還有一個東西叫指標,是一種物件。 指標和引用定義類似,只不過把&改成了*符號,可以不初始化。指標在本質上是一個地址,因此指標的賦值需要用取地址符&(注意和引用要區分)。獲取指標指向的地址有兩種方式,這裡先介紹一種,用*指標名來獲取指標指向的地址的變數的一個引用。舉個例子:

int i=25;
int* x=&i;
int y=*x;
(*x)=6;

執行完上述程式碼後,i的值變成了6。 和引用相同,指標指向的型別必須與賦值給其的地址型別匹配。指標一般有三種形式: 1.指向一個物件 2.空指標 3.無效指標 空指標即指標值為NULL,這個東西定義在標頭檔案cstdlib中。如果指標未被初始化或者指向物件的空間被回收等等則該指標為無效指標。我們應儘量避免出現無效指標,這往往會讓你的程式碼出現錯誤而且難以除錯。 指標可以使用加減運算子,表示向前或向後任意單位長度的物件的地址。 因為指標是物件,所以可以有指標的指標,指標的指標稱為二級指標,二級指標的指標稱為三級指標,三級指標的指標稱為四級指標,以此類推。指標的功能十分強大,但也難以除錯,很多程式設計師往往會在除錯指標上花費大量時間。

2.指標拓展

1.引用,指標與常量

這是比較搞腦子的一塊內容,請做好準備。 引用與常量的關係只有一中:常量引用,即引用本身的型別必須是常量,但引用的可以是常量也可以是物件。

const int i=5;
const int &r1=i;       //正確,i是常量
const int &r2=15;      //正確,15是常量
int i=42;
const int &r=i;   //正確,r綁定了變數i,引用r不能修改i的值
r=0;   //錯誤,不能用r來修改i的值

因此我們在函式中傳遞某些引數時可以使用常量引用來減少空間複雜度。 指標與常量的關係有兩種情況,一種與引用類似,稱為指向常量的指標,本身型別必須是常量,但指向的值不一定是常量,只是無法通過該指標來修改值,常量指標可以改變其指向的地址。如:

const int i=0,j=0;
const int *p=&i;
p=&j;   //正確

另一種稱為常量指標,這種指標必須初始化,且指向的地址初始化後不能被改變,這種指標的定義有點不同,需要這麼定義: 型別 *const 指標名 如:

int k=12;
int *const p=&k;
const double d=1.5;
const double *const p2=&d;  //p2是一個指向常量的常量指標

2.指標與一維陣列

這裡我們要介紹指標的第二種獲取對應地址的變數的引用的方式:下標運算子。下標運算子可以獲得指標指向位置往後任意個單位的地址的引用。這讓大家想到了什麼?陣列!沒錯,陣列本質上就是在系統棧空間中開出了一塊連續的空間,然後使用陣列時陣列就是一個指向該陣列第0位的常量指標,我們可以用星號來得到第0位的地址的引用。 知道了這些,我們就可以開掛了。有些人或許在抱怨C++中用不了下標為負數的陣列,其實這是可以的。比如我們想開一個下標為[-10..9]的陣列a,可以這麼開:

int _a[20];
int *const a=&_a[10];

或者這樣也可以:int _a[20],*const a=_a+10;然後我們就可以使用一個下標為可以為負數的a陣列了。 同樣,在傳遞引數時,傳遞整個陣列不方便,我們可以把它作為指標來傳遞,這樣做在修改時會直接在你原先的陣列中修改。比如:

void func(int* a){
    //內容
}

同理,交換兩個陣列也可以用類似的方式。但是由於直接開出來的陣列用的是常量指標,無法交換,所以我們可以用普通的指標去指向它們,然後交換普通的指標。

    int _a[100],_b[100];
    int *a=_a,*b=_b;  //使用普通指標代替
    for(int i=0;i<4;++i) a[i]=1;
    std::swap(a,b);
    for(int i=0;i<4;++i) printf("%d",b[i]);

上面這段程式碼就會輸出1111。

3.指標與多維陣列

多維陣列就是多級指標,但是系統並不知道你一個指標所指向的區域有多大,所以作為引數向函式傳進去時需要表明你的陣列大小(注意:如果填的陣列大小一定要與實際的相符)。同樣,在函式中不會另開一個數組,如果出現了修改,會直接在你傳進去的多維陣列中修改。舉個例子:

int b[14][20][20];
void func(int a[14][20][20]){
    a[0][0][0]=1; 
}
int main(){
    func(b);
    printf("%d",b[0][0][0]);
} 

這段程式碼會輸出1。 如果要在函式中返回多維陣列,同樣可以用指標。C++規定函式不可返回陣列,所以我們需要將其調typedef之後以指標的形式返回。

typedef int A[20];
int a[20][20];
A* func(){
    //內容 
    return a;
}

注意,這種方式不可返回函式中開的陣列,因為在函式結束時該記憶體空間會被銷燬,此時返回的指標會指向未知記憶體區域。

4.指標與函式

我們可以使用指標來換函式名,這樣的指標稱為函式指標。函式的型別是由其返回值和引數決定的,因此我們需要這樣定義一個指向函式的指標: 函式返回值型別 (*函式指標)<函式引數表>

先寫一個這樣的函式

void func(const int &x){
    //內容
}

主程式裡可以這麼寫:

void (*p)(const int&);   //定義函式指標
p=&func;                 //將函式指標p指向函式func
(*p)(4);                 //呼叫p指向的函式
p(4);                    //這是一個與上一行等價的呼叫

函式指標是一個物件,因此我們可以像傳變數一樣把它傳來傳去,但是往往需要類型別名。如:

typedef void (*F)(const int&);
void func(const int &x){
    //內容
}
F func2(F p){
    return p;
}

這樣我們就可以像sort函式那樣傳個cmp函式之類的來使程式碼功能更多。

4.指標的相關應用——連結串列

1.動態記憶體

C++語言中,我們可以使用new語句來在系統堆空間中開出點空間來並返回地址,我們可以使用指標來儲存開出來的地址。如:

int* x=new int;  //x指向了一個未初始化的int型別變數

C++還支援開動態的一維陣列:

int* x=new int[10];  //此時x是指向一個大小為10的陣列的下標為0的位置的指標

特別的是,動態一維陣列的下標範圍可以不是常量表達式。如果要開多維的,就有些麻煩了,以二維為例,開一個大小為n×5的陣列就需要這麼開了:

int** x=new int*[n];   //注意此時x是一個二級指標
    for(int i=0;i<n;++i) x[i]=new int[5];

工程上,使用new語句有時會出現一些鬼畜的錯誤,我們就需要使用一些其它的東西,比如說這個定義在標頭檔案new中的nothrow物件,我們可以這麼寫:

int* x;
x=new (nothrow) int;  //如果分配出錯,x就會變成空指標

既然是動態記憶體,我們當然可以隨時釋放它。釋放要使用delete表示式,形式是:

delete p;   //其中p一定要是一個指標,會把p指向的動態記憶體釋放掉

注意,這裡p指向的一定要是一個new語句開出來的記憶體的地址,不然會出錯。執行該語句後,p變成了空懸指標,是一種無效指標。為了避免這類無效指標再來出一些奇奇怪怪的錯誤,我們可以把它變成空指標。

2.連結串列

如果要使用純正的C++連結串列,我們需要使用結構體(或者類)巢狀定義並用動態記憶體去使用。比如,我們可以定義一個雙向連結串列的結點:

struct Node{
    Node* next; Node* pre; 
    int key;
};

然後我們可以使用動態記憶體去開結點,更改結點。我們會發現,很多時候都要用到類似與(*x).y的形式,非常不方便,C++給出的一種簡便寫法,就是x->y,這可以使你的連結串列更加簡潔。一下是一段依次讀入n個數然後輸出的程式碼:

#include<cstdio>
struct Node{
    Node* next;  
    int key;
};
int main(){
    Node* head=new Node;
    Node* x=head; 
    int n;
    scanf("%d%d",&n,&head->key);
    for(int i=1;i<n;++i){
        x=x->next=new Node;
        scanf("%d",&x->key);
    }
    x->next=NULL,x=head;
    for(int i=0;i<n;++i){
        Node* y=x;
        x=x->next;
        printf("%d ",y->key);
        delete y;
    }
    return 0;
}

4.總結

指標是C語言的靈魂,也是C++中重要的一部分,用得好可以使你的程式碼更簡潔,執行更快,功能更多,而動態記憶體和相關的連結串列速度有些慢,不建議在競賽中使用。指標主要是可以實現一些對於記憶體空間的操作,這個概念有些抽象,所以當指標出錯時,其除錯難度也將大大增加。所以對於OIer們,我不建議大量使用指標(特別是在不是模板的程式碼中),在java等較新的語言中,有相當一部分使用了面向物件中的動態繫結替代了指標的使用。

版權宣告:此篇文章為本部落格管理員“傻逼”所寫