c++基礎7:指針和引用

一.指針

 1.指針概念:指針就是用來保存內存地址的變量。

    聲明指針的方式:int *p;與運算符*結合,p就表示一個指針。

 

 2. 爲什麼使用指針(指針的三大作用):由於指針可以通過內存地址直接訪問數據,可避免在程序中複製大量的代碼,因此指針的效率最高,其三大作用如下

     2.1 處理堆中存放的大型數據

     2.2 快速訪問類的成員數據和函數

     2.3 以別名的方式向函數傳遞參數

 

 3.運算符*&,以及運算符->

   &取地址運算符(後面再講引用的時候這個就變成引用運算符了)。 如

int a=5;
cout<<&a<<endl;//使用&獲取變量a的內存地址

   *指針運算符或間接引用運算符(注意:如果*用於聲明指針,那麼它就是指針說明符,如下)

int a=1;
int *p=&a;//這裏的*表明是指針說明符
cout<<*p<<endl;//這裏的*表示指針運算符,使用*獲取指針變量p中保存的地址處的值,也就是1

 ->是成員指針運算符或指向成員運算符

 

4.複雜變量的解釋(要判斷是一個變量的類型就看與他最先結合的運算符是什麼),舉例如下:

 int p;//變量p是一個普通的整形變量

int *p;//由於變量p先與運算符*結合,所以p本質是一個指針,再與int結合,所以p就是一個指向整形數據的指針

int  p[3];//由於變量p只與運算符[]結合,所以p本質是一個數組,再與int結合,所以p就是一個由整形數據組成的數組

int *p[3];//變量p先與[]結合,因爲[]的優先級高於*,所以p本質上是一個數組,剩下的就是要知道p是一個什麼數組,然後p再與*結合,說明是p是一個指針數組,然後再與int結合,說明p是一個指向整形數據的指針所組成的數組

int (*p)[3];//由於()改變了優先級,所以變量p先與*結合,所以p本質上是一個指針,那它是什麼類型的指針呢,然後p再與[]結合,說明p是一個指向數組的指針,然後再與int結合,說明p是一個指向整形數據組成的數組的指針

 int **p;//變量p先與*結合,說明p本質是一個指針,那它是什麼指針呢,然後再與*結合,說明p是一個指向指針的指針,然後再與int結合,說明p是一個指向整形數據的指針的指針

 int p(int a);//p首先與()結合,說明p是一個函數,然後進入()分析,發現函數有一個int型的參數a,然後再與int結合,說明p是具有整型參數且返回類型爲整型的函數

int (*p)(int a);//由於(*p)改變了優先級,所以p先與*結合,說明p本質是一個指針,然後再與(int a)結合,說明p是一個函數指針,然後再看(int a)發現函數具有一個int型的參數a,然後再與int結合,說明p是一個指向具有整型參數且返回類型爲整型的函數的指針

 

 5.指針的四方面重要內容

     指針的類型,指針所指向的類型,指針的值(指針所指向的內存區),指針本身所佔據的內存區

    5.1 判斷這四個方面的規則

     指針的類型:把指針聲明語句的指針名字去掉,剩下的部分就是指針的類型

     指針所指向的類型:把指針聲明語句裏的指針名字和名字左邊的指針聲明符*去掉,就是指針所指向的類型

     指針的值:在32位程序裏,所有類型的指針的值都是一個32位整數,指針的值是指向的內存區域的首地址

     指針本身所佔據的內存區:指針本身佔據的內存長度可以使用sizeof(指針的類型)測一下就知道,在32位平臺裏,指針本身佔據了4個字節的長度

 

舉例說明指針的類型和指針所指向的類型:

舉例 指針(ptr)(本身)的類型 指針所指向的類型
int *ptr int * int
char *ptr char * char
int **ptr int ** int *
int (*ptr)[3] int (*)[3] int ()[3]
int *(*ptr)[4] int *(*)[4] int *()[4]

 

 6.指針與常量

聲明定義方式 註解
常量指針 int *const p; 指針本身不可改變,指向的變量可變
指向常量的指針 const int *p; 指針本身可變,其指向的變量不可變
指向常量的常指針 const int *const p; 指針本身不可變,其指向的變量也不可變  

 

 7.指針的注意事項:迷途指針

       定義一個指針之後,如果沒有給他賦初值,那麼該指針就是一個迷途指針,它可以指向任何地址,並且如果對該指針進行操作就會對位置區域的數據進行修改或刪除,照成意想不到的後果,所以解決辦法是將定義的指針進行初始化,如下

int *p=0;

這樣,這個指針就稱爲“空指針”。

不僅在初始化的時候,還有一種情況迷途指針也會造成危害,就是刪除指針delete p;之後,雖然指向的內存空間釋放了,但是指針本身還存在,如果再次使用該指針也會造成很嚴重的後果,所以再刪除一個指針之後,將該指針賦值爲空。雖然空指針是非法的,容易是程序奔潰,但是我們寧願程序崩潰,也不願意調試起來很困難。如下:

int *p=new int;
delete p;
p=0;
*p=23;

刪除指針p後,賦值爲0,然後在使用該空指針,程序運行的時候,運行到*p=23就會報錯,這樣我們就知道我們使用了一個迷途指針,從而及時修改程序。要是我們將p=0這句話去掉,那麼程序就不會報錯,但是何時崩潰就不知道了,這樣加重了我們查問題的難度。

 

 二.引用

1.引用的概念:引用就是別名,如

int &rnum=num;//這裏的&是引用運算符

rnum是整形變量num的別名,這樣,對rnum的操作實際就是對num的操作。

這裏要注意,別名rnum前面的符號&不是取地址運算符,而是引用運算符。

