天天看點

黑馬程式員——C語言基礎學習(四)---數組和指針的總結學習

------  <a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="blank">Java教育訓練、Android教育訓練、iOS教育訓練、.Net教育訓練</a> 期待與您交流! -------

                  數組和指針的總結學習

一.數組

  1.數組的基本概念

把具有相同類型的若幹變量按有序的形式組織起

來.這些按序排列的同類資料元素的集合成為數組.

  2.數組的幾個名詞

1)數組:一組具有相同資料類型的資料的有序的集合

2)數組元素:構成數組的資料。數組中的每一個數組元素具有相同的

  名稱,不同的下标,可以作為單個變量使用,是以也稱為下标變量。

3)數組的下标:是數組元素的位置的一個索引或訓示。

4)數組的維數:數組元素下标的個數。

  根據數組的維數可以将數組分為一維、二維、 三維、多元數組。 

  3.數組分類

(1)按數組元素的類型不同,數組可分為:
數值數組:用來存儲數值的
字元數組:用來存儲字元 ‘a’
指針數組:用來存放指針(位址)的
結構數組:用來存放一個結構體類型的資料
(2)按次元分類
  一維數組
  二維數組
  多元數組

  4.一維數組定義及注意事項

(1)一維數組介紹
如果一個數組的元素不是數組,那麼就是一維數組
(2)一維數組的定義
類型說明符 數組名[常量表達式];
(3)數組定義的注意點

1) 數組的類型實際上是指數組元素的取值類型。對于同一個數組,其所有元素

   的資料類型都是相同的。第一個數組元素的位址是數組所占記憶體塊的首位址.

2) 數組名的書寫規則應符合辨別符的書寫規定。 
命名規則: 
1、隻能由字母、數字、下劃線組成
2、不能數字開頭 
3、不能與關鍵字重名 
4、嚴格區分大小寫
命名規範: 
1、起一個有意義名字
2、駝峰标示
3) 數組名不能與其它變量名相同。

4) 方括号中常量表達式表示數組元素的個數,如a[5]表示數組a有5個元素。但是

   其下标從0 開始 計算。是以5個元素分别為a[0], a[1], a[2], a[3], a[4]

5) 不能在方括号中用變量來表示元素的個數,但是可以是符号常數或常

   量表達式。c99不支援使用變量作為元素個數,但llVM編譯器,它支援

6) 允許在同一個類型說明中,說明多個數組和多個變量。 

  5.一維數組初始化

(1)一維數組的初始化數組初始化指派是指在數組定義時給數組元素賦予初值。數組初始化是在編譯階段

   進行的.這樣将減少運作時間,提高效率。初始化方式有兩種:定義的同時初始化、先定義,後初始化

(2)定義的同時初始化
類型說明符 數組名[常量表達式] = { 值, 值......值 };
又細分以下幾種情況:

1)指定元素的個數的同時,對所有的元素進行顯式的初始化 

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

2)指定數組的元素個數,對數組進行部分顯式初始化定義的同時對數組

  進行初始化,沒有顯式初始化的元素,那麼系統會自動将其初始化為0

  int nums[10] = {1,2};
3)不指定元素個數,定義的同時初始化,它是根據大括号中的元素的個數來确定數組的元素個數
  int nums[] ={1,2,3,5,6};

4)指定元素個數,同時給指定元素進行初始化

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

注:給數組指派的方法除了用指派語句對數組元素逐個

   指派外,還可采用初始化指派和動态指派的方法

(3)先定義,後初始化
正确寫法:
int nums[3];
nums[0] = 1; nums[1]= 2; nums[2] = 3;
錯誤寫法:
int nums[3];
nums ={1,2,3}; // 因為數組名是一個常量,是數組的首位址,是以不能這樣指派. 

  6.一維數組的引用方式

數組元素通路一般形式:
數組名[下标]
數組元素通常也稱為下标變量,必須先定義數組,才能使用下标變量. 

  7.一維數組的存儲方式

存儲方式:
1)計算機會給數組配置設定一塊連續的存儲空間
2)數組名代表數組的首位址,從首位址位置,依次存入數組的第1個、第2個....、第n個元素
3)每個元素占用相同的位元組數(取決于數組類型)4)并且數組中元素之間的位址是連續 

  8.一維數組的位址

1)數組内部的元素位址是連續的

在記憶體中,記憶體從大到小進行尋址,為數組配置設定了存儲空間後,數組的

