天天看點

c 程式設計語言第三章,<<c程式設計語言>>第三章 程式控制流

3.1 語句與程式塊

在表達式之後加上一個分号(;),它們就變成了語句。

用一對花括号“{”與“}”把一組聲明和語句括在一起就構成了程式塊,在文法上等價于單條語句。

3.2 if-else語句

每個else與最近的前一個沒有else配對的if進行比對。

if (n > 0)

if (a > b)

z = a;

else

z = b;

程式的縮進結構明确表明了設計意圖,但編譯器無法獲得這一資訊,它會将else部分與内層的if配對。

是以最好經常把if else語句塊括起來!

二分查找:

int binsearch ( int x, int v[ ], int n )

{

int  low, high, mid;

low = 0;

high = n - 1;

while  ( low <= high )  {

mid = ( low + high ) / 2;

if  ( x < v[mid] )

high = mid - 1;

else if  ( x > v[mid] )

low = mid + 1;

else      

return  mid;

}

return  -1;   

}

練習3-1     上面折半查找的例子中,while循環語句内執行了兩次測試。重寫該函數,

使循環内部隻執行一次測試。比較兩種版本函數的運作時間。

答:

while (low <= high) {

mid = (low + high) / 2;

if (x < v[mid])

high = mid - 1;

else

low = mid ;

}

if (x == v[mid])

return mid;

else

return -1;

3.4   switch語句

s w i t c h語句是一種多路判定語句,它根據表達式是否與若幹常量整數值中的某一個比對來相

應地執行有關的分支動作。

switch  ( 表達式)  {

case  常量表達式:  語句序列

case  常量表達式:  語句序列

default:  語句序列

}

如果沒有default情形,且沒有任何一個情景比對,那麼該switch語句不執行任何動作。各個case的執行順序是任意的。

在s w i t c h語句中c a s e情形的作用就像标号一樣,在某個c a s e情形之後的代碼執行完後,就進入下一個c a s e情形執行,除非顯式控制轉出。轉出

s w i t c h語句最常用的方法是使用b r e a k語句與r e t u r n語句。為了防止直接進入下一個情形,最好在每個情形後以break結尾。

練習3-2     編寫一個函數escape(s, t),将字元串t複制到字元串s中,并在複制過程中将換行符、

制表符等不可見字元分别轉換為\n、\t等相應可見的轉義字元。再編寫一個相反功能的函數。

答:

#include

void escape(char s[], char t[])

{

int i, j;

for (i = 0, j = 0; s[i] != '\0'; i++) {

switch (s[i]) {

case '\n':

t[j++] = '\\';

t[j++] = 'n';

break;

case '\t':

t[j++] = '\\';

t[j++] = 't';

break;

default:

t[j++] = s[i];

break;

}

}

t[j] = '\0';

}

void escape2(char s[], char t[])

{

int i, j;

for (i = 0, j = 0; s[i] != '\0'; j++) {

switch (s[i]) {

case '\\':

if (s[i+1] == 'n') {

t[j] = '\n';

i += 2;

}

else if (s[i+1] == 't') {

t[j] = '\t';

i += 2;

}

else {

t[j] = s[i];

i++;

}

default:

t[j] = s[i++];

break;

}

}

}

練習3-3     編寫函數expand(s1, s2),将字元串s1中類似于a-z一類的速記符号在字元串s2中

擴充為等價的完整清單abc...xyz。該函數可以處理大小寫字母和數字,并可以處理a-b-c、

a-z0-9與-a-z等類似的情況。作為前導和尾随的-字元原樣排印。

答:

#include

void expand(char s1[], char s2[])

{

int i, j, k;

i = j = 0;

while (s1[i] != '\0') {

if (s1[i] == '-' && 0 < i && s1[i+1] != '\0' &&

s1[i-1] != '-' && s1[i+1] != '-') {

j--;     // avoid duplicate letter

for (k = s1[i-1]; k <= s1[i+1]; k++, j++)

s2[j] = k;

i += 2;

} else {

s2[j++] = s1[i++];

}

}

s2[j] = '\0';

}

main()

{

char s1[] = "-a-b-hAbC-G0-8---";

char s2[100];

expand(s1, s2);

printf("before expand:%s\nafter expand: %s\n", s1, s2);

}

3.5 while循環與for循環

for (表達式1; 表達式2; 表達式3)

語句

等價于=>

表達式1;

while (表達式2) {

語句

表達式3;

}

如果表達式1與表達式3 被省略了,那麼它退化成了w h i l e循環語句。

如果用于測試的表達式2 不存在,那麼就認為表達式2 的值永遠是真的,進而,f o r循環語句

for  ( ; ; )   {

}

就是一個“無限”循環語句,這種語句要用其他手段(如b r e a k語句或r e t u r n語句)才能終止執行。

例子:atoi 函數的實作,将字元轉化為數字,除去前面的空白字元,處理+ - 操作,

#include 

int atoi ( char  s[ ])

{

int  i, n , sign;

for ( i = 0;  isspace ( s[i] );  i++ )  

;

sign =  ( s[i] == '-' ) ? -1 : 1;

if  (s[i] == '+' || s[i] == '-' )   

i++;

for  ( n = 0;  isdigit ( s[i] );  i++)

n = 10 * n + (s[i] - '0' );

return  sign * n;

}

shell排序:

void shellsort ( int v[ ], int n )

{

int  gap, i, j, temp;

for  ( gap = n/2;  gap > 0;  gap /= 2 )

for  ( i = gap;  i < n;  i++ )

for  ( j = i-gap;  j >= 0 && v[j] > v[j+gap];  j -= gap )  {

temp = v[j];

v[j] = v[j+gap];

v[j+gap] = temp;

}

}

}

将整型轉化為字元串itoa的實作:

