读书笔记:C程序设计语言,第四章:函数与程序结构 (含课后题全解)

时间:2022-12-13 13:40:43

  • 设计好的函数可以封装具体的操作细节,使整个程序的结构更加清晰,降低修改程序的难度
  • c语言多是由许多小函数组成。
  • 一个程序可以保存在一个或多个源文件中,各个文件单独编译,也可以和库中已经编译过的函数一起加载
  • 声明函数时声明参数类型
  • 每一个外部对象只能有一个定义(我的理解就是只能对全局变量定义一次)

4.1函数的基本知识

  1. 类似于unix的grep功能,请在下面的语句中:
       Ah Love! could you and I with Fate conspire
       To grasp this sorry Scheme of Things entire,
       Would not we shatter it to bits -- and then
       Re-mould it nearer to the Heart's Desire!
    找到,含有ould的句子,并打印该句。代码如下:
    #include <stdio.h>
    #define MAXLINE 1000 /* maximum input line length */
    int getline(char line[], int max)
    int strindex(char source[], char searchfor[]);
    char pattern[] = "ould"; /* pattern to search for */
    /* find all lines matching pattern */
    main()
    {
    char line[MAXLINE];
    int found = 0;
    while (getline(line, MAXLINE) > 0)
    if (strindex(line, pattern) >= 0) {
    printf("%s", line);
    found++;
    }
    return found;
    }
    /* getline: get line into s, return length */
    int getline(char s[], int lim)
    {
    int c, i;
    i = 0;
    while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
    s[i++] = c;
    if (c == '\n')
    s[i++] = c;
    s[i] = '\0';
    return i;
    }
    /* strindex: return index of t in s, -1 if none */
    int strindex(char s[], char t[])//这里返回的数组中的位置,而strstr返回的是指针
    {
    int i, j, k;
    for (i = 0; s[i] != '\0'; i++) {
    for (j=i, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++)//这是因为要将几个字符都匹配好
    ;
    if (k > 0 && t[k] == '\0')
    return i;
    }
    return -1;
    }

  2. 一个最简单的函数形式:
    dummy(){}//默认返回int类型
    可以再开发期间保留位置,用以日后填充代码
  3. return 1和return (1)没什么区别
  4. return也可以没有返回值,仅仅返回控制权
  5. unix下,如果将上面三个函数放到不同的文件中,可以如下编译:
    cc main.c getline.c strindex.c
    如果其中一个有错,那么可以只对一个编译,其他的加载,如main.c有错:
    cc main.c getline.o strindex.o

练习题

Exercise 4-1. Write the function  strindex(s,t)  which returns the position of the  rightmost  occurrence of  t in  s, or  -1 if there is none. (中文翻译的时候翻译成为了字符串t,但是显然英文题中没有说字符串,所以参考答案只给了一份查找字符t的,显然难度小了许多
/* Test driver by Richard Heathfield
* Solution (strrindex function) by Rick Dearman
*/

#include <stdio.h>

/* Write the function strrindex(s,t), which returns the position
** of the rightmost occurrence of t in s, or -1 if there is none.
*/

int strrindex( char s[], char t )
{
int i;
int count = -1;

for(i=0; s[i] != '\0'; i++)
{
if(s[i] == t)
{
count = i;
}
}

return count;
}


typedef struct TEST
{
char *data;
char testchar;
int expected;
} TEST;

int main(void)
{
TEST test[] =
{
{"Hello world", 'o', 7},
{"This string is littered with iiiis", 'i', 32},
{"No 'see' letters in here", 'c', -1}
};

size_t numtests = sizeof test / sizeof test[0];
size_t i;

char ch = 'o';
int pos;

for(i = 0; i < numtests; i++)
{
pos = strrindex(test[i].data, test[i].testchar);

printf("Searching %s for last occurrence of %c.\n",
test[i].data,
test[i].testchar);

printf("Expected result: %d\n", test[i].expected);
printf("%sorrect (%d).\n", pos == test[i].expected ? "C" : "Inc", pos);
if(pos != -1)
{
printf("Character found was %c\n", test[i].data[pos]);
}
}

return 0;
}

4.2返回非整型值的函数

  1. 一个atof的函数:将字符串s转换为相应的双精度浮点数。函数并不完美,因为还要占用了过多空间,<stdlib.h>里面有优秀的相应的函数。
    #include <ctype.h>
    /* atof: convert string s to double */
    double atof(char s[])
    {
    double val, power;
    int i, sign;
    for (i = 0; isspace(s[i]); i++) /* skip white space */
    ;
    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;
    }
    return sign * val / power;
    }

  2. 原来还可以在main函数中对函数进行声明:
    double sum, atof(char []);
    声明函数atof返回一个doule类型的值
  3. 注意atof的定义和声明必须一致,虽然不会故意犯这个错,但是当声明的函数和函数的定义不在一份源文件中的时候就会非常容易遗忘,那么将会产生不可预期的后果。因为返回的类型默认是int的。
  4. 根据atof的函数,我们可以写出atoi的函数,将字符串转化为int类型:
    /* atoi:  convert string s to integer using atof */
    int atoi(char s[])
    {
    double atof(char s[]);
    return (int) atof(s);
    }
    如果没有显示声明(int),要转换为int类型的话,那么将会给出警告信息

练习题

