本章例程

13.1类型无关的链表查找

#include <stdio.h>
#include "node.h" Node *search_list(Node *node, void const *value,
int (*compare)(void const *, void const *))
{
while(node != NULL){
if(compare(&node->value, value) == )
break;
node = node->link;
}
return node;
}

13.2打印命令行参数

#include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv)
{
while(*++argv != NULL)
printf("%s\n",*argv);
return ;
}

13.3处理命令行参数

#include <stdio.h>
#define TRUE 1 void process_standard_input(void);
void process_file(char *file_name); int option_a,option_b /*etc.*/ ; void main(int argc, char **argv){
while(*++argv != NULL && **argv == '-'){
switch(*++*argv){
case 'a':
option_a = TRUE;
break;
case 'b':
option_b = TRUE;
break;
/*etc.*/
}
} if(*argv == NULL)
process_standard_input();
else{
do{
process_file(*argv);
}while(*++argv != NULL);
}
}

13.4神秘函数

#include <stdio.h>

void mystery(int n)
{
n += ;
n /= ;
printf("**********", - n);
}

13.5把二进制值转化为字符

#include <stdio.h>

void binary_to_ascii(unsigned int value)
{
unsigned int quotient;
quotient = value / ;
if(quotient != )
binary_to_ascii(quotient);
putchar(value % + '');
}

本章问题

1.下面显示了一列声明。

从下面的列表中挑出与上面各个声明匹配的最佳描述。

answer:

(建议可以试试书中所提到的cdecl程序验证,Linux的安装命令sudo apt get install cdecl)

2.给定下列声明:

char *array[];
char **ptr = array;

如果变量ptr加上1,它的效果是怎样的?

answer:As with all pointer arithmetic,the value one is scaled to the size of whatever the pointer is pointing at,which in this case is a pointer to a character,the result is that ptr is advanced to point at the next element of the array.

(和所有的算术指针一样,它是可以指向任何大小的标量的指针,在这个情况下它指向一个字符,所以结果就是ptr指向数组中的下一个元素)

3.假定你将要编写一个函数,它的起始部分如下所示:

