C语言程序设计学习笔记(三)

第六章 循环控制结构

循环结构:需要重复执行的操作

被重复执行的语句序列称为循环体

  • 计数控制的循环
  • 条件控制的循环
  • 当型循环结构
  • 直到型循环结构
  • for
  • while
  • do-while
while(循环控制表达式)
    {
        语句序列
    }

计算循环控制表达式的值,如果循环控制表达式的值为真,执行循环体中的语句,返回;如果循环控制表达式的值为假,退出循环。

do
{
    语句序列
}while(循环控制表达式);

执行循环体中的语句,计算循环控制表达式的值,如果循环控制表达式的值为真,返回;如果循环控制表达式的值为假,退出循环。

for(初始化表达式;循环控制表达式;增值表达式)
    {
        语句序列
    }

初始化表达式的作用是为循环控制变量赋初值,决定了循环的起始条件

如何对循环变量进行增值,决定了循环的执行次数

如果在循环体内再次改变这个变量的值,将改变循环正常的执行次数

//L6-1

#include <stdio.h>
main()
{
    int  i, n, sum;
    printf("Input n:");
    scanf("%d", &n);
    sum = 0;      /* 累加和变量初始化为0 */
    for (i=1; i<=n; i++)
    {
        sum = sum + i;        /* 做累加运算 */ } printf("sum = %d\n", sum); }

逗号运算符:可把多个表达式连接在一起,作用是实现对各个表达式的顺序求值

空语句(仅由一个分号构成):表示什么也不做,常用于编写延时程序

//L6-2

#include <stdio.h>
main()
{
    int   i, n;    
    long  p = 1;        /* 因阶乘值取值范围较大,故p定义为长整型,并赋初值1 */
    printf("Please enter n:");
    scanf("%d", &n);    
    for (i=1; i<=n; i++) { p = p * i; /* 做累乘运算 */ } printf("%d! = %ld\n", n, p); /* 以长整型格式输出n的阶乘值 */ }

//L6-3

#include <stdio.h>
main()
{
    int   i, n;    
    long  p = 1;       
    printf("Please enter n:");
    scanf("%d", &n);    
    for (i=1; i<=n; i++) { p = p * i; printf("%d! = %ld\n", i, p); /* 输出1~n之间的所有数的阶乘值 */ } }

//L6-4(嵌套循环)

#include <stdio.h>
main()
{
    int  i, j, n;
    long p, sum = 0;      /* 累加求和变量sum初始化为0 */
    printf("Input n:");
    scanf("%d", &n);
    for (i=1; i<=n; i++) { p = 1; /* 每次循环之前都要将累乘求积变量p赋值为1 */ for (j=1; j<=i; j++) { p = p * j; /* 累乘求积 */ } sum = sum + p; /* 累加求和 */ } printf("1!+2!+…+%d! = %ld\n", n, sum); }
//运行结果
Input n:4
1!+2!+…+4! = 33

编写累加求和程序的关键在于寻找累加项的构成规律

  • 当累加项较为复杂或者前后项之间无关时,需要单独计算每个累加项
  • 当累加项的前项和后项之间有关时,可以根据关系通过前项来计算后项

//6-5

#include  <stdio.h>
main()
{
    int i, j;       
    for (i=0; i<3; i++)      /* 控制外层循环执行3次 */
    {
        printf("i=%d: ", i);
        for (j=0; j<4; j++)     /* 控制内层循环执行4次 */
        {
        printf("j=%d ", j); } printf("\n"); } }
//运行结果
i=0: j=0 j=1 j=2 j=3
i=1: j=0 j=1 j=2 j=3
i=2: j=0 j=1 j=2 j=3

//6-6(条件控制的循环)

#include  <stdlib.h>
#include  <stdio.h>
main()
{
    int  magic;     /* 计算机"想"的数 */
    int  guess;     /* 用户猜的数 */
    magic = rand();     /* 调用随机函数"想"一个数magic */
    printf("Please guess a magic number:");
    scanf("%d", &guess);    /* 输入用户猜的数guess */
    if (guess > magic)     /*若guess>magic,则提示"Wrong!Too big"*/ printf("Wrong! Too big!\n"); else if (guess < magic)/*若guess<magic,则提示"Wrong!Too small"*/ printf("Wrong! Too small!\n"); else /* 否则提示"Right!"并打印这个数 */ printf("Right!\n"); }