Exercise 4-2. Extend  atof to handle scientific notation of the form 123.45e-6 where a floating-point number may be followed by e or  E and an optionally signed exponent. 
答:
code:
/*
** Written by Dann Corbit as K&R 2, Exercise 4-2 (Page 73).
** Keep in mind that this is *JUST* a student exercise, and is
** light years away from being robust.
**
** Actually, it's kind of embarassing, but I'm too lazy to fix it.
**
** Caveat Emptor, not my fault if demons fly out of your nose,
** and all of that.
*/
#include <ctype.h>
#include <limits.h>
#include <float.h>
#include <signal.h>
#include <stdio.h>

int my_atof(char *string, double *pnumber)
{
/* Convert char string to double data type. */
double retval;
double one_tenth = 0.1;
double ten = 10.0;
double zero = 0.0;
int found_digits = 0;
int is_negative = 0;
char *num;

/* Check pointers. */
if (pnumber == 0) {
return 0;
}
if (string == 0) {
*pnumber = zero;
return 0;
}
retval = zero;

num = string;

/* Advance past white space. */
while (isspace(*num))
num++;

/* Check for sign. */
if (*num == '+')
num++;
else if (*num == '-') {
is_negative = 1;
num++;
}
/* Calculate the integer part. */
while (isdigit(*num)) {
found_digits = 1;
retval *= ten;
retval += *num - '0';
num++;
}

/* Calculate the fractional part. */
if (*num == '.') {
double scale = one_tenth;
num++;
while (isdigit(*num)) {
found_digits = 1;
retval += scale * (*num - '0');
num++;
scale *= one_tenth;
}
}
/* If this is not a number, return error condition. */
if (!found_digits) {
*pnumber = zero;
return 0;
}
/* If all digits of integer & fractional part are 0, return 0.0 */
if (retval == zero) {
*pnumber = zero;
return 1; /* Not an error condition, and no need to
* continue. */
}
/* Process the exponent (if any) */
if ((*num == 'e') || (*num == 'E')) {
int neg_exponent = 0;
int get_out = 0;
long index;
long exponent = 0;
double getting_too_big = DBL_MAX * one_tenth;
double getting_too_small = DBL_MIN * ten;

num++;
if (*num == '+')
num++;
else if (*num == '-') {
num++;
neg_exponent = 1;
}
/* What if the exponent is empty? Return the current result. */
if (!isdigit(*num)) {
if (is_negative)
retval = -retval;

*pnumber = retval;

return (1);
}
/* Convert char exponent to number <= 2 billion. */
while (isdigit(*num) && (exponent < LONG_MAX / 10)) {
exponent *= 10;
exponent += *num - '0';
num++;
}

/* Compensate for the exponent. */
if (neg_exponent) {//是否是负数
for (index = 1; index <= exponent && !get_out; index++)
if (retval < getting_too_small) {
get_out = 1;
retval = DBL_MIN;
} else
retval *= one_tenth;
} else
for (index = 1; index <= exponent && !get_out; index++) {
if (retval > getting_too_big) {
get_out = 1;
retval = DBL_MAX;
} else
retval *= ten;//每一个循环都乘以10,因为是10的次方
}
}
if (is_negative)
retval = -retval;

*pnumber = retval;

return (1);
}
/*
** Lame and evil wrapper function to give the exercise the requested
** interface. Dann Corbit will plead innocent to the end.
** It's very existence means that the code is not conforming.
** Pretend you are a C library implementer, OK? But you would fix
** all those bleeding gaps, I am sure.
*/
double atof(char *s)
{
double d = 0.0;
if (!my_atof(s, &d))
{
#ifdef DEBUG
fputs("Error converting string in [sic] atof()", stderr);
#endif
raise(SIGFPE);
}
return d;
}


char *strings[] = {
"1.0e43",//表示1.0*10^43,这里表示1.0乘以10的43次方
"999.999",
"123.456e-9",
"-1.2e-3",
"1.2e-3",
"-1.2E3",
"-1.2e03",
"cat",
"",
0
};
int main(void)
{
int i = 0;
for (; *strings[i]; i++)
printf("atof(%s) = %g\n", strings[i], atof(strings[i]));
return 0;
}

4.3 外部变量

  1. 所谓内部外部对象就是指定义在函数内的,所谓外部就是定义在函数外的,有函数也有变量
  2. 外部变量定义在函数外,所以可以供很多函数使用
  3. C语言不允许函数内部定义函数,所以函数都是外部的
  4. 外部变量是一种函数叫交换数据的一种方式
  5. 外部变量的作用域和生命周期都比较长
  6. 逆波兰表示法:
    正常:(1-2)*(4+5)
    逆波兰表示:1 2 - 4 5 + *
  7. 书中讲述了一个计算器的代码,代码和下面即将出现的练习题是一样的。(练习题要求在书中代码的基础上添加了一些功能)
    值得注意的是:当传值给程序的时候,将操作数和运算符用空格分开。这样程序才方便识别,由此带来的一个问题就是,不太懂程序getop()函数使用的getch和ungetch的问题,我觉得也很奇怪,难道两个函数的作用是想去区分开:123+是12+3还是1+23吗?那么只有12 3 +这样带空格的才能非常清晰的表达,而且程序也不错执行出错。

练习题

Exercise 4-3. Given the basic framework, it's straightforward to extend the calculator. Add the modulus (%) operator and provisions for negative numbers.答:
code:
#include<stdlib.h>
#include<stdio.h>
#include<ctype.h>
#include<math.h>

#define MAXOP 100
#define NUMBER 0
#define TRUE 1
#define FALSE 0