void func(int ***arg){

参数类型是什么?画一张图,显示这个变量的正确用法,如果想取得这个参数所指代的整数,你应该使用怎样的表达式?

answer:

(这是一个指向指针的指针的指针,表达式***arg可以获得这个参数指向的整数)

4.下面的代码可以如果进行改进?

Transaction *trans;
trans->product->orders += ;
trans->product->quantity_on_hand -= trans->quantity;
trans->product->supplier->reorder_quantity += trans->quantity;
if(trans->product->export_restricted){
...
}

answer:

把trans声明为寄存器变量可能有所帮助,这取决于你使用的环境。在有些机器上,把指针放入寄存器的好处相当突出,其次,声明一个保存trans->product值的局部变量。这个表达式可以被多次使用,但不需要每次重新计算,有些编译器会自动为你做这两件事,但有些编译器不会。

这个Transaction的数据结构可能是下面这个样子的:

而不像链表中没有其他的数据结构,所以可以用Node **,而不用Transaction **.

5.给定下列声明:

typedef struct{
int x;
int y;
}Point; Point *p;
Point *a = &p;
Point **b = &a;

判断下面各个表达式的值:

answer:

a = &p

b = p

c = p.x

d = &a

e illegal

f  illegal

g = a = &p

h illegal

i illegal

j illegal

k illegal

l  = x

m = p

6.给定下列声明:

typedef struct{
int x;
int y;
}Point; Point x,y;
Point *a = &x, *b = &y;

解释下列各语句的含义。

a. x = y;

b. a = y;

c. a = b;

d. a = *b;

e. *a = *b;

answer:

a. Copie the values from y into x

(将y的值赋给x)

b. Illegal,as a is a pointer but y is not

(非法,因为a是一个指针而y不是)

c. Copie the pointer value from b into a

(复制指针b的值给a)

d. Illegal,as a is a pointer but *b is not

(非法,a是一个指针而*b不是)

e. Copies the value from y into x

(把y的值赋给x)

7.许多ANSI C的实现都包含了一个函数,称为getopt。这个函数用于帮助处理命令行参数,但是,getopt在标准中并未提及,拥有这样一个函数,有什么优点?又有什么缺点?

answer:

它的唯一优点如此明显,你可能没有对它多加思考,这也是编写这个函数的理由--这个函数使处理命令行参数更为容易,但这个函数的其他方面都是不利因素,你只能使用这个函数所支持的方式处理参数。由于它并不是标准的一部分,所以使用getopt将会降低程序的可移植性。

8.下面的代码段有什么错误(如果有的话)?你如何修正它?

char *pathname = "/usr/temp/xxxxxxxxxxxxxxx"
...
/*
** insert the filename in to the pathname;
*/
strcpy(pathname + , "abcde");

answer:If the filename exceeds(超过) 15 characters(this one does not),it will overflow the memory allocated to hold the string literal,If the initial value of the pathname is ever changed,you must remember to change the literal constant ten also,but the real problem is that it overwrites the string literal,the effects of which are not defined by the Standard,The solusion is to declare pathname as an array,not a pointer.

(如果文件名超过15个字符,为了保存这个字符串会使内存溢出,如果pathname的初始值被改过,你必须记得改变字面值常量10,不过在实际问题中,字符串经常被更改,其影响是标准未定义的,这个解决办法应该声明pathname为一个数组,而不是一个指针)

9.下面的代码段有什么错误(如果有的话)?你如何修正它?

char pathname[] = "/usr/temp/";
...
/*
** append the filename in to the pathname;
*/
strcat(pathname, "abcde");

answer:The array is only as large as needed to hold its initial value,so appending anything to it overflows the memory array,probably overwriting some other variables,The solution is to make the array enough larger than the initail value to hold the longest filename that will be appended to it later,A potential(可能的) problem is the use of strcat;if this pathname prefix(前缀) is suppoised to be used with several different filenames,appending the next one with strcat will not produce the desired result.

(这个数组的大小只能容纳初始值,所以在后面添加任何东西都会使数组内存溢出,可能会覆盖其他变量,这个解决办法要使数组足够大可以包含最长的文件名,一个可能存在问题的地方是strcat,如果这个文件名前缀允许使用若干不同的文件名,在后面连接一个字符串可能不会产生预期的效果)

10.下面的代码段有什么错误(如果有的话)?你如何修正它?

char pathname[] = "/usr/temp/";
...
/*
** append the filename in to the pathname;
*/
strcat(pathname, filename);

answer:If the string in filename is longer than nine characters,it will overflow the array,if the length of a pathname has an upper bound,the array should be declared at least that large.Otherwise,the length of the filename can be computed and used to obtain memeory dynamically.

(如果filename中的字符长度比九还长,将会溢出数组,如果pathname的长度有一个上界,数组至少需要声明这个长度,否则,如果filename的长度能被计算可以使用动态内存获取)

11.标准表示如果对一个字符串常量进行修改,其效果是未定义的,如果你修改了字符串常量,有可能会出现什么问题呢?

answer:首先,有些编译器把字符串常量存放在无法进行修改的内存区域,如果你试图对这类字符串常量进行修改,就会导致程序终止,其次,即使一个字符串常量在程序中使用的地方不止一处,有些编译器只保存这个字符串常量的一份拷贝。修改其中一个字符串常量将影响程序中这个字符串常量出现的所有地方,这使得调试工作极为困难,例如,如果一开始执行了下面这条语句

strcpy("hello\n", "Bye!\n");

然后再执行下面这条语句:

printf("hello\n");

将打印出Bye!

本章练习

1.编写一个程序,从标准输入中读取一些字符,并根据下面的分类计算各类字符所占的百分比:

控制字符

空白字符

数字

小写字母

大写字母

标点符号

不可打印字符

这些分类是根据ctype.h中的函数定义的,不能使用一系列的if语句。

answer:

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h> int unprint(int ch){
return !isprint(ch);
} int (*test[])(int ch) = {
iscntrl,
isspace,
isdigit,
islower,
isupper,
ispunct,
unprint
}; #define COUNT (sizeof(test) / sizeof(test[0])) char *label[] = {
"control",
"whitespace",
"digit",
"lower case",
"upper case",
"punctuation",
"unprintable"
}; int value[COUNT];
int total; int main()
{
int i;
int ch;
while((ch = getchar()) != EOF){
total += ;
for(i = ; i < COUNT; i++)
if(test[i](ch))
value[i]++;
} if(total == )
printf("no character in the input\n");
else{
for(i = ; i < COUNT; i++){
printf("%3.0f%% %s characters\n",
value[i] * 100.0 / total,
label[i]);
}
} return ;
}