//6-7

#include  <time.h>        /* 将函数time()所需要的头文件time.h包含到程序中 */
#include  <stdlib.h>
#include  <stdio.h>
main()
{
    int  magic, guess, counter = 0;    
    srand(time(NULL));        /* 为函数rand()设置随机数种子 */
    magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); scanf("%d", &guess); counter ++; if (guess > magic) printf("Wrong! Too big!\n"); else if (guess < magic) printf("Wrong! Too small!\n"); else printf("Right!\n"); }while (guess != magic); printf("counter = %d\n", counter); }
//运行结果
Please guess a magic number:50
Wrong! Too small!
Please guess a magic number:75
Wrong! Too big!
Please guess a magic number:62
Wrong! Too big! Please guess a magic number:56 Wrong! Too big! Please guess a magic number:53 Wrong! Too big! Please guess a magic number:51 Right! counter = 6

//L6-8

#include  <time.h>       
#include  <stdlib.h>
#include  <stdio.h>
main()
{
    int  magic, guess, counter = 0;    
    srand(time(NULL));    
    magic = rand() % 100 + 1;      
    do{ printf("Please guess a magic number:"); scanf("%d", &guess); counter ++; if (guess > magic) printf("Wrong! Too big!\n"); else if (guess < magic) printf("Wrong! Too small!\n"); else printf("Right!\n"); }while (guess!=magic && counter<10);/*猜不对且未超过10次时继续猜*/ printf("counter = %d\n", counter); }

//L6-9

#include  <time.h>       
#include  <stdlib.h>
#include  <stdio.h>
main()
{
    int  magic, guess, counter = 0;    
    int ret;     /* 保存函数scanf()的返回值 */
    srand(time(NULL));    
    magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); ret = scanf("%d", &guess); while (ret != 1) /* 若存在输入错误,则重新输入 */ { while (getchar() != '\n'); /* 清除输入缓冲区中的非法字符 */ printf("Please guess a magic number:"); ret = scanf("%d", &guess); } counter++; if (guess > magic) printf("Wrong!Too big!\n"); else if (guess < magic) printf("Wrong!Too small!\n"); else printf("Right!\n"); } while (guess!=magic && counter<10); /*猜不对且未超过10次时继续猜*/ printf("counter = %d\n", counter); }

//L6-10

#include  <time.h> 
#include  <stdlib.h>
#include  <stdio.h>     
main()
{
    int  magic, guess, counter = 0, ret;        
    char reply;        /* 保存用户输入的回答 */
    srand(time(NULL)); 
    do{ magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); ret = scanf("%d", &guess); while (ret != 1) /* 若存在输入错误,则重新输入 */ { while (getchar() != '\n'); /* 清除输入缓冲区中的非法字符 */ printf("Please guess a magic number:"); ret = scanf("%d", &guess); } counter++; if (guess > magic) printf("Wrong!Too big!\n"); else if (guess < magic) printf("Wrong!Too small!\n"); else printf("Right!\n"); } while (guess!=magic && counter<10); /*猜不对且未超10次继续猜*/ printf("counter = %d\n", counter); printf("Do you want to continue(Y/N or y/n)?"); /*提示是否继续*/ scanf(" %c", &reply); /* %c前有一个空格 */ }while (reply=='Y' || reply=='y'); /* 输入Y或y则程序继续 */ }

goto、break、continue和return语句是C语言中用于控制流程转移的跳转语句

goto语句为无条件转向语句,它既可以向下跳转,也可往回跳转

它的作用是在不需要任何条件的情况下直接使程序跳转到该语句标号所标识的语句去执行

其中语句标号代表goto语句转向的目标位置,应使用合法的标识符表示语句标号,其命名规则与变量名相同

break语句用于退出switch结构,也可用于由while、do-while和for构成的循环体中

当执行循环体遇到break语句时,循环将立即终止,从循环语句后的第一条语句开始继续执行

//L6-11

#include <stdio.h>
main()
{
    int  i, n;
    for (i=1; i<=5; i++)
    {
        printf("Please enter n:");
        scanf("%d", &n);
        if (n < 0)    goto END; printf("n = %d\n", n); } END:printf("Program is over!\n"); }

