《C程序设计语言》学习笔记(04)函数与程序结构

第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(){}

  1. 程序开发期间用以保留位置(待以后填充代码)
  2. 省略了返回类型,默认 int
     

4.2 返回非整型值的函数【简易累加计算器程序】

/* atof: s->双精度浮点数*/
double atof(char s[]);
  1. 声明和定义必须一致;
  2. 若没有函数原型,则函数将在第一次出现的表达式中被隐式声明。 sum += atof(line);
  3. 若未声明的名字出现在表达式,且后面紧跟圆括号,上下文认为该名字是函数名;
  4. 返回值假定为 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:定义在函数内部

外部链接:通过同一个名字对外部变量的所有引用实际上都是引用同一个对象。

作用:

  1. 可作为函数之间的数据交换一种方式,任何函数都可通过名字访问外部变量。
  2. 永久存在,值在两次函数调用之间保持不变。

逆波兰表示法:所有运算符都跟在操作数的后面。(后缀式)

#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 作用域规则

名字的作用域:程序中可以使用该名字的部分。

外部变量或函数的作用域:从声明的地方开始到文件末尾结束。

  1. 若要在外部变量定义之前使用该变量,或定义与使用不在同一源文件,则必须在声明中强制性使用 extern;
  2. 外部变量只能定义一次,且初始化只能在定义中,必须制定数组长度;
  3. 其他文件用 extern 声明来访问,不需指明数组长度。
     

4.5 头文件

  • main.c:主函数 main
  • stack.c:push 与 pop 以及他们使用的外部变量
  • gettop.c:gettop 函数
  • getch.c:getch 与 ungetch

实际程序中,他们分别来自单独编译的库,需分割成多个文件。

  • calc.h:集中存放共享的部分(声明)

    1. 期望每个文件只能访问它完成任务所需信息;
    2. 现实中维护较多头文件比较困难;
    3. 中等规模程序中,最好只用一个头文件存放共享的对象。

     

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 预处理器

  1. 文件包含 #include "文件名"#include <文件名>

  2. 宏替换 #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);  // 相当于
      
  3. 条件包含

    #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
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值