2.编写一个通用目的的函数,遍历一个单链表,它应该接受两个参数,一个指向链表第一个节点的指针,和一个指向回调函数的指针,回调函数应该接受单个参数,也就是指向一个链表节点的指针,对于链表中的每个节点,都应该调用一次这个回调函数。这个函数需要知道链表节点的什么信息?

answer:

This function is probably not terribly useful,as it is nearly as easy to traverse the list on your own as it is to call a general purpose function to do it,On the other hand,the paradigm is valuable enough to justify this exercise.Note that the function still needs to know where the link is in the node structure,so it isn't all that general after all.

(这个函数可能不是特别有用,因为你自己调用一个通用目的函数来遍历这个链表几乎一样容易,另一方面,这个练习足以证明这个范式是有用的,注意这个函数仍然需要知道链表指向的节点的结构,所以它并不完全通用)

#include <stdio.h>
#inclue "node.h" void sll_traversal(Node *root,void (*trav)(Node *))
{
while(root != NULL){
trav(root);
root = root->link;
}
}

3.转换下列代码段,使它改用转移表而不是switch语句。

Node *list;
Node *current;
Transaction *transaction;
typedef enum {NEW, DELETE, FORWARD, BACKWORD,
SEARCH,EDIT} Trans_type;
...
switch(transaction->type){
case NEW:
add_new_trans(list,transaction);
break;
case DELETE:
current = delete_trans(list,transaction);
break;
case FORWORD:
current = current->next;
break;
case BACKWORD:
current = current->prev;
case SEARCH:
current = search(list,transaction);
break;
case EDIT:
edit(current,transaction);
break;
default:
printf("Illegal transaction type!\n");
break;
}

answer:

Several of the cases do their work directly;this code must be moved into functions,The main difficulty here is to comp up with a common prototype that will serve the needs of all of the necessary functions,it must contain each argument used by any function,the new functions must use the common prototype,and the existing function must be modified to match it,Most of the function will end up with at least one argument that they do not need,A pointer must be passed to current so that the appropriate funcitons can modify it.

(在不同的情况下执行不同的工作,这些代码必须移到函数中,主要的困难在于提出一个共同的原型来适应不同需要的函数,要包括任何一个函数使用的所有参数,新的函数都要使用这个共同的原型,已经存在的函数要转换来匹配新的原型,大多数函数至少有一个它们不需要的参数结尾,current必须通过指针来使函数修改它)

void add_new_trans(Node *list, Node **current, Transaction *trans);
void delete_trans(Node *list, Node **current, Transaction *trans);
void search(Node *list, Node **current, Transaction *trans);
void edit(Node *list, Node **current, Transaction *trans); void forword(Node *list, Node **current, Transaction *tran)
{
*current = (*current)->next;
} void backword(Node *list, Node **current, Transaction *tran)
{
*current = (*current)->prev;
} void (*function[])(Node *, Node **, Transaction *) = {
add_new_trans,
delete_trans,
forword,
backword,
search,
edit
}; #define COUNT (sizeof(function) / sizeof(function[0])); if(transaction->type >= NEW && transaction <= EDIT)
function[transaction->type](list, &current, transaction);
else
printf("Illegal transaction type!\n");

4.编写一个名叫sort的函数,它用于对一个任何类型的数组进行排序,为了使函数更为通用,他的其中一个参数必须是一个指向比较回调函数的指针,该回调函数由调用程序提供,比较函数接受两个参数,也就是两个指向需要进行比较的值的指针,如果两个值相等,返回0,如果第一个值小于第二个返回小于零的整数,否则,返回大于零的整数。sort函数的参数将是:

1.一个指向需要排序的数组的第一个值的指针

2.数组中值的个数

3.每个数组元素的长度

4.一个指向比较回调函数的指针

sort函数没有返回值。你将不能根据实际类型声明数组参数,因为函数应该可以对不同类型的数组进行排序,如果你把数据当成一个字符数组使用,你可以用第三个参数寻找实际数组中每个元素的起始位置,也可以用它交换两个数组元素(每次一个字节)。

对于简单的交换排序,你可以使用下面的算法,当然也可以使用你认为更好的算法。

answer:This function is patterned after the qsort function provided in the ANSI C library,The only two tricks are locating the beginning of array element and interchanging two elements.The element length is used for both tasks.

