c語言數組與指針詳解(上)

完全搞懂c語言數組與指針

部分引用數組

  1. c語言指針怎麼理解 知乎
  2. 程序設計入門————c語言 (浙江大學翁愷)
  3. 《c primer plus》第六版

基礎知識

1. 指針基礎

基本符號
  • &:表明對變量取地址
  • int*或char*或者把這個星號緊貼着變量好比int *a = &b: 表明新建一個用來儲存地址的變量,這也表明&b這個值的類型是int*。
  • int *a, b 或 int* a, b 中只有a是int指針類型,b是int整型。函數

  • 關於電腦大小端的討論:大端是指是指數據的高字節,保存在內存的低地址中,而數據的低字節,保存在內存的高地址中。小端是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內在的低地址中。例以下圖:
    測試

  • 假設 int b=4; int *a = &b 則*a=4: 由於*a表明a變量中的地址所指的值。重複一下對比:&b是指針類型,值是地址;*b是實際指針b所指的變量的值。
  • 若是打印地址則用%p,以16進制顯示指針的值,而不是用%x,如: printf("%p\n", &i)
  • 32位和64位下指針的長處不一樣,32位下爲4個字節,和int同樣,64位下8個字節。設計

2. 數組基礎

  • 數組特色:
    • 數組大小一旦定義不可改變
    • 全部的元素具備相同的數據類型
    • 數組中的元素在內存中是連續依次排列的
    • 數組的集成初始化:int a[] = {1,2,3,4,25,6,5,4}; 即讓編譯器本身來數元素的數量
    • 若是這樣賦值:int a[3] = {2}; 則結果是這個數組有三個元素,a[0]=2,a[1]=0,a[2]=0;編譯器自動補全後面的數字爲0;
    • 集成初始化的定位:int a[6] = {[1] = 2, [3] = 3, 6}; 結果是 a[0]=0, a[1]=2, a[2]=0, a[3]=3, a[4]=6, a[5]=0; 適合初始數據稀疏的數組。
    • 若是想讓定義的數組變成只讀,即不可修改的類型,則能夠在最前面加上一個const。如:const int a[2] = {2, 3, 4}; 固然此條也適用於二維數組。
    • 數組只有在最開始即定義初始化的時候能夠集成賦值,下列賦值方法錯誤:int a[3] = {}; a[3] = {1,2,3}; 這時會錯誤,由於這是一個單個元素賦值的方法,何況a[3]已經超出了範圍。
    • 求數組的大小,穩定的方法是 sizeof(a)/sizeof(a[0]) ; 就算修改初始數組a中的數據,也不用修改遍歷時的代碼;
    • 數組做爲函數參數時,每每必須再用另外一參數來傳入數組的大小。
      • 不能在[]中給出數組的大小
      • 不能在函數中再利用sizeof計算數組的元素的個數
    • 定義數組a, b:int a[10]; b=[]; 則不能直接用b=a來給數組b賦值。
    • 對於數組a,&a=a=&a[0]
  • 二維數組:
    • int a[2][3] 至關於一個2行3列的矩陣
    • int a[0][0] 表示第一行第一列,意味着下標一樣也是從0開始
    • 二維數組的遍歷須要嵌套for循環
    • a[i][j]表示第i行第j列的元素,a[i,j]是一個表達式,至關於a[j],沒有意義,會報錯。
    • 二維數組初始化的時候列數能夠省略,行數能夠由編譯器來數。例如:inta[][5] = {{0,1,2,3,4},{2,3,4,5,6}};
    • 初始化二維數組的兩種方法:部分初始化則將剩下的那部分賦值爲0
      • int a[2][3] = {{5, 6},{7, 8}}; 則a[0][0]=5, a[0][1]=6, a[0][2]=0, a[1][0]=7, a[1][1]=8, a[1][2]=0;
      • int a[2][3] = {5, 6, 7, 8}; 則a[0][0]=5, a[0][1]=6, a[0][2]=7, a[1][0]=8, a[1][1]=0, a[1][2]=0;
    • 三位數組理解方法:好比int box[10][20][30]; 則能夠理解成由10個二維數組(每一個是20行30列)堆疊起來,這20個數組元素中的每一個元素是內含30個元素的數組。

經過程序加深理解一些概念

1. 數組的名字就至關於這個數組第一個元素的內存地址:

#include <stdio.h> 

int main(){
    int a[10]={1,2,3,4,5,6,7,8,9,10};  //定義一個整型數組,這裏a實質上是一個指向數組中第一個數據a[0]的指針
    int *p=a;  

    printf("%d\n",*p);
    printf("%d",*(p+1));    
    
    return 0;
}

返回結果爲:
1
2指針

2. 利用指針對數組進行初始化

#include <stdio.h>

int main(){
    int d[10];
    int *e;
    
    e=&d[0]; //e保存了數組d的第一個數據的地址
    
    for (int i=0; i<10; i++){
        *e = i; //把該地址中的數據依次賦值0,1,2,3,4,5,6,7,8,9 
        e++; //地址累加一次,也就是數組中下一個數據的地址
    } 
    
    for (int i=0; i<10; i++){  
        printf("%d\n", d[i]);  //打印數組d中的全部元素 
    } 
    return 0;
    
}

