C/C++ 語言指針詳解(一)

2021-02-20 循序漸進C++

指針是 C 語言的精髓,因為有了指針,C 語言可以和彙編語言比效率。在教學過程中,指針這部分內容常常給學生帶來困惑。下面我來說說指針在 C 及 C++語言中的用法。

1.指針的概念

上一篇文章,變量的三個要素之一,變量的值包括變量的數據值和變量的地址值。這個地址值也可以由另外的一個變量來存儲,這個變量就需要是指針變量。

指針的定義及相關術語

(1)指針用來存放某個變量的地址的值的變量,這個變量與一般變量不同,它所存放的值是某個變量在內存中的地址的值。
(2)一個指針變量存放了哪個變量的地址,就說這個指針指向了哪個變量。
(3)指針必須初始化或者賦值(指向了變量數據)後,才能進行間接訪問操作
內存空間的訪問方式:

通過地址訪問(通過指針間接訪問它所指向的變量的值)

取地址符:&
例:int x;
則&x 表示變量 x 在內存中的起始地址,
x 是整型數據,佔四個字節的內存單元
那麼&x 就可以存放在一個指針變量裡。這就需要定義一個指針變量。

2. 指針定義格式

語法格式
存儲類型   數據類型 *指針名=初始地址;
例:int *pi=&x;
說明:
(1)這個語句就定義了 pi 這個指針變量,pi 是指針名,int 是數據類型,是指這個指針變量要存放的是 int 類型變量的地址,其他類型變量的地址不可以給指針變量 pi 賦初值或賦值。
(2)語句雖然沒有給指針指定存儲類型,但是有默認的存儲類型 auto 類型,即存儲類型為自動類型,也就是函數級的自動類型變量,存放在棧空間,由編譯系統管理 pi 變量的所佔用的內存空間。
(3)這條聲明語句中的「」號非常重要,只有有了「」號,才能說明 pi 是指針變量,才能存放另外一個整型變量的地址;
(4)聲明了指針變量 pi,並且給這個變量賦了初值為 x 變量的地址,我們就可以說 pi 指向了 x。(5)定義指針變量的時候可以賦初值,也可以不賦初值,如果賦初值需要注意的是該變量必須在指針初始化之前已聲明過,且變量類型應與指針類型一致。也可以用一個已賦初值的指針去初始化另一 個指針變量。
(6)既然 pi 是一個指針變量,那麼 pi 也會分配內存單元,也佔用內存空間,因此也有地址,&pi 就是指針變量的地址,不管指向什麼類型的變量的指針,所佔用的空間大小是一樣的,都是 4 個字節的內存單元。
(7)如果指針沒有賦初值,那麼在通過使用指針間接它所指向的變量前,一定要賦值,這樣也很好理解,指針沒有指向變量,怎麼能間接訪問這個變量的數據值呢?因此說使用指向前,指針不能為空 。
我們來看這個程序段

#include <iostream>
using namespace std;
int main()
{
int x = 100;//聲明 x 變量,並賦初值 100
int *pi = &x;//聲明 pi 指針變量,並賦值為 x 的地址
cout << x << endl;//輸出 x 變量的數據值
cout << &x << endl;//輸出 x 變量的地址值
cout << *pi << endl;//通過指針間接訪問 x 變量的值
cout << pi << endl;//pi 變量的值,就是 x 的地址
cout << &pi << endl;//pi 變量的地址值
getchar();
return 1;
}

執行結果如下圖 1 所示

圖1 程序代碼執行結果

