C語言關於數組與指針內容小結

數組的基本概念

什麼是數組:數組就是:數組是相同類型的元素的一個集合       類型說明符 數組名 [常量表達式];
其中,類型說明符是任一種基本數據類型或構造數據類型。數組名是用戶定義的數組標識符。方括號中的常量表達式表示數據元素的個數,也稱爲數組的長度。例如:
int a[10]; /* 說明整型數組a,有10個元素 */
float b[10], c[20]; /* 說明實型數組b,有10個元素,實型數組c,有20個元素 */
char ch[20]; /* 說明字符數組ch,有20個元素 */
數組應注意以下五點:
數組的類型實際上是指數組元素的取值類型。對於同一個數組,其所有元素的數據類型都是相同的。
數組名的書寫規則應符合標識符的書寫規定。
數組名不能與其它變量名相同。
方括號中常量表達式表示數組元素的個數,如a[5]表示數組a有5個元素。但是其下標從0開始計算。因此5個元素分別爲a[0], a[1], a[2], a[3], a[4]。
不能在方括號中用變量來表示元素的個數,但是可以是符號常數或常量表達式

數組的應用

1、一維數組的引用

數組元素是組成數組的基本單元。數組元素也是一種變量, 其標識方法爲數組名後跟一個下標。下標表示了元素在數組中的順序號。數組元素的一般形式爲:
    數組名[下標]
其中下標只能爲整型常量或整型表達式。如爲小數時,C編譯將自動取整。例如:
    a[5]
    a[i+j]
    a[i++]
都是合法的數組元素。

數組元素通常也稱爲下標變量。必須先定義數組,才能使用下標變量。在C語言中只能逐個地使用下標變量,而不能一次引用整個數組。例如,輸出有10個元素的數組必須使用循環語句逐個輸出各下標變量:
for(i=0; i<10; i++)
printf("%d",a[i]);
而不能用一個語句輸出整個數組。因此,下面的寫法是錯誤的:
printf("%d",a);

2、一維數組的初始化:

給數組賦值的方法除了用賦值語句對數組元素逐個賦值外, 還可採用初始化賦值和動態賦值的方法。

數組初始化賦值是指在數組定義時給數組元素賦予初值。數組初始化是在編譯階段進行的。這樣將減少運行時間,提高效率。初始化賦值的一般形式爲:
    類型說明符 數組名[常量表達式] = { 值, 值……值 };
其中在{ }中的各數據值即爲各元素的初值,各值之間用逗號間隔。例如:
  1. int a[10]={ 0,1,2,3,4,5,6,7,8,9 };
相當於
a[0]=0; a[1]=1 ... a[9]=9;

C語言對數組的初始化賦值還有以下幾點規定:
1) 可以只給部分元素賦初值。當{ }中值的個數少於元素個數時,只 給前面部分元素賦值。例如:
int a[10]={0,1,2,3,4};
表示只給a[0]~a[4]5個元素賦值,而後5個元素自動賦0值。
2) 只能給元素逐個賦值,不能給數組整體賦值。例如給十個元素全部賦1值,只能寫爲:
int a[10]={1,1,1,1,1,1,1,1,1,1};
而不能寫爲:
int a[10]=1;
3) 如給全部元素賦值,則在數組說明中,可以不給出數組元素的個數。例如:
int a[5]={1,2,3,4,5};
可寫爲:
int a[]={1,2,3,4,5};

多維數組(二維數組)

這裏主要討論二維數組:
二維數組定義的一般形式如下:
   類型標識符  數組名【常量表達式1】【常量表達式2】;
例如:
   int a[2][3];
   float b[3][10];
二維數組的初始化有兩種:
(1)分行初始化,如:
     static int a[2][3]={{1,2,3,},{4,5,6}};
 (2)統一初始化,如:
     static int a[2][3]={1,2,3,4,5,6};

指針的基本概念

在計算機中,所有的數據都是存放在內存中的,一般把內存中的一個字節稱爲一個內存單元,不同的數據類型所佔用的內存單元數不一樣,如int佔用4個字 節,char佔用1個字節。爲了正確地訪問這些內存單元,必須爲每個內存單元編上號。每個內存單元的編號是唯一的,根據編號可以準確地找到該內存單元。

