人數

2010年6月26日 星期六

課外補充陣列及字串

網路上搜集到的一份資料 是關於 陣列 還有字串的部份
大家常有的 字串 轉int ...等 或許可以參考方法 解決
[陣列及字串]
簡介
為什要用陣列? 想像一下如果我們要寫一個程式, 程式要求要輸入全班同學的期中考數學科成績, 寫成程式會長什麼樣子? 
#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的好處在這裏就可以看出來. 

#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) );
}

字串應用(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);
}

沒有留言: