《C语言程序设计》讲义

《C语言程序设计》讲义

第一章   C语言概述

[教学目的] 介绍课程的性质、作用。通过本章的学习,使学生了解C语言的特点及C简单程序的构成,掌握C程序的编辑、编译、连接和运行的过程。

[教学内容] C语言特点,简单程序举例及上机步骤

   重点:C程序基本结构及运行调试步骤

[教学方法] 多媒体

一、  C语言出现的背景

从计算机应用基础中学过计算机语言及语言处理系统,我们学习高级语言--C语言。

计算机:计算机是解决问题的电子工具、是由人来指挥的,人们为了用计算机来解决实际问题,一般总是要编制程序。

程序:是指以某种程序设计语言为工具编制出来的动作序列,它表达了人们解决问题的思路,用于指挥计算机进行一系列操作,从而实现预定的功能。

程序设计语言:是用户用来编写程序的语言,是人与计算机之间交换信息的桥梁、工具。是计算机软件系统的重要组成部分,而相应的各种语言处理程序属于系统软件。例如C语言。

二、  C语言的特点

1语言简洁、紧凑,使用方便、灵活;

2、运算符丰富

3、数据类型多(整型、实型、字符型、数组类型、指针类型、结构体类型、共用体类等)

4、具有结构化的控制语句

5、语法不太严格,自由度大

6、既是高级语言,又具有低级语言的功能

7、生成目标代码质量高,程序执行效率

8、可移植性好

三、C语言程序构成(采用程序实例加以说明,并提倡良好的程序设计书写风格)

例1

    /* 例1   The  first  C  Program*/

#include “stdio.h”

void  main()

{

            printf(“This is a C program.\n”) ;

}

例2

#include “stdio.h”

void main()

{

     int a, b, sum;

     a = 123;

     b = 456;

     sum = a + b;

     printf(“sum = %d\n”, sum);

}

3

int max(int x, int y)

{

  int z;

  if (x>y) 

     z=x;

  else

     z=y;

  return(z);

}

main( )

{

  int a,b,c;   

  scanf(“%d%d”,&a,&b);

  c=max(a,b);

  printf(“  max=%d”,c);

}

说明:

1C语言是由函数构成的,至少有一个main()函数;

2、每个函数由函数首部和函数体组成;函数体由说明语句、执行语句组成;

函数首部:

    返回值类型   函数名([形参列表])

函数体:

    函数首部下用一对{}括起来的部分。函数体一般包括声明部分、执行部分两部分。

     {

声明部分: 在这部分定义本函数所使用的变量。

执行部分: 由若干条语句组成命令序列(可以在其中调用其它函数)。

     }

3、每个C程序从main()函数开始执行,并在main()中结束;

4、每个语句和数据定义的最后必须加分号;

5、C程序无输入、输出语句:输入功能由输入(scanf()等)函数完成;输出功能由输出(printf())函数完成;

6、可加注释 /*……*/

四、上机步骤

程序:为了使计算机能按照人们的意志工作,就要根据问题的要求,编写相应的程序。程序是一组计算机可以识别和执行的指令,每一条指令使计算机执行特定的操作。

源程序:程序可以用高级语言或汇编语言编写,用高级语言或汇编语言编写的程序称为源程序。C程序源程序的扩展名为“.c” 。C源程序的C源代码是必须存放到计算机内的存储器中的。

目标程序:源程序经过“编译程序”翻译所得到的二进制代码称为目标程序。目标程序的扩展名为“.obj” 。

可执行程序:目标程序与库函数连接,形成的完整的可在操作系统下独立执行的程序称为可执行程序。可执行程序的扩展名为“.exe”(在dos/windows环境下)。

1、源程序文件的建立和编辑

    1. 编写源程序,形成 .C文件
    2. 需用编辑工具:tc.exe、记事本

2、编译

    1. 编译源程序,形成目标程序  . Obj文件
    2. 需用编译工具 :tcc.exe

3、连接

    1. 连接OBJ文件和调用的库函数,形成运行程序 .exe 文件
    2. 需用连接工具 :tlink.exe

4、运行 .exe 文件。执行程序,查看结果 

五、 课堂小结

1、 C语言的构成要素,main函数在程序中的作用

2、 上机操作的过程

六、 本章小结

1、本章内容相对来说比较简单,学生能够领会并掌握起来,主要是C语言的特点,对C程序中的函数的认识。

2、还有一个基本内容是程序的执行过程,通过演示学生让领会,通过下次上机实践加深学生的印象。

第二章  数据类型、运算符与表达式

[教学目的] 了解C语言丰富的数据类型;常量、变量的概念。掌握变量定义原则和符号常量定义方法;常用基本数据类型(整型、实型、字符型)数据的常量表示、机内存储、变量分类以及掌握正确选择数据类型。了解数值型数据混合运算及类型转换规律;了解C语言的丰富运算符,丰富的运算符可以构成灵活的表达式;掌握算术、赋值、逗号运算符及它们构成的表达式;掌握运算符的优先级、结合性规律;函数分类及标准函数使用。

[教学内容] 常量与变量、 整型数据、 实型数据 、字符型数据 、 变量赋初值、各类数值型数据间的混合运算 、算术运算符与算术表达式 、 赋值运算符和赋值表达式 、 逗号运算符与逗号表达式、标准函数使用

   重点难点:掌握常用基本数据类型(整型、实型、字符型)数据的常量表示、机内存储、变量分类以及掌握正确选择数据类型。算术、赋值、自增/减运算符及它们构成的表达式、运算符的优先级、结合性规律;自增、自减运算

[教学方法] 多媒体

一 、C语言基本语法成分

1、C语言字符集。(字符集)

    (1) 字母:A-Z,a-z

 (2) 数字:0-9

 (3) 空白符:空格,制表符(跳格),换行符(空行)的总称。

    (4) 特殊字符:+ - * /  <  >  (  )  [  ]  {  }  _  =  !  #  %  .  ,  ;  :  ‘  “  |  &  ?  $  ^  \  ~

2、标识符(名字)定义规则及注意事项。

(1) 标识符(名字):有效字符序列

    1. 标准C语言标识符定义规则:标识符只能由字母、数字和下划线三种字符组成。并且第一个字符必须为字母或下划线。
    2. 大小写敏感。如:sum  SUM  Sum  suM是不同的。
    3. 不能与“关键词”同名。

(2) C语言的标识符可以分为以下三类

    1.  预定义标识符: 在C语言中也都有特定的含义,但允许用户另作它用,但这将使这些标识符失去系统规定的原意。 如:main,printf 等 。
    2.  用户标识符: 用户根据需要定义,一般用来给变量、函数、数组或文件等命名。除要遵循标识符的命名规则外,还应注意做到“见名知义”,即选择的用户标识符应具有相关含义,以增加程序的可读性。

     例如以下非法:

  • 3s 以数字开头。 ② s*T 出现非法字符*。

③   -3x 以减号开头。 ④ bowy-1 出现非法字符-(减号)。

    1.  关键词(保留字):C语言规定的具有固定名字和特定含义的标识符。C语言的关键字分为以下几类:
    1. 类型说明符:用于定义、说明变量、函数或其它数据结构的类型,如int。
    2. 语句定义符:用于表示一个语句的功能。如if就是条件语句的语句定义符。
    3. 预处理命令字:用于表示一个预处理命令。如include。

注意:

    关键词必须用小写字母。

    用户定义的标识符不能与关键字相同。如不允许使用关键字为变量、数组、函数等操作对象命名。如果用户标识符与关键字相同,程序在编译时将给出出错信息,如果与预定义标识符相同,系统并不报错,只是该预定义标识符将失去原定含义,代之以用户确认的含义,可能会引发一些运行时的错误。   

4、运算符

代表对各种数据类型实际数据对象的运算。运算符将常量、变量、函数连接起来组成表达式,表示各种运算。运算符可以由一个或多个字符组成。

5、分隔符

逗号,空格,制表符。起分隔、间隔作用。例如,逗号主要用在类型说明和函数参数表中,分隔各个变量。空格多用于语句各单词之间,起分隔作用。在关键字、标识符之间必须要有一个或多个空格符分隔。

6、注释符

 格式:/* ... */或 //。编译时编译系统忽略注释。

作用:注释、调试程序。例如:在调试程序时,对暂不使用的语句也可用注释符括起来,待调试结束后再去掉注释符。

7、C语言的主要语法单位

(1)表达式: 运算符与运算对象组合就形成了表达试。如,2 + 3 * 4

(2)变量定义: 变量也有数据类型,所以在定义变量时要说明相应变量的类型。如: int  i;

(3)语句: 语句是程序最基本的执行单位,程序的功能就是通过对一系列语句的执行来实现的。

(4)函数定义与调用。

二 基本数据类型(整型、字符型、实型、枚举型)

一个程序应包括数据结构和算法。数据结构是在程序中要指定数据的类型和数据的组织形式。算法是如何对数据结构进行处理达到编程者的目的的系列步骤或方法。 C语言的数据结构是以数据类型形式出现的。不同的数据类型决定数据的取值范围、处理方式和存储表示方式。 C的数据类型如下:

 构造类型

 整型int

 字符型 char

 实型(浮点型)

 枚举类型enum

 基本类型

 空类型void

 指针类型 *

 单精度型 float

精度型 double

 双精度型 double

数据类型

 数组类型

 结构体类型struct

 共用体类型 union

根据实际情况,选择合理的数据类型。这是我们掌握各种数据类型的目的和要求。

学习中主要防止数据溢出现象发生。注意预先分析、估算运算结果的可能范围,采用取值范围更大的类型。

(一)常量与变量

      1、 常量:在程序运行过程中,其值不能被改变的量。

       两种形式:一般常量和符号常量

  1. 直接常量(字面常量):

整型常量:如12、0、-3等

实型常量:如4.5、-1.234等

字符常量:如‘a’、‘1’等,用单引号表示;

字符串常量:如“a”、“abc”、“1”,用双引号表示。

  1. 符号常量:

符号常量即是用一个标识符来代替一个常量;符号常借助于预处理命令#define来实现;

           定义形式:

#define  标识符  字符串

        如:#define  PI  3.1415926535

例:计算圆的周长和面积 。(c5.c)

#define  PI  3.14

#define  R   10

void main()

