搞清楚C語言指針

2{icon} {views}

Part 0:為什麼要寫這篇文章

C語言中的指針是C語言的精髓,也是C語言的重難點之一。
然而,很少有教程能把指針講的初學者能聽懂,還不會引起歧義。
本文章會嘗試做到這一點,如有錯誤,請指出。

Part 1:地址和&

我們先拋開指針不談,來講一個小故事:

一天,小L準備去找小S玩。但是小L不知道小S的家住在哪裡,正當他着急的時候,他看到了一個路牌,上面寫着:小S的家在神仙小區403

哦,真的是要素過多。為什麼這麼說?

  1. 小L和小S:我們可以看做是兩個變量/常量。
  2. 小S的家:這裏可以看做是變量/常量小S的地址。
    我們要搞清楚,每個變量/常量都和我們一樣:我們每個人都有自己的家,正如變量也有自己的地址。通俗的理解,地址是給變量/常量來存放值的地點
  3. 路牌:注意注意注意!這裏就指出了變量/常量小S的地址:神仙小區403
    事實上,我們等會會講,輸出一個變量的地址其實是個16進制的数字。

搞懂了上面,我們再來聊聊&
&這個符號我們一個不陌生,你最初用到應該是在:scanf("%d",&a)裡邊。
&叫做取址符,用來獲取一個變量/常量的地址。
那麼我們為什麼要在scanf裡邊用&,不在printf裡邊用呢?
一開始我也很疑惑,後來我看到了這個例子:
你是一個新生,你要進教室。
但是你並不知道教室在哪裡,這個時候你需要教室的地址。
下課了,你要出教室。
由於你已經在教室里了,你就不需要獲取教室的地址就可以出去了。

Part 2:一定要記住的東西

一定要記住:指針就是個變量!
重要的事情說三次:
指針就是個變量!他儲存的是地址!他自己也有地址!
指針就是個變量!他儲存的是地址!他自己也有地址!
指針就是個變量!他儲存的是地址!他自己也有地址!

為什麼這麼說?我們從指針的定義開始:

指針的定義方法:<類型名+*> [名稱]
也就是說,指針的定義大概是這樣的:

int* ip;            //類型是int*,名稱是ip
float* fp;          //類型是float*,名稱是fp
double* dp;         //類型是double*,名稱是dp

有的書上會這麼寫:

int *ip;
float *fp;
double *dp;

這麼寫當然沒問題,但是對於初學者來說,有兩個問題:

  1. 有的初學者會把*p當做是指針名
  2. 有的初學者會把定義時出現的*p取值時出現的*p弄混

指針他有沒有值?有!我們會在下一節給他賦值。
既然他的定義方式和變量一樣,他也有值,他為什麼不是變量呢?

Part 3:與指針相關的幾個符號

與指針相關的符號有兩個,一個是&,一個是*
先來聊聊&
&我們上面講過,他是來取地址的。舉個例子:

#include <stdio.h>
int main(){
    int a = 10;
    float b = 10.3;
    printf("%p,%p",&a,&b);
}

%p用來輸出地址,當然,你也可以寫成%d或者%x。先不管這個,我們來看看他會輸出什麼:

那麼也就是說,變量ab的地址是000000000062FE1C000000000062FE18
那麼我們怎麼把這個地址給指針呢?很簡單:p = &a;,舉個例子:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("a的地址:%p\n",&a);
    printf("指針p自身的地址:%p\n",&p);
    printf("指針p指向的地址:%p",p);
}

得到輸出:

a的地址:000000000062FE1C
指針p自身的地址:000000000062FE10
指針p指向的地址:000000000062FE1C

你發現了嗎?如果我們有p = &a;,我們發現:直接輸出p會輸出a的地址,輸出&p會輸出p的地址(這就是為什麼我一再強調p是個變量,他有自己的地址,正如路牌上有地址,路牌自身也有個地址一樣)。

請注意!如果你的指針為int*,那麼你只能指向int類型;如果是double*類型,只能指向double類型,以此類推

當然,void*類型的指針可以轉化為任何一種不同的指針類型(如int*,double*等等)

那麼,我們來聊聊第二個符號*
*有兩個用法。第一個在定義指針時用到,第二個則是取值,什麼意思?看下面這個例子:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("a的地址:%p\n",&a);
    printf("指針p自身的地址:%p\n",&p);
    printf("指針p指向的地址:%p\n",p);
    printf("指針p指向的地址的值:%d",*p);
}

得到輸出:

a的地址:000000000062FE1C
指針p自身的地址:000000000062FE10
指針p指向的地址:000000000062FE1C
指針p指向的地址的值:10

哈,我們得到了a的值!
也就是說,當我們有p = &a,我們可以用*p得到a的值。
那能不能操作呢?當然可以。
我們可以把*p當做a的值,那麼,我們嘗試如下代碼:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("指針p指向的地址的值:%d\n",*p);
    *p = 13;
    printf("指針p指向的地址的值:%d\n",*p);
    *p += 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p -= 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p *= 9;
    printf("指針p指向的地址的值:%d\n",*p);
    *p /= 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p %= 3;
    printf("指針p指向的地址的值:%d\n",*p);
}