continue语句与break语句都可用于对循环进行内部控制,但二者对流程的控制效果是不同的

当在循环体中遇到continue语句时,程序将跳过continue语句后面尚未执行的语句,开始下一次循环

即只结束本次循环的执行,并不终止整个循环的执行

//L6-12

#include <stdio.h>
main()
{
    int  i, n;
    for (i=1; i<=5; i++)
    {
        printf("Please enter n:");
        scanf("%d", &n);
        if (n < 0)     continue; printf("n = %d\n", n); } printf("Program is over!\n"); }

在嵌套循环的情况下,break语句和continue语句只对包含它们的最内层的循环语句起作用,不能用break语句跳出多重循环。

若要跳出多重循环,break语句只能一层一层地跳出,显然goto语句是跳出多重循环的一条捷径。

以下两种情况使用goto语句可以提高程序的执行效率,使程序结构更清晰

  • 快速跳出多重循环
  • 跳出共同的出口位置,进行退出前的错误处理工作

//L6-13

#include <stdio.h>
main()
{
    int  x;     
    int find = 0;    /* 置找到标志变量为假 */
    for (x=1; !find; x++)        /* find为假时继续循环 */
    {
        if (x%5==1 && x%6==5 && x%7==4 && x%11==10)
        {
        printf("x = %d\n", x);
        find = 1;    /* 置找到标志变量为真 */ } } }
//运行结果
x = 2111

结构化程序设计:

结构化程序设计是一种进行程序设计的原则和方法,按照这种原则和方法设计的程序,具有结构清晰、容易阅读、容易修改、容易验证等特点

  • 程序应该只有一个入口和一个出口
  • 不应有不可达语句和死循环
  • 尽量避免使用goto语句,因为它破坏了结构化设计风格
  • 采用自顶向下、逐步求精的模块化设计方法

常用的程序调试与排错方法:

  • 除为了取得堆栈轨迹和一两个变量的值之外,尽量不要使用排错系统,因为人很容易在复杂数据结构和控制流的细节中迷失方向
  • 有时以单步运行遍历程序的方式还不如努力思考,并辅之以在关键位置增设打印语句和测试代码,后者的效率更高

排错策略:

  • 缩减输入数据,设法找到导致失败的最小输入
  • 注释掉一些代码,分而治之
  • 增量测试

类型溢出

//L6-14

#include <stdio.h>
main()
{
    long i, sum = 0;
    for (i=1; ;i++)
    {
        sum = sum + i*i*i;
        if (sum >= 1000000)      break;
    }       
    printf("count = %d\n", i); } 
//运行结果
count = 45

//L6-15

#include <stdio.h>
main()
{
    double term, result = 1; 
    int n;
    for (n=2; n<=100; n=n+2)
    {
        term = (double)(n * n) / ((double)( n - 1) * ( n + 1)); 
        result = result * term; } printf("pi = %f\n", 2 * result); }
//运行结果
pi = 3.126079

第七章 函数

分而治之:

  • 把较大的任务分解成若干较小、较简单的任务,并提炼出公用任务的方法
  • 函数是C语言中模块化程序设计的最小单位,既可以把每个函数都看作一个模块,也可以将若干相关的函数合并成一个模块

信息隐藏:

  • 把函数内部的信息对不需要这些信息的其他模块隐藏起来,让使用者不必关注函数内部是如何做的
  • 只知道它能做什么以及如何使用它即可,从而使得整个程序的结构更加紧凑,逻辑也更清晰

标准库函数:使用ANSIC的库函数,必须在程序的开头将该函数所在的头文件包含进来

自定义函数

函数在使用之前必须定义

函数名是函数的唯一标识,用于说明函数的功能

为了便于区分,通常变量名用小写字母开头的单词组合而成,函数名则用大写字母开头的单词组合而成

Windows风格:

  • 函数名使用“动词”或者“动词+名词”的形式
  • 变量名使用“名词”或者“形容词+名词”的形式

函数体必须用一对花括号{}包围,这里的花括号是函数体的界定符

在函数内部定义的变量只能在函数体内访问,称为内部变量

函数头部参数表里的变量,称为形式参数,也是内部变量

形参表是函数的入口

函数名相当于运算的规则,形参表里的形参相当于运算的操作数,函数的返回值就是运算的结果

若函数没有返回值,则需用void定义返回值的类型

