Linux C一站式编程习题答案
<<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、再类比一下,八进制(或十六进制)与十进制之间如何相互换算?