分析上面語句可以看出,pi指針保存的是x變量的地址,第2條輸出語句和第4條輸出語句的結果是一樣的,都是x變量的地址00F2FBFC,是8個16進位字符,也就是說是一個32位的長整型數據,表示指向的變量在內存中的編號。對於第3條輸出語句cout << pi << endl;的輸出結果為x變量的值100,在開始學習這裡內容學生會有疑惑,都是因為那個聲明語句int pi = &x;帶來的困惑。在這個聲明語句中,看似pi賦值為&x,怎麼到了輸出pi時,pi就成了x變量的值了呢?因為那個是聲明語句,聲明語句中的「」號,只是表示後面的變量pi是指針類型,只有是指針類型,才可以賦值為另外一個變量的地址,離開了這個聲明語句,那個「*」號,就成了通過指針訪問變量的取內容運算符了。因此cout << *pi << endl;輸出pi指針指向的變量的值。圖1結構中x變量的數據值、x變量的地址、pi變量的數據值、pi變量的地址值,在內存中的模型示意圖,如圖2所示

圖2 pi指針指向x變量的物理內存模型示意圖

如果畫pi變量指向x變量的邏輯圖,就很簡單了,示意圖如圖3所示

圖3 pi指針指向x變量的邏輯示意圖

兩個格子,分別表示pi變量和x變量,畫一個箭頭,箭尾畫在pi指針變量的小格子裡面,箭頭指向x的小格子,不要指到x變量的小格子裡。這樣就表示pi指針存放的是x變量的地址。

3. 指針變量可以指向不同的數據類型

定義指針變量:
1)int *pi;
//pi是一個指向int型變量的指針;
2)float *pl;
//pl是一個指向float型變量的指針;
3) char *pc;
//pc是一個指向char型變量的指針;
4)char (*pa)[3];
//pa是一個指向一維數組的指針,這個一維數組是長度為3的字符數組;
5) char *pm[3];
//pm是一個指針數組,pm是數組名,這個數組的三個元素是字符變量的地址
6)int (pf)();
//pf是一個指向函數的指針,該函數的返回值為int型數值;
//也就是說指針不僅可以指向數據,也可以指向代碼,函數就是代碼段、
//函數名就是函數的地址
7)int * fun();
//函數的返回值是指針,是指向整型變量的指針
8)int **pp;
//pp是一個指向指針的指針,即二級指針。
//要存儲一個指針變量的地址,就需要一個二級指針
//如果pi是執行整型變量的指針
//pp變量就可以存儲pi變量的地址
//一個「」號,*pp表示間接訪問pp指向變量的值,就取得了pi變量的值,
//pi變量是x變量的地址,因此**pp才可以訪問到x變量的值。
9)

struct DATE{
int year,month,day;//三個結構體成員
};//定義結構體變量DATE
DATE *pd;//定義指向結構體變量的指針;

10)結構體成員的指針類型是自身結構體類型(也是定義鍊表的格式)

struct NODE
   { 
     int   data;
     struct NODE *next; //結構體成員是指向自身結構的結構體變量的指針
};

這種結構體類型也叫自引用類型
11)指向對象的指針

class PERSON  //定義PERSON類
{
 private:
 char *name; //姓名  類的成員是指向字符的指針
      Int  age; //年齡
     char   gender;//性別
     public:
     PERSON(char *n,int nl,char xb) //類的構造函數
{
  strcpy(name,n);
   age=nl;
gender=xb;
}
     }; 
  PERSON  *pa;//pa是指向PERSON類的指針

4.指針的運算

指針有三種運算:賦值運算、算術運算和關係運算。

(1)賦值運算

說明:指針名=地址「地址」中存放的數據類型與指針類型必須相符。b. 向指針變量賦的值必須是地址常量或變量,不能是普通整數。但可以賦值為整數0,表示空指針。c. 指針的類型是它所指向變量的類型,而不是指針本身數據值的類型,任何一個指針本身的數據值都是unsigned long int型。d. 允許聲明指向 void 類型的指針。該指針可以被賦予任何類型對象的地址。void vobject;
//錯,不能聲明void類型的變量void *pv;
//可以聲明void類型的指針int  *pint;int i;void main()
//void類型的函數沒有返回值{pv = &i;//void類型指針指向整型變量pint = (int *)pv;// void指針賦值給int指針需要類型強制轉換:}

