zoukankan      html  css  js  c++  java
  • 《C程序设计语言》第四章 函数和程序结构


    4.1 函数的基本知识

    如果函数定义中省略了返回值类型,则默认为int类型。

    练习4-1     编写函数strindex(s, t),它返回字符串t在s中最右边出现的位置。
    如果s中不包含t,则返回-1。
    答:
    #include <stdio.h>
    int strindex(char s[], char t[])
    {
         int i, j, k;
         for (i = 0; s[i] != '\0'; i++)     // to end of s
              ;
         for (i = i - 1; i >= 0; i--) {
              for (j = 0; t[j] != '\0'; j++)     // to end of t
                   ;
              // compare s and t in reverse order
              for (j = j - 1, k = i; j >= 0 && k >= 0; j--, k--)
                   if (s[k] != t[j])
                        break;
              if (j == -1)
                   return k + 1;
         }
         return -1;
    }
    main()
    {
         char s[] = "thisiscdaiandrachel";
         char t1[] = "cdai";
         char t2[] = "xyz";
         char t3[] = "this";
         char t4[] = "elx";
         printf("%d\n", strindex(s, t1));
         printf("%d\n", strindex(s, t2));
         printf("%d\n", strindex(s, t3));
         printf("%d\n", strindex(s, t4));
    }


    4.2 返回非整型值的函数

    由于atof函数的返回值类型不是int,因此该函数必须声明返回值的类型。
    其次,调用函数必须知道atof函数返回的是非整型值。可以在调用函数中显示声明atof函数。
    #include <stdio.h>
    #define MAXLINE 100
    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;
    }

    函数atof的声明与定义必须一致。如果atof函数与调用它的main函数放在同一源文件中,
    并且类型不一致,编译器会检测到该错误。如果atof函数式单独编译的,这种不匹配的
    错误就无法检测出来。

    如果没有函数原型,则函数将在第一次出现的表达式中被隐式声明。函数的返回值将被
    假定为int类型,但上下文并不对其参数作任何假设。

    练习4-2     对atof函数进行扩充,使它可以处理形如123.45e-6的科学表示法。其中,浮点数
    后面可能会紧跟一个e或E以及一个指数(可能有正负号)。
    答:
    #include <stdio.h>
    #include <ctype.h>

    /* atof: convert string s to double */
    double atof(char s[])
    {
         double val, power, epow;
         int i, sign, j, esign;
         for (i = 0; isspace(s[i]); i++) /* skip white space */
              ;
         // 1. Sign
         sign = (s[i] == '-') ? -1 : 1;
         if (s[i] == '+' || s[i] == '-')
              i++;
         // 2. Integer part
         for (val = 0.0; isdigit(s[i]); i++)
              val = 10.0 * val + (s[i] - '0');
         // 3. Float part
         if (s[i] == '.')
              i++;
         for (power = 1.0; isdigit(s[i]); i++, power *= 10)
              val = 10.0 * val + (s[i] - '0');
         // 4. E part
         if (s[i] == 'e' || s[i] == 'E')
              i++;
         esign = (s[i] == '-') ? -1 : 1;
         if (s[i] == '+' || s[i] == '-')
              i++;
         for (j = 0; isdigit(s[i]); i++) {
              j = 10 * j + (s[i] - '0');
         }
         for (epow = 1.0; j > 0; j--) {
              if (esign == -1)
                   epow /= 10;
              else
                   epow *= 10;
         }
         return sign * val / power * epow;
    }
    main()
    {
         printf("%.2lf\n", atof("4.85E3"));
         printf("%.2lf\n", atof("3.6"));
         printf("%.2lf\n", atof("3.6e5"));
         printf("%.2lf\n", atof("-3.6e5"));
         printf("%.8lf\n", atof("-3.6e-5"));
         printf("%.2lf\n", atof("3.6e12"));
    }


    4.3 外部变量

    外部变量定义在函数之外,可以在许多函数中使用。
    自动变量在其所在函数退出时消失,而外部变量是永久存在的。
    C语言不允许在一个函数中定义其他函数,因此函数本身是外部的。
    通过同一名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象。


    4.4 作用域

    名字的作用域指的是程序中可以使用该名字的部分。
    外部变量或函数的作用域从声明它的地方开始,到其所在文件的末尾结束。

    例如:
    main() { ... }

    int sp = 0;
    double val[MAXVAL];

    void push(double f) { ... }
    double pop(void) { ... }

    在push与pop中不需进行任何声明就可以访问变量sp与val,但是这两个变量名不能用在main函数中,
    push与pop函数也不能用在main函数中。

    如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,
    则必须在相应的变量声明中强制性地使用关键字extern。将外部变量的声明与定义严格区分开来很重要。

    变量声明用于说明变量的类型,而变量定义除此之外还将引起存储器的分配。
    上面外部变量sp和val[]的定义int sp = 0; double val[MAXVAL];将会分配存储单元;而声明extern int sp;
     extern double val[];则不会。定义中必须制定数组的长度,但extern声明则不一定要指定。

    在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern
    声明来访问它。


    4.5 头文件

    首先分割文件:
         主函数main单独放在main.c中;
         push与pop函数放在stack.c;
         getop放在getop.c
         将getch与ungetch函数放在getch.c中。
    之所以分割成多个文件,主要是考虑在实际的程序中,它们分别来自于单独编译的库。

    此外,必须考虑定义和声明在这些文件之间的共享问题。尽可能把共享的部分集中在一起,放在
    头文件calc.h中。
    对于某些中等规模的程序,最好只用一个头文件存放程序中各部分共享的对象。
    较大的程序需要使用更多的头文件,我们需要精心地组织它们。


    4.6 静态变量

    文件stack.c中定义的变量sp与val以及文件getch.c中定义的变量buf与bufp,它们仅供其所在的
    源文件中的函数使用,其他函数不能访问。用static限定外部变量与函数,可以将其后声明的对象
    的作用域限定为被编译源文件的剩余部分,达到隐藏外部对象的目的。

    static char buf[BUFSIZE];
    static int bufp = 0;

    int getch(void) { ... }
    void ungetch(int c) { ... }

    其他函数就不能访问变量buf与bufp,因此这两个名字不会和同一程序中的其他文件中的相同名字
    相冲突。

    static也可用于声明函数,则该函数名除了对该函数声明所在的文件可见外,其他文件都无法访问。

    static也可用于声明内部变量。static类型的内部变量是一种只能在某个特定函数中使用但一直占据
    存储空间的变量。

    练习4-11     修改getop函数,使其不必使用ungetch函数。


    4.7 寄存器变量

    register声明告诉编译器,将register变量放在机器的寄存器中。但编译器可以忽略此选项。

    register int x;
    f(register unsigned m)


    4.8 程序块结构

    变量i与程序块外声明的i无关。

    if (n > 0) {
         int i;
    }

    自动变量(包括形参)可以隐藏同名的外部变量与函数。

    int x;
    int y;
    f(double x)
    {
         double y;
    }


    4.9 初始化

    在不进行显式初始化的情况下,外部变量和静态变量都将被初始化为0;
    而自动变量和寄存器变量的初值为无用的信息。

    对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一次;
    对于自动变量与寄存器变量,则在每次进入函数或程序块时都将被初始化。

    对于自动变量与寄存器变量,初始化表达式可以不是常量表达式,表达式可以包含任意
    在此表达式之前已经定义的值,包括函数调用。

    数组的初始化可以在声明的后面紧跟一个初始化表达式列表。

    int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    当省略数组的长度时,编译器将把花括号中初始化表达式的个数作为数组的长度。
    如果初始化表达式的个数比数组元素少,则没有初始化的元素将被初始化为0。
    如果初始化表达式的个数比数组元素多,则是错误的。

    字符数组的初始化比较特殊:可以用一个字符串来代替初始化表达式序列。

    char pattern[] = "ould"; 等价于 char pattern[] = { 'o', 'u', 'l', 'd' };


    4.10 递归

    将一个数作为字符串打印的情况。数字是低位先于高位生成的,但必须以与此相反的次序打印。

    #include <stdio.h>

    /* printd: print n in decimal */
    void printd(int n)
    {
         if (n < 0) {
              puchar('-');
              n = -n;
         }
         if (n / 10)
              printd(n / 10);
         putchar(n % 10 + '0');
    }

    递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。
    递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写和理解。
    在描述树等递归定义的数据结构时使用递归尤为方便。

    练习4-12     运用printd函数的设计思想编写一个递归版本的itoa函数,即通过递归调用把整数
    转换为字符串。
    答:
    #include <stdio.h>
    #include <math.h>

    void itoa(int n, char s[])
    {
         static int i;
         if (n / 10)
              itoa(n / 10, s);
         else {
              i = 0;
              if (n < 0)
                   s[i++] = '-';
         }
         s[i++] = abs(n) % 10 + '0';
         s[i] = '\0';
    }

    main()
    {
         char s[10];
         itoa(189271, s);
         printf("%s\n", s);
         itoa(-9012398, s);
         printf("%s\n", s);
    }

    练习4-13     编写一个递归版本的reverse(s)函数,以将字符串s倒置。
    答:

    void reverser(char s[], int i, int len)
    {
         int c, j;
         j = len - (i + 1);
         if (i < j) {
              c = s[i];
              s[i] = s[j];
              s[j] = c;
              reverser(s, ++i, len);
         }
    }

    本题不适合用递归方法来解决。


    4.11 C预处理器

    预处理器是编译过程中单独执行的第一个步骤。两个最常用指令:#include和#define指令。
    以及其他的一些,如条件编译与带参数的宏。

    文件包含

    #include "文件名":在源文件所在位置查找文件
    #include <文件名>

    在大的程序中,#include指令是将声明捆绑在一起的较好的方法。保证了所有的源文件都具有
    相同的定义和变量声明。

    宏替换

    #define 名字 替换文本

    这是一种最简单的宏替换,后续所有出现名字记号的地方都将被替换为替换文本。
    替换只对记号进行,对括在引号中的字符串不起作用。
    替换文本可以是任意的,如:#define forever for(;;)

    宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。
    如#define max(A, B) ((A) > (B) ? (A) : (B))
    则x = max(p+q, r+s)将被替换为x = ((p+q) + (r+s)) ? (p+q) : (r+s));





  • 相关阅读:
    jquery选择器
    jquery是方便了javascript,算是其子,导入方式必须记住,js文件自己官网下载
    checkbox操作全选、反选、取消
    onsubmit事件、事件传播判定函数stoppropagetion、事件使用2种方式
    onload函数绑定,byclass数组遍历、this获取整个标签内容
    区分byid和byclass的取值方法关键是有无数组、onfocus和onblur
    dom :document、element,如何取父标签和子标签,innertext获取标签内容
    定时器,setInterval、clearInterval
    一个简单css+js的开关组件
    Bootstrap 模态框强化
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157888.html
Copyright © 2011-2022 走看看