天天看點

考試C語言,指針不會?看這一篇部落格就夠了C語言指針

文章目錄

最近在看一本關于【C語言】的書,普通文法沒什麼,就是指針這個地方确實有點難,現在寫一點關于我對指針的了解,當然肯定會有了解不到位的地方,希望多多擔待。
1. 預設你有其他語言基礎比如Java,Python,C++等語言基礎
2. 預設你對計算機有所了解
3. 預設你想學習,而不是當個熱鬧看      

那麼到底什麼才是指針呢?

指針就是一串代表記憶體位址的數字,通常使用十六進制來表示。

正因為我們可以直接對記憶體位址進行操作,是以我們才說C語言功能強大。

說起指針,以我目前所學,我認為暫時可以分為以下幾種類型:

  1. 普通指針——指向一個變量的指針
  2. 數組指針——能對數組進行操作的指針
  3. 函數指針——指向函數的指針

pass:為什麼數組指針不說是指向數組的指針。

這個原因會在數組指針的地方,對數組進行分析,讓你了解數組的形成。這裡就不多做贅述了

什麼是普通指針,普通指針就是指向基本資料類型的指針,比如int 、float等。

我認為實戰是最好的了解方式,是以會有代碼以及注釋詳細了解,不過在你看代碼之前,你應該知道這些東西:

  1. 如何定義一個指針
  2. 如何給指針指派
  3. 給指針指派後,怎麼使用原變量的值

就和定義一個普通變量一樣:

類型 *變量名

指針變量接收的是變量的記憶體位址 在C語言中,通過符号**&**來取出變量的記憶體位址

指派也是同樣的

那麼你知道了這些知識後,就看代碼:

#include<stdio.h>

int main()
{
    int num = 10;
    // 建立一個int類型的變量,并指派為10
    int* pnum;
    // 建立一個int類型的指針,你還能這樣寫 int *pnum
    pnum = &num;
    // &num是num在記憶體空間的記憶體位址
    // 這句代碼是将num的記憶體位址指派給pnum
    printf("num的值為:%d\n&num的值為:%p\n*pnum的值為:%d\npnum的值為:%p",num,&num,*pnum,pnum);
    // 将各個值都列印出來看看效果
    return 0;
}      

運作結果是:

num的值為:10
&num的值為:0xff8effe0
*pnum的值為:10
pnum的值為:0xff8effe0      

看了這段代碼,是不是對指針有了更深刻的了解了呢?

如果你想學好,就暫停你的進度,思考一下:

  • pnum是什麼,他開辟的記憶體空間是多大
  • *pnum是什麼,有什麼用
  • &pnum是什麼,他的作用是什麼

思考之後來看看吧~~

那麼我們看着代碼和運作結果可以總結出以下内容:

接下來,你就想想你,你的身份證号,你的身份證,國家資訊系統

  1. num是一個變量,這個變量可以對10進行操作
    • 将10當成你,num是你的名字
    • 聲明一個變量後,記憶體空間會為變量開辟一個記憶體空間以及記憶體位址
    • 而你出生後也會有一個身份證号
  1. &num是變量的記憶體位址,這裡&num雖然是記憶體位址,但是不說&num是num的指針,因為指針是一個變量,俗稱指針變量
    • num在記憶體的記憶體位址相當于你的身份證号
    • 你的身份證号隻是一串數組,抽象存在
  1. pnun是一個指針變量,他的值是是&num,也就是一個普通變量的記憶體位址
    • 把它當成你的身份證
    • 你的身份賬号在上面,就可以通過身份證号(指針)進行操作(買票,辦卡等)
  1. *pnum是通過記憶體位址擷取到該記憶體位址存儲的值
    • 就是通過你的身份證在國家系統找到了你
    • 也可以對你進行操作(比如征信,車票等)

試想,你如果聲明一個變量,并将該指針變量的記憶體位址給該指針,也就是讓指針變量存儲的是指針的記憶體空間,會有什麼事情發生?

思考:我們的指針變量是一個存儲記憶體位址的指針,但他同樣也還是一個變量,是以也會在記憶體中有自己的記憶體位址,而剛好指針存儲的就是變量!!等等等等,一拍即合,我們就把指針的記憶體賦給指針,看看會發生什麼!

上代碼:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int * p;
    p = &p;
    printf("p=%p\n&p=%p\n*p=%p",p,&p,*p);
    return 0;
}
      

編譯結果:

