首頁 > 軟體

C語言零基礎講解指標和陣列

2022-04-18 22:02:07

一、指標和陣列分析-上

1.陣列的本質

  • 陣列是一段連續的記憶體空間
  • 陣列的空間大小為 sizeof(array_type) * array_size
  • 陣列名可看做指向陣列第一個元素的常數指標

下面看一段程式碼:

#include <stdio.h>
 
int main()
{
    int a[5] = {0};
    int* p = NULL;
    
    printf("a = 0x%Xn", (unsigned int)(a));
    printf("a + 1 = 0x%Xn", (unsigned int)(a + 1));
    
    printf("p = 0x%Xn", (unsigned int)(p));
    printf("p + 1 = 0x%Xn", (unsigned int)(p + 1));
    
    return 0;
}

輸出結果如下:

通過這段程式碼說明指標運算是合法的。

2.指標的運算

指標是一種特殊的變數,與整數的運算規則為

p + n; <-->(unsigned int)p + n*sizeof(*p);

結論∶

當指標 p 指向一個同型別的陣列的元素時:p+1 將指向當前元素的下一個元素;p-1 將指向當前元素的上一個元素。

  • 指標之間只支援減法運算
  • 參與減法運算的指標型別必須相同

p1- p2; <--> ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);

注意:

  • 只有當兩個指標指向同一個陣列中的元素時,指標相減才有意義,其意義為指標所指元素的下標差
  • 當兩個指標指向的元素不在同一個陣列中時,結果未定義

下面看一段簡單的指標運算程式碼:

#include <stdio.h>
 
int main()
{
    char s1[] = {'H', 'e', 'l', 'l', 'o'};
    int i = 0;
    char s2[] = {'W', 'o', 'r', 'l', 'd'};
    char* p0 = s1;
    char* p1 = &s1[3];
    char* p2 = s2;
    int* p = &i;
	
    printf("%dn", p0 - p1);
    //printf("%dn", p0 + p2);  //ERROR
    printf("%dn", p0 - p2);
    //printf("%dn", p0 - p);   //ERROR
    //printf("%dn", p0 * p2);  //ERROR
    //printf("%dn", p0 / p2);  //ERROR
	
    return 0;
}

輸出結果如下:

注意兩個指標指向不同的陣列,雖然它們兩相減符合語法,但是最後的結果肯定沒有意義。

再來看一段指標運算的應用程式碼:

#include <stdio.h>
 
#define DIM(a) (sizeof(a) / sizeof(*a))
 
int main()
{
    char s[] = {'H', 'e', 'l', 'l', 'o'};
    char* pBegin = s;
    char* pEnd = s + DIM(s); // Key point
    char* p = NULL;
    
    printf("pBegin = %pn", pBegin);
    printf("pEnd = %pn", pEnd);
    
    printf("Size: %dn", pEnd - pBegin);
	
    for(p=pBegin; p<pEnd; p++)
    {
        printf("%c", *p);
    }
    
    printf("n");
   
    return 0;
}

輸出結果如下:

注意以下幾點:

  • 陣列大小的計算方法:#define DIM(a) (sizeof(a) / sizeof(*a))
  • char* pEnd = s + DIM(s); // Key point ==> pEnd 指向 'o' 後面的地址 ==> 這在 C 語言中是一個擦邊球的邊界位置,也是一個技巧,在這個邊界位置可以認為該指標是合法的,可以和其他指標進行比較運算和減法運算等,在 C++ 標準庫裡面也合法

3.指標的比較

  • 指標也可以進行關係運算 (<,<=,>,>=)
  • 指標關係運算的前提是同時指向同一個陣列中的元素
  • 任意兩個指標之間的比較運算(==,!=)無限制
  • 參與比較運算的指標型別必須相同

4.小結

  • 陣列宣告時編譯器自動分配一片連續的記憶體空間
  • 指標宣告時只分配了用於容納地址值的 4 位元組空間
  • 指標和整數可以進行運算,其結果為指標
  • 指標之間只支援減法運算,其結果為陣列元素下標差
  • 指標之間支援比較運算,其型別必須相同

二、指標與陣列分析-下 

1.陣列的存取方式

以下標的形式存取陣列中的元素

以指標的形式存取陣列中的元素

2.下標形式 VS 指標形式

  • 指標以固定增量在陣列中移動時,效率高於下標形式
  • 指標增量為1且硬體具有硬體增量模型時,效率更高
  • 下標形式與指標形式的轉換