3. 數組做爲函數參數時,每每必須再用另外一參數來傳入數組的大小。

  • 不能在[]中給出數組的大小
  • 不能在函數中再利用sizeof計算數組的元素的個數
  • 聲明數組形參時,下列方法等價:(函數原型能夠省略參數名)切記:此爲函數聲明時用法,不可直接引用於函數定義
    • int sum(int *a, int len);
    • int sum(int *, int);
    • int sum(int ar[], int n);
    • int sum(int [], int);
  • 函數定義中不能省略參數名,如下兩種方法可行且等價:
    • int sum(int *a, int len)
      {
      //省略其餘代碼
      }
    • int sum(int a[], int len)
      {
      //省略其餘代碼
      }
#include <stdio.h>
//此方法爲最簡單,最基礎的數組遍歷 
int search(int key, int a[], int len)

int main()
{
    int a[]= {1,3,5,2,9,4,12,23,15,32};
    int r = search (12, a, sizeof(a)/sizeof(a[0]));   //傳入參數的時候在main函數中計算好函數的個數傳入到search函數中;另外,此處a傳入的時a[0]元素的地址。
    printf("%d\n", r);
    return 0;
 } 
 
int search(int key, int a[], int len)   //len變量必需要加,由於在search函數中沒法用sizeof函數計算數組的大小
{
    int ret = -1;
    int i;
    for (i=0; i<len; i++){
        if (key == a[i]){
            ret = i;
            break;
        }
    }
    return ret;
}

4. 二維數組中數組名的含義

#include<stdio.h>

int main(){
    int a[2][3]={{1,2,3},{4,5,6}};
    
    printf("%p\n",a);   //輸出指針a數據,也就是指針a[0]的地址 
    printf("%p\n",a+1);   //輸出a+1的數據 ,也就是a[1]的地址
    printf("%p\n",&a[0]);
    printf("%p\n",&a[1]);     //驗證上述
    printf("%p\n",(*a)+1);  //輸出的是a[0][1]的地址
    printf("%p\n",&a[0][1]);   //驗證
    printf("%d\n",*(a[0]));  //輸出的是a[0]a[0]的值 
    printf("%d\n",*(*(a+1)+1));  //輸出的是a[1][1]的值 
}

注意:a是一個2行3列的數值,a+1表示的a[1]值所在的地址,a[1]的值又表明a[1][0]的值所在的地址code

5. int與char指針類型的區別

int i=2; int *a=&i 和 char j='m'; char *b=&j 區別在於:int佔據四個字節,a中雖然記載的i的第一個字節的地址,可是因爲a是int類型的指針,*a讀取的時候自動再日後讀3個字節;而b是char類型的指針,則只讀取當前記錄的這一個字節,天然不能用指針b來保存int i的值。blog

#include <stdio.h>

int main(){
    int i = 2;
    int j = 's';
    
    int *a = &i;
    char *b = &i;
    
    int *m = &j;
    char *n = &j;
    
    printf("%d\n", *a);
    printf("%d\n", *b);  //會產生warning 
    /* 解釋爲何前兩行輸出爲何同樣:
     * 在存儲中,2做爲int存儲爲 00000010 00000000 00000000 00000000 四個字節,用此種表示方法是由於個人電腦是個小端電腦(Little-endian)。詳述見下條。
     * a 和 b 所記錄的都是四個字節中第一個字節的地址,*a讀取到的是4個完整的字節,而*b讀取到的是第一個字節 00000010,因爲巧合,兩者所表明的都是數字1。 
     */

    printf("%c\n", *m);  //會產生warning
    printf("%c\n", *n);
    
    return 0; 
}

6. 指針內存位置理解深刻剖析(必定在本身的電腦上運行試下)

#include <stdio.h>

int main()
{
    int a = 1, b = 2;
    char c = 'c', d = 'd';
    int *m, *n;
    char *j, *k;

    m = &a;
    n = &b;
    j = &c;
    k = &d;
    
    printf("int變量在內存中的存儲狀況");
    printf("a  %p\n", m);
    printf("b  %p\n", n); 
    //因爲棧自頂向下的存儲方法,內存位置上a與b兩個元素是緊鄰着的,a位置高,b低,相差四個字節。 
    printf("\n");
    printf("對int指針變量+1會獲得什麼結果,實際改變幾個字節?\n");
    printf("&b+1  %p\n", n+1);
    printf("&a-&b %d\n", m-n);
    //上兩個語句測試獲得結果,a與b的地址m,n相差並非4而是1。由於相差的是存儲單元數而不是字節數 
    printf("\n");
    printf("char指針變量什麼狀況,原本就相差1個字節?\n");
    printf("c  %p\n", j);
    printf("d  %p\n", k);
    //因爲char變量只佔1個字節,因此這兩個變量位置地址相差爲1 
    printf("\n");
    printf("\n");
    
    printf("int型指針變量佔據幾個字節?\n");
    printf("&m  %p\n", &m);
    printf("&n  %p\n", &n);
    printf("char型指針變量佔據幾個字節?\n");
    printf("&j  %p\n", &j);
    printf("&k  %p\n", &k); 
    //能夠獲得,在64位系統上,像m,n這種指針自己的存儲都是佔據8個字節,不論是char類型仍是int類型。 
    
    return 0;
 }

一句話歸納指針的加減:指針加1,指針的值遞增它所指向類型的大小(以字節位單位)
dates + 2 == &dates[2]
*(dates + 2) == dates[2]內存

相關文章
相關標籤/搜索