內存單元的編號叫做地址(Address),也稱爲指針(Pointer)。 

內存單元的指針和內存單元的內容是兩個不同的概念。 可以用一個通俗的例子來說明它們之間的關係。我們用銀行卡到ATM機取款時,系統會根據我們的卡號去查找賬戶信息,包括存取款記錄、餘額等,信息正確、餘 額足夠的情況下才允許我們取款。在這裏,卡號就是賬戶信息的指針, 存取款記錄、餘額等就是賬戶信息的內容。對於一個內存單元來說,單元的地址(編號)即爲指針,其中存放的數據纔是該單元的內容。

在C語言中,允許用一個變量來存放指針,這種變量稱爲指針變量。因此,一個指針變量的值就是某個內存單元的地址或稱爲某內存單元的指針。

設有字符變量c,其內容爲 'K'(ASCII碼爲十進制數 75),c佔用了0X11A號內存單元(地址通常用十六進數表示)。設有指針變量p,內容爲 0X11A,這種情況我們稱爲p指向變量c,或說p是指向變量c的指針。


 
嚴格地說,一個指針是一個地址,是一個常量。而一個指針變量卻可以被賦予不同的指針值,是變量。但常把指針變量簡稱爲指針。爲了避免混淆,本教程約定:“指針”是指地址,是常量,“指針變量”是指取值爲地址的變量。定義指針的目的是爲了通過指針去訪問內存單元。

既然指針變量的值是一個地址,那麼這個地址不僅可以是變量的地址,也可以是其它數據結構的地址。在一個指針變量中存放一個數組或一個函數的首地址有何意義呢?

因爲數組或函數都是連續存放的。通過訪問指針變量取得了數組或函數的首地址,也就找到了該數組或函數。這樣一來,凡是出現數組,函數的地方都可以用一個指 針變量來表示,只要該指針變量中賦予數組或函數的首地址即可。這樣做,將會使程序的概念十分清楚,程序本身也精練、高效。

在C語言中,一種數據類型或數據結構往往都佔有一組連續的內存單元。用“地址”這個概念並不能很好地描述一種數據類型或數據結構,而“指針”雖然實際上也 是一個地址,但它卻是一個數據結構的首地址,它是“指向”一個數據結構的,因而概念更爲清楚,表示更爲明確。 這也是引入“指針”概念的一個重要原因。

一級指針的應用

所謂一級指針可以說是儲存變量地址的指針變量, 如 int a = 1; int *p = &a;

(1) & 取地址運算符
在以前我們所講的scanf語句裏面出現過&地址符,如
int a; 定義了一個整型變量,系統爲這個整型變量分配一個內存地址。
scanf(“%d”,&a); 把讀入的數據放在這個內存地址中。
printf(“a=%d”,a); 把這個變量的值(內存地址的值)輸出。
而如果改用指針,程序可以改爲這樣:
int a,*p=a;
scanf(“%d”,p);
printf(“a=%d”,a);
(2) * 取(指針)內容運算符
我們也可以通過一定的操作把指針中保存的地址所保存的內容提取出來。
如:int a,*p=a;
scanf(“%d”,p);
printf(“a=%d”,*p);
注意第一行 int *p;中的*號僅僅是爲了說明p變量是一個指針變量。
第三行的*p是指把指針變量p所保存的內存單元地址中所保存的那個值輸出。
如:輸入2,輸出a=2
需要注意的是,我們定義好一個指針變量後,原則上會跟普通變量相聯繫,如:
#include "stdio.h"
void main()
{
   int *p;
   int a=5;
   printf("%d %d\n",p,a);
   printf("%d %d",*p,&a);
}   


需要注意的是,雖然定義了p指針變量,但是並沒有把p=&a這樣的賦值,所以只能輸出
輸出: 1245068 5
       4394640 1245048