a[n] <--> *(a +n) <--> *(n + a) <--> n[a]

注意:現代編譯器的生成程式碼優化率已大大提高,在固定增量時,下標形式的效率已經和指標形式相當;但從可讀性和程式碼維護的角度來看,下標形式更優。

下面看一個陣列的存取方式程式碼:

#include <stdio.h>
 
int main()
{
    int a[5] = {0};
    int* p = a;
    int i = 0;
    
    for(i=0; i<5; i++)
    {
        p[i] = i + 1;
    }
    
    for(i=0; i<5; i++)
    {
        printf("a[%d] = %dn", i, *(a + i));
    }
    
    printf("n");
    
    for(i=0; i<5; i++)
    {
        i[a] = i + 10;
    }
    
    for(i=0; i<5; i++)
    {
        printf("p[%d] = %dn", i, p[i]);
    }
    
    return 0;
}

輸出結果如下:

注意這個奇怪的寫法:i[a] = i + 10; ==> a[i] = i + 10;

下面通過一個範例,說明陣列和指標的不同:

ext.c:

int a[] = {1, 2, 3, 4, 5};

test.c:

#include <stdio.h>
 
int main()
{
    extern int a[];
    
    printf("&a = %pn", &a);
    printf("a = %pn", a);
    printf("*a = %dn", *a);
 
    return 0;
}

輸出結果如下:

下面來驗證一下陣列名究竟是不是指標,將 test.c 改成:

#include <stdio.h>
 
int main()
{
    extern int* a;
    
    printf("&a = %pn", &a);
    printf("a = %pn", a);
    printf("*a = %dn", *a);
 
    return 0;
}

輸出結果如下:

ext.c 中 a[ ] 的地址為 0x804a014,test.c 中的extern int* a; 只是申明識別符號 a,編譯器會認為在這之前就已經給了地址值,就是 0x804a014,所以printf("a = %pn", a); 就是列印0x804a014 地址中的 4 個位元組的數,也就是 a[ ] 陣列中的第一個元素 1,所以列印 0x1,*a 就是取 0x1 地址中的數,但是這個地址值是留給作業系統的,不可存取,存取就會產生段錯誤。

3.a 和 &a 的區別

  • a 為陣列首元素的地址
  • &a 為整個陣列的地址
  • a 和 &a 的區別在於指標運算

這個就能看出 a + 1 和 &a + 1 的不同,a + 1 增加的步長是一個元素的大小,&a + 1 則是增加的步長是整個陣列的大小。

下面看一個指標運算的經典問題:

#include <stdio.h>
 
int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1); 
    int* p2 = (int*)((int)a + 1);
    int* p3 = (int*)(a + 1);
    
    printf("%d, %d, %dn", p1[-1], p2[0], p3[1]);
    
    return 0;
}

輸出結果如下:

p1[-1] 就是 *(p1 - 1),由於 p1 指向的元素是 5 後面的位置,減 1 之後就指向了 5;p2 的地址是 0x804a015(注意 linux 系統為小端系統),*p2 就是 0x02000000,對應十進位制的值就是 33554432;p3 的地址為 &a[1],所以 p3[1] 就是 3 了。

4.陣列引數

陣列作為函數引數時,編譯器將其編譯成對應的指標

結論:一般情況下,當定義的函數中有陣列引數時,需要定義另一個引數來標示陣列的大小。

下面看一段程式碼:

#include <stdio.h>
 
void func1(char a[5])
{
    printf("In func1: sizeof(a) = %dn", sizeof(a));
    
    *a = 'a';
    
    a = NULL;
}
 
void func2(char b[])
{
    printf("In func2: sizeof(b) = %dn", sizeof(b));
    
    *b = 'b';
    
    b = NULL;
}
 
int main()
{
    char array[10] = {0};
    
    func1(array);
    
    printf("array[0] = %cn", array[0]);
    
    func2(array);
    
    printf("array[0] = %cn", array[0]);
    
    return 0;
}

輸出結果如下:

這段程式碼就說明陣列引數退化成指標,因為 sizeof(a) 為 4 個位元組,而不是 5 個位元組。

5.小結

陣列名和指標僅使用方式相同

  • 陣列名的本質不是指標
  • 指標的本質不是陣列

陣列名並不是陣列的地址,而是陣列首元素的地址

函數的陣列引數退化為指標

到此這篇關於C語言零基礎講解指標和陣列的文章就介紹到這了,更多相關C語言 指標和陣列內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com