第四章:陣列及字串

第一節:簡介

為什要用陣列? 想像一下如果我們要寫一個程式, 程式要求要輸入全班同學的期中考數學科成績, 寫成程式會長什麼樣子?
#include < stdio.h >
void main(void)
{
	int num1, num2, num3, num4, num5;
	scanf("%d",&num1);
	scanf("%d",&num2);
	scanf("%d",&num3);
	scanf("%d",&num4);
	scanf("%d",&num5);
}
上面的程式是全班只有5位學生時的情形, 那要是全班有50位學生時, 程式要怎麼寫? 這時就可求助於陣列(Array)
#include < stdio.h >
void main(void)
{
	int i;
	int student[50];
	
	for ( i=0; i<50; i++ )
	{
		scanf("%d", &student[i]);
	}
}
程式碼明顯的精簡許多, 用原先的寫法最少要宣告50個變數, 還要寫50個scanf.

使用#define的好處在這奡N可以看出來.

#include < stdio.h >

#define STUDENTS 5

void main(void)
{
	int i;
	int student[STUDENTS];
	// 只要改define中STUDENTS的數值, 陣列大小和迴圈要跑的次數都會同時改變
	for ( i=0; i < STUDENTS; i++ )
	{
		scanf("%d", &student[i]);
		printf("I get %d\n",student[i]);
	}
}
簡單的來說, 陣列就是一次宣告出許多個相同型態的變數來使用.
int a[5];
上面的語法會宣告出5個整數, 要取用陣列中的某個變數來用時, 要給一個索引值, 要取用這5個整數的方法分別是
a[0]=1; // 把a[0]設定為1
a[1]=2; // 把a[1]設定為2
a[2]=3; // 把a[2]設定為3
a[3]=4; // 把a[3]設定為4
a[4]=5; // 把a[4]設定為5
a[5]是不存在的.
宣告大小為N時, 取用的索引值範圍為0,1,2,.....N-1

第二節:一維陣列的使用

宣告陣列時, 同樣一開始可以設定好它們的初值
void main(void)
{
	int a[5]={1,2,3,4,5};
	// 設定 a[0]=1, a[1]=2, a[2]=3, a[3]=4, a[4]=5
	int b[]={1,2,3};
	// 若size沒指定, 因為給了3個數字, 在此會自動設定為b[3];
}
設定陣列時, 還常和迴圈一起使用
void main(void)
{
	int a[5];
	int i;
	for ( i=0; i<5; i++ )
	{
		a[i]=i;
	}
	// 設定a[0]=0, a[1]=1, a[2]=2, a[3]=3, a[4]=4
}
範例:讀入5個數字, 求出它們的平均值.
#include < stdio.h >

#define NUM 5

void main(void)
{
	int i;
	float sum=0;
	float aver;
	float num[NUM];
	
	// 分別讀入5個數值
	for ( i=0; i < NUM; i++ )
	{
		scanf("%f", &num[i]);
	}
	
	// 計算總和
	for ( i=0; i < NUM; i++ )
	{
		sum+=num[i];
	}
	// 求平均值
	aver=sum/(float)NUM;
	printf("average=%f\n",aver);
}

第三節:多維陣列

使用陣列時,只用了一個索引值, 叫做一維陣列. 我們可以宣告出需要多個索引 值的陣列來.
void main(void)
{
	int a[2][2];
	// 這時候, 有a[0][0], a[0][1], a[1][0], a[1][1]等4個變數可以使用
}
設定二維陣列初值的方法為
void main(void)
{
	int a[2][2]={ {00, 01},
		      {10, 11} };
	int b[2][2]={ 00,01,10,11 };
}
實實上, 陣列在記憶體中都是一塊連續的記憶體空間. 一維陣列時很容易想像:
int a[5];
a[0]的位址是&a+0;
a[1]的位址是&a+1;
a[2]的位址是&a+2;
a[3]的位址是&a+3;
a[4]的位址是&a+4;
二維陣列同樣會是一塊連續的記憶體空間
int a[5][5];
a[0][0]的位址是&a+0*5+0;
a[0][1]的位址是&a+0*5+1;
a[0][2]的位址是&a+0*5+2;
.....
a[1][1]的位址是&a+1*5+1;
.....
a[x][y]的位址是&a+x*5+y;
結論
int b[N][M];
b[x][y]的位址是&b+x*M+y;
int c[N][M][P];
c[x][y][z]的位址是&c+x*M*P+y*P+z;
範例:印出陣列中所有變數的記憶體位址
#include < stdio.h >

#define N 3
#define M 2

void main(void)
{
	char a[N][M];
	for ( int i=0; i < N; i++ )
	{
		for ( int j=0; j < M; j++ )
		{
			printf("%d\n",&a[i][j]);
		}
	}
}

第三節:陣列的應用(1)

範例:用一維陣列來儲存向量, 把兩個向量相加, 結果記錄在另一個陣列中
#include < stdio.h >
#include < stdlib.h >
#include < time.h >