第一行第一個數系統分配給p的一個地址(整數),第二個是a變量的值;
第二行是p指針中保存那個內存中地址的值,此處也是系統隨機給的值;後一個是系統分給a變量的地址。
#include "stdio.h"
void main()
{
   int *p;
   int a=5;
   p=&a;
   printf("%d %d\n",p,a);
   printf("%d %d",*p,&a);
} 


輸出: 1245048 5
       5 1245048
第一行第一個數是指針變量保存的那個內存地址,即系統分配給a變量的內存地址,第二個數是a變量的值。
第二行第一個數是p變量所保存的那個內存地址的值,即a的值。後一個數輸出的是系統分配給a變量的內存地址。
即此時:p==&a  *p==a都是成立的。

 高級指針(二級指針)的應用

所謂二級指針可以說是儲存指針變量地址的指針變量, 如 int *q = &p;
關於二級指針的應用,用一個例子來說明就行了,是一個C語言查找函數,函數原型: void find1(char array[], char search, char *pa)
這個函數參數中的數組array是以\0值爲結束的字符串,要求在字符串array中查找與參數search給出的字符相同的字符。如果找到,通過第三個參數(pa)返回array字符串中首先碰到的字符的地址。如果沒有找到,則爲pa爲NULL。

依題意,實現代碼如下。
    void find1(char [] array, char search, char *pa)  
    {  
        for (int i = 0; *(array + i) != '\0'; ++i)  
        {  
            if (*(array + i) == search)  
            {          
                pa == array + i;    // pa得到某元素的地址  
                break;   
            }  
        }  
    }  

這個函數實現能實現所要求的功能嗎?

下面調用這個函數試試

    int main(int argc, char *argv[])  
    {  
        char str[] = "abcdefdfefde";    // 待查找的字符串  
        char a = 'd';    // 設置要查找的字符  
        char *p = NULL;    // 先置爲空  
        find1(str, a, p);    // 調用函數以實現查找操作  
        if (NULL == p)  
        {  
            cout << "Not found!" << endl;  
        }  
        else  
        {  
            cout << "found it!" << endl;  
        }          
           
        return 0;  
    }  


運行結果是Not found!        Why?!

分析:  先看函數定義處:

void find1(char [] array, char search, char *pa) 

再看調用處:

find1(str, a, p);

仔細考慮此時形參結合所發生的事:array得到了數組名爲str, search得到了a的值, pa得到了p的值(是NULL,而非p自身的地址)!但實參p並未得到形參pa傳回的值(某元素的地址)。可見儘管使用了指針,也並沒實現傳址,當實參形 參都是指針時,它們也僅僅是傳值——傳了別人的地址,沒有傳回來。此時我們就可以用到二級指針

修正:
    void find2(char [] array, char search, char **ppa)  
    {  
        for (int i = 0; *(array + i) != '0'; ++i)  
        {  
            if (*(array + i) == search)  
            {          
                *ppa = array + i;  
                break;  
            }  
        }  
      
        if ('\0' == *(array + i))  
        {  
            *ppa = NULL:  
        }  
    }  



主函數的調用處改如下:
                       find2(str, a, &p);    // 調用函數以實現操作 
ppa是指向指針p的指針。
對*ppa的修改就是對指針p的修改。
注意: 不要將指針的用法照搬到指向指針的指針頭上!
如:
char *pa 和 const char *pa是類型相容的;
但char **pa 和 const char **pa則類型不相容!
似乎我們找到了二級指針的應用場合,那就是用來存放指針變量地址的指針變量

 數組與指針的關係

1 一維數組與指針:

我們知道,定義一個數組時,系統將分配一個連續的內存地址,如:
int a[5]則會分配一段連續的空間分別用來表示a[0] a[1] a[2] a[3] a[4] a[5]的地址。
#include "stdio.h"
void main()
{
   int a[5],i=0;
   for(i=0;i<5;i++)
   printf("%d ",&a[i]);
   printf("\n%d",a);
} 

輸出:
1245036 1245040 1245044 1245048 1245052
1245036
可以看出,for循環一次輸出a數組中各項的地址,是連續的。本編譯環境支持一個int變量佔用四個字節。
最後輸出的是數組名,因爲數組名錶示的是這個數組的首地址即a==&a[0]是成立的。            
此時如果用指針來表示這個地址,直接保存這個數組的首地址就可以了。如
#include"stdio.h"
void main()
{
   int a[5]={11,12,13,14,15},*p,i;
   for(i=0;i<5;i++)
     printf("%d ",a[i]);
   printf("\n");
   p=a;//或寫成p=&a[0];
   printf("%d\n",*p);
   printf("%d\n",*(p+2));
   p=p+1;
   printf("%d\n",*p);
} 


2 二維數組與指針
我相信指針和數組之間的曖昧纏綿讓很多C初學者很頭痛吧,特別是多維數組,那真的是要了親命,這裏我給大家好好分析一下指針和多維數組之間的關係。
大家都知道一維數組名即是一個指針常量,它代表數組第一個元素的地址,我們知道一維數組的長度,那麼可以通過數組名輸出一維數組的所有元素:
    #include <stdio.h>

    int main(void)
    {
        int i;
        int a[5] = {1, 2, 3, 4, 5};
        int *p = a;
        for( i = 0; i < 5; i++ )
            printf( "%d\n", *(p + i) );
        return 0;
    }


但是在多維數組中,數組名卻是第一個數組的地址,怎麼理解呢?比如說定義一個二維數組:
    int a[2][5] = {1, 2, 3, 4, 5,
                   6, 7, 8, 9, 10};

那麼數組名a就是二維數組的第一行一維數組的地址,而不要錯誤的認爲它代表的是第一行第一個元素的地址噢,那我們應該怎麼正確的申明一個指向整形數組的指針呢?
int (*p)[5] = a;
它使 p 指向二維數組的第一行的一維數組(注意是指向的第一行一維數組)。

    #include <stdio.h>

    int main(void)
    {
        int i;
        int a[2][5] = {1, 2, 3, 4, 5,
                       6, 7, 8, 9, 10};
        int (*p)[5] = a;
        for( i = 0; i < 5; i++ )
            printf( "%d\n", *(*p + i) );
        return 0;
    }

上面的程序也是依次輸出了1~5,這裏 p 是指向二維數組的第一行地址的指針,那麼 *p 即是代表第一行數組,那麼也就是第一行數組的地址,我們可以再通過 *(*p + i) 輸出第一行數組的各個元素。有點亂?呵呵,再仔細想想,或者直接說 a 是指向的第一行,那麼 *a 就代表第一行的那個一維數組,但 *a 仍然是一個指針常量,而 **a 纔是這個一維數組的第一個元素的值。


如果我們要定義一個指針可以逐個訪問元素呢?下面兩種方法都是正確的聲明和賦值:
int *p = &a[0][0];
int *p = a[0];
第一種方法,就乾脆把數組的第一行第一個元素取地址給指針變量p,這可能是最直觀的方法。
第二種方法,其實原理一樣,前面剛說了 a 是指向的第一行,第一行是一個一維數組,那麼a[0]就是這個一維數組的第一個元素的地址。特別注意這裏的 a[0] 不是一個值,而是一個地址。
    #include <stdio.h>

    int main(void)
    {
        int i;
        int a[2][5] = {1, 2, 3, 4, 5,
                       6, 7, 8, 9, 10};
        int *p = a[0];
        for( i = 0; i < 10; i++ )
            printf( "%d\n", *(p + i) );
        return 0;
    }

上面的代碼就輸出了二維數組中的所有元素。

    //附:這裏的a[0]卻輸出了hello
    int main(void)
    {
        char a[2][10] = {"hello", "hi"};
        printf( "%s\n", a[0] );
        //printf( "%s\n", *a );
        //printf( "%s\n", *a + 1 ); 輸出 "ello"
        //printf( "%s\n", *(a + 1) ); 輸出 "hi"
        return 0;
    }

而這又是爲什麼呢?這裏的 a[0] 仍然是一個地址,它指向的是一個字符串常量,%s 是可以打印地址的,這裏跟 %d 輸出值是不一樣的,a[0] 指向了字符串的第一個字母的地址,一直輸出到 NULL 爲止。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章