C語言入門系列之6.一維和二維數組

一、數組的概念

有如下幾組數據:

  • 學生的學習成績
  • 銀行的賬單
  • 一行文字

這些個數據的特點是:

  • 具有相同的數據類型
  • 使用過程中需要保留原始數據 。

C語言爲這些數據,提供了一種構造數據類型——數組

在程序設計中,爲了處理方便,把具有相同類型的若干變量按有序的形式組織起來,這些按序排列的同類數據元素的集合稱爲數組
在C語言中,數組屬於構造數據類型;
數組元素有序不是指元素大小順序,而是位置順序

簡而言之,數組就是具有相同類型的數據組成的序列,是一個有序集合。
數組中的每一個數據稱爲數組元素,也稱爲下標變量,即每個元素由其所在的位置序號(稱數組元素的下標)來區分。
用數組名與下標可以用統一的方式來處理數組中的所有元素,從而方便地實現處理一批具有相同性質數據的問題。

一個數組可以分解爲多個數組元素,這些數組元素可以是基本數據類型或是構造類型,因此按數組元素的類型不同,數組又可分爲數值數組、字符數組、指針數組、結構數組等類別。

二、一維數組的定義與引用

1.一維數組定義

在C語言中使用數組必須先進行定義。
一維數組的定義方式爲:

類型說明符 數組名 [常量表達式];

例如int a[10]定義了一個整型數組,數組名爲a,此數組有10個元素,10個元素都是整型變量。

注意事項

(1)類型說明符是任一種基本數據類型或構造數據類型,對於同一個數組,其所有元素的數據類型都是相同的。

(2)數組名是用戶定義的數組標識符,書寫規則應符合標識符的書寫規定。

(3)方括號中的常量表達式表示數據元素的個數,也稱爲數組的長度。

(4)允許在同一個類型說明中,說明多個數組和多個變量。
例如int a, b, c, d, k1[10], k2[20];

(5)a[10]表示a數組有10個元素,下標從0開始,這10個元素是a[0]、a[1]、…、a[9]。
因此,不存在數組元素a[10]。

(6)C語言不允許對數組的大小作動態定義,即數組的大小不依賴於程序運行過程中變量的值,因爲在編譯的時候就要爲數組預留空間,所以在編寫代碼的時候不能通過變量來定義數組的大小。
例如,下面這樣定義數組是不行的:

int n;
scanf("%d"&n);  /* 在程序中臨時輸入數組的大小 */        
int a[n];

常見錯誤

float a[0];     	/* 數組大小爲0沒有意義 */
int b(2)(3);  	/* 不能使用圓括號 */
int k, a[k];  	/* 不能用變量說明數組大小 */

正確示意如下:

int a[10];                  // 聲明整型數組a,有10個元素。
float b[10],c[20];          // 聲明實型數組b,有10個元素,實型數組c,有20個元素。
char ch[20];                // 聲明字符數組ch,有20個元素。

補充–一維數組在內存中的存放

定義一個一維數組int mark[100];,其在內存中地存放原理如下:
一維數組在內存中的存放

2.一維數組的引用

數組元素是組成數組的基本單元,也是一種變量,其標識方法爲數組名後跟一個下標,下標表示了元素在數組中的順序號。
引用數組元素的一般形式爲數組名[下標],下標可以是整型常量或整型表達式
例如:

a[0] = a[5] + a[7] - a[2*3];
a[i+j];
a[i++];

這些都是合法的數組元素。

注意事項

(1)數組元素通常也稱爲下標變量,必須先定義數組,才能使用下標變量。
在C語言中只能逐個地使用下標變量,而不能一次引用整個數組。
例如,輸出有10個元素的數組必須使用循環語句逐個輸出,示意如下:

#include <stdio.h>

int main(){
	int i, a[10];
	for(i = 0;i < 10; i++){
		a[i] = i;
	}
	for(i = 9;i >= 0;i--){
		printf("%d ", a[i]);
	}
	
	return 0;
} 

打印:

9 8 7 6 5 4 3 2 1 0

不能用一個語句輸出整個數組,printf("%d",a);寫法是錯誤的。

(2)定義數組時用到的數組名[常量表達式]和引用數組元素時用到的數組名[下標]是有區別的。
例如∶