(2)算術運算

指針與整數的加減運算
指針 p 加上或減去 n ,其意義是指針當前指向位置的前方或後方第 n 個數據的地址。
這種運算的結果值取決於指針指向的數據類型。
指針加一,減一運算
指向下一個或前一個數據。
例如:y=*px++ 相當於 y=*px;px++
(*和++優先級相同,自右向左運算,但是如果++運算符是後綴,儘管++運算符優先級高,由於它的風格更高,因此它會讓指針先進行取內容運算,再做加1運算)兩個指針相減
兩個指針相差數據的個數。
說明:兩個指針不能做加法運算,因為兩個指針相加沒有意義 。

#include <iostream>
using namespace std;
int main()
{
    int i = 5;//聲明變量i
    int *p = &i;//聲明p指針指向變量i
    cout << "p為" << p << endl; //i的地址
    cout << "*p為" << (*p)++ << endl; //5
    //通過指針p訪問i變量,語句執行結束後,把i變量的值加1
    cout << p << endl;//依然是i變量的地址
    cout << *p << endl;//i變量的值是6
    getchar();
    return 1;

輸出結果如圖4所示。
上面代碼第4行,如果把括號去掉,結果會有很大的不同
如果把cout << "*p為" << (*p)++ << endl;
語句改成
cout << "*p為" << *p++ << endl; //5
輸出的結果依然是5,但是這條語句執行結束後,不是指針間接訪問的變量加1,而是指針變量加1,一個指針加上常量1,那麼指針就指向整數i變量後面的數據了,因此後面的輸出語句cout<<p<<endl;//這個是變量i後面整數的地址
cout<<*p<<endl;
//間接地訪問了i變量後面地址單元裡的內容
輸出結果如圖5所示。

圖4 程序執行結果

圖5  去掉包含p指針的括號後程序執行結果

指針加減一個常數後,指針值的大小和指針指向數據類型有關, 如圖6所示。

圖6 指針加減一個常數,移動字節數與指向變量的數據類型有關

總結:指針加減整數的操作表示空間位置上的移動;但是移動的字節數與指針所指數據類型相關。
對float指針加6實際增加了24個字節
對 int指針加5實際增加了20個字節
對char指針減7實際減少了7個字節
對double指針減2實際減少了16個字節

(3)關係運算

兩個指針可以做 <,<=,>,>=,==,!=這樣的關係運算。圖下程序段,

int a[10]={10,20,30,40,50,60,70,80,90,100};
//定義一個長度為10的數組
int *p=&a[0];//p 指針指向第一個數組元素
int *q=&a[9];  //q指針指向第10個數組元素
while(p<=q)//循環條件,兩個指針做關係運算
{
   Cout<<*p<<」\t」; //通過指針間接訪問數組中的每個數據元素
   p++;
}

5.指針與數組

數組名是一個指針常量,一個數組的數組名就是該數組首地址的值。指針常量不同於指針變量,指針常量的值不能改變,但指針變量的值是可以改變的。
說明(或聲明)與賦值
例:int a[10],  pa;
pa=&a[0]; 或 pa=a; 兩個賦值語句都是讓pa指針指向數組的第一個元素
通過指針使用用數組元素,經過上述聲明及賦值後:
pa就是a[0],(pa+1)就是a[1],... ,(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。都是表示數組下標為i的數組元素,
但是不能寫 a++,因為a是數組首地址是常量。

#include <iostream>
using namespace std;
int  main()
{
static int a[5]={5,4,3,2,1};
int i,j;
i=a[0]+a[4];
j=*(a+2)+*(a+4);// 等價於 j=a[2]+a[4] 結果為4
cout<<i<<endl<<j;
getchar();
return 1;

6.字符指針

字符指針是指向字符串的指針。字符指針便於對字符串進行操作。
如:char *p=「abcd」,*q;
q=「mnpq」;
字符指針比較特殊,比如p是字符串「abcd」的首地址,q存放的是「mnpq」字符串的首地址
cout<<*p<<endl;//輸出字符a
cout<<*q<<endl;//輸出字符m

cout<<p<<endl;//
不會輸出地址值,而是輸出從這個地址開始,到』\0』結束的字符串,因此這個語句的輸出結果是「abcd」這個字符串,因為字符串就是以『\0』結束的。同樣cout<<q<<endl;輸出結果是「mnpq」這個字符串。

7.指針數組

數組的元素是指針的類型
定義格式:類型  *數組名[數組長度]
例:int  *pa[2];
//pa就是指針數組,每個數組元素是整型變量的地址

#include <iostream>
using namespace std;
int main()
{
 int a[3] = { 1,2,3 };
 int* pa[3];
 pa[0] = &a[0];//pa[0]指向a[0] *pa[0]就是a[0]
 pa[1] = &a[1];//pa[1]指向a[1] *pa[1]就是a[1]
 *(pa + 2) = a + 2;//等價於pa[2]=&a[2] *pa[2]就是a[2]
 cout << *pa[0] << "," << **(pa + 1) << ',' << *pa[2] << endl;//1,2,3
 // *(pa+1)就是pa[1],pa[1]指向a[1];因此pa[1]還是一個地址
 //**(pa+1)等價於*pa[1]等價於a[1]
 getchar();
 return 1;
}

8. 指向數組的指針

指向一維數組的指針,可以認為是一個二級指針,因為數組可以表示為一級指針,因此它是指向一級指針的指針,故為二級指針。
定義格式:類型 (*指針名)[數組長度]如:

int a[2][3]={1,2,3,4,5,6};  
int (*pa)[3];  
pa=a+1;  

對於二維數組a,我們可以把它看成是一個一維數組,這個一維數組有兩個數組元素,每個數組元素又可以看成是一個有三個數組元素的一維數組。
對於a來說,a[0]和a[1]是它的兩個數組元素;
但是a[0]和a[1]分別是與三個數組元素的數組名;
a[0]這個數組中的三個數組元素是{a[0][0],a[0][1],a[0][2]}
a[1]這個數組中的三個數組元素是{a[1][0],a[1][1],a[1][2]}
這樣看來,對於 a是一個二級指針就好理解了
a就是a[0],(a+1)就是a[1];
a是指針,通過指針取內容,取到了兩個數組元素a[0]和a[1];
a[0]有是三個數組元素的數組名,a[0]就是a[0][0]同樣是通過指針取內容a[0]就是(a),因為a是a[0],因此**a就是a[0][0],對於二維數組取到數組元素,就是把數組名看成了二級指針。
(a+1)+1呢?還是一個地址,a[1]是地址,地址加上一個常量1,是下一個元素的地址,a[1]是三個數組元素{a[1][0],a[1]以[1],a[1][2]}的首地址,a[1]+1 就是a[1][1]的地址,因此((a+1)+1)就是數組元素a[1][1],以此類推,
((a+i)+j)是數組元素a[i][j]

using namespace std;
int main()
{
 int a[2][3] = { 1,2,3,4,5,6 };
 int(*pa)[3];
 pa = a + 1;//a是二級指針,a+1還是二級指針,pa指針指向了{a[1][0],a[1][1],a[1][2]}這三個元素的數組,pa+1或a+1實際移動的字節數是三個整型數據的空間,即12個字節
 cout << pa[1][0] << "," << pa[0][0] << "," << *(*pa + 2) << endl;
//pa[1][0]是數組a後面的數據,不是數組a中的數據,因此這個是隨機值
//pa[0][0]就是a[1][0],值是4
//* *(*pa + 2)就是pa[0][2],是a[1][2]這數組元素,值是6
 getchar();
 return 1;
}

9.指針與函數(1)形參是指針的函數

如果定義一個函數,形參定義的是指針,那麼調用的時候,指針形參對應的實參一定是地址,我們把這種調用方式成為傳地址調用。先定義一個函數, 用指針變量作參數, 實現兩個數的交換,然後在主函數中調用它。

相關焦點

  • c語言指針與字符數組
    ,字符串的使用在C語言中也是非常重要的,常常會遇到一些操作,如字符串的修改、拷貝、字符串長度等,在物聯網的應用中也尤為突出,物聯網應用中所用的模組,大多是需要使用AT指令的,這就需要對字符串的操作。我們定義一個指針變量char * pc;內存示意,通常指針是4個字節,假設這裡的地址是200
  • C語言指針詳解
    先聲明幾個指針放著做例子:例一:(1)int *ptr;(2)char *ptr;(3)int **ptr;(4)int (*ptr)[3];(5)int *(*ptr)[4];如果看不懂後幾個例子的話,請參閱我前段時間貼出的文章 如何理解c和c++的複雜類型聲明>&
  • C 語言會比 C++ 快?
    在我們達到這一狀態之後,我們再來探究將代碼更改為純 C99。請注意,我完成這些實現的方式是通過獲取現在可以在存儲中看到的代碼,並將其更改為更慣用的 Modern C ++ [1]。但是,這些通常與之前版本的simplifier.cpp非常接近,不同之處在於現在可以直接比較變化。
  • C 語言指針詳解
    方案一:課間 1 班學生全都去 2 班, 2 班學生全都來 1 班,當然,走的時候要攜帶上書本、筆紙、零食……場面一片狼藉;方案二:兩位老師課間互換教室。顯然,方案二更好一些,方案二類似使用指針傳遞地址,方案一將內存中的內容重新「複製」了一份,效率比較低。
  • C+相比其他語言到底難在哪裡?
    看過程式語言排行榜的都知道,c/c++自02年以來,不管時代如何發展,其排名一直在前五以內,足見其在程式語言界的地位。編程界流行這麼一句話:c幾乎什麼都能做,c++幾乎什麼都能做好,足見其功能的強大。
  • C語言是C++的母語,萬變不離指針,指針是C語言的一大法寶!
    我們都知道C語言是一門過程性語言,所謂過程性就是在解決問題時,將問題按步驟分解。 例如,做菜的時候,先點火,再倒油,接著下菜翻炒,最後加鹽和醬油。但有時候借鑑面向對象的思想來組織代碼,邏輯層次會更加清晰。
  • C語言的那些小秘密之函數指針
    我們經常會聽到這樣的說法,不懂得函數指針就不是真正的C語言高手。我們不管這句話對與否,但是它都從側面反應出了函數指針的重要性,所以我們還是有必要掌握對函數指針的使用。先來看看函數指針的定義吧。本文引用地址:http://www.eepw.com.cn/article/270442.htm  函數是由執行語句組成的指令序列或者代碼,這些代碼的有序集合根據其大小被分配到一定的內存空間中,這一片內存空間的起始地址就成為函數的地址,不同的函數有不同的函數地址,編譯器通過函數名來索引函數的入口地址,為了方便操作類型屬性相同的函數,c/c++引入了函數指針,函數指針就是指向代碼入口地址的指針
  • 一文帶你了解c++和c中字符串的使用
    ,但是如果你對c語言理解不是很深的話,那你可能就不能"享受"到這裡面的"美味"用法了,既然標題都標註了這個,我也不賣關子,下面會有總結分享的。說完了c,那麼對於我們的c++來說,它定義字符串就簡單多了,因為有關鍵字來定義,你一看就知道。那麼下面大家就隨著我的筆步一起來看看究竟吧!
  • C/C+編程筆記:const 變量詳解!一文了解具體用法
    由於分文件編寫,不好呈現,所以在這裡不為大家提供c語言全局const變量默認為外部聯編的案例。如果各讀者有興趣,可自行嘗試。 在這裡解釋一下上述代碼:第二行代碼,使用了const(expression),顯示轉換,這是由於c++比c類型轉換更嚴格。
  • C 語言程序設計---指針
    上次 C 語言寫到了數組,有些書是先講指針,有些書是先講函數,按照我以前學習 C 語言的順序,以及對 C 語言的理解,學習的順序是這樣的:數組--->指針--->函數,所以本篇文章講解 C 之指針。C 語言是值得好好學習的一門語言,是一門基礎語言,更是我編程入門的語言,其中很多編程思想,至今影響著我,在工作中對我的幫助很大。
  • C語言函數指針之回調函數
    如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。2 為什麼要用回調函數?
  • C 2 C++進階篇(1)
    首先談談筆者的水平,只學過c和數據結構,接觸過指針,對於取地址&從來沒有接觸過(因為據說是老師說不符合嚴謹的c....), python
  • python+C、C++混合編程的應用
    排序說明不了語言的好壞,反應的不過是某個軟體開發領域的熱門程度。語言的發展不是越來越common,而是越來越專注領域。有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。
  • 深入淺出剖析C語言函數指針與回調函數
    回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
  • 為什麼指針被譽為 C 語言靈魂?
    這篇看完,相信你會對指針有一個新的認識,坐等打臉😂一、內存本質編程的本質其實就是操控數據,數據存放在內存中。因此,如果能更好地理解內存的模型,以及 C 如何管理內存,就能對程序的工作原理洞若觀火,從而使編程能力更上一層樓。
  • C/C++可變參數函數
    c/c++支持可變參數的函數,即函數的參數是不確定的。一、為什麼要使用可變參數的函數?一般我們編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的所有實際參數。但在某些情況下希望函數的參數個數可以根據需要確定,因此c語言引入可變參數函數。這也是c功能強大的一個方面,其它某些語言,比如fortran就沒有這個功能。典型的可變參數函數的例子有大家熟悉的printf()、scanf()等。二、c/c++如何實現可變參數的函數?
  • C語言簡明教程(九)指針(二)
    通過改變指針變量的值使它指向字符串中的不同字符。編寫程序 11-3.c 用 i++ 來遍歷數組,程序 11-4.c 用 p++ 遍歷數組。解題思路:定義一個函數 copy_string 用來實現字符串複製的功能,在主函數中調用此函數,函數的形參和實參可以分別用字符數組名或字符指針變量。分別編程,以供分析比較。編寫程序 11-5.c 用字符數組名作為函數參數,程序 11-6.c 用字符型指針變量作實參,程序 11-7.c 用字符指針變量作形參和實參。
  • C語言,C++,C ,Java之間的關係
    C語言,C++,C#,Java,這幾種語言,應該說是當前最流行,也是最基礎的計算機語言。是不是有些人看著會頭大,大腦會不叫混亂,一個計算機怎麼會有那麼的的語言呢?看著就頭大。之後,為了方便理解,又出現了編匯語言---有英語單詞組成,這裡可以理解了,但是,編匯語言還是要轉換為計算機語言,這裡有專門的軟體將編匯語言轉為計算機語言。這個軟體我想大家應該猜到了,就是編譯器。裡面單詞與及一些語法和01010的代碼相對應,可以把編匯語言很好的翻譯成機器語言。
  • 深入淺出剖析C語言函數指針與回調函數(一)
    百度的權威解釋如下:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
  • 剖析C語言中a=a+++a的無聊問題
    看法一:  a=a+++++a這個東西可以用來討論,甚至是討論它的無所事處,作為增長知識和發現自身理解問題的漏洞是可以的。但是絕對不能拿來作為考試題目,特別是選擇題或填空題等客觀題目。但是如果作為一道主觀探討題還是挺有趣的,理解深刻的人一定可以寫的很好。