#define DIMENSION 3
#define MAX_NUMBER 10

void main(void)
{
	int vector1[DIMENSION];
	int vector2[DIMENSION];
	int vector3[DIMENSION];
	int i;

	// 給定計算亂數的初值
	srand( time(NULL) );

	for ( i=0; i < DIMENSION; i++ )
	{
		// 利用亂數來設定數值
		vector1[i]=rand()%(MAX_NUMBER+1);
		vector2[i]=rand()%(MAX_NUMBER+1);
	}
	
	// 印出vector1的內容
	printf("(");
	for ( i=0; i < DIMENSION; i++ )
	{
		printf("%d ",vector1[i]);
	}
	printf(")+");
	// 印出vector2的內容
	printf("(");
	for ( i=0; i < DIMENSION; i++ )
	{
		printf("%d ",vector2[i]);
	}
	printf(")=");

	// 計算並印出vector3的內容
	printf("(");
	for ( i=0; i < DIMENSION; i++ )
	{
		vector3[i]=vector1[i]+vector2[i];
		printf("%d ",vector3[i]);
	}
	printf(")\n");
}
範例:記錄全班同學的考試成績, 把低於60分不及格的同學號碼印出來
#include < stdio.h >
#include < stdlib.h >
#include < time.h >

#define STUDENTS 30
#define MAX_NUMBER 100

void main(void)
{
	int i;
	int student[STUDENTS];
	// 給定計算亂數的初值
	srand( time(NULL) );

	for ( i=0; i < STUDENTS; i++ )
	{
		// 利用亂數來設定數值
		student[i]=rand()%(MAX_NUMBER+1);
	}
	
	for ( i=0; i < STUDENTS; i++ )
	{
		if ( student[i] < 60 )
		{
			printf("Student %d get %d points, fail!\n", i, student[i]);
		}
	}
}

第四節:字串

在程式語言中, 一個英文單字, 一個句子, 都可以當成一個字串. 簡單的說, 要記錄size超過一個字母的東西, 就叫做一個字串. 在C語言中, 一個一維的的字元陣列可以當成一個字串.
#include < stdio.h >
void main(void)
{
	char a[]={"Hello"};
	printf("%s \n",a);
}
上面的寫法, 相當於
#include < stdio.h >
void main(void)
{
	char a[6];
	a[0]='H';
	a[1]='e';
	a[2]='l';
	a[3]='l';
	a[4]='o';
	a[5]='\0'; // a[5]=0; 0是字串的結束符號
	printf("%s \n",a);
}
或是
#include < stdio.h >
void main(void)
{
	char a[6]={'H','e','l','l','o','\0'};
	printf("%s \n",a);
}
利用scanf來讀取一個字串的方法如下
#include < stdio.h >
void main(void)
{
	char a[80];
	scanf("%s",a);
	printf("%s \n",a);
}
用scanf來讀字串, 字串中不能有空白. 若有空白會被當成兩個不同的字串. 要讀取有空白的字串要用gets;
#include < stdio.h >
void main(void)
{
	char a[80];
	gets(a);
	printf("%s \n",a);
}
字串不能直接互相做比對, 下面的程式是沒有意義的
#include < stdio.h >

void main(void)
{
	char a[]="Hello";
	char b[]="How are you";
	// 下面的比較會變成 &a 和 &b 這兩個位址互相去比較
	if ( a==b )
	{
		printf("a==b\n");	
	}
}
比較字串要用C的庫存函式strcmp
#include < stdio.h >
#include < string.h >
void main(void)
{
	char a[]="Hello";
	char b[]="How are you";
	if ( strcmp(a,b)==0 )
	{
		printf("a==b\n");	
	}
	else
	{
		printf("a!=b\n");
	}	
}
定義在string.h中經常使用的函式有
函式名稱 用途
strcpy copy字串
strcat 把一個字串插到另一個字串的後面
strlen 計算字串的長度

範例:

#include < stdio.h >
#include < string.h >
void main(void)
{
	char a[80]="Hello,";
	char b[]=" how are you?";
	char c[80];
	strcat(a,b);
	printf("%s\n",a);
	strcpy(c,a);
	printf("%s\n",c);
	printf("%d\n", strlen(c) );
}

第4節:字串應用(1)

範例:把一個字串反過來
#include < stdio.h >
#include < string.h >

#define MAX_STRING 80

void main(void)
{
	char a[MAX_STRING];
	char b[MAX_STRING];
	int  len;
	int  i,j;
	
	gets(a);
	len=strlen(a);

	for ( i=0, j=len-1; i < len; i++, j-- )
	{
		b[j]=a[i];
	}
	b[len]='\0';
	printf("%s \n",b);
}
範例:把字串中的空白字元都拿掉
#include < stdio.h >
#include < string.h >

#define MAX_STRING 80