元素自然的從上往下排列 存儲,整個數組的位址為首元素的位址。

2)數組名存放的是數組的首位址
數組的首位址:數組的第一個元素首位址(第一個元素的第一個位元組位址)
3)數組每個元素的位址
就是每個元素的首位址 

  9.計算數組的長度

因為數組在記憶體中占用的位元組數取決于其存儲的資料類型和資料的個數
數組在記憶體中占用的總位元組數:sizeof(數組名);
是以計算數組長度可以使用如下方法
數組的長度 = 數組占用的總位元組數 / 數組元素占用的位元組數 

  10.數組的越界

1)越界指派,越界讀取,都是很危險的 
2)一個長度為n的數組,最大下标為n-1, 下标範圍:0~n-1 

  11.數組元素作函數實參

數組元素就是下标變量,它與普通變量并無差別.是以它作為函數實參使用與普通變量是完
全相同的,在發生函數調用時,把作為實參的數組元素的值傳送給形參,實作單向的值傳遞 

  12.數組名作為函數參數

1)用數組元素作函數參數不要求形參也必須是數組元素,但是用數組名
  作函數參數時,則要求形參和相對應的實參都必須是類型相同的數組
2)在C語言中,數組名除作為變量的辨別符之外,數組名還代表了該數組
  在記憶體中的起始位址, 是以,當數組名作函數參數時,實參與形參之間
  不是"值傳遞",而是"位址傳遞",實參數組名将 該數組的起始位址傳
  遞給形參數組,兩個數組共享一段記憶體單元,編譯系統不再為形參數組
  配置設定存儲單元。
3)在變量作函數參數時,所進行的值傳送是單向的。即隻能從實參傳向
  形參,不能從形參傳回實 參。形參的初值和實參相同,而形參的值發
  生改變後,實參并不變化,兩者的終值是不同的。 

  13.數組名作函數參數的注意點

1)形參數組和實參數組的類型必須一緻,否則将引起錯誤。

2)形參數組和實參數組的長度可以不相同,因為在調用時,隻傳送首位址

  而不檢查形參數組的長度。當形參數組的長度與實參數組不一緻時,

  雖不至于出現文法錯誤(編譯能通過),但程式執行結果将與實際不符,

  這是應予以注意的。

3)在函數形參表中,允許不給出形參數組的長度,或用一個變量來表示數

  組元素的個數。 如void nzp( int a[], int n )

4)多元數組也可以作為函數的參數。在函數定義時對形參數組可以指定

  每一維的長度,也可省去第一維的長度。 

  14.冒泡排序思想 

(1)冒泡排序

冒泡排序(Bubble Sort,台灣譯為:泡沫排序或氣泡排序)是一種簡

單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,

如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地

進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法

的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。 

(2)冒泡排序步驟(大數下沉)
1)比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。

2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最

  後一對。在這一點,最後的元素應該會是最大的數。

3)針對所有的元素重複以上的步驟,除了最後一個。
4)持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。 

  15.冒泡排序的執行個體:

#include <stdio.h>