int a[10];          /* 定義數組長度爲10 */
t = a[6];             /* 引用a數組中序號爲6的元素,此時6不代表數組長度 */

顯然,兩者的含義是不一樣的。

3.一維數組的初始化

給數組賦值的方法除了用賦值語句對數組元素逐個賦值外,還可採用初始化賦值動態賦值的方法。
數組初始化賦值是指在數組定義時給數組元素賦初值。
數組初始化是在編譯階段進行的,這樣將減少運行間,提高效率;
之前用賦值語句或輸入語句也可給數組素指定初值,是在運行時完成。

初始化賦值

初始化賦值的一般形式爲:

類型說明符 數組名[常量表達式]={值,值,……值};

具體的實現方法有以下幾種:
(1)在定義數組時對數組元素賦以初值。
例如int a[10]= {0,1,2,3,4,5,6,7,8,9};,將數組元素的初值依次放在一對大括號內。
經過上面的定義和初始化之後,得到a[0] = 0,a[1] = 1,…,a[9] = 9。

測試如下:

#include <stdio.h>

int main(){
	int i, a[10] = {0, 1, 2, 3, 4, 5, 6, 7 ,8 ,9};
	for(i = 9;i >= 0;i--){
		printf("%d ", a[i]);
	}
	
	return 0;
}

與之前的效果是一樣的。

(2)可以只給一部分元素賦值。
例如int a[10] = {0,1,2,3,4};定義a數組有10個元素,但大括號內只提供5個初值,這表示只給前面5個元素賦初值,後5個元素值爲0。

測試如下:

#include <stdio.h>

int main(){
	int i, a[10] = {0, 1, 2, 3, 4};
    for(i = 9;i >= 0;i--){
        printf("%d ", a[i]);
    }
	
	return 0;
}

打印:

0 0 0 0 0 4 3 2 1 0

顯然,未定義的元素默認設爲0。

(3)如果想使一個數組中全部元素值爲0,可以寫成int a[10] = {0,0,0,0,0,0,0,0,0,0};int a[10] = {0};

(4)在對全部數組元素賦初值時,由於數據的個數已經確定,因此可以不指定數組長度。
例如int a[5] = {1,2,3,4,5};也可以寫成int a[] = {1,2,3,4,5};

在第二種寫法中,大括號中有5個數,系統就會據此自動定義a數組的長度爲5。
如果數組長度與提供初值的個數不相同,則數組長度不能省略。
例如,想定義數組長度爲10,就不能省略數組長度的定義,而必須寫成int a[10] = {1,2,3,4,5}; 只初始化前5個元素,後5個元素爲0。

數組初始化與未初始化比較測試如下:

#include <stdio.h>

int main(){
	int i, a[5] = {3, 4, 5}, b[5];
	printf("Array a is:\n");
	for(i = 0;i < 5;i++){
		printf("%8d", a[i]);
	}
	printf("\nArray b is:\n");
	for(i = 0;i < 5;i++){
		printf("%8d", b[i]);
	}
	
	return 0;
}

打印:

Array a is:
       3       4       5       0       0
Array b is:
      -1      -1 4236709       0       1

顯然,b數組未賦值,所以打印出了很亂的值。

動態賦值

動態賦值的方法示例如下:

#include <stdio.h>

int main(){
	int i, max, a[10];
	printf("Input 10 numbers:\n");
	for(i = 0;i < 10;i++){
		scanf("%d", &a[i]);
	}
	max = a[0];
	for(i = 1;i < 10;i++){
		if(a[i] > max){
			max = a[i];
		}
	}
	printf("max=%d", max);
	
	return 0;
}

打印:

Input 10 numbers:
13
46
70
95
73
25
62
78
54
9
max=95

顯然,在輸入10個數給數組賦值後,打印出了最大值。

練習:
利用數組來求解Fibonacci數列前20個數。
代碼如下:

#include <stdio.h>

int main(){
	int i;
	int a[20] = {1, 1};
	for(i = 2;i < 20;i++){
		a[i] = a[i - 1] + a[i - 2];
	}
	
	for(i = 0;i < 20;i++){
		printf("%6d", a[i]);
		if(i % 5 == 4){
			printf("\n");
		}
	}
	
	return 0;
}