得到輸出:

指針p指向的地址的值:10
指針p指向的地址的值:13
指針p指向的地址的值:16
指針p指向的地址的值:13
指針p指向的地址的值:117
指針p指向的地址的值:39
指針p指向的地址的值:0

棒極了!我們可以用指針來操作變量了。
那麼,我們要這個干什麼用呢?請看下一節:實現交換函數

Part 4:交換函數

交換函數是指針必學的一個東西。一般的交換我們會這麼寫:

t = a;
a = b;
b = t;

那麼我們把它塞到函數裡邊:

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

好,我們滿懷信心的調用他:

#include <stdio.h>
void swap(int a,int b){
      int t;
      t = a;
      a = b;
      b = t;
}
int main(){
      int x = 5,y = 10;
      printf("x=%d,y=%d\n",x,y);
      swap(x,y);
      printf("x=%d,y=%d",x,y);
}

於是乎,你得到了這個輸出:

x=5,y=10
x=5,y=10

啊啊啊啊啊啊啊啊,為什麼不行!!!
問題就在你的swap函數,我們來看看他們做了些啥:

swap(x,y);             --->把x賦值給a,把y賦值給b
///進入函數體
int t;                 --->定義t
t = a;                 --->t賦值為a
a = b;                 --->a賦值為b
b = t;                 --->b賦值為t

各位同學,函數體內有任何一點談到了x和y嗎?
所謂的交換,交換的到底是a和b,還是x和y?
我相信你這時候你恍然大悟了,我們一直在交換a和b,並沒有操作x和y

那麼我們怎麼操作?指針!
因為x和y在整個程序中的地址一定是不變的,那麼我們通過上一節的指針運算可以得到,我們能夠經過指針操作變量的值。
那麼,我們改進一下這個函數

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

我們再來試試,然後你就會得到報錯信息。

我想,你是這麼用的:swap(x,y)
問題就在這裏,我們看看swap需要怎樣的兩個變量?int*int*類型。
怎麼辦?我告訴你一個小秘密:
任何一個變量加上&,此時就相當於在原本的類型加上了*
什麼意思?也就是說:

int a;
&a ---> int*;
double d;
&d ---> double*;
int* p;
&p ---> int**;//這是個二級指針,也就是說指向指針的指針

那麼,我們要這麼做:swap(&a,&b),把傳入的參數int換為int*

再次嘗試,得到輸出:

x=5,y=10
x=10,y=5

累死了,總算是搞好了

Part 5:char*表示字符串

char*這個神奇的類型可以表示個字符串,舉個例子:


#include <stdio.h>

int main()
{
    char* str;
    str = "YOU AK IOI!";
    printf("%s",str);
}

請注意:輸入和輸出字符串的時候,都不能帶上*&

你可以用string.h中的函數來進行操作

Part 6:野指針

有些同學他會這麼寫:

int* p;
printf("%p",p);

哦千萬不要這麼做!
當你沒有讓p指向某個地方的時候,你還把他用了!這個時候就會產生野指針。
野指針的危害是什麼?
第一種是指向不可訪問(操作系統不允許訪問的敏感地址,譬如內核空間)的地址,結果是觸發段錯誤,這種算是最好的情況了;

第二種是指向一個可用的、而且沒什麼特別意義的空間(譬如我們曾經使用過但是已經不用的棧空間或堆空間),這時候程序運行不會出錯,也不會對當前程序造成損害,這種情況下會掩蓋你的程序錯誤,讓你以為程序沒問題,其實是有問題的;

第三種情況就是指向了一個可用的空間,而且這個空間其實在程序中正在被使用(譬如說是程序的一個變量x),那麼野指針的解引用就會剛好修改這個變量x的值,導致這個變量莫名其妙的被改變,程序出現離奇的錯誤。一般最終都會導致程序崩潰,或者數據被損害。這種危害是最大的。

不論如何,我們都不希望看到這些發生。
於是,養成好習慣:變量先賦值。

指針你可以這麼做:int *p =NULL;讓指針指向空

不論如何,他總算有個值了。

Part 7:總結

本文乾貨全部在這裏了:

  1. 指針是個變量,他的類型是數據類型+*,他的值是一個地址,他自身也有地址
  2. 指針有兩個專屬運算符:&*
  3. 指針可以操作變量,不能操作常量
  4. 指針可以表示字符串
  5. 請注意野指針的問題

本文沒有講到的:

  1. char[],char,const char的區別與聯繫
  2. const修飾指針會怎麼樣?
  3. void*指針的運用
  4. 多級指針的運用
  5. NULL到底是什麼
  6. malloc函數的運用

感謝觀看!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準