(这个函数是模仿ANSI C库中提供的qsort函数,其中有两个小把戏,一个是传递数组首元素的位置,还有就是交换两个元素,两个函数中都使用了元素的长度)

5.编写代码处理命令行参数是十分乏味的,所以最好有一个标准函数来完成这项工作。但是不同的程序以不同的方式处理它们的参数,所以,这个函数必须非常灵活,以便使它能用于更多的程序。在本题中,你将编写这样一个函数。你的函数通过寻找和提取参数来提供灵活性。用户所提供的回调函数将执行实际的处理工作。下面是函数的原型。注意它的第四个参数和第五个参数是回调函数的原型。

char ** do_args(int argc, char **argv, char *control,
void (*do_arg)(int ch, char *value),
void (*illegal_arg)(int ch));

头两个参数就是main函数的参数,main函数对它们不做修改,直接传递给do_args,第三个参数是个字符串,用于标志程序期望接受的命令行参数。最后两个参数都是函数指针,它们是由用户提供的。do_args函数按照下面这样的方式处理命令行参数:

跳过程序名参数

while 下一次参数以一个横杆开头

  对于参数横杠后面的每个字符

    处理字符

返回一个指针,指向下一个参数指针

为了“处理字符”,你首先必须观察该字符是否位于control字符串内,如果它并不位于那里,调用illegal_arg指向函数,把这个字符作为参数传递过去。如果它位于control字符串内,但它的后面并不是跟一个+号,那么就调用do_arg所指向的函数,把这个字符和一个NULL指针作为参数传递过去。

如果该字符位于control字符串内并跟一个+号,那么就应该有一个值与这个字符相联系。如果当前参数还有其他字符,它们就是我们需要的值。否则,下一个参数才是这个值。在任何一种情况下,你应该调用do_arg所指向的函数,把这个字符和指向这个值的指针传递过去。如果不存在这个值(当前参数没有其他字符,且后面不再有参数),那么你应该调用Illegal_arg函数。注意:你必须保证这个值中的字符以后不会被处理。

当所有以一个横杆开头的参数被处理完毕后,你应该返回一个指向下一个命令行参数的指针的指针(也就是一个诸如&argv[4]或argv+4的值)。如果所有的命令行参数都以一个横杆开头,你就返回一个指向“命令行参数列表中结尾的NULL指针”的指针。

这个函数必须不能修改命令行参数指针,也不能修改参数本身,为了说明这一点,假定程序prog调用这个函数:下面的例子显示了几个不同集合的参数的执行结果。

answer:

#include <stdio.h>
#include <stdlib.h> enum {NONE, FLAG, ARG}; argtype(register int ch, register int *control)
{
while(*control != '\0')
if(ch == *control++)
return *control == '+' ? ARG : FLAG;
return NONE;
} char **do_args(int argc, char **argv, char *control,
void (*do_arg)(int ch, char *value),
void (*illegal_arg)(int ch))
{ register char *argp;
register int ch;
register int skip_arg; while((argp = *++argv) != NULL && *argp == '-'){
skip_arg = ;
while(!skip_arg && (ch = *++argp) != '\0'){
switch(argtype(ch, control)){
case FLAG:
(*do_arg) (ch, NULL);
break;
case ARG:
if(*++argp != '\0' || (argp = *++argv) != NULL){
(*do_arg)(ch, argp);
skip_arg = ;
break;
}
(*illegal_arg)(ch);
return argv;
case NONE:
(*illegal_arg)(ch);
break;
}
}
}
return argv;
}

这可能是目前为止在这本书中学习最难的章节了,尤其是之前没有接触过回调函数、转接表和命令行参数,学习这一章可能会异常艰辛,所以要加油挺过这一关,可以多看几遍书,题目的话也要不停的阅读弄清楚意思了再动手,读不懂题目可能还是对内容不熟悉,另外这一章里的介绍的命令行参数的处理过程还是非常有用的,尤其对于喜欢用命令行的人和在Linux/Unix下工作的人,算是比较底层的东西。