若函数不需要入口参数,则用void代替函数头文件形参表中的内容

在函数定义的前面写上一段注释来描述函数的功能及其形参,是一个非常好的编程习惯

函数调用:有main()的程序才能运行

  • 主调函数
  • 被调函數
  • 参数传递

//L7-1

#include <stdio.h>
long  Fact(int  n);      /* 函数原型声明 */
main()
{
    int  m;
    long ret;
    printf("Input m:");
    scanf("%d", &m);
    ret = Fact(m);      /* 调用函数Fact(),并将函数的返回值存入ret */
    printf("%d! = %ld\n", m, ret);
}
/* 函数功能:用迭代法计算n! */
long  Fact(int  n)      /* 函数定义 */
{
    int  i;
    long result = 1;
    for (i=2; i<=n; i++)      
    {       
     result *= i;
    }
    return result;
}

函数的返回值只有一个,函数的返回值的类型可以是除数组以外的任何类型

函数中的return语句可以由多个,但不表示函数可以有多个返回值

防御性程序设计:在程序中增加一些代码,专门用于处理某些异常情况的技术

程序的健壮性(Robustness):在函数的入口处增加对函数参数合法性的检查,就是一种常用的增强程序健壮性的方法

//L7-2

#include <stdio.h>
long  Fact(int  n);     
int main()
{
    int  m;
    long ret;
    printf("Input m:");
    scanf("%d", &m);
    ret = Fact(m);
    if (ret == -1)     /* 增加对函数返回值的检验 */
     printf("Input data error!\n");
    else
     printf("%d! = %ld\n", m, ret);
    return 0;
}
/* 函数功能:用迭代法计算n! 当n>=0时,返回n!的值;否则返回-1 */
long  Fact(int  n)
{
    int  i;
    long result = 1;
    if (n < 0)     /* 增加对函数入口参数合法性的检查 */
    {
     return -1;
    }
    else
    {
        for (i=2; i<=n; i++) 
    result *= i;
        return result;
    }
}

实参的数量必须与形参相等,它们的类型必须匹配,匹配的原则与变量赋值的原则一致

//L7-3

#include <stdio.h>
unsigned long  Fact(unsigned int  n);     
int main()
{
    int  m;
    do{
     printf("Input m(m>0):");
     scanf("%d", &m);
    }while (m<0);    /* 增加对输入数据的限制,确保输入的数据为无符号整数 */
    printf("%d! = %lu\n", m, Fact(m)); /* 无符号长整型格式输出阶乘值 */
    return 0;
}
/*函数功能:用迭代法计算无符号整型变量n的阶乘 */
unsigned long  Fact(unsigned int  n)
{
    unsigned int  i;
    unsigned long result = 1;
    for (i=2; i<=n; i++) 
     result *= i;
    return result;
}

//L7-4

#include <stdio.h>
unsigned long  Fact(unsigned int  n);     
int main()
{
      int m, k;
      unsigned long p;
      do{
      printf("Input m,k (m>=k>0):");
      scanf("%d,%d", &m, &k);
      }while (m<k||m<=0||k<0);
      p = Fact(m) / (Fact(k) * Fact(m-k));
      printf("p = %lu\n", p);
return 0;
}
/* 函数功能:用迭代法计算无符号整型变量n的阶乘 */
unsigned long  Fact(unsigned int  n)
{
unsigned int  i;
    unsigned long result = 1;
    for (i=2; i<=n; i++) 
    result *= i;
    return result;
}

函数设计的基本原则:

  • 函数的规模要小
  • 函数的功能要单一
  • 每个函数只有一个入口和一个出口
  • 在函数的接口中清楚地定义函数的行为
  • 在函数的入口处,对参数的有效性进行检查
  • 在执行某些敏感性操作时(除法、开方、取对数、赋值、参数传递),应检查操作数及其类型的合法性以避免发生除零、数据溢出、类型不匹配等因思维不缜密而引起的错误
  • 要考虑到函数调用失败应如何处理
  • 当函数需要返回值时,应确保函数中的所有控制分支都有返回值。函数没有返回值时应用void声明

递归:一个对象部分地由它自己组成或按它自己定义

递归的典型实例:字典、阶乘

//L7-5