void BubbleSort(int *array, int length) {
    // temp
    for (int i = 0; i < length - 1; i++) {
        for (int j = 0; j < length - 1 - i; j++) {
            if(array[j] > array[j+1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}


void printArray(int *array, int length) {
    for (int i = 0; i < length; i++) {
        printf("%d\n", array[i]);
    }
}



int main(int argc, const char * argv[]) {
    int nums[] = { 1, 3, 5, -2, 4, 98, -100, 35 };
    int length = sizeof(nums) / sizeof(int);
    printArray(nums, length);
    
    BubbleSort(nums, length);
    
    printArray(nums, length);
    return 0;
}




           

  16.選擇排序思想 

(1)選擇排序

選擇排序(Selection sort)是一種簡單直覺的排序算法.它的工作原

理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始

位置,然後,再從剩餘未排序元素中繼續尋找最小元素,然後放到排序

序列末尾。以此類推,直到所有元素均排序完畢。

(2)選擇排序基本思想

第一趟排序在所有待排序的n個記錄中選出關鍵字最小的記錄,将它與

資料表中的第一個記錄交換位置,使關鍵字最小的記錄處于資料表的

最前端;第二趟在剩下的n-1個記錄中再選出關鍵字最小的記錄,将其

與資料表中的第二個記錄交換位置,使關鍵字次小的記錄處于資料表

的第二個位置;重複這樣的操作,依次選出資料表中關鍵字第三小、第

四小...的元素,将它們分别換到資料表的第三、第四...個位置上。

排序共進行n-1趟,最終可實作資料表的升序排列。 

  17.選擇排序執行個體

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int nums[] = { 12, 3, 40, 25, 7 };
    int length = sizeof(nums) / sizeof(int);
    
    for (int j = 0; j < length - 1; j++) {
        for (int i = j + 1; i < length; i++) {
            if(nums[j] > nums[i]) {
                int temp = nums[j];
                nums[j] = nums[i];
                nums[i] = temp;
            }
        }
        
    }
    
    
    
    for (int j = 0; j < length - 1; j++) {
        for (int i = j + 1; i < length; i++) {
            
        }
        
    }
    for (int i = 0; i < length - 1; i++) {
        for (int j = 0; j < length - 1 - i; j++) {
            
            
        }
    }
    
    return 0;
}
           

二.指針

  1.位址

(1)計算機硬體系統的内部存儲器中,擁有大量的存儲單元(容量為1位元組).為了友善管理,必須

   為每一個存儲單元編号,這個編号就是存儲單元的位址.每個存儲單元都有一個唯一個位址.

   記憶體位址----->内從中存儲單元的編号.
(2)在位址所辨別的存儲單元中存放資料。
   注:記憶體單元的位址與記憶體單元中的資料是兩個完全不同的概念。
      變量位址── 系統配置設定給變量的記憶體單元的起始位址 

  2.指針的基本概念

記憶體單元的編号也叫做位址.既然根據記憶體單元的編号或位址就可以找到所需的記憶體單元,

是以通常也把這個位址稱為指針.記憶體單元的指針和記憶體單元的内容是兩個不同的概念。

總結:對于一個記憶體單元來說,單元的位址即為指針,其中存放的資料才是該單元的内容

  3.使用指針好處

a.為函數提供修改調用變量的靈活手段;
b.讓函數有多個傳回值

c.可以改善某些子程式的效率>>在資料傳遞時,如果資料塊較大(比如說資料緩沖區或比較大

  的結構),這時就可以使用指針傳遞位址而不是實際資料,即提高傳輸速度,又節省大量記憶體。

d.為動态資料結構(如二叉樹、連結清單)提供支援 

  4.變量的存取方式

存取方式分為兩種:
直接存取:變量的指派和取值(通過變量名進行存取值)
間接存取:通過指針(位址)間接操作完成

  5.指針變量的概念

在C語言中,允許用一個變量來存放指針,這種變量稱為指針變量.是以,

一個指針變量的值就是某個記憶體單元的位址或稱為某記憶體單元的指針。

注: 嚴格意義上說,指針是一個位址,是一個常量針變量是存放一個位址,是一個

  6.定義一個指針變量

對指針變量的定義包括三個内容:
 1)指針類型說明,即定義變量為一個指針變量;
 2)指針變量名;
 3)變量值(指針) 
類型說明符 *變量名; 
注意: 

1)在定義指針時,“*”号表示定義的變量是指針變量,變量的值隻能存放位址。這時

  候的'*' 沒有取位址内容的意思,隻是一個類型,是以初始化的時候還是接收位址.

2)一個類型的指針隻能指向同類型的變量,不能指向其他類型的變量。 
3)指針也可以被聲明為全局、靜态局部和局部的。 

  7.指針變量的初始化和引用

(1)指針變量的初始化方法

1)定義的同時進行初始化

  int a = 5; int *p = &a; 

2)先定義後初始化
  int a; int *p;p=&a; 
3)把指針初始化為NULL
  int*p=NULL; 或 int *q=0; 
注意點:
1、多個指針變量可以指向同一個位址 
2、指針的指向是可以改變的 
3、指針沒有初始化裡面是一個垃圾值,這時候我們這是一個野指針, 
如果操作一個野指針 
1)可能會導緻程式崩潰
2)通路你不該通路資料 
是以指針必須初始化才可以通路其所指向存儲區域
(2)使用“*”擷取指針對應存儲區域的内容

C語言中提供了位址運算符&來表示變量的位址。指針運算符 *來讀取位址裡面的内容. 

其一般形式為: 

    &變量名; *指針變量名 

關于*的使用注意:

1)在定義變量的時候 * 是一個類型說明符,說明定義的這個變
        量是一個指針變量 ,沒有提取位址内容的意思
