<<Linux C編程一站式學習>>前15章習題及部分習題答案
2011-05-12 23:11
http://hi.baidu.com/wenlei168/blog/item/c24a0621003928aa4623e885.html
1、總結前面介紹的轉義序列的規律,想想在printf的格式化字符串中怎么表示一個%字符?寫個小程序
試驗一下。
printf("\%");
===================================================================
1、假設變量x和n是兩個正整數,我們知道x/n這個表達式的結果要取Floor,例如x是17,n是4,則結果
是4。如果希望結果取Ceiling應該怎么寫表達式呢?例如x是17,n是4,則結果是5;x是16,n是4,則結
果是4。
(x+n-1)/n
===================================================================
1、定義一個函數increment,它的作用是把傳進來的參數加1。例如:
void increment(int x)
{
x = x + 1;
}
int main(void)
{
int i = 1, j = 2;
increment(i); /* i now becomes 2 */
increment(j); /* j now becomes 3 */
return 0;
}
我們在main函數中調用increment增加變量i和j的值,這樣能奏效嗎?為什么?
能 定義的函數相當於一個自增的語句,調用函數后執行自增命令后就能增加變量i和j的值
2、如果在一個程序中調用了printf函數卻不包含頭文件,例如int main(void) { printf("\n"); },編
譯時會報警告:warning: incompatible implicit declaration of built-in function ‘printf’。
請分析錯誤原因。
頭文件作為一種包含功能函數、數據接口聲明的載體文件,用於保存程序的聲明(declaration),而定義
文件用於保存程序的實現 (implementation)。 沒有了頭文件,程序便不能執行實現printf函數
===================================================================
1、以下程序段編譯能通過,執行也不出錯,但是執行結果不正確(根據第 3 節 “程序的調試”的定義
,這是一個語義錯誤),請分析一下哪里錯了。還有,既然錯了為什么編譯能通過呢?
int x = -1;
if (x > 0);
printf("x is positive.\n");
正確的寫法是:
int x = -1;
if (x > 0) /*多了個分號,變成了語句,無論如何都會打印下個語句
*/
printf("%d is positive.\n", x); /*只能打印x is positive,沒有%d與冒號外的變量x*/
else printf("%d isn't positive.\n", x);
===================================================================
1、寫兩個表達式,分別取整型變量x的個位和十位。
x = x % 10 ;
x = x % 100 / 10 ;
2、寫一個函數,參數是整型變量x,功能是打印x的個位和十位。
void p(int x)
{
printf("%d %d", x%10, x%100/10);
}
===================================================================
1、把代碼段
if (x > 0 && x < 10);
else
printf("x is out of range.\n");
改寫成下面這種形式:
if (____ || ____)
printf("x is out of range.\n");
____應該怎么填?
if(x <= 0 || x >= 10)
2、把代碼段:
if (x > 0)
printf("Test OK!\n");
else if (x <= 0 && y > 0)
printf("Test OK!\n");
else
printf("Test failed!\n");
改寫成下面這種形式:
if (____ && ____)
printf("Test failed!\n");
else
printf("Test OK!\n");
____應該怎么填?
if (x < 0 && y < 0)
3、有這樣一段代碼:
if (x > 1 && y != 1) {
...
} else if (x < 1 && y != 1) {
...
} else {
...
}
要進入最后一個else,x和y需要滿足條件____ || ____。這里應該怎么填?
x = 1 || y = 1
4、以下哪一個if判斷條件是多余的可以去掉?這里所謂的“多余”是指,某種情況下如果本來應該打印
Test OK!,去掉這個多余條件后仍然打印Test OK!,如果本來應該打印Test failed!,去掉這個多余條
件后仍然打印Test failed!。
if (x<3 && y>3)
printf("Test OK!\n");
else if (x>=3 && y>=3)
printf("Test OK!\n");
else if (z>3 && x>=3)
printf("Test OK!\n");
else if (z<=3 && y>=3)
printf("Test OK!\n");
else
printf("Test failed!\n");
可以去掉的只有x<3 && y>3
最終結果為:
if (x>=3 && y>=3)
printf("Test OK!\n");
else if (z>3 && x>=3)
printf("Test OK!\n");
else if (z<=3 && y>=3)
printf("Test OK!\n");
else
printf("Test failed!\n");
===================================================================
1、編寫一個布爾函數int is_leap_year(int year),判斷參數year是不是閏年。如果某年份能被4整除
,但不能被100整除,那么這一年就是閏年,此外,能被400整除的年份也是閏年。
int is_leap_year(int year)
{
if (year % 4) {
return 0;
} else if (year % 100) {
return 1;
} else if (year % 400) {
return 0;
} else {
return 1;
}
}
2、編寫一個函數double myround(double x),輸入一個小數,將它四舍五入。例如myround(-3.51)的值
是-4.0,myround(4.49)的值是4.0。可以調用math.h中的庫函數ceil和floor實現這個函數。
double myround(double x)
{
return floor(x - 0.5) + 1.0;
}
===================================================================
1、編寫遞歸函數求兩個正整數a和b的最大公約數(GCD,Greatest Common Divisor),使用Euclid算法
:
1. 如果a除以b能整除,則最大公約數是b。
2. 否則,最大公約數等於b和a%b的最大公約數。
Euclid算法是很容易證明的,請讀者自己證明一下為什么這么算就能算出最大公約數。最后,修改你的
程序使之適用於所有整數,而不僅僅是正整數。
/*負數不是自然數,一般不討論最大公約數*/
#include <stdio.h>
int Euclid(int a,int b)
{
if(b==0)
return a;
else
return Euclid(b,a%b);
}
int main(void)
{
int m, n;
printf("Enter two integers: ");
scanf("%d %d", &m, &n);
printf("Greatest common divisor: %d\n", Euclid(m, n));
system("pause");
return 0;
}
2、編寫遞歸函數求Fibonacci數列的第n項,這個數列是這樣定義的:
fib(0)=1
fib(1)=1
fib(n)=fib(n-1)+fib(n-2)
上面兩個看似毫不相干的問題之間卻有一個有意思的聯系:
int fibonacci(int n)
{
if(n > 2)
return fibonacci(n-1) + fibonacci(n-2);
if(n < 0)
return -1;
return 1;
}
===================================================================
1、用循環解決第 3 節 “遞歸”的所有習題,體會遞歸和循環這兩種不同的思路。
循環解決求兩個正整數a和b的最大公約數:
int Euclid(int a,int b)
{
if(b==0)
return a;
else
return Euclid(b,a%b);
}
循環解決求Fibonacci數列的第n項:
int fibonacci(int n)
{
int t, a[n];
a[0] = a[1] = 1;
for(t = 2; t < n +1 ; t++)
a[t] = a[t-1] + a[t-2];
return a[n];
}
2、編寫程序數一下1到100的所有整數中出現多少次數字9。在寫程序之前先把這些問題考慮清楚:
1. 這個問題中的循環變量是什么?
2. 這個問題中的累加器是什么?用加法還是用乘法累積?
3. 在第 2 節 “if/else語句”的習題1寫過取一個整數的個位和十位的表達式,這兩個表達式怎
樣用到程序中?
#include<stdio.h>
int main(void)
{
int t, count;
count =0;
for(t = 9; t<100; t++)
{
if(t % 10 == 9)
count++;
if(t % 100 / 10== 9)
count++;
}
printf("1到100的所有整數中出現 %d 次數字9\n",count);
return 0;
}
===================================================================
1、求素數這個程序只是為了說明break和continue的用法才這么寫的,其實完全可以不用break和
continue,請讀者修改一下控制流程,去掉break和continue而保持功能不變。
#include <stdio.h>
int is_prime(int n)
{
int i;
for (i = 2; i < n; i++)
if (n % i == 0)
{
if (i == n)
return 1;
else
return 0;
}
}
int main(void)
{
int i;
for (i = 1; i <= 100; i++)
{
if (is_prime(i))
printf("%d\n", i);
}
return 0;
}
2、上一節講過怎樣把for循環改寫成等價的while循環,但也提到如果循環體中有continue語句這兩種形
式就不等價了,想一想為什么不等價了?
while(exc):
while(exc)-->...-->continue-(直接)->while(exc)-->...
for(a;b;c):
a-->b-->...-->continue-(直接)->c-->b-->...
===================================================================
1、上面打印的小九九有一半數據是重復的,因為8*9和9*8的結果一樣。請修改程序打印這樣的小九九:
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
8 16 24 32 40 48 56 64
9 18 27 36 45 54 63 72 81
#include <stdio.h>
int main(void)
{
int i, j;
for (i = 1; i <= 9; i++)
{
for (j = 1; j < i+1; j++)
{
if(i*j < 10)
printf("%d ", i*j);
else
printf("%d ", i*j);
}
printf("\n");
}
return 0;
}
2、編寫函數diamond打印一個菱形。如果調用diamond(3, '*')則打印:
*
* * *
*
如果調用diamond(5, '+')則打印:
+
+ + +
+ + + + +
+ + +
+
如果用偶數做參數則打印錯誤提示。
#include<stdio.h>
void diamond(int line, char mark)
{
int i, j;
for(i = 1; i <= line/2 + 1; i++)
{
for(j = 1; j <= line + 1 - 2*i; j++)
printf(" ");
for(j = 1; j <= 2*i - 1; j++)
printf("%c ", mark);
printf("\n");
}
for(i -= 2; i > 0; i--) /*i -= 1是錯誤的,因為上面通過i++后i又增加了一次*/
{
for(j = 1; j <= line + 1 - 2*i; j++)
printf(" ");
for(j = 1; j <= 2*i - 1; j++)
printf("%c ", mark);
printf("\n");
}
}
int main(void)
{
int line;
char mark;
scanf("%d %c",&line, &mark);
if(line % 2)
diamond(line, mark);
else printf("Error!\n");
return 0;
}
===================================================================
1、在本節的基礎上實現一個打印復數的函數,打印的格式是x+yi,如果實部或虛部為0則省略,例如:
1.0、-2.0i、-1.0+2.0i、1.0-2.0i。最后編寫一個main函數測試本節的所有代碼。想一想這個打印函數
應該屬於上圖中的哪一層?
void printf(int a, int b)
{
scanf("%f %f",&a, &b);
if (a && b)
{
if(b < 0.0)
printf("z = %f %fi\n", a, b);
else printf("z = %f + %fi\n", a, b);
}
else if (a && b == 0)
printf("z = %f\n", a);
else if (a == 0.0 && b)
printf("z = %fi\n", b);
else printf("z = 0\n");
}
數據存儲表示層
2、實現一個用分子分母的格式來表示有理數的結構體rational以及相關的函數,rational結構體之間可
以做加減乘除運算,運算的結果仍然是rational。測試代碼如下:
int main(void)
{
struct rational a = make_rational(1, 8); /* a=1/8 */
struct rational b = make_rational(-1, 8); /* b=-1/8 */
print_rational(add_rational(a, b));
print_rational(sub_rational(a, b));
print_rational(mul_rational(a, b));
print_rational(div_rational(a, b));
return 0;
}
注意要約分為最簡分數,例如1/8和-1/8相減的打印結果應該是1/4而不是2/8,可以利用第 3 節 “遞歸
”練習題中的Euclid算法來約分。在動手編程之前先思考一下這個問題實現了什么樣的數據抽象,抽象
層應該由哪些函數組成。
===================================================================
1、本節只給出了make_from_real_img和make_from_mag_ang函數的實現,請讀者自己實現real_part、
img_part、magnitude、angle這些函數。
2、編譯運行下面這段程序:
#include <stdio.h>
enum coordinate_type { RECTANGULAR = 1, POLAR };
int main(void)
{
int RECTANGULAR;
printf("%d %d\n", RECTANGULAR, POLAR);
return 0;
}
結果是什么?並解釋一下為什么是這樣的結果。
37814176 2
因為枚舉中與整型變量的定義中都有RECTANGULAR
===================================================================
1、編寫一個程序,定義兩個類型和長度都相同的數組,將其中一個數組的所有元素拷貝給另一個。既然
數組不能直接賦值,想想應該怎么實現。
===================================================================
1、用rand函數生成[10, 20]之間的隨機整數,表達式應該怎么寫?
srand((unsigned)time(NULL));
===================================================================
1、補完本節直方圖程序的main函數,以可視化的形式打印直方圖。例如上一節統計20個隨機數的結果是
:
0 1 2 3 4 5 6 7 8 9
* * * * * * * *
* * * * * * *
* * *
*
*
#include<stdio.h>
int main(void)
{
char num[64];
int i, j, a[10]={0}, max=0;
gets(num);
for(i=0; i<64; i++)
for(j=0; j<10; j++)
{
if(num[i]==j+'0')
++a[j];
}
for(i=0; i<10; i++)
max= max>a[i] ? max: a[i];
printf("0 1 2 3 4 5 6 7 8 9\n");
for(i=0; i<max; i++) /*控制列*/
for(j=0; j<10; j++) /*控制行*/
{
if(a[j]>0&&j!=9){
printf("* ");
a[j]--;
}
else if(a[j]>0&&j==9){
printf("* \n");
a[j]--;
}
else if(a[j]==0&&j!=9)printf(" ");
else if(a[j]==0&&j==9)printf("\n");
}
printf("\n");
}
2、定義一個數組,編程打印它的全排列。比如定義:
#define N 3
int a[N] = { 1, 2, 3 };
則運行結果是:
$ ./a.out
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
1 2 3
程序的主要思路是:
1. 把第1個數換到最前面來(本來就在最前面),准備打印1xx,再對后兩個數2和3做全排列。
2. 把第2個數換到最前面來,准備打印2xx,再對后兩個數1和3做全排列。
3. 把第3個數換到最前面來,准備打印3xx,再對后兩個數1和2做全排列。
可見這是一個遞歸的過程,把對整個序列做全排列的問題歸結為對它的子序列做全排列的問題,注意我
沒有描述Base Case怎么處理,你需要自己想。你的程序要具有通用性,如果改變了N和數組a的定義(比
如改成4個數的數組),其它代碼不需要修改就可以做4個數的全排列(共24種排列)。
完成了上述要求之后再考慮第二個問題:如果再定義一個常量M表示從N個數中取幾個數做排列(N == M
時表示全排列),原來的程序應該怎么改?
最后再考慮第三個問題:如果要求從N個數中取M個數做組合而不是做排列,就不能用原來的遞歸過程了
,想想組合的遞歸過程應該怎么描述,編程實現它。
===================================================================
1、看下面的程序:
#include <stdio.h>
int main(void)
{
int i;
char str[6] = "hello";
char reverse_str[6] = "";
printf("%s\n", str);
for (i = 0; i < 5; i++)
reverse_str[5-i] = str[i];
printf("%s\n", reverse_str);
return 0;
}
首先用字符串"hello"初始化一個字符數組str(算上'\0'共6個字符)。然后用空字符串""初始化一個同
樣長的字符數組reverse_str,相當於所有元素用'\0'初始化。然后打印str,把str倒序存入
reverse_str,再打印reverse_str。然而結果並不正確:
$ ./main
hello
我們本來希望reverse_str打印出來是olleh,結果什么都沒有。重點懷疑對象肯定是循環,那么簡單驗
算一下,i=0時,reverse_str[5]=str[0],也就是'h',i=1時,reverse_str[4]=str[1],也就是'e',
依此類推,i=0,1,2,3,4,共5次循環,正好把h,e,l,l,o五個字母給倒過來了,哪里不對了?用gdb跟蹤
循環,找出錯誤原因並改正。
===================================================================
1、快速排序是另外一種采用分而治之策略的排序算法,在平均情況下的時間復雜度也是Θ(nlgn),但比
歸並排序有更小的時間常數。它的基本思想是這樣的:
int partition(int start, int end)
{
從a[start..end]中選取一個pivot元素(比如選a[start]為pivot);
在一個循環中移動a[start..end]的數據,將a[start..end]分成兩半,
使a[start..mid-1]比pivot元素小,a[mid+1..end]比pivot元素大,而a[mid]就是pivot元素;
return mid;
}
void quicksort(int start, int end)
{
int mid;
if (end > start) {
mid = partition(start, end);
quicksort(start, mid-1);
quicksort(mid+1, end);
}
}
請補完partition函數,這個函數有多種寫法,請選擇時間常數盡可能小的實現方法。想想快速排序在最
好和最壞情況下的時間復雜度是多少?快速排序在平均情況下的時間復雜度分析起來比較復雜,有興趣
的讀者可以參考[算法導論]。
===================================================================
1、實現一個算法,在一組隨機排列的數中找出最小的一個。你能想到的最直觀的算法一定是Θ(n)的,
想想有沒有比Θ(n)更快的算法?
2、在一組隨機排列的數中找出第二小的,這個問題比上一個稍復雜,你能不能想出Θ(n)的算法?
3、進一步泛化,在一組隨機排列的數中找出第k小的,這個元素稱為k-th Order Statistic。能想到的
最直觀的算法肯定是先把這些數排序然后取第k個,時間復雜度和排序算法相同,可以是Θ(nlgn)。這個
問題雖然比前兩個問題復雜,但它也有平均情況下時間復雜度是Θ(n)的算法,將上一節習題1的快速排
序算法稍加修改就可以解決這個問題:
/* 從start到end之間找出第k小的元素 */
int order_statistic(int start, int end, int k)
{
用partition函數把序列分成兩半,中間的pivot元素是序列中的第i個;
if (k == i)
返回找到的元素;
else if (k > i)
從后半部分找出第k-i小的元素並返回;
else
從前半部分找出第k小的元素並返回;
}
請編程實現這個算法。
===================================================================
1、本節的折半查找算法有一個特點:如果待查找的元素在數組中有多個則返回其中任意一個,以本節定
義的數組int a[8] = { 1, 2, 2, 2, 5, 6, 8, 9 };為例,如果調用binarysearch(2)則返回3,即a[3]
,而有些場合下要求這樣的查找返回a[1],也就是說,如果待查找的元素在數組中有多個則返回第一個
。請修改折半查找算法實現這一特性。
2、編寫一個函數double mysqrt(double y);求y的正平方根,參數y是正實數。我們用折半查找來找這個
平方根,在從0到y之間必定有一個取值是y的平方根,如果我們查找的數x比y的平方根小,則x2<y,如果
我們查找的數x比y的平方根大,則x2>y,我們可以據此縮小查找范圍,當我們查找的數足夠准確時(比
如滿足|x2-y|<0.001),就可以認為找到了y的平方根。思考一下這個算法需要迭代多少次?迭代次數的
多少由什么因素決定?
3、編寫一個函數double mypow(double x, int n);求x的n次方,參數n是正整數。最簡單的算法是:
double product = 1;
for (i = 0; i < n; i++)
product *= x;
這個算法的時間復雜度是Θ(n)。其實有更好的辦法,比如mypow(x, 8),第一次循環算出x?x=x2,第二
次循環算出x2?x2=x4,第三次循環算出4?x4=x8。這樣只需要三次循環,時間復雜度是Θ(lgn)。思考一
下如果n不是2的整數次冪應該怎么處理。請分別用遞歸和循環實現這個算法。
從以上幾題可以看出,折半查找的思想有非常廣泛的應用,不僅限於從一組排好序的元素中找出某個元
素的位置,還可以解決很多類似的問題。[編程珠璣]對於折半查找的各種應用和優化技巧有非常詳細的
介紹。
===================================================================
1、修改本節的程序,要求從起點到終點正向打印路線。你能想到幾種辦法?
2、本節程序中predecessor這個數據結構占用的存儲空間太多了,改變它的存儲方式可以節省空間,想
想該怎么改。
3、上一節我們實現了一個基於堆棧的程序,然后改寫成遞歸程序,用函數調用的棧幀替代自己實現的堆
棧。本節的DFS算法也是基於堆棧的,請把它改寫成遞歸程序,這樣改寫可以避免使用predecessor數據
結構,想想該怎么做
===================================================================
1、本節的例子直接在隊列元素中加一個指針成員表示前趨,想一想為什么上一節的例 12.3 “用深度優
先搜索解迷宮問題”不能采用這種方法表示前趨?
2、本節例子中給隊列分配的存儲空間是512個元素,其實沒必要這么多,那么解決這個問題至少要分配
多少個元素的隊列空間呢?跟什么因素有關?
===================================================================
1、現在把迷宮問題的要求改一下,只要求程序給出最后結論就可以了,回答“有路能到達終點”或者“
沒有路能到達終點”,而不需要把路徑打印出來。請把例 12.4 “用廣度優先搜索解迷宮問題”改用環
形隊列實現,然后試驗一下解決這個問題至少需要分配多少個元素的隊列空間。
===================================================================
1、二進制小數可以這樣定義:
(0.A1A2A3...)2=A1×2-1+A2×2-2+A3×2-3+...
這個定義同時也是從二進制小數到十進制小數的換算公式。從本節講的十進制轉二進制的推導過程出發
類比一下,十進制小數換算成二進制小數應該怎么算?
2、再類比一下,八進制(或十六進制)與十進制之間如何相互換算?