#include <stdio.h>
long  Fact(int  n);
int main()
{
    int  n;
    long result;
    printf("Input n:");
    scanf("%d", &n);
    result = Fact(n);    /* 调用递归函数Fact()计算n! */
    if(result == -1)      /* 处理非法数据 */
     printf("n<0, data error!\n");
    else       /* 输出n!值 */
     printf("%d! = %ld\n", n, result);
    return 0;
}
/* 函数功能:用递归法计算n!,当n>=0时返回n!,否则返回-1 */
long  Fact(int  n)
{
    long result = 1;
    if (n < 0)        /* 处理非法数据 */
     return -1;
    else if (n==0 || n==1)       /* 基线情况,即递归终止条件 */
     return 1;           
    else       /* 一般情况 */
     return (n * Fact(n-1)); /* 递归调用,利用(n-1)!计算n! */
}

递归是一种可根据自身来定义或求解问题的编程技术,它是通过将问题逐步分解为与原始问题类似的更小规模的子问题来求解问题的

一个递归调用函数必须包含如下两个部分

  • 一般情况
  • 基线情况:递归调用的最简形式
  • 递归是一种比迭代更强的循环结构
  • 迭代显式地使用重复结构,而递归使用选择结构,通过重复函数调用实现重复结构
  • 迭代和递归都涉及终止测试,迭代在循环条件为假时终止循环,递归则在遇到基线条件时终止递归
  • 迭代不断修改循环控制变量,直到它使循环条件为假时为止,迭代则不断产生最初问题的简化副本,直到简化为递归的基线情况
  • 如果循环条件测试永远为真,则迭代变成无限循环,如果递归永远无法推回到基线情况,则将变成无穷递归

//L7-6(Fibonacci数列)

#include <stdio.h>
long Fib(int a);
int main()
{
    int n, i, x;
    printf("Input n:");
    scanf("%d",&n);
    for (i=1; i<=n; i++)
    {
        x = Fib(i);    /* 调用递归函数Fib()计算Fibonacci数列的第n项 */
        printf("Fib(%d)=%d\n", i, x);
    } 
    return 0;
}
/* 函数功能:用递归法计算Fibonacci数列中的第n项的值 */
long Fib(int n)
{
    if (n == 0)    return 0;     /* 基线情况 */
    else if (n == 1)   return 1;     /* 基线情况 */
    else   return (Fib(n-1) + Fib(n-2)); /* 一般情况 */
        
}
//运行结果
Input n:10
Fib(1)=1
Fib(2)=1
Fib(3)=2
Fib(4)=3
Fib(5)=5
Fib(6)=8
Fib(7)=13
Fib(8)=21
Fib(9)=34
Fib(10)=55

变量的作用域:

程序中被花括号括起来的区域叫做语句块,函数体是语句块,分支语句和循环语句也是语句块

变量的作用域规则是,每个变量仅在定义它的语句块内有效,并且拥有自己的存储空间

不在任何语句块内定义的变量称为全局变量,全局变量的作用域为整个程序,即全局变量在程序的所有位置均有效

相反,在除整个程序以外的其他语句块内定义的变量称为局部变量

全局变量从程序运行开始起就占据内存,仅在程序结束时才将其释放(将内存中的值恢复为随机值)

因此,在程序运行期间的任何时候,在程序的任何地方,都可以访问全局变量的值

//L7-7

#include <stdio.h>
long Fib(int a);
int count;     /*全局变量count用于累计递归函数被调用的次数,自动初始化为0*/
int main()
{
    int n, i, x;
    printf("Input n:");
    scanf("%d", &n);
    for (i=1; i<=n; i++)
    {
        count = 0;   /* 计算下一项Fibonacci数列时将计数器count清零 */
        x = Fib(i);
        printf("Fib(%d)=%d, count=%d\n", i, x, count);
    }
    return 0;
}
/* 函数功能:用递归法计算Fibonacci数列中的第n项的值 */
long Fib(int n)
{
    count++;     /* 累计递归函数被调用的次数,记录于全局变量count中 */
    if (n == 0)    return 0;     /* 基线情况 */
    else if (n == 1)   return 1;     /* 基线情况 */
    else   return (Fib(n-1) + Fib(n-2)); /* 一般情况 */
}
//运行结果
Input n:10
Fib(1)=1, count=1
Fib(2)=1, count=3
Fib(3)=2, count=5
Fib(4)=3, count=9
Fib(5)=5, count=15
Fib(6)=8, count=25
Fib(7)=13, count=41
Fib(8)=21, count=67
Fib(9)=34, count=109
Fib(10)=55, count=177