2)在不是定義變量的時候 *是一個操作符,通路指針所指向存儲空間 

  8.指針常見的應用場景

1)在函數中間接通路調用者中的變量
void change(int *num){
*num = 10;
}
 2)函數傳回多個值
//寫個一個函數,計算兩個數的和、差、乘積、商
voidsumAndMinusAndJiAndShang(int num1,int num2,int *sum,int *minus,int *ji,int*shang)
{
*sum = num1 + num2;
 *minus = num1 - num2;
*ji = num1 * num2;
 *shang = num1 / num2;
}

  9.二級指針概述 

如果一個指針變量存放的又是另一個指針變量的位址,則稱

這個指針變量為指向指針的指針變量。也稱為“二級指針”

  10.在多級換算的時候的規律 

我們要記住,指針是用來存儲位址的就可以了.

可以總結為 :

指針變量名 == &所指向的的變量

例如 p = &a; 然後指針上面加一個* 它所指向的變量就減去一個&.

例如 p ==&a;左邊加一 個* 右邊就去掉一個 & 就是*p == a; 

那麼我們來舉一個例子.**pp怎麼換算呢? 
首先 pp == &p 
那麼**pp == **(&p); *(&p) == p; 
那 麼**pp ==**(&p) == *p ;
然後p == &a,是以**pp == **(&p) == *p == *(&a),
因為*(&a)== a;
那 麼**pp == **(&p) == *p == *(&a) == a ,
也就是**pp ==a.

  11.數組指針

指向一個數組的指針就是數組指針.

數組指針其實就是數組的首位址.

數組元素指針

一個變量有位址,一個數組包含若幹元素,每個數組元素都

有相應的位址.所謂數組元素指針就是數組元素的位址.

注意:

數組名a不代表整個數組,隻代表數組首元素的位址。

“p=a;”的作用是“把a數組的首元素的位址賦給

指針變量p”,而不是“把數組a各元素的值賦給 p”

  12.使用指針引用數組元素

在指針指向數組元素時,允許以下運算:
加一個整數(用+或+=),如p+1
減一個整數(用-或-=),如p-1
自加運算,如p++,++p
自減運算,如p--,--p
兩個指針相減,如p1-p2(隻有p1和p2都指向同一數組中的元素時才有意義)
每次相加相減都是以自身類型所占用的位元組數來移動位址的.
例如:int型 p +2 就是移動兩個4位元組 .
     注意:   
1)如果p的初值為&a[0],則p+i和a+i就是數組元素a[i]的位址
2)*(p+i)或*(a+i)是p+i或a+i所指向的數組元素,即a[i]
3)如果指針p1和p2都指向同一數組
結論:
引用一個數組元素,可用下面兩種方法:
(1)下标法,如a[i]形式
(2)指針法,如*(a+i)或*(p+i)收地
注:a是常量(a++錯誤),p是變量(p++正确) 

  13.一維指針數組介紹

一個數組的元素值為指針則是指針數組.
形式:類型說明符 *數組名[數組長度]
例:int *pa[3]
表示pa是一個指針數組,它有三個數組元素,每個元素值都是一個指針,指向整型變量。

  14.指針變量之間運算

(1)兩指針變量減法運算
兩指針變量相減所得之差是兩個指針所指數組元素之間相差的元素個數*類型所占位元組數.
指針之間的算術運算
(pointer2位址值-pointer位址值)/sizeof(所指向資料類型) ==兩者相差的元素數
(2)兩指針變量進行關系運算
指向同一數組的兩指針變量進行關系運算可表示它們所指數組元素之間的關系(先後順序,高低順序等)。
指針變量還可以與0比較.設p為指針變量,則p==0表明p是空指針,它不指向任何變量;p!=0表示p不是空指針。
注意:
指針之間可以相減,但不可以相加(相加無意義)
空指針是由對指針變量賦予0值而得到的。 

  15.二維數組指針的定義、初始化

資料類型 (*指針變量名)[二維數組列數];
例 int a[4][5]; int (*p)[5] = a;
如要将二維數組賦給一指針,應這樣指派:
int a[3][4];
int(*p)[4]; //該語句是定義一個數組指針,指向含4個元素的一維數組。
p=a;//将該二維數組的首位址賦給p,也就是a[0]或&a[0][0]
p++;//該語句執行過後,也就是p=p+1;p跨過行a[0][]指向了行a[1][]

是以數組指針也稱指向一維數組的指針,亦稱行指針,每次加i都是移動一

