目录
第4章 函数与程序结构
4.1 函数的基本知识【模式匹配】
把程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,并且降低修改程序的难度。(封装)
/* 找到所有与模式匹配的行:getline, strindex */
#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 1000
int getLine(char line[], int max);
int strindex(char source[], char searchfor[]);
char pattern[] = "ould"; //待查找的模式
/* 找出所有与模式匹配的行 */
int main()
{
char line[MAXLINE];
int found = 0;
while(getLine(line, MAXLINE) > 0)
{
if(strindex(line, pattern) >= 0)
{
printf("strindex = %d\n", strindex(line, pattern));
printf("%s", line);
found++;
}
}
return found;
}
/* 将行保存到 s 中,并返回该行的长度*/
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++] = '\n';
s[i] = '\0';
return i;
}
/* 返回t在s中的位置,若未找到则返回-1 */
int strindex(char s[], char t[])
{
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;
}
最简单的函数:dummy(){}
- 程序开发期间用以保留位置(待以后填充代码)
- 省略了返回类型,默认 int
4.2 返回非整型值的函数【简易累加计算器程序】
/* atof: s->双精度浮点数*/
double atof(char s[]);
- 声明和定义必须一致;
- 若没有函数原型,则函数将在第一次出现的表达式中被隐式声明。 sum += atof(line);
- 若未声明的名字出现在表达式,且后面紧跟圆括号,上下文认为该名字是函数名;
- 返回值假定为 int,若带参,则要声明它们;若不带参,则使用 void 声明。
/* 简易计算器程序,累加程序 */
#include <stdio.h>
#include <ctype.h>
#define MAXLINE 30
int main()
{
double sum, atoF(char []);
char line[MAXLINE];
int getLine(char line[], int max);
sum = 0;
while(getLine(line, MAXLINE) > 0)
printf("\t%g\n", sum += atoF(line));
return 0;
}
/* 将行保存到 s 中,并返回该行的长度*/
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++] = '\n';
s[i] = '\0';
return i;
}
/* 字符串s 转换成双精度浮点数 */
double atoF(char s[])
{
double val, power;
int exp, i, sign;
for(i=0; isspace(s[i]); ++i) // 空白符:空格、换页、换行、回车、tab
;
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; // 小数点后的位数
}
val = sign * val / power;
if(s[i] == 'e' || s[i] == 'E') // 读取12.3e-4科学计数法
{
sign = (s[++i] == '-') ? -1 : 1;
if(s[i] == '+' || s[i] == '-')
i++;
for(exp = 0; isdigit(s[i]); ++i)
exp = 10.0 * exp + (s[i] - '0');
if(sign == 1)
while(exp-- > 0)
val *= 10;
else
while(exp-- > 0)
val /= 10;
}
return val;
}
4.3 外部变量【逆波兰计算器】
external:定义在函数之外的变量
internal:定义在函数内部
外部链接:通过同一个名字对外部变量的所有引用实际上都是引用同一个对象。
作用:
- 可作为函数之间的数据交换一种方式,任何函数都可通过名字访问外部变量。
- 永久存在,值在两次函数调用之间保持不变。
逆波兰表示法:所有运算符都跟在操作数的后面。(后缀式)
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXOP 100
#define NUMBER '0'
int getop(char []); // get op 拿操作符
double stof(char []);// str转double
void push(double);
double pop(void);
/* 逆波兰计算器 */
int main()
{
int type;
double op2;
char s[MAXOP];
while((type = getop(s)) != EOF)
{
switch(type)
{
case NUMBER:
push(stof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop(); // 第一个值存入临时变量,放后面
push(pop() - op2); // 第二个值/第一个值
break;
case '/':
op2 = pop();
if(op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknow command %s\n", s);
break;
}
}
return 0;
}
/* 字符串s 转换成双精度浮点数 */
double stof(char s[])
{
double val, power;
int exp, i, sign;
for(i=0; isspace(s[i]); ++i) // 空白符:空格、换页、换行、回车、tab
;
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; // 小数点后的位数
}
val = sign * val / power;
if(s[i] == 'e' || s[i] == 'E') // 读取12.3e-4科学计数法
{
sign = (s[++i] == '-') ? -1 : 1;
if(s[i] == '+' || s[i] == '-')
i++;
for(exp = 0; isdigit(s[i]); ++i)
exp = 10.0 * exp + (s[i] - '0');
if(sign == 1)
while(exp-- > 0)
val *= 10;
else
while(exp-- > 0)
val /= 10;
}
return val;
}
#define MAXVAL 100
int sp = 0;
double val[MAXVAL];
void push(double f) // 把f压入栈顶
{
if(sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g\n", f);
}
double pop(void) // 弹出并返回栈顶的值
{
if(sp > 0)
return val[--sp];
else
{
printf("error: stack empty\n");
return 0.0;
}
}
int getch(void);
void ungetch(int);
int getop(char s[])
{
int i, c;
while((s[0] = c = getch()) == ' ' || c == '\t') // 跳过空格和tab
;
s[1] = '\0'; // 字符串终止符
if(!isdigit(c) && c != '.')
return c; // 不是数字和小数点,看做是操作符op
i = 0;
if(isdigit(c)) // 收集整数部分,存s[]中
while(isdigit(s[++i] = c = getch()))
;
if(c == '.') // 收集小数部分
while(isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if(c != EOF) // 操作数收集完毕,退回c
ungetch(c);
return NUMBER; // 返回number标志,数字存于s[]中
}
#define BUFSIZE 100
char buf[BUFSIZE]; // 数组作为缓冲区 buffer
int bufp = 0; // 缓冲区中下一个空闲位置
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
if(bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
4.4 作用域规则
名字的作用域:程序中可以使用该名字的部分。
外部变量或函数的作用域:从声明的地方开始到文件末尾结束。
- 若要在外部变量定义之前使用该变量,或定义与使用不在同一源文件,则必须在声明中强制性使用 extern;
- 外部变量只能定义一次,且初始化只能在定义中,必须制定数组长度;
- 其他文件用 extern 声明来访问,不需指明数组长度。
4.5 头文件
- main.c:主函数 main
- stack.c:push 与 pop 以及他们使用的外部变量
- gettop.c:gettop 函数
- getch.c:getch 与 ungetch
实际程序中,他们分别来自单独编译的库,需分割成多个文件。
-
calc.h:集中存放共享的部分(声明)
- 期望每个文件只能访问它完成任务所需信息;
- 现实中维护较多头文件比较困难;
- 中等规模程序中,最好只用一个头文件存放共享的对象。
4.6 静态变量
4.7 寄存器变量 见第1章
4.8 程序块结构
if(n>0)
{
int i; // 变量的声明可以在程序块内
for(i=0; i<n; i++)
...
}
- 这个 i 与改程序外声明的 i 无关;
- 作用域到右花括号为止;
- 每次进入程序块时,会被重新初始化!
4.9 初始化
规则:不进行显示初始化的情况下,extern 和 static 都将初始化为 0;auto 和 register 初值没有定义。
- 初始化表达式必须是常量表达式,且只初始化一次;
- 每次进入函数或程序块都将被初始化。
char pattern[] = "ould";
char pattern[] = {'o', 'u', 'l', 'd', '\0'};
4.10 递归【快速排序】
函数直接或间接调用自身
Note:
把 mid 拿到最前面,作为比较的参照物。遍历从 left+1 到 right,比 left 小的元素换到前面(此时的left 是前面刚换过来的 mid),last 指向最后一个比 left 小的元素。最后,left 与 最后一个比 left 小的元素换位置,至此,left 元素固定,左侧<left,右侧>left。
这里实际上每次选取并固定位置的参考是 mid,和数据结构上的代码要相区别。
// 版本1:常规数组排序
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if(left >= right) // 左右下标碰到,排序结束
return;
swap(v, left, (left+right)/2); // 把 mid 拿到最前面,作为比较的参照物
last = left; // last 移动到v[0]
for(i = left+1; i<=right; ++i) // 遍历从 left+1 到 right
if(v[i] < v[left]) // 小于v[left]的元素交换到第二个元素位置,并往后推移
swap(v, ++last, i);
swap(v, left, last); // 交换left, last,如此v[last]固定位置
qsort(v, left, last-1);
qsort(v, last+1, right);
}
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
// 版本2:文本行排序,数组元素是字符串,v[left]... v[right] p94
void qsort(int *v[], int left, int right)
{
int i, last;
void swap(int *v[], int i, int j);
if(left >= right) // 左右下标碰到,排序结束
return;
swap(v, left, (left+right)/2); // 把 mid 拿到最前面,作为比较的参照物
last = left; // last 移动到v[0]
for(i = left+1; i<=right; ++i) // 遍历从 left+1 到 right
if(strcmp(v[i], v[left]) < 0)
// 小于v[left]的元素交换到第二个元素位置,并往后推移
swap(v, ++last, i);
swap(v, left, last); // 交换left, last,如此v[last]固定位置
qsort(v, left, last-1);
qsort(v, last+1, right);
}
void swap(int *v[], int i, int j)
{
char *temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
// 版本3:数据结构版
void QuickSort(int R[], int low, int high)
{
int i = low, j = high, temp;
if(low<high) //这个是函数退出标志(递归结束)
{
temp = R[0]; // 每趟搞定最左边的元素
while(i<j)
{
while(i<j && R[j]>temp) --j;
if(i<j)
R[i] = R[j];
++i;
while(i<j && R[i]<temp) ++i;
if(i<j)
R[j] = R[i];
--j;
}
R[i] = temp;
QuickSort(R, low, i-1);
QuickSort(R, i+1, high);
}
}
4.11 C 预处理器
-
文件包含
#include "文件名"
或#include <文件名>
-
宏替换
#define 名字 替换文本
-
宏定义分行,行末加反斜杠符 \
-
定义的名字作用域到源文件末尾处结束
#define forever for(;;) #define max(A,B) ((A)>(B)?(A):(B)) #define SQL(x) x*x // 边缘效应 ... SQL(3+4) = 3+4*3+4 = 19 #undef 取消名字的宏定义
-
形参不能用带引号的字符串替换,用 # 作为前缀。
#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); // 相当于
-
-
条件包含
#if SYSTEM == SYSV #define HDR "sysv.h" #elif SYSTEM == BSD // else if #define HDR "bsd.h" #else #define HDR "default.h" #endif // end if 终止if #include HDR #ifndef HDR // if not define 如果未定义 #define HDR "default.h" #endif