如果一个变量的类型固定,只有很有限的几个地方需要修改它的值,而且这个变量的值经常被程序中多个模块和函数使用,大多数地方只是读取它的值,而不修改它的值,那么这时就比较适合将这个变量定义为全局变量

使用全局变量使函数之间的数据交换更容易,也更高效

但由于全局变量可以在任何函数中被访问,任何函数都可以对它进行改写

所以很难确定是哪个函数在什么地方改写了它,这就给程序的调试和维护带来困难

由于全局变量破坏了函数的封装性,因此建议尽量不要使用全局变量,不得不使用时一定要严格限制,尽量不要在多个地方随意修改它的值

变量的存储类型:变量的存储类型是指编译器为变量分配内存的方式,它决定变量的生存期

存储类型

  • 自动变量
  • 静态变量
  • 外部变量
  • 寄存器变量

自动变量:

auto 类型名 变量名;

如果没有指定变量的存储类型,那么变量的存储类型就缺省为auto

自动变量在进入语句块时自动申请内存,退出语句块时自动释放内存,它仅能被语句块内的语句访问,在退出语句块后不能再进行访问

在不同的并列语句块内可以定义同名变量,不会相互干扰

因为它们各自占据着不同的内存单元,并且有着不同的作用域

//L7-8

#include <stdio.h>
void Swap(int a, int b);
int main()
{
    int a, b;
    printf("Input a, b:");
    scanf("%d,%d", &a, &b);
    Swap(a, b); 
    printf("In main():a = %d, b = %d\n", a, b);
    return 0;
}
void Swap(int a, int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("In Swap():a = %d, b = %d\n", a, b);
}

程序每次运行进入一个语句块,就像进入了一个屏蔽层,因此同名变量不会相互干扰

函数的参数传递是单向值的传递,只能把实参的值单向传递给形参,而不能反向将形参的值传递给实参

在并列的语句块之间,只能通过一些特殊通道传递数据,如函数参数、返回值以及全局变量

传给函数形参的是函数实参值的一个副本,因此实参的值是不能在被调函数内被修改的

如果不希望形参值在函数内被修改,只要将关键词const放在形参前面,将形参值声明为常量即可

静态变量:

static 类型名 变量名;

//L7-9

#include <stdio.h>
long Func(int n);
int main()
{
    int i, n;
    printf("Input n:");
    scanf("%d", &n);
    for (i=1; i<=n; i++)
    {
     printf("%d! = %ld\n", i, Func(i));
    }
    return 0;
}
long Func(int n)
{
    auto long p = 1;    /* 定义自动变量 */
    p = p * n;
    return p;
}
//运行结果
Input n:10
1! = 1
2! = 2
3! = 3
4! = 4
5! = 5
6! = 6
7! = 7
8! = 8
9! = 9
10! = 10

//L7-10

#include <stdio.h>
long Func(int n);
int main()
{
    int i, n;
    printf("Input n:");
    scanf("%d", &n);
    for (i=1; i<=n; i++)
    {
     printf("%d! = %ld\n", i, Func(i));
    }
    return 0;
}
long Func(int n)
{
    static long p = 1;    /*定义静态局部变量*/
    p = p * n;
    return p;
}
//运行结果
Input n:10
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

静态变量是与程序共存亡的,而自动变量是程序块共存亡的

静态变量与全局变量都是在静态存储区分配内存的,都只分配一次存储空间,并且仅被初始化一次,都能自动初始化为0

在函数内定义的静态变量称为静态局部变量,静态局部变量只能在定义它的函数内访问

而在所有函数外定义的静态变量称为静态全局变量,静态全局变量可以在定义它的文件内的任何地方被访问

但不能像非静态的全局变量那样被程序的其他文件所访问

不同于自动变量的是,静态局部变量在退出函数后仍能保持其值到下一次进入函数时

而自动变量占据的内存在退出函数后立即被释放了,在每次调用函数时都需要重新初始化

外部变量:

extern 类型名 变量名;

如果在所有函数之外定义的变量没有指定存储类别,那么它就是一个外部变量,外部变量是全局变量,它的作用域是从它的定义点到本文件的末尾