p=0xfff75af4
&p=0xfff75afc
*p=0x0      

哦豁,結果清晰可見,我們也是以産生了一些疑惑,為什麼會出現兩個記憶體位址呢?

找到了原因!

昨天是使用手機敲得C代碼,因為那時候還在火車上,沒法拿電腦

今天使用了電腦,編譯器是gcc,編輯器是vs code

重新編譯了一下

運作結果如下:

p=0061FECC
&p=0061FECC
*p=0061FECC      

欸,這就很舒服了,記憶體位址是一樣的,是以雖然安卓有C語言的編譯器,但還是使用電腦吧。

今天使用了電腦,但是我們的代碼卻是不妥的,因為這裡涉及到了一個二級指針

我們也收到了一條警告

assignment to 'int *' from incompatible pointer type 'int **'
翻譯:從不相容的指針類型'int **'指派給'int *'        

參考大佬的話,p是一級指針,&p是二級指針,那麼問題來了,什麼是一級指針,什麼是二級指針

一級指針就是普通變量的指針

二級指針就是指針變量的指針,也就是指針的指針

就像這樣:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int test = 10;  // 普通變量
    int * p  = &test;   // 指向普通變量的指針,也就是一級指針
    int ** pp = &p;     // 指向指針的指針,也就是二級變量
    printf("test的記憶體位址是%p\np的記憶體位址是%p\npp的記憶體位址是%p", &test, &p, &pp);
    return 0;
}      

運作結果:

test的記憶體位址是0061FECC
p的記憶體位址是0061FEC8
pp的記憶體位址是0061FEC4      

你就可以這麼了解:幾級指針,就嵌套了幾個位址

他和常量指針長得很像,但是他倆卻差了很多,首先,指針常量是指針常量,而常量指針是常量指針。

與指針變量相差別,就和常量與變量的差別一樣。常量是不可改變的,指針常量也是不可改變的,但是指針常量指向的普通變量卻不是不可改變的。

也就是說,指針常量是一個常量,而我們在定義普通常量時通常是使用的const或者#define,而定義指針常量是使用

#include <stdio.h>

int main(int argc, char const *argv[])
{
    /* code */
    int num = 10;   // 普通變量
    int *const p = &num;    // 指針常量
    // 錯誤使用
    int num2 = 11;
    p = &num2;      // 因為是常量,無法再進行指派
    return 0;
}
      

這個指針是常量,無法再進行指派運算。

這是一個指針,隻不過他指向的是一個常量。

我們無法通過指針操作常量,但是可以對指針重新指派。

#include <stdio.h>

int main(int argc, char const *argv[])
{
    const int num = 10; // 常量
    const int *p = &num;  // 常量指針
    // 錯誤使用
    *p = 20;
    // 正确使用
    int num2 = 20;
    p = &num2;
    return 0;
}
      

數組指針是:

指向數組的指針,它本質上還是一個指針,類比普通指針

指針數組是:

一個存放指針的數組,本質上是數組,就如經常說的字元數組,整型數組一樣

數組本質上隻是編譯器在記憶體空間上開辟的一連串的記憶體

而代表數組的變量其實隻是這一連串記憶體空間的第一個元素的記憶體位址。

是以當你給編譯器看一個數組時,他并不是像人一樣能看到這個數組的全貌,他隻能看到這個數組的第一個元素,并且知道這個元素的記憶體位址

看看這串代碼:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a[] = {1, 2, 3, 4};     // 一個數組
    printf("a的記憶體位址%p\na[0]的記憶體位址%p", &a, &a[0]);
    return 0;
}
      

他的運作結果為:

a的記憶體位址0061FEC0
a[0]的記憶體位址0061FEC0      

相信你對數組有了更深的了解。

那麼為什麼定義數組需要強制類型呢?

拿int類型來說,int類型占用4個位元組

在人們眼中的元素位置的+1

相當于編譯器眼裡的+4(4是類型占用的位元組數)

是以才能精準的拿到某個元素

數組下标是怎麼定義的呢?為什麼下标從0開始

數組的下标也是這麼來的,通過對記憶體位址的相加減來擷取

因為編譯器隻記得數組第一個元素的記憶體位址

而下标就是讓第一個元素的記憶體+i(i是下标)

通過下标擷取元素的過程可以類比為:

  • arr[1] => *(&arr +1)
先讓記憶體位址加下标,再通過指針擷取到元素