void itoa ( int n, char s[ ] );

{

int i, sign;

if ( ( sign = n ) < 0 )

n = -n;

i = 0;

do {

s[i++] = n % 10 + '0';

} while ( (n /= 10) > 0);

if (sign < 0)

s[i++] = '-';

s[i] = '\0';

reverse(s);

}

因為即使n為0也要至少把一個字元放到數組 s中,是以在這裡有必要使用 d o - w h i l e語句,至

少使用d o - w h i l e語句要友善一些。

atof 将字元串轉化為浮點數 比如123.45  ---與atoi相比,在小數點.之後開始記錄小數點位數pow*=10.0

double atof( char s[ ])

{

double val, power;

int i, sign;

for ( i = 0; isspace(s[i]); i++ )

;

sign = (s[i] == '-' ) ? -1 : 1;

if ( s[i] == '+' || s[i] == '-' )

i++;

for (val = 0.0; isdigit(s[i]); i++)

val = 10.0 * val +(s[i] -'0' );

if (s [i] ] = = '.')

i++;

for ( power = 1.0; isdigit(s[i]); i++) {

val = 10.0 * val +(s[i] -'0' );

power *= 10.0;

}

return sign * val / power;

}

練習3-4     在數的對二的補碼表示中,上面的itoa函數不能處理最大的負數-2的(字長-1)次方

的情況。解釋其原因,并修改函數使它在任何機器上運作時都能列印出正确的值。

答:

例如char字長為8位,則對二補碼範圍為-128~127。

128的二進制源碼為10000000,-128通過補碼的負數轉換規則也得到10000000,即-128二進制碼為80(可用prinf("%hhx);驗證)。即-128的二進制表示實際上市128,是以值為-128的char,n=-n;後值仍為-128。

修改函數,不将n轉為正數,而是将每次取模運算的結果轉為正數。進而避開無法将最大負數轉為正數的問題。

#include

#define abs(x) ((x) < 0 ? -(x) : (x))

void itoa(int n, char s[])

{

int i, sign;

sign = n;

i = 0;

do {

s[i++] = abs(n % 10) + '0';

} while ((n /= 10) != 0);

if (sign < 0)

s[i++] = '-';

s[i] = '\0';

//reverse(s);

}

main()

{

char s[20];

itoa(10, s);

printf("%s\n", s);

itoa(-128, s);

printf("%s\n", s);

}

練習3-5     編寫函數itob(n, s, b),将整數n轉換為以b為底的數,并将轉換結果以字元的形式

儲存到字元串s中。例如,itob(n, s, 16)把整數n格式化為十六進制整數儲存在s中。

答:

#include

#include "reverse.c"

#define abs(x) (x) < 0 ? -(x) : (x)

void itob(int n, char s[], int b)

{

int i, x, sign;

sign = n;

i = 0;

do {

x = abs(n % b);

if (x >= 10)

s[i++] = (x - 10) + 'A';          else

s[i++] = x + '0';

} while ((n /= b) != 0);

if (sign < 0)

s[i++] = '-';

s[i] = '\0';

reverse(s);

}

main()

{

char s[20];

itob(29, s, 2);

printf("%s\n", s);

itob(-257, s, 16);

printf("%s\n", s);

}

練習 3-6     修改itoa函數,使得該函數可以接收三個參數。第三個參數為最小字段寬度。

為了保證轉換後結果至少具有第三個參數指定的最小寬度,必要時在結果左邊填充一定的空格。

答:

...

if (sign < 0)

s[i++] = '-';

while (i <= w-1)     // fill space

s[i++] = ' ';

...

3.7 break和continue語句

在f o r循環語句中,c o n t i n u e語句的執行則意味着使控制傳遞到增量部分。 c o n t i n u e語句隻

能用于循環語句,不能用于 s w i t c h語句。如果某個 c o n t i n u e語句位于s w i t c h語句中,而後者又位

于循環語句中,那麼該c o n t i n u e語句用于控制下一次循環。

在循環的某些部分比較複雜時常常要使用 c o n t i n u e語句。如果不使用 c o n t i n u e語句,那麼就

可能要把測試反過來,或嵌入另一層循環,而這又會使程式的嵌套更深。

3.8 goto語句與标号

最常見的用法是在某些深度嵌套的結構中放棄處理,例如一次中止兩層或多層循環。 b r e a k語句不能直接用于這一目的,它隻能用于從最内層循環退出。下面是使用g o t o語句的一個例子:

for ( ⋯ )

for ( ⋯ ) {

if (disaster)

goto error;

}

error:

清理操作

如果錯誤處理比較重要并且在好幾個地方都會出現錯誤,那麼使用這種組織就比較靈活友善。

标号的形式與變量名字相同,其後要跟一個冒号。标号可以用在任何語句的前面,但要與

相應的g o t o語句位于同一函數中。标号的作用域是整個函數

看一個例子,考慮判定在兩個數組 a與b中是否具有相同元素的問題。一種可能的解決方 法是: for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (a[i] == b[j]) 第3章 控 制 流計計 53 下載下傳 goto found; ⋯ found: ⋯ 所有帶有g o t o語句的程式代碼都可以改寫成不包含 g o t o語句的程式,但這可能需要以增加一 些額外的重複測試或變量為代價。例如,可将這個判定數組元素是否相同的程式段改寫成如下 形式: found = 0; for (i = 0; i < n && !found; i++) for (j = 0; j < m && !found; j++) if (a[i] == b[j]) found = 1; if (found) ⋯ else ⋯ 除了以上介紹的幾個程式段外,依賴于 g o t o語句的程式段一般都比不使用 g o t o語句的程式段 難以了解與維護。雖然不特别強調這一點,但我們還是建議盡可能減少 g o t o語句的使用。