外部变量保存在静态存储区内,在程序运行期间分配固定的存储单元,其生存期是整个程序的运行期

没有显示初始化的外部变量,由编译程序自动初始化为0

寄存器变量

register 类型名 变量名;

寄存器是CPU内部的一种容量有限但速度极快的存储器

将使用频率较高的变量声明为寄存器型,可以避免CPU对存储器的频繁数据访问,使程序更小,执行速度更快

模块化程序设计:

模块化程序设计思想最早出现在汇编语言中,在结构程序设计的概念提出以后,逐步完善并形成了模块化程序设计方法

C语言中的函数是功能相对独立的用于模块化程序设计的最小单位

在C语言中可把每个子任务设计成一个子函数,总任务由一个主函数和若干子函数组成的程序完成,主函数起着任务调度的总控作用

无论结构化方法还是面向对象方法,模块化的基本指导思想都是“信息隐藏”

模块分解的基本原则是:高聚合、低耦合,保证每个模块的相对独立性

高聚合:模块内部的联系越紧密越好,内聚性越强越好,模块的功能要相对独立和单一,让模块各司其职,每个模块只专心负责一件事情

低耦合:模块之间的联系越松散越好,模块之间仅仅交换那些为完成系统功能必须交换的信息,模块对外的接口越简单越好

模块化程序设计的好处是:

  • 可以先将各个模块逐个击破,最后再将它们集成在一起完成总任务
  • 便于进行单个模块的设计、开发、调试、测试和维护等工作
  • 可以使得开发人员能够团队合作,按模块分配和完成子任务,实现并行开发,有利于缩短软件开发的周期
  • 有利于模块的复用,使得构建一个新的软件系统时不必从零做起,直接使用已有的经过反复验证的软件库中现成的模块组装或在此基础上构建新的系统,有利于提高软件生产率和程序质量

逐步求精技术:

逐步求精技术就是按照先全局后局部、先整体后细节、先抽象后具体的过程,组织人们的思维活动

从最能反映问题体系结构的概念出发,逐步精细化、具体化,逐步补充细节,直到设计出可在机器上执行的程序

  • 自底向上方法:先编写出基础程序段,然后再扩大、补充和升级
  • 自顶向下方法:先写出结构简单清晰的主程序来表达整个问题,在此问题中包含的各个复杂问题用子程序来实现

逐步求精技术可以理解为是一种由不断的自底向上修正所补充的自顶向下的程序设计方法,特点是:

  • 结构清晰,容易阅读,容易修改
  • 可以简化程序的正确性验证

//L7-11(模块化设计实例)

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#define MAX_NUMBER 100
#define MIN_NUMBER 1
#define MAX_TIMES  10
int MakeNumber(void);
void GuessNumber(const int number);
int IsValidNum(const int number);
int IsRight(const int number, const int guess);
 
int main()
{
       int number;       /* 计算机生成的随机数 */
    char reply;       /* 用户对于是否继续猜数的回答 */
    srand(time(NULL));    /* 初始化随机种子 */
    do{
        number = MakeNumber();    /* 计算机生成一个随机数 */
        GuessNumber(number);      /* 用户猜数字 */
        printf("Do you want to continue(Y/N or y/n)?");  /*提示是否继续*/
        scanf(" %c", &reply);       /* %c前有一个空格 */
    }while (reply=='Y' || reply=='y');   /* 输入Y或y则程序继续 */
    return 0;
}
/*     函数功能:      用户猜数字
       函数参数:      number是计算机生成的数
      函数返回值:    无
 */
void GuessNumber(const int number)
{
    int guess;          /* 用户猜的数*/
    int count = 1;       /* 用户猜的次数 */
    int right = 0;      /* 猜的结果对错与否 */
    int ret;        /* 记录scanf()的返回值,即读入的数据项数 */
    do{
        printf("Try %d:", count);
        ret = scanf("%d", &guess);      /* 读入用户猜的数 */     
        /* 处理用户输入,判断是否有输入错误,是否在合法的数值范围内 */
        while (ret != 1 || !IsValidNum(guess))
        {
        printf("Input error!\n");
        while (getchar() != '\n'); /* 清除输入缓冲区中的错误数据 */
        printf("Try %d:", count);
    ret = scanf("%d", &guess); /* 读入用户猜的数 */
        }
        count++;       /* 记录用户猜的次数 */
        right = IsRight(number, guess); /* 判断用户猜的数是大还是小 */
    }while (!right && count <= MAX_TIMES);
    if (right)        /* 若用户猜对了,则输出相应的提示信息 */
    printf("Congratulations! You're so cool!\n");
    else        /* 若超过MAX_TIMES次仍未猜对,输出相应的提示信息 */
    printf("Mission failed after %d attempts.\n", MAX_TIMES);
}
 