《C与指针》第十三章练习的更多相关文章

  1. C和指针 第十三章 习题

    1,1标准输入读入字符,统计各类字符所占百分比 #include <stdio.h> #include <ctype.h> //不可打印字符 int isunprint(int ...

  2. C和指针 第十三章 高级指针话题

    高级声明: int (*f)(); 这里声明有两个括号,第二个括号是函数调用,第一个括号是聚组作用.(*f)是一个函数,所以f是指向返回整型的函数的指针.程序中的每个函数都位于,内存中某个位置,所以存 ...

  3. perl5 第十三章 Perl的面向对象编程

    第十三章 Perl的面向对象编程 by flamephoenix 一.模块简介二.Perl中的类三.创建类四.构造函数 实例变量 五.方法六.方法的输出七.方法的调用八.重载九.析构函数十.继承十一. ...

  4. C++ Primer Plus学习:第十三章

    第十三章 类继承 继承的基本概念 类继承是指从已有的类派生出新的类.例: 表 0-1 player.h class player { private: string firstname; string ...

  5. 【C++】《C++ Primer 》第十三章

    第十三章 拷贝控制 定义一个类时,需要显式或隐式地指定在此类型地对象拷贝.移动.赋值和销毁时做什么. 一个类通过定义五种特殊的成员函数来控制这些操作.即拷贝构造函数(copy constructor) ...

  6. PRML读书会第十三章 Sequential Data(Hidden Markov Models,HMM)

    主讲人 张巍 (新浪微博: @张巍_ISCAS) 软件所-张巍<zh3f@qq.com> 19:01:27 我们开始吧,十三章是关于序列数据,现实中很多数据是有前后关系的,例如语音或者DN ...

  7. <构建之法>第十三章到十七章有感以及这个项目读后感

    <构建之法>第十三章到十七章有感 第13章:软件测试方法有哪些? 主要讲了软件测试方法:要说有什么问题就是哪种效率最高? 第14章:质量保障 软件的质量指标是什么?怎么样能够提升软件的质量 ...

  8. 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记

    第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...

  9. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

  10. Gradle 1.12 翻译——第十三章 编写构建脚本

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

随机推荐

  1. DB2获取有效工作时长函数(排除节假日、排除午休时间)

    CREATE OR REPLACE FUNCTION DIFFHOURTIME_WITHOUTHOLIDAY_FUN ( STARTTIME ), ENDTIME ) ) RETURNS DOUBLE ...

  2. 004-For与Function进阶实战、Lazy的使用

    004-For与Function进阶实战.Lazy的使用 For进阶 非常常见的形式 可以加入条件表达式进行数据过滤 Function进阶 函数是有值的(默认的话为Unit),所以可以直接将结果赋值给 ...

  3. 译:Boost Property Maps

    传送门:Boost Graph Library 快速入门 原文:Boost Property Map 图的抽象数学性质与它们被用来解决具体问题之间的主要联系就是被附加在图的顶点和边上的属性(prope ...

  4. MYSQL 处理批量更新数据的一些经验。

    首先,我们需要了解下MYSQL CASE EXPRESSION 语法. 手册传送门:http://dev.mysql.com/doc/refman/5.7/en/control-flow-functi ...

  5. sac 文档使用

    目前我遇到的问题是我想要得到BHE,BHN 方向的数据,但发现IRIS下载的数据都是BH1,BH2 方向的,很困惑,请教大神后发现,原来IRIS之所以提供BH1,BH2方向是因为很多时候台站的水平方向 ...

  6. java语法基本知识

    java中,变量分为局部和成员变量.局部变量在程序运行的过程中在栈stack中分配存储空间. 从上到下是:heap, stack, data segment, code segment.

  7. 排序系列 之 直接插入排序算法 —— Java实现

    直接插入排序算法 基本思想: 把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素:排序过程即每次从无序表中取出第一个元素,将它插入到有序表中,使之成为新的 ...

  8. Mac下使用firefoxdriver

    Mac使用Firefox浏览器只需要设置WebDriver driver = new FirefoxDriver(),不需要安装firefoxdriver,前提是你的Firefox被安装在默认的位置. ...

  9. Django01

    1.创建django project 2.创建app 在一个project下可以创建多个app,比如运维系统这个project下面包含监控app.cmdb app等等,这些app共享project里的 ...

  10. 球形环境映射之angular与latlong格式互换

    这么做只是纯好奇,因为这种格式互换在实际中是没有意义的,下面映射方式互换的贴图说明了一切. 刚开始打算使用matlab进行贴图映射方式的转换,但许久不用很是生疏,而且生成图片要考虑很多事情,尤其是生成 ...