數組指針就是指向數組第一個元素的指針,相信認真看了2.1和2.2的你能夠很快了解

定義一個數組指針

int a[] = {1, 3, 5, 7};     // 一個數組
int (*p)[4] = &a;   // 定義一個指針,指向數組的頭元素      

通過指針通路第二個數組元素:

printf("通路數組的第二個元素:%d", *(*p+1));      

完整代碼:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a[] = {1, 3, 5, 7};     // 一個數組
    int (*p)[4] = &a;   // 定義一個指針,指向數組的頭元素
    printf("a的記憶體位址%p\na[0]的記憶體位址%p\n", &a, &a[0]);
    printf("通路數組的第二個元素:%d", *(*p+1));
    return 0;
}      
a的記憶體位址0061FEBC
a[0]的記憶體位址0061FEBC
通路數組的第二個元素:3      

指針數組,顧名思義,他是個數組,就如經常說的字元數組,整型數組一樣,隻不過指針數組的定義方法和存儲對象也有億點點不一樣。

定義一個指針數組(以整型為例)

int *pArr[10];  // 定義一個指針數組      

要注意與數組指針的定義差別開

數組指針的定義:

int (*arrP)[10];      

一定要注意這個括号,這涉及到了*符号的運算優先級,一但寫錯,就是不同的兩個東西了。

簡單使用:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int *arr[10];       // 定義一個指針數組
    int arrSize = 10;   // 指針數組的長度
    for (int i = 0; i < arrSize; i++)
    {
        arr[i] = &i;    // 将臨時位址放在指針數組裡
        printf("數組的元素:%p\n數組元素所指向的元素%d\n", *arr[i]);
        /* code */
    }
    /* code */
    return 0;
}      

輸出結果:

數組的元素:0061FEA0
數組元素所指向的元素0
數組的元素:0061FEA0
數組元素所指向的元素1
數組的元素:0061FEA0
數組元素所指向的元素2
數組的元素:0061FEA0
數組元素所指向的元素3
數組的元素:0061FEA0
數組元素所指向的元素4
數組的元素:0061FEA0
數組元素所指向的元素5
數組的元素:0061FEA0
數組元素所指向的元素6
數組的元素:0061FEA0
數組元素所指向的元素7
數組的元素:0061FEA0
數組元素所指向的元素8
數組的元素:0061FEA0
數組元素所指向的元素9      

因為i是臨時變量,是以在每次循環之後都會銷毀,下次使用再次開辟,是以記憶體位址是一樣的。

在我們定義函數的時候,編譯器也會在記憶體空間給函數開辟一個記憶體,而該記憶體的首位址就是函數的記憶體位址,而函數指針就是指向該記憶體位址的。

衆所周知,C語言是面向過程的語言,或者稱函數式程式設計。

而在C語言中,函數也确實起了很大的作用,在C語言的學習中,你見過最多的可能就是

main

函數,同時也是你第一個見得函數。

我們來看看這個main函數

int main(){return 0;}      

我們把他濃縮成一行,比較好瞅

  • int是傳回類型,每個函數都要有這個,不傳回東西的函數的傳回值類型為void
  • main是函數名,固定的,無法重載
  • 括号裡面是參數清單,一般是預設沒有,也可以傳遞

    void

    或者

    int argc, char const *argv[]

  • {}大括号裡面是函數的具體實作代碼,比如說

    printf("Hello World!");

  • return 是函數結束的關鍵字,傳回值為0表示程式正确運作,為其他表示有其他異常
切記main函數不要

void main(){},這個真的很重要

見名知意,這個東西也是一個指針,隻不過他指向的是一個函數,準确來說是函數在記憶體空間中開辟空間的頭位址。

定義也是有億點點麻煩,不過卻也不是不好了解。

定義:

int (*funP)(int num1, int num2);    // 定義一個函數,有兩個整型參數      

因為運算符優先級的存在,是以我們需要對變量名與*進行首先運算

使用:

#include <stdio.h>

/*
  定義一個兩數求和函數
  傳回兩個數的和的結果
*/
int sum(int num1, int num2)
{
    int ans = num1 + num2;
    return ans;
}

int main(int argc, char const *argv[])
{
    int (*funP)(int num1, int num2);    // 定義一個函數,有兩個整型參數
    funP = sum;         // 将函數sum的位址給funP
    int ans = funP(1, 2);   // 使用指針使用函數
    printf("%d", ans);
    return 0;
}
      

繼續閱讀