2.引用的作用(爲什麼要用“引用”)

其實引用只是爲變量另外起了一個名字,就像#define num rnum==(int &runm=num),將num定義成rnum,兩者在內存中是同一個空間。引用它不像int rnum=num;rnum其實是在內存中新分配了一個空間,所以rnum和num佔據的是兩個不同的內存空間。那麼引用在程序中到底有什麼用呢?

我們知道我們在傳函數的參數的時候,分兩種:按值傳遞和按地址傳遞。

按值傳遞:
void swap(int a,int b)

按值傳遞,編譯器會自動在棧中創建a和b的副本,如果形參不是int類型,而是類類型,那麼副本就會很大,效率很低,這時候就要考慮按地址傳遞

按地址傳遞:

void swap(int *a,int *b)
{
   int c;
    c=*a;
    *a=*b;
    *b=c;
}

上面的功能是達到了,把指針作爲函數的接受參數雖然能正常使用,但是它卻不易閱讀,而且很難使用。這時候引用作爲形參就派上用場了:

如果引用作爲參數,在函數內部可以修改a的值和b的值,這樣破壞了按值傳遞的保護機制,不過我們可以使用const來聲明一個不可修改值的引用,假設我們不想在函數內修改a的值,那麼上面的代碼修改如下:

void swap(const int &a,int &b)
{
   int c;
    c=a;
    a=b;//編譯報錯:不能給常量a賦值
    b=c;
}

通過上面的代碼,a就不能被賦值了,這樣a就被稱爲常引用
總結:引用在使用中單純的給某個變量取個別名是沒有意義的,引用的主要目的是可以作爲按地址的參數傳遞還可以作爲函數的返回值(注意:局部變量是不能返回引用的,因爲局部變量在函數返回後會被銷燬)

 

3.引用的兩個特點

    第一:定義引用的同時要對該引用進行初始化,否則編譯不能通過。如下

//正確的定義引用
int a=0;
int &ra=a;
//錯誤的定義引用ra,必須進行初始化
int a=0;
int &ra;
ra=a;

    第二:引用可以改變其指向地址的數據,但是不能改變其自身的地址(也就是說別名的地址是不會被改變的,但是別名的值會變),如

int a;
int &ra=a;
a=999;
cout<<"a="<<a<<"  "<<"&a="<<&a<<endl;//a=999 &a=0012ff60
cout<<"ra="<<ra<<"  " <<"&ra="<<&ra<<endl;//ra=999 &ra=0012ff60

int b=1000;
ra=b;
cout<<"a="<<a <<"  "<<"&a="<<&a<<endl;//a=1000 &a=0012ff60
cout<<"ra="<<ra<<"  " <<"&ra="<<&ra<<endl;//ra=1000 &ra=0012ff60
cout<<"b="<<b <<"  "<<"&b="<<&b<<endl;//b=1000 &b=0012ff48

ra=1;
cout<<"a="<<a <<"  "<<"&a="<<&a<<endl;//a=1 &a=0012ff60
cout<<"ra="<<ra<<"  " <<"&ra="<<&ra<<endl;//ra=1 &ra=0012ff60
cout<<"b="<<b<<"  " <<"&b="<<&b<<endl;//b=1000 &b=0012ff48

上面的例子中ra=b之後,查看ra的地址可以看出來,ra的地址並沒有變化,也就是說ra是a的別名,那麼就不可能變成其他變量(b)的別名,對ra的操作還是在操作a,而不是b,所以最後在ra=1之後改變的還是a的值。


 

4.引用的注意事項

    4.1 引用聲明的時候必須進行初始化 int num=5;int &rnum=num;

    4.2 不能建立數組的引用(int &a[5]),不能建立引用的引用(int &&a),不能建立引用的指針(int &*a),

    4.3 可以建立指針的引用:

int *p;
int *&q=p;

上面的q與它最先結合的運算符是&,所以他的本質是一個引用,然後再與*結合,所以q是一個指針的引用,也就是指針p的別名。
    

     4.4 引用在作爲函數的返回值的時候,千萬注意,局部變量是不能作爲返回值的,因爲局部變量在函數返回的時候已經被釋放了,如下:

class A
{

}

A &func()
{
     A a;
     return a;
}

上面的func函數返回的是局部變量類A的對象a,如果外部使用了它會報錯,因爲局部變量a在函數返回的時候已經被釋放了。


 

 三.指針和引用

1.首先對指針和引用的運算符&和*進行說明:

運算符&和*在聲明定義的時候(包括形參的聲明)稱爲引用運算符(聲明變量是引用)指針說明符(聲明變量是指針);它們在使用的時候稱爲取地址運算符(獲取變量的內存地址)和指針運算符(獲取指針指向的地址裏的內存數據),例如:

運算符&:

int a=5;
int &a1=a;//引用運算符
cout<<&a1<<endl;//取地址運算符
int func(int &a,int &b);//引用運算符

運算符*

int b=6;
int *b1=&b;//指針說明符;取地址運算符
cout<<*b1<<endl;//指針運算符
int func(int *a,int *b);//指針說明符

 

2.常指針和常引用

它們的聲明方式相同,都是使用const來定義,但是由於引用本身是不可更改的,所以不用這樣聲明:int const &a;

 

3.指針和引用的區別

指針可以爲空,引用不可以。

指針可以被賦值,引用只能被初始化,不能被賦值。

在堆中創建一塊內存區域,必須使用指針來指向它,不能使用引用來指向它,如int &r=new int;這句話是錯誤的。這時候你可以這樣int *&r=new int;r表示一個指針的引用,也就是指向new int所在的堆區的指針的別名。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章