/*      函数功能:      计算机生成一个随机数
        函数参数:      无
      函数返回值:    返回计算机生成的随机数
 */
int MakeNumber(void)
{
    int number;
    number = (rand() % (MAX_NUMBER - MIN_NUMBER + 1) ) + MIN_NUMBER;
    assert(number >= MIN_NUMBER && number <= MAX_NUMBER);
    return number;
}
/*     函数功能:      判断用户的输入是否在合法的数值范围(1-100)之内
    函数参数:       number是用户输入的数
    函数返回值:     若合法,则返回非0值;否则,返回0
*/
int IsValidNum(const int number)
{
    if (number >= MIN_NUMBER && number <= MAX_NUMBER)
    return 1;
    else
        return 0;
}
/*     函数功能:       判断guess和number谁大谁小
        猜大了,提示"Wrong! Too high.",猜小了,提示"Wrong! Too low."
    函数参数:        number是被猜的数,guess是猜的数息
    函数返回值:如果猜对,则返回1;否则,返回0
*/
int IsRight(const int number, const int guess)
{
    if (guess < number)      /* 若猜小了,输出相应的提示信息 */
    {
     printf("Wrong! Too small!\n");
     return 0;
    }
    else if (guess > number) /* 若猜大了,输出相应的提示信息 */
    {
     printf("Wrong! Too big!\n");
     return 0;
    }
    else return 1;
}

assert():断言

断言可以用于测试算法的正确性,当后面括号内的表达式为真时,它静如淑女,为假时,它宣判程序的死刑

断言仅用于调试程序,不能作为程序的功能

在以下情况下使用断言:

  • 检查程序中各种假设的正确性
  • 证实或测试某种不可能发生的情况确实不会发生

代码风格:

代码风格是一种习惯,养成良好的代码风格对保证程序的质量至关重要,因为很多程序错误都是由程序员的不良编程习惯引起的

代码风格包括程序的版式、标识符命名、函数接口定义、文档等内容

虽然程序的版式不会影响程序的功能,但却影响程序的可读性,它是保证代码整洁、层次清晰的主要手段

一行内只写一条语句,一行代码只定义一个变量

在定义变量的同时初始化该变量

if、for、while、do等语句各占一行,分支和循环体内的语句一律用花括号括起来

分节符{}一般都占一行,且位于同一列,同时与引用它们的语句左对齐

采用阶梯层次对应好各层次,同层次的代码放在同层次的缩进层上

一般用设置为4个空格的tab键缩进

在每个函数定义结束后加一空行,能起到使程序布局更加美观、整洁和清晰的作用

在一个函数体内,相邻的两组逻辑上紧密相关的语句块之间加空行

关键字之后加空格,以突出关键字

函数名之后不加空格,紧跟左括号

赋值、算术、关系、逻辑等运算符的前后各加一个空格

函数参数的逗号分隔符和for中分号后面加一个空格

如果代码行太长,在适当位置进行拆分

良好的注释应使用简明易懂的语言,来对程序中的特殊部分的功能和意义进行说明

既简单明了,又准确易懂,能精确地表达和清晰地展现程序的设计思想,并能揭示代码背后隐藏的重要信息

在重要的程序文件的首部对程序的功能、编程者、编程日期以及其他相关信息,加以注释说明

在用户自定义函数的前面,对函数接口加以注释说明

在一些重要的语句行的右方,如定义一些非通用的变量、函数调用、造成多重嵌套的语句块

在一些重要的语句块的上方,尤其是语义转折处

注释可长可短,但应画龙点睛

边写代码,边写注释

修改代码的同时也修改注释

/* 函数功能:用迭代法计算无符号整型变量n的阶乘 */

猜你喜欢

转载自www.cnblogs.com/dingdangsunny/p/11254271.html