/* Getop: get next operator or numeric operand. */
int Getop(char s[])
{
#define PERIOD '.'
int i = 0;
int c;
int next;

/* Skip whitespace */
while((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';

/* Not a number but may contain a unary minus. */
if(!isdigit(c) && c != PERIOD && c != '-')
return c;

if(c == '-')
{
next = getch();
if(!isdigit(next) && next != PERIOD)//成立就说明这里就就是要一个负符号
{
return c;//一个负符号,不是表示负数的那种
}
c = next;
}
else
{
c = getch();
}

while(isdigit(s[++i] = c))
c = getch();
if(c == PERIOD) /* Collect fraction part. */
while(isdigit(s[++i] = c = getch()))//取了之后再赋值给s[++i],这个如果不是数字,就是不需要的
;
s[i] = '\0';//把while中多了一个c给消除掉
if(c != EOF)
unGetch(c);//这个c暂时保留,是下一个数或者运算符的
return NUMBER;
}

int main(void)
{
int type;
double op2;
char s[MAXOP];
int flag = TRUE;

while((type = Getop(s)) != EOF)
{
switch(type)
{
/* other cases snipped for brevity */

case '%':
op2 = pop();
if(op2)
push(fmod(pop(), op2));//对第一个参数除以第二个参数,求余数
else
printf("\nError: Division by zero!");
break;

}
}
return EXIT_SUCCESS;
}

Exercise 4-4. Add the commands to print the top elements of the stack without popping, to duplicate it, and to swap the top two elements. Add a command to clear the stack.答:
code:
#include<stdlib.h>
#include<stdio.h>
#include<ctype.h>
#include<math.h>

#define MAXOP 100
#define NUMBER 0
#define TRUE 1
#define FALSE 0

/* This programme is a basic calculator.

Extra cases have been added to:
1. Show the top item of the stack without permanently popping it.
2. Swap the top two items on the stack.
3. Duplicate the top item on the stack.
4. Clear the stack.

I have used functions for each of the new cases rather than have the
code inline in order to limit the physical size of the switch block.

In anticipation of the following exercise the following characters have
been used for the operations (in the same order as above): ? ~ # !
rather than use alphabetic characters.

It is actually rather difficult to be original in this exercise.

This is exercise 4-4 from Kernighan & Ritchie, page 79.

*/

int Getop(char s[]);
void push(double val);
double pop(void);
void showTop(void);
void duplicate(void);
void swapItems(void);
void clearStack();

int main(void)
{
int type;
double op2;
char s[MAXOP];
int flag = TRUE;

while((type = Getop(s)) != EOF)
{
switch(type)
{
case NUMBER:
push(atof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop()- op2);
break;
case '/':
op2 = pop();
if(op2)
push(pop() / op2);
else
printf("\nError: division by zero!");
break;
case '%':
op2 = pop();
if(op2)
push(fmod(pop(), op2));
else
printf("\nError: division by zero!");
break;
case '?':
showTop();
break;
case '#':
duplicate();
break;
case '~':
swapItems();
break;
case '!':
clearStack();
case '\n':
printf("\n\t%.8g\n", pop());
break;
default:
printf("\nError: unknown command %s.\n", s);
break;
}
}
return EXIT_SUCCESS;
}

#define MAXVAL 100

int sp = 0; /* Next free stack position. */
double val[MAXVAL]; /* value stack. */

/* push: push f onto stack. */
void push(double f)
{
if(sp < MAXVAL)
val[sp++] = f;
else
printf("\nError: stack full can't push %g\n", f);
}

/*pop: pop and return top value from stack.*/
double pop(void)
{
if(sp > 0)
return val[--sp];
else
{
printf("\nError: stack empty\n");
return 0.0;
}
}

void showTop(void)
{
if(sp > 0)
printf("Top of stack contains: %8g\n", val[sp-1]);
else
printf("The stack is empty!\n");
}


void duplicate(void)
{
double temp = pop();//复制两下的意思就是就是将栈顶元素复制两次

push(temp);
push(temp);
}

void swapItems(void)
{
double item1 = pop();
double item2 = pop();

push(item1);
push(item2);
}

/* pop only returns a value if sp is greater than zero. So setting the
stack pointer to zero will cause pop to return its error */

void clearStack(void)
{
sp = 0;
}

int getch(void);
void unGetch(int);

/* Getop: get next operator or numeric operand. */
int Getop(char s[])
{
int i = 0;
int c;
int next;

/* Skip whitespace */
while((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';

/* Not a number but may contain a unary minus. */
if(!isdigit(c) && c != '.' && c != '-')
return c;

if(c == '-')
{
next = getch();
if(!isdigit(next) && next != '.')
{
return c;
}
c = next;
}
else
c = getch();

while(isdigit(s[++i] = c))
c = getch();
if(c == '.') /* Collect fraction part. */
while(isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if(c != EOF)
unGetch(c);
return NUMBER;
}

#define BUFSIZE 100

char buf[BUFSIZE];
int bufp = 0;

/* Getch: get a ( possibly pushed back) character. */
int getch(void)
{
return (bufp > 0) ? buf[--bufp]: getchar();
}

/* unGetch: push character back on input. */
void unGetch(int c)
{
if(bufp >= BUFSIZE)
printf("\nUnGetch: too many characters\n");
else
buf[bufp++] = c;
}


Exercise 4-5. Add access to library functions like sin, exp, and pow. See <math.h> in Appendix B, Section 4.答:
code:
#include<stdlib.h>
#include<stdio.h>
#include<ctype.h>
#include<math.h>
#include <string.h>

#define MAXOP 100
#define NUMBER 0
#define IDENTIFIER 1
#define TRUE 1
#define FALSE 0

/*

The new additions deal with adding functions from math.h to the
calculator.

In anticipation of the following exercise the code deals with an
identifier in the following manner:

If the identifier is recognised as one of the supported mathematical
functions then that function from the library is called. If the
identifier is not one of the supported functions, even if it is a
valid function from math.h it is ignored.

The main changes are the introduction of another define value
(IDENTIFIER) along with its associated case in the switch statement.
Getop has also been changed to deal with reading in alphabetical
characters.

This is exercise 4-5 from Kernighan & Ritchie, page 79.

*/

int Getop(char s[]);
void push(double val);
double pop(void);
void showTop(void);
void duplicate(void);
void swapItems(void);
void clearStack();
void dealWithName(char s[]);

int main(void)
{
int type;
double op2;
char s[MAXOP];
int flag = TRUE;

while((type = Getop(s)) != EOF)
{
switch(type)
{
case NUMBER:
push(atof(s));
break;
case IDENTIFIER://还是利用的标识符的方式
dealWithName(s);
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop()- op2);
break;
case '/':
op2 = pop();
if(op2)
push(pop() / op2);
else
printf("\nError: division by zero!");
break;
case '%':
op2 = pop();
if(op2)
push(fmod(pop(), op2));
else
printf("\nError: division by zero!");
break;
case '?':
showTop();
break;
case '#':
duplicate();
break;
case '~':
swapItems();
break;
case '!':
clearStack();
case '\n':
printf("\n\t%.8g\n", pop());
break;
default:
printf("\nError: unknown command %s.\n", s);
break;
}
}
return EXIT_SUCCESS;
}

#define MAXVAL 100

int sp = 0; /* Next free stack position. */
double val[MAXVAL]; /* value stack. */

/* push: push f onto stack. */
void push(double f)
{
if(sp < MAXVAL)
val[sp++] = f;
else
printf("\nError: stack full can't push %g\n", f);
}

/*pop: pop and return top value from stack.*/
double pop(void)
{
if(sp > 0)
return val[--sp];
else
{
printf("\nError: stack empty\n");
return 0.0;
}
}

void showTop(void)
{
if(sp > 0)
printf("Top of stack contains: %8g\n", val[sp-1]);
else
printf("The stack is empty!\n");
}

/*
Alternatively:
void showTop(void)
{
double item = pop();
printf("Top of stack contains: %8g\n", item);
push(item);
}
*/


void duplicate(void)
{
double temp = pop();

push(temp);
push(temp);
}

void swapItems(void)
{
double item1 = pop();
double item2 = pop();

push(item1);
push(item2);
}

void clearStack(void)
{
sp = 0;
}

/* deal with a string/name this may be either a maths function or for
future exercises: a variable */
void dealWithName(char s[])
{
double op2;

if( 0 == strcmp(s, "sin"))
push(sin(pop()));
else if( 0 == strcmp(s, "cos"))
push(cos(pop()));
else if (0 == strcmp(s, "exp"))//幂指数
push(exp(pop()));
else if(!strcmp(s, "pow"))
{
op2 = pop();
push(pow(pop(), op2));
}
else
printf("%s is not a supported function.\n", s);
}

int getch(void);
void unGetch(int);

/* Getop: get next operator or numeric operand. */
int Getop(char s[])
{
int i = 0;
int c;
int next;
/*size_t len;*/

/* Skip whitespace */
while((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';

if(isalpha(c))
{
i = 0;
while(isalpha(s[i++] = c ))
c = getch();
s[i - 1] = '\0';
if(c != EOF)
unGetch(c);
return IDENTIFIER;//如果是字母就返回这个标识符
}

/* Not a number but may contain a unary minus. */
if(!isdigit(c) && c != '.' && c != '-')
return c;

if(c == '-')
{
next = getch();
if(!isdigit(next) && next != '.')
{
return c;
}
c = next;
}
else
c = getch();

while(isdigit(s[++i] = c))
c = getch();
if(c == '.') /* Collect fraction part. */
while(isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if(c != EOF)
unGetch(c);
return NUMBER;
}

#define BUFSIZE 100

char buf[BUFSIZE];
int bufp = 0;

/* Getch: get a ( possibly pushed back) character. */
int getch(void)
{
return (bufp > 0) ? buf[--bufp]: getchar();
}

/* unGetch: push character back on input. */
void unGetch(int c)
{
if(bufp >= BUFSIZE)
printf("\nUnGetch: too many characters\n");
else
buf[bufp++] = c;
}


Exercise 4-6. Add commands for handling variables. (It's easy to provide twenty-six variables with single-letter names.) Add a variable for the most recently printed value.
答:
下段代码完成如下功能:
读书笔记:C程序设计语言,第四章:函数与程序结构 (含课后题全解)
code:
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>

#define MAXOP 100
#define NUMBER 0
/* 4-6 these are new for this exercise*/
#define IDENTIFIER 1
#define ENDSTRING 2
/* 4-6 end of new stuff */
#define TRUE 1
#define FALSE 0
#define MAX_ID_LEN 32
#define MAXVARS 30

/*
The new additions deal with adding variables to the calculator.

If the identifier is recognised as one of the supported mathematical
functions then that function from the library is called. If the
identifier is not one of the supported functions, even if it is a
valid function from math.h it is ignored.

This is a class 1 solution as it uses structures which are not
introduced until Chapter 6. This allows the use of "normal" names for
variables rather than the suggested single letter though any
identifier is limited to 31 characters.

The main changes are:

1. The introduction of two more define values (IDENTIFIER,
ENDSTRING) along with associated cases in the switch statement.
2. Getop has also been changed to deal with reading in alphabetical
characters and coping with the '=' sign.
3. A structure to hold the variable name and value.
4. Another case in the switch statement to deal with the '=' sign.
5. Altering the clearStack function to clear the array of structs as
well as the stack.
6. The '<' operator now prints the last accessed variable.

Improvements:
The code could be made class 0 by the use of "parallel" arrays for the
names and values rather than a struct but this would be messy and is
the situation that structs were made for.
The use of a binary tree together with dynamically allocated memory
would allow the arbitrary limit of 30 variables to be avoided. This
would still be a class 1 solution.

This is exercise 4-6 from Kernighan & Ritchie, page 79.
*/

/* 4-6 this is new for this program */
struct varType{
char name[MAX_ID_LEN];
double val;
};
/* 4-6 End of new stuff */

int Getop(char s[]);
void push(double val);
double pop(void);
void showTop(void);
void duplicate(void);
void swapItems(void);

/* 4-6 this is new for this program */
/* Changed clearStack(void) to clearStacks(struct varType var[])*/
void clearStacks(struct varType var[]);
void dealWithName(char s[], struct varType var[]);
void dealWithVar(char s[], struct varType var[]);

int pos = 0;
struct varType last;

/* 4-6 End of new stuff */

int main(void)
{
int type;
double op2;
char s[MAXOP];
struct varType var[MAXVARS];

/* Use the new function here */
clearStacks(var);

while((type = Getop(s)) != EOF)
{
switch(type)
{
case NUMBER:
push(atof(s));
break;
case IDENTIFIER:
dealWithName(s, var);
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop()- op2);
break;
case '/':
op2 = pop();
if(op2)
push(pop() / op2);
else
printf("\nError: division by zero!");
break;
case '%':
op2 = pop();
if(op2)
push(fmod(pop(), op2));
else
printf("\nError: division by zero!");
break;
case '?':
showTop();
break;
case '#':
duplicate();
break;
case '~':
swapItems();
break;
case '!':
clearStacks(var);
break;
case '\n':
printf("\n\t%.8g\n", pop());
break;
/* 4-6 this is new for this program */
case ENDSTRING:
break;
case '=':
pop();//关于为什么要pop两下才能拿到呢,
var[pos].val = pop();//其实和那个push的后++和那个pop的先--有关系,不要纠结
//实际调试的时候发现问题,稍稍改改就好了
//pos储存的就是last的在数组的下标
last.val = var[pos].val;
push(last.val);
break;
case '<':
printf("The last variable used was: %s (value == %g)\n",
last.name, last.val);
break;
/* 4-6 End of new stuff */
default:
printf("\nError: unknown command %s.\n", s);
break;
}
}
return EXIT_SUCCESS;
}

#define MAXVAL 100

int sp = 0; /* Next free stack position. */
double val[MAXVAL]; /* value stack. */

/* push: push f onto stack. */
void push(double f)
{
if(sp < MAXVAL)
val[sp++] = f;
else
printf("\nError: stack full can't push %g\n", f);
}

/*pop: pop and return top value from stack.*/
double pop(void)
{
if(sp > 0)
{
return val[--sp];
}
else
{
printf("\nError: stack empty\n");
return 0.0;
}
}

void showTop(void)
{
if(sp > 0)
printf("Top of stack contains: %8g\n", val[sp-1]);
else
printf("The stack is empty!\n");
}

/*
Alternatively:
void showTop(void)
{
double item = pop();
printf("Top of stack contains: %8g\n", item);
push(item);
}
*/

void duplicate(void)
{
double temp = pop();

push(temp);
push(temp);
}

void swapItems(void)
{
double item1 = pop();
double item2 = pop();

push(item1);
push(item2);
}

/* 4-6 this is new for this program */
/* Altered to clear both the main stack and that of the variable
structure */
void clearStacks(struct varType var[])
{
int i;

/* Clear the main stack by setting the pointer to the bottom. */
sp = 0;

/* Clear the variables by setting the initial element of each name
to the terminating character. */
for( i = 0; i < MAXVARS; ++i)
{
var[i].name[0] = '\0';
var[i].val = 0.0;
}
}

/* a string/name may be either a maths function or a variable */
void dealWithName(char s[], struct varType var[])
{
double op2;

if(!strcmp(s, "sin"))
push(sin(pop()));
else if(!strcmp(s, "cos"))
push(cos(pop()));
else if (!strcmp(s, "exp"))
push(exp(pop()));
else if(!strcmp(s, "pow"))
{
op2 = pop();
push(pow(pop(), op2));
}
/* Finally if it isn't one of the supported maths functions we have a
variable to deal with. */
else
{
dealWithVar(s, var);
}
}

/* Our identifier is not one of the supported maths function so we have
to regard it as an identifier. */
void dealWithVar(char s[], struct varType var[])
{
int i = 0;

while(var[i].name[0] != '\0' && i < MAXVARS-1)
{
//如果在之前的var里面已经有这个字段了的话,就替换
if(!strcmp(s, var[i].name))//s是新输入的字符 相等时返回0
{
strcpy(last.name, s);//将后一个复制到前一个参数
last.val = var[i].val;//这样的目的,在于查询,然后要查询b的值
push(var[i].val);//那么要有个办法把其储存到last里面,
pos = i;
return;
}
i++;
}

/* variable name not found so add it */
strcpy(var[i].name, s);
/* And save it to the last variable */
strcpy(last.name, s);
push(var[i].val);
pos = i;
}
/* 4-6 End of new stuff */

int getch(void);
void unGetch(int);

/* Getop: get next operator or numeric operand. */
int Getop(char s[])
{
int i = 0;
int c;
int next;

/* Skip whitespace */
while((s[0] = c = getch()) == ' ' || c == '\t')
{
;
}
s[1] = '\0';

if(isalpha(c))
{
i = 0;
while(isalpha(s[i++] = c ))
{
c = getch();
}
s[i - 1] = '\0';
if(c != EOF)
unGetch(c);
return IDENTIFIER;
}

/* Not a number but may contain a unary minus. */
if(!isdigit(c) && c != '.' && c != '-')
{
/* 4-6 Deal with assigning a variable. */
if('=' == c && '\n' == (next = getch()))
{
unGetch('\0');
return c;
}
if('\0' == c)
return ENDSTRING;

return c;
}

if(c == '-')
{
next = getch();
if(!isdigit(next) && next != '.')
{
return c;
}
c = next;
}
else
{
c = getch();
}

while(isdigit(s[++i] = c))
{
c = getch();
}
if(c == '.') /* Collect fraction part. */
{
while(isdigit(s[++i] = c = getch()))
;
}
s[i] = '\0';
if(c != EOF)
unGetch(c);
return NUMBER;
}

#define BUFSIZE 100

int buf[BUFSIZE];
int bufp = 0;

/* Getch: get a ( possibly pushed back) character. */
int getch(void)
{
return (bufp > 0) ? buf[--bufp]: getchar();
}

/* unGetch: push character back on input. */
void unGetch(int c)
{
if(bufp >= BUFSIZE)
printf("\nUnGetch: too many characters\n");
else
buf[bufp++] = c;
}
 
Exercise 4-7. Write a routine ungets(s) that will push back an entire string onto the input. Should ungets know about buf and bufp, or should it just use ungetch?
答:
需要使用:buf和bufp,能够仅仅使用ungetch函数,下段代码就是完成这个过程。
code:
/* K&R Exercise 4-7 */
/* Steven Huang */

#include <string.h>
#include <stdio.h>

#define BUFSIZE 100

char buf[BUFSIZE]; /* buffer for ungetch */
int bufp = 0; /* next free position in buf */

int getch(void) /* get a (possibly pushed back) character */
{
return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c) /* push character back on input */
{
if(bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}

/*
ungets() actually takes a little bit of thought. Should the
first character in "s" be sent to ungetch() first, or should
it be sent last? I assumed that most code calling getch()
would be of this form:

char array[...];
int i;

while (...) {
array[i++] = getch();
}

In such cases, the same code might call ungets() as:

ungets(array);

and expect to repeat the while loop to get the same string
back. This requires that the last character be sent first
to ungetch() first, because getch() and ungetch() work with
a stack.

To answer K&R2's additional question for this problem,
it's usually preferable for something like ungets() to just
build itself on top of ungetch(). This allows us to change
ungetch() and getch() in the future, perhaps to use a linked
list instead, without affecting ungets().
*/
void ungets(const char *s)
{
size_t i = strlen(s);

while (i > 0)
ungetch(s[--i]);
}

int main(void)
{
char *s = "hello, world. this is a test.";
int c;

ungets(s);
while ((c = getch()) != EOF)
putchar(c);
return 0;
}

Exercise 4-8. Suppose that there will never be more than one character of pushback. Modify getch and ungetch accordingly.
答案:
code:
/* K&R Exercise 4-8 */
/* Steven Huang */

#include <stdio.h>

int buf = EOF; /* buffer for ungetch */

int getch(void) /* get a (possibly pushed back) character */
{
int temp;

if (buf != EOF) {
temp = buf;
buf = EOF;
} else {
temp = getchar();
}
return temp;
}

void ungetch(int c) /* push character back on input */
{
if(buf != EOF)
printf("ungetch: too many characters\n");
else
buf = c;
}

int main(void)
{
int c;

while ((c = getch()) != EOF) {
if (c == '/') {//完全搞不懂这段代码到底在啥子
putchar(c);//
if ((c = getch()) == '*') {
ungetch('!');
}
}
putchar(c);
}
return 0;
}


Exercise 4-9. Our getch and ungetch do not handle a pushed-back EOF correctly. Decide what their properties ought to be if an EOF is pushed back, then implement your design.
No solution 

Exercise 4-10. An alternate organization uses getline to read an entire input line; this makes getch and ungetch unnecessary. Revise the calculator to use this approach.
No solution 

4.4 作用域规则

  1. 外部变量和函数可以分开放在不同源文件中,我认为这样是便于管理
  2. 作用域是指可以使用某个变量和函数的部分
  3. 必须使用extern的有两种情况
    外部变量的定义之前使用该变量
    外部变量的定义与变量的使用不在同一文件中
  4. 外部变量的声明和与定义区分是非常有必要的
    声明用于说明变量的熟悉
    定义还引起存储器的分配

    定义如下:
    int sp;
    声明如下:
    extern int sp;
  5. 定义在一个程序(含多个源文件)中只有一个,声明可以有多个。
  6. 如果是数组,定义必须指定数组长度,声明不用
  7. 外语变了的初始化只能出现在其定义
  8. 使用extern可以满足在同一个文件,先使用后定义的情况。

4.5 头文件

  1. 主要讲述如何组织头文件,以前面那个计算器的例子说明的,比如栈就应该放在一个单独的文件中
  2. 头文件不应该太多,难以维护
  3. 头文件存放程序各个部分共享的对象

4.6 静态变量

  1. 用static修饰的变量仅能让其源文件的函数使用,达到的了外部隐藏的目的
  2. static不仅能用于变量,函数也可以,效果一样
  3. 如果用static修饰函数内的变量,那么该变量会一直存在,占据着存储空间,但是别的函数时访问不到的

练习题

Exercise 4-11.  Modify  getop so that it doesn't need to use  ungetch. Hint: use an internal  static variable. 
答:
网页打不开

4.7寄存器变量

  1. register告诉编译器,声明的变量使用频率高,放在寄存器中。使程序更小、执行速度更快。
然而实际使用的时候,不会那么如意。因为底层环境对这件事做了限制。具体限制和机子有关
  1. .我们一般可以访问变量在内存中的地址,但是无法访问在寄存器中的地址

4.8程序块结构

  1. c不允许函数中的函数
  2. 但是程序块,一般来用{}框起来的都是程序块,有if、for
  3. 在程序块中声明的变量只能在程序块中使用
  4. 函数中定义的变量和函数外的变量,即使名字相同,其实都是不一样的
  5. 我们应该尽量避免程序

4.9 初始化

  1. 不进行显示初始化的时候,外部变量和静态变量将被初始化为0。而自动变量则没有定义,即是无用信息
  2. 外部变量和静态变量,初始化表达式必须是常量表达式,初始化一次
  3. 自动变量的初始化等效于简写的赋值语句
  4. 数组的初始化,可以采用以下形式:
      int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
  5. 不能跳过前面的数组元素而直接初始化后面的数组元素
  6. 下面两者等价:
     char pattern = "ould";

      char pattern[] = { 'o', 'u', 'l', 'd', '\0' };
    数组长度为5,不是4,因为"\0"也占一个位置

4.10 递归

  1. 函数可以直接调用或者间接调用自身就是递归
  2. 函数的递归调用,每次都会得到一个与以前的自动变量的集合不同的自动变量的集合
  3. 递归并不节省存储器的开销,因为递归的调用过程中I许在某个地方维护一个存储处理值的栈
  4. 一种书上说的简单的快速排序,我觉得比较复杂,:
    /************************************************************************/
    /*
    0 1 2 3 4 5 这一排是数组下标
    | | | | | |
    4 6 5 3 0 2

    left=0,right=5 0+5/2=2 ,用e代表left,用a代表last,用r代表right
    a
    e r
    | |
    5 6 4 3 0 2

    a
    e i r
    | | |
    5 6 4 3 0 2

    a
    e i r
    | | |
    5 6 4 3 0 2

    a
    e i r
    | | |
    5 6 4 3 0 2


    e a i r 相当于以5为枢纽
    | | | |
    5 4 6 3 0 2

    e a i r
    | | | |
    5 4 3 6 0 2

    e a i r
    | | | |
    5 4 3 6 0 2

    e a i r
    | | | |
    5 4 3 0 6 2

    i
    e a r
    | | |
    5 4 3 0 6 2


    i
    e a r last++,并且交换
    | | |
    5 4 3 0 2 6

    i
    e a r swap(v,left,last)
    | | |
    2 4 3 0 5 6

    到此我觉得全过程已经非常清楚了,就是这个方法真的简单吗?
    我怎么觉得很难理解啊 */
    /************************************************************************/
    #include <stdio.h>
    /* swap: interchange v[i] and v[j] */
    void swap(int v[], int i, int j)
    {
    int temp;
    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
    }
    /* qsort: sort v[left]...v[right] into increasing order */
    void qsort(int v[], int left, int right)
    {
    int i, last;
    void swap(int v[], int i, int j);
    if (left >= right) /* do nothing if array contains */
    return; /* fewer than two elements */
    swap(v, left, (left + right)/2); /* move partition elem */
    last = left; /* to v[0] */
    for (i = left + 1; i <= right; i++) /* partition */
    if (v[i] < v[left])
    swap(v, ++last, i);
    swap(v, left, last); /* restore partition elem */
    qsort(v, left, last-1);
    qsort(v, last+1, right);
    }
    void main(){
    int v[]={4,5,2,6,1};
    qsort(v,0,4);

    }

练习题

Exercise 4-12.  Adapt the ideas of  printd to write a recursive version of  itoa; that is, convert an integer into a string by calling a recursive routine. 
答:
code:
#include <stdlib.h>
#include <stdio.h>

char *utoa(unsigned value, char *digits, int base)
{
char *s, *p;

s = "0123456789abcdefghijklmnopqrstuvwxyz"; /* don't care if s is in
* read-only memory
*/
if (base == 0)
base = 10;
if (digits == NULL || base < 2 || base > 36)
return NULL;
if (value < (unsigned) base) {
digits[0] = s[value];//相当于没有高位,只有个位
digits[1] = '\0';
} else {
for (p = utoa(value / ((unsigned)base), digits, base);*p;p++);//肯定会定,因为digits[1] = '\0';
utoa( value % ((unsigned)base), p, base);
}
return digits;
}

char *itoa(int value, char *digits, int base)
{
char *d;
unsigned u; /* assume unsigned is big enough to hold all the
* unsigned values -x could possibly be -- don't
* know how well this assumption holds on the
* DeathStation 9000, so beware of nasal demons
*/

d = digits;
if (base == 0)
base = 10;
if (digits == NULL || base < 2 || base > 36)
return NULL;
if (value < 0) {
*d++ = '-';
u = -value;
} else
u = value;
utoa(u, d, base);
return digits;
}
int main(){
int a=531;
char s[10];
itoa(a,s,10);
printf("%s",s);
getchar();

}

Exercise 4-13.  Write a recursive version of the function  reverse(s), which reverses the string  s in place. 
答:
code,下面这个做法相当高端,调试着看:
/*

EXERCISE 4-13 Gregory Pietsch

*/

static void swap(char *a, char *b, size_t n)
{
while (n--) {
*a ^= *b;//最好调试着看
*b ^= *a;//这样的做法有点高端哦
*a ^= *b;//
a++;
b++;
}
}

void my_memrev(char *s, size_t n)
{
switch (n) {
case 0:
case 1:
break;
case 2://2和3情况一样
case 3:
swap(s, s + n - 1, 1);
break;
default:
my_memrev(s, n / 2);
my_memrev(s + ((n + 1) / 2), n / 2);
swap(s, s + ((n + 1) / 2), n / 2);
break;
}
}
/************************************************************************/
/* 这个函数只需要一个参数哦 */
/************************************************************************/
void reverse(char *s)
{
char *p;

for (p = s; *p; p++)
;
my_memrev(s, (size_t)(p - s));//这样也能得到数组长度?两个char指针想减
}

int main(){
char s[]="abcd";
reverse(s);
}

4.11 c预处理器

  1. 预处理器是变异过程中过程单独执行的第一个步骤,
  2. 基本而常用的预处理器指令有两个:
    #define 用任意字符串代替一个标记
    #include 用任意在编译期间把指定文本的内容包含进当前文件
  3. #include “” 表示从源文件所在位置查找该文件,如果没有则用相应的规则查找文件
    #include <>直接用相应的规则查找文件
  4. 被包含的文件发生变化,那么就需要重新编译
  5. #define 叫宏替换
  6. #define 一般一行,如果想换行可以使用末尾加上反斜杠即可\
  7. #define 从的作用域从定义处开始,直到编译的源文件末尾处结束
  8. #define替换对在引号内的内容不进行替换
  9. #define替换的任意性
     #define  forever  for (;;)    /* infinite loop */
  10. 还可以带参数
    #define  max(A, B)  ((A) > (B) ? (A) : (B))
    等于下一句:
    x = ((p+q) > (r+s) ? (p+q) : (r+s));
  11. 替换是以代码的形式插入,而非函数调用,调用函数的开销比较大
  12. #define使用很容易出错,注意用括号:
    #define square(x)  x * x  /* WRONG */

    调用时: square(z+1).
  13. #undef getchar可以取消宏
  14. #define无法用带引号的文本替换,但是可以使用#来应对这样的情况:

    #define  dprint(expr)   printf(#expr " = %g\n", expr)

     dprint(x/y)

     printf("x/y" " = &g\n", x/y); 等于 
    printf("x/y = &g\n", x/y);
  15. define中的##,比较高级,相当于连接两个东西,看看例子:

    #define  paste(front, back)  front ## back
    调用:paste(name, 1),返回:name1
  16. #if #endif #elif #else,属于条件包含的三个方法,其中elif为else if
  17. 是否定义过?使用关键字:defined,如果定义了返回1,否则返回0,可以避免多次包含头文件
    #if !defined(HDR)
    #define HDR
    /* contents of hdr.h go here */
    #endif
  18. #ifdef  和 #ifndef,与上一段类似
     #ifndef HDR
    #define HDR
    /* contents of hdr.h go here */
    #endif


  19. 举例:检测system变量,然后选择包含的文件。
       #if SYSTEM == SYSV
    #define HDR "sysv.h"
    #elif SYSTEM == BSD
    #define HDR "bsd.h"
    #elif SYSTEM == MSDOS
    #define HDR "msdos.h"
    #else
    #define HDR "default.h"
    #endif
    #include HDR

练习题

Exercise 4-14.  Define a macro  swap(t,x,y) that interchanges two arguments of type  t. (Block structure will help.) 
/*
* Solution to exercise 4-14 in K&R2, page 91:
*
*Define a macro swap(t,x,y) that interchanges two arguments of type t.
*(Block structure will help.)
*
* Feel free to modify and copy, if you really must, but preferably not.
* This is just an exercise in preprocessor mechanics, not an example of
* how it should really be used. The trickery is not worth it to save three
* lines of code.
*
* To exchange the values of two variables we need a temporary variable and
* this one needs a name. Any name we pick, the user of the macro might also
* use. Thus, we use the preprocessor argument concatenation operator ## to
* create the name from the actual variable names in the call. This guarantees
* that the result won't be either of the actual arguments. In order to
* make sure the result also does not fall into the implementation's name
* space, we prefix the name with something safe.
*
* Lars Wirzenius <liw@iki.fi>
*/

#include <stdio.h>
/************************************************************************/
/* 我觉得这个define无非就是命名了一个变量而已,然后做了一个替换而已嘛
由于##是用实际参数来##前后的参数。
下题中的 safe是怎么个意思
int safe?
*/
/************************************************************************/
#define swap(t, x, y) \
do { \
t safe ## x ## y; \
safe ## x ## y = x; \
x = y; \
y = safe ## x ## y; \
} while (0)

int main(void) {
int ix, iy;
double dx, dy;
char *px, *py;

ix = 42;
iy = 69;
printf("integers before swap: %d and %d\n", ix, iy);
swap(int, ix, iy);
printf("integers after swap: %d and %d\n", ix, iy);

dx = 123.0;
dy = 321.0;
printf("doubles before swap: %g and %g\n", dx, dy);
swap(double, dx, dy);
printf("integers after swap: %g and %g\n", dx, dy);

px = "hello";
py = "world";
printf("pointers before swap: %s and %s\n", px, py);
swap(char *, px, py);
printf("integers after swap: %s and %s\n", px, py);
getchar();
return 0;
}


其他问题总结

在Linux下使用fmod函数的一个问题

在使用gcc编译的时候:
gcc test.c -lm

eclipse下向控制台输出EOF

ctrl+d即可,如果不能话, 解决eclipse中CDT控制台无法输入EOF(文件结束符)的问题