行的位址的大小,而非移動一個元素的位址大小

  16.指針數組和二維數組指針變量的差別

int*pa[3]={&a,&b,&c}; // pa是一個指針數組
int*pa1[2]={a[0],a[1]} // pa1是一個指針數組
int(*pa)[3]; // 二維數組指針
應該注意指針數組和二維數組指針變量的差別。這兩者雖
然都可用來表示二維數組,但是其表示方法和意義是不同的。
int(*p)[3]; 表示一個指向二維數組的指針變量。該二維數組的列數為3或
分解為一維數組的長度為3。p每加1移動的是(列數*int類型長度)個位址.
int *p[3];表示p是一個指針數組,有三個下标變量p[0],p[1],p[2]均為指針
變量。p每加1移動的 是(一個指針變量的大小)個位址.

  17.字元串指針介紹及使用

(1)字元串指針
在C語言中,可以用兩種方法通路一個字元串
1)字元數組
2)字元串指針指向字元串
char *變量名="字元串内容";
字元串指針變量的定義說明與指向字元變量的指針變量說明是相同的。隻能 按對指針變量的指派不同來差別. 對指向字元變量的指針變量應賦予該字元變量的位址.
如:
char c,*p=&c;
表示p是一個指向字元變量c的指針變量。
而:
char *s="CLanguage";
則表示s是一個指向字元串的指針變量。把字元串的首位址賦予s。
3)字元串指針的定義和初始化
定義的同時進行初始化
char *ps="CLanguage";
等效于:
char *ps;
ps="CLanguage";
注意:

1、使用字元數組來儲存的字元串是儲存在棧裡的,儲存棧裡面的東西是可

   讀可寫,所有我們可以改變裡面的字元當把一個字元串常量指派一個字

   符數組的 時候,那麼它會把字元串常量中的每一個字元都放到字元數組裡面.

2、使用字元指針來儲存字元串,它儲存的是字元串常量位址,常量區是隻

   讀的, 是以我們不可以修改字元串中的字元.

(2)字元串指針使用注意
1)可以利用指針的加減來檢視字元串的每一個字元. 例:*(p+1)
2)不可以修改字元串内容
3)妙用字元串指針

将指針變量指向一個格式字元串,用在printf函數中,用于

輸出二維數組的各種位址表示的值。但在printf語句中用

指針變量PF代替了格式串。這也是程式中常用的方法

例:
static inta[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
char *PF;
PF="%d,%d,%d,%d,%d\n";
printf(PF,a,*a,a[0],&a[0],&a[0][0]);
printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);
printf("%d,%d\n",a[1]+1,*(a+1)+1);
printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));
4)不能夠直接接收鍵盤輸入

錯誤的原因是:沒有初始化就直接在scanf裡面接收字

符串,這個時候指針是一個野指針(就算是初始化為NULL

也是一樣),他并沒有指向某一塊記憶體空間,是以不允許

這樣寫如果給str配置設定記憶體空間是可以這樣用的 

  18.指針應用舉例:

    例1:

#include <stdio.h>

/*
    将指針變量作為函數參數傳遞應用
 
  */


void func(int *p) {
    
    *p *= 2;
}




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

           

  例2

/*
 
 使用指針作為函數參數可以傳回多個值應用

 */

#include <stdio.h>

void inputIntNum(int *num) {
    
    
    int count;
    while(1) {
        printf("請輸入數字\n");
        count = scanf("%d", num);
        while (getchar() != '\n');
        if(count > 0) break;
        
        printf("輸入的不是數字, ");
    }
}



char czInput(int *num) {
    // 如果使用者輸入的是數字, 那麼就将數字存儲在 *num 中
    // 如果使用者輸入的不是數字, 那麼将第一個字元傳回
    // 如果輸入的時數字, 則傳回 '\0'
    char ch = '\0';
    int count;
    
    printf("請輸入\n");
    count = scanf("%d", num);
    if( count == 0 ){
        // 輸入失敗
        ch = getchar();
    }
    if(ch != '\n') {
        while (getchar() != '\n');
    }
    
    return ch;
}

int main(int argc, const char * argv[]) {
    
    
    int num;
    char ch;
    
    
    if( (ch = czInput(&num)) != '\0' ) {
        // 輸入的是字母
        printf("輸入的第一個字母是 %c\n", ch);
    } else {
        // 輸入數字
        printf("輸入的數字是 %d\n", num);
    }
    
    
    return 0;
}