void main(void)
{
	char a[MAX_STRING];
	char b[MAX_STRING];
	int  len;
	int  i,j;
	
	gets(a);
	len=strlen(a);

	for ( i=0, j=0; i <= len; i++ )
	{
		if ( a[i]!=' ' )
			b[j++]=a[i];
	}
	printf("%s \n",b);
}
另一種解法
#include < stdio.h >
#include < string.h >

#define MAX_STRING 80

void main(void)
{
	char a[MAX_STRING];
	int  len;
	int  i,j;
	
	gets(a);
	len=strlen(a);

	for ( i=0, j=0; i < len; i++ )
	{
		if ( a[i]!=' ' )
			a[j++]=a[i];
	}
	a[j]='\0';
	printf("%s \n",a);
}

第五節:字串進階

如果字串當中的字元都是數字, C的庫存函式中有提供把字串轉換回數字的函式.
#include < stdio.h >
#include < stdlib.h >
void main(void)
{
	char string[]="100";
	char string2[]="0.5";
	int  num;
	double num2;
	num=atoi(string);
	// 函式atoi可以把字串換算成整數
	num2=atof(string2);
	// 函式atof可以把字串換算成浮點數
	printf("%d %lf\n",num,num2);
}
用sscanf也可以做到同樣的效果
#include < stdio.h >
#include < stdlib.h >
void main(void)
{
	char string[]="100";
	char string2[]="0.5";
	int  num;
	double num2;
	sscanf(string,"%d",&num);
	sscanf(string2,"%lf",&num2);
	// sscanf和scanf用法一樣, 只是輸入的來源改成從字串中讀取而不是從鍵盤
	printf("%d %lf\n",num,num2);
}
相對地, 數字也可以反回去轉成字串, 這要用sprintf來做到.
#include < stdio.h >
#include < stdlib.h >
void main(void)
{
	char string[80];
	int  num=5;
	float fnum=0.5;
	sprintf(string,"num=%d fnum=%f",num,fnum);
	// sprintf的用法和printf完全相同, 
	// 只是printf把結果印在螢幕上, sprintf把結果印到一個字串中.
	printf("%s\n",string);
}
atoi及atof函式本身的運作原理很簡單, 其實就是一個一個字元去把數字算出來而已. 下面是一個簡單的atoi函式的過程.
// 模擬atoi函式的運作
#include < stdio.h >
#include < stdlib.h >
#include < string.h >

void main(void)
{
	char string[80];
	int  i;
	int	 len;
	int  num;

	gets(string);
	len=strlen(string);

	num=0;
	for ( i=0; i < len; i++ )
	{
		num*=10;
		num+=string[i]-'0';
	}
	printf("%d\n",num);
}
sprintf的原理也是一個一個數字把它再換回去成為字元而已, 下面是一個把數字轉回字串的程式
#include < stdio.h >
#include < stdlib.h >
#include < string.h >

void main(void)
{
	char string[80];
	char temp;
	int  i,j;
	int  len;
	int  num;
	scanf("%d",&num);
	len=0;
	do
	{
		string[len++]='0'+num%10;
		num/=10;
	}while(num>0);
	string[len]='\0';
	// 字串反轉
	for ( i=0, j=len-1; i < len/2; i++,j-- )
	{
		temp=string[j];
		string[j]=string[i];
		string[i]=temp;
	}
	printf("%s\n",string);
}
為什麼會需要用到sprintf?有很多情形下, 不會使用到printf來做輸出. 像是在視窗作業系統 開啟一個Message Box來顯示訊息. 此時就需要先把需要輸出的數字都轉成字串, 再透過WIN32 API 函式來秀出訊息.
範例:開啟一個MessageBox來顯示計算求得的正方形面積
#include < stdio.h >
#include < windows.h >
void main(void)
{
	char string[80];
	int  r;
	int  area;
	scanf("%d",&r);
	area=r*r;
	sprintf(string,"Area=%d",area);
	MessageBox(NULL,string,"Hello",MB_OK);
	// Win32 API之一, 會開啟一個簡單的message box, 印出第2個參數的字串內容
}
那為什麼需要用到atoi/atof?用scanf來讀鍵盤時,使用者可能會輸入錯誤. 例如說要輸入數字卻輸入字元. 比較好的方法就是scanf中讀進去的東西都先把它當成字串, 檢查字串中有沒有不合理的東西出現. 不合理就要求重新輸入, 合理的話才把字串轉成數字.
#include < stdio.h >
#include < stdlib.h >
#include < string.h >

void main(void)
{
	char string[80];
	int  num;
	int  len;
	while(1)
	{
		printf("Please input an integer:");
		scanf("%s",string);
		// 檢查字串中有沒有不是數字的字元出現
		len=strlen(string);
		for ( int i=0; i < len; i++ )
		{
			if ( string[i] < '0' || string[i] > '9' )
				break;
		}
		// 如果每個字元都是'0','1',...'9'當中的任一個, 就跳出迴圈
		if ( i==len )
			break;
	}
	num=atoi(string);
	printf("I get %d\n",num);
}