{

  float area,l;

  l= 2 * PI * R;

  area=R*R*PI;

  printf(“周长=%f\n",l);

  printf(“面积=%f\n",area);

}

说明:

① 习惯上,符号常量用大写字母表示;

              ② 定义符号常量时,不能以“;”结束;

③ 一个#define占一行,且要从第一列开始书写;

④ 一个源程序文件中可含有若干个define命令,不同的define命令中指定的“标识符”不能相同;

2、变量:在程序运行过程中,其值会发生变化。

  1. 每个变量必须有一个名字,变量名是标识符。
  2. 标识符是用来标识数据对象,是一个数据对象的名字。
  3. 命名规则:以字母或下划线开始,后跟字符、数字或下划线。

例:x1,_average,lotus_1_2_3,#abc,1fs,M.D.Jhon

  1. 变量名不能是关键字(即保留字,是C编译程序中保留使用的标识符。 如:auto、break、char、do、else、if、int等)
  2. 变量必须先定义再使用

例如:

float  x;                          定义单精度浮点型变量

double  area, length;              定义双精度浮点型变量

long z,k;                          定义整型变量

char ch1,ch2=‘2’;                定义字符变量

  1. 变量名(用标识符表示)、变量在内存中占据的存储单元、变量值三者关系。(实质)
  • 变量名代表内存中的一个存储单元,存放该变量的值。
  • 该存储单元的大小由变量的数据类型决定。
  • 变量值是指存放在变量名所指的存储单元中的数据。

    

    变量名在程序运行过程中不会改变,变量的值可以改变。本质上是通过变量名找到与变量相对应的存储单元,从而引用存储于其中的值,当然这是计算机系统根据程序意图所作的具体操作。

(二)整型数据

1、整型常量的表示方法

(1) 十进制      如:123,-456,0

(2) 八进制数    如:0123,-011        (以0开头的数)

(3) 十六进制数  如:0x123,-0x12,0xABC   (以0x开头的)

2、整型变量

(1) 整型数据在内存中以二进制形式存放,每一个整型变量在内存中占2个字节。

例:定义整型变量i=10 和j= -10的存放形式。

(2)整型变量的分类:基本型int、短整型short、长整型long、无符号型unsigned

(3) 整型变量的定义

对变量的定义,一般放在函数体开头部分的声明部分(也可放在函数中某一分程序内)

         例:#include

     main( )

{  int a, b, c, d;  

unsigned u;

        a=12;  b=-24;  u=10;

        c=a+u; d=b+u;

        printf(“a+u=%d, b+u=%d\n”,c,d);

}

(4)  整型数据的溢出

一个int 型变量的最大允许值为32767,如果再加1,其结果不是32768,而是-32768。即“溢出”。

(三)实型数据

1、实型常量的表示方法

(1) 十进制浮点数 如:0.123,.456,0.0,123.,123.0   

整数部分和小数部分都可省,但不能同时省

(2) 指数形式    如:123e3,123E3代表123×103

           指数部分为整常数;  尾数部分可以是整常数,也可以是实常数;尾数部分和指数部分均不可省。E10, 100.e15.2,  .e5均为不合法的浮点数。

2、实型变量

(1)实型数据在内存中的存放形式

一个实型数据一般在内存中占4个字节(32位)。实型数据是按照指数形式存储的。

(2)实型变量的分类:单精度float、双精度double、长双精度long double

(四)字符型数据

1、字符常量

(1) 括在一对单引号中的一个字符(单引号仅作界限符),如:‘a’ 、‘X’

(2)一个字符常量占1个字节,存放的是字符的ASCII码值。

(3) 转义字符:以‘ \ ’开头,后跟一个约定的字符或所要表示字符的十六进制(或者八进制)的编码;

字符形式

  

\n

回车换行,光标移动到下一行首

\t

横向跳格(8位为一格,光标跳到下一格的起始位置,如第9、第17位等)

\v

竖向跳格

\b

退格,光标往左移动一格

\r

回车不换行,光标移动到本行首

\f

走纸换页

\\

表示反斜杠字符\

\’

表示单撇号字符

\”

表示双引号字符

\ddd

13位八进制所代表的字符(取值范围0377)

\xhh

12位十六进制所代表的字符(取值范围0ff)

\0

代表字符串结束字符

2、字符变量:  字符变量用来存放字符常量,只能放一个字符。

定义格式:

     [存储类型] char  变量名表;

 

 例如:char  c1,c2;

       c1=‘a’ ;

       c2=‘1’ ;

 一个字符变量在内存中占一个字节。

例 大小写字母的转换(ASCII码表:小写字母比对应的大写字母的ASCII码大32,本例还可以看出允许字符数据与整数直接进行算术运算,运算时字符数据用ASCII码值参与运算)(c9.c)

void main()

{

char c1,c2,c3;

  c1='a';

  c2='b';

  c1=c1-32;

  c2=c2-32;

  c3=125;        

  printf("\n%c %c %c\n",c1,c2,c3);

  printf("%d %d %d\n",c1,c2,c3);

  getch();

}

                A B }

                65 66 125

说明:

    (1)字符数据以ASCII码存储的形式与整数的存储形式类似,这使得字符型数据和整型数据之间可以通用(当作整型量)。

    (2)可以将整型量赋值给字符变量(0~255),也可以将字符量赋值给整型变量。

    (3)可以对字符数据进行算术运算,相当于对它们的ASCII码进行算术运算。

    (4)一个字符数据既可以以字符形式输出(ASCII码对应的字符),也可以以整数形式输出(直接输出ASCII码)。

(五)字符串常量

1、 括在一对双引号中的0个或多个字符组成的序列;双引号仅作界限符;如:“C language programming”、“a\\n”、“#123”、“ ”等为字符串常量;

    2、字符串常量的实际存储:在存储完字符串中的有效字符后还应存储字符串结束标志‘\0’。

3、区分字符常量与字符串常量

  1. C语言没有专门的字符串变量,如果想将一个字符串存放在变量中,可以使用字符数组。即用一个字符数组来存放一个字符串,数组中每一个元素存放一个字符。   里定义了一系列专门的字符串处理函数。(第六章)
  1. 引用符号不同:字符常量由单引号括起来,字符串常量由双引号括起来。
  2. 容量不同:字符常量只能是单个字符,字符串常量则可以含一个或多个字符。
  3. 赋给变量不同:可把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量。要存放一个字符串常量可以使用字符数组。
  4. 占用内存空间大小不同:字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加1。增加的一个字节中存放字符"\0"(ASCII码为0)。这是字符串结束的标志。例如,字符常量'b'和字符串常量"b"虽然都只有一个字符,但占用的内存空间不同。

(六) 变量赋初值

在定义变量时对变量进行赋值称为变量的初始化;

格式:类型说明符  变量1=值1,变量2=值2,……;

如:  int  a=3, b=4, c=5;

       float  x=3.4, y=0.75;

       char  ch1=‘K’, ch2=‘P’;

(七)各数值型数据的混合运算

整型(包括int,short,long)和实型(包括float,double)数据可以混合运算,另外字符型数据和整型数据可以通用,因此,整型、实型、字符型数据之间可以混合运算。

       例如:

              表达式10+’a’+1.5-8765.1234*’b’是合法的。

       在进行运算时,不同类型的数据先转换成同一类型,然后进行计算,转换的方法有两种:自动转换(隐式转换);强制转换。

  1.  自动动转换(隐式转换)

         自动转换发生在不同类型数据进行混合运算时,由编译系统自动完成。

      什么情况下发生:

  • 运算转换-----不同类型数据混合运算时
  • 赋值转换-----把一个值赋给与其类型不同的变量时
  • 输出转换-----输出时转换成指定的输出格式
  • 函数调用转换----实参与形参类型不一致时转换

char, short

double

long

float

unsigned

int

高精度

低精度

    1.  强制转换是通过类型转换运算来实现。

              一般形式: 

(类型说明符)表达式

           功能:把表达式的结果强制转换为类型说明符所表示的类型。

例如:

   (int)a         将a的结果强制转换为整型量。

   (int)(x+y)  将x+y的结果强制转换为整型量。

   (float)a+b  将a的内容强制转换为浮点数,再与b相加。

注意:

    (float) 22/5   与  (float) (22/5) 不同

错误的类型转换:

     (int) b=a+5

     b= int (3*a)

三、运算符、表达式

(一)基本概念

运算符:

     运算符描述对数据执行何种操作。C语言中的运算处理功能都是由运算符来实现的。

  1.  优先级:指各种运算符号的运算优先顺序。
  2.  结合性:指运算符号和运算对象的结合方向。 分为:从左向右(左结合)和从右向左(右结合)

重点:运算规则(运算符的优先级和结合性)。

表达式:

     使用运算符和( )将运算对象(常量、变量、函数等)连接起来,构成表达式。它描述了一个具体的求值运算过程。

  1. 计算表达式的值

-按照运算符的运算规则求值

-求值时注意运算符的优先级和结合性

  1. 表达式值的类型

-自动转换

-强制转换

重点:表达式书写要求、类型转换规则、表达式值及类型。

(二)算术运算符与算术表达式

   1、基本的算术运算符: +  –  *    /    %

      优先级: *    /    %  高于 +  –

结合性:左结合性

% (模运算符或求余运算符,%要求两侧均为整型数据。如7%4=3, 4%6=4 ,-12 % 5=-2,21%7=0)。

   2、算术表达式:用算术运算符和括号将运算对象(操作数)连接起来的、符合C语法规则的式子称为算术表达式。

例如:

        a * b / c - 1.5 + ’a’

         (a+b)/(a-b)

注意:

(1)C语言算术表达式的乘号(*)不能省略。例如:数学式b2-4ac,相应的C表达式应该写成:b*b-4*a*c。

(2)C语言表达式中只能出现字符集允许的字符。例如,数学πr2相应的C表达式应该写成:PI*r*r。(其中PI是已经定义的符号常量)

(3) C语言算术表达式不允许有分子分母的形式。例如,(a+b)/(c+d)。

(4)C语言算术表达式只使用圆括号( )改变运算的优先顺序(不要指望用{ }[ ])。可以使用多层圆括号,此时左右括号必须配对,运算时从内层括号开始,由内向外依次计算表达式的值。

   3、强制类型转换运算符:(类型名)(表达式)

   4、自增、自减运算符:+ +   – –

      作用是使变量的值增一或减一。

如:  ++i, i++,   --j,  j–

说明:

(1)++i,--i, j++, j--

    ++i,--i (前置运算) :先自增(减),再参与运算;

    j++, j--(后置运算):先参与运算, 再自增(减)。

例如:

       i=3时,  分析 j=++i;       j=i++; 的区别。

(2)自增、减运算符只用于变量,而不能用于常量或表达式。

       例如:

             6++, (a+b)++,(-i)++都不合法。

(3)++,--的结合方向是“自右向左”(与一般算术运算符不同)。

        例如:

            -i++    -(i++)    合法。

【例2-5】编写一C程序,分解出6378的每一个位数。

参考源代码:

/*  例2-5,2-5.c  */

#include

void main()

{

int x1, x2, x3, x4, m = 6378;            /* 定义整型变量 */

x1 = m / 1 % 10;                     /* 求个位数 */

x2 = m / 10 % 10;                    /* 求十位数 */

x3 = m / 100 % 10;                   /* 求百位数 */

x4 = m / 1000 % 10;                  /* 求千位数 */

printf("%d,%d,%d,%d\n", x4, x3, x2, x1); /* 输出结果 */

}

(三)、赋值运算符与赋值表达式

  1. 简单的赋值运算符:=    除逗号表达式外,优先级最低

〈变量〉〈赋值运算符〉〈表达式〉

赋值表达式的求解过程:

    1. 先计算赋值运算符右侧的“表达式”的值
    2. 将赋值运算符右侧“表达式”的值赋值给左侧的变量。

赋值的含义:将赋值运算符右边的表达式的值存放到左边变量名标识的存储单元中。

  例如:

      x = 10 + y; 

      执行赋值运算(操作),将10+y的值赋给变量x,同时整个表达式的值就是刚才所赋的值。

说明:

(1)赋值运算符左边必须是变量,右边可以是常量、变量、函数调用或常量、变量、函数调用组成的表达式。

     例如:

         x=10 

         y=x+10 

         y=func()

     都是合法的赋值表达式。

(2)赋值符号“=”不同于数学的等号,它没有相等的含义。(“==”相等)

       例如:

       C语言中x=x+1是合法的(数学上不合法),它的含义是取出变量x的值加1,再存放到变量x中。

       例如:

       对于表达式:x = y = z = 5,根据赋值运算符结合性可知,上式可等效为:x= (y = ( z = 5 ))

(3)赋值运算时,当赋值运算符两边数据类型不同时,将由系统自动进行类型转换。

       转换原则: 先将赋值号右边表达式类型转换为左边变量的类型,然后赋值。

  1. <实型变量> = <整型表达式>

-小数部分自动补0

  1. <整型变量> = <实型表达式>
    • 自动舍去实型表达式的小数部分(注意不进行四舍五入)
  2. <字符变量> = <整型表达式>
  3. <整型变量> = <长整型表达式>
    • 自动截取表达式值的低字节赋值,舍去高字节
  4. <整型变量> = <字符数据>
  5. <长整型变量> = <整型表达式>
    • 自动给高字节补0 或补1
  1. 复合赋值运算符: +=    *=   %=等
  2. 嵌套的赋值表达式

(四)逗号运算符与逗号表达式

1、逗号运算符: ,  所有运算符中优先级最低

2、逗号表达式:

表达式1,表达式2,……,表达式n

求解过程:先求表达式1,再求表达式2,依次求下去,直到求出表达式n,整个逗号表达式的值就是表达式n的值

例如:逗号表达式3+5,6+8的值为14。

3、逗号表达式主要作用:

   将若干表达式“串联”起来,表示一个顺序的操作(计算),在许多情况下,使用逗号表达式的目的只是想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达式的值。

   例如:for(s=0,i=1;i<10;i++)

(五)位运算和位运算符

所谓位运算就是指进行这些二进制位的运算。

1、按位与&是一个双目运算符,参加运算的两数据,按二进位进行与运算。

每位遵循原则:0&0=0,0&1=0,1&0=0,1&1=1

如:3&6=2

00000011

& 00000110

   00000010

如果参与&运算的是负数,则以补码形式表示二进制数,再按位进行运算。

按位与运算的用途:

(1)清零:原来的数中为1的位,新数中相应的位为0

(2)取一个数中的某些指定位或要想将一个数中某些位保留下来,就与一个数进行&运算,此数在这些位取1。

2、按位或|:是一个双目运算符,参加运算的两数据,按二进位进行或运算。每位遵循原则:0|0=0,0|1=1,1|0=1,1|1=1

如:3|6=7

00000011

| 00000110

 00000111

按位或运算的用途:

使一个数中某些位变为1,只要将该数与一个数进行|运算,此数在这些位取1。

3、按位异或^: 是一个双目运算符,参加运算的两数据,按二进位进行异或运算。每位遵循原则:00=0,01=1,10=1,11=0

如:3∧6=5

00000011

∧   00000110

      00000101

按位异或运算的用途:

(1)将特定的位翻转,只要该数与一个数进行∧运算,此数在这些位取1;

(2)与0相∧,保留原值;

(3)交换两个数的值,不用临时变量;设有a,b两个数值,则经过如下运算,可使a,b的值交换。

a=a∧b;

b=b∧a;

a=a∧b;

4、按位取反~: 是一个单目运算符,用来对一个二进制数按每位取反。

每位遵循原则:~0=1, ~1=0

按位取反运算符的用途:使程序的可移植性好。

~运算符的优先级比算术运算符、关系运算符、逻辑运算符和其它位运算符的都高。

5、左移运算符<< 是一个双目运算符,用来对一个数的二进位全部左移若干位。高位左移后溢出,舍弃不起作用。

按位左移运算的用途:

在被溢出舍弃的高位中不包含1时,左移n位相当于该数乘2n

6、右移运算符>> 是一个双目运算符,用来对一个数的二进位全部右移若干位。低位右移后溢出,舍弃不起作用,对无符号数,高位补0。

按位右移运算的用途:右移n位相当于该数除以2n。

在右移时,需要注意符号位问题。

    1. 对无符号数,右移时左边高位移入0
    2. 对有符号数,如原来符号位为0,则左边也移入0。如符号位原来为1,左边移入0还是1,要取决于所用计算机系统。此时移入0的称为“逻辑右移”,即简单右移。移入1的称为“算术右移”。

说明:

  1. 与位运算相关的复合赋值运算符有:&=、|=、>>=、<<=、∧=
  2. 掌握位运算符和算术运算符、关系运算符、逻辑运算符的优先级。
  3. 注意位运算符和逻辑运算符的区别。
  4. 不同长度的数据进行位运算时两个数据右对齐。

四、函数

函数:完成特定功能的程序段。

1、分类:

  • 标准函数(库函数):由系统设计好的,存放在系统函数库中,可以由用户直接调用的程序。
  • 自定义函数:由用户根据系统提供的函数定义规则实现特定功能的程序模块。(第八章)

2、标准函数使用要求:

     (1)使用#include “xxx.h”,把要调用的函数的库文件包含进来,放在程序最前面。

       例如:要调用数学sqrt(x)函数,则加 #include “math.h”头文件。

     (2) 使用原则:从函数功能、格式、返回值及类型、注意事项四个方面学习。

3、 函数调用形式:函数名([参数表])

例如:putchar('a')

           sqrt(x)

4、学会查阅库函数,正确使用。

5、标准函数应用举例---随机函数rand()

(1)包含头文件 :

#include

#include      

(2)原型说明:

int rand(void)

   rand()函数返回0到RAND_MAX之间的伪随机数。RAND_MAX常量被定义在stdlib.h头文件中。其值等于32767,或者更大。

void srand (unsigned int n)

srand(n)函数使用自变量n作为种子,用来初始化随机数产生器。一般用法: srand( (unsigned)time( NULL ) );

(其中:time()是时间函数,返回1970年以来的秒时间。原型在 中。请查阅相关书籍)

(3)求一定范围内的随机数。

  •  随机整数。要取[a,b)之间的随机整数(包括a,但不包括b),使用: 

    ( rand() % (b - a) ) + a

          如果要得到 [a,b]之间的随机整数:     (rand() %  (b – a+1) ) + a

  •  伪随机实数。要取得0~1之间的实数,可以用:   rand() / (double)(RAND_MAX)

          还有其他方法。。。。        rand() / ((32767.0/100) )  实数:0~100.0

(4)random()函数也可以产生伪随机数。见下例。

例 随机产生一个4位自然数,输出它的逆数。如设某数1965,则其逆数为5691。

随机产生任意整数区间[a,b]内的一个整数,要用到随机函数random()和种子函数randomize(),相关头文件是“stdlib.h”。函数random(n)负责产生区间[0,n)间的整数,randomize()负责产生数的随机性。至于分解数字,可用运算符“%”和“/”实现。

不难推导,产生[a,b]区间整数的公式为random(b-a+1)+a。对本例,4位自然数区间是[1000,9999],故产生其间任意整数的表达式为:random(9000)+1000。

本例需要6个整型变量:原始数x,逆数y,千位、百位、十位、个位上的数字qw、bw、sw、gw。(当然,这些变量的名字是可以任意取的。)

/*例3-20,3-20.c*/

#include “stdlib.h”

#include “time.h”

void main()

{

  int x, y, qw, bw, sw, gw;

  randomize();   /* 置随机函数种子 */

  x = random(9000) + 1000;

  gw = x % 10;  /* 依次分解各位数字 */

  sw = x / 10 % 10;

  bw = x / 100 % 10;

  qw = x / 1000 % 10;

  y = gw * 1000 + sw * 100 + bw * 10 + qw;  /* 组合成逆数 */

printf(“\nx=%d, y=%d”,x,y);

}

【融会贯通】有50位运动员(编号1001~1050),都非常出色,现要从中选出4位参加田径接力赛。为公平起见,请编写程序,让计算机输出其中幸运的4位运动员的编号。

五  C语句概述

C程序的总体层次结构:程序、模块,函数,语句。

语句:是向计算机发出的用来完成一定操作任务的指令。

C语言语句分类:

  1. 控制语句(9种)
  2. 表达式语句(典型的赋值,函数调用、空语句)
  3. 复合语句(相当一条语句,哪里有语句,哪里就可以用复合语句)

 六 课堂小结

1、++、--运算是学习中的难点,目前不要求有太深的理解,要求把引用规则掌握下来,会简单的应用。

2、各种运算符的优先级是一个重点,参阅课本附表,加深记忆,熟练掌握优先级及结合性。

3、表达式值的求解,能熟练求出任意表达式的值,特别是对于%和自反运算符、逗号运算符。

七  本章后记及上机总结

1、本章内容比较琐碎,学生掌握起来有一定的难度,所以注意让学生做好预习与复习的工作,并通过上机对内容进行深一步的认识。

2、本章是对C语言数据类型的基本知识,包括整型、实型、字符型。首先要求学生对此有一个基本认识,然后通过编程对数据进行定义以使理论与实践结合起来。在这一部分中学生出现问题比较多的是在编程过程中对数据的范围考虑不是很周全,应强调一下。

3、本章涉及的另一个重点是C语言的运算符号,主要是算术运算符,此外还有自增、自减运算符、自反运算符、逗号运算符、赋值运算符等,其中自增和自减难度大一点,同过实例进行验证或通过调试程序使学生掌握它们运算的规律,另一方面应该多加练习。

4、本章内容通过上机可以看出学生对于基本的C程序的编程已经掌握下来了,对于C语言的表达式中的混合运算中的类型转换有点欠缺,。

第三章   顺序程序设计

[教学目的]掌握表达式语句,复合语句的构成;了解算法概念、算法特性、算法表示、结构化程序设计概念;了解结构化流程图、N-S流程图; 掌握格式化输入/输出函数,常用其它输入/输出函数的使用;掌握顺序结构程序设计

[教学内容] 程序的三种基本结构、 赋值语句、 字符数据的输入输出函数、格式输入与输出函数、 顺序结构程序设计举例

重点难点:了解算法概念、算法特性、算法表示、结构化程序设计概念;了解C语言语句分类,理解结构化程序设计方法的概念;输入与输出的格式控制

[教学方法] 多媒体

 

一、 算法及表示

(一)算法

为解决一个问题而采取的方法和步骤称为“算法”。对于同一个问题可以有不同的解题方法和步骤,也就是有不同的算法。算法有优劣,一般而言,应当选择简单的、运算步骤少的,既运算快、内存开销小的算法(算法的时空效率)。

算法5个特点:有穷性、确定性、有0个或多个输入、有1个或多个输出、有效性。

算法表示方法:自然语言,传统流程图,结构化流程图,伪代码、PAD图等。

定义变量X

输入一个数给X

X!=0?

d=x%10;

输出d;

x=x/10;

结束

例:输出一个正整数的各位数字。

用自然语言描述:

① 输入一个整数送给x;

② 求x除以10的余数,结果送给d,并输出d ;

③ 求x除以10的整数商,结果送给x;

重复② ,③步,直到x变为零时终止。

用伪代码描述:

① 输入一个整数送x;

② while(x 0)

    {d=x%10;

       输出d;

      x=x/10;

    }

例:对于一个大于或者等于3的正整数,判断它是不是一个素数。(素数,是指除了1和该数本身之外,不能被其它任何整数整除的数)(疑问?1,2是否是素数)

(二)结构化程序设计方法

  1. “模块化”的设计方法。将一个复杂的任务,分解成若干个功能单一、相对独立的小任务来进行设计,每个小任务就是一个模块。每个模块都仅由3种基本结构组成。
  2. 程序的设计过程采用“自顶向下,逐步求精”。
  3. 程序的设计风格要以良好的可读性为目标,以用户为中心。
  4. 程序设计的步骤:
  • 确定要解决的问题。主要目的是搞清楚“做什么”。
  • 算法设计与数据结构设计。主要任务是解决“如何做” 。涉及两个方面的内容,一是数据结构设计(解决数据的表示与存储),另一个是算法设计(描述操作步骤)。
  • 编写程序和调试程序。
  • 整理资料,交付使用。

(三)结构化程序三种结构

  1. 顺序结构:在顺序结构程序中,各语句(或命令)是按照位置的先后次序,顺序执行的,且每个语句都会被执行到。
  2. 选择结构:在选择结构程序中,某些语句根据相应的条件选择执行或者是跳过,顺序执行但不一定每条语句都执行。
  3. 循环结构:在循环结构程序中,某些语句需要重复执行。
  4. 结构化程序三种结构特点

(1)只有一个入口。

(2)只有一个出口。

(3)结构内的每一部分都有机会被执行到。

(4)结构内不存在“死循环”(无终止的循环)。

已经证明:由三种基本结构顺序组成的算法结构,可以解决任何复杂问题。由基本结构组成的算法属于结构化”算法

二、C语句概述

1、C语句分类:

①控制语句:二个分支语句(if-else、switch),三个循环语句(for、while、do-while),四个转移语句(continue、break、goto、return)

②函数调用语句  如:printf(“Hello, world!”);

③表达式语句 如: x+y;   i++; a=2;  a=3*5,  40 ;

④空语句    ;

⑤复合语句      {  语句序列  }

三、 数据输入输出的概念及在C语言中的实现

    1. 所谓输入输出是以计算机主机为主体而言的。
    2. C语言本身不提供输入输出语句,输入输出操作是通过函数调用实现的。
    3. 要使用C语言库函数,应用“#include”将有关头文件包括到用户源程序中。

(一)字符数据的输入输出

1、字符输出函数——putchar

语法:putchar()

语义:(向stdout终端)输出一个字符;

2、字符输入函数——getchar  

语法:getchar ( ),是一个无参函数;

语义:(从stdin终端上) 输入一个字符,函数的值就是从输入设备得到的字符。

  

(二)格式输入输出

1、格式输出函数——printf

  语法:printf (“格式控制”, 输出表列);

      格式控制:是用双引号括起来的字符串,包含两种信息:⑴  普通字符和转义字符(这类字符总是原样输出)⑵格式说明:由%和格式控制符组成。如:%d, %f等;

如:printf(“a=%d, b=%d”,a,b); 若a、b的值分别为2和3,则输出结果为: a=2, b=3

2、格式输入函数  scanf

 语法:scanf(“格式控制”,地址表列);

格式控制:包含三类符号

(1)空白字符:(空格、Tab或\t、\n),输入时不必一一对应;

(2)普通字符:非格式转换说明符、非空白符、非%

输入时必须一一对应;

(3)格式转换说明符:

注意:

scanf函数规定,组成输入项表的输入对象须是地址量;如:

            scanf(“%d,%d,%d”,&a,&b,&c);

            scanf(“a=%d,b=%d,c=%d”,&a,&b,&c);

①第一个输入语句,正确的输入数据流为:123,456,789,处理的结果为: 123→a,456→b,789→c

②同理对第二个输入语句,正确的输入数据流应是:a=123,b=456,c=789 该输入数据流中除123,456,789被赋给相应变量外,其余都被丢弃

四、顺序程序设计举例

在顺序结构程序中,各语句(或命令)是按照位置的先后次序,顺序执行的,且每个语句都会被执行到。语句执行时按照书写的先后次序执行!

   顺序程序设计的步骤:

1、程序开头的编译预处理命令。

   在程序中要使用标准函数(又称库函数),除printf()和scanf()外,其它的都必须使用编译预处理命令#include  “xxx.h”,将相应的头文件(*.h)包含进来。

2、顺序结构程序的函数体含有完成具体功能的各个语句和运算,主要包括:

   (1)变量类型的说明。(定义变量)

   (2)提供数据。  (给变量赋初值或用输入函数或赋值语句)

   (3)处理部分。        (计算等)

   (4)输出部分。      (用输出函数)

例  输入三角形的三边长,求三角形面积。

       【简要分析】 为简单起见,设输入的三边长a,b,c能构成三角形。从数学知识已知求三角形面积的公式为: 面积area2=s(s-a)(s-b)(s-c),其中s=(a+b+c)/2

  #include

  main()

   {

    float  a,b,c,s,area;

          sacnf(%f,%f,%f”,&a,&b,&c);

          s=1.0/2*(a+b+c);

          area=sqrt(s*(s-a)*(s-b)*(s-c));

 printf(“a=%7.2f,b=%7.2f,c=%7.2f,s=%7.2f\n”,a,b,c,s);

 printf(“area=%7.2f\n”,area); 

     }

例 从键盘输入一个大写字母,要求改用小写字母输出。

【简要分析】小写字母的ASCII码比对应的大写字母的ASCII码大32。字母处理转化为对其ASCII码(整数)处理。 具体过程:输入一个字符并输出,处理( ASCII -32,即转变为小写)后再输出。

#include

main()

 { 

    char c1,c2; 

    c1=getchar();               /* 输入大写字符 */ 

    printf("%c,%d\n",c1,c1); 

    c2=c1+32;                   /* 转变为小写 */     

    printf("%c,%d\n",c2,c2);    /* 输出 */

 }

思考:如果键盘输入的不是大写字母,情况怎样?如何修改?

【融会贯通】  输入平面中两点的坐标x1、y1,x2、y2,计算两点之间的距离。

【融会贯通】 求ax2+bx+c=0方程的根。a,b,c由键盘输入,设b2-4ac>0。

五 课堂小结

1、数据的输入输出函数:单个字符的输入getchar()和输出函数putchar() ;格式输入scanf()与输出函数printf() ,其中scanf()函数printf()函数中涉及众多的格式,比较难于掌握,特别是输入函数的输入控制流,应该多编程熟悉,在调试的过程中掌握下来。

2、 顺序结构程序设计举例 ,是对内容进行综合的处理,但都是最简单的一些过程,所以相对来说比较直观,按照顺序编写语句即可。

六  本章后记及上机总结

1、本章内容涉及到的是C语言中的两个重要操作:输入和输出。因为C中的控制方式与其他语言有很大不同,里面包含很多细节的东西,不是很快能够领会掌握下来的,应该是一个稍微长期的过程,给学生解释清楚,以免学生知难而退。

2、本章中的两对输入和输出函数的用法先要求学生通过讲解及实例了解基本和常用的方法,然后通过上机题对输入和输出的操作进行调试,在实践中得以领会并理解。

3、从本章开始涉及编程知识,但是都是简单的顺序处理过程,比较简单,但它是以后编程的基础,所以对学生的要求要高,要把编程的过程能够熟练的掌握下来,最好的办法是上机实践。

4、通过上机辅导,发现学生对于main函数的应用已有足够的认识,变量的定义及使用也比较灵活,但在顺序程序设计中对问题与程序不能很好的结合,思路不够开阔,应加强读程序与编程的练习。

第四章   选择结构程序设计

[教学目的]掌握关系运算符与关系表达式、逻辑运算符与逻辑表达式、if语句的结构、switch语句和条件运算符。

[教学内容] 关系运算符与关系表达式、 逻辑运算符与逻辑表达式 、 if语句 、 条件运算符 、switch语句

  重点难点::条件的表达、if语句的嵌套、switch语句;if语句的嵌套、多路分支语句的应用

[教学方法] 多媒体

一、选择结构

选择结构是根据给定的条件进行判断,以决定执行某个分支程序段。

编写语句之前,应首先确定要判断的是什么条件,以及当判断结果为不同的情况(“真”或“假”)时应该执行什么样的操作。

C语言中的选择结构是用if语句、switch语句实现的。

二、关系运算符与关系表达式

   1、关系运算符:<   <=   >    >=   = =    ! =

优先级:<   <=    >   >= 高于 = =      !=

关系运算符低于算术运算符,高于赋值运算符

   2、关系表达式:用关系运算符将两个表达式连接起来的式子。

关系表达式求值:关系成立,值为1;关系不成立,值为0

例:  假如 a=3,b=2,c=1,则:

  关系表达式“a>b”的值为“真”,即表达式的值为:1。

  关系表达式“b+c

例:判断奇偶数:A%2==0      A%2!=0

例: 设 int a,b,c;    char ch;则:

a>b==c    等价于  (a>b)==c

d=a>b      等价于 d=(a>b)

ch>’a’+1   等价于 ch>(’a’+1 )

d=a+b>c   等价于d=(a+b>c)

3<=x<=5   等价于(3<=x)<=5

b-1==a!=c  等价于( (b-1) ==a )!=c

三、逻辑运算符及其优先次序

1、 C语言的三种逻辑运算符

    &&        逻辑与         ||      逻辑或

   !      逻辑非  单目(一元)运算符

优先次序(由高到低)

             !           &&             ||

结合方向

!   右结合    &&    ||    左结合

2、 逻辑表达式

  概念: 用逻辑运算符将表达式连接起来的式子就是逻辑表达式。

逻辑表达式的值:逻辑表达式的值应该是一个逻辑量“真”或“假”。 C给出逻辑运算结果时,以数值1代表“真”,以0代表“假”,但判断一个量是否为“真”时,以0代表“假”,以非0代表“真”。

例:非0值作为逻辑值参与运算=“真”(此时与1的作用一样)

  若a=4,       则!a=0(假)

 若a=4,b=5,   则表达式a&&b值为1(真), 表达式a||b值为1(真),    !a||b值为1(真)    4&&0||2值为1(真)      ‘c’(真)&&’d’(真)值为1

例:判断大小写字母,设char ch;则:

ch>=‘a’ && ch<=‘z’   (小写字母)

ch>=‘A’ && ch<=‘Z’  (大写字母)

例:int x ,请写出与x==0等价的逻辑表达式,写出x为正奇数的逻辑表达式。

   x==0 含义:x=0时,表达式值为真;否则为假。 等价的逻辑表达式为  !x。

   判断x为奇数:x%2==1或x%2!=0,则x为正奇数的逻辑表达式:x>0&& x%2!=0或

 x>0&& x%2。

四、三目条件运算符及其表达式

   

1、条件运算符:?:

2、格式:e1?e2:e3

3、语义:判e1的值,为1时计算e2,否则计算e3;

    如:max=(a>b)?a:b  等价于 if (a>b) max=a;         else  max=b;

说明:

(1)  条件运算符的结合方向自右至左

如:a>b?a:c>d?c:d  等价于 a>b?a:(c>d?c:d)

若int a=1,b=2,c=3,d=4;则表达式的值为________

 (2)  条件运算符的优先级仅高于逗号运算符与赋值运算符;

(3)  只有当if语句的真假均只执行一个赋值语句且给同一变量赋值时,才能用条件表达式取代;如:if (a>b) max=a; else max=b;

例:将小写字母转换为大写。

#include "stdio.h"

main()               /*c4-11.c*/

{

char ch;

printf("input ch=");

scanf("%c",&ch);

ch= ch>='a' && ch<='z' ? ch-32:ch;

printf("%c\n",ch);

}

双分支选择语句

例  输入两个实数,按数值由小到大的次序输出这两个数。

【简要分析】比较交换方法。

算法:(思路、方法)

(1) 输入2个数给a,b;(scanf()函数。)

(2) 用if语句比较大小,其中a存放小数,b存放大数。

(3) 输出a,b。(printf()函数。)

#include "stdio.h"

  main()

   {

       float a,b,t;

       scanf("%f%f",&a,&b);

       printf("a=%f,b=%f\n",a,b);

       if(a>b)

          {

          t=a;

          a=b;

          b=t;

         }

      printf("a=%f,b=%f",a,b);

  }

注意:建议分别输出交换前、后a,b的值。(对比观察)

1、if语句的三种形式

语法1:if (表达式) 语句;

语法2:if (表达式) 语句1; else   语句2;

语法3:if (表达式1) 语句1;

       else  if (表达式2)  语句2;

           else   if (表达式3)  语句3;                  ……

                  else  语句n+1;

说明:

  1. if 关键字后均为表达式(逻辑表达式、关系表达式、赋值表达式、变量等);

        如:if (a=5) 语句;   if (b) 语句;

  1. 条件表达式必须用括号括起来,在语句后必须加分号;
  2. 满足条件需执行一组语句时,该组语句必须用{  }括起来   ;
  3. if 语句嵌套时,ese 总是与它最靠近的未配对的if 匹配;
  4. 因为if 语句执行时总是简单地测试其中作为条件的“表达式”的值是0还是非0,便可利用这种特性来简化程序设计。如对于:

if (expression!=0)   完全可用      if (expression)  来代替;

同理:   if(!exp)语句;等价于:if(exp= =0) 语句;

2、例题分析

例:输入三个数a,b,c,按照由小到大的顺序输出。

算法:(伪代码-三个数的选择法排序)

(1)if a>b 将a,b交换 (a是a,b中的小者)

(2)if a>c 将a,c交换  (a是a,c中的小者,因此a是三者中的最小者)

(3)if b>c 将b,c交换 (在剩下的两个数b,c中选次小数,存放在b中)

(4)输出a,b,c。

#include “stdio.h”

main()                     /*(c4-5.c)*/

{

    int a,b,c,t;

    printf("input a,b,c=");

    scanf("%d,%d,%d",&a,&b,&c);

    if (a>b)

       {    t=a;

        a=b;

        b=t;

       }

    if (a>c)

       {

        t=a;

        a=c;

        c=t;

        }

    if (b>c)

        {

        t=b;

        b=c;

        c=t;

        }

    printf("%5d%5d%5d\n",a,b,c);

}

例:求最大或最小值。

#include “stdio.h”

main()

{

    int a,b,c,max;

    printf("input a,b,c=");

    scanf("%d,%d,%d",&a,&b,&c);

    if (a>b)

        max=a;

    else

        max=b;

    if (c>max)

        max=c;

    printf("max is :%d\n",max);

}

这是一种常见的求最大最小值的方法。增加变量,通常取名为max或min,用于存放最大或最小值.

【融会贯通】输入一个字符,判断其是否是英文字母、空格、数字和其它字符。

【融会贯通】给出一个百分制成绩,要求输出成绩等级A、B、C、D、E。   90分以上为A,分为B,70~79分为C,60~69分为D,60分以下为E。

六、if语句的嵌套

当if语句中的执行语句又是if语句时,则构成了if 语句嵌套的情形。

例  银行利息计算问题。已知银行整存整取存款不同期限的月息利率分别为:

0.315% 期限一年

0.330% 期限二年

0.345% 期限三年

0.375% 期限五年

0.420% 期限八年

要求输入存钱的本金和期限,求到期时能从银行得到的利息与本金的合计。

【简要分析】根据存款期限,找出月息利率,利用if语句形式三实现。最后计算利息与本金的合计。

/* 例:银行利息计算问题。(c4-7.c) */

#include

main( )                 /*(c4-7.c)*/

{

int year;

float money,rate,total;      /* money:本金 rate:月利率 total:本利合计*/

printf("Input money and year =?");

scanf("%f%d", &money, &year);      /* 输入本金和存款年限 */

if(year==1) rate=0.00315;         /* 根据年限确定利率 */

else if(year==2) rate=0.00330;

else if(year==3) rate=0.00345;

else if(year==5) rate=0.00375;

else if(year==8) rate=0.00420;

else rate=0.0;

total=money + money * rate * 12 * year;   /* 计算到期的本利合计 */

printf(" Total = %.2f\n", total);

}

例: 求一元二次方程ax2+bx+c=0的根。

有以下几种可能:

(1)  a=0,不是二次方程。

(2)  b2-4ac=0,有两个相等的实根

(3)  b2-4ac>0,有两个不等的实根

(4)  b2-4ac<0,有两个共轭复数根

【融会贯通】请用传统流程图描述算法,if/else实现分支结构,试写源程序。(P60 课堂练习4)

保险公司根据月签单金额将业务员划分为5级别。规则是:月签单10万元以上为“金牌”保险员,月签单8万元以上为“银牌”保险员,月签单6万元以上为“铜牌”保险员,月签单4万元以上为“铁牌”保险员,否则“红牌”警示。请编写程序,输入业务员月签单金额,即输出该业务员对应级别。

七、switch 语句

语法1: switch (表达式)

              {  case C1:语句序列1;

    case C2:语句序列2;

      ……

    case Cn:语句序列n;

    default:语句序列n+1;

    }

语法2:switch (表达式)

      { case C1:语句序列1;break;

        case C2:语句序列2;break;

             ……

case Cn:语句序列n;break;

default: 语句序列n+1;break;

 }

    说明:

(1)switch后面的()内的表达式,ANSI标准允许他为任何类型。

(2) case后的常量表达式一般不可以为实型数据。

(3) 当表达式的值与某个case后面的常量表达式的值相等时,就执行此case后面的语句,若所有case中的常量表达式的值都与表达式的值不相等,执行default后面语句。

(5) 每个case后面的常量表达式的值必须互不相同。

(6) 各个case与default出现次序不影响结果。

 (7)  break的使用。

 (8)  多个case可以共用一组语句。

例  模拟计算器程序。用户输入运算数和四则运算符, 输出计算结果。 例如:输入 23+47 (输入格式为:运算数1   运算符(+、-、*、/) 运算数2 );输出70。

【简要分析】用switch语句用于判断运算符, 然后输出运算值。当输入运算符不是+,-,*,/时给出错误提示。

#include "stdio.h“

void main()

{

  float a,b,s;

  char c;

  printf("input expression: a+(-,*,/)b \n");

  scanf("%f%c%f",&a,&c,&b);

  switch(c)

    {

    case '+': printf("%f\n",a+b);break;

    case '-': printf("%f\n",a-b); break;

    case '*': printf("%f\n",a*b);

    case '/’: printf(“%f\n”,a/b); break;       /*除数为0??*/

    default: printf("input error\n");

    }

}

改进程序:

#include "stdio.h"

void main()                                 /*c4-2.c*/

{

  float a,b,s;

  char c;

  printf("input expression: a+(-,*,/)b \n");

  scanf("%f%c%f",&a,&c,&b);

  switch(c)

     {

      case '+': s=a+b;break;

      case '-':  s=a-b; break;

      case '*':  s=a*b;break;

       case '/':  if( (int)b==0)

                      {   printf(“被零除,出错了"); exit();}

                    else 

                        s=a/b; 

                    break;

      default: printf("input error\n");    exit();

     }

  printf("%f\n",s);

 }

例  已知某公司员工的保底薪水为500,某月所接工程的利润profit(整数)与利润提成的关系如下(计量单位:元):

0≤profit≤1000                    没有提成;

1000

2000

5000

10000

计算其每月工资。输入利润,输出工资(500+提成部分)。

【简要分析】 将利润profit与提成的关系,转换成某些整数与提成的关系。

分析:提成比例的变化点,都是1000的整数倍,如果将利润profit整除1000(即profit/1000),则当:

      profit≤1000       对应0、1

1000 < profit≤2000    对应1、2

2000 < profit≤5000    对应2、3、4、5

5000 < profit≤10000         对应5、6、7、8、9、10

10000<profit             对应10、11、12、……

思考:如何解决相邻两个区间的重叠问题?

最简单的方法:利润profit先1(最小增量),然后再整除1000即可:

              profit -1 <1000    对应0

  1000≤profit -1 <2000    对应1

  2000≤profit -1 <5000    对应2、3、4

  5000≤profit -1 <10000  对应5、6、7、8、9

10000≤profit -1                 对应10、11、12、……

/* 利润与利润提成问题。(c-14.c) */

#include "stdio.h"

main()

{long   profit;

int   grade;

float  salary=500;

printf("Input  profit:");

scanf("%d",&profit);

grade=(profit-1)/1000;

switch(grade)

  { case   0:  break;                             /*profit≤1000*/

    case   1: salary+=profit*0.1;break;         /*1000

    case   2:

    case   3:

    case   4: salary+=profit*0.15;break;        /*2000

    case   5:

    case   6:

    case   7:

    case   8:

    case   9: salary+=profit*0.2;break;        /*5000

    default:salary+=profit*0.25;                  /*10000

  }

printf("salary=%.2f\n",salary);

}

【融会贯通】 购物优惠问题。某商场举行购物优惠活动,(x代表购物款,y代表折扣):x<1600 时 y=0%, x<2400 时y=5%, x<3200 时 y=10%, x<6400 时 y=15%, x>=6400时 y=20%,输入一个顾客的购物款后,显示它的应付款数。

   算法分析(思路):先对具体问题进行逻辑分析,确定判断条件,确定每个分支的出入路径,再把各种情况对应的处理语句列出来。

【简要分析】根据购物款找出对应折扣点。

语句选择:关键是选择语句应用(if()语句、 switch()语句实现)+前面学过的所有知识的再应用.

   编程一般方法:

变量类型的说明。(定义变量)

提供数据。  (给变量赋初值或用输入函数或赋值语句)

处理部分。        (判断、计算等)

输出部分。      (用输出函数)

  编写程序,上机调试验证。

【融会贯通】工资计算问题。某单位马上要对退休人员加工资,增加金额取决于工龄和现工资两个因素:对于工龄大于等于20年的,如果现工资高于2000,加200元,否则加180元;对于工龄小于20年的,如果现工资高于1500,加150元,否则加120元。工龄和现工资从键盘输入,编程求加工资后的工资。

    算法分析(思路):先对具体问题进行逻辑分析,确定判断条件,确定每个分支的出入路径,再把各种情况对应的处理语句列出来。 (根据工龄和现工资来确定加薪,用嵌套)

    语句选择:关键是选择语句应用(if()语句、 switch()语句实现)+前面学过的所有知识的再应用.

  编程一般方法:

变量类型的说明。(定义变量)

提供数据。  (给变量赋初值或用输入函数或赋值语句)

处理部分。        (判断、计算等)

输出部分。      (用输出函数)

  编写程序,上机调试验证。

【融会贯通】某篮球专卖店篮球单价145元/个。批发规则为:一次购买10个以内不打折;一次购买20个以内打9折;一次购买40个以内打8折;一次购买50个以内打7折;一次购买超过50个(含50个)一律按65元/个计算。写程序实现当输入窗户购买篮球的个数时,立即输出其付款金额。请用自然语言描述算法,并用switch实现分支结构,试写源程序。 ( P60 课堂练习5 

练习:

1. 输入一对坐标值,判断它代表的点属于直角坐标系中的哪个象限。

2. 求解一元二次方程ax2 + bx + c = 0。如果有实根,则输出之;否则输出“无实根”字样。

3 某市出租车计费,起步价4元,前3公里不计费;超过3公里但不足20公里,按单程1.40元/公里计费;从20公里开始,一律按单程1.00元/公里计费。实际行驶里程四舍五入取整后作为计算时的里程。请为出租车写一个程序,当输入实际里程时,立即输出乘客应付的出租车费。

4. 输入一天24小时制的时间(0~23时),输出对应的时段。规定:[0~4]点为深夜;(4,6]点为凌晨;(6,8]点为早晨;(8,12]点为上午;(12,18]点为下午;(18,0]点为晚上。

八、 课堂小结 

1、关系运算符与关系表达式的书写及优先级,能够正确的表示条件,并且能正确得出所给条件的值。

2、条件运算符是唯一的一个三元运算符,某些时候它可以和if---else结构相互替换,要求能正确书写并且能正确运算。

2、if-else语句的应用,共有四种结构:if(条件)  语句、if (条件) 语句  else  语句 、else if 结构以及if 的嵌套,要求能正确应用并能按照执行过程写出对应程序段的结果。

3、 switch语句也是一个分支,其用法与if语句有很大的区别,格式要求也比较复杂,它一般应用在分支比较多的情况中,而且在使用时要注意掌握break的使用。

4、前面的两章内容和分支语句结合在一起可以解决一些复杂的逻辑判断问题,这就是分支语句的综合运用,结合实例对所学内容进行复习总结,多编、多读、多动手。

5、在编写程序过程中,注意分支的作用范围,及复合语句的运用。

九、本章后记及上机总结

1、本章是分支结构控制的内容,涉及两大内容:一是条件的书写;二是分支控制语句。其中条件的书写涉及两种运算符号:关系运算符和逻辑运算符,这些内容在其它语言中也有讲述,学生比较容易接受,但在符号的书写上有些差别,特别提醒学生,但在使用中仍然有不少同学出现差错。

2、本章中的另一个重点是实现分支结构的语句: if语句 、 条件运算符 、switch语句,这些内容在使用时有一定的规则,特别要求学生先掌握它们的使用方法,然后再和编程联系起来。

3、在分支控制中if语句是重中之重,它的使用三种形式,分别实现不同的控制,在具体选择时有一定的难度,在这个内容上加大例题量,使学生加深认识,在例题中找出使用的规律。此外,用来控制多路分支的switch语句,也有一定的难度,对该语句的要求不是很高,要求学生会简单的应用,但能够顺利阅读包含该语句的程序,为了提高学生的兴趣,特意让学生编写一个小游戏,以便从中找出乐趣。

4、在上机辅导中该章问题较多,主要是控制语句的使用,理论和实践结合不起来,不能就问题熟练的编写程序,思路不清晰。通过单独的上机小节对问题加以解释,并把正确的程序给学生演示出来。此外,要求学生练习条件的书写,以便进行逻辑判断,因为它也是后续章节中要使用的内容。

第五章  循环结构程序设计

[教学目的]掌握while语句、do-while语句、for语句、break语句和continue语句的用法;循环的嵌套(多重循环)、循环算法及应用

[教学内容]  while语句、do-while语句、for语句、循环的嵌套、 break语句和continue语句

  重点:循环语句的应用

  难点:循环算法及应用,break语句和continue语句功能

[教学方法] 多媒体

一 、循环概述

问题1:假如全班40人,欲从键盘上输入每人的数学成绩,然后计算出平均成绩;

问题2: 编程计算n!。

从题目中看出,这里面包含一些要重复执行一组语句,如果打破程序顺序执行的规则,这时就要使用C程序设计中的另一个基本功能—循环控制重复执行就是循环。

  1. 重复工作是计算机特别擅长工作之一。
  2. 重复的动作是受控制的,重复执行不是简单地重复,每次重复,操作的数据(状态、条件)都可能发生变化。
  3. C语言提供三种循环控制语句,构成了三种基本的循环结构。
  • while语句构成的循环结构(“当型循环”)
  • do-while语句构成的循环结构(“直到型循环”)
  • for语句构成的循环结构(“当型循环”)

循环结构是程序中一种很重要的结构。其特点是:在给定条件成立时,反复执行某程序段,直到条件不成立为止。给定的条件称为循环条件,反复执行的程序段称为循环体。

二、for 语句

语法:for(表达式1;表达式2;表达式3)

          循环体语句;

语义:

1、先求表达式1;

2、求解表达式2,若其值为真,则执行第三步;若为假,则结束循环;

3、执行循环体中的语句;

4、求解表达式3;

5、转回第二步继续执行

如: for( i=1; i<=100; i++) sum=sum+i;可看成:

            for(循环变量赋初值;循环条件;循环变量增值)    语句;

 说明:

(1)for循环简洁灵活;

(2)循环体可以是复合语句;

(3)for语句中的三个表达式均可以是逗号表达式,故可同时对多个变量赋初值及修改。如:for(i=0, j=1; j

(4)for语句中三个表达式可省:

例:计算s=1+2+3+…+100.

#include "stdio.h"

main( )

{

 int i,s;

 for(i=1,s=0;i<=100;i++)

   s = s + i;

 printf("s=%d\n",s);

}

【融会贯通】计算s=1+3+5+7+…+99.   如何修改?

            计算s=1+1/2+1/3+……+ 1/n.  如何修改?

            计算s=1+1/3+1/5+… 的前n项和.如何修改?

例  求n!.    (n! = 1*2*…*n)

#include "stdio.h"

main( )

{

 int i,produc=1,item,n;

scanf(“%d”,&n);

for (i = 1; i <= n ; i++)

      { 

        product = product * item (第i项)

        item=i;

       }

 printf("product =%d\n",product );

   总结累乘计算方法。

例  求Sn=a+aa+aaa+…+aa…a之值,其中a是一个数字。                        

                      n个a

例如当a=2,n=4时,sn=2+22+222+2222。a和n由键盘输入。

#include "stdio.h"

main()

{     

int s,a,i,t,n;

s=0;t=0;

scanf(“%d%d”,&a,&n);

for(i=1;i<=n;i++)

   {      

         t=t*10+a;

         s=s+t;

    }

printf(“s=%f\n", s);

}

例  判断输入整数num是否是完全平方数。

#include "stdio.h"

#include "stdlib.h"

 main()

{

  int i,sum=0,num;

  scanf("%d",&num);

  for(i =1; sum < num; i++)

        sum += (2*i-1);

  if(sum==num)

      printf("%d is yes  ",num);

   else

       printf("%d is no   ",num);       

    }  

例  求1到200之间的完全平方数的程序。

#include

main ()

 {

    int m, n;

for (n = 1; n <= 200; n++)

    {

         m = (int)sqrt(n);

          if (m * m == n)

                printf("%d ", n);

      }

}

例  一位百万富翁遇到一陌生人,陌生人找他谈一个换钱计划,该计划如下:我每天给你十万元,而你第一天只需给我一分钱,第二天我仍给你十万元,你给我二分钱,第三天我仍给你十万元,你给我四分钱…你每天给我的钱是前一天的两倍,直到满一个月(30天),百万富翁很高兴,欣然接受了这个契约。请编写一个程序计算这一个月中陌生人给了百万富翁多少钱,百万富翁给陌生人多少钱。

【简要分析】 设变量 s和 t(分别为long型)分别记录百万富翁给陌生人的钱和陌生人给百万富翁的钱,第一天时:

s=1  (以分为单位)

t=100000(以元为单位)

k=1;

然后用for循环:2到30,其循环体是:

k=2k(每天翻一倍)

s=s+k

t=t+100000

循环体结束后将s/100,将以分为单位转换为以元为单位。

#include "stdio.h"

void main()

{

   long s=1,t=100000,k=1,i;

    for(i=2;i<=30;i++)

    {

         k=2*k;

         s=s+k;

         t=t+100000;

    }

printf("s=%ld,t=%ld\n",s/100,t);

}

运行结果:

s=10737418,t=3000000

【融会贯通】小红今年12岁,她父亲比她大20岁,编一程序,计算出她的父亲在几年后比她年龄大一倍。那时他们两人的年龄各为多少?

   【简要分析】用n 和m分别表示小红的年龄和其父亲的年龄,经过i年后小红的年龄和父亲的年龄分别是n+i和m+i。i从1开始循环,找到(m+i)=2*(n+i)即可。

【融会贯通】 从键盘输入20个整数,求出它们的平均值及比平均值大的数。

【融会贯通】 根据以下公式求p的值。m与n为两个正数且要求m>n。

【融会贯通】 输入实数x和正整数n,计算xn = x * x * … * x。

三、while语句

语法:while (exp)

       循环体语句;

语义:当exp为真时,执行循环体;为假时,执行循环语句的后续语句;

 如:用while语句构成循环,求sum=1+2+…+100 

main()

{  int  i=1, sum=0;

    while (i<=100)

      { sum+=i;

         i++; }

   printf(“%d”,sum);}

说明:

(1)  循环体可以用复合语句;

(2) 在while语句前应有为测试表达式(exp)中的循环控制变量赋初值的语句,以确保循环的正常开始;

(3)循环体内应有改变循环控制变量的语句,以确保循环进行有限次后正常结束; 如:i=1;while (i<=100)

             sum=sum+1; (死循环)

(4) while 循环的特点是先判断后执行,故循环有可能一次都不被执行;

      如:i=3;

          while (i<3)

          printf(“i=%d\n”,i);

例  用公式求π的近似值,直到最后一项的绝对值小于10-6为止。

π/4≈1-1/3+1/5-1/7+……

 # include

main( )

{  int s=1; float  n=1.0,  t=1,  pi=0;

   while((fabs(t))>1e-6)

   { pi=pi+t;

  n+=2;

      s=-s; 

t=s/n; 

 }

   pi=pi*4;

   printf(“pi=%10.6f\n”,pi);}

【融会贯通】  输入一行字符,统计其中的数字、字母、空格 和其它字符出现的次数。

【简要分析】我们先定义三个变量用来存放字母,数字和其它字符的个 数,利用循环来逐个判断这一字符串,分别对每一个字符进行ASCII码范围的比较,并分别对相应的变量作加一操作。

四、do-while语句

语法:do

            {循环体语句;}

            while (exp);

语义:当exp为真时,执行循环体;为假时,执行循环语句的后续语句;

如:用do-while语句构成循环,求 sum=1+2+…+100 

程序如下:

main()

{  int i=1,sum=0;

   do

     { sum+=i;

       i++;  }

    while (i<=100);

    printf(“%d”,sum);}

 说明:

(1) 循环体可以用复合语句;

(2) 循环控制变量在执行do前必须赋初值;循环体内应有改变循环控制变量的语句;

(3) do-while 循环的特点是先执行后判断,故循环至少被执行一次;

如:i=3;

          do

          {  sum+= i;

              i++;

           } while (i>10);

例  键盘输入任意一个正整数,求其逆数。所谓“逆数”是指将原来的数颠倒顺序后形成的数。如输入1986时,输出6891。

【简要分析】 对正整数x,无论它有多少位数字,我们每次只把低位数字(digit)取走(x%10),再用个算式将x的各位数字依次移到低位(x=x/10),直到x等0为止。例如,设原数x=927,则:

第一次循环:低位digit=7,x=92,y=7;

第二次循环:新低位digit=2,x=9,y=7*10+2=72;

第三次循环:新低位digit=9,x=0,y=72*10+9=729;

因x=0,循环结束。

用自然语言描述的程序逻辑如下:

①设置环境;

②定义变量digit、x、y分别表示原始数、原始数的个位数和逆数;

③输入原始正整数x;

④从x中分解出个位数字digit;

⑤合并个位digit至逆数y中;

⑥原始数x缩小10倍:x=x/10;

⑦如果x非零,则转④;

⑧输出逆数y,结束。

参考源代码:

/* 例5-18,5-18.c */

#include

void main()

{

int digit;

   long x, y = 0;

   printf( ”\n请输入任意一个正整数:” );

scanf(“%d”, &x);

   do

   { 

digit = x % 10;     /* 取出低位数字 */

      y = y * 10 + digit;   /* 合并数字 */

      x = x / 10;         /* 移动数字 */

   } while ( x );

   printf( ”\n逆数:%d”, y );

}

【融会贯通】输出数列:2/1,3/2,5/3,8/5,13/8,…,直到分母刚超过100为止。

五、break和continue语句

break语句:可以用于switch语句中,也可以用于循环语句中,当用于循环语句中时,用于在满足条件情况下,跳出本层循环。

continue语句:用于循环语句中,在满足条件情况下,跳出本次循环。即跳过本次循环体中下面尚未执行的语句,接着进行下一次的循环判断。

六、循环嵌套

在一个循环语句的循环体内又完整地包含了另一个循环语句,称为循环嵌套。

例 计算s=1+(1+2)+(1+2+3)+…+(1+2+3+4+…10)

  分析:共有10项,用外循环;各项公式为:s=s+j  (j=i  (i=1,2,3..) ),内循环实现。

  main()

   {

    int i,j,s,sum=0;

    for(i=1;i<=10;i++)                  /*共10项*/

        {    

                s=0;

                for(j=1;j<=i;j++)        /*求各项和*/

                     s=s+j;

                sum=sum+s;                /*累加*/

        }

     printf("sum=%d\n",sum);

   }

【融会贯通】 计算:S=1!+2!+3!+…+19!+20!

七、循环算法

1、程序设计要点

  1. 循环程序的实现要点:
    1. 归纳出哪些操作需要反复执行?   循环体
    2. 这些操作在什么情况下重复执行? 循环条件
  2. 选用合适的循环语句
    1. for  while   do-while
  3. 循环具体实现时考虑(循环条件):
    1. 事先给定循环次数,首选for
    2. 通过其他条件控制循环,考虑while或do-while

if(循环次数已知)

    使用for语句

else            /* 循环次数未知 */

     if  (循环条件在进入循环时明确)

         使用while语句

     else      /* 循环条件需要在循环体中明确 */

         使用do-while语句

2、 穷举法

  “穷举法”也称为“枚举法”或“试凑法”, 即采用循环结构将所有可能出现的情况一一进行测试,判断是否满足给定的条件。

例  搬砖问题。36人搬36块砖,男人1人搬3块,女人1人搬2块,小孩3人搬1块。男人、女人、小孩都必须有,问各有几人?

【简要分析】 算法为穷举法,可用任何一种循环语句编程。假定男人、女人和小孩数分别为m、w和c,按题意,男人不到12人(1=

#include "stdio.h"

main( )

{

  int m,w,c;

  for(m=1;m<12;m++)         /*男人情况*/

     for(w=1;w<18;w++)        /*女人情况*/

        {

           c=36-m-w;            /*小孩情况*/

           if(3*m+2*w+c/3==36 && c%3==0)

               printf(“m=%d,w=%d,c=%d\n”,m,w,c); 

        }

}

【融会贯通】马克思手稿中有一道趣味数学问题:有30个人,其中有男人、女人和小孩,在一家饭馆吃饭花了50先令;每个男人花3先令,每个女人花2先令,每个小孩花1先令;问男人、女人和小孩各有几人?

【融会贯通】 将一元兑换成5分、2分、1分的硬币,要求每一种方案中三种硬币均有,打印各种兑换方案。

【融会贯通】 百钱买百鸡问题”:鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何?

【融会贯通】 将五张100元的大钞票,换成等值的50元,20元,10元,5元一张的小钞票,每种面值至少1张,编程输出所有可能的换法,程序应适当考虑减少重复次数。

3、 “递推法”

“递推法”又称为“迭代法”,其基本思想是把一个复杂的计算过程转化为简单过程的多次重复。

例  猴子吃桃问题:小猴在某天摘桃若干个,当天吃掉一半,觉得还不过瘾,又多吃了一个。第二天吃了剩下的桃子的一半又多一个;以后每天都这样吃下去,直到第8天要吃时只剩下一个桃子了,问小猴子第一天共摘下了多少个桃子?

【简要分析】 这是一个“递推”问题,先从最后一天的桃子数推出倒数第二天的桃子数,再从倒数第二天的桃子数推出倒数第三天的桃子数……。

   设第n天的桃子为xn,那么它是前一天的桃子数的xn-1 的一半减1,递推公式为:xn=xn-1/2 – 1 。即: xn-1 =( xn+1)×2。

#include "stdio.h”

main()

{

  int  tao,n;

  tao=1;            /*已知第8天的桃子数*/

  for (n=7;n>=1;n=n-1)

      tao=(tao+1)*2;

  printf("tao(1)=%d\n",tao);

 }

程序执行后输出结果如下:

tao(1)=382

例  求Fibonacci数列的前20个数。该数列的生成方法为:F1=1,F2=1,Fn=Fn-1+Fn-2(n>=3),即从第3个数开始,每个数等于前2个数之和。

  main()

  {   long int f1=1,f2=1;                 /*定义并初始化数列的前2个数*/

       int i;                              /*定义并初始化循环控制变量i*/

       for(i=1 ; i<=10; i++ )                /*1组2个,10组20个数*/

         {

           printf("%15ld%15ld", f1, f2);      /*输出当前的2个数*/

           if(i%2==0)

                printf("\n");                  /*输出2次(4个数),换行*/

            f1 = f1 + f2;

            f2 = f2 + f1;                     /*计算下2个数*/

        }

   }

运行结果为:

1                  1             2              3

5                  8             13             21

34                55             89            144

233               377            610            987

1597             2584           4181           6765

例 用公式 pi/4=1-1/3+1/5-1/7… ,求 的近似值,直到最后一项绝对值小于10-4为止。

【简要分析】 本题的递推公式复杂,只能将分子和分母分别递推出新的值后再做除法运算。其递推方法和求解的步骤如下:

    1. 分母n的初值为1,递推公式:n=n+2;

    2. 分子s的初值为1.0,递推公式:s=(-1)*s;

    3. 公式中某项的值为: t=s/n。

   从以上求pi的公式来看,不能决定n的最终值应该是多少;但可以用最后一项t=s/n的绝对值小于0.0001来作为循环的结束条件。

#include   "math.h"

main( )

  {int  n;

   float  t, s, pi;

   pi=0;             /*变量pi用来存放累加和*/

   n=1;              /*第一项的分母值为1*/

   s=1.0;            /*第一项的分子值为+1*/

   t= s/n;           /*先求出第一项的值*/

   while (fabs(t)>=0.0001)

      { pi=pi+t;

        n+=2; 

        s= -1* s;

        t= s/n;      /*递推出下一项的值*/

      }

   printf("pi=%f\n", pi*4) ; }

4、标志法

用一个标志变量表示事物的状态。先假设事物为某个初态,然后对事物状态进行测试,当事物状态与初态不符时,则改变标志变量的值。最后通过判断标志变量的取值而确定出事物的真实状态。

当事物仅为几种状态时,该算法特别适用。

键盘输入一个任意整数,判断它是否是素数。所谓素数即除了1和自身再也没有约数的数(1不是素数),如13、17等。

用自然语言描述程序逻辑如下:

  • 设置环境;
  • 定义变量x、a、flag,其中x接授用户输入的任意一个正整数;
  • 输入x,并假设它是素数,即设标志变量flag=1;
  • a=2;
  • a≤x-1吗?若是则转⑥,否则转⑧;
  • x是a的倍数吗?若是则改变标志变量的值,即令flag=0,转⑧;
  • a自增1,转⑤;
  • 若标志flag=1,则x是素数,否则x不是素数。

/* 例5-6,5-6.c */

#include

void main()

{

int x, a, flag;

printf( ”\n请输入一个正整数:”);

scanf(“%d”, &x);

    flag = 1;

     for ( a =2; a <= x - 1; a++ )

    {       

      if ( x % a == 0 ) 

flag = 0;

break;

}

        }

     if ( flag == 1 ) 

       {

      printf( “%d是素数。”, x );

    }

   else

    {

      printf( “%d不是素数。”, x );

    }

}

例 韩信有一队兵,他想知道有多少人,便让士兵排队报数。按从1至5报数,最末一个士兵报的数为1;按从1至6报数,最末一个士兵报的数为5;按从1至7报数,最末一个士兵报的数为4;最后再按从1至11报数,最末一个士兵报的数为10。你知道韩信至少有多少兵吗?

分析:设兵数为x,则按题意x应满足下述关系式:

x%5 == 1 && x%6 == 5 && x%7 == 4 && x%11 == 10

#include

main()

{

  int  x = 1;     

  int  find = 0;               /*设置找到标志为假*/

  for (x=1; !find ;x++)

  {

      if (x%5==1 && x%6==5 && x%7==4 && x%11==10)

      {

          printf(" x = %d\n", x);

          find = 1;      

      }      

  }  

}

 破案显神威。某地发生一起特大盗窃案,与本案有关的犯罪嫌疑人有A、B、C、D、E、F六人。根据口供,有六条线索:

1. A、B中至少有一人作案。

2. A、D两人不可能是同案犯。

3. A、E、F中有两人参与作案。

4. B、C或同时作案,或与本案无关。

5. C、D中有且只有一人作案。

6. 若D没有作案,则E也不可能作案。

问:谁是真凶?

【简要分析】本例采用穷举法,让计算机用多重循环对六个犯罪嫌疑人全面排查。每一个人不外乎两种情况,要么作案要么没有作案。设作案用1表示,没有作案用0表示,则A、B、C、D、E、F这六个变量描述的6个线索为:

线索1:A+B!=0;

线索2:A+D!=2;

线索3:A+E+F==2;

线索4:B+C!=1;

线索5:C+D==1;

线索6:D+E!=0。

这六条线索必须同时满足,是“与”的关系。

/* 例5-20,5-20.c */

#include

void main()

{

   int  A, B, C, D, E, F;

   printf( "\nA,B,C,D,E,F" );

   for ( A = 0; A <= 1; A++ )

      for ( B = 0; B <= 1; B++ )

         for ( C = 0; C <= 1; C++ )

            for ( D = 0; D <= 1; D++ )

               for ( E = 0; E <= 1; E++ )

                  for ( F = 0; F <= 1; F++ )

                    if ( A + B != 0 && A + D != 2 && A + E + F == 2  \

&& B + C != 1 && C + D == 1 && D + E != 0 )

printf( “\n%d, %d, %d, %d, %d”, A, B, C, D, E, F );

}

【融会贯通】设A、B、C为三个非零的正整数,计算并输出下列不定方程组解的个数CNT以及满足此条件的所有A、B、C。

A+B+C=13

A-C=5

八、 课堂小结

1、while、do-while语句的语法结构。

2、while与do-while区别所在。

3、注意循环控制条件的书写及循环控制变量的引用。

九、本章后记及实训小结

1、本章讲述控制循环的四种结构,重点掌握其中三个:while语句、do-while语句、for语句。随着内容的增多,对学生的要求也加大了,解决的问题也复杂了,在本章的学习中出现的问题也较多。为了利于学生掌握所学内容,根据学生要求把例题讲少、讲细,尽可能做到讲一个懂一个,而且在讲述中把算法放在一个重要位置,原则是“给学生一个思想,而不是单纯的解决一个问题。”

2、无论哪一种语句都有固定的格式与要求,首先要求学生把基本的语法掌握下来,然后再根据需要进行应用,循环有三种实现方法,学生可根据自己的喜好或问题的要求选择不同的结构进行循环控制。

3、循环可以嵌套,增加了程序的灵活性,但也增加了难度,学生对于外层循环和内层循环有时不能正确区分,在例题讲述中加强了这部分内容,同时课后习题也涉及到这样的内容,要求学生独立解决。

4、本章中难点是 break语句和continue语句,该内容使用有一定的难度,仍根据常规先让学生掌握基本的要求和使用规则,多读带有该语句的程序,从中总结出规律性的东西,使自己对它们的用法逐渐清晰起来,循序渐进。

5、几种循环有相同地方,但也有区别,让学生通过实例进行区分。

6、学生对本章的内容能够掌握下来,但是应用在编程中还有些欠缺,在上机实践中出现问题比较多的是循环的控制,语句的组织,但在调试的过程中大部分同学能够找到问题所在,并逐一解决。

第六章    数组

[教学目的] 掌握一维数组、二维数组和字符数组的应用,6个字符串处理函数

[教学内容] 一、二维数组、字符数组的定义、引用、初始化及应用;字符数组的输入输出 、字符串处理函数 ;数组算法

重点难点:一维数组、二维数组和字符数组的应用,函数gets()、puts()、strcat()、strlen()、strcpy()、strcmp() ;冒泡排序、选择排序算法;二维数组与字符数组的初始化及应用

[教学方法] 多媒体

一 、 基本概念

计算机处理数据时,经常出现数是某种有序的形式进行组织的情况。

例:将41名学生的c程序设计成绩输入计算机并储存起来。

main()

     { int s1,s2,s3,s4,s5, … , s41 ;

        scanf(“%d%d%d%d …”,&s1,&s2,

                                  &s3,&s5,…,&s41);

        … …

     }

用数组处理:

main()

         { int   i, s[41];         

          for(i=0; i<41; i++)

           scanf(“%d”, &s[i] );

         }

共同特征:

(1)都是由若干个分量组成;(若干学生组成)

(2)是由若干个同一类型的分量组成; (可为任何数据类型,此为数值型)

(3)分量按一定顺序排列,有一定对应关系(按学号排列,学号与成绩一一对应)

1、数组:   具有相同数据类型的数据的有序的集合。

2、数组元素:   数组中的元素。数组中的每一个数组元素具有相同的名称,不同的下标,可以作为单个变量使用,所以也称为下标变量。在定义一个数组后,在内存中使用一片连续的空间依次存放数组的各个元素。

3、数组的下标:  是数组元素的位置的一个索引或指示。

4、数组的维数:  数组元素下标的个数。根据数组的维数可以将数组分为一维、二维、三维、多维数组。

5、数组特征:

(1)数组元素在内存中占用连续的存储单元。

(2)数组元素的个数固定。

(3)数组元素的类型相同。

、数值数组定义与引用

一)一维数组的定义和引用

定义数组,就是要:

(1)规定数组的名称,其取名规则与变量名相同;

(2)规定数组的类型,包括其数据类型和存储类型;

(3)规定数组的大小,即数组的维数及包含的数组元素的个数。数组元素就是包含在数组中的变量。

1、一维数组的定义:

         类型说明符   数组名[常量表达式]

例如:int a[100];定义了一个数组a,元素个数为100,数组元素类型为整型。 数组a的各个数据元素依次是a[0],a[1],a[2]…a[9](注意:下标从0-9)。每个数据元素都可以作为单个变量使用(如赋值,参与运算,作为函数调用的参数等)。

C语言还规定,数组名是数组的首地址。即a=&a[0]。

    2、一维数组元素的引用:

数组名[下标]    例如:a[0]=a[5]+a[7]-a[2*3]

只能逐个引用数组元素,不能一次引用整个数组。

3、一维数组在内存中占一段连续的存储空间,其首地址:a或&a[0]

4、一维数组的初始化

(1)在定义数组时对数组元素赋以初值;int a[10]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

(2)可以只给一部分元素赋值; int a[10]={0, 1, 2,  3, 4};

(3)如果想使一个数组中全部元素值为0,可简便实现;

int a[10]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

其实,对static数组不赋初值,系统会对所有数组元素自动赋以0值,即上句等价于:

 static int a[10];

(4) C允许通过所赋初值的个数来隐含定义一维数组的大小;int a[]={0,1,2,3,4,5,0};   相当于    int a[7]={0,1,2,3,4,5,0};

5、一维数组的应用

例  请将下列一组数据读入到 S 数组中并输出。

       30,  56,  88,  45,  100,  20

main()

    {    int s[6] , i ;                     /*定义*/

         for(i=0;i<6;i++)

           scanf(“%d,”,&s[i] );        /*数组元素输入*/

         for(i=0;i<6;i++)

            printf(“%4d”,s[i] );     /*数组元素输出*/

   }    

例 用数组的方法输出斐波拉契数列:1,2,3,5,8,13,21,34,55,89,……写C程序,输出该数列前N项。

【简要分析】 显然这是一个典型的递推问题,结合数组,从第三个数开始,其递推公式是:

x[i]=x[i-1]+x[i-2],其中i=2,3, …,N-1

利用循环结构,用N-S流程图描述的程序逻辑如图所示。

设置环境,定义数组x[N],计数变量i

赋初值:令x[0]=1,x[1]=2,i=2;

产生x数组:i<N

产生新项:x[i]=x[i-1]+x[i-2]

i=i+1

输出x数组,结束

       N-S流程图

参考源代码:

#include

#define N 20

void main()

{

long i, x[N] = { 1, 2 };

clrscr();

printf(“%ld\t%ld\t”, x[0], x[1]);

for ( i = 2; i < N; i++ )       /* 尚剩N-2项 */

  x[i] = x[i - 1] + x[i- 2];    /* 产生各项 */

for ( i = 0; i < N; i++ )       /* 输出数列 */

  printf("%ld\t", x[i] );

}

【思考验证】如果将x数组定义为整型int,程序是否能正常运行?

【融会贯通】某数列前三项为0、1、1,以后各项均为前相邻三项之和,输出该数列前N项。

二)二维数组的定义和引用

1、二维数组的定义

      类型说明符   数组名[常量表达式1][常量表达式2];

      如:int number[5][4];

         数组的存储结构:以行为主序的连续空间

    2、二维数组的引用: 二维数组元素的表示形式为:数组名[下标][下标]

3、二维数组的初始化

(1)分行给二维数组赋初值:如

     static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

(2)可以将所有数据写在一个花括号内,按数组排列的顺序对元素赋初值;如: static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};

(3)如果花括号内的初值个数少于每行中的数组元素个数,则每行中后面的元素自动赋初值0;也允许代表给每行元素赋初值的花括号的数目少于数组的行数,这时,后面各行的元素也自动赋0值。

(4)C语言规定,可以用初始化的方法来隐含定义二维数组第一维的大小,即可以省略数组定义中第一个方括号中的表达式,但不能省略第二个方括号中的表达式。

如:static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};    等价于

static int a[ ][4]={1,2,3,4,5,6,7,8,9,10,11,12};

在定义时也可以只对部分元素赋初值而省略第一维长度,但应分行赋初值。如:

 static int a[ ][4]={{0,0,3},{0},{0,10}};

4、二维数组的输入与输出

用二重循环,以a[m][n]为例     for(i=0;i

for(j=0;j

            {……}

   要引用二维数组的全部元素,即要遍历二维数组,通常应使用二层嵌套的for循环:外层对行进行循环,内层对列进行循环。其格式一般为:

for(i=0;i<=行数-1;i++)

      for(j=0;j<=列数-1;j++)

         { …a[i][j]…}

例  定义一个二维数组,并输出每一行的最小值。

#include

#define N 4

main()

{   int  a[N][N], m[N], i, j;

  printf("Input numbers: \n");

  for (i=0; i

    for (j=0; j

     scanf("%d", &a[i][j]);     /*对二维数组初始化*/

for (i=0; i

 { m[i]=a[i][0];      /*设每行的第一个元素为当前行的最小值*/

      for (j=1; j

      if (m[i]>a[i][j]) 

         m[i]=a[i][j];      /*依次与其余元素比较,直至找到每一行的最小值*/

 }

 printf("Min is: ");

 for (i=0; i

      printf("%d ", m[i]); /*输出每一行的最小值*/

  }

(三)数组元素的操作:包括对数组的建立、数组元素的引用、数组元素的输出。

例:请将下列一组数据读入到 S 数组中,并从中找出最小的值并输出。

            30,  56,  88,  45,  100,  20

main()

    {

     int s[6]={30,56,88,45,100,20} , i, 

     min=s[0];

     for(i=1;i<6;i++)

            if(s[i]

                 min=s[i];

     printf(“%d”,min );

    }    

【融会贯通】 按键盘输入和随机函数方式输入n(n=10)个整数组成的数组,求:

(1)按照每行3个数输出。

(2)计算这10个数的累和(积)并输出。

(3)把数组中的数逆序输出。例如{1,3,4,-6,7,21},则输出为{21,7,-6,4,3,1}。

(4)查找并输出能被3整除的数,如无,则输出“NO EXIST”。

【融会贯通】 把A数组中的N个数与B数组中的N个数相加(或减),将结果放在C数组中。

【融会贯通】 给定n个实数,统计并输出其中在平均值以上(包括等于平均值)的实数个数。

三、数值数组算法

(一)排序算法

例 从键盘上任意输入6个整数,要求按升序输出。

冒泡法排序的基本思路:通过相邻两个数之间的比较和交换,使较小的数逐渐从底部移向顶部,较大的数逐渐从顶部移向底部,就像水底的气泡一样逐渐向上冒,故而得名。

假设有6个数(36、24、85、48、90、60),顺序存储在数组array[6]中,采用冒泡法排序的过程下图所示:

array[0]    array[1]    array[2] array[3]    array[4]    array[5]

     36         24         85       48           90          60

     24         36        85        48           90          60

     24         36        48        85           90          60

     24         36        48        85           60          90

          ……………………

     24         36        48        60           85          90

第一趟冒泡排序,结果最大的数被交换到最后一个元素位置上。

从以上过程可以看出冒泡法排序过程为:

(1)比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止——第一趟冒泡排序,结果最大的数被安置在最后一个元素位置上。

(2) 对前n-1个数进行第二趟冒泡排序,结果使次大的数被安置在第n-1个元素位置。

(3) 重复上述过程,共经过n-1趟冒泡排序后,排序结束。

规律:n个数要比较n-1趟,而在第j(j=1,2,3…n-1)趟比较中,要进行n-j次两两比较。

算法设计要点:

从上述实例可知,冒泡法排序算法的关键是:

(1)比较轮数:6个数需要比较5趟,可以证明n个数需要n-1趟。

(2)每轮比较的最后一个元素的下标:

设趟次为m、一共n个数排序,通过上表可以推导出每趟比较的最后一个元素的下标为n - m。

/*功能:冒泡法排序*/

#include "stdio.h"

#include "conio.h"

#define   N   6                 /*定义符号常量N(数据个数)*/

void main()

 { int  array[N];           /*定义1个1维整型数组array,共有N个元素*/

  int  loop1, loop2, temp;  /*定义循环变量和用于元素交换的临时变量*/

  /*输入N个数*/

  printf("Please input %d numbers(departed by space): ", N);

 for(loop1 = 0; loop1 < N; loop1++) 

        scanf("%d", &array[loop1]);

for(loop1 = 1; loop1 <= N - 1; loop1++)      /*控制比较趟数*/

 {

    for(loop2 = 0; loop2 < N - loop1; loop2++)   /*进行每趟比较*/

      {

          if (array[loop2] > array[loop2 + 1])  /*交换两数*/

              {

                temp = array[loop2]; 

                array[loop2] = array[loop2 + 1];

                array[loop2 + 1] = temp;

               }

    }

}

/*输出排序结果*/

    printf("\nthe  result of sort: ");

    for(loop1=0; loop1

         printf("%d  ", array[loop1]);

    printf("\n");

 }

选择排序是一种简单的排序方法。基本思想是:

第一趟从a[0]~a[n-1](n个数)中选取最小值,与a[0]交换,

第二趟从a[1]~a[n-1] (n-1个数)中选取最小值,与a[1]交换,

第三趟从a[2]~a[n-1] (n-2个数)中选取最小值,与a[2]交换,

第i趟从a[i-1]~a[n-1](n-i+1个数)中选取最小值,与a[i-1]交换,…,

第n-1趟从a[n-2]~a[n-1]](2个数)中选取最小值,与a[n-2]交换,

n个数总共通过n-1趟,得到一个按排序码从小到大排列的有序序列。

例如,给定n=8,数组R中的8个元素的排序

码为:(8,3,2,1,7,4,6,5),

则直接选择排序过程如图所示。

#define  N  8

main( )

{

 int a[N];

 int i,j,t;

 for (i=0; i

    scanf("%d",&a[i]);

 printf("\n");

 for (i=0; i

 /*控制比较趟数,确定基准位置 */

    for(j=i+1; j

       if (a[i]>a[j])      /*交换两数*/

         {

           t=a[j];

           a[j]=a[j];

           a[j]=t;

          }

 printf("The sorted numbers: \n");

 for (i=0; i

   printff("%d",a[i]);

 }

(二)查找算法

顺序查找即为从数组的一端开始,逐个进行数组元素的值和给定值x的比较,若某个元素的值和给定值x相等,则查找成功;反之,若直至最后一个数组元素,其值和给定值x都不相等,则表明数组中没有所查的数据,查找不成功。

例  已知存放在a数组中的数据两两不相同,在a数组中查找和x值相同的元素的位置。若找到,输出该值和其在a数组中的位置;若没找到,输出相应的提示信息。

#define N 100                                                   

main()                                                              

{

 int a[N],x,n,i,flag=-1;

 printf("Input n:\n");

scanf("%d",&n);

for(i=0;i

    scanf("%d",&a[i]);

printf("Input x:\n");

scanf("%d",&x);

for(i=0;i

    if(a[i]==x)

   {

     flag=i;

      break;

     }

if(flag!=-1)

  printf("%d index is %d",x,flag+1);

else 

 printf("%d donnt be founded!\n",x);

}

【融会贯通】 评委打分。某次歌咏比赛共有N个评委给M个选手打分(含一位小数),统计时去掉一个最高分和一个最低分,输出各选手的最后得分(指平均分,保留一位小数)。

(三)统计算法

例  统计随机产生的其中的0~9数字出现的次数并输出。

#include "stdio.h"

#include "stdlib.h"

#define N 10

 main()

{

   long int i,z, x[N]={0};

    randomize();

    for(i=0;i

    {

    z=rand()%10;

    printf("%ld   ",z);

    x[z]++;

    }     

    printf("\n  ");

    for(i=0;i

    printf("\n%ld----%ld   ",i,x[i]);

}

【融会贯通】 随机产生1000个大写字母,统计A~Z各个字符出现的次数并输出。

【融会贯通】 一辆以固定速度行驶的汽车,司机在上午10点时看到里程表上是一个对称数95859(公里),两小时后里程表上出现了另一个对称数。问该车的速度是多少?新的里程是多少公里?

四、字符数组

(一)字符数组的定义

      格式:char 数组名[exp];

       如:char c[10];

(二)字符数组的初始化

1、 给每一个字符型数组元素依次赋给一个单字符长量。如:char str[6]={‘C’, ‘h’, ‘i’, ‘n’, ‘a’, ‘\0’};

    2、 直接在赋值号右边给出字符串常量。如:    char str[6]=“China”;

注意: 

    1.  字符数组的长度可以通过赋初值的方式隐含定义;如:

 char str[ ]={‘C’, ‘h’, ‘i’, ‘n’, ‘a’, ‘\0’};

 char str[ ]=“China”;

如果括号中提供的初值个数(即字符个数)大于数组长度,则作语法错误处理。如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定义为空字符(即‘\0’)。

 (三)字符串和字符串结束标志

字符串结束标志是‘\0’,如:

char ch[]=”China”;  “China”在存储器中实际占用6个字节

(四)字符数组的输入和输出

1、字符数组的输出

(1)用格式符“%c”控制的 printf(),逐个字符输出

for(i=0;i

          printf(“%c”,str[i]);

(2)用格式符“%s”控制的 printf();字符数组值整体输出

printf(“%s”,str);

(3)puts (字符数组名);字符数组值整体输出

注意:

① puts用字符数组名,而不用数组元素名;

② 输出的字符串中可含转义字符;如:

static char str[]=“China\nBeijing”;   puts(str);

③ 如果数组长度大于字符串实际长度,也只输出到遇‘\0’时结束(即若一个字符数组中包含一个以上的‘\0’,则遇到第一个时输出就结束)。如:printf(“%s\n”,“ABCD\0EFGH”);  只输出ABCD

2、字符数组的输入

(1)用格式符“%s”控制的scanf();

如: static char str1[5],str2[5],str3[5];

scanf(“%s%s%s”, str1,str2,str3);  /*不能写成&str1*/

若输入数据流为:How  are  you? str1、str2、str3分别接收到“How”、“are”、“you?”,且在各个字符串的最后自动加‘\0’。

       如果利用一个scanf函数输入多个字符串时,则以空格分隔;

 

输入注意:

用字符数组名,不要加&

输入串的长度<数组元素个数;

遇空格或回车结束;

自动加‘\0’

(2)用gets( );如:char ch[16];  gets(ch);

注意:

①gets一次只能输入一个字符串;

②自变量应是数组名,而不能是数组元素名;

③要求从键盘上输入一个字符串直到遇到换行符为止,系统会自动把换行符换成‘\0’加在字符串末尾。

与scanf不同,输入字符串中的空格也会被接收。

( 五)字符串处理函数

1、puts(字符数组)

        功能:将一个以‘\0’结束的字符序列输出到终端;

          如:  static char str[ ]=“China”;puts(str);

         说明:输出的字符串中可含转义字符。

    2、gets(字符数组)

 功能:从终端输入一个字符串到字符数组中,并得到一个函数值,该函数值是字符数组的起始地址; 如:    gets(str);

        说明:gets与puts只能输入或输出一个字符串。 

3、strcat(字符数组1,字符数组2)

     功能:将字符串2接到字符串1的后而且去掉字符串1的尾空;

     如: static char str1[30]=“YangZhou  ”, str2[ ]=“China”;

          printf(“%s\n”,strcat(str1,str2));

说明:  ①字符数组1的长度需足够大; ②去掉字符串1的尾空。

4、strcpy(字符数组1, 字符串2)

     功能:将字符串2拷贝到字符数组1中去;

     如: static char str1[10],str2[ ]=“China”;

            strcpy (str1,str2);

5、strcmp(字符串1,字符串2)

  功能:比较字符串1和字符串2,返回:①串1= 串2,返回0;

         ②串1>串2,返回正整数;③串1<串2,返回负整数。

6、strlen(字符数组)

    功能:测试字符串的长度;

    如: static char str1[10]=“China”;        printf(“%d\n”,strlen(str));

    说明:不包含‘\0’在内。特殊字符%%、\’、\\、\n代表一个字符。

7、strlwr(字符串)

    功能:将字符串中的大写字母转换成小写字母;

8、strupr(字符数组)

    功能:将字符串中的小写字母转换成大写字母。

例 输入一行字符,将其中元音字母、非元音字母、数字和其它符号分别放入几个字符数组中。

【简要分析】设原文放于字符数组str中,从中挑选出的字符形成的四个子串分别放于字符数组str1、str2、str3、str4中。考虑到特殊情况,定义四个子串的长度与原串长度相同。分别用四个变量k1、k2、k3、k4代表四个子串的下标,显然,这四个下标的增长速度是不同的。

用自然语言描述的程序逻辑如下:

①  设置环境,定义字符数组str、str1、str2、str3、str4并清空,定义各自的下标i、k1、k2、k3、k4,并置零。

②  输入一行英文,放于str数组中。

③  str[i]不等于结束符?成立转④,不成立则转⑩。 

④  如果str[i]是元音字母,则将其放于str1[]数组中,转⑦。

⑤  如果str[i]是非元音字母,则将其放于str2[]数组中,转⑦。

⑥  如果str[i]是数字字符,则将其物于str3[]数组中,否则将其放于str4[]数组中。

⑦  i=i+1。

⑨  转③,继续判断str中下一字符。

⑩  输出分流后的字符数组str1、str2、str3、str4,结束。

参考源代码:

/* 例6-12,6-12.c */

#include

#include

void main()

{

   char str[80], str1[80], str2[80], str3[80],str4[80];

   int i = 0, k1 = 0, k2 = 0, k3 = 0, k4 = 0;

   printf(“\n请输入一行字符:”);

   gets(str);

   while ( str[i] != '\0' )

   {

        if ( isalpha(str[i]) )                 /* 判断某一个字符是否是字母 */

          if ( strchr("aeiouAEIOU", str[i]) )  /* 判断某一个字符是否是元音字母 */

str1[k1++] = str[i];

          else

              str2[k2++] = str[i];

        else

if ( isdigit(str[i]) )           /* 判断某一个字符是否是数字 */

str3[k3++] = str[i];     

else

str4[k4++] = str[i];       /* 该字符非字母、非数字 */

      i++;

   }

   puts(str1);

   puts(str2);

   puts(str3);

   puts(str4);

}

例 统计输入的一行字符中英文大写字母、英文小写字母、数字字符及其它字符各有多少个。

#define  N  81

#include   "stdio.h"

#include   "string.h"

void  main()

 { char str[N];

    int i, n, upper=0, lower=0,digit=0,other=0;

    gets(str);     n = strlen(str);

    for(i=0;i

         if(str[i]>='A'&&str[i]<='Z')   upper++;

         else if(str[i]>='a'&&str[i]<='z')   lower++;

                else if(str[i]>='0'&&str[i]<='9')  digit++;

                       else  other++;    printf("uppercase:%d,lowercase:%d,digit:%d,other:%d\n",upper,lower,digit,other);

 }

例 输入一个字串,判断它是否是对称串。如”abcdcba”是对称串,”123456789”不是。

#include "string.h"

#include "stdio.h"

void main()

{

char a[30],b[30];

    gets(a);

    strcpy(b,a);

    if(strcmp(strrev(a),b)==0)

    printf("yes");

 else

    printf("no");

}

例 随机产生N个大写字母输出,然后统计其中共有多少个元音字符。(设N为200)

#include "stdio.h"

#include "stdlib.h"

#include "time.h"

#define N 200

void main()

{

  char x[N];

  int k=0,i; 

  randomize();

   for(i=0;i<=N-2;i++)

       {

 x[i]=random(26)+65;

       if(x[i]=='A'||x[i]=='E'||x[i]=='I'||x[i]=='O'||x[i]=='U')

          k++;

}

    printf("k=%d",k);

}

【融会贯通】 从键盘输入两个字串,输出其中较短的那个字串,并输出它的长度。

例 从键盘上输入一行由小写英文组成的字符串,用置换法(置换规律:按字母表逆序)对其加密。

“abcdefghijklmnopqrstuvwxyz”,按位置置换为:

“zyxwuvtsrqponmlkjihgfedcba”。

例如,设原文为“student”,则密文是:“hgfwumg”。

不难理解:对同一原文,采用不同的置换规律,会得到不同的密文。

用自然语言描述的程序逻辑如下:

① 设置环境,定义变量。

② 将字母逆序表放于数组key[27]中;输入原文,放于数组str[80]中。

③ i=0。

④ 原文处理到完否(str[i]为’\0’)?没有,则转⑤,否则转⑧。

⑤ 计算字母str[i]在key[27]中出现的位置k。

⑥ 用字母key[k]置换字母str[i]。

⑦ i=i+1,转④。

⑧ 输出密文str串,结束。

参考源代码:

/* 例6-14,6-14.c */

#include

void main()

{

   char key[] = ”zyxwuvtsrqponmlkjihgfedcba”, str[80];

   int i, k;

   printf(“\n请输入原文:”);

   gets(str);

   for ( i = 0; str[i] != ‘\0’; i++ )

   {

    k = str[i] – 97;

    str[i] = key[k];

}

printf(“\n密文是:”);

puts(str);

    }

练习:

1. 随机产生200个四位正整数,先按它们后三位(或前三位)升序排列,如果后三位(或前三位)相等,则按原先的数值大小降序排列。最后,把排序后的后10个数存入数组b中。

2. 随机产生N个四位正整数,将其中的素数选出,并升序排列之输出(以每行M个素数的格式)。

3. 键盘输入N个实数,输出其中的最大数及其出现的个数。

4. 键盘输入N个整数,分别求其中奇数、偶数的均方差。

5. 筛法找素数。用筛法求[1,n]区间的素数,并以每行10个的格式输出。筛法即埃拉托色尼筛法,他是古西腊数学家。算法是不断从数组元素中挖掉(清0)非素数,最后剩下的就是素数。

① 将[1,100]内各整数放入一维数组中,如a数组。

② 挖掉a[0],因1不是素数。( a[0] = 0 )

③ 用一个未被挖去的数a[i]去除它后边的所有元素,凡是能除尽者挖掉。即挖掉a[i]的所的倍数。

④ a[i]小于N的平方根,重复③,否则结束。

⑤ 输出a数组中的非0元素。

6. 抛骰计点。模拟掷两个骰子1000次,统计顶面各点数(2至12)出现的次数。骰子是立方体,六个面上的点数是1点、2点……6点。

7. 随机产生N2个两位自然数,排列成N阶方阵。对原始方阵,分别完成:

 1)将第一行与最后一行交换,第二行与倒数第二行交换……

 2)求两对角线元素之和。 

  3) 求转置矩阵。(即行列互换:第1行作第1列,第2行作第2列,……)

8. 键盘输入一个字串,删除其中的元音字母。

9. 输入一行英文,统计其中有多少个单词。

10. 加密N行英文。各行加密方法:第一个字符的ASCII值加第二个字符的ASCII值,得到第一个字符,第二个字符的ASCII值加第三个字符的ASCII值,得到第二个新字符,依此类推一直处理到最后第二个字符,最后一个字符的ASCII值加原第一个字符的ASCII值,得到最后一个新的字符,得到的新字符分别存放在原字符串对应的位置上。最后把已处理的行字符串逆转。

11. 输入一行英文,先将其中第奇数个字符按字母表顺序升序排列、将其中第偶数个字符按字母表顺序降序排列,输出。最后交换左半串字符、右半串字符,如果原串长度为奇数,则交换时正中间字符位置不变。

12. 输入一行英文,先去掉其中的标点和多余空格,使各单词间只用1个空格分隔,然后将所有单词倒排。如:“ok? I am a student. ”,排序结果为:“ko I ma a tneduts”。

五、 本章后记及实训小结

1、本章涉及到一、二维数组、字符数组的定义、引用、初始化及应用,是C语言的核心内容之一,相对于前面的内容来说,虽然知识点不是很多,但在应用时会有一定的难度。为了降低难度,对问题进行分解,然后再把它们组合在一起形成一个完整的程序。

2、数组的应用是一个难点,为了方便学生解决问题,把现实中经常要处理的过程作成算法,让学生理解领会,并用在自己的程序设计中。对数组操作的一些基本算法:排序(起泡法和选择法)、查找、求极值、插入和删除等,要求学生不但理解,而且能够用程序实现。

3、字符数组的应用与数值型数组有些区别,要求学生能够正确把握,通过实例比较让学生自己进行区别。此外,字符串处理函数(gets()、puts()、strlen()、strcpy()、strcmp()、strcat())既是一个重点也是个难点,学生应通过具体应用对此掌握下来,并注意进行总结。

4、在上机中出现问题较多的是数组的控制,界限的问题,应用在程序中仍然是个大问题。为解决这个问题,在掌握基本知识的情况下,一方面要求学生多读程序,建立正确的解决问题的思路;另一方面多动手实践,把实寻训题及课后习题尽可能都编写出来上机调试,以找出问题所在,并找出解决的方法。

第七章  指    针

[教学目的] 熟练掌握指针、地址、指针类型、void指针类型、空指针等概念,熟练掌握指针变量的定义和初始化、指针的间接访问、指针的加减运算和指针表达式,掌握指针与数组、函数、字符串、自由空间等的联系。

[教学内容] 地址和指针的概念、指向变量的指针变量、指向数组的指针变量 、指向字符串的指针变量 

重点:指针、地址、空指针等概念,指针变量的间接访问、指针的加减运算和指针表达式,指针与数组、字符串等的联系。

难点:指针变量的间接访问、指针的加减运算和指针表达式,指针与数组、字符串等的联系。

[教学方法] 多媒体

一 、引入

    在数组的学习中我们知道,数组的名称是一个特殊的数据,代表的是存放数组元素的首地址,地址不属于任何基本数据类型,那么有没有办法把一个变量的地址存放在另一种变量中呢,答案是可以的,那么这种变量就是一种新类型的变量—指针变量。

二、指针和地址的概念

1、指针是c语言中比较灵活的数据结构,灵活地运用指针可以:

   (1) 有效地表示复杂的数据结构;

   (2) 动态分配内存;

   (3) 更方便地使用字符串和数组;

   (4) 直接处理内存地址。

2、对内存单元的存取有两种方式

   (1) 直接访问:按变量地址存取变量的值。

   (2) 间接访问:将变量i的地址存放在另一个内存单元j中,要访问i,则先按j的地址取出i的地址,再按i的地址取出i的值。

3、指针变量的概念

   一个变量的地址称为该变量的“指针”。

   如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”,指针变量的值是指针。

三、变量的指针和指向变量的指针变量

1、指针变量的定义

指针变量必须在使用之前先定义,它是用来存放地址的。

定义的一般形式为: 类型标识符   *标识符

例:   int  *p1 ;   char  *p2 ;    float  *p3 ;

注:

(1) 标识符前面的“*”,表示该变量为指针变量。但指针变量名是p1、p2、p3,而不是*p1、*p2、*p3

(2)  "*" 表示"指向", 故*p1,*p2, *p3表示指针变量 p1,  p2, p3所指向的变量。

(3) 一个指针变量只能指向同一个类型的变量。

2、指针变量的引用

两个有关的运算符:

  1. &:取地址运算符

(2)  *:指针运算符(或称"间接访问"运算符)

例如,&a为变量a的地址,*p为指针变量p所指向的或存储空间。

使用*p与定义*p不同,定义时,int *p中的“*”不是运算符,它只是表示其后面的变量是一个指针类型的变量,而在程序执行语句中,引用“*p”,其中的“*”是一个指针运算符,*p表示“p指向的存储单元”,例如:

int *p,i = 10;

p=&i;

printf(“%d”,*p);

printf(“%d”,i);

将输出相同的结果 10

关于&:

①  运算符&后面必须是内存中的对象(变量、数组元素等等),不能是常量、表达式或寄存器变量,如 q1=&(k+1)是错误的。

②  &后的运算对象类型必须与指针变量的基类型相同

int x,y,*pi;

float z;

pi = &z;    /*error*/

既然在指针变量中只能存放地址,因此,在使用中不要将一个整数赋给一指针变量。下面的赋值是不合法的:

int *pi;

pi=100;

假设

int i=200,x;

int *pi;

我们定义了两个整型变量i,x,还定义了一个指向整型数的指针变量pi。i,x中可存放整数,而pi中只能存放整型变量的地址。我们可以把i的地址赋给pi:

pi=&i;

假设变量i的地址为1800,此时指针变量pi指向存储空间1800,这个赋值可形象理解为下图所示的联系。

uploading.4e448015.gif

正在上传…重新上传取消

以后,我们便可以通过指针变量pi间接访问变量i,例如: x=*pi;

运算符* 访问以pi为地址的存贮区域,而pi中存放的是变量i的地址,因此,*pi访问的是地址为1800的存贮区域(因为是整数,实际上是从1800开始的两个字节),它就是i所占用的存贮区域,所以上面的赋值表达式等价于 x=i;

通过指针访问它所指向的一个存储单元是以间接访问的形式进行的,所以没有直接地对一个变量进行访问来的直观,因为通过指针访问哪一个存储单元,取决于指针的值(即指向),例如"*p2=*p1;"实际上就是"j=i;",前者不如后者来的明显。但由于指针是变量,所以,我们可以通过改变它们的指向,以间接方式访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和高效。

指针变量可出现在表达式中,设

int x,y,*px=&x;

指针变量px指向整数x,则*px可出现在x能出现的任何地方。例如:

    y=*px+5;  /*表示把x的内容加5并赋给y*/

    y=++*px;  /*px的内容加上1之后赋给y  (++*px相当于++(*px))*/

    y=*px++;  /*相当于y=*px; px++*/

3、指针变量作为函数参数

C语言中,函数的参数的类型可以是整型、实型、字符型、数组,也可以是指针类型。

在对本节的学习中,我们要注意一点的是,C语言中函数的实际参数向形式参数对应的局部变量传值的原理都是一样,即:将实参的值传送到形参对应的局部变量的存储单元中。

注意:

① 不能企图通过改变形参地址而使实参地址也改变,也就是说调用函数不能改变实参指针变量的值。

    ② 调用函数可以改变实参指针变量所指变量的值。

例 分析下列语句是否正确。

① int i;

   char *p = &i;        /* 错误!p与i类型不同 */

② int i, *p = &i, *q;

   q = i;                     /* 错误!q、i类型不同,q为指针变量,i为普通整型变量 */

   q = p;                    /* 正确!q、p均是同类型的指针变量 */

③ char *p=200;       /* 错误!不能将一个常量直接赋给指针变量 */

四、指针和数组

数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

1、指向数组元素的指针变量(与前面介绍的指向变量的指针变量相同)

若: int a[10],*p;

     p=&a[0];

则:(1) p+i和a+i就是a[i]的地址,或者说它们指向数组的第i个元素。

    (2) *(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。

    (3) 指向数组的指针变量可以带下标,如p[i]与*(p+i)等价。

例:输出数组全部元素。(设a数组,整型,10个元素)

指针法、下标法、地址法

在使用指针变量时应注意的几个问题:

  (1)数组名是数组的首地址,因此 p=&a[0]  与  p=a  等价。

  (2)指针变量可以实现使本身的值改变,数组名不可以。

                  例:   p++正确     a++不正确

  (3)要注意指针变量的当前值。

  (4)指针变量可以指到数组后的内存单元。

  (5)注意指针变量的运算,若先使p指向数组a(即p=a),则

      ① p++ (或p+=1) 指向下一个元素。

      ② *p++等价于*(p++)先得到p指向的变量的值,p再③加1。

      ③*(p++)与*(++p)作用不同,前者先取*p,后使p加 1;后者相反。

      ④ (*p)++表示p所指向的元素值加1。

      ⑤ 若p当前指向a数组第i个元素,则:

         *(p--)相当于a[i--],先取p值作"*"运算,再使p自减;

         *(++p)相当于a[++i],先使p自加,再作"*"运算;

         *(--p)相当于a[--i],先使p自减,再作"*"运算。

2、数组名作函数参数

   用数组名作参数,在调用函数时实际上是把数组的首地址传给形参,这样实参与形参共同指向同一段内存。因而调用过程中,如形参数组元素值发生变化也就使实参数组的元素发生了变化,但这种变化并不是从形参传回实参的,而是由于形参与实参共享同一段内存而造成的。(例题见课件)

如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下几类情况:

    (1) 形参和实参都用数组名

    (2) 实参用数组名,形参用指针变量

    (3) 实参和形参都用指针变量

    (4) 实参为指针变量,形参为数组名

3、指向多维数组的指针和指针变量

(1 ) 多维数组的地址

在这节中,我们需要知道多维数组,特别是二维数组的行、列和每个元素的地址计算规则。

(2 )多维数组的指针

可以用指针变量指向多维数组及其元素

   ① 指向数组元素的指针变量

例:用指针变量输出数组元素的值。

main( )

    {

static int a[2][3]={2,3,6,7,8,5};

      int i,j,*p;

   p=a[0];

   for(i=0;i<2;i++)

    {for(j=0;j<3;j++)

     printf("%3d",*p++);

     printf("\n");   

     } 

 }

② 指向由m个整数组成的一维数组的指针变量。(指向一维数组的指针变量)

例:输出二维数组任一行任一列的元素的值。

    main ( )

{static int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

     int (*p)[4],i,j;

     p=a;

     scanf("i=%d,j=%d",&i,&j);

     printf("a[%d,%d]=%d\n'',i,j,*(*(p+i)+j));   }    

说明:

  1. int (*p)[4] 表示p指向一个包含4个元素的一维数组.
  2. p+1使指针移动2*4个字节, *(p+1)使指针的指向由横向转向纵向.
  3. *(p+i)+j使指针移动2*4*i+2*j.

③ 多维数组的指针作函数参数。   

五、字符串的指针和指向字符串的指针变量

1、字符串的表示形式

  (1)字符数组

    例:main

       {static char string[ ]="I Love China!";

        printf("%s\n",string);         }

(2)字符串指针

    例:main

       {char  *string="I Love China!";

        printf("%s\n",string);         }

例: 将字符串a复制到字符串b中。

方法1:地址法

方法2:指针法

2、字符串指针作函数参数

例:用函数调用实现字符串的复制。

方法1:地址法   方法2:指针法

3、字符指针变量与字符数组的区别

(1)字符指针变量中存放的是地址,决不是将字符串放到字符指针变量中。

(2)赋初值的方式:对数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。                 

(3)指针变量可以指向一个字符型数据,但如果未对它赋以一个地址值,则它并未具体指向哪一个字符数据。

      如:    char   *a;

            scanf("%s",a);

           是错误的

(4)指针变量的值是可以改变的,而数组名所指的地址是固定的。

    如: main( )

         {char  *a="I love  China!";

            a=a+7;

            printf("%s", a);  }

如: main( )

           {static char  a[ ]="I love  China!";

            a=a+7;

            printf("%s", a);      }

           是错误的!

(5)用指针变量指向一个格式字符串。(指针变量的一个特殊用法)

例 输入任意一行字符,降序排列之。

#include

main()

{

   char *cp, i, j, n, temp;

   printf(“\n请输入一行字符: ”);

   gets(cp);

   n = strlen(cp);

   for ( i = 0; i < n-1; i++ )

      for ( j = i + 1; j < n; j++ )

         if ( *(cp + j) > *( cp + i) )

         {

             temp = *(cp + j) ;

             *(cp + j) = *(cp +i) ;

             *(cp + i) = temp ;

         }

   printf(“\n 排序结果是: ”);

puts(cp);

}

六、有关指针的数据类型和指针运算小结

1、指针的数据类型:

      int i,*p1,a[10],*p2[5],(*p3)[5],*p4(),(*p5)(),**p6;

(1) 指针变量加(减)一个整数

C规定,一个指针变量加(减)一个整数并不是简单地加(减)一个整数,而是将该指针变量的地址和它指向的变量所占的内存字节数相加(减)。即:

     p+i 代表地址计算:p+i*d (d为一个变量所占字节数)

(2) 指针变量赋值

将一个变量地址赋给一个指针变量,而不能把一个整数赋给指针变量,也不能把一个指针变量的值赋给一个整型变量。

       如: p=&a;  p=array;  p=&array[i];等

(3) 指针变量可以有空值,即该指针变量不指向任何变量。

        表示: p=NULL;

   NULL是整数0,p指向地址为0的单元,所以先定义 NULL,即: #define   NULL  0

(4) 两个指针变量可以相减

两个指针变量指向同一个数组的元素,则两个指针变量之差是两个指针之间的元素个数,但两个指针变量之和无意义。

(5) 两个指针变量比较

两个指针变量指向同一个数组的元素,则可以进行比较。指向前面的指针变量小于指向后面的指针变量。若不指向同一个数组则比较无意义。

七、本章后记及总结

   本章内容带有一定的难度,在学习的过程出现了不少问题,一部分同学对指针的概念不能正确的认识,但随着课程的深入和实践的增加,大部分同学基本能运用指针来处理问题,在指针应用中,指向数组的指针是一个重点,在这不部分内容上加大了力度,不但占用课时多,而且习题也多。再一个重点是返回指针值的函数,在应用中也比较常见,因此除了掌握本章的基本内容外,把这两个内容作为重点。

第八章    函    数

[教学目的]掌握函数定义、形参、实参、返回值、一般调用、递归调用;掌握局部变量和全局变量,动态变量和静态变量。

[教学内容] 函数定义的一般形式 、函数参数和函数的值、函数调用的一般形式 、 函数的嵌套调用、函数的递归调用 、数组作为函数参数 、局部变量和全局变量 、变量的存储类别

重点:函数定义、形参、实参、返回值、一般调用、递归调用、局部变量和全局变量,动态变量和静态变量。

难点:函数的递归调用;局部变量和全局变量、动态变量和静态变量的用法。

[教学方法] 多媒体

使用函数可以控制任务的规模、控制变量的作用范围、程序的开发可以由多人分工协作、使用函数、可以重新利用已有的、调式好的、成熟的程序模块。

一、函数的定义

1、任何函数(包括主函数main())都是由函数说明和函数体两部分组成。根据函数是否需要参数,可将函数分为无参函数和有参函数两种。

(1)无参函数的一般形式

    函数类型  函数名( void )

            { 说明语句部分;

                可执行语句部分; }

注意:在旧标准中,函数可以缺省参数表。但在新标准中,函数不可缺省参数表;如果不需要参数,则用“void”表示,主函数main()例外。

(2)有参函数的一般形式

函数类型  函数名( 数据类型  参数[,数据类型  参数2……] )

            { 说明语句部分;

                可执行语句部分;  }

有参函数比无参函数多了一个参数表。调用有参函数时,调用函数将赋予这些参数实际的值。

为了与调用函数提供的实际参数区别开,将函数定义中的参数表称为形式参数表,简称形参表。

2、说明

(1)函数定义不允许嵌套。

在C语言中,所有函数(包括主函数main())都是平行的。一个函数的定义,可以放在程序中的任意位置,主函数main()之前或之后。但在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。

(2)空函数──既无参数、函数体又为空的函数。其一般形式为:

[函数类型]  函数名(void)

        { }

(3)在老版本C语言中,参数类型说明允许放在函数说明部分的第2行单独指定。

二、函数的返回值与函数类型

C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。

1、函数返回值与return语句

有参函数的返回值,是通过函数中的return语句来获得的。

(1)return语句的一般格式:  return ( 返回值表达式 ); 

(2)return语句的功能:返回调用函数,并将“返回值表达式”的值带给调用函数。

注意:调用函数中无return语句,并不是不返回一个值,而是一个不确定的值。为了明确表示不返回值,可以用“void”定义成“无(空)类型”。

2、函数类型

在定义函数时,对函数类型的说明,应与return语句中、返回值表达式的类型一致。

三、对被调用函数的说明和函数原型

在ANSI C新标准中,采用函数原型方式,对被调用函数进行说明,其一般格式如下:

函数类型  函数名(数据类型[  参数名][, 数据类型[  参数名2]…]);

    C语言同时又规定,在以下2种情况下,可以省去对被调用函数的说明:

(1)当被调用函数的函数定义出现在调用函数之前时。因为在调用之前,编译系统已经知道了被调用函数的函数类型、参数个数、类型和顺序。

(2)如果在所有函数定义之前,在函数外部(例如文件开始处)预先对各个函数进行了说明,则在调用函数中可缺省对被调用函数的说明。

四、 函数的形参与实参

函数的参数分为形参和实参两种,作用是实现数据传送。形参出现在函数定义中,只能在该函数体内使用。发生函数调用时,调用函数把实参的值复制1份,传送给被调用函数的形参,从而实现调用函数向被调用函数的数据传送。

说明:

(1)实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此,应预先用赋值、输入等办法,使实参获得确定的值。

(2)形参变量只有在被调用时,才分配内存单元;调用结束时,即刻释放所分配的内存单元。因此,形参只有在该函数内有效。调用结束,返回调用函数后,则不能再使用该形参变量。

(3)实参对形参的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。

(4)实参和形参占用不同的内存单元,即使同名也互不影响。

(5) 实参与形参的类型相同或赋值兼容。

五、  函数的调用

(一)函数调用

在程序中,是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。

C语言中,函数调用的一般形式为:

 函数名([实际参数表])

切记:实参的个数、类型和顺序,应该与被调用函数所要求的参数个数、类型和顺序一致,才能正确地进行数据传递。

在C语言中,可以用以下几种方式调用函数:

(1)函数表达式。函数作为表达式的一项,出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。如:y=2*abs(x);

(2)函数语句。C语言中的函数可以只进行某些操作而不返回函数值,这时的函数调用可作为一条独立的语句。如:printf(“hello!”);

(3)函数实参。函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。如:m=max(a,max(b,c));

说明:

①  调用函数时,函数名称必须与具有该功能的自定义函数名称完全一致。

②  实参在类型上按顺序与形参,必须一一对应和匹配。如果类型不匹配,C编译程序将按赋值兼容的规则进行转换。如果实参和形参的类型不赋值兼容,通常并不给出出错信息,且程序仍然继续执行,只是得不到正确的结果。

③  如果实参表中包括多个参数,对实参的求值顺序随系统而异。有的系统按自左向右顺序求实参的值,有的系统则相反。Turbo C和MS C是按自右向左的顺序进行的 。

(二)函数的嵌套调用

函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其它函数。这与其它语言的子程序嵌套调用的情形是类似的,其关系可表示如图。

     

(三)函数的递归调用

函数的递归调用:一个函数在它的函数体内,直接或间接地调用它自身,前者称为直接递归调用,后者称为间接递归调用。

C语言允许函数的递归调用:在递归调用中,调用函数又是被调用函数,执行递归函数将反复调用其自身,每调用一次就进入新的一层。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。

递归的组成:递归方式和递归终止条件

例 利用函数的递归调用求x的n次方。

【简要分析】递归有两个阶段,第一阶段是“回推”,欲求x的n次方,回求x的n-1次方,再加求x的n-2次方……,当回推到x的0次方时,此时能够得到x的0次方为1,就不再回推了;然后进入第二阶段“递推”,由x的0次方开始,求x的1次方,x的2次方……,直到x的n次方。设变量表如下表。

变量表

变量名

含   义

类型

初值

x

底数

int

输入

n

幂指数

int

输入

result

计算结果

int

0

参考源代码:

/* 例8-4,8-4.c */ 

#include

void main()

{

   double xpower(double x, int n );

   int n;

   double x, result;

   scanf("%lf, %d", &x, &n);

   result = xpower(x, n);          /* 以表达式的方式调用函数xpower() */

   printf("result = %lf \n", result);

}

double xpower(double x , int n)

{

   if ( n <= 0 )

     return 1;

   else

     return  ( x * xpower(x, n-1) );    /*  返回计算结果给主调函数 */

}

以求5的3次方为例,递归调用的过程如图所示。

图 8-6  函数递归调用分析图

x * xpower(5, 1)

xpower函数

x * xpower(5, 0)

xpower函数

xpower(5,0)=1

xpower(5,1)=5

xpower( 5, 3)

输出计算结果

main( )

x * xpower(5, 2)

xpower函数

xpower(5,2)=25


   递归是C语言的重要特点之一,递归的优点就是程序结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便;递归的缺点是递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。

须要说明的是,一个问题能用递归方法求解,必须符合两个条件:

第一、可将一个问题转化为具有同样解法的规模较小的问题;

第二、必须有明确的结束条件。

【融会贯通】有5个人坐在一起,问第5个人多少岁,他说比第4个人大2岁,问第4个人的岁数,他说比第3个人大2岁,问第3个人的岁数,他说比第2个人大2岁,问第2个人,他说比第1个人大2岁,问第一个人,他说是10岁。请问第5个人的岁数?

【融会贯通】用递归法计算n!(通过函数的递归调用计算阶乘)

算法分析:

1             (n<=1)

递归数学模型

n*power(n-1)   ( n>1)

power(n)=

六 、数组作为函数参数 (选讲)

数组用作函数参数有两种形式:一种是把数组元素(又称下标变量)作为实参使用;另一种是把数组名作为函数的形参和实参使用。

(一) 数组元素作为函数参数

    数组元素就是下标变量,它与普通变量并无区别。数组元素只能用作函数实参,其用法与普通变量完全相同:在发生函数调用时,把数组元素的值传送给形参,实现单向值传送。

说明:

(1)用数组元素作实参时,只要数组类型和函数的形参类型一致即可,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。

(2)在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送,是把实参变量的值赋予形参变量。

(二)数组名作为函数的形参和实参

数组名作函数参数时,既可以作形参,也可以作实参。

数组名作函数参数时,要求形参和相对应的实参都必须是类型相同的数组(或指向数组的指针变量),都必须有明确的数组说明。

说明:

(1)用数组名作函数参数,应该在调用函数和被调用函数中分别定义数组,且数据类型必须一致,否则结果将出错。例如,在本案例中,形参数组为a[],实参数组为sco[],它们的数据类型相同。

(2)C编译系统对形参数组大小不作检查,所以形参数组可以不指定大小。本案例中的形参数组a[]。 如果指定形参数组的大小,则实参数组的大小必须大于等于形参数组,否则因形参数组的部分元素没有确定值而导致计算结果错误。

(3)实参和形参共用一部分存储单元,形参中元素值发生变化,则结果会带回实参数组。(表面上双向传递)

(三)用多维数组名作为函数参数

例:有一个3*4的矩阵,求所有元素中的最大值。

算法:1、int max;max=array[0][0];

      2、if(max

      3、全部比较完后,max中放的就是最大数。

要求:编一个函数实现上述功能。

说明:1、多维数组名作为实参和形参,形参数组定义时可以省略第一维;

2、传送的仍然是起始地址,按行存放。

七、全局变量与局部变量

C语言中所有的变量都有自己的作用域。变量说明的位置不同,其作用域也不同,据此将C语言中的变量分为全局变量和局部变量。      

(一) 局部变量

在一个函数内部说明的变量是局部变量,它只在该函数范围内有效。

也就是说,只有在包含变量说明的函数内部,才能使用被说明的变量,在此函数之外就不能使用这些变量了。所以局部变量也称“内部变量”。

例如:

int f1(int a)     /*函数f1*/

    {  int b,c;

        ……    }     /*a,b,c作用域:仅限于函数f1()中*/

 int f2(int x)     /*函数f2*/

   {  int y,z;     

 ……

{int y;

…… }        /*y作用域:仅限于复合语句中*/

   }       /*x,y,z作用域:仅限于函数f2()中*/

 main()

    { int m,n;

       ……    }      /*m,n作用域:仅限于函数main()中*/

关于局部变量的作用域还要说明以下几点:

1、主函数main()中定义的内部变量,也只能在主函数中使用,其它函数不能使用。同时,主函数中也不能使用其它函数中定义的内部变量。因为主函数也是一个函数,与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。

2、形参变量也是内部变量,属于被调用函数;实参变量,则是调用函数的内部变量。

3、允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。

4、在复合语句中也可定义变量,其作用域只在复合语句范围内。

(二)全局变量

在函数外部定义的变量称为外部变量。以此类推,在函数外部定义的数组就称为外部数组。外部变量不属于任何一个函数,其作用域是:从外部变量的定义位置开始,到本文件结束为止。外部变量可被作用域内的所有函数直接引用,所以外部变量又称全局变量。

对于全局变量还有以下几点说明:

(1)外部变量可加强函数模块之间的数据联系,但又使这些函数依赖这些外部变量,因而使得这些函数的独立性降低。

从模块化程序设计的观点来看这是不利的,因此不是非用不可时,不要使用外部变量。

(2)在同一源文件中,允许外部变量和内部变量同名。在内部变量的作用域内,外部变量将被屏蔽而不起作用。

(3)外部变量的作用域是从定义点到本文件结束。如果定义点之前的函数需要引用这些外部变量时,需要在函数内对被引用的外部变量进行说明。外部变量说明的一般形式为:

extern  数据类型  外部变量[,外部变量2……];

注意:外部变量的定义和外部变量的说明是两回事。外部变量的定义,必须在所有的函数之外,且只能定义一次。而外部变量的说明,出现在要使用该外部变量的函数内,而且可以出现多次。

八、变量的存储类别

在C语言中,对变量的存储类型说明有以下四种:自动变量(auto)、寄存器变量(register)、外部变量(extern)、静态变量(static)。自动变量和寄存器变量属于动态存储方式,外部变量和静态内部变量属于静态存储方式。

(一)动态存储──自动局部变量(又称自动变量)

1、定义格式:[auto]  数据类型  变量表;

2、存储特点

(1)自动变量属于动态存储方式。在函数中定义的自动变量,只在该函数内有效;函数被调用时分配存储空间,调用结束就释放。

在复合语句中定义的自动变量,只在该复合语句中有效;退出复合语句后,也不能再使用,否则将引起错误。

(2)定义而不初始化,则其值是不确定的。如果初始化,则赋初值操作是在调用时进行的,且每次调用都要重新赋一次初值。

(3)由于自动变量的作用域和生存期,都局限于定义它的个体内(函数或复合语句),因此不同的个体中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量,也可与该函数内部的复合语句中定义的自动变量同名。

建议:系统不会混淆,并不意味着人也不会混淆,所以尽量少用同名自动变量!

(二)静态存储──静态内部变量

1、定义格式: static  数据类型  内部变量表;

2、存储特点

 (1)静态内部变量属于静态存储。在程序执行过程中,即使所在函数调用结束也不释放。换句话说,在程序执行期间,静态内部变量始终存在,但其它函数是不能引用它们的。

 (2)定义但不初始化,则自动赋以"0"(整型和实型)或'\0'(字符型);且每次调用它们所在的函数时,不再重新赋初值,只是保留上次调用结束时的值!

3、何时使用静态内部变量

(1)需要保留函数上一次调用结束时的值。

(2)变量只被引用而不改变其值。

(三)寄存器存储──寄存器变量

一般情况下,变量的值都是存储在内存中的。为提高执行效率,C语言允许将局部变量的值存放到寄存器中,这种变量就称为寄存器变量。定义格式如下:

          register   数据类型   变量表;

1、只有局部变量才能定义成寄存器变量,即全局变量不行。

2、对寄存器变量的实际处理,随系统而异。例如,微机上的MS C和TC 将寄存器变量实际当作自动变量处理。

3、允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量。

(四)外部变量(属于静态存储方式)

1、静态外部变量──只允许被本源文件中的函数引用

其定义格式为:  static  数据类型  外部变量表;

2、非静态外部变量──允许被其它源文件中的函数引用

定义时缺省static关键字的外部变量,即为非静态外部变量。其它源文件中的函数,引用非静态外部变量时,需要在引用函数所在的源文件中进行说明:

extern  数据类型  外部变量表;

注意:在函数内的extern变量说明,表示引用本源文件中的外部变量!而函数外(通常在文件开头)的extern变量说明,表示引用其它文件中的外部变量。

静态局部变量和静态外部变量同属静态存储方式,但两者区别较大:

(1) 定义的位置不同。静态局部变量在函数内定义,静态外部变量在函数外定义。

(2) 作用域不同。静态局部变量属于内部变量,其作用域仅限于定义它的函数内;虽然生存期为整个源程序,但其它函数是不能使用它的。

静态外部变量在函数外定义,其作用域为定义它的源文件内;生存期为整个源程序,但其它源文件中的函数也是不能使用它的。

(3) 初始化处理不同。静态局部变量,仅在第1次调用它所在的函数时被初始化,当再次调用定义它的函数时,不再初始化,而是保留上1次调用结束时的值。而静态外部变量是在函数外定义的,不存在静态内部变量的“重复”初始化问题,其当前值由最近1次给它赋值的操作决定。

务必牢记:把局部变量改变为静态内部变量后,改变了它的存储方式,即改变了它的生存期。把外部变量改变为静态外部变量后,改变了它的作用域,限制了它的使用范围。因此,关键字“static”在不同的地方所起的作用是不同的。

九、 本章后记及上机总结

1、函数定义的一般形式 、函数参数(形参和实参)、函数调用的一般形式和函数的返回值,因为涉及的知识点较多,这是一个比较复杂的问题,需要多编程练习。

2、函数的嵌套调用、函数的递归调用也是函数调用中常用到的方式,但递归调用的难度要大,所以掌握起来有不小的难度。

3、数组名称作为函数参数是学习的重点,因为它传递的是地址,故可以批量传递数据,且能把改变的数组元素值返回主调函数中。

4、变量的另外分类方式:按作用域是局部变量和全局变量 ;按存储位置是静态、动态和寄存器变量,如果对各变量本质认识了,这部分内容的难度不大。

第九章   结构体与共用体

[教学目的]掌握定义结构体类型变量的方法、结构体变量的引用和初始化、结构体数组的应用;掌握链表的基本操作;了解共用体的定义及应用、枚举类型、 用typedef定义类型。

[教学内容]  结构体类型变量的定义、引用 、初始化; 结构体数组的 定义、初始化、应用; 指向结构体类型数据的指针; 用指针处理链表; 共用体的概念、 变量的引用方式; 枚举类型; 用typedef定义类型

 重点难点:结构体变量的引用和初始化、结构体数组的应用;链表的基本操作:建立、插入、删除和输出;结构体数组的应用;链表的基本操作:建立、插入、删除和输出

[教学方法] 多媒体

一、概述

在前面的学习中我们知道C语言除了基本类型之外,还有两种类型,一是指针,二是构造类型。对于构造类型,我们并不陌生了,数组就是其中一种,数组是由同一种基本类型构造而成的。那么能不能把不同的基本类型合在一起构造一种新类型呢?

C语言允许将不同类型的数据组合成一个有机的整体,这个整体是一种数据结构,我们把它称为结构体(structure)。 这种数据结构相当于其他高级语言中的“记录”。 在C中,如何描述这种数据结构呢?

struct student     /*类型名*/

{ int num;

 char name[20];

      char sex;

      int age;

      float score;

      char addr[30]; } ;

声明一个结构体类型的一般形式为:

struct  结构体名  /* struct是结构体类型关键字*/

     {数据类型  成员1;

      数据类型  成员2;

        ……              ……

      数据类型  成员n;  };        /* 此行分号不能少!*/

说明:

(1)结构体名用作结构体类型的标志,又称“结构体标记” 结构体名可以省略。

(2)各成员都应进行类型声明,成员也可是构造型。

(3)成员表列也可称为“域表” 。

二、定义结构体类型变量的方法

1、先定义结构体类型,再定义结构体变量

一般形式: struct     结构体名

{  类型标识符    成员名;

           类型标识符    成员名;

           …………….};

struct  结构体名  变量名表列;

  

2、定义结构体类型的同时定义结构体变量

一般形式:

struct   结构体名

{ 类型标识符    成员名;

         类型标识符    成员名;

          …………….

}变量名表列;

3、直接定义结构体变量

一般形式:

struct

{类型标识符    成员名;

      类型标识符    成员名;

         …………….

}变量名表列;

说明:结构体类型与结构体变量概念不同

类型:不分配内存;              变量:分配内存

类型:不能赋值、存取、运算;     变量:可以

结构体可嵌套,结构体成员名与程序中变量名可相同,不会混淆

例 struct  date

      { int month;

        int day;

        int year;    };

   struct  student

      { int  num;

       char name[20];

       struct  date  birthday;

       }stu;

三、结构体变量的引用

1、引用规则

 结构体变量不能整体引用,只能引用变量成员

2、引用方式:   结构体变量名.成员名

可以将一个结构体变量赋值给另一个结构体变量

结构体嵌套时逐级引用

四、结构体变量的初始化

对结构体变量可以在定义时指定初始值

形式一:

struct      结构体名

{类型标识符    成员名;

    类型标识符    成员名;

         …………….};

struct  结构体名  结构体变量={初始数据};

   {int num;

    char  name[20];

    char sex;

    int age;

    char addr[30]; };

struct student stu1={112,“Wang Lin”,‘M’,19, “200 Beijing Road”};

形式二:

struct     结构体名

{ 类型标识符    成员名;

     类型标识符    成员名;

         …………….}结构体变量={初始数据};

形式三:

struct

{ 类型标识符    成员名;

      类型标识符    成员名;

         …………….

}结构体变量={初始数据};

五、结构体数组

1.定义结构体数组

形式一: 

struct  student

   {int  num;

    char name[20];

    char sex;

    int age; };

struct  student   stu[2];

形式二:

   struct  student

    {int  num;

     char name[20];

     char sex;

     int age; }stu[2];

形式三:

    struct

    {int  num;

     char name[20];

     char sex;

     int age;

      }stu[2];

2、结构体数组初始化:分行初始化和顺序初始化

 

 六、结构体数组引用

引用方式:结构体数组名[下标].成员名

struct  student

        {  

  int  num;

             char name[20];

             char sex;

             int age;

        }str[3];

例  分析以下代码及输出结果。

/* 例9-1,9-1.c */

#include

#include

struct  curriculum

{

   char curname[30] ;

   float curgrade;

};

struct student

{

   char name[8] ;

   char stuid[10] ;

   char department[30] ;

   char semester[10];

   struct curriculum course;

  } stu1 = { "lihong", "200133420", "computerdepartment", "200609", "Clanguage", 87 };

  /*  stu1为全局变量,并在定义stu1时初始化成员 */

void main(  )

   {

       struct student stu2;

      printf(“Enter stu2’s  information: \n”);

      gets(stu2.name);   /* 从键盘输入数据初始化stu2 */

  gets(stu2.stuid);

  gets(stu2.department);

  gets(stu2.semester);

  gets(stu2.course.curname);

  scanf("%f",&stu2.course.curgrade);

      printf(“the sut1’s  information is : \n”);

printf(" %s %s %s %s %s %f\n", stu1.name, stu1.stuid, stu1.department, stu1.semester, stu1.course.curname, stu1.course.curgrade);

      printf(“the sut2’s  information is : \n”);

printf(" %s %s %s %s %s %f\n", stu2.name, stu2.stuid, stu2.department, stu2.semester, stu2.course.curname, stu2.course.curgrade);

   }

运行输出:

liuliting

20060205

electronComputer

200703

English

76

 lihong 200133420 computerdepartment 200609 Clanguage 87.000000

 liuliting  20060205 electronComputer 200703 English 76.000000

七、 结构体和指针

1、指向结构体变量的指针

定义形式:struct  结构体名   *结构体指针名;

  1. (*结构体指针名).成员名 
  2. 结构体指针名->成员名 
  3. 结构体变量名.成员名

指向运算符  优先级: 1  结合方向:从左向右

2、指向结构体数组的指针

3、 用结构体变量和指向结构体的指针作函数参数:

(1)用结构体变量的成员作参数----值传递

(2)用指向结构体变量或数组的指针作参数----地址传递

(3)用结构体变量作参数----多值传递,效率低

分析以下代码及输出结果。

/* 例9-2,9-2.c */

#include

 #include

struct  curriculum

 {

   char curname[30] ;

    float curgrade;

 };

  struct  student

  {

     char name[15] ;

     char stuid[10] ;

     char department[30] ;

     char semester[10];

     struct curriculum course;

  } stu1 = { "zhangcheng", "200333067", "ComputerDepartment", "200409",  "JAVALanguage", 87 };

  void main( )

  {

  struct student sst ;

struct student *ptrst1 = &stu1, *ptrst2 = &sst ;

   

printf("Enter the pionter ptr's information : \n");

    gets(ptrst2->name);

gets(ptrst2->stuid);

gets(ptrst2->department);

gets(ptrst2->semester);

 gets((*ptrst2).course.curname);

scanf("%f", &(*ptrst2).course.curgrade);

printf("Thet stu1's information is : \n");

    printf(" %s %s %s %s %s %f\n", stu1.name, ptrst1->stuid, (*ptrst1).department, (*ptrst1).semester, (*ptrst1).course.curname, (*ptrst1).course.curgrade);

 printf("Thet ptr's information is : \n"); 

puts(ptrst2->name);

puts(ptrst2->stuid);

puts(ptrst2->department);

puts(ptrst2->semester);

puts(ptrst2->course.curname);

printf("%f", ptrst2->course.curgrade);

 }

运行输出:

Enter the pionter ptr's information :

zenqiong

200302043

EnglishDepartment

200503

English

68

Thet stu1's information is :

 zhangcheng 200333067 ComputerDepartment 200409 JAVALanguage 87.000000

Thet ptr's information is :

zenqiong

200302043

EnglishDepartment

200503

English

68.000000

结构体变量的指针指向的是这个结构体变量所占内存单元的首地址。某结构体变量的指针只能指向该类结构体变量,而不能指向它的成员。

  

八、枚举类型

  定义枚举类型用enum开头,其一般形式为:

     enum 枚举名

        {枚举表};

  例如,把每周中表示某天的枚举类型定义如下:

    enum weekday

     { sun,mon,tue,wed,thu,fri,sat

      } day1,day2;

     day1=sun;day2=fri;

说明:

1、枚举元素按常量处理, 不能对枚举常量赋值。

2、枚举元素是有值的,编译系统按定义时的顺序使它们的值分别为0,1,2,3,4,...…

 3、可以改变枚举元素的值。

 4、一个整数不能直接赋给一个枚举变量。

5、枚举常量不是字符串,不能用下面方法输出字符串“fri”:

   printf("%s",fri);   错误

   printf("%d",fri);  正确

 6、 枚举变量可以用作循环变量,并且可以对枚举循环变量进行自增和自减运算

九、自定义类型

C语言提供了用typedef定义新的类型名代替已有的类型名。

    例:    typedef   int   INTEGER;

           typedef   float   REAL;

    则以下对变量的定义等价:

              int  i,j;   float  a,b;

              INTEGER  i,j; REAL  a,b;

    定义一个新的类型名的方法:

    (1)先按定义变量的方法写出定义体(如int i;).

    (2)将变量名换成新类型名(将i换成INTEGER).

    (3)在最前面加typedef.

    (4)然后可以用新类型名去定义变量.   

说明:

(1)用typedef可以定义各种类型名,  但不能用来定义变量;

    (2)用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型;

    (3)typedef与#define有相似之处,  但并不相同;

    (4)常用typedef定义一些数据类型,  单独放在一个文件中,用#include命令把它们包含进来;

    (5)使用typedef有利于程序的通用与移植。

十、共用体 (选讲)

1、共用体的定义

     结构体是把不同类型的变量存放到不同的内存单元中,为节省空间,有时候需要把几种不同类型的变量存放到同一段内存单元中,这就要借助于另一种构造类型—共用体。

在 C语言中,共用体表示几个变量共用一个内存单元,这些变量可以是不同的类型。共用体的定义与结构体的定义十分相似,共用体类型的定义形式为:

union  共用体名

    {成员表列};

union    type  

  {  int  i;

     char  ch;

      float  f;

   } a;        

共用体变量的定义:

① 类型与变量同时给出

② 类型与变量分别给出

③ 只给出变量名

结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有自己的内存单元。共用体变量所占的内存长度等于占用内存单元最长的成员的长度。

2、共用体变量的引用

只有先定义了共用体变量才能引用它,而且不能直接引用共用体变量,只能引用共用体变量中的成员。例如上面定义a,b,c共用体变量,则可引用:

                a.i           a.ch              a.f

3、共用体数据类型的特点

(1)每一瞬时只有一个成员起作用;

(2)共用体变量中起作用的成员是最后一次存放的成员;

(3)共用体变量的地址和它的各成员的地址都是同一地址;

(4)不能对共用体变量名赋值,也不能企图引用变量名来得到成员的值,又不能在定义共用体变量时对它初始化;

   (5)不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针;

   (6)共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。

4、共用体变量的应用

   (1)在数据处理中,常用一个数据空间存放不同的对象。

(2)便于不同类型间的转换

5、共用体变量作为函数参数

十一、用指针处理链表 (选讲)

1、链表概述

    链表是一种常见重要的数据结构,它是动态的进行存储分配的。链表中的元素称为结点,每个结点包括两部分:一部分为用户实际需要的数据、另一部分为下一个结点的地址。

2、静态链表

例 建立一个静态链表,它由三个学生数据的结点组成,输出各结点的数据

3、 处理链表的有关函数

   (1) malloc(size)    在内存的动态存储区中分配一个长度为size的连续空间。此函数的值是一个指向分配域起始地址的指针。如果此函数未能成功的执行,则返回值为0。

   (2) calloc(n,size)   在内存的动态存储区中分配n个长度为size的连续空间。此函数的值是一个指向分配域起始地址的指针。如果此函数未能成功的执行,则返回值为0。

   (3)free(ptr)  释放由ptr指向的内存区。

注:(1)  n和size为整型常量、变量或表达式。

    (2)  ptr为指针类型。

    (3)  用malloc函数开辟一个结点的存储空间。

    (4)  用calloc函数开辟n个结点的存储空间。

4、建立链表

建立链表的过程就是把一个个结点插入链表的过程。其操作的主要部分为,申请一个存储空间并返回该空间的首地址,将该地址接到原链表的尾部,该过程重复进行,直到输入学号为0时为止。

5、 打印链表

6、  删除链表

删除的情况有以下几种:

(1)空链表

(2)非空链表:

① 没有发现要删除的对象

            ② 删除的结点是头结点

            ③ 删除的结点是中间结点

            ④ 删除的结点是尾结点

7、 插入链表

插入的情况有以下几种:

(1)空链表

(2)非空链表:

① 头结点的前面

            ② 中间的位置

            ③ 末结点的后面       

十二、 本章小结

本章主要涉及到的是构造类型:结构体类型和共用体类型。包括有变量的定义、引用 、初始化,结构体数组的定义、初始化、应用,指向结构体类型数据的指针。另一个重点的内容是用指针处理链表,它包含几个基本操作:建立、插入、删除和输出。

第七章  文 件

[教学目的]掌握文件的打开与关闭,灵活使用文件的读写函数。

[教学内容] C文件概述、文件类型指针、文件的打开与关闭、文件的读写函数fputc、fgets、fread、fwrite、fprintf和fscanf函数。

 重点难点:文件的打开与关闭,文件的读写函数; 文件的读写,特别是fread、fwrite函数的应用。

[教学方法] 多媒体

一、文件的概念

1、C语言文件概述

文件是程序设计中的一个重要概念。此处所讲的文件,是指C语言数据文件。所以可以这样来定义文件:存储在外部介质上数据的集合。在一般的高级语言中,按文件的存放方式将文件分成ASCII码文件和二进制文件:ASCII码文件又称文本文件,正文文件。它的每一位字节存放一个ASCII码,而在二进制文件中,是把内存中的数据按其在内存中的存储形式直接存储到外部存储介质上。

2、标准级(流式)输入输出

在C语言中,处理文件有两种方式:一种称为缓冲文件系统,一种称为非缓冲文件系统。对于缓冲文件系统,系统能自动在内存中为文件打开一输入输出缓冲区。非缓冲文件系统则由程序员为每个文件设定缓冲区。

3、文件缓冲区

由于读写外部存储介质的速度相对于内存慢很多,为了提高读写效率,系统在打开一文件的同时,在内存中分配了一块区域与该文件相联系,这块区域就叫文件缓冲区。当系统向外存写数据时,并不立即将数据写入外存,而是将数据写入文件缓冲区,当文件缓冲区写满后,再一次向外存写数据。

二、文件类型指针

在C语言中,每个被使用的文件都在内存中开辟一个区域用来保存文件的有关信息。这个信息保存在一上结构变量中。该结构名为 FILE(不能小写)。在一个程序中,可能有许多不同的文件,系统用FILE结构来管理这些文件,而从程序员的角度来看待文件,就是指向FILE结构的指针,此指针即为文件指针。

定义文件指针:    FILE  *fp;

三、文件的打开与关闭

1、文件的打开:就是在内存中定义一个FILE数据结构,然后将指定的磁盘文件与该结构相联系,然后系统通过FILE结构操作文件,程序员通过指向FILE结构的指针来使用文件。

(1)语法:FILE   *fp;

fopen(文件名,文件的使用方式);

说明:文件名可包含路径,但需用双反斜线(\\),也就是转义序列。

(2)文件的使用格式

r       文件以只读方式打开文本文件,以这种方式打开的文件不能写    

w       创建用于写的文本文件,如果原文件存在,则清空

a       向文本文件尾部追加,不存在时可创建

r+      打开存在的文本文件,用于更新(读或写)

w+      创建一新文本文件用于读写

a+     为拼接而打开,在文件属拼接

rb      为输入打开一个二进制文件

wb      为输出打开一个二进制文件

返回值:成功时返回指向文件FILE结构变量的指针,失败时返回NULL。

2、文件的关闭

语法:fclose(文件指针);

说明:关闭文件指针所指的文件,将保存在文件缓冲区中的内容存盘,释放文件结构变量。

四、文件的读写

1、字节级

(1) fputc()

用途:输出一个字符到文件中(写操作)

语法:fputc(int c,FILE *fp);

返回值:成功时返回字符c,出错时返回EOF。

头文件:stdio.h

(2)fgetc() 函数

用途:从文件中读取一个字符

语法: fgetc(FILE *fp);

返回值:成功时,返回读取的字符,在文件结束或出错时,返回EOF。

例:从文件中读取字符,并通过显示器显示出来,然后将其关闭。

2、记录级

(1)fread()函数

用途:从一个文件中读数据(一般用于二进制文件)。

语法: fread (buffer,size,count,fp);

说明:从fp指向的文件中读取count个size字节数据项到buffer所指的块中,每一项数据的长度为size

返回值:成功时返回值为count的值,出错时返回一个出错的短整型值,可能为零。

(2)fwrite()函数

用途:向一个文件中写数据(一般用于二进制文件)。

语法:fread (buffer,size,count,fp);

说明:向fp指向的文件中写由buffer所指块中的n项数据,每一项数据的长度为size。

返回值:成功时返回值为count的值,出错时返回一个出错的短整型值,可能为零。

3、字符串及主要函数:fprintf(FILE *fp, char *fmt,out_list);

   fscanf(FILE *fp,char *fmt,input_list);

fputs(char *string,FILE *fp);

   fgets(char *string,int n,FILE *fp);

例  从键盘上输入若干个字符,逐个将其存入文件“c:\\myfile-1.txt”中,直到遇到输入的字符是‘#’号为止。

【简要分析】 C盘上的文件myfile-1.txt以前是不存在的,它是运行程序时新创建的,故文件使用方式应选择写方式“w”。根据文件操作的一般步骤,用N-S流程图描述的程序逻辑如图所示。

开始,设置环境

定义变量ch,filename

输入文件名filename

打开指定文件

当ch!=’#’时

输入一个字符到ch中

将ch写入文件中

关闭文件,结束

流程图

参考源代码:

/*例7-6,7-6.c*/

#include

void main()

{

    FILE *fp;

    char ch;

    fp = fopen(“c:\\myfile-1.txt”, "w");    /*打开文件*/

    ch = getchar();                   /*输入一个字符*/

    while ( ch != '#' )

    {

        fputc(ch, fp);                /*写一个字符到文件*/

        putchar(ch);                  /*将字符输出到屏幕上*/

        ch = getchar();                   /*输入一个字符*/

    }

    fclose(fp);                       /*关闭文件*/

}

【融会贯通】 从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,输出到磁盘文件“file.txt”中保存,输入的字符串以‘!’结束,然后再将文件“file.txt”输出到显示器上。

例 从键盘输入若干学生的姓名、学号、年龄和地址,把它们存到磁盘文件“c:\student.txt”中。

/*例7-8,7-8.c*/

#include

#define size 4

struct student /*定义结构*/

{

    char name[20];

    int num ;

    int age ;

    char addr[20];

} stud[size];      /*定义结构数组*/

void save()     /*写文件函数*/

{

    FILE *fp ;

    int i ;

    if ( ( fp = fopen("c:\\student.txt", "wb") ) == NULL ) /*打开文件*/

    {

        printf("\n不能打开文件错误 !");

        exit(0);

    }

    for ( i = 0; i < size; i++ )  

       fwrite(&stud[i], sizeof(struct student), 1, fp) ;  /*写文件*/

    fclose(fp);

}

void main()

{

    int i ;

    for ( i = 0; i < size; i++ )       /*输入信息到结构数组*/

       scanf("%s%d%d%s", stud[i].name, &stud[i].num, &stud[i].age, stud[i].addr);

    save();

}

五、本章小结

通过学习对C文件有了基本认识,能熟练进行文件类型指针的定义、文件的打开与关闭。但在对文件的读写上,还不能非常熟练,特别是字节级的读写函数的应用,存在一些问题,应该多上机实践。在本章中主要掌握读写函数的用法,这些函数有:fputc()、fgets()、fread()、fwrite()、fprintf()和fscanf()函数。

第十一章 预处理命令(选讲)

[教学目的]了解并掌握宏定义的应用,include命令的用法 。

[教学内容]  不带参数的宏定义 、 带参数的宏定义 、“文件包含”处理 。

重点:带参宏定义的应用与include命令。

难点:带参宏定义的应用。

[教学方法] 多媒体

一 、新课引入

在C语言中我们提到的是语句,它是C的基本单位。除此之外,ANSI C标准规定可以在C源程序中加入一些“预处理命令”,以提高编程效率。这些预处理命令不是C语言本身的组成部分,不能直接对它们进行编译。所谓编译预处理是指,在对源程序进行编译之前,先对源程序中的编译预处理命令进行处理;然后再将处理的结果和源程序一起进行编译,以得到可供执行的目标代码。C语言与其他高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能,它的预处理功能主要有以下三种:宏定义、文件包含和条件编译。为了与一般C语句相区别,这些预处理命令以符号“#”开头。

二、 新课讲授

(一)宏定义

1、不带参数的宏定义

(1)不带参数的宏定义的一般格式

     #define   标识符   字符串

其中:“define”为宏定义命令;“标识符”为所定义的宏名,通常用大写字母表示,以便于与普通变量区别;“字符串”可以是常数、表达式、格式串等。

        例如: # define PI  3.1415926

宏展开:在预编译时将宏名替换成字符串的过程称为宏展开。

例81 #define  PI  3.1415926

  main()

{ float l,s,r,v;

   printf(“input radius: ”);

   scanf(“%f”,&r);

   l=2.0*PI*r;

   s=PI*r*r;

   v=3.0/4.0*PI*r*r*r; printf(“l=%10.4f\ns=%10.4f\nv=%10.4f\n”,l,s,v);}

经过编译预处理,宏展开后,该程序的6,7,8行展开为:

   l=2.0*3.1415926*r;

   s=3.1415926*r*r;

   v=3.0/4.0*3.1415926*r*r*r;

说明:(1)宏名一般习惯用大写字母表示,以便与变量名相区别。

(2) 使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量,提高程序的通用性。

(3) 宏定义是用宏名代替一个字符串,也就是作简单置换,不作正确性检查。

(4) 宏定义不是C语句,不必在行末加分号。

(5) # define 命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。

(6)可以用# undef命令终止宏定义的作用域, 例如:

   # define G  8.8

   main()

   {… }/* G的有效范围 */

   # undef G

   f1( )

(7) 在进行宏定义时,可以引用已定义的宏名,可以层层置换。

例 8.2

# define R 3.0

# define PI 3.1415926

# define L 2*PI*R

# define S PI*R*R

   main()

{ printf(“L=%f\nS=%f\n”,L,S);}

宏展开后,printf函数调用语句展开为:

   printf(“L=%f\nS=%f\n”,2*3.1415926*3.0,3.1415926*3.0*3.0);

(8)程序中用双引号括起来的字符串内的字符,即使与宏名相同也不进行置换。

(9) 宏定义与定义变量的含义不同,不分配内存空间。

2、带参数的宏定义

(1)带参宏定义的一般格式

   # define   宏名(形参表) 字符串

(2)带参宏的调用和宏展开

调用格式:宏名(实参表);

宏展开:用宏调用提供的实参字符串,直接置换宏定义命令行中、相应形参字符串,非形参字符保持不变。

见例8.3

# define PI 3。1415926     /*无参宏,符号常量*/

# define S(r) PI*r*r           /* 有参宏*/

   main()

{ float a,area;

    a=3.6;

 area=S(a);

    printf(“r=%f\narea=%f\n”,a,area);}

经宏展开后,程序第6行展开为:

   area=3.1415926*a*a;

说明:(1)定义有参宏时,宏名与左圆括号之间不能留有空格。否则,C编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。

(2)有参宏的展开,只是将实参作为字符串简单地置换形参字符串,而不做任何语法检查。在定义有参宏时,在所有形参外和整个字符串外,均加一对圆括号。

(3)虽然有参宏与有参函数确实有相似之处,但不同之处更多,主要有以下几个方面:

1)调用有参函数时,是先求出实参的值,然后再复制一份给形参。而展开有参宏时,只是将实参简单地置换形参。

2)函数调用是在程序运行时处理的,分配临时的内存单元,而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有返回值的概念。

3)使用有参函数,无论调用多少次,都不会使目标程序变长,但每次调用都要占用系统时间进行调用现场保护和现场恢复;而使用有参宏,由于宏展开是在编译时进行的,所以不占运行时间,但是每引用1次都会使目标程序增大1次。

   看书中例子:例8.5

(二)“文件包含”处理

1.文件包含的概念

文件包含是指,一个源文件可以将另外一个源文件的全部内容包含进来。

2.文件包含命令的格式

#include  “包含文件名”  或 #include  <包含文件名>

两种格式的区别仅在于:

(1)使用双引号:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找。

(2)使用尖括号:直接到系统指定的“包含文件目录”去查找(即存放C库函数头文件所在的目录),这称为标准方式。

一般说,如果为调用库函数而用# include命令包含相关的头文件,则用尖括号,以节省查找时间。 如果要包含的是用户自己编写的文件,一般用双引号

3.文件包含的优点:一个大程序,通常分为多个模块,并由多个程序员分别编程。有了文件包含处理功能,就可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。这样,凡是要使用其中数据或调用其中函数的程序员,只要使用文件包含处理功能,将所需文件包含进来即可,不必再重复定义它们,从而减少重复劳动。

注意:(1)在编译时,不是作为两个文件进行连接,而是作为一个源程序编译,得到一个目标文件。

(2)这种常用在文件头部的被包含的文件被称为“标题文件”或“头部文件”,常以“h”为后缀。当然不用“.h”为后缀,而用“.c”为后缀或者没有后缀也是可以的。

(3)一个include命令只能指定一个被包含文件,如果要包含n个文件,要用n个include命令.

(4)文件包含可以嵌套。

(5)被包含文件中的全局静态变量,在包含它的文件中也有效,不必用extern 声明。

三、课堂小结

 1、掌握宏定义的方式,能正确进行有参宏定义的展开。

2、理解文件包含的含义,并能正确运用。

四  课后作业

1、复习本章内容。

2、课后习题1、2。

3、预习下一章的内容。

五  实践题(不要求在机器上调试,但要求学生能熟练展开和书写)

1、课本例题。

2、课件例题。

六  本章后记

本章主要涉及两个内容:宏定义和文件包含,其中宏定义又分为有参的和无参的,有参宏定义是个难点,通过讲解例题把宏定义展开的原则掌握下来,即字符串的替换。文件包含在项目开发中也是一个常用内容,它可以实现一些固定内容的共享,减少重复劳动,因此要掌握它的用法。

你可能感兴趣的:(C语言,c语言,开发语言)