在一组文本行中查找包含字符串“ould”的行
#include <stdio.h>
#define MAXLINE 1000 //最大输入行长度
int getline (char line[], int max);
int strindex (char source[], char searchfor[]);
char pattern[] = “ould”; //待查找模式
/*找出所有与模式相匹配的行 */
main()
{
char line[MAXLINE];
int found = 0;
while (getline(line, MAXLINE) > 0)
if (strindex(line, pattern) >= 0) {
printf(“%s”, line);
found++;
}
return found;
}
/* getline函数:将行保存到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++] = c;
s[i] = ‘\0’;
return i;
}
/* strindex函数:返回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;
}
1. 函数的基本知识
Ø 函数的定义形式:
返回值类型函数名(参数声明表)
{
声明和语句
}
程序可以看成是变量定义和函数定义的集合。函数之间的通信可以通过参数,函数返回值以及外部变量进行。函数在源文件中出现的次序可以是任意的。只要保证每一个函数不被分离到多个文件中,源程序就可以分成多个文件
2. 返回非整型值的函数
#include <ctype.h>
/* atof函数:把字符串s转换为相应的双精度浮点数 */
double atof(char s[])
{
double val, power;
int i, sign;
for(i=0; isspace(s[i]); i++)
;
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;
}
return sign * val / power;
}
/*支票簿计算 */
#include <stdio.h>
#define MAXLINE 100
main()
{
double sum, atof(char []);
char line[MAXLINE];
sum = 0;
while (getline(line, MAXLINE) > 0)
printf(“\t%g\n”, sum += atof(line));
return 0;
}
3. 外部变量
由于C语言不允许在一个函数中定义其他函数,因此函数本身是“外部的”。
Ø 默认情况下,外部变量与函数具有下列性质:通过同一名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)
Ø 因为外部变量可以在全局范围内访问,这就为函数之间的数据交换提供了一种可以代替函数参数与返回值的方式。任何函数都可以通过名字访问一个外部变量
Ø 如果函数之间需要大量的变量,使用外部变量要比使用一个很长参数表更方便有效,但是这种方式可能对程序结构产生不良的影响,而且可能会导致程序中各个函数之间具有太多的数据联系。
Ø 外部变量与内部变量相比具有更大的作用域和更长的生存期。局部变量只能在函数内部使用,从其所在的函数被调用时变量开始存在,在函数退出时变量也将消失。而外部变量是永久存在的,它们的值在一次函数调用到下一次函数调用之间保持不变。因此,如果两个函数必须共享某些数据,而这两个函数互不调用对方,这种情况下最方便的方式便是把这些共享数据定义为外部变量,而不是作为函数参数传递。
逆波兰计算器
逆波兰表示表示法表示的具有加减乘除四则运算功能的计算器程序
Ø 在逆波兰表示法中,所有运算符都跟在操作数的后面
中缀表达式:(1 - 2) * (4 + 5)
逆波兰表示法:1 2 – 4 5 + * 逆波兰表示法中不需要圆括号
Ø 计算器程序的实现:每个操作数都被依次压入到栈中;当一个运算符到达时,从栈中弹出相应数目的操作数(对于二元运算符来说是两个操作数),把该运算符作用于弹出的操作数,并把运算结果再压入到栈中。
Ø 对于逆波兰表示法:1 2 – 4 5 + *:首先把1和2压入到栈中,再用两者之差-1取代它们;然后,将4和5压入到栈中,再用两者之和9取代它们。最后,从栈中取出栈顶的-1和9,并把它们的积-9压入到栈顶。到达输入行的末尾时,把栈顶的值弹出并打印
while (下一个运算符或操作数不是文件结束指示符)
if (是数)
将该数压入到栈中
else if (是运算符)
弹出所需数目的操作数
执行运算
将结果压入到栈中
else if (是换行符)
弹出并打印栈顶的值
else
出错
#include <stdio.h>
#include <stdlib.h> //为了使用atof()函数
#define MAXOP 100 //操作数
#define NUMBER ‘0’ //标识找到一个数
int getop(char []);
void push(double);
double pop(void);
main()
{
int type;
double op2;
char s[MAXOP];
while ((type = getop(s)) != EOF) {
swirch (type) {
case NUMBER:
push (atof(s));
break;
// +,*两个运算符满足交换律,操作数的弹出顺序无关紧要 |
push (pop() + pop());
break;
case ’*’:
push (pop() * pop());
break;
// -,/两个运算符的左右操作符必须加以区分。在函数调用: push (pop()-pop()); /*错*/ 中并没有定义两个pop调用的求值次序。为保证正确的次序,必须像main函数中一样把第一个值弹出到一个临时变量中 |
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;
}
#define MAXVAL 100 //栈VAL的最大深度
int sp = 0; //下一个空闲栈位置
double val[MAXVAL]; //值栈
void push(double f) /* push函数:把f压入到值栈中 */
{
if (sp < MAXVAL)
val[sp++] = f;
else
printf(“error: stack full, can’t push %g\n”, f);
}
double pop(void) /* pop函数:弹出并返回栈顶的值 */
{
if (sp > 0)
return val[--sp];
else {
printf(“error: stack empty\n”);
return 0.0;
}
}
#include <ctype.h>
int getch(void);
void ungetch(int);
int getop(char s[]) /* getop函数:获取下一个运算符或数值操作数 */
{
int i, c;
while ((s[0] = c = getch()) == ‘ ’ || c == ‘\t’)
;
s[1] = ‘\0’;
if (!isdigit(c) && c != ‘.’)
return c; //不是数
i = 0;
if (isdigit(c)) //收集整数部分
while (isdigit(s[++i] = c = getch()))
;
if (c == ‘.’) //收集小数部分
while (isdigit(s[++i] = c = getch()))
;
s[i] = ‘\0’;
if (c != EOF)
ungetch(c);
return NUMBER;
}
Ø getch函数用于读入下一个待处理的字符
Ø ungetch函数用于把字符放回到输入中
#define BUFSIZE 100
char buf[BUFSIZE]; //用于ungetch函数的缓冲区
int bufp = 0; //buf中下一个空闲位置
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;
}
Ø ungetch函数把要压回的字符放到一个共享缓冲区(字符数组)中,当该缓冲区不空时,getch函数就从缓冲区中读取字符;当缓冲区为空时,getch函数调用getchar函数直接从输入中读字符,同时增加一个下标变量来记住缓冲区中当前字符的位置。
4. 函数的定义与参数
Ø 我们通常把函数定义中圆括号内列表中出现的变量称为形式参数,而把函数调用中与形式参数对应的值称为实际参数
Ø 在C语言中,所有函数参数都是”通过值”传递的.即:传递给被调用函数的参数值存放在临时变量中,而不是存放在原来的变量中.值传递是函数间传递数据的唯一方式.调用函数将实参传递给被调用函数,被调用函数将创建同类型的形参并用实参初始化
Ø 在C语言中,被调用函数不能直接修改主调函数中变量的值,而只能修改其私有的临时副本的值
5. 作用域规则
Ø 对于在函数开头声明的局部变量来说,其作用域是声明该变量名的函数。不同函数中声明的具有相同名字的各个局部变量之间没有任何关系。函数的参数同样如此。
Ø 外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译的)文件的末尾结束
Ø 如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性的使用关键字extern
Ø 严格区分外部变量的声明与定义:变量声明用于说明变量的属性(主要是变量的类型)。而变量的定义还将引起存储器的分配:
将int sp;放在所有函数的外部,那么这条语句将定义外部变量sp,并为之分配存储单元,默认为0,同时这条语句还可以作为该源文件中其余部分的声明。
extern int sp;为源文件的其余部分声明了一个int类型的外部变量sp,但这个声明并没有建立变量或为它们分配存储单元。
Ø 在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明)。外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。
double val[MAXVAL];
extern double val[];
Ø 外部变量的初始化只能出现在其定义
6. 静态变量
Ø 普通的外部变量可以被程序的任一文件中所包含的函数使用,而被staic所修饰的外部变量只可以被与它在同一个文件中的函数使用。
Ø 外部的static声明也可用于声明函数。通常,函数名是全局可访问的,对整个程序的各个部分而言都可见。但是,如果把函数声明为static类型,则该函数名除了对该函数声明所在的文件可见外,其他文件都无法访问。
Ø static也可用于声明内部变量。static类型的内部变量同局部变量一样,是某个特定函数的变量,只能在该函数中使用,但它与局部变量不同的是,不管其所在函数是否被调用,它一直存在,而不像自动变量那样,随着所在函数的被调用和退出而存在和消失。即static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量
7. 寄存器变量
Ø register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register变量放在机器的寄存器中,这样可以使程序更小,执行速度更快。
Ø register声明只适用于局部变量以及函数的形式参数
Ø 过量的寄存器声明并没有什么害处,因为编译器可以忽略过量的或不支持的寄存器变量声明。
8. 初始化
Ø 在不进行显式初始化的情况下,外部变量和静态变量都将被初始化为0。而局部变量和寄存器变量的初值则没有定义(即初值为无用的信息)
Ø 对于外部变量和静态变量来说,初始化表达式必须是常量表达式,且只初始化一次(从概念上讲是在程序开始执行前进行初始化)
对于局部变量和寄存器变量,则在每次进入函数或程序块时都将被初始化。初始化表达式可以不是常量表达式:表达式中可以包含任意在表达式之前已经定义的值,包括函数调用
数组的初始化
Ø 数组的初始化可以在声明的后面紧跟一个初始化表达式列表,初始化表达式列表用花括号括起来,各初始化表达式之间通过逗号分隔。当省略数组的长度时,编译器将把花括号中初始化表达式的个数作为数组的长度
Ø 如果初始化表达式的个数比数组元素数少,则对外部变量,静态变量和局部变量来说,没有初始化表达式的元素将被初始化为0。如果初始化表达式的个数比数组元素多,则是错误的。不能一次将一个初始化表达式指定给多个数组元素,也不能跳过前面的数组元素而直接初始化后面的数组元素
字符数组的初始化
Ø 字符数组的初始化比较特殊:可以用一个字符串来代替初始化表达式序列
char pattern[] = “ould”;
等价于
char pattern[] = {‘o’, ‘u’, ‘l’, ‘d’, ‘\0’};
数组的长度是5(4个字符加上一个字符串结束符’\0’)
9. C预处理器
#include指令(用于在编译期间把指定文件的内容包含进当前文件中)
#include “文件名” 或 #include <文件名>
Ø 源文件的开始处通常都会有多个#include指令,它们用以包含常见的#define语句和extern声明,或从头文件中访问库函数的函数原型声明
#define指令(用任意字符序列替代一个标记)
#define 名字 替换文本
Ø 替换文本可以是任意的
#define forever for(;;)
Ø 宏定义也可以带参数
#define max(A,B) ((A) > (B) ? (A) : (B));
Ø 可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用,而不是宏调用
#undef getchar
int getchar(void) {...}
Ø 参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串
#define dprint(expr) printf(#expr “= %g\n”, expr);
dprintf(x/y); 等同于 printf(“x/y = %g\n”, x/y);
条件包含
可以使用条件语句对预处理本身进行控制
Ø #if语句对其中的常量整型表达式(不能包含sizeof、类型转换运算符或enum常量)进行求值,若该表达式的值不为0,则包含其后的各行,直到遇到#endif, #elif或#else语句为止
#if SYSTEM == SYSV
#define HDR “sysv.h”
#elif SYSTEM == BSD
#define HDR “bsd.h”
#else
#define HDR “default.h”
#endif
#include HDR
Ø 在#if语句中可以使用表达式defined(名字),该表达是遵循下列规则:
当名字已经定义时,其值为1;否则,其值为0
#if !defined(HDR)
#define HDR
/* hdr.h文件的内容放在这里 */
#endif
Ø 上述代码可写为
#ifndef HDR
#define HDR
/* hdr.h文件的内容放在这里 */
#endif