打印:

     1     1     2     3     5
     8    13    21    34    55
    89   144   233   377   610
   987  1597  2584  4181  6765

練習:
用冒泡法(起泡法)對10個數排序(由小到大)。
代碼如下:

#include <stdio.h>

int main(){
	int i, j;
	int a[10];
	for(i = 0;i < 10;i++){
		scanf("%d", &a[i]);
	}
	
	for(i = 9;i >= 1;i--){
		for(j = 0;j < i;j++){
			int temp;
			if(a[j] > a[j + 1]){
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
	printf("The sorted nubers:\n");
	for(i = 0;i < 10;i++){
		printf("%d ", a[i]);
	}
	
	return 0;
}

打印:

12 37 65 43 97 82 120 63 9 17
The sorted nubers:
9 12 17 37 43 63 65 82 97 120

顯然,最後得到的就是已經排好序的數組。

三、二維數組的定義和引用

1.二維數組的定義

二維數組定義的一般形式爲:

類型說明符 數組名[常量表達式][常量表達式];

例如:定義a爲3X4 (3行4列)的數組,b爲5X10(5行10列)的數組,如下:

float a[3][4], b[5][10];

不能寫成

float a[3, 4], b[5, 10];

二維數組可理解爲元素是一維數組的一維數組,例如int a[3][4];理解如下:
二維數組理解
多維數組的定義:
例如float a[2][3][4];

二維數組在內存中的存放方式示意如下:
二維數組在內存中的存放

三維數組在內存中的存放方式示意如下:
三維數組在內存中的存放

2.二維數組的引用和初始化

引用數組元素的表示形式:

數組名[下標][下標]

其中,下標可以是整型常量或整型表達式

int a[4][3], i=2, j=1;
a[2][3];
a[i][j];
a[i+1][2*j-1];

a[i, j]就是錯誤的。

int a[3][4] = {1, 5, 9}是給a數組的第一個子數組a[0]的前3個元素賦值,與前者不一樣。

初始化數組的形式爲:

數據類型 數組名[常量表達式1][常量表達式2] = {初始化數據};

有4種方法對二維數組初始化:
(1)直接分行給二維數組賦初值。
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

(2)可以將所有數據寫在一個大括號內,按數組排列的順序對各元素賦初值。
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

(3)可以對部分元素賦初值。
int a[3][4]={{1}, {5}, {9}};,存放如下:
二維數組的初始化-方式三1

也可以對各行中的某一元素賦初值。
int a[3][4]={{1}, {0, 6}, {0, 0, 11}};,存放如下:
二維數組的初始化-方式三2

其實也可以只對某幾行元素賦初值。
int a[3][4]={{1}, {5, 6}};,存放如下:
二維數組的初始化-方式三3

(4)如果對全部元素都賦初值,則定義數組時對第一維的長度可以不指定,但第二維的長度不能省
如,int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};等價於int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

在定義時也可以只對部分元素賦初值而省略第一維的長度,但應分行賦初值。
int a[][4] ={{0, 0, 3}, {}, {0, 10}};,存放如下:
二維數組的初始化-方式四

練習:
如下圖,一個學習小組有5個人,每個人有三門課的考試成績。將各個數據保存到二維數組a[5][3]中,並求全組分科的平均成績和總平均成績。
二維數組的初始化-成績例題

代碼如下:

#include <stdio.h>

int main(){
	int i, j;
	double savg[3];
	int a[5][3] = {{80, 75, 92}, {61, 65, 71}, {59, 63, 70}, {85, 87 ,90}, {76, 77, 85}};
	for(i = 0;i < 3;i++){
		int sum = 0;
		for(j = 0;j < 5;j++){
			sum += a[j][i];
		}
		savg[i] = sum / 5.0;
	}
	int tavg = 0;
	for(i=0;i<3;i++){
		printf("Average Grade:%.2f\n", savg[i]);
		tavg += savg[i];
	}
	printf("Total AVerage Grade is:%.2f", tavg / 3.0);
	
	return 0;
}

打印:

Average Grade:72.20
Average Grade:73.40
Average Grade:81.60
Total AVerage Grade is:75.33

練習:
將一個二維數組行和列元素互換,存到另一個二維數組中。
例如,將數組a[2][3]轉化爲數組b[3][2]如下:
二維數組的初始化-轉置例題

代碼:

#include <stdio.h>

int main(){
	int i, j;
	int a[5][3] = {{80, 75, 92}, {61, 65, 71}, {59, 63, 70}, {85, 87 ,90}, {76, 77, 85}}, b[3][5];
	printf("Array A:\n");
	for(i = 0;i < 3;i++){
		for(j = 0;j < 5;j++){
			printf("%4d", a[j][i]);
			b[i][j] = a[j][i];
		}
		printf("\n");
	}
	printf("Arary B:\n");
	for(i = 0;i < 5;i++){
		for(j = 0;j < 3;j++){
			printf("%4d", b[j][i]);
		}
		printf("\n");
	}
	
	return 0;
}

打印:

Array A:
  80  61  59  85  76
  75  65  63  87  77
  92  71  70  90  85
Arary B:
  80  75  92
  61  65  71
  59  63  70
  85  87  90
  76  77  85

練習:
有一個矩陣,要求編程序求出其中最大的元素,以及其所在的行號和列號。
代碼如下:

#include <stdio.h>

int main(){
	int i, j, row = 0, column = 0, max = 0;
	int a[5][3] = {{80, 75, 92}, {61, 65, 71}, {59, 63, 70}, {85, 87 ,90}, {76, 77, 85}};
	max = a[0][0];
	for(i = 0;i < 3;i++){
		for(j = 0;j < 5;j++){
			if(a[j][i] > max){
				max = a[j][i];
				row = j;
				column = i;
			}
		}
	}
	printf("Max=%d, in row %d column %d", max, row, column);
	
	return 0;
}

打印:

Max=92, in row 0 column 2

練習:
從鍵盤上輸入9個整數,(對照九宮格的形式,輸入三行,每行輸入三個數) 保存在二維數組中,按數組原來位置輸出第一行和第一列的所有元素。
如果數組如下:
二維數組的初始化-九宮格例題輸入

則輸出爲:
二維數組的初始化-九宮格例題輸出

代碼如下:

#include <stdio.h>

int main(){
	int i, j, a[3][3];
	for(i = 0;i<3;i++){
		for(j = 0;j<3;j++){
			printf("a[%d][%d] = ", i, j);
			scanf("%d", &a[i][j]);
		}
	}
	for(i=0;i<3;i++){
		for(j=0;j<3;j++){
			if(i == 1 || j == 1){
				printf("%-6d", a[i][j]);
			}
			else{
				printf("%-6c", ' ');
			}
		}
		printf("\n");
	}
	
	return 0;
}

打印:

a[0][0] = 12
a[0][1] = 34
a[0][2] = 56
a[1][0] = 78
a[1][1] = 90
a[1][2] = 98
a[2][0] = 76
a[2][1] = 54
a[2][2] = 43
      34
78    90    98
      54

四、數組的應用--二分法

利用數組進行數據查找——(二分法)折半查找法:
適應情況:
在一批有序數據中查找某數;
基本思想:
選定這批數中居中間位置的一個數與所查數比較,看是否爲所找之數,若不是,利用數據的有序性,可以決定所找的數是在選定數之前還是在之後,從而很快可以將查找範圍縮小一半。以同樣的方法在選定的區域中進行查找,每次都會將查找範圍縮小一半,從而較快地找到目的數。

練習:
假設在數組a中的數據是按由小到大順序排列的:
-12 0 6 16 23 56 80 100 110 115
從鍵盤上輸入一個數,判定該數是否在數組中,若在,輸出所在序號。
實現思路:

  1. 設low、mid和high三個變量,分別指示數列中的起始元素、中間元素與最後一個元素位置, 其初始值爲low=0,high=9,mid=4,判斷mid指示的數是否爲所求,mid指示的數是23,不是要找的80,須繼續進行查找。
  2. 確定新的查找區間。因爲80大於23,所以查找範圍可以縮小爲23後面的數,新的查找區間爲[56 80 100 110 115],low,mid,high分別指向新區間的開始、中間與最後一個數。實際上high不變,將low(low=mid+1)指向56,mid (mid=(low+high)/2)指向100,還不是要找的80,仍須繼續查找。
  3. 上一步中,所找數80比mid指示的100小,可知新的查找區間爲[56 80],low不變,mid與high的值作相應修改。mid指示的數爲56,還要繼續查找。
  4. 根據上一步的結果,80大於mid指示的數56,可確定新的查找區間爲[80],此時,low與high都指向80,mid亦指向80,即找到了80,到此爲止,查找過程完成。

注意:
若在查找過程中,出現low > high的情況,則說明序列中沒有該數,亦結束查找過程。

代碼如下:

#include <stdio.h>
#define M 10

int main(){
	static int a[M] = {-12, 0, 6, 16, 23, 56, 80, 100, 110, 115};		// 定義靜態變量 
	int n, low = 0, mid, high = M - 1, found = 0;
	printf("Input a number to be searched:\n");
	scanf("%d", &n);
	while(low <= high){
		mid = (low + high) / 2;
		if( n== a[mid]){
			found = 1;
			break;
		}
		else if(n > a[mid]){
			low = mid + 1;
		}
		else{
			high = mid - 1;
		}
	}
	if(found == 1){
		printf("The number %d is found, and the index is %d\n", n, mid);
	}
	else{
		printf("The number %d is not found\n", n);
	}
	
	return 0;
}

打印:

Input a number to be searched:
80
The number 80 is found, and the index is 6

可以看到,在程序中定義了一個靜態變量;
C程序在編譯時,普通變量存放在棧區,static關鍵字會使變量存放在data區。

補充知識——內存分爲四大區:

  • code區
    寫的代碼存放的地方。
  • data區
    常量、字符串和static聲明的變量存放的地方,特點是不會改變,整個程序結束之後纔會釋放。
  • stack區
    普通變量存放的地方,函數調用完成後就會釋放。
  • heap區
    malloc函數定義,由開發者自己分配。

還可以進一步優化:
如果輸入的數大於最大的數或小於最小的數,說明這個有序序列中不存在要尋找的數,可以直接不用循環查找,改進如下:

#include <stdio.h>
#define M 10

int main(){
	static int a[M] = {-12, 0, 6, 16, 23, 56, 80, 100, 110, 115};
	int n, low = 0, mid, high = M - 1, found = 0;
	printf("Input a number to be searched:\n");
	scanf("%d", &n);
	if(n < a[0] || n > a[M - 1]){
		while(low <= high){
			mid = (low + high) / 2;
			if( n== a[mid]){
				found = 1;
				break;
			}
			else if(n > a[mid]){
				low = mid + 1;
			}
			else{
				high = mid - 1;
			}
		}
		if(found == 1){
			printf("The number %d is found, and the index is %d\n", n, mid);
		}
		else{
			printf("The number %d is not found\n", n);
		}
	}
	else{
		printf("Illegal Input!!!");	
	}
	
	return 0;
}

打印:

Input a number to be searched:
a
Illegal Input!!!

顯然,此時可以識別非法輸入。
也可以改進如下:

#include <stdio.h>
#define M 10

int main(){
	static int a[M] = {-12, 0, 6, 16, 23, 56, 80, 100, 110, 115};
	int n, low = 0, mid, high = M - 1, found = 0;
	printf("Input a number to be searched:\n");
	scanf("%d", &n);
	while(scanf("%d", &n) != 1){
		printf("Illegal Input!!\nPlease Input Again!!\n");
		getchar();
	}
	while(low <= high){
		mid = (low + high) / 2;
		if( n== a[mid]){
			found = 1;
			break;
		}
		else if(n > a[mid]){
			low = mid + 1;
		}
		else{
			high = mid - 1;
		}
	}
	if(found == 1){
		printf("The number %d is found, and the index is %d\n", n, mid);
	}
	else{
		printf("The number %d is not found\n", n);
	}
	
	return 0;
}

打印:

Input a number to be searched:
a
Illegal Input!!
Please Input Again!!
bc
Illegal Input!!
Please Input Again!!
Illegal Input!!
Please Input Again!!
12
The number 12 is not found

顯然,此時如果輸入有誤,會提示再輸入,直到輸入合法,再向下執行並判斷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章