C语言程序设计课件

文档属性

名称 C语言程序设计课件
格式 rar
文件大小 1.1MB
资源类型 教案
版本资源 通用版
科目 信息技术(信息科技)
更新时间 2010-05-08 17:18:00

文档简介

(共33张PPT)
本章重点
14.1 文件的基本概念
14.2 打开与关闭文件
14.3 文件的定位
14.4 对文件的操作
第14章 文件
本章重点
14.1 文件的基本概念
14.2 打开与关闭文件
14.3 文件的定位
14.4 对文件的操作
第14章 文件
14.1 C语言文件的概念
   文件是一种保存数据的基本数据结构,在逻辑上可认为文件是记录在外部介质上的数据的集合。
   对文件输入输出方式也称“存取方式”。C语言中,有两种对文件的存取方式:顺序存取和直接存取。
   顺序存取文件的特点是:每当“打开”这类文件进行读或写操作时,总是从文件的开头开始,从头到尾顺序地读写;
   直接存取文件又称随即存取文件,其特点是:可以通过调用C语言的库函数去指定开始读写的字节号,然后直接对此位置上的数据进行读写操作。
本章重点
14.1 文件的基本概念
14.2 打开与关闭文件
14.3 文件的定位
14.4 对文件的操作
第14章 文件
14.2.1 文件指针
   文件指针,实际上是指向一个结构体类型的指针变量,这个结构体中包含有:缓冲区的地址、在缓冲区中当前存取的字符的位置、对文件是“读”还是“写”、是否出错、是否已经遇到文件结束标志等信息。所有一切都在stdio.h头文件中进行了定义,并称此结构体类型名为FILE,可以用此类型名来定义文件指针。
  定义文件指针的一般形式为:
  FILE *指针变量名
   在标准头文件stdio.h中,同时预定义了三个非常有用的文件类型指针stdin、stdout、stderr。这三个标准文件类型指针通常都连到终端设备。
 stdin 标准输入流指针,通常指键盘。
 stdout 标准输出流指针,通常指显示器屏幕。
 stderr标准出错流指针,通常指显示器屏幕。
为了对文件进行使用操作,需要把FILE指针作为一个参数传递给每个标准I/O函数。
14.2.2 打开文件
1.打开文件(fopen函数)
打开一个文件调用标准I/O库函数fopen, 它使打开文件与一个流相联,返回一个指向该流的文件类型指针,用于后续的文件操作。用法:
FILE *fopen(const char *filename, const char *mode);
(1)如打开成功,则fopen的返回一个文件类型指针,否则返回NULL。
(2)参数filename指定打开的文件名。
(3)参数mode指定文件打开方式,具体说明如下:
①对文本文件的打开方式:
r 以只读方式打开。
w 以写方式打开,如果已存在该文件名的文件,文件被重写。
a 附加方式。打开用于在文件末尾写,当文件不存在时,创建新文件用于写。
r+ 打开一个已存在文件用于更新。
w+ 创建一个新文件用于更新,如果已存在该文件名的文件,文件被重写。
a+ 打开用于附加,当文件不存在时,创建新文件用于在文件末尾写。
②对二进制文件的打开方式:
rb 以只读方式打开。
wb 以写方式打开,如果已存在该文件名的文件,文件被重写。
ab 附加方式。打开用于在文件末尾写,当文件不存在时,创建新文件用于写。
rb+ 打开一个已存在文件用于更新。
wb+ 创建一个新文件用于更新,如果已存在该文件名的文件,文件被重写。
ab+ 打开用于附加,当文件不存在时,创建新文件用于在文件末尾写。
例如:打开一个文件
#include
main()
{FILE *fp;
if((fp=fopen("jiaotong", "r"))==NULL)

{ printf("cann't open the file:jiaotong");
exit(0); }
else
{printf("open the file:jiaotong");
fclose(fp);
}
}
  该程序的作用是打开当前目录下的一个名叫:jiaotong的文件,如过存在该文件并被打开,则在屏幕上输出一行信息:open the file:jiaotong.;否则输出:cann’t open the file:jiaotong.。虽然此处用只读方式(r)去打开文件,但并未做读取其中内容。
14.2.3 关闭文件
  对文件进行读写操作完成之后,应及时关闭该文件。关闭一个文件调用标准I/O库函数fclose, 它清除与文件相关的流缓冲区。此后将不能再通过该指针,对原来与其联系在一起的文件进行操作;除非再次打开原文件,并让该指针重新指向它。函数fclose的用法:
  int fclose(FILE *stream);
  参数stream为文件流指针。如调用成功,则fclose的返回0,否则返回EOF(-1)。
 例如:关闭一个文件
#include
main()
{FILE *fp;
if((fp=fopen("abc", "r"))==NULL)
{exit(0);
}
if(0==fclose(fp))
{ printf("close the file:jiaotong");}
else
{ printf("cann't close the file:jiaotong"); }
}
  运行该程该程序前,确认在当前目录下有一个名叫jiaotong的文件,程序先打开该文件,并立即关闭它,如果文件关闭成功,在屏幕上输出一行信息:close the file:jiaotong.;否则输出:cann’t close the file:jiaotong。
本章重点
14.1 文件的基本概念
14.2 打开与关闭文件
14.3 文件的定位
14.4 对文件的操作
第14章 文件
14.5 调用getc(fgetc)和putc(fputc)函数进行输入和输出
1.输入字符函数fgetc
fgetc函数的功能是从指定的文件中读一个字符,函数调用的形式为:
字符变量=fgetc(文件指针);
例如:
ch=fgetc(fp);
其意义是从打开的文件fp中读取一个字符并送入ch中。
例如:用fgetc从标准输入终端设备读取一个字符。
#include
main()
{
char ch;
printf("Enter a character followed by :");
ch = fgetc(stdin);
printf("The character read is '%c'\n", ch);
}
 程序的运行情况如下:
 Enter a character followed by :b<回车>   (输入字符“a”)
 The character read is 'b' (显示输入字符“a”)
 说明:
  该程序运行时,提示用户输入一个字符,用户从键盘上输入一个字符并回车,函数fgetc将从标准输入设备stdin读取一个字符。
2.输出字符函数fputc
fputc函数的功能是把一个字符写入指定的文件中,函数调用的 形式为:
fputc(字符量,文件指针);
其中,待写入的字符量可以是字符常量或变量,例如:   fputc('a',fp);
其意义是把字符‘a’写入fp所指向的文件中。
例如:用fputc向标准输出终端设备输出一个字符串。
#include
#include
main()
{
int i;
char ch[]="Hi everybody!";
for(i=0; i{
fputc(ch[i], stdout);
}}
 说明:
 该程序运行时,向标准终端输出设备stdout(显示屏)输出字符串:“Hi everybody!”。
#include
#include
main()
{int i;
char ch[]=“Hello everybody!";
for(i=0; i{ fputc(ch[i], stdout);
}}
 说明:
 该程序运行时,向标准终端输出设备stdout(显示屏)输出字符串:“Hello everybody!”。
14.6 判文件结束函数EOF
读取文件时,当文件中的数据全部读完后,文件位置指针将位于文件的结尾。此时如果读数据,将会出现错误。为了保证读写数据的正确性,需要进行文件尾测试,文件尾测试使用函数feof(),其格式如下:
格式:int feof(FILE *fp)
功能:测试fp指向的文件是否到达文件尾。若到达文件尾,返回值为非0,否则返回值为0。
14.7 fscanf函数和fprintf函数
1.格式读函数fscanf()
 格式:int fscanf (FILE *fp,const char *format [, address, …])
 功能:根据format中的格式从fp指向的文件中读取数据存入到相应的address指向的变量中。2.格式写函数fprintf()
 格式:int fprintf (FILE *fp,const char *frmat [, argument, …])
 功能:根据格式字符串frmat把argument列表中的表达式值写到fp所指向的文件中。
14.8 fgets函数和fputs函数
1.读字符串函数fgets
  函数的功能是从指定的文件中读一个字符串到字符数组中,函数调用的形式为:
fgets(字符数组名,n,文件指针);
  其中的n是一个正整数。表示从文件中读出的字符串不超过 n 1个字符。在读入的最后一个字符后加上串结束标志‘\0’。
  例如:
fgets(str,n,fp);
  其意义是从fp所指的文件中读出n 1个字符送入字符数组str中。
2.写字符串函数fputs
  fputs函数的功能是向指定的文件写入一个字符串,其调用形式为:
fputs(字符串,文件指针);
  其中字符串可以是字符串常量,也可以是字符数组名或指针变量,例如:
fputs("abcd",fp);
  其意义是把字符串“abcd”写入fp所指的文件之中。
14.9 fread函数和fwrite函数
  直接I/O又叫二进制I/O,用于整块数据的读写。可用来读写一组数据,如一个数组元素,一个结构变量的值等。
1.fread函数
 fread函数是从输入流中读取一个数据块。
 调用形式:
  int fread (void* ptr, int size, int n, FILE  *stream);
2.fwrite函数
fwrite函数用来输出一个数据块到指定流中。
 用法:
 int fwrite(const void* ptr, int size, int n, FILE *stream);
  fwrite将n项长度为size字节数据复制到流中,终结空字符不拷贝。如果调用成功,则fputs返回0;出错时,则返回EOF。参数stream为文件流指针。
14.10 文件定位函数
   实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
   移动文件内部位置指针的函数主要有两个,即 rewind 函数和fseek函数。另外函数ftell用来得到文件指针的当前位置,用相对于文件头的字节位移量表示。
14.10.1 freek函数
格式:int fseek (FILE *fp, long offset, int from)
功能:移动文件位置指针到指定位置。
说明:
(1)fseek()把文件位置指针移动到与from所指定的文件位置距离offset个字节处,如果指针移动成功,则返回0,出错时返回非0。
(2)参数offset为字节偏移量,为长整型数据,正数代表前进,负数代表后退。
(3)参数form代表移动的开始位置。
14.10.2 ftell函数
 格式:long ftell (FILE *fp)。
 功能:得到fp指向的文件的文件位置指针位置。
 说明:ftell()在调用成功后返回当前指针位置,出错时返回 1L。
  当打开一个文件时,通常并不知道该文件的长度,通过以下函数调用可以求出文件的字节数:
fseek(fp, 0L, SEEK_END);
  t=ftell(fp); 
  若二进制文件中存放的是struct st结构体类型数据,则通过以下语句可以求出该文件中以该结构体为单位的数据块的个数。
  fseek(fp, 0L, SEEK_END);
  t=ftell(fp);
  n=t/sizeof(struct st);
14.10.3 rewind函数
 格式:void rewind (FILE *fp)
 功能:将文件位置指针定位于文件的开始头。
 说明:该函数的作用是把文件位置指针返回到文件开始处,清除文件结束标志和出错标志,无返回值。(共36张PPT)
C语言程序设计
李琨

第2章 顺序结构
本章要点:
第一节 语句
第二节 数据输出
第三节 数据输入
第一节 语句
一、表达式语句
在赋值表达式的尾部加上一个“;”号,就构成了表达式语句,也称为赋值语句。以下是几点说明:
1.赋值语句必须在最后出现分号,分号是语句中必不可少的部分,如“x+=10”是表达式,“x+=10;”是赋值语句。
2.任何赋值表达式都可以加上分号而称为赋值语句。
3.赋值语句是一种可执行语句,应当出现在函数的可执行部分。
※重点提示:分号是赋值语句中必不可少的部分,若没有分号,则只是一个赋值表达式而已。
二、空语句
只有一个分号构成的语句称为“空语句”。
如{;}就是一个空语句。
空语句在执行时不产生任何动作,但并不表示空语句没有用途。一般空语句可以用在循环体内对程序起“延时”作用。如:
{for(i=1;i<=100;i++) ; }
※重点提示:复合语句中最后一个语句的最后一个分号不能忽略。
三、复合语句
  在C语言中,可以用一对大括号“{}”把一些语句括起来构成复合语句,又称分程序。复合语句的形式如下:
  { 语句1;语句2;…;语句n;}
1.在复合语句内,不仅可以有执行语句,还可以有定义部分,定义部分应出现在可执行语句的前面。
2.复合语句中最后一个语句的最后一个分号不能忽略。
3.复合语句可以嵌套。即复合语句内部还可以包含其他复合语句。
四、顺序结构
一个程序中的语句将按照它们在程序中出现的顺序逐条执行,由这样的语句构成的程序结构称为顺序结构。
第二节 数据输出
一、 printf函数的一般调用形式
 printf函数是标准输出函数,功能是在终端设备上按指定格式进行输出。
  printf()函数的一般形式为:
  printf(“格式控制”,输出项序列)
 例如:printf(”a=%d,b=%c”,a,b)中,”a=%d,b=%c”称为格式控制字符串,a,b是输出项序列中的输出项,都是printf函数的参数
说明:
(1)输出控制是由双引号括起来的格式转换控制信息。
分为两种信息:1.用于说明的信息,计算机会原样输出;2.带有%的格式转换说明,用于指定输出数据的格式。
例如,printf(“a=%d,b=%d”,2,3);
(2) 输出数据列表包括需要输出的一些数据。
注意输出数据格式应该一致。
例如,printf(“%d,%f”,3.89,6);
(3)在输出控制中,格式说明的个数应该与输出项的个数相同。
注意:如果格式说明的个数少于输出项的个数,多余的输出项不予输出;
如果格式说明的个数多于输出项的个数,则对多余的格式说明将输出不定值。
例如,printf(“%d,%d”,2,3,4);
二、 printf函数中常用的格式说明
 “格式控制”部分是用双引号括起来的字符串,也称“转换控制字符串”,它包括三种信息:格式说明符、转义字符和普通字符。
1.格式字符
  格式说明符由%和格式字符组成,如%d、%f等。作用是转换输出数据的格式。对于不同类型的数据用不同的格式字符。下面详细介绍几种常用的格式字符。
① d格式字符。用来输出十进制整数。其用法如下:
(a)%d,按型数据的实际长度输出。
 例如:
 printf("%d,\n",x);
 如果x=789,则输出结果为:
 789
(b)%1d,输出长整型数据。例如:
  printf("%1d,\n",a);
 如果a被定义为:
  long int a=56789;
 则输出结果为:
  56789
  如果用%d格式输出,就会出错,因为整型数据的范围是-32768~32767。对超出此范围的long型数据应当用%1d格式输出。
(c)%-md,m是指定的输出字符宽度。如果数据的位数小于m,则输出共占m位,数据左靠齐,右边补空格,若省略“-”号,则右靠齐,左边补空格;如果数据的位数大于m,则按实际位数输出。例如:
  printf("%5d,%5d\n",a,b);
  如果a=7788,b=56789,则输出结果为: 7788,56789
② f格式字符。用来输出实数(包括单精度、双精度),以小数形式输出。其用法如下:
(a)%f,实数的整数部分全部输出,小数部分保留六位。需要指出的是,并非全部数字都是有效数字。单精度实数的有效位数一般是7位,双精度实数的有效位数一般是16位。
(b)%mf,输出的实数共占m位,小数部分保留六位。
(c)%.nf,输出的实数,总宽度按实际宽度,小数部分占n位。
(d)%-m.nf,输出的实数包括小数点在内共占m位,其中小数部分占n位。如果数据的实际宽度小于m,则左靠齐,右边补空格,省略“-”时,右靠齐,左边补空格。
 如:main()
{ float a=123.45;
printf(“%f,%11f,%.2f,%-10.3f\n”,a,a,a,a); }
 程序运行结果为:
 123.449997, 123.449997,123.45,123.450
  说明:a的值应该是123.45,但输出的结果却是123.449997,这是由于实数在内存中的存储误差引起的。
③ c格式字符。用来输出一个字符。例如:
char ch= 'a';
printf("%c",ch);
则输出一个字符‘a’。也可以指定输出字符的宽度,如:
printf("%2c",ch);
则输出‘a’,即ch变量输出占2列,第一列补空格。
④ s格式字符。用来输出一个字符串。其用法如下:
(a)%s,按原样输出一个字符串。例如:
printf("%s", "Happy New Year!");
则输出字符串:
Happy New Year!
(b)%-ms,与整数输出格式“%-md”类似。
(c)%m.ns,输出占m列,但只取字符串中左边n个字符。这n个字符输出在m列的右边,左边补空格。
(d)%-m.ns,n个字符输出在m列范围的左边,右边补空格。若n>m,则m自动取n值,即保证n个字符正常输出。
如:字符串输出示例
main()
{ 
printf(“%3s,%6.3s,%.2s,%-5.4s\n”,“Hello”,“Hello”,“Hello”,“Hello”); }
程序运行结果为:
Hello, Hel,He,Hell
表2.1 输出格式字符及其功能说明
格式字符 说 明
c 输出一个字符
d或i 输出带符号的十进制整数
o 以八进制无符号形式输出整形数(不带前导0)
x或X 以十六进制无符号形式输出整形数(不带前导),x输出小写字母,X输出大写字母
u 按无符号的十进制形式输出整型数
f 以带小数点的形式输出单精度和双精度数
E或e 以[-]m.dde(或E)±xx的指数形式输出浮点数。d的个数由精度指定,精度为0时小数部分不输出
G或g 由系统决定采用%f格式还是采用%e格式,以使输出宽度最小
s 输出字符串中的字符,直到遇到“\0”。或者输出由精度指定的字符数
p 输出变量的内存地址
% 打印一个%
2.附加格式字符
在%和上述格式字符之间可以插入以下几种附加符号,如下所示。
表3.2 输出常用附加格式字符
附加字符 功能说明
m(m为一正整数) 规定输出数据的最小宽度为m位
.n(n为一正整数) 对实数,表示输出的实数保留n位小数,对字符串,表示截取字符串的前n个字符
- 使输出的数据在域内向左靠齐,省略“-”时,向右靠齐
3.长度修饰符
  长度修饰符加在%和格式字符之间,对于长整型一定要加l(long),h可用于短整型(short)或无符号短整形数的输出。如printf(“%ld,\n”,a);
4.转义字符
  可以在printf函数中的“格式控制”部分使用“转义字符”,如“\n”、“\b”、“\f”、“\t”、“\r”、“\344”等。
※重点提示:如果想输出字符“%”,则应该在“格式控制”字符串中用连续两个%表示。
三、 调用printf函数时的注意事项
1.在格式控制字符串中,格式说明与输出项从左到右在类型上必须一一对应匹配。
 例如:
  printf("x=%f,c=%d",x,c);
 中的x与%f对应,c与%d对应。
2.在格式控制串中,格式说明与输出项的个数应相同,若格式说明少于输出项,则多余的输出项不输出,相反,对于多余的格式将输出不定值。
3.在格式控制串中,可以包含任意的合法字符(包括转义字符)。
4.输出项序列中可以是变量、常量或表达式。如果输出项是表达式时,则printf函数将先对其进行运算,然后输出它的运算结果。如:
 main()
 {
printf("x=%f",3*4.8+9/4-sqrt(2.0));
  }
 输出结果为:
  x=32784.400000
5.printf函数允许没有输出项序列部分。它表示输出一个字符串。此时printf函数成为如下格式:
  printf("输出字符串");
 例如:
  printf("Please input a number:");
 则输出:
  Please input a number:
6.printf函数的返回值通常是本次调用中输出字符的个数。
※重点提示:格式说明与输出项从左到右在类型上、个数上都必须一一对应匹配。
第三节 数据输入
一、scanf函数的一般调用格式
 scanf()函数的一般形式为:
 scanf(“格式控制”,输入项表)
  “格式控制”的含义同printf函数;“输入项地址序列”是由若干个变量地址(变量前加&号)组成的序列,各地址按排次序依次接收转换格式后的读入数据。
例如:
 main()
 { int a,b;
scanf("%d%d",&a,&b);
printf("%d,%d\n",a,b);
 }
 程序运行结果为:
 123 456<回车>
 123,456
 ※重点提示:输入项地址序列中的变量前需要加取地址符号”&”。
二、scanf函数中常用的格式说明
  在格式控制部分一般只包含“格式说明符”这一项内容。和printf函数中的格式说明符相似,以%开始,以一个格式字符结束,中间可以插入附加的字符。
1.scanf常用的格式字符。表3.3列出scanf函数常用的格式字符及其功能说明。
表2.3 scanf格式字符
格式字符 功能说明
d 以带符号的十进制形式输入整数正数
u 以无符号十进制形式输入整数
o 以八进制无符号形式输入整数
x 以十六进制无符号形式输入整数
f 以小数形式或指数形式输入实数
c 输入单一字符
s 输入一个字符串
2.附加格式字符。表3.3列出scanf函数的附加格式字符及其功能说明。
表2.4 scanf常用附加字符
格式字符 功能说明
l 用于输入长整型数据(用%ld,%lo,%lx)以及double型数据(用%lf或%le)
h 用于输入短整型数据(用%hd,%ho,%hx)
m(m表示一个正整数) 用于指定输入数据所占宽度(列数)为m
* 表示该输入项在读入后不赋给相应的变量
3.几点说明
(1)可以指定输入数据所占列数,系统自动按它截取所需数据。
(2)标准C在scanf()函数中不使用%u说明符,对unsigned型数据,以%d,%o,%x输入。
(3)在输入时若想跳过某个数据,可在%后加一个相应的“*”。
(4)输入数据时不能规定精度,如scanf(“%5.2d”,&x)是不合法的。
※重点提示:scanf函数输入的数据不能规定精度。
三、通过scanf函数从键盘输入数据
  通过scanf函数从键盘输入数据时,需要注意以下几点:
1.scanf函数中的输入项必须是“地址量”,它可以是一个变量的地址,也可以是数组的首地址,但不能是变量名。例如,如果定义了a,b为整型变量,则
  scanf(“%d,%d”,a,b);
  是不合法的,应将“a,b”改为“&a,&b”。请读者予以注意,这是初学者易出错的地方。
2.输入数据时,各个数据之间可以用空格“[”或Tab键或回车键作为间隔符。
3.除了空格、Tab键和回车键外,用户还可以自己指定其他字符作为输入间隔。需要注意的是,如果在“格式控制”字符串中除了格式说明以外还有其他字符,则在输入数据时应输入与这些字符相同的字符。
例如,对应语句:scanf(“%d,%d,%d”,&a,&b);
 输入方式应为:1,3<回车>
注意1后面是逗号,它与scanf函数中的“格式控制”中的逗号对应,若输入时不用逗号,而用空格或回车键等字符就不对。
4.特别需要注意的是,在使用格式说明符%c输入一个字符时,凡是从键盘输入的字符,包括空格、回车等均被作为有效字符接收。
例如对语句:scanf(“%c%c”,&c1,&c2);
  若输入:a b<回车>
 原意图是把字符'a'赋给c1,'b'赋给c2,而结果却是把[赋给了c2。因为%c只要求读入一个字符,后面不需要用空格作为两个字符间隔,因此[作为下一个字符赋给c2。
5.在输入数据时,遇以下情况时该数据认为结束:
① 遇空格、Tab键,或回车键。
② 按指定的宽度结束,如“%4d”,只取4列。
③ 遇非法输入。
※重点提示:scanf函数中的输入项必须是“地址量”,它可以是一个变量的地址,也可以是数组的首地址,但不能是变量名。(共31张PPT)
第五章 字符型数据 位运算
李琨
主要内容
第一节 字符编码
第二节 字符型数据
第三节 字符型数据的输入输出
第四节 位运算
第一节 字符编码
计算机中的数据分为数值型和非数值型两大类。
无论是数值型数据和非数值型的数据,在计算机中都是以二进制的形式存放的。
国际上通用的字符包括:大小写英文字母、运算符、标点符号、数字和不可打印的控制符号,合计不超过128个。
国际上通用的是美国标准信息交换码,ASCII码。见书p140
第二节 字符型数据
一、字符型常量
字符常量是用一对单引号括起来的一个字符。如‘a’、‘B’、‘>’、‘?’等都是字符常量。作为字符常量的小写字母a,在程序中写成‘a’,以便和标识符a区别开来。
关于字符常量,以下有几点说明:
(1)其中单引号只是作为定界符使用,并不是字符常量的组成部分,也就是说在输出字符常量时,一对单引号并不被输出。
(2)单引号中的大写字母和小写字母代表不同的字符常量,如‘A’和‘a’是不同的字符常量。
(3)被一对单引号括起来的字符不允许是单引号或反斜杠,即‘’’或‘\’。
(4)字符常量只能包含一个字符,故‘abc’是不合法的。
(5)在C语言中,字符常量具有数值,这个值就是该字符在规定的字符集中的ASCII代码值。在ASCII字符集中的256个字符的值为0~255。如‘a’的值为97,‘A’的值为65。
(6)字符常量在机器内以整型常量的形式存放,占一个字节。因此,字符常量与整型常量等价。也就是说,字符常量可以像整数一样,在程序中参与各种运算。
※重点提示:字符常量只是单个的字符,而且与标识符之间是有区别的,标识符不带有单引号,而字符常量必须使用单引号。
转义字符常量
除了以上形式的字符常量外,C语言还允许用一种特殊形式的字符常量,即以一个“\”开头的字符序列。
例如,在printf函数中的‘\n’,这里的“n”不代表字母n而作为“换行”符。这类字符称为转义字符,又称为反斜线字符,意思是将反斜杠(\)后面的字符转换成另外的意义。其特点是都以“\”开头。
字符形式 功能
\n 回车换行,将光标从当前位置移到下一行开头
\t 横行跳格,相当于TAB键,光标从当前位置跳到下一个TAB位置
\b 退格,光标从当前位置向左退一格
\r 回车不换行,光标从当前位置移到本行开头
\f 换页,将光标从当前位置移到下一页开头
\\ 反斜杠字符“\”
\’ 单引号字符(撇号)
\’’ 双引号字符
\ddd 1~3位八进制数代码对应的字符
\xhh 1~2位十六进制数代码对应的字符
以下是关于转义字符的几点说明:
1.转义字符常量只代表一个字符。
2.反斜杠后的八进制数可以不用0开头,如‘\141’代表的就是字符常量‘a’。
3.反斜杠后的十六进制数只能以小写字母x开头,不允许用大写X,也不能用0x开头,如‘\x41’是代表字符常量‘A’。
※重点提示:转义字符常量,均以一个“\”开头。若要输出一个\字符,则输出格式应为’\\’。
二、字符变量
字符变量就是值为单个字符的变量。
字符型变量的说明格式为:
 char 变量名表;
1.字符变量在内存中占一个字节,只能存放一个字符,可以是ASCII字符集中的任何字符。当把字符放入字符变量中时,字符变量中的值就是该字符的ASCII值。
2.在合法的取值范围内,字符型变量与整型变量可以通用。
3.字符型数据输入、输出形式灵活多样。一字符数据既可以以字符形式输出,也可以以整数形式输出。
※重点提示:字符变量可以作为整型变量来处理,可以参与对整型变量所允许的任何运算。
三、可对字符量进行的运算
  由于字符量具有数值,在机器内也是以整型常量的形式存放,因此字符可参与任何整数运算。
1.利用算术运算实现大写字母与小写字母的互换。
例如: ‘A’+32≡65+32≡’a’
 ‘b’-32≡98-32≡’B’
2.利用算术运算实现字符与整数值之间的互换。
例如: ‘9’﹣’0’≡57﹣48≡9
  表达式中,57、48分别是用十进制表示的字符‘9’、’0’的ASCII代码值。注意,编程时要分清整数9和字符9的区别。
3.字符常量也可以进行关系运算。
 例如: ‘a’<’b’
  在ASCII代码表中,’a’的值是97,小于’b’的值98,所以关系运算的结果为“真”。
4.字符常量还可以进行逻辑运算。
 例如: ‘a’&&’b’的逻辑值为1。
 ※重点提示:字符常量在机器内以整型常量的形式存放,占一个字节。因此,字符常量与整型常量等价。也就是说,字符常量可以像整数一样,在程序中参与各种运算。
第三节 字符的输入和输出
6.3.1 调用printf和scanf函数输出和输入字符
用printf函数输出字符时须使用格式说明%c,但可以在格式字符前加一整数m,用来指定输出字符的宽度。
调用scanf函数可以输入字符。也须使用格式说明符号%,且输入的字符变量前必须加&.以下是几点关于scanf输入字符的几点说明:
(1)当使用的格式说明符%c一个紧接着一个,在输入的时候字符之间没有间隔符时,空格、回车和横向跳格符都将按字符读入。
(2)在格式控制串中可以加入空格。
(3)格式控制符前也可以加一整数m,用来指定输入数据所占宽度。这时在输入字符数据时,应严格按指定的宽度输入数据,且取指定宽度中的第一个字符作为输入的数据。
(4)当从键盘输入字符,并且在格式说明中并未指定宽度时,输入的字符将按顺序赋予各输入项。
 printf和scanf函数的输入格式符可以参考第三章中的详细说明。
 以下是scanf函数应用举例。
例如:求方程ax2+bx+c=0的根。其中a,b,c由键盘输入,设b2-4ac>0。
#include
main()
{ float a,b,c,disc,x1,x2,p,q;
printf("Please enter float a,b,c:\n");
scanf("a=%f,b=%f,c=%f",&a,&b,&c);
disc=b*b-4*a*c;
p=-b/(2*a);
q=sqrt(disc)/(2*a);
x1=p+q; x2=p-q;
printf("\n\nx1=%5.2f\nx2=%5.2f\n",x1,x2);}
程序运行结果为:
x1=-0.37
x2= 5.37
说明:
(1)该程序是利用如下一元二次方程的求根公式来求解的:
再将此公式分为两项:
则方程两根可表示为:


(2)程序第9行中sqrt()是求平方根函数。由于要调用数学函数库中的函数,因此必须在程序的开头用预处理命令#include,把头文件“math.h”包含到程序中来。请注意,以后凡是在程序中要用到数学函数库中的函数,都应当这样处理。
 ※重点提示:用printf函数输出以及用scanf函数输入字符都必须使用格式说明%c。
6.3.2 调用putchar和getchar函数输出和输入字符
1.putchar()函数
putchar的作用是向终端设备输出一个字符。
其一般形式为:putchar(参数)
(1)在一个函数中要调用putchar函数,应在该函数的前面(或本文件开头)加上编译预处理语句:
#include
(2)putchar函数的参数可以是字符变量或字符常量或整型变量,也可以是某个字符对应的ASCII码值,还可以是表达式。并且还可以是控制字符,如‘\n’,它的作用是回车换行,即使输出的当前位置移到下一行的开头。
(3)putchar函数使用举例
2.getchar()函数
getchar函数的作用是向终端设备输入一个字符。
其一般形式为: getchar()
getchar函数不需要参数,函数的值是从输入设备得到的字符。该函数的使用方式有两种:
(1)把函数getchar得到的字符代码赋给一个字符型或整型变量。例如:
int a; a=getchar();
(2)把函数getchar得到的字符代码直接作为表达式的一部分,而不赋给任何变量。例如:printf("%c",getchar());
(3)getchar函数使用举例
①从键盘上输入一个字符,然后显示该字符及其十进制、八进制、十六进制的ASCII码值。
#include
main()
{ int a;
printf("Please enter a character: ");
a=getchar();
printf("%c\t%d\t%o\t%x\n",a,a,a,a);
}
程序运行结果为:
Please enter a character: F<回车>
F 70 106 46
② 试利用getchar函数与putchar函数显示由键盘输入的一个字符
#include
main()
{
char c1;
c1=getchar();
putchar(c1);
}
程序运行结果为:
M<回车>
M
说明:putchar函数的参数可以是表达式,因此上述程序的第3、4、5行可以简化为一条语句:putchar(getchar());
该语句中的putchar参数是函数getchar。
第四节 位运算
C 既具有高级语言的特点,又具有低级语言的功能,位运算能力就是其特色之一。
位运算就是指进行二进制位的运算。C提供的位运算有:
名称 运算符 名称 运算符
按位与 & 按位异或
按位或 左移 <<
取反 ~ 右移 >>
注意:按位求反(~)运算符为单目运算符,优先级最高;其他为双目运算符,结合行自左向右。位运算的运算对象只能是整型或者字符型数据。
一、按位求反(~)
求反运算符~为单目运算符,具有右结合性。
一般形式:~a
运算功能是把a的各二进位按位求反。
例如,~9的运算为:
~(0000000000001001)结果为:
1111111111110110
二、左移(<<)
左移运算符“<<”是双目运算符。
一般形式:a<运算功能:将a的各二进制位向左移动i位。右端补0,左端溢出的部分舍弃。
例如: a<<4;
指把a的各二进位向左移动4位。如a=00001101(十进制13),左移4位后为11010000(十进制-48)。
三、右移(>>)
右移运算符“>>”是双目运算符。
一般形式:a>>i
运算功能:将a的各二进制位向右移动i位。右端溢出的部分舍弃;若a为无符号整数或正整数,左端补0 ;若a为负整数,左端补1。
例如: a>>2;
指把a的各二进位向右移动2位。如a=00001101(十进制13),左移2位后为00000011(十进制3)。
四、按位与(&)
按位与“&”是双目运算符。
一般形式:a&b
运算功能:参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1,否则为0。
例如,3&9=1: 0011
& 1001
────
(0001)2=(1)10
五、按位异或(^)
按位异或“^”是双目运算符。
一般形式:a^b
运算功能:参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1,否则为0。
例如,9&5=12: 1001
& 0101
────
(1100)2=(12)10
六、按位或(|)
按位或“|”是双目运算符。
一般形式:a|b
运算功能:参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1,否则为0。
例如,9|5=12: 1001
& 0101
────
(1101)2=(13)10
说明:
(1)x、y和“位数”等操作数,都只能是整型或字符型数据。除按位取反为单目运算符外,其余均为双目运算符。
(2)参与运算时,操作数x和y,都必须首先转换成二进制形式,然后再执行相应的按位运算。
实例说明:
从键盘上输入1个正整数给int变量num,按二进制位输出该数。
说明
1.复合赋值运算符
除按位取反运算外,其余5个位运算符均可与赋值运算符一起,构成复合赋值运算符: &=、|+、^=、<<=、>>=
2.不同长度数据间的位运算──低字节对齐,短数的高字节按最高位补位:
对无符号数和有符号中的正数,补0;
有符号数中的负数,补1。(共44张PPT)
2.1 程序设计方法与风格
2.2 结构化程序设计
2.3 面向对象的程序设计
2.4 典型考题分析
第二章 程序设计基础
2.1.1 程序设计方法
2.1.2 程序设计风格
2.1程序设计方法与风格
2.1.1 程序设计方法
程序设计方法所要做的工作是:如何对实际问题进行抽象和分解以及对程序进行组织,才能使程序的可读性、稳定性、可维护性、效率等更好。程序设计方法可分为:
结构化设计方法
模块内部程序各部分要按照自顶向下的结构划分
各程序部分应按功能组合
各程序之间的联系尽量通过调用子程序来实现,不用或少用GOTO方式
面向对象程序设计方法
2.1.2 程序设计风格
原则:清晰第一,效率第二
1. 源程序中的内部文档
符号名的命名:有一定实际含义
程序的注释:
作用:给出程序的整体说明和程序的主要功能
分类:
序言性注释
功能性注释
程序的视觉组织:层次清晰
2. 数据说明
数据说明的次序规范化
说明语句中变量安排有序化
使用注释来说明复杂数据的结构
2.1.2 程序设计风格(续)
3.语句的结构
在一行内只写一条语句
程序编写应优先考虑清晰性,在编写程序时要做到:清晰第一,效率第二
在保证程序正确的基础上再要求提高效率
避免使用临时变量前使程序的可读性下降
避免不必要的转移
尽量使用库函数
避免采用复杂的条件语句
尽量减少使用“否定”条件语句
数据结构要有利于程序的简化
要模块化,使模块功能尽可能单一化
利用信息隐蔽,确保每一个模块的独立性
从数据出发去构造程序
不要修补不好的程序,要重新编写
2.1.2 程序设计风格(续)
4.输入和输出
对输入数据检验数据的合法性进行检验
检查输入项的各种重要组合的合理性
输人格式要简单,使得输入的步骤和操作尽可能简单
输人数据时,应允许使用自由格式
应允许缺省值
输入一批数据时,最好使用输入结束标志
在以交互式输入/输出方式进行输人时,要在屏幕上使用提示符明确提示输入的请求,同时在数据输入过程中和输入结束时,应在屏幕上给出状态信息
当程序设计语言对输入格式有严格要求时,应保持输入格式与输入语句的一致性;给所有的输出加注释,并设计输出报表格式
2.2.1 结构化程序设计的原则
2.2.2 结构化程序的基本结构与特点
2.2.3 结构化程序设计原则和方法的应用
2.2 结构化程序设计
基本思想
关于GOTO语句
工程思想
结构化思想
自顶向下,逐步求精,模块化,限制使用GOTO语句
2.2.1 结构化程序设计的原则.
1.自顶向下
2.逐步求精
3.模块化
4.限制使用GOTO语句
2.2.2 结构化程序的基本结构与特点
三种基本结构
顺序结构
选择结构
重复结构
2.2.2 结构化程序的基本结构与特点(续)
顺序结构
定义:按照程序语句行的自然顺序,一条语句一条语句地执行程序。
图示:
2.2.2 结构化程序的基本结构与特点(续)
选择结构
定义:根据设定的条件,判断应该选择执行哪一条分支的语句序列。
说明:又称分支结构、简单选择结构、多分支选择结构
图示:
2.2.2 结构化程序的基本结构与特点(续)
循环结构,又称重复结构
定义:根据给定的条件,判断是否需要重复执行同一相同的程序段,利用循环结构可以简化大量的程序
分类:
当型循环结构:先判断后执循环体。
直到型循环结构:先执循环体后判断
图示:
2.2.2 结构化程序的基本结构与特点(续)
特点
关系清晰、易读、易理解性好、易维护。
“自顶向下、逐步细化”,提高效率,降低成本
2.2.3 结构化程序设计原则和方法的应用
在实际设计结构化程序时,应注意以下几点:
用有限的控制结构:顺序、选择、循环等
选用的控制结构只允许有一个入口和一个出口
程序语句组成容易识别的语句序列块,每块只有一个入口和一个出口
使用嵌套的基本控制结构进行组合嵌套来实现
前后一致
避免GOTO语句
2.3.1面向对象的程序设计
2.3.2 面向对象方法特点
2.3.3 面向对象方法的基本概念
2.3 面向对象的程序设计
2.3.1面向对象的程序设计
面向对象的程序设计是在结构化程序设计的基础上,以更接近人们通常思维的方式来解决问题的一种全新的软件开发技术。
面向对象的程序设计以对象为核心,强调对象的“抽象性”、“封装性”、“继承性”和“多态性”。
本质:对系统的复杂性进行概括、抽象和分类,使软件的设计与现实形成一个由抽象到具体、由简单到复杂这样一个循序渐进的过程,从而解决大型软件研制中存在的效率低、质量难以保证、调试复杂、维护困难等问题。
2.3.2 面向对象方法特点:
主要特点:
与人类习惯的思维方法一致
稳定性好
可重用性好
易于开发大型软件产品
可维护性好
2.3.3 面向对象方法的基本概念
1.对象(Object)
对象是基本的运行时认得实体,它既包括数据(属性),也包括作用于数据的操作(行为)。
一个对象把属性和行为封装为一个整体
一个对象通常可由对象名、属性和操作3部分组成
2.3.2 面向对象方法的基本概念(续)
对象特点
标识惟一性
分类性
多态性
封装性
模块独立性好
2.3.2 面向对象方法的基本概念(续)
2.类和实例
类是具有共同属性、共同操作方法的对象的集合,是对象的抽象
对象是其对应类的一个实例
2.3.2 面向对象方法的基本概念(续)
3.消息
对象之间进行通信的机制
三部分组成
接收消息的对象的名称
消息标识符(消息名)
零个或多个参数
2.3.2 面向对象方法的基本概念(续)
4.继承
继承是父类和子类之间共享数据的方法的机制
一个子类可以继承它的父类(或祖先类)中的属性和操作
子类中可以定义自己的属性和操作
单重继承、多重继承
2.3.2 面向对象方法的基本概念(续)
5.多态性
不同的对象收到同一消息可以产生完全不同的结构,这一现象叫做多态性
优点:灵活性、可重用性、可扩充性。
典型考题分析
2.4 典型考题分析
【例2-1】从程序设计方法和技术的发程序角度来说,程序设计主要经历了结构化设计和_____的程序设计阶段。
答案: 面向对象
2.4 典型考题分析
【例2-2】对建立良好的程序设计风格,下面描述正确的是______。
A)程序应简单、清晰、可读性好
B)符号名的命名只要符合语法
C)充分考虑程序的执行效率
D)程序的注释可有可无
答案: A
2.4 典型考题分析
【例2-3】源程序的文档化不包括_________。
A)符号名的命名要有实际意义
B)正确的文档格式
C)良好的视觉组织
D)正确的程序注释
答案:D
2.4 典型考题分析
【例2-4】注释一般为序言性注释和_______注释。
答案 功能性
2.4 典型考题分析
【例2-5】在设计程序时,应采纳的原则之一是_______。
A)程序结构应有助于读者理解
B)不限制GOTO语句的使用
C)减少或取消注解行
D)程序越短越好
答案:A
2.4 典型考题分析
【例2-6】下列选项中不属于结构化程序设计方法的是__________。(2006年4月)
A)自顶向下
B)逐步求精
C)模块化
D)可复用
答案:D
2.4 典型考题分析
【例2-7】下列选项不符合良好程序设计风格的是__________。(2006年9月)
A)源程序要文档化
B)数据说明的次序要规范化
C)避免滥用 GOTO 语句
D)模块设计要保证高耦合、高内聚
答案:D
2.4 典型考题分析
【例2-8】结构化程序设计的三种基本控制结构是__________。
A)过程、子程序和分程序
B)顺序、选择和重复
C)递归、堆栈和队列
D)调用、返回和转移
答案:B
2.4 典型考题分析
【例2-9】结构化程序设计主要强调的是__________。
A)程序的规模
B)程序的易读性
C)程序的执行效率
D)程序的可移植性
答案:B
2.4 典型考题分析
【例2-10】关于结构化程序设计原则和方法的描述错误的是__________。
A)选用的控制结构只准许有一个入口和一个出口
B)复杂结构应该用嵌套的基本控制结构进行组合嵌套来实现
C)不允许使用GOTO语句
D)语言中所没有的控制结构,应该采用前后一致的方法来模拟
答案:C
2.4 典型考题分析
【例2-11】采用面向对象技术开发的应用系统的特点是________。
A)重用性更强
B)运行速度更快
C)占用存储量小
D)维护更复杂
答案:A
2.4 典型考题分析
【例2-12】在面向对象方法中,类的实例称为________。(2005年4月)
答案:对象
2.4 典型考题分析
【例2-13】消息传递是对象间通信的手段,一个对象通过向另一个对象发送消息来请求其服务。一个消息通常包括_______。
A)接收消息的对象的名称、消息标识符和必要的参数
B)接收消息的对象的名称和消息标识符
C)发送消息的对象的名称、调用的接收方的操作名和必要的参数
D)消息标识符
答案:A
2.4 典型考题分析
【例2-14】一个对象在收到消息时,要予以响应。不同的对象收到同一消息可以产生完全不同的结果,这一现象叫做对象的__________。
A)继承性
B)多态性
C)抽象性
D)封装性
答案:B
2.4 典型考题分析
【例2-15】在面向对象程序设计中,从外面看只能看到对象的外部特征,而不知道也无需知道数据的具体结构以及实现操作的算法,这称为对象的______。
答案:封装性
2.4 典型考题分析
【例2-16】使用已经存在的类作为基础建立新类的定义,这种技术叫做类的________。
答案:继承
2.4 典型考题分析
【例2-17】一个类允许有多个父类,这种继承称为________。
答案:多重继承(共23张PPT)
第八章 数组
第八章 数组
数组是是一组有序的、同类型数据的集合 ,这些数据称为数组元素。
数组元素在内存中占据连续的存储空间。
同一数组中元素的类型相同。
根据数组中元素具有下标的个数可以把数组分为:一维数组、二维数组和多维数组。
一、一维数组的定义
当数组中每个元素只有一个下标的数组称为一维数组。
一维数组的定义方式为:
  类型名 数组名[常量表达式];
 例如:  int array[10];
   它表示定义了一个名为array的数组,此数组有10个元素,每个元素的数据类型为整型。
第一节 一维数组
一维数组定义的说明:
1.类型名用来说明数组元素的数据类型,可以是以前介绍过的任一种数据类型。
2.数组名由用户指定,命名规则和变量名相同,遵循标识符定义规则。
3.常量表达式规定了数组元素的个数,即数组的长度。整个数组所占字节数=类型长度×数组长度。
4.常量表达式中不能包括变量,即C语言不允许定义动态数组。
5.常量表达式中可以包括常量和符号常量。
6.每个数组元素只有一个下标,C语言规定数组第一个元素的下标总为0(称为数组的下界)。
7.定义数组后,C编译程序即为该数组在内存中开辟相应个数的存储单元,每个存储单元可以直接用相应的数组元素表示。
8.数组定义中,数组长度除多数情况下作显式说明外,有两种情况下不必或不能用长度说明,而用[]代替。
(1)给数组全体元素赋初值时,可省去数组长度说明。
(2)数组名作为函数的参数,在函数的参数说明部分,当指出参数是数组时,不能用长度说明。
二、一维数组的初始化
 当系统为所定义的数组在内存中开辟一串连续的存储单元时,这些存储单元中并没有确定的值,数组的初始化就是指在定义数组时给数组元素赋初值。
一维数组初始化的定义形式为:
 类型名 数组名[常量表达式或省略]={值0,值1,……};
  其中,{}中各值是对应的数组元素初值,各值之间用逗号隔开。例如:
  int a[5]={0,1,2,3,4};
 也可以省略为:
 int a[ ]={0,1,2,3,4};
说明:
1.可以只给部分数组元素赋初值。当{}中值的个数少于数组元素个数时,则表示初值只赋于数组开始的若干个元素,余下部分元素为相应类型的缺省值,int为整型数0,字符型为空格等。
2.{}中值的个数不能超过数组元素的个数。
3.只能给数组元素逐个赋值,不能给数组整体赋值。
4.对较大数组中的若干不连续的数组元素赋予非零的初值,其余数组元素为0值时,可以用“,”表示对应位置的元素为0值。
5.若全部元素均赋为0,可对数组不赋初值。
6. C语言中,还可以通过赋初值来定义数组的大小,这时数组说明符的一对方括号中可以不指定数组的大小。
例如:
int a[ ]={1,2,3}
此时就隐含的定义了a数组含有3个元素。
三、一维数组元素的引用
在定义数组之后,数组元素就可以被引用了。
引用一维数组元素的一般形式如下:
数组名 [下标表达式];
数组名后的一对方括号是不可缺少的,下标表达式可以是由整型的常量和变量构成的表达式。
例如:
int a[5] ={10,20,30,40,50};
可以使用a[0]表示第一个元素,即a[0]==10;…a[4]表示最后一个元素,即a[4]==50
说明:
(1)数组中的元素其实就是一个变量,占用一个存储单元。如数组a中的变量就是普通的int类型的变量,该类型变量的一切合法操作对于数组元素来说都是正确的。
例如:
a[0]=5;
a[0]++;
a[1] = a[0]+14;
printf(“%d”,a[4]);
(2)引用数组元素时,下标表达式的值的上限为定义时的常量表达式减1,下限为0。在引用时,要保证下标不能超过定义时的常量表达式减1,即下标不能越界。
例如,定义了int a[5];下标不能超过4,否则越界溢出,编译出错。
(3)数组是一个构造类型,是集合的概念,一次不能整体引用数组里的所有元素。企图简单的通过数组名a整体代表其中的5个int类型的变量是不行的。
第二节 二维数组
先看一个例子:
某校近三年招收各专业毕业生情况如下:
计算机 电子 管理 数学
2002 90 40 80 30
2003 100 50 90 40
2004 95 45 100 50
 要把这些数据组织起来,可以有两种选择:
⑴按从左到右从上到下的顺序存入一个一维数
组中。(查询困难)
⑵每年用一个一维数组,把这些数据分别存入
三个数组中。 (增加一年数据困难)
一、 二维数组的定义
当数组元素的下标为两个时,该数组称为二维数组。
1.二维数组的定义格式
存储类型 数据类型 数组名[常量表达式1][常量表达式2];
功能:定义一个二维数组,有“长度1×长度2”个元素。其元素的存储类型和数据类型分别由定义中的“存储类型”和“数据类型”指定。
说明:
(1)存储类型、数据类型、数组名和长度的含义和选取方法同一维数组。
(2)数组元素的各维下标从0开始,最大下标为“长度 1”。
2.二维数组的逻辑结构和存储结构
二维数组的逻辑结构,可以看成是由若干行,每行由若干列组成。例如有如下数组定义语句:int a[3][4];则其逻辑结构如下:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
二维数组存储结构是“按行存放,先行后列”,
说明:
(1)数组名是用户定义标识符
(2)数组名后面的两对方括号必不可少,之间不能有空格。方括号中必须是常量表达式,表达式1表示数组的行数,表达式2表示数组的列数。其中必须是正的整型常量,绝对不能是变量。
(3)定义后,a数组在内存中占用6个连续的存储单元,按照行的顺序依次存储各个元素。 见书p75图8-2、图8-3.
(4)可以把二维数组看成是一个特殊的一维数组。
二、二维数组的初始化
在定义二维数组之后,系统只是在内存中开辟了一系列的连续存储单元,并没有对每一个数组元素进行赋值。因此,我们在定义数组之后要进行初始化。
二维数组初始化的方法有以下几种。
(1)分行给二维数组所有元素赋初值。
例如:int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
初始化后,a[1][2]的值即是6。
(2)每行的初始值数可以少于该行元素的个数,此时系统对为赋值的元素默认为0.
 例如:int a[3][4]={{0,1},{4},{8,9,10}};
 初始化后,问a[0][1]和a[2][3]的值即为1和0。
(3)行数也可以少于数组应有的行数,此时系统对未赋值的行中所有元素默认为0.
 例如:int a[2][3]={{10,20,30}};
初始化后,第0行的三个元素初值分别为10,20,30,但未赋值的第1行所有元素的初值均默认为0.
(4)可以省略内层的花括号对。
例如:int a[2][3]={10,20,30,40,50,}
(5)定义语句中的表达式1(行数)可以省略,但是表达式2(列数)不能省略。
如: int a[][3]={{10,20,30,},{40,50,60}};
该句省略了定义语句中的常量表达式1。系统会根据后面赋初始值的情况来判断,
  注意:给二维数组赋初值时,行数不能超过定义的行数,每行的初值个数不能超过定义时的列数。下列的定义是错误的:
9.5.4 通过赋值定义二维数组的大小
  在定义一个二维数组时,可以通过赋初值的个数来确定数组的大小,但只可以省略第1个方括号的常量表达式,而不能省略第2个括号的常量表达式。
例如:
int a[ ][2]={ {1,2}, {3}, {4}, {5} };
以上语句中,a数组中的第一维的常量表达式省略,在所赋初值中,有4对行向量,则第一维的大小可由所赋初值的行数来确定。即等价于:
int a[4][2]= { {1,2}, {3}, {4}, {5} };
当使用行花括号赋初值时,第1维的大小由赋初值的行数来决定。当省略行花括号时,第1维的大小按以下规则来决定:
(1)当初值的个数能被第2维的常量表达的值除尽时,所得商数就是第1维的大小;
(2)当初值的个数不能被第2维的常量表达的值除尽时,则:
第1维的大小 = 所得商数+1
例如:
int a[ ][2]={1,2,3,4};
按此规则,a数组的第一维大小应该是2,即
int a[2][2]={ {1,2}, {3,4} };
9.5.2 二维数组元素的引用
  二维数组元素的引用方法也有两种,分别是“指针法”和“下标法”。这里只讨论“下标法”。假设定义了一个二维数组:int a[N1][N2],其引用形式为:
数组名[下标表达式1][下标表达式2]
  二维数组元素的引用时注意事项:
(1)二维数组各维的下标也是从0开始
(2)下标表达式的值必须是整数,且不得超越数组定义的上、下界。
(3)引用二维数组元素时,一定要把两个下标分别放在两个括号内。(共52张PPT)
12.1 对函数的深入讨论
12.2 编译预处理
12.3 标识符的作用域
12.4 函数的存储分类
12.5 动态存储分配
12.6 定义用户类型
12.7 对C语言的一些说明
第十二章 对C语言的深入讨论
12.1.1 main函数的参数
12.1.2 函数指针
12.1.3 函数的递归调用
12.1 对函数的深入讨论
12.1.1 main函数的参数
main函数是C程序中的主函数,是整个程序的入口,每个C程序有且只有一个main函数。
带参数的main函数
main函数通常有两个参数,其定义形式如下:
main(int argc,char *argv[])
{ …… }
argc和argv是main函数的两个参数,名称可改变,但参数类型是C规定好的,不可改变。
第一个参数必须是整型,第二个参数必须是字符串的指针,也就是char类型的二维指针。第二个参数也可定义为char **argv。
main函数参数的作用:
argc传入命令参数的个数。
argv传入命令参数的各个字符串。
例:如果我们把一个C语言源程序保存为my.c,经过编译、链接后会产生一个my.exe的可执行程序,这样我们就可以离开C语言的开发环境,直接运行该程序了,例如在DOS环境中输入如下程序:
my a command line (CR代表回车)
在上面的命令中,my是可执行程序,后面的a、command和line是my命令的参数。这些参数就是通过main函数的参数传递给程序的。my也被当作一个命令参数被传入。
在上面命令中,argc的值为4,表示有4个命令参数,argv指向一个指针数组,该数组有4个字符串数组元素,分别指向4个命令参数my、a、command和line,如下图所示:
argv[3]
argv[2]
argv[1]
argv[0]
argv
line
command
a
my
命令行各参数间以空格或Tab键隔开,如果参数包含空格,必须把参数放在一对双引号内,例如:
my “a command” line (CR代表回车键)
则a command被当成一个参数.
12.1.2 函数指针
在C语言中,函数名代表一个函数的入口地址,因此可以定义一个指针指向该入口地址,即指向函数的指针。
指向函数的指针变量定义格式如下:
函数返回值类型 (*指针名)(函数参数列表);
例 以下程序使用函数指针调用函数
int fun(int a,int *b)
{ …… }
main()
{ int (*pfun)(int,int*),a,b,s;
pfun=fun;
……
s=(*pfun)(a,&b);
……
}



12.1.3 函数的递归调用
定义:函数直接或间接的调用自身叫函数的递归调用。
说明:
若要采用递归的方法解决问题,可以把要解决的问题转化为一个新的问题,而这个新的问题的解决仍与原来的解决方法相同,只是处理的对象有规律的递增或递减,从而应用这个转化过程使问题得到解决。
C编译系统对递归函数的自调用次数没有限制。
每调用函数一次,在内存堆栈区分配空间,用于存放函数变量、返回值等信息,所以递归次数过多,可能引起堆栈溢出。
必须有一个明确的结束递归的条件。
例 以下程序运行后的输出结果是:
fun(int x)
{ if(x/2>0) fun(x/2);
printf(“%d”,x);
}
main()
{ fun(6); }
输出结果:136
main()
fun(6)
fun(3)
fun(1)
x为6
输出6
x为3
输出3
x为1
输出1
12.2.0 概述
12.2.1 宏
12.2.2 文件包含
12.2 编译预处理
在C中,凡是以”#”开头的行,都称为“编译预处理”命令行,编译预处理命令行并不是C语言本身的组成部分,不能直接对它们编译,而是由预处理程序在编译前作出相应的处理。
作用:对源程序编译之前做一些处理,生成扩展C源程序
种类
宏定义 #define
文件包含 #include
格式:
“#”开头
占单独书写行
语句尾不加分号
12.2.0 概述
如 if(x==YES) printf(“correct!\n”);
else if (x==NO) printf(“error!\n”);
展开后: if(x==1) printf(“correct!\n”);
else if (x==0) printf(“error!\n”);
12.2.1 宏
不带参数宏定义
一般形式: #define 宏名 [宏体]
功能:用指定标识符(宏名)代替字符序列(宏体)
宏展开:预编译时,用宏体替换宏名---不作语法检查
如 #define YES 1
#define NO 0
#define PI 3.1415926
#define OUT printf(“Hello,World”);
宏体可缺省,表示宏名
定义过或取消宏体
定义位置:任意(一般在函数外面)
作用域:从定义命令到文件结束
#undef可终止宏名作用域
格式: #undef 宏名
例 #define YES 1
main()
{ ……..
}
#undef YES
#define YES 0
max()
{……..
}
YES原作用域
YES新作用域
宏定义可嵌套(宏定义行的替换文本中可以包含已经定义过的宏名),不能递归
例 #define MAX MAX+10 ( )
引号中的内容与宏名相同也不置换
例 #define PI 3.14159
printf(“2*PI=%f\n”,PI*2);
宏展开:printf(“2*PI=%f\n”,3.14159*2);
宏定义中使用必要的括号()
例 #define WIDTH 80
#define LENGTH WIDTH+40
var=LENGTH*2;
宏展开:var= 80+40 *2;
( )
( )
例 #define WIDTH 80
#define LENGTH WIDTH+40
var=LENGTH*2;
宏展开:var= 80+40 *2;
说明:
宏名一般用大写字母。这不是语法规定,只是一种习惯
可以使用宏名代替一个字符串
当宏定义在一行中写不下,需要在下一行继续时,只需在最后一个字符后紧接一个反斜线“\”。例如:
#define LEAP_YEAR year % 4==0\
&& year %100!=0 || year % 400==0
注意在第二行开始不要有空格,否则空格会一起被替换
宏名的有效范围为定义命令之后到本源文件结束,除非用#undef命令终止宏名的作用域
对程序中用双引号括起来的字符串内的子串和用户标识符中的成分不做替换
同一个宏名不能重复定义,除非两个宏定义命令行完全一致
带参数宏定义
功能:将一个带参数的表达式定义为一个带参数的宏。
一般形式: #define 宏名(参数表) 宏体
例 #define S (r) PI*r*r
相当于定义了不带参宏S,代表字符串“(r) PI*r*r”
宏展开:形参用实参换,其它字符保留
宏体及各形参外一般应加括号()
例 #define S(a,b) a*b
………..
area=S(3,2);
宏展开: area=3*2;
不能加空格
例 #define POWER(x) x*x
x=4; y=6;
z=POWER(x+y);
宏展开:z=x+y*x+y;
一般写成: #define POWER(x) ((x)*(x))
宏展开: z=((x+y)*(x+y));
说明:
在替换带参数的宏名时,一对圆括号不能少,圆括号中实参的个数应该与形参个数相同,若有多个参数,各参数用逗号隔开
与不带参数的宏相同,同一个宏名不能重复定义,除非两个宏定义命令行完全一致
#define MAX(x,y) (x)>(y) (x):(y)
…….
main()
{ int a,b,c,d,t;
…….
t=MAX(a+b,c+d);
……
}
宏展开:t=(a+b)>(c+d) (a+b):(c+d);
int max(int x,int y)
{ return(x>y x:y);
}
main()
{ int a,b,c,d,t;
…….
t=max(a+b,c+d);
………
}
例 用宏定义和函数实现同样的功能
带参的宏与函数区别
带参宏
函数
处理过程
不分配内存
简单的字符置换
分配内存
先求实参值,再代入形参
处理时间
编译时
程序运行时
参数类型
无类型问题
定义实参,形参类型
程序长度
变长
不变
运行速度
不占运行时间
调用和返回占时间
12.2.2 文件包含
功能:一个源文件可将另一个源文件的内容全部包含进来
一般形式: #include “文件名”
或 #include <文件名>
#include “file2.c”
file1.c
file2.c
file1.c
file2.c
A
B
A
处理过程:预编译时,用被包含文件的内容取代该预处理命令,再对“包含”后的文件作一个源文件编译
<> 直接按标准目录搜索
“” 先在当前目录搜索,再搜索标准目录
可指定路径
被包含文件内容
源文件(*.c)
头文件(*.h)
宏定义
数据结构定义
函数说明等
文件包含可嵌套
#include “file2.c”
file1.c
A
file3.c
C
#include “file3.c”
file2.c
B
file1.c
A
file3.c
file2.c
说明:
#include命令行通常书写在文件的开头,故有时也把包含文件称作“头文件”。头文件名可以由用户指定,其后缀不一定用“.h”。
包含文件中,一般包含一些公用的#define命令行、外部说明或函数的原型说明。例如stdio.h就是这样的头文件。
当包含文件修改后,对包含该文件的源程序必须重新进行编译连接。
在一个程序中,允许有任意多个#include命令行。
在包含文件中还可以包含其他文件。
例 文件包含举例

#define N 10
#define f2(x) (x*N)

#include
#define M 8
#define f(x) ((x)*M)
#include “14.h”
main()
{ int i,j;
i=f(1+1); j=f2(1+1);
printf(“%d %d“,i,j);
}
运行结果:16 11
12.3.0 概述
12.3.1 存储分类
12.3.2 局部变量
12.3.3 全局变量
12.3 标识符的作用域
12.3.0 概述
标识符必须先定义后使用,用户定义的标识符都有一个有效的作用域,所谓变量的作用域,指的是程序中的某一部分。只有在这一部分,该变量才是有效的,才可以被C编译和连接程序所识别。
变量是对程序中数据的存储空间的抽象
内存
…….
main()
{ int a;
a=10;
printf(“%d”,a);
}
编译或函数调用时为其分配内存单元
10
2000
2001
程序中使用变量名对内存操作
12.3.1 存储分类
变量的分类
生存期:变量在某一时刻存在-------静态变量与动态变量
作用域:变量在某区域内有效-------局部变量与全局变量
变量的存储类型
auto -----自动型
register-----寄存器型
static ------静态型
extern -----外部型
变量定义格式: 存储类型 数据类型 变量列表;
或 数据类型 存储类型 变量列表;
变量的存储类型可放在类型名的左边,也可放在类型名的右边.
如: int sum;
auto int a,b,c;
register int i;
static float x,y;
或: int anto a,b,c;
int register i;
float static x,y;
12.3.2 局部变量
局部变量有3种类型:anto、register和static。
anto变量
在定义局部变量时,如果没有指定相应的存储类,则系统默认为anto类型。
anto类型的变量只能在定义它的函数或复合语句内有效。当程序进入定义该变量的函数或复合语句时,系统为这些变量临时分配内存单元。当程序离开这个函数或复合语句时,系统将销毁这些内存单元,使其数据不再有效。
例 int sum;
等价于:
anto int a;
或 int anto a;
例 main()
{ int i=1,i_sum;
{ float f, f_sum;
int i;
i=10;
printf(“(1) i=%d\n”,i);
}
printf(“(2) i=%d\n”,i);
}
输出结果:
(1) i=10
(2) i=1
register变量
register变量(寄存器变量)与anto变量一样,是自动类变量,其作用域和生存期与anto变量一样。
与anto变量唯一区别是anto变量在内存中分配存储空间,而register变量在CPU的寄存器分配。
说明:
寄存器变量的运行速度要远远大于其他类型的变量,读者可根据需要在必要的时候使用寄存器变量。
由于PC机中寄存器的数量极其有限,使用过多的寄存器变量可能会影响程序其他部分的运行效率。
由于寄存器的数量有限,系统在分配寄存器变量时有可能分配不成功,这时系统将为register变量分配anto变量。因此register说明只是对编译系统的一种建议,而不是强制性的。
由于register变量分配的是寄存器而不是内存,因此register类型的变量没有地址,不能使用取地址运算符。
例 main()
{ register int i=1;
int s=0;
for(i=0;i<100;i++)
s+=i;
printf(“sum=%d\n”,sum)
}
static变量
在函数内部或复合语句中定义变量时,如果使用static来说明,就构成了静态局部变量。
静态局部变量的作用域与anto和register类型变量一样,但其生存期却完全不同。
静态局部变量在内存的静态存储区占据着永久性的存储单元,即使离开定义该变量的函数(或复合语名),该存储单元也不会被销毁。当下次再进入定义该变量的函数(或复合语句)时,存储单元仍然保存着原来的值。
静态局部变量的初值是在编译时赋予的,在程序被执行时不再赋初值。
对未赋初值的静态局部变量,C语言编译系统自动给它赋初值0。
例 fun(int a)
{ int b=0;static int c=3;
b++;
c++;
return(a+b+c);
}
main()
{ int i, a=5;
for(i=0;i<3;i++)
printf(“ %d %d”,i,fun(a));
printf(“\n”);
}
输出结果:
0 10 1 11 2 12
第几次调用 调用时初值 调用结束时的值
a b c a b c 函数值
第1次调用 5 0 3 5 1 4 10
第2次调用 5 0 4 5 1 5 11
第3次调用 5 0 5 5 1 6 12
12.3.3 全局变量
在函数外部任意的位置定义的变量,称为全局变量。全局变量都是静态变量,其作用域从定义的位置开始,到整个文件结束为止。
例 int sum;
int fun1()
{ sum+=20; }
int a;
int fun2()
{ a=20; sum+=a; }
main()
{ sum=0;
fun1();
a=8;
fun2();
printf(“sum=%d a=%d”,sum,a);
}
int p=1,q=5;
float f1(int a)
{ int b,c;
…….
}
int f3()
{…..
}
char c1,c2;
char f2(int x,int y)
{ int i,j;
……
}
main()
{ int m,n;
…….
}
c1,c2的作用范围
p,q的作用范围
extern char c1,c2;
c1,c2
的作用范围
扩展后
c1,c2
的作用范围
扩展后
extern char c1,c2;
可以使用extern和static来说明全局变量。
在同一编译单位内使用extern来说明全局变量。
当调用全局变量的函数在该全局变量前面时,可使用extern说明全局变量。此时该变量的作用域就从extern说明处开始。
使用extern说明全局变量,并不是对变量进行了重新定义,它仅仅是告诉编译器,该变量已经定义,可使用了。
在整个程序中,对一个全局变量的定义只能出现一处,它要求编译器分配内存单元。在定义时不能出现extern说明符。
在整个程序中,对全局变量的说明可出现在多处,它并不分配存储单元,仅仅告诉编译器,该变量已在别的地方定义了。在对全局变量进行说明时,必须使用extern说明符。
在不同编译单位内使用extern来说明全局变量。
我们知道,C是由函数构成。当C语言的源程序过大时,为了管理上的方便,往往把源程序分别存放在若干个不同的源文件中。这些源文件可以单独进行编译,生成目标文件(obj文件)。然后再使用链接程序将这些目标文件连接成一个可执行文件(exe文件)。通常,人们把每个可以单独编译的源文件称为编译单位。
在不同编译单位内需要使用同一个全局变量时,就必须使用extern来对变量进行说明。
如果在每个编译单位都定义一个相同名称的全局变量,在对每个编译单位进行编译时并不会出现错误,但在编译生成目标文件进行链接时,就会出现“重复定义变量”的错误。可在其中一个编译单位内定义变量,而在其他的编译单位内使用extern来说明变量。

int a;
void fun()
main()
{ a=10;
printf(“In main a=%d\n”,a);
a=100;
fun();
}

extern int a;
void fun()
{ printf(“In fun a=%d\n”,a); }
使用static说明全局变量。
当使用static说明符来说明全局变量时,则这个全局变量为静态全局变量,静态全局变量只能被本编译单位使用,不能被其他编译单位使用。

static int a;
void fun()
main()
{ a=10;
printf(“In main a=%d\n”,a);
a=100;
fun();
}

extern int a;
void fun()
{ printf(“In fun a=%d\n”,a); }
12.4.1 用extern说明函数
12.4.2 用static说明函数
12.4 函数的存储分类
函数不可以嵌套定义,也就是在函数内部不可以再定义函数,所以所有函数在本质上都是外部的,可在定义函数时使用static和extern说明符来改变函数的作用域。
12.4.1 用extern说明函数
可在函数返回值类型前面加上extern来说明函数,使用extern说明的函数称为外部函数。
例: extern int fun()
{ …… }
extern可省略,我们以前定义的函数都属于外部函数。
外部函数可以被其他编译单位内的函数调用,如果在其他编译单位需要调用该函数,必须在调用前使用extern对所调用的函数进行说明。
12.4.2 用static说明函数
使用static进行说明的函数称为静态函数,也叫内部函数。静态函数只能被本编译单位内的函数调用,不能被其他编译单位内的函数调用。
12.5.1 malloc函数
12.5.2 free函数
12.5.3 calloc函数
12.5 动态存储分配
12.5.1 malloc函数
C语言提供了4个函数用于内存空间的动态分配与释放,它们分别是malloc、calloc、realloc和free。
malloc函数
作用:动态分配内存单元。
调用形式:malloc(size)
返回值类型:void *
如果系统有足够的内存可供分配,函数返回一个指向有size个字节的存储区首地址,该首地址的基类型为void类型
若没有足够的存储空间,函数返回空值(NULL)
unsigned int类型,表示需要分配的字节数
例 malloc函数应用举例
int *p;
float *q;
……
p=(int*)malloc(2);
q=(float*)malloc(4);
……
在本例中p、q被定义成了两个指针变量,基类型分别为int类型和float类型。通过malloc函数为这两个指针变量分配内存单元。在调用malloc函数时,传入需要分配的字节数,int类型占用两个字节,float类型占用4个字节,则在调用malloc函数时分配传入参数2和4。
由于malloc函数返回的是void*类型的指针,与变量p和q的基类型不相符,因此需要将其进行强制类型转换。
若有:
if(p!=NULL) *p=6;
if(q!=NULL) *q=3.8;
赋值后数据的存储情况如下:
p
6
q
3.8
说明:
动态分配得到的内存单元没有名字,只能通过指针变量来引用它。
一旦指针变量的值发生改变,原存储单元及所存数据都无法再引用。
通过malloc函数要配的动态存储单元中没有确定的初值。
在调用malloc进行存储分配时,如果不能确定数据类型所占字节数,可以调用sizeof运算符来求得。如上例可改为:
p=(int*)malloc(sizeof(int));
q=(float*)malloc(sizeof(float));
这是一种常用的方式,由系统来计算指定数据类型的字节数
12.5.2 free函数
free函数
作用:释放动态分配的内存单元。
调用形式:free(p)
返回值:无
说明:
静态存储分配的变量和数组,在生存期过后,或者程序运行结束后,所占用的存储单元会由系统自动释放。
动态分配的内存单元,需手动进行释放。
free函数是将指针p所指向的空间释放,使这部分空间可以由系统得新分配。
指向动态分配函数分配的地址
12.5.3 calloc函数
calloc函数
作用:分配单个数据类型的存储单元或分配多个同一类型的连续的存储空间。
调用形式:calloc(n,size);
返回值:基类型为void的指针。
分配成功,函数返回存储空间的首地址
否则,返回空值
说明:
调用形式中n和size的类型都为unsigned int
calloc函数用来给n个同一类型的数据项分配连续的存储空间,其中每个数据项的长度为size个字节
由calloc分配的存储空间,系统自动置初置0。
使用calloc函数分配的动态存储单元,同样必须用free函数释放。
例: int *pint;
pint=(int*)calloc(10,sizeof(int));
程序调用calloc函数在内存中分配了10个连续的int类型的存储单元,由pint指向存储单元的首地址。每个存储单元可以存放一个int型数据。
释放存储空间:
free(pint);
12.6 定义用户类型
C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。 。
C语言提供了typedef关键字,用以说明一种新的数据类型名,说明新类型名的形式为:
typedef 类型名 标识符;
在typedef语句中,“类型名”必须是在此语句之前就已经定义的类型标识符,“标识符”是一个用户自定义标识符,用作新的类型名。
typedef的作用仅仅是用作“标识符”来代表已存在的“类型名”,并未产生新的数据类型。原有的类型名依然有效。
例12.16 typedef int MYINT;
本例中,把MYINT标识符说明成了一个int类型的类型名。在此说明后,可以用标识符MYINT来定义整型变量,如:
MYINT i,j; int i,j;
由此可以看出,MYINT标识符也就是关键字int的一个别名。
对typedef的几点说明如下:
(1)习惯上将用typedef说明的新类型名用大写字母表示,以便与程序中其他标准类型标识符相区别。
(2)用typedef只是对已经存在的类型用一个用户命名的标识符来代替,并不是另外设置的一个新的类型。
(3)typedef还可以用于以下形式:
typedef char *CHARP;
CHARP p,a[10]; char *p,*a[10];
(4)typedef还可以用于说明在下一章学到的结构体类型以及共用体类型,对简化程序的书写有极大的好处。
12.7 对C语言的一些说明
C语言是一种高级语言,它可以使用接近英语国家的自然语言和数字语言作为语言的表达形式。
C语言是一种用的最广泛的语言。由于它可以直接控制计算机硬件,因此其编写的程序运行效率较高。
许多系统软件均由C语言编写,如Linux,Unix等操作系统都是由C语言编写而成的。
用C语言编写的程序,我们把它称为C语言源程序。
C语言源程序不能直接执行,它必须通过相关编译、链接程序编译并链接成可执行程序,才能在计算机上执行。
C语言源程序一般保存在一个以扩展名为.c的文本文件中,这个文件能够被我们理解,但是不能被计算机理解。我们需要通过编译程序将源文件编译成目标文件,目标文件保存在一个扩展名为.obj的文件中。
C语言的每一条指令都最终要转换成二进制的机器指令。
目标文件虽然保存了计算机能够理解的机器指令,但是它不能别计算机执行。
为了得到计算机能够理解的可执行文件,必须使用链接链接程序把这些目标文件链接成一个可执行文件。这个可执行文件的扩展名为.exe。
总之,把C语言源程序变为可执行文件,必须经过编译和链接两个步骤。
无论C语言源程序由多少个文件组成,main函数有且仅有一个。
注意:
在C语言的基本数据类型中没有专门的逻辑类型。C语言使用整数来表示逻辑值,其中0表示假,非0表示真。
为了处理数据方便,C语言提供了构造类型。例如,数组、结构体、共用体都是构造类型。构造类型又称为集合类型。
C语言由三种基本结构组成:顺序结构、选择结构和循环结构。这三种结构可以完成任何符合结构化的任务。
使用C语言可以很方便的实现算法。
算法的几个基本特征:
(1)有穷性:指算法必须在执行有限个步骤后终止。
(2)可行性:针对实际问题设计的算法,应该能够得到满意的结果。
(3)确定性:算法中的每个步骤必须有明确的定义,不允许有模棱两可的解释,也不允许有多义性。
(4)零个或多个输入:作为一个算法,可以有一个或多个输入,也可没有输入。
(5)一个或多个输出:算法的目的是为了求解,“解”就是输出。作为一个算法,至少有一个输出,也可以有多个输出。(共28张PPT)
第六章 函数
本章要点
7.1 函数的定义和返回值
7.2 库函数
7.3 调用函数
7.4 函数的声明
7.5 调用函数和被调用函数之间的数据传递
7.1 函数的定义和返回值
一、函数的概念
函数从本质上来说就是具有独立功能程序段,用于完成特定的任务并根据需要可返回一个确定的值。它有一个名字叫函数名。若有其它程序要完成该函数的功能,可通过函数名调用它,函数可多次反复调用。
引入函数主要是解决两个问题:一是为了解决代码的重复。如有一个程序段在程序中要出现很多次,每次都要写出来既非常麻烦又使程序显得很长,此时就可以把该程序段定义成一个函数,在使用该程序段的地方直接调用该函数就可以了;二是结构化、模块化编程的需要。
建立函数的过程称“函数的定义”,在程序中使用函数称“函数的调用”。
被调用的函数称“被调函数”,而调用函数的函数称“主调函数”。
在C程序中,main()可以调用任何非主函数,非主函数可以调有非主函数也可被其它函数调用但不能调用main()函数,也就是说main()只能作用主调函数。
一个C语言程序,是由一个或多个函数组成。
二、函数的定义
(1)C语言函数定义的一般形式:
存储类型说明符 函数返回值类型名 函数名(类型名 形参1,类型名 形参2,……)
{
说明部分
执行部分
}
① 存储类型说明符说明该函数是内部函数还是外部函数。
② 函数返回值类型名是用来说明该函数返回值的类型,函数返回值的类型名缺省时,返回值类型默认为int行,而不是无返回值。如果函数没有返回值,则其类型说明符应为“void”。
函数首部
③ 第一行通常称为函数首部,通过它就能知道函数的功能及调用方式。
④ 其它部分称函数体。函数体包括两个部分,说明部分和执行部分,说明部分通常用来定义在本函数中使用的变量、数组等,执行部分是函数功能的实现,通常由一系列的可执行语句构成。
(2)关于函数定义的几点说明
① C语言规定,不能在同一个函数内部再定义函数。
② 函数名和形式参数都是由用户命名的标识符。在同一程序中,函数名必须唯一,形式参数名只要在同一函数中即可,可以与其他函数中的变量同名。
③ 若省略了函数返回值的类型名,则C默认函数返回值的类型为int类型。
④ 形参可以省略,称为无参函数。在调用时不需实参。即
存储类型说明符 函数返回值类型名 函数名()
⑤ 函数体中,除形参外,用到的其它变量必须在说明部分进行定义,且可以和其他函数中的变量同名。
三、函数的返回值
函数值通过return语句返回,return语句的一般形式为:
return 表达式;或return (表达式)或 return;
以下是几点说明:
(1)return语句中表达式的值就是所求的函数值,且其类型必须与函数首部所说明的类型一致。若类型不一致,则由系统自动转换为函数值的类型。
(2)在程序执行到return语句时,流程就返回到调用该函数处,并带回函数值。在同一个函数内,可以在多处出现return语句。
(3)return语句也可以不含表达式。此时,它只是使流程返回到调用函数,并没有确定的函数值。
(4)函数体内可以没有return语句,程序就一直执行到函数末尾,然后返回调用函数,此时也没有确定的函数值带回。
四、函数定义举例
(1)定义一个函数,其功能是对三个实型参数求最大值,并返回这个值。
float max(float f1,float f2,float f3) {
float m1;
if (f1>f2)
m1=f1;
else
m1=f2;
if(m1m1=f3;
return(m1);
}
(2)无参函数定义示例:
定义一个函数,其作用是打印出五行欢迎词“Welcome you, my dear friends”。
void welc()
{
int i;  
  for(i=1;i<=5;i++) 
  printf(“Welcome, my dear friends”);
}
说明:该函数的类型说明为“void”,所以为无返回值的函数,函数中不需要return语句。
※重点提示:
要想让函数返回一个确定的值,必须通过语句“return(表达式)”来实现,其中表达式就是函数的返回值。如果没有return语句,或return语句不带表达式并不表示没有返回值,而是表示返回一个不确定的值。如果不希望有返回值,必须在定义函数时把“数据类型说明符”说明为“void”。
第二节 库函数
C语言提供了丰富的库函数,包括常用的数学函数,字符、字符串处理函数,输入输出函数等,用户在编写程序时可以直接调用这些已有的库函数。
以下是关于库函数调用的几点说明:
(1)调用C语言标准库函数时要求用include命令
对每一类库函数,都有其相应的头文件名,调用某个库函数时,用户在源程序中须用include命令包含其头文件名。一般形式为:
#include <头文件名.h>或#include “头文件名.h”
(2)标准库函数的调用
库函数调用的一般形式为:
函数名(参数表)
C语言中库函数的调用有两种方式:
①在表达式中调用。如:
x=pow(2),就是在赋值表达式中调用pow函数来求x的值。
②作为独立的语句完成某种操作。如:
printf(“***&&&\n”);
就是调用了printf函数,且调用之后加了分号,构成了一条独立的语句,完成该输出操作。
(3)各个库函数的功能、参数的个数和类型、函数值的类型都有其规定,用户在调用时根据需要选择合适的库函数,并严格按照该库函数的规则,正确的进行调用。
※重点提示:库函数的调用需要注意的是:函数的功能,函数的参数个数、类型,函数的返回值,对参数的一些特殊要求。
第三节 函数的调用
一、 函数调用的两种形式
1.有返回值的函数的调用形式
有返回值的函数调用,可以作为表达式或表达式的一部分,也可以作为一条语句。其调用形式是:
函数名(实际参数列表)
调用的结果是获得一个返回值,该返回值可以参加相应类型的计算。
如:sum(x,y)是一函数,可以由以下两种调用方式:
y=2+sum(3,4);(作为表达式的一部分,参与计算)或
sum(3,4);(单独作为一条语句,不使用返回值)
2.无返回值的函数调用形式
无返回值的函数调用只能作为一条语句,其调用形式如下:
函数名(实际参数列表);(注意,这里的分号必不可少)
如:max(a,b,c);
3.函数调用执行过程
首先为被调函数的所有形式参数分配内存,再计算实际参数的值,再一一对应地赋给相应的形式参数(对于无参函数,不做该项工作);
然后进入函数体,为函数说明部分定义的变量分配存储空间,再依次执行函数体中的可执行语句;
当执行到“return(表达式)”语句时,计算返回值(如果是无返回值的函数,不做该项工作),收回本函数中定义的变量所占用的存储空间(对于static类型的变量,其空间不收回),返回主调函数继续执行。
※重点提示:有返回值的函数调用,可以作为表达式或表达式的一部分,也可以作为一条语句。而无返回值的函数调用只能作为一条语句。
二、 函数调用时的语法要求
函数调用时,需要遵循以下几条语法要求:
(1)调用函数时,函数名必须与所调用的函数名字完全一致。
(2)实参的个数必须与形参一致。实参可以是表达式,在类型上应按位置与形参一一对应匹配。如果类型不匹配,C编译程序按赋值兼容的规则进行转换,否则,程序运行后不能得到正确的结果。
(3)C语言规定,函数必须先定义后调用(函数的返回值类型为int或char时除外)。
(4)实参可以是 C程序中函数可以直接或间接的自己调用自己,即递归调用。
※重点提示:函数调用时,必须特别注意实参和形参的类型匹配。
第四节 函数的声明
在C语言中,要调用某个函数,需对该函数进行函数声明。即遵循“先定义后调用”的规则。
函数说明的一般形式为:
类型名 函数名(参数类型1,参数类型2,…)或
类型名 函数名(参数类型1 参数名1,参数类型2 参数名2,…)
以下是函数说明的几条规则:
(1)调用系统函数时,需要在程序的开头包含相应的头文件。但scanf()和printf()等少数的几个函数不需要。
(2)当被调函数定义在主调函数之前时,对被调函数的说明可以省去,也可以不省。
(3)当被调函数的返回值类型是整形或字符型时,不管其定义在主调函数之前还是之后,对被调函数的说明都可以省去,也可以不省。
(4)其它情况一律需要对被调函数进行说明。
(5)当被调函数和主调函数在同一个程序文件中,可在主调函数的函数体说明部分对被调函数进行说明,说明格式有两种,如下:
类型名 被调函数名() 或
类型名 被调函数名(形式参数列表)
(6)函数说明可以是一条独立的语句,也可以与普通变量一起出现在同一个定义语句中。如double sub(float,double)和double x,y,sub(float,double)都是合法的。
※重点提示:内部函数的说明包含在.h文件中,故调用某个内部函数,必须包含相应的头文件。
二、函数声明的位置
在所有函数的外部,在声明之后的任何位置都可以调用该函数。
在主调函数的内部,此时的函数声明和普通变量定义一样,出现在调用函数的函数体的说明部分。此时只能在调用函数内部才能识别该被调函数,而不能被其他函数调用。
※重点提示:函数调用时,必须特别注意实参和形参的类型匹配。
第五节 函数的参数传递
一、形参和实参的概念
每个函数一般都能完成一定的功能,可以形象地把函数看成一个加工厂,该加工厂对材料进行加工,最后生产出产品。而加工厂加工的材料,称为“函数的参数”,而加工出来的产品称为“函数的返回值”(有的函数没有返回值)。
在定义函数时,只是形式化地说明函数加工的对象,这种对象并不实际存在,就象建立一个本材加工厂时只是说明该加工厂把木材加工成木板,但并没有实际的木材,把这种参数称为“形式参数”。
当程序段要调用该函数完成指定的功能,就需要给它实际的材料,以便加工出“产品”。就象一个人要把木材加工成木板,就必须把实际的木材送到加工厂加工一样,函数调用时传入的参数称为“实际参数”。
二、数据传递的形式
(1)C语言中,调用函数和被调函数之间的数据可以通过三种方式进行传递:
① 实际参数和形式参数之间进行数据传递。
② 通过return语句把函数值返回调用函数。
③ 通过全局变量。
(2)参数值的传递
① 实参的个数与类型应与形参一致,否则将会出现编译错误。
② 实参可以是常量、变量、数组元素和表达式,但如果在被调函数中有取形参地址或给形参赋值的语句,则对应的实参必须是变量和数组元素。
③ 定义函数时定义的形参并不占用实际的存储单元,只有在被调用时才由系统给它分配存储单元,在调用结束后,形参所占用的存储单元被回收。
④到目前为止,函数最多只能返回一个函数值。
⑤C语言规定,函数间的参数传递是“值传参”,即单向传递,实参可以把值传给形参,但形参的值不能传给实参,也就是说对形参的修改是不会影响到对应的实参。
此处要注意一个“假象”,数组名作为参数传递的是数组的首地址,严格地说其传递的也是“值(地址)”。后面所说的指针变量作为参数也是如样,传的也是值(地址值)。
※重点提示:主调函数在调用函数时,需要把相应的实际参数传给相应的形式参数,实际参数的个数和类型要和形式参数的个数和类型必须一致。(共32张PPT)
第4章 循环结构
本章要点:
4.1 while语句
4.2 do-while语句
4.3 for语句
4.4 循环结构的嵌套
4.5 break语句和continue语句
4.1.1 while循环的一般形式
由while语句构成的循环也称“当”循环,While语句的一般形式为:
While(表达式)
  循环语句;
以下是几点说明:
1.while是C语言的关键字。
2.while后的表达式,可以是C语言中任意合法的表达式,通常为关系表达式或逻辑表达式,但也可以是其他运算表达式。当表达式的值为零时,表示条件为假;非零时,表示条件为真。
3.循环体可以是一条简单可执行语句,也可以是复合语句。
4.如果第一次计算时表达式的值就为0,则循环语句一次也不被执行,流程直接跳过While语句,执行下一条语句。
 ※重点提示:while型循环语句中循环体的执行次数可以从0到无穷。若第一次计算表达式的值为0,则循环体执行0次;若表达式恒为真,则陷入死循环。
4.1.2 while循环的执行过程
while循环的执行过程是:
计算while后表达式的值,
当值为非零时,执行循环体中的语句;
当值为零时,退出while循环。
例如:计算1+2+3+…+100。
main()
{ int i,sum=0;
i=1;
while(i<=100)
{
sum+=i;
i++;
}
printf("d\n",sum);}
程序运行结果为:5050
(1)循环语句中“sum+=i”相当于“sum=sum+i”,建议读者采用前一种写法,因为它不仅比后一种写法编码短,而且更能体现C语言的特色。
(2)注意,在循环体中应有使循环趋于结束的语句。
例如,本例中的“i++;”,每循环一次i的值就增加1,当i>100时,循环条件就不满足,循环到此结束。如果无此语句,则i的值一直不变,循环永不结束,这就称为“死循环”。在程序设计中,是不允许死循环出现的。
※重点提示:在循环体中应有使循环趋于结束的语句。在程序设计中,是不允许死循环出现的。
第二节 do-while语句
一、do-while语句的一般形式
do{
循环语句;
}
while(表达式);
以下是几点说明:
1.do是C语言的关键字,必须和while联合使用。
2.在while(表达式)后的分号“;”不可丢,它表示do-while语句的结束。
3.while后括号中的表达式可以是任意合法的表达式,由它来控制循环是否执行。
4.do-while之间的循环体可以是一条可执行语句也可以是由“{}”构成的符合语句。
二、 do-while循环的执行过程
  do-while语句的执行过程是:
(1)先执行一次指定的循环语句。
(2)然后判断表达式的值,若为真(非0),重复(1),否则跳出循环。
 do while语句的特点是:先执行语句,后判断表达式的值。故do While语句又称“直到型”循环结构。由于是先执行后判断,因此do while语句的循环语句至少被执行一次。
 注意:while圆括号后面有一个分号“;”,书写时不能丢。
例如:用do while循环结构来计算1+2+3+…+100。
例4.3:
main()
{ int i,n=0;
i=0;
do
{
i++;
++i;
}
while(n!=0);
printf(“%d",i);
}
※重点提示:do-while语句先执行语句,后判断表达式的值。故do-while语句又称“直到型”循环结构。由于是先执行后判断,因此do-while语句的循环语句至少被执行一次。
第三节 for语句
一、for语句的一般形式
for语句的一般形式为:
for(表达式1;表达式2;表达式3)
  循环语句;
以下是几点说明:
1.表达式1一般为循环变量赋初值;表达式2一般为关系表达式或逻辑表达式,用于执行循环的条件判定;表达式3一般为赋值表达式或自增、自减表达式,用于修改循环变量的值。
2. for是C语言中的关键字。
3. 循环体语句如果只有一个,可以不加花括号;如果循环体语句超过一个,则必须用花括号括起来组成符合语句。
4. 圆括号内的三个表达式基本可以缺省,但是“;”不能省。循环体语句可以由一个空语句组成,表示不做任何动作。
二、for循环执行过程
for语句的执行过程是:
① 先计算表达式1的值。
② 再计算表达式2的值,若其值为真,则执行循环体一次;否则跳转第⑤步。
③ 然后计算表达式3的值。
④ 转回上面第②步。
⑤ 结束循环,执行for语句下面一个语句。
三、有关for语句的说明
(1)圆括号中的表达式1往往是对与循环有关的变量赋初值;表达式2类似于while语句中圆括号内的表达式,控制循环;表达式3通常是循环变量的自增,自减操作以保证不会进入死循环。
(2)for循环的一般形式可以改写为等价的while循环。
表达式1;
while(表达式2)
{
循环体语句;
表达式3;
}
(3)for语句的一般形式中的“表达式1”可以省略,即:
for(;表达式2;表达式3)
   循环语句;
但注意省略表达式1时,其后的分号不能省略。此时,应在for语句之前给循环变量赋初值。
(4)如果省略表达式2,即:
for(表达式1;;表达式3)
   循环语句;
则表示表达式2的值始终为真,循环将无终止地进行下去。
例如:
for(i=1;;i++)
printf("%d",i);
将无限循环输出1,2,3,4,5,6,……
(5)如果省略表达式3,即:
for(表达式1;表达式2;)
   循环语句;
此时,也将产生一个无穷循环。因此,程序设计者应另外设法保证循环能正常结束,可以将循环变量的修改部分(即表达式3)放在循环语句中控制。例如:for(i=1;i<=100;)
{ sum+=i;
i++;
}
上述for语句中没有表达式3,而是将表达式3(i++)放在循环语句中,作用相同,都能使用循环正常结束。注意表达式2后面的分号不能省略。
(6)也可以同时省略表达式1和表达式3,即:
for(;表达式2;)
   循环语句;
也即省略了循环的初值和循环变量的修改部分,此时完全等价于while语句。
(7)同时省略表达式1、表达式2和表达式3,即:
for(;;)
   循环语句;
相当于赋循环变量的初值,循环控制条件始终为真,不修改循环变量,故循环将无终止地进行下去。
(8)在for语句中,表达式1和表达式3不仅可以使用简单表达式,也可以使用逗号表达式,即包含一个以上的简单表达式,中间用逗号间隔。在逗号表达式内按自左至右求解,整个表达式的值为其中最右边的表达式的值。例如:
for(i=1;i<=100;i++,sum=sum+i)
相当于
for(i=1;i<=100;i++)
   sum=sum+i;
(9)在for语句中,表达式一般为关系表达式或逻辑表达式,但也可以是其他表达式(如字符表达式、数值表达式)。
(10)for语句的循环语句可以是空语句。
例子:for(i=1;i<=1000;i++);
注意以上语句最后的分号不能省略,它代表一个空语句。
例如:用for循环结构来计算1+2+3+…+100。
main()
{
int i,sum=0;
for(i=1;i<=100;i++)
sum+=i;
printf("%d\n",sum);
}
程序运行结果为:5050
 ※重点提示:for语句中的表达式可以部分或全部省略,但两个分号不能省略,且三个表达式均省略时,循环将会无限制执行,而形成死循环。因此,编写程序时,在for后面的一对圆括号内,应只含有能对循环进行控制的表达式,其它的操作尽量放在循环体内完成。
第四节 循环结构的嵌套
1.循环嵌套的形式
  在一个循环体内又完整地包含了另一个循环,称为循环嵌套。循环的嵌套可以是多层,但每一层循环在逻辑上必须是完整的。例如以下几种形式的二重嵌套。
2.关于循环嵌套的几点说明
(1)使用嵌套时,应注意一个循环结构应完整地嵌套在另一个循环体内,不允许循环体间交叉。例如以下循环结构是不正确的:
while()
{…
do
{…}
}
while();
(2)除了上述二重嵌套外,还可以有三重嵌套、四重嵌套等多层嵌套。
(3)嵌套的外循环和内循环的循环控制变量不得同名,但并列的内、外循环允许有同名的循环控制变量。例如以下为合法的循环结构:
for(j=1;j<=10;j++)
{ …
for(i=1;i<=10;i++)
{…}
  for(i=1;i<=10;i++)
{…}
  …
}
3.循环嵌套程序举例
利用双层for循环结构打印出9×9乘法表。
main()
{
int i,j;
for(i=1;i<10;i++)
printf("%5d",i);
printf("\n");
for(i=1;i<=46;i++)
printf("-");
printf("\n");
for(i=1;i<10;i++)
{
for(j=1;j<=9;j++)
printf("%5d",i*j);
printf("\n");
}
}
程序运行结果为:
1 2 3 4 5 6 7 8 9
--------------------------------------------------------------------
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
第五节 break语句和continue语句
一、break语句
在第四章中我们介绍过,使用break语句可以使流程跳出switch语句体,在循环结构中,也可以使用break语句使流程跳出本层循环体,从而提前结束本层循环。
  break语句的一般形式为:
 break;
关于break语句有以下几点说明:
(1)break语句不能用于循环语句和switch语句之外的任何其他语句中。
(2)break语句只能用于循环体内,不能用在循环语句上。如for(i=1;i<10;break,i++)是不正确的。
(3)break语句只能跳出一层循环,即从当前循环层中跳出。如果要跳出多层循环,可使用goto语句。
例如:设计一个程序,求能同时满足除以3余1、除以5余3、除以7余5、除以9余7的最小正整数。
main()
{
int i;
for(i=1;;i++)
if(i%3= =1&&i%5= =3&&i%7= =5&&i%9= =7)
break;
printf("%d\n",i);
}
程序运行结果为:313
由于此题无法确定循环的条件和循环次数,因此应采用无限循环配以break语句的方法。
※重点提示:当break语句出现在循环体中的switch语句体内时,其作用只是跳出该switch语句体,当break语句出现在循环体中,但并不在switch语句体内时,则在执行break后,跳出本层循环。
二、continue语句
1.continue语句的一般形式
continue语句的作用是结束本次循环,即不再执行循环体中continue语句之后的语句,而是跳转到循环的开始处,进行下一次是否执行循环的判定。
它的一般形式为:
continue;
2.关于continue语句的几点说明
(1)continue语句只是结束循环结构中的本次循环,并非跳出整个循环过程。具体说:对while和do~while语句,遇continue语句后,转向执行while之后圆括号内的条件表达式的判断;对for语句,遇continue语句后,转向执行表达式3。
(2)执行continue语句并没有使整个循环终止。
(3)continue语句与break语句有本质的区别:continue语句只是结束本次循环,而不终止整个循环的执行;而break语句的作用则是强制终止整个循环过程。
三、continue语句与break语句的区别
例如:打印出数字0~10,但跳过(即不输出)数字5。
main()
{
int i;
for(i=0;i<=10;i++)
{
if(i==5)
continue;
printf("%5d",i);
}
}
程序运行结果为:
0 1 2 3 4 6 7 8 9 10
如果在本例中将第7行的“continue;”语句,改为“break;”语句,则输出结果为:
  0 1 2 3 4
  可以清楚地看出break语句是终止整个循环过程,它与continue语句作用是截然不同的。
 ※重点提示:当break语句出现在循环体中的switch语句体内时,其作用只是跳出该switch语句体,当break语句出现在循环体中,但并不在switch语句体内时,则在执行break后,跳出本层循环。(共44张PPT)
第3章 选择结构
本章要点:
3.1 关系运算和逻辑运算
3.2 if语句
3.3 条件运算符和条件表达式
3.4 switch语句
3.5 语句标号和goto语句
3.1 关系运算和逻辑运算
一、C语言中的逻辑值
C语言中的逻辑值有两个:“真(true)”和“假(false)”。
C语言编译系统在给出逻辑运算结果时,以数值1代表“真”,以0代表“假”,但在判断一个量是否为“真”时,以0代表“假”,以非0代表“真”。
二、关系运算符
关系运算实际上就是“比较运算”,将两个数进行比较,判断比较的结果是否符合指定的条件。
表3.1 C语言中的关系运算符
运算符 名称 优先次序
< 小于 优先级相同(高)
<= 小于或等于
> 大于
>= 大于或等于
==(连续两个=) 等于 优先级相同(低)
!= 不等于
关于关系运算符的说明:
(1)表3-1中前4种运算符(<、<=、>、>=)的优先级别相同,后两种也相同。前4种优先级别高于后两种。如,“>”优先级别高于“!=”。
(2)关系运算符与算术运算符、赋值运算符的优先级关系如下:
算术运算符(高)→关系运算符(中)→赋值运算符(低)
(3)关系运算符的结合方向是“自左向右”。
三、关系表达式及关系表达式的值
用关系运算符将两个表达式(算术表达式、关系表达式、逻辑表达式、赋值表达式、字符表达式等)连接起来的式子,称关系表达式。例如,a>=b,a>= =c等。
在C语言中,关系表达式的结果值为1或0。当关系表达式成立时,其结果值为1,否则,其值为0。若a=10,b=6,表达式a>=b为“真“,其值为1。
当关系运算符两边的值类型不一致时,系统将自动进行转换。转换规则与双目运算中的类型转换规则相同。
关系运算示例:试求x=5>3>1的值。
分析:x=5>3>1相当于:
 x=(5>3)>1
 =1>1
 =0 故x的值为0。
关系表达式主要用于选择结构中的条件判断。
※重点提示:表示“等于”关系的运算符“= =”和“不等于”关系的运算符“!=”与数学中的表示方法不同。在使用它们时请予以注意,以免写错关系运算符而导致错误的结果。
例如,欲判断x是否等于0,如果写成:x=0就不是判断x是否等于0的含义了,而成了把0赋值给变量x。正确的写法应为:x= =0。
四、逻辑运算符和逻辑表达式
1.C语言中的逻辑运算符
关系表达式只适于描述单一的条件,对于较复杂的复合条件就需要将若干个关系表达式连接起来才能描述。
例如,描述“x大于0且不等于2”,就需要将两个关系表达式x>0和x!=2连接起来:x>0&&x!=2。其中“&&”是C语言中的逻辑运算符。
C语言提供了三种逻辑运算符,如表3.2所示。
表3.2 C语言中的逻辑运算符
运算符 名称 示例 示例说明 说明 优先次序
! 逻辑非 !a a非 单目运算符 高

&& 逻辑与 a&&b a与b 双目运算符
|| 逻辑或 a||b a或b 双目运算符
关于逻辑运算符的说明:
(1)逻辑运算符的优先次序如下:
!(高)→&&(中)→||(低)
(2)逻辑运算符具有自左向右的结合性
(3)逻辑运算符与赋值运算符、关系运算符、算术运算符的优先次序如下:
!(逻辑非)→算术运算→关系运算→&&(逻辑与)→||(逻辑或)→赋值运算
例如:
a>=b) && (x>y) 可以写为:a>=b&&x>y
(a= =b)||(x= =y) 可以写为:a= =b||x= =y
(!a)||(a>=b) 可以写为:!a||a>=b
五、逻辑表达式和逻辑表达式的值
用逻辑运算符将关系表达式或逻辑值连接起来的式子称逻辑表达式。
一个逻辑表达式中可以包含多个逻辑运算符,如x>y&&a<=b-!c
参与逻辑运算的量为非零值或整数零,非零值和整数零分别表示运算量为“真”或“假”。
逻辑运算的结果值,即逻辑表达式的值应该是一个逻辑值“真”或“假”,即为“1”或“0”。
表3.3为逻辑运算的运算规则表。用它表示当a和b的值为不同组合时,各种逻辑运算所得到的值。
a b !a !b a && b a || b
非0 非0 0 0 1 1
非0 0 0 1 0 1
0 非0 1 0 0 1
0 0 1 1 0 0
以下是关于逻辑运算的几点说明:
(1)参与逻辑运算的量不但可以是0和1,或者是0和非零的整数,也可以是任何类型的数据。
如字符型、实型或指针型等。但最终以0和非0来判定它们属于“真”或“假”。
(2)在逻辑表达式求解中,并不是所有逻辑运算符都需要执行,有时只需执行一部分运算符就可得出逻辑表达式的最后结果。
例如x&&y&&z,只有x为真时才需要判断y的真假,若x为假,则立即得出整个表达式为假。
(3)在处理一个复杂表达式时,对于表达式中不同位置上出现的数值,应区分哪些是作为数值运算或关系运算的对象,哪些作为逻辑运算的对象。
例如求下列表达式的值:
  8>=5&&2||6<5-!0
 计算步骤如下:
  ① 表达式自左至右扫描求解。因为关系符优先于&&,所以首先处理8>=5,在关系运算符>=两侧的8和5作为数值参加关系运算,8>=5的值为1。
  ② 再进行1&&2||6<5-!0运算。自左至右扫描求解,&&两侧的1和2作为逻辑运算对象,因为参加运算的皆为非0,故结果为1。
  ③ 接下来进行1||6<5-!0运算。6的左侧为||运算符,右侧为<运算符,根据优先级别,应先进行<的运算。即先进行6<5-!0运算。现在5的左侧为<运算符,右侧为-运算符,而-优先于<,因此,应先进行5-!0的运算。又由于!的优先级别最高,故先进行!0的运算,得结果1。
④ 然后进行5-1运算。得结果为4。
⑤ 下面再进行6<4运算。得结果为0。
⑥ 最后进行1||0运算。得到1。
故表达式的值为1。
※重点提示:若“&&”的左边的运算对象的值为0,则不再对右边的运算对象进行运算,直接得出整个表达式的值为0;若“||”的左边的运算对象为1,则不再对右边的运算对象进行运算,直接得出整个表达式的值为1。
第二节 if语句
一、if语句结构
if语句是选择结构的一种形式,又称为条件分支语句。
它的流程控制方式是:根据给定的条件进行判定,由判定的结果(真或假)决定执行给出的两种操作之一。
C语言中提供了三种形式的if语句:不含else的if语句、if~else语句和if~else~if语句。
(1)语句形式如下:
if(表达式) 语句
圆括号中的表达式一般是关系表达式或逻辑表达式,用于描述选择结构的条件。
但也可以是其他任意的数值类型表达式,包括整型、实型、字符型等。如果在if子句中需要多个语句,则应该使用大括号“{}”把一组语句组成复合语句。
(2)执行过程
 首先计算if后面圆括号中的表达式的值,若为非0值,则执行语句1,然后脱离本选择结构,继续执行if语句的下一个语句;否则不执行语句1,直接转去执行if语句的下一个语句。
图3-1 if语句程序流程图
2.if-else语句
(1)语法形式如下:
if(表达式) 语句1;
else 语句2;
语句1称为if子句,语句2称为else子句,这些子句只允许是一条语句,若需要多条语句时,则应该使用“{}”将这些语句括起来组成复合语句。
另外要注意,else不是一条独立的语句,它只是if语句的一部分,因此在程序中else必须与if配对。
图3.2 if-else语句执行过程
(2)执行过程
首先计算圆括号内的表达式值,若为非0值,则执行语句1,然后脱离本选择结构,继续执行if语句的下一个语句;否则执行语句2,然后脱离本选择结构,继续执行if语句的下一个语句 。
3.if-else-if语句
(1)语法形式如下:
if(表达式1) 语句1;
else if(表达式2) 语句2;
else if(表达式3) 语句3;
 ……
else if(表达式n-1) 语句n-1;
else 语句n;
(2)执行过程
  若表达式1为真,执行语句1,否则进行下一步判断;若表达式2为真,执行语句2,否则进行下一步判断;……;最后所有表达式都为假时,执行语句n。if-else-if语句的执行过程如图3-3所示。
※重点提示:使用if~else~if语句时,请注意各个分支结构的条件一定要按照某种顺序书写。这样做不仅使程序条理清晰,而且能使条件书写简捷,不易出错。
四、嵌套的if语句
 在if语句中又包含一个或多个if语句称为if语句的嵌套。
以下是C语言中if~else语句的二重嵌套的三种形式。
(1)在if子句中嵌套具有else子句的if语句,如图3.4(a)所示
(2)在if子句中嵌套不含else子句的if语句,如图3.4(b)所示
(3)在else子句中嵌套if语句,如图3.4(c)所示。
图3.4 嵌套的if语句结构
如果把嵌套写成如下形式:
 if(表达式1)
  if(表达式2)
    语句1;
 else
  语句2;
程序设计者把else与第一个if书写在同一列,试图以此表示它们的匹配的。但是,根据if和else配对规则,else却是与第二个if配对(离它最近)。
C语言不是以书写格式来分隔语句的,而是由逻辑关系决定的。
当if和else的数目不等时,为了保险起见,也可以用一对大括号括起来确定配对关系。
可作如下处理:
if(表达式1)
  {
if(表达式2)
   语句1;
}
else
  语句2;
例如:从键盘输入两个整数,试比较它们的大小。
main()
{ int a,b;
printf("Please enter two integers:\n");
scanf("%d,%d",&a,&b);
if(a!=b)
if(a>b)
printf("%d>%d\n",a,b);
else
printf("%d<%d\n",a,b);
else
printf("%d= =%d\n",a,b);
}
 程序运行结果为:
 Please enter two integers:
 3,57<回车>
 3<57
 Please enter two integers:
 9,-6<回车>
 9>-6
 Please enter two integers:
 456,456<回车>
 456= =456
 ※重点提示:在嵌套内的if语句既可以是if语句形式也可以是if~else语句形式,这就会出现多个if和多个else重叠的情况。此时要特别注意if和else的配对问题。if和else配对规则为:else总是与它前一个最近的if配对。
第三节 条件运算符和条件表达式
对于有些选择分支结构,C语言另外还提供了一个特殊的运算符——条件运算符。
由此构成的表达式也可以形成简单的选择结构,这种选择结构能以表达式的形式内嵌在允许出现表达式的地方,使得可以根据不同的条件使用不同的数据参与运算。
1.条件运算符
 条件运算符由两个符号“ ”和“:”组成,要求有3个操作对象,称三目(元)运算符,它是C语言中唯一的三目运算符。
2.条件表达式
 条件表达式的一般形式为:
 表达式1?表达式2:表达式3
以下是几点说明:
(1)通过情况下,表达式1是关系表达式或逻辑表达式,用于描述条件表达式中的条件,表达式2和表达式3可以是常量、变量或表达式。
(2)条件表达式的执行顺序为:先求解表达式1,若值为非0,表示条件为真,则求解表达式2,此时表达式2的值就作为整个条件表达式的值;若表达式1的值为0,表示条件为假,则求解表达式3,表达式3的值就是整个条件表达式的值。
(3)在程序中,通过把条件表达式的值直接赋予某个变量。例如:
 min=(a 执行结果就是将条件表达式的值赋予变量min,即将a和b二者中较小的赋给min。
(4)条件表达式的优先级别仅高于赋值运算符,而低于前面介绍过的所有运算符。
(5)条件运算符的结合方向为“自右至左”。
(6)条件表达式允许嵌套,即允许条件表达式中的表达式2和表达式3又是一个条件表达式。例如:x>0 1:x<0 -1:0
  上述条件表达式中,表达式3部分又是一个条件表达式。根据条件表达式的结合性,上述条件表达式等价于:x>0 1:(x<0 -1:0)。
 其作用是判断x的符号情况。当x为正数时,该条件表达式的值为1;当x为负数时,该条件表达式的值为-1;当x为0时,该条件表达式的值为0。
(7)条件表达式不能取代一般的if语句,仅当if语句中内嵌的语句为赋值语句(且两个分支都给同一变量赋值)时才能代替if语句。
(8)表达式1、表达式2、表达式3的类型可以不同。此时条件表达式的值的类型为它们中较高的类型。
※重点提示:条件表达式的执行顺序为:先求解表达式1,若值为非0,则此时表达式2的值就作为整个条件表达式的值;反之,则表达式3的值就是整个条件表达式的值。
第四节 switch语句
if语句处理两个分支,处理多个分支时需使用if~else~if结构,但如果分支较多,则嵌套的if语句层数就越多,程序不但冗长而且理解也比较困难。
C语言又提供了一种专门用于处理多分支结构的条件选择语句,称为switch语句,又称开关语句。
使用switch语句直接处理多个分支。
一般形式为:
 switch(表达式)
{ case 常量表达式1:
 语句1;  break;
 case 常量表达式2:
 语句2;  break;
 ……
 case 常量表达式n:
 语句n;  break;
  default:
 语句n+1;
    break;}
说明:
(1)switch、case、break、default均为C语言的保留字。
(2)swich的表达式通常是一个整型或字符型变量,也允许是枚举型变量,其结果为相应的整数、字符或枚举常量。
(3)常量表达式又称为开关常数,必须是与表达式对应一致的整数、字符或枚举常量。
(4)语句1至语句n,可以是简单语句,也可以是复合语句。
(5)在关键字case和常量表达式之间一定要有空格。
 ※重点提示:在switch语句中,default以及break是可以有也可以没有的,且case语句标号后的语句也可以省略不写。
图3.6 switch语句的执行过程
例如,下面的switch语句可以根据键入考试成绩的等级(grade)输出百分制分数段:
键入'A',输出85~100
键入'B',输出70~84
键入其他任意字符,输出error
switch (grade)
{ case'A':
printf("85~100\n");
break;
case'B':
printf("70~84\n");
break;
default:
printf("error\n");}
 注意:
(1)在switch~case语句中,各个case常量表达式不一定要按其值的大小顺序来书写语句,但要求各个case后的常量表达式必须是不同的值,以保证分支选择的唯一性。例如:
switch (ch)
 {case'A':
语句1; break;
case'B':
语句2; break;
case'A':
语句2;  break;
default: 语句3;
}
该例中前2个case语句都是合法的,当变量ch取'A'、'B'时,分别执行语句1、2,但最后一条case语句与第一条case语句的常量表达式的值相同,这是不允许的。
(2)如果在case后面包含多条执行语句时,也不需要加大括号,进入某个case后,会自动顺序执行本case后面的所有执行语句。
(3)default总是放在最后,这时,default后不需要break语句。
(4)default部分也不是必须的,如果没有这一部分,当switch后面圆括号中表达式的值与所有case后面的常量表达式的值都不相等时,则不执行任何一个分支直接退出switch语句。此时,switch语句相当于一个空语句。
(4)在switch~case语句中,多个case可以共用一条执行语句,例如:
case 'A':
case 'B':
printf(">60\n");
break;
在A、B两种情况下,均执行相同的语句,即输出“>60”。
(5)在switch语句中,只有最后一个分支(default)中的break语句的存在于否不影响执行结果。因为没有这个break语句,执行该分支的所有语句后也会自动退出switch语句。而其余各分支中的break语句有与无时的流程是完全不同的。
第五节 goto语句
一、 语句标号
在C语言中,语句标号不必特殊加以定义,标号可以是任意合法的标识符,当在标识符后面加一个冒号,该标识符就成为一个语句标号。
注意,C语言中,语句标号必须是标识符,而不能是数值常量形式,标号可以和变量同名。
通常,标号用作goto语句的转向目标。
如:goto state;
 C语言中,可以在任何语句前加上语句标号,如:
 state: printf(“end\n”);
二、 goto语句
 goto语句称为无条件转向语句,goto语句的一般形式为:
  goto 语句标号;
  goto语句的作用是把程序的执行转向语句标号所在的位置,这个语句标号必须与此goto语句同在一个函数内。
 ※重点提示:滥用goto语句将使得程序的流程毫无规律,可读性差,对于初学者来说应尽量不采用。(共12张PPT)
第十章 数组与函数
主要内容
10.1 一维数组与函数
10.2 二维数组与函数
10.1 一维数组与函数
10.1.1 一维数组元素作实参
调用函数时,数组元素可以作为实参传送给形参,每个数组元素实际上代表内存中的一个存储单元,因此对应的形参必须是类型相同的变量。
数组元素的值可以传送给该变量,在函数中只能对该变量进行操作,而不能直接引用对应的数组元素。
10.1.2 一维数组名作实参
数组名作为函数的参数,在函数间传递的并不是整个数组,而是数组的首地址,换句话说,就是形参数组与实参数组指的是同一个数组。
因此,在被调函数中改变了形参数组的某元素值,其对应的实参数组元素值也跟着发生改变。当数组名作为形参时,其对应的实参可以是指针变量、数组名、地址表达式。
在函数中,可以通过此指针变量来引用调用函数中的对应的数组元素,从而达到对调用函数中对应的数组元素进行操作。
通常对应函数的首部可以是以下三种格式:
(1)fun(int *a)
(2)fun(int a[])
(3)fun(int a[N])
  
例10.1 有以下程序
int fun(int *x,int n)
{
int i,sum=0;
for(i=0;ireturn sum;
}
main()
{ int a[]={1,2,3,4,5},s=0;
s=fun(a,5);
printf(“%d\n”,s);
}
15
注意:
(1)形参x的基类型必须与主函数中的数组a的类型一致。
(2)在fun函数中引用主函数数组a中的元素时,只能使用fun函数中指向主函数数组的指针来引用主函数中的数组元素。
(3)用户定义函数fun的首部也可以写成fun(int x[],int n)或fun(int x[5],int n);
10.1.3 一维数组元素地址作实参
一维数组元素的地址作为函数的实参与一维数组名作为函数的实参有相似之处,它们都是地址值,对应的形参也应当是基类型相同的指针变量。
例10.2 有以下程序
void sum(int p[])
{
p[0]=p[-1]+p[1];
}
main()
{
int a[]={1,2,3,4,5,6,7,8,9,10};
s=fun(&a[2]);
printf(“%d\n”,a[2]);
}
6
10.2 二维数组与函数
10.2.1 二维数组名作实参
当二维数组名作为实参时,对应的形参必须是一个行指针变量。例如:
#define M 5
#define N 3
main()
{ double s[M][N];

fun(s);
… }
则fun函数的首部可以是以下三种形式之一:
(1)fun(double (*a)[N])
(2)fun(double a[][N])
(3)fun(double a[M][N])
注意:
无论哪种方式,系统都将把a处理成一个行指针。数组名传递给函数的是一个地址值,因此,对应的形参也必定是一个类型相同的指针变量,在函数中引用的将是主函数中的数组元素,系统只为形参开辟一个存放地址的存储单元,而不可能在调用函数时为形参开辟一系列存放数组的存储单元。
10.2.2 指针数组作实参
当指针数组作为实参时,对应的形参应当是一个指向指针的指针。
例如:
#define M 5
#define N 3
 main()
{ double s[M][N], *ps[M];

for(i=0;ifun(s);

}
则fun函数的首部可以是以下三种形式之一:
(1)fun(double *a[M])
(2)fun(double *a[])
(3)fun(double **a)
  因为传送的是一维指针数组,所以形参的定义形式与一维数组名作实参的形式类似。(共30张PPT)
本章重点
13.1 结构体类型
13.2 共用体
第13章 结构体、共用体和用户自定义类型
13.1 结构体类型
C语言的基本数据类型有整型,实型,字符型等,在实际问题中,把一个对象抽象成数据来表示时,往往需要一组不同基本数据类型的组合。
例如,一辆银灰色的车牌号为湘A00001的雪弗兰轿车以80km/h的速度行驶。
在这里由车牌、车名、型号、车速共同构成了对一辆轿车的描述,在数据处理中,这四个属性应该当作一个整体来处理。但它们不属于同一类型,无法用数组来容纳,而用单个变量来分别代表各个属性,又难以反映出它们的内在联系。
C语言提供结构体(structure)数据结构,将不同数据类型、但相互关联的一组数据,组合成一个有机整体使用。
13.2.1 结构体类型的说明
结构体类型说明的一般形式是:
struct 结构体标识名
{ 类型名1 结构成员名表1;
类型名2 结构成员名表2;

类型名n 结构成员名表n;
};
结构类型说明的形式如下:
strcut 结构类型名
{ 数据类型 成员1;
  数据类型 成员2;
    ……  ……  ;
  数据类型 成员n;
};
也可以表述成如下形式:
struct 结构体名
{ 分量表 };
一、 结构体类型的说明
(1) struct是结构类型关键字,“结构体名”和“成员名”都是用户自定义的标识符。
(2)结构体名用来唯一标识该结构体,可以省略不写。
(3)结构体中所含的成员个数,可以是任意多个。结构体中的成员名可以和程序中的其它变量同名,不同结构中的成员也可以同名。
(4)结构体要以分号结束
struct student
{ char name[20];
char sex;
int age;
float score;
};
例如:定义学生档案信息结构类型:
成员表列
struct是类型关键字
后分号不能少,表明语句结束。
结构体名
name
sex
age
score
struct date struct student
{ int month; { int num ;
int day; char name[20]
int year; char sex ,
}; int age ;
struct date birthday;
char addr[30];
} ;
num
name
sex
age
birthday
month
day
year
addr
结构类型student的 “birthday”数据项可以是一个已经定义的结构类型date
num
name
sex
age
birthday
month
day
year
addr
结构体可以嵌套说明,即在结构体说明里再说明另外一个结构
13.1.2 结构体类型的变量的定义
定义结构类型变量由以下四种方法:
1.直接在结构体类型说明之后定义结构体变量。
这种形式说明的一般形式为:
struct 结构名
{成员表列
}  变量名表列;
例如:
struct student
{ char name[10]; char sex;
int age; float score;
} s1, *ps, stu[3];
2.直接说明结构变量。即在结构变量定义中省去了结构类型名,而直接给出结构变量。一般形式为:
struct
{成员表列
} 变量名表列;
例如以上结构中把student省略,
struct
{ char name[10]; char sex;
int age; float score;
} s1, *ps, stu[3];
3.先定义结构,再说明结构变量。例如:
struct student
{ char name[10]; char sex;
int age; float score;
};
struct student s1, *ps, stu[3];
4.使用typedef说明一个结构体类型名,再用新类型名来定义变量。例如:
 typedef struct
{ char name[10]; char sex;
int age; float score;
} STU;
 STU s1,*ps, stu[3];
  此处STU是一个具体的结构体类型名,它能够唯一的标识这种结构体类型。因此,可用它来定义变量,不可再写关键字struct。
13.2.3 结构体类型变量的赋值
(1)结构体变量赋初值
和其他类型变量一样,对结构变量可以在定义时进行初始化赋值。
struct Student
{ int number;
char name[31];
short age;
char sex;
char grade;
} boy2, boy1 = {1361, "zhang", 12, 'm', '5'};
(2)结构体数组赋初值
规则和给数组赋值一样,只是结构体数组中的每个元素都是一个结构体变量,所以赋初值的时候,都必须吧每个数组元素的各个成员的值依次放在一对花括号中,以便区分各个元素。
struct Student
{ char name[31];
short age;
char sex;
float score;
} stu[3]= {{“Jim”,’M’, 20,59},{“Sam”,’W’,21,78},{“Bill”,’M’,22,85}};
13.2.4 结构体类型的变量的引用
1. 对结构体成员的引用
引用结构变量成员的三种形式是:
结构变量名.成员名
结构体指针变量 成员名
(*结构体指针变量).成员名
 
struct student
{ int num ;
char name[20]
char sex ,
int age ;
struct date
{
int year;
int mon;
int day;
} birthday;
float grade[3];
float score;
}s1,*ps,s[3];
ps=&s1;
(1)若要引用结构体变量s1中的age成员,可写作:s1.age;ps->age;(*ps).age;
(2)引用结构体数组元素的成员,可写成s[0].age.在这里,s数组的每一个元素可以简单地看做是一个结构体变量。
(3)访问结构体中的数组成员,比如访问s1中的grade数组,则可以写成s1.grade[0]。
(4)若结构体中的字符数组存放的是字符串,则可以对其数组名直接引用,如s1.name、ps->name等。
(5)嵌套定义的结构体变量成员的引用:在结构体说明中,某成员变量又是一个结构体变量,若要引用其成员,可以一级一级的找到最低一级的成员。
例如,s1.birthday.year;
对结构体变量中的成员进行操作
结构体变量中的每个成员都属于某个具体的数据类型,因此,要操作结构体中的成员,可以采用操作普通变量的方式来对结构体成员进行输入和输出。
(1)对结构体变量中的整型age成员进行输入输出。
输入:scanf(“%d”,&s1.age);
scanf(“%d”,&s[0].age);
scanf(“%d”,&ps->age);
输出:printf(“%d”,s1.age);
printf(“%d”,ps->age);
(2)对结构体变量中的字符串name成员进行输入和输出
输入:scanf(“%s”,s1.name);gets(s1.name);
输出:printf(“%s”,s1.name);puts(s1.name);
(3)对结构体成员变量birthday的成员year操作
输入:scanf(“%d”,&s1.birthday.year);
scanf(“%d”,&s[0].birthday.year);
输出:printf(“%d”,s1.birthday.year);
printf(“%d”.s[0].birthday.year);
3. 通过指针变量来引用结构体成员
当通过指针变量来引用结构体成员,并且与++等运算符组成表达式时,应当根据运算符的优先级来确定表达式的含义。
例如,struct {
int a; char * s;
}x,*p=&x;
++p->a; ++(p->a)
(p++)->a p++->a
*p->s *p->s++ (*p->s)++ *p++->s
13.2.5 函数之间结构体变量的数据传递
在函数调用中,可以把结构体变量、结构体变量的成员、地址、结构体成员的地址等等作为函数传递的实参,函数可以返回结构体类型值和结构体类型指针。规则与前面所讲内容一致。
(1)向函数传递结构体变量的成员。
(2)向函数传递结构体变量
(3)传递结构体变量的地址 例13.10
(4)向函数传递结构体变量成员的地址 例13.11
(5)函数的返回值可以是结构体类型或返回值是指向结构体变量的指针,即当做基本类型看待。
13.2.6 用结构体构成链表
(1)结构体中含有指向本结构体类型的指针成员。结构体中的成员可以是各种类型的数据。那么,当结构体中有一个或多个成员指针,且它们的基类型就是本结构体类型时,通常把这种结构体称为“引用自身的结构体”。例如,
struct link
{
char data;
struct link *next;
};
typedef struct link LINK; p120 例13.12
LINK a;
a.next=&a;
(2)建立链表,以及顺序访问链表中各结点个数可以是任意多个,可以根据需要在程序中动态的分配多个结点。
见p121 例13.13
13.2 共用体
   共用体的类型说明和使用方法与结构体完全相同,共用体与结构体的唯一区别是:结构体的各成员都有自己的内存存储单元,结构体类型占用存储空间的总字节数,即为各成员所占字节数的总和;而共用体则不一样,在共用体中,各成员使用共同的存储单元,共用体占用存储空间的总字节数为共用体中占字节数最大的那个成员的存储字节数。
struct memb
{ float v;
int n;
char c;
} sa1;
sa1占7个字 节内存空间
union memb
{ float v;
int n;
char c;
} usa2;
usa2占4个字节内存空间
1001
1005
1007
v
n
c
2001
v
n
c
2002
2003
2004
共用变量占用的内存空间,等于最长成员的长度,而不是各成员长度之和。
13.2.1 共用体类型的说明和变量定义
1.共用体类型说明
 共用体类型说明的一般形式为:
union 共用体标识名
{ 类型名1 共用体成员名1;
类型名2 共用体成员名2;
 …
 类型名n 共用体成员名n;
};
例如:
union un
{
int i;
char c;
float f;
}u1; //u1所占的字节数为其成员f的字节数,即为4个字节。
  说明:
union为关键字,是共用体类型的标志。
共用体表示几个变量公用一个内存位置, 在不同的时间保存不同的数据类型和不同长度的变量。
在内存单元中,所有成员具有相同的首地址,并且重叠在一起。共用体中的成员可以是简单变量,也可以是数组、指针、结构体和共用体。
2. 共用体变量的定义
与结构体变量的定义方法相同,共用体变量的定义也有4种方式。采用一种方式举例如下:
union un_1
{
int a;
char c;
float fd;
}un=66;
说明:
(1)共用体变量在定义的同时只能用第一个成员的类型值进行初始化。
(2)由于共用体变量中的所有成员共享存储空间,依次所有成员的首地址相同,变量的地址就是该变量的所有成员的地址。
13.3.2 共用体变量的引用
1.共用体变量中成员的引用
  可以使用以下三种形式引用共用体变量中的成员:
(1)共用体变量名.成员名
(2)指针变量名->成员名
(3)(*指针变量名).成员名
例如,
union un
{
int i;
char c;
float f;
}u1;
则引用以下成员:u1.i=20; u1.c=getchar(); scanf(“%f”,&un.f);
共用体中的成员变量可参与其所属类型允许的任何操作。但在访问共用体成员时应注意,共用体变量中起作用的是最近一次存入的成员变量值,原有成员变量的值将被覆盖。
2.共用体变量的赋值
   不能在定义公用体变量时对它初始化。对共用体变量赋值必须针对其成员进行,不能直接对共用体变量赋值,不过在ANSI C中允许具有相同类型的共用体变量相互赋值。例如:
 union cif_ty cif1, cif2;
 cif = 10;
 cif1.i = 10;
 cif2 = cif1;
   共用体变量的地址和它的各个成员的地址都一样,公用体数组的地址和它的第一个成员的地址一样。
3.向函数传递共用体变量的值
   同结构体变量一样,共用体类型的变量可以作为实参进行传递,也可以传送共用体变量的地址。
例13.14 利用共用体类型的特点分别取出int变量中高字节和低字节中的两个数。
p124 例13.14(共21张PPT)
第九章 数组与指针
主要内容
9.1 一维数组与指针
9.2 二维数组与指针
9.1 一维数组与指针
9.1.1 一维数组首地址和数组元素的地址
9.1.2 通过指针引用一维数组元素
9.1 一维数组和指针
9.1.1 一维数组和数组元素的地址
一维数组在主存中占连续的存储空间,数组名代表的是数组的首地址。
可定义一个指针变量,通过赋值或赋初值的形式,把数组名或数组的第一个元素的地址赋值该指针变量,该指针变量就指向了该数组。
值得注意的是,这个指针变量中的地址值不可改变,也就是说,不可以给数组名重新赋值,因而数组名也可以认为是一个地址常量。
a [0]
a [1]
a [2]
a [3]
a [9]
...
整型指针p
&a [0]
a
若定义:
int a[10],*p,i;
数组元素a[0]----a[9],都可以看作是一个变量,因此每个数组元素均有一个地址:& a[i] (0<=i<=9)
使用p=&a[0]将指针p指向数据元素a[0],由于数组元素在内存中是连续的,则可以通过移动指针p来访问数组中的每个元素。
在C语言中,数组占用一串连续的存储单元,如:
C规定:数组名代表数组在内存中的起始地址,则a可表示数组a的首地址,也可使用p=a来让指针p指向数组a的起始地址。
a[0]
a[1]
a[2]
a[3]
a[9]
...
整型指针p
****
a
注:数组名是表示数组首地址的地址常量,永远指向a数组的首地址,不能对a重新赋值。
对于一维数组a,数组名a是数组元素a[0]的地址,即&a[0]与a是等值的。属性也相同,都是int 的地址。
&a[1]是数组元素的地址;
&a[2]是数组元素的地址;
&a[i](0<=i<=9有效)是数组元素的地址;
例: int a[10];
a++; a=&a[3];
9.1.1 一维数组首地址和数组元素的地址
设有: int i,a[10],*p,p1,p2;
则:
p=&i; (将变量i地址 p)
p=a; (将数组a首地址 p)
p=&a [i]; (将数组元素地址 p)
p1=p2; (指针变量p2值 p1)
不能把一个整数 p,也不能把p的值 整型变量
如 int i, *p;
p=1000; ( )
i=p; ( )
指针变量所指的变量的数据类型只能是它的基类型
a[0]
a[1]
a[2]
a[3]
a[9]
...
整型指针p
&a[0]
p
通过指针引用数组元素:
设: int a[10],*p;
p=&a[0];
则指针变量p指向了数组元素a[0],可使用间接访问运算符“*”来引用变量a[0]
如*p=18;等价于a[0]=18;
9.1.2 通过指针引用一维数组元素
例: main()
{ int a[5],*p, i;
for(p=a, i=0;i<5;i++)
scanf( “%d”, p+i);
for(p=a;pprintf (“%d”,*p);
}
通过数组首地址引用数组元素:
设: int a[10];
因a表示数组a的首地址,可把a当作是一个指针常量。
则: *a 等价于a[0]
*(a+1)等价于a[1]……
a 等价于&a[0]
a+1 等价于&a[1]……
例: main()
{ int a[5],i;
for(i=0;i<5;i++)
scanf( “%d”, a+i);
for(p=a;pprintf (“%d”,*(a+i));
}
//a是指针常量,不能使用a++对a进行移动
用带下标的指针变量引用一维数组元素:
设有: int *p,a[5];
p=a;
我们可使用p[0]表示p指针指向的内存单元,p[1]表示p指针指向的内存单元的下一个内存单元,则可使用p[0]表示a[0],p[1]表示a[1]……
例: main()
{ int a[10]={1,2,3,4,5,6,7,8,9,10},*p=&a[3],*q=p+2;
printf (“%d\n”,*p+*q);
}
输出结果:10
例: main()
{ int i, s=0,t[]={1,2,3,4,5,6,7,8,9};
for( i=0;i<9;i+=2) s+=*( t+i );
printf (“%d\n”, s);
}
输出结果:25
9.2 二维数组与指针
9.2.1 二维数组首地址和数组元素的地址
9.2.2 指针数组与二维数组
9.2.3 行指针
9.2.4 指针数组与行指针的区别
对于一维数组:
数组名a表示数组的首地址,即a [0]的地址
数组名a 是地址常量
a+i是元素a [i]的地址
a [i] *(a +i)
这些同样可以运用于二维数组。
a
int a[10];
9.2.1 二维数组首地址和数组元素的地址
任何一个二维数组由若干个一维数组组成
对于二维数组:
(1)a是数组名,
包含三个元素
a[0],a[1],a[2]
(2)每个元素a[i]
又是一个一维
数组,包含4个
元素
a
a+1
a+2
*(*(a+0)+1)
*(a[0]+1)
int a[3][4];
a[0]
a[1]
a[2]
2000
2008
2016
2000
2002
2008
2010
2016
2018
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
基类型
a[0]+1
a[1]+1
a[2]+1
*(a+0)+1
*(a+1)+1
*(a+2)+1
设有:int a[3][4], *p;
对二维数组 int a[3][4],有
a-----二维数组的首地址,即第0行的首地址,地址常量,不能进行赋值运算。
a+i-----第i行的首地址
a[i] *(a+i)------第i行第0列的元素地址
a[i]+j *(a+i)+j -----第i行第j列的元素地址
*(a[i]+j) *(*(a+i)+j) a[i][j]
a+i=&a[i] <------- a[i]=*(a+i) =&a[i][0],
值相等,含义不同
a+i &a[i],表示第i行首地址,指向行
a[i] *(a+i) &a[i][0],表示第i行第0列元素地址,指向列
int a[3][4];
a[0]
a[1]
a[2]
2000
2008
2016
2000
2002
2008
2010
2016
2018
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
a
a+1
a+2
二维数组名也是一个地址常量
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
二维数组元素表示形式:
(1)a[1][2]
(2)*(a[1]+2)
(3)*(*(a+1)+2)
(4)*(&a[0][0]+1*4+2)
地址表示:
(1) a+1
(2) &a[1][0]
(3) a[1]
(4) *(a+1)
(5)(int *) (a+1)
属性为行指针
属性为元素指针
地址表示:
(1) &a[1][2]
(2) a[1]+2
(3) *(a+1)+2
(4)&a[0][0]+1*4+2
二维数组元素及地址
int a[3][4];
a[0]
a[1]
a[2]
2000
2008
2016
2000
2002
2008
2010
2016
2018
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
a
a+1
a+2
表示形式
含义
地址
a
二维数组名,数组首地址
a[0],*(a+0),*a
第0行第0列元素地址
a+1
第1行首地址
a[1],*(a+1)
第1行第0列元素地址
a[1]+2,*(a+1)+2,&a[1][2]
第1行第2列元素地址
*(a[1]+2),*(*(a+1)+2),a[1][2]
第1行第2列元素值
2000
2000
2008
2008
2012
13
9.2.2 指针数组与二维数组
指针数组
定义形式:类型名 *指针数组名[常量表达式]
例:int *p[3]
定义了一个具有三个元素的一维数组,其中每个元素只能存放指针,这些指针的基类型为整型,故称p为指针数组
说明:[]的优先级高于*的优先级,则*p[3]中,p先与[]结合,构成p[3],说明p是一个数组名; *说明数组p中的每个元素只能存放指针
p[0]
p[2]
p[1]
定义指针数组
通过指针数组引用二维数组元素
例:
int *p[3],a[3][4],i,j;
for( i=0;i<3;i++) p[i]=a[i];
p[i]的基类型与a[i]的基类型相同
赋值号右边的a[i]代表a数组每行的首地址。
赋值号左边的p[i]是指针变量。
循环执行的结果是使p数组中的每个元素指向了a数组每行的开头。
当数组p中的每个元素指向a数组每行的开头时,则a数组中的元素:
a [i] [j] *(a [i] + j) *( p [i] +j)
例 有以下程序段:
int a[3][2]={1,2,3,4,5,6},*p[3];
p[0]=a[1];
则*(p[0]+1)所代表的数组元素是:
输出结果:a[1][1]
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
p[0]
p[1]
p[2]
定义行指针
定义形式:数据类型 (*指针数组名)[常量表达式];
例 int (*p)[4];
( )不能少
int (*p)[4]与int *p[4]不同
p的值是一维数组的
首地址,p是行指针
可让p指向二维数组某一行
如:
int a[3][4], (*p)[4]=a;
p[0]+1或 *p+1
p[1]+2或 *(p+1)+2
*(*p+1)或 (*p)[1]
*(*(p+1)+2)
一维数组指针变量维数和
二维数组列数必须相同
9.2.3 行指针
定义了一个指针变量p,p只能存放含有四个整型元素的一维数组首地址。
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
p
p
p
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
a
a+1
a+2
p
p+1
p+2
9.2.4 指针数组与行指针的区别
int *p[3]:表示一个数组,它含有三个元素p[0]、p[1]、p[2]、p[4],且这三个元素只能存放整型元素的地址。
int (*p)[4]:表示一个指针变量,它仅有一个存储空间,只能存放一个长度为4的一维数组的指针。
例: int *p[3],a[3][4],i;
for( i=0;i<3;i++) p[i]=a[i];
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
p[0]
p[1]
p[2]
例: int (*p)[4],a[3][4],i,j;
for( i=0;i<3;i++) p=&a[i];
int a[3][4];
a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[0][2]
a[0][3]
a[1][2]
a[1][3]
a[2][2]
a[2][3]
p
p
p(共118张PPT)
3.1 软件工程基本概念
3.2 软件分析及其方法
3.3 软件设计及其方法
3.4 软件测试
3.5 程序的调试
3.6 典型考题分析
第三章 软件工程基础
内容提要
软件工程基本概念,软件生命周期概念,软件工具与软件开发环境。
结构化分析方法,数据流图,数据字典,软件需求规格说明书。
结构化设计方法,总体设计与详细设计。
软件测试的方法,白盒测试与黑盒测试,测试用例设计,软件测试的实施,单元测试、集成测试和系统测试。
程序的调试
3.1.1 软件定义与软件特点
3.1.2 软件危机与软件工程
3.1.3 软件工程过程与软件生命周期
3.1.4 软件工程的目标与原则
3.1.5 软件开发工具与软件开发环境
3.1 软件工程基本概念
3.1.1 软件定义与软件特点
1.软件的定义和组成
定义:
计算机软件(Software)是计算机系统中与硬件相互依赖的另一部分。
组成:
程序
数据
文档
国标(GB)定义
与计算机系统的操作有关的计算机程序、规程、规则,以及可能有的文件、文档及数据。
可执行
3.1.1 软件定义与软件特点(续)
2.软件的特点
软件是一种逻辑实体,而不是具体的物理实体,具有抽象性
软件没有明显的制造过程。对软件的质量控制,必须在软件开发方面下功夫
软件不存在老化问题,但存在退化问题,必须要修改和维护
对计算机系统有着依赖性——软件移植的问题
软件复杂性高,开发和维护成本高
软件开发涉及诸多社会因素
3.1.1 软件定义与软件特点(续)
3.软件的分类
应用软件:解决特定领域的应用而开发的软件。
系统软件:计算机管理自身资源,提高计算机使用效率,并为计算机用户提供各种服务的软件,如操作系统。
支撑软件:介于系统软件和应用软件之间,协助用户开发软件的工具性软件,如测试软件。
3.1.2 软件危机与软件工程
1.软件危机
软件工程源自于软件危机
主要表现:
软件需求的增长得不到满足
软件开发成本和进度无法控制
软件质量难以保证
软件不可维护或维护程度非常低
软件成本不断提高
软件开发生产效率的提高赶不上硬件的发展和应用需求的增长
归结为成本、质量和生产率等问题
3.1.2 软件危机与软件工程
2.软件工程的产生与定义
软件工程学——工程学的新兴领域
定义:
国标(GB):应用于计算机软件的定义、开发和维护的一整套方法、工具、文档、实践标准和工序。
德国人Fritz Bauer:软件工程是建立并使用完善的工程化原则,以较经济的手段获取能在实际机器上有效运行的可靠软件的一系统方法。
IEEE:将系统的、规范的、可度量的方法应用于软件开发、运行和维护的过程,即将工程应用于软件中。
主要思想:在软件开发过程中需要应用工程化原则的重要性
3.1.2 软件危机与软件工程
2.软件工程的产生与定义
软件工程3个要素:
方法:完成软件工程项目的技术手段。
工具:支持软件的开发、管理、文档生成。
过程:支持软件开发的各个环节的控制、管理。
3.1.3 软件工程过程与软件生命周期
1.软件工程过程
ISO 9000定义:软件工程过程是指把输入转化为输出的一组彼此相关的资源和活动。此定义包括以下两个方面的内容:
第一:软件工程过程是指为获得软件产品,在软件工具支持下由软件工程师完成的一系列软件工程活动。软件工程过程通常包含4种基本活动:
P(Plan)——软件规格说明
D(Do)——软件开发
C(Check)——软件确认
A(Action)——软件演进
3.1.3 软件工程过程与软件生命周期
1.软件工程过程(续)
第二:从软件开发的观点看,软件工程过程就是使用适当的资源(包括人员、硬件、软件工具、时间),为开发软件进行的一组开发活动,在过程结束时将输入(用户要求)转化为输出(软件产品)。
总之,软件工程的过程是将软件工程的方法和工具综合起来,以达到合理、及时地进计算机软件开发的目的。
软件工程过程应确定方法使用的顺序、要求交付的文档资料、为保证质量和适应变化所需要的管理、软件开发各个阶段完成的任务。
3.1.3 软件工程过程与软件生命周期
2.软件生命周期
定义:软件产品从提出、实现、使用维护、停止使用到退役的过程
3个阶段
6个阶段工作
3.1.3 软件工程过程与软件生命周期
定义阶段
制定计划:”能做吗?“
需求分析:“做什么?”
开发阶段:
软件设计:“如何做?”,分为概要设计和详细设计两个阶段。
软件实现:“实现”,编码。
软件测试:”做的怎么样?“
运行维护阶段
使用,不断维护
3.1.4 软件工程的目标与原则
1.软件工程的目标
成功的项目:
成本
功能
移植
维护费用
按时
及时交付
目标:
在给定成本、进度的前提下,开发出具有有效性、可靠性、可理解性、可维护性、可重用性、可适应性、可移植性、可追踪性和可互操作性且满足用户需求的产品
3.1.4 软件工程的目标与原则
2.软件工程学的范畴(软件工程的理论和技术性研究的内容)
3.1.4 软件工程的目标与原则
3.软件工程的原则
抽象
信息隐蔽
模块化
局部化
确定性
一致性
完备性
可验证性
3.1.5 软件开发工具与软件开发环境
1.软件开发工具
协助开发人员进行软件开发活动所使用的软件或环境
需求分析工具、设计工具、编码工具、排错工具、测试工具等。
2.软件开发环境
全面支持软件开发全过程的软件工具的集合
计算机辅助软件工程:CASE
3.2.1 需求分析与需求分析方法
3.2.2 结构化分析方法
3.2.3 软件需求规格说明书
3.2 结构化分析方法
3.2.1 需求分析与需求分析方法
1.需求分析
定义:
任务:导出目标系统的逻辑模型,解决“做什么”的问题
全面理解用户的各项要求
准确地表达各项要求
主要工作:
需求获取
需求分析
编写需求规格说明书
需求审评
3.2.1 需求分析与需求分析方法
2.需求分析方法
结构化分析方法
面向数据流的结构化分析方法(SA)
面向数据结构的Jackson方法(JSD)
面向数据结构的结构化数据系统开发方法(DSSD)
面向对象分析方法(OOA)
静态分析方法
动态分析方法
3.2.2 结构化分析方法
1.关于结构化分析方法
结构化程序设计理论在需求分析阶段的运用
面向数据流进行需求分析的方法
自顶向下、逐层分解
主要工具:数据流图、数据字典
3.2.2 结构化分析方法
2.结构化分析的常用工具
数据流图(DFD)
以图形的方式描绘数据在系统中流动和处理的过程。
只反映系统必须完成的逻辑功能,是一种功能模型。
数据字典(DD)
结构化分析方法的核心
对数据流图中出现的被命名的图形元素的确切解释
判定树
一种描述加工的图形工具,适合描述问题处理中具有多个判断,而且每个决策与若干条件有关。
判定表:描述加工的一种图形工具,关似与判定树
3.2.2 结构化分析方法
数据流图
3.2.2 结构化分析方法
数据流图:基本图形元素
3.2.2 结构化分析方法
数据流图:分层数据流图
3.2.3 软件需求规格说明书
需求分析阶段的最后成果
作用:
便于用户、开发人员进行理解和交流;
反映出用户问题的结构,可以作为软件开发工作的基础和依据;
作为确认测试和验收的依据。
主要内容
概述、数据描述、功能描述、性能描述、参考文献、附录
特点:
①正确性;②无歧义性;③完整性;④可验证性;⑤一致性;⑥可理解性;⑦可修改性;⑧可追踪性。
3.3.1 软件设计的基本概念
3.3.2 概要设计
3.3.3 详细设计
3.3 软件设计及其方法
3.3.1 软件设计的基本概念
1.软件设计的基础
开发阶段:设计、实现(编码)和测试
需求分析:主要解决“做什么”问题
软件设计:主要解决“怎么做”问题
3.3.1 软件设计的基本概念
1.软件设计的基础
重要性:
主要内容:
结构设计:定义软件系统各主要部件之间的关系
数据设计:将分析时创建的模型转化为数据结构的定义
接口设计:描述软件内部、软件和操作系统之间以及软件与人之间如何通信
过程设计:把系统结构部件转换成软件的过程性描述
步骤:
概要设计和详细设计
3.3.1 软件设计的基本概念
2.软件设计的基本原理
抽象
一种思维工具
抽出事物本质的共同特点,不考虑细节
模块化
模块
模块化
信息隐蔽和局部化
信息隐藏:每个模块的实现细节对于其它模块来说是隐蔽的
局部化:把一些关系密切的软件元素物理地放得彼此靠近
模块独立性
每个模块只涉及软件要求的具体的子功能和软件系统中其它的模块的接口是简单的
衡量指标:耦合性、内聚性
模块的独立程度是评价设计好坏的重要度量标准
高质量的软件设计,应尽量做到高内聚、低耦合,有利于提高模块的独立性。
3.3.1 软件设计的基本概念
内聚性
度量一个模块功能强度的一个相对指标。
一个模块只做一件事
7种类型
3.3.1 软件设计的基本概念
耦合性
度量模块之间的相互联系程度
取决于接口的复杂程度、调用方式、哪些信息通过接口
模块连接方式有7种,构成耦合性的7种类型
3.3.2 概要设计
1.概要设计的基本任务
系统结构设计
主要任务:划分为模块
数据结构和数据库的设计
实现需求定义和规格说明过程中提出的数据对象的逻辑表示
编写概要设计文档
概要设计说明书、数据库设计说明书、用户手册和集成测试计划。
概要设计的评审
对概要设计文档中给出的设计方案可行性、正确性、有效性、一致性等进行审核
3.3.2 概要设计(续)
2.软件结构图(SC)
用来表示软件结构
基本图符
模块用矩形表示,矩形内注明模块的功能和名字
箭头表示模块间的调用关系
带注释的箭头表示模块调用过程中来回传递的信息
3.3.2 概要设计(续)
结构图构成的基本形式:
A
B
基本形式
A
D
C
B
顺序形式
A
B
顺序形式
A
B
B
选择形式
3.3.2 概要设计(续)
系统结构图(SC)中的模块
4种类型的模块
3.3.2 概要设计(续)
结构图的形态特征
深度:表示控制的层数
宽度:整体控制跨度(最大模块数的层)的表示
扇出:一个模块直接调用的其他模块数
扇入:调用该模块的个数
3.3.2 概要设计(续)
3.面向数据流的设计方法
数据流图(DFD):需求分析工具
系统结构图(SC):概要设计工作
主要任务:数据流图变换成结构图
数据流的类型
变换流
事务流
3.3.2 概要设计(续)
变换流
数据流图:取得数据、变换数据、给出数据
3.3.2 概要设计(续)
变换流
系统的结构图:输入、中心变换、输出
3.3.2 概要设计(续)
事务流
数据流图
3.3.2 概要设计(续)
事务流
系统的结构图:
3.3.2 概要设计(续)
实施要点与设计过程
分析、确认数据流图的类型,区分是事务型还是变换型
说明数据流的边界
数据流图映射为程序结构
根据设计准则把数据流转换成程序结构图
3.3.2 概要设计(续)
变换分析
确定数据流图是否具有变换特性
确定输入流和输出流的边界,划分出输入、变换和输出,独立出变换中心
第一级分解
按上述步骤如出现事务流的映射方式对各个子流进行逐级分解,直至分解到基本功能;
对每个模块写一个简要的说明
利用软件的设计原则对软件结构透一步转化
事务分析
与变换分析类似
主要差别:映射方法不同
3.3.2 概要设计(续)
4.设计准则
提高模块独立性
深度、宽度、扇度和扇出适度
使模块的作用域在该模块的控制域内
应减少模块的接口和界面的复杂性
设计成单入口、单出口的模块
设计功能可预测的模块
3.3.3 详细设计
详细设计的任务:
确定实现算法和局部数据结构
不同于编码或编程
详细设计的常用工具:
图形工具:程序流程图、N-S、PAD
表格工具:判定表;
语言工具:PDL(伪码)
3.3.3 详细设计(续)
程序流程图
图形元素:
方框:处理步骤
菱形:逻辑条件
箭头:控制流
5种控制结构
顺序型
选择型
先判断重复型
后判断重复型
多分支选择型。
3.3.3 详细设计(续)
程序流程图
3.3.3 详细设计(续)
N-S图
流程图:随意性与灵活性
N-S图:限制了随意的控制转移,保证了程序的良好结构
5种基本控制结构:
3.3.3 详细设计(续)
N-S图
3.3.3 详细设计(续)
N-S图
特点:
每个构件具有明确的功能域
控制转移必须遵守结构化设计要求;
易于确定局部数据和(或)全局数据的作用域
易于表达嵌套关系和模块的层次结构
3.3.3 详细设计(续)
PAD图
PAD——问题分析图,Problem Analysis Diagram
表现程序逻辑结构的图形工具
5种基本控制结构
3.3.3 详细设计(续)
PAD图
3.3.3 详细设计(续)
PAD图
特征
结构清晰,结构化程度高
易于阅读
程序的纵线数等于程序的层次数
程序执行从PAD图最左主干线上端结点开始,自上而下、自左向右依次执行,程序终止于最左主干线
3.3.3 详细设计(续)
PDL(伪码)
PDL——过程设计语言,Program Design Language
混合语言,类似编程语言
常用词汇:
顺序:
条件:IF/THEN/ELSE/ETIDIF
循环:DOWHILE/ENDDO
循环:REPEAT UNTIL/ENDREPEAT
分支:CASE OF/WHEN/SELECT/WHEN/SELECT/ENDCASE
PDL特征:
有为结构化构成元素、数据说明和模块化特征提供的关键词语法;
处理部分的描述采用自然语言语法
可以说明简单和复杂的数据结构
支持各种接口描述的子程序定义和调用技术。
3.4.1 软件测试的目的
3.4.2软件测试的内容
3.4.3软件测试的准则
3.4.4软件测试技术与方法综述
3.4.5软件测试的实施
3.4软件测试
3.4.1 软件测试的目的
软件测试的目的:
测试是程序的执行过程,目的在于发现错误
一个好的测试用例在于能发现至今未发现的错误
一个成功的测试是发现了至今未发现的错误的测试
3.4.2软件测试的内容
软件测试的内容:
需求阶段的需求测试
编码阶段的程序单元测试
集成测试
系统测试
3.4.3软件测试的准则
软件测试的准则:
所有测试都应追溯到需求
严格执行测试计划,排除测试的随意性
充分注意测试中的群集现象
程序员应避免检查自己的程序
穷举测试不可能
妥善保存测试计划、测试用例、出错统计和最终分析报告,为维护提供方便
3.4.4软件测试技术与方法综述
1.静态测试与动态测试
静态测试
人工评审软件文档或程序,借以发现其中的错误
主要方法:代码检查、静态结构分析、代码质量度量
动态测试
上机测试
关键:设计高效、合理的测试用例
分两类:白盒测试方法和黑盒测试方法
3.4.4软件测试技术与方法综述(续)
2.白盒测试方法与测试用例设计
也称结构测试或逻辑驱动测试
测试用例是根据程序的内部逻辑来设计
主要用于单元测试
基本原则
保证所测模块中每一个独立路径至少执行一次
保证所测模块所有判断的每一个分支至少执行一次
保证所测模块每一个循环都在边界条件和一般条件至少执行一次
验证所有内部数据结构的有效性
主要方法:逻辑覆盖、基本路径测试
3.4.4软件测试技术与方法综述(续)
逻辑覆盖测试
程序中的逻辑:判断、分支、条件
可分为:
语句覆盖:每一个语句都能执行一次
路径覆盖:所有的可能路径都至少经历一次
判定覆盖:每个判定至少都获得一次“真值”和“假值”的机会
条件覆盖:每个判定中每个条件都获得一次 “真”和“假”的机会
判断-条件覆盖:判定中的每个条件都能取得各种可能的“真”和“假”值,并且使每个判定都能取到“真”和“假”两种结果
强度顺序
语句覆盖<路径覆盖<判定覆盖<条件覆盖<判定-条件覆盖
3.4.4软件测试技术与方法综述(续)
基本路径测试
把覆盖的路径数压缩到一定限度内
思想和步骤:
根据软件过程性描述中的控制流程确定程序的环路复杂性度量,用此度量定义基本路径集合,并由此导出一组测试用例对每一条独立执行路径进行测试
3.4.4软件测试技术与方法综述(续)
3.黑盒测试方法与测试用例设计
也称功能测试或数据驱动测试
对软件已经实现的功能是否满足需求进行测试和验证
根据程序的功能说明来设计测试用例
主要用于确认测试
主要方法
等价类划分法
边界值分析法
错误推测法
3.4.4软件测试技术与方法综述(续)
等价类划分法
有效等价类
无效等价类
边界值分析法
大量的错误是发生在输入或输出范围的边界上
错误推测法
根据经验或直觉推测程序易出错的地方
3.4.5软件测试的实施
3.4.5软件测试的实施(续)
1.单元测试
对象:针对程序模块,进行正确性检验的测试
目的:发现各模块内部可能存在的各种差错
依据:从程序的内部结构出发设计测试用例,其依据是详细的设计说明书和源程序
方法:以白盒测试为主,辅以黑盒测试
3.4.5软件测试的实施(续)
1.单元测试
内容:
模块接口测试
局部数据结构测试
路径测试
错误处理测试
边界测试
步骤:
在编码阶段进行
源程序代码编制完成,经过评审和验证,确认没有语法错误之后
利用设计文档,设计可以验证程序功能、找出程序错误的多个测试用例
对于每一组输入,应有预期的正确结果
3.4.5软件测试的实施(续)
1.单元测试
驱动模块、桩模块
3.4.5软件测试的实施(续)
2.集成测试
任务:把模块在按照设计要求组装起来的同时进行测试
目的:发现与接口有关的错误
依据:集成测试的依据是概要设计说明书
内容:软件单元的接口测试、全局数据结构测试、边界条件和非法输入的测试
方式:非增量方式组装与增量方式组装。
3.4.5软件测试的实施(续)
2.集成测试
非增量方式组装
也称为一次性组装方式
增量方式组装
也称渐增式集成方式
3种方式:
自顶向下
自底向上
自顶向与自底向上相结合
3.4.5软件测试的实施(续)
自顶向下
3.4.5软件测试的实施(续)
自底向上
3.4.5软件测试的实施(续)
3.确认测试
又称有效性测试
目的:验证软件的功能和性能及其它特性是否与用户的要求一致
依据:软件需求规格说明书
方法:黑盒测试法
4.系统测试
任务:在实际运行(使用)环境下,对计算机系统进行一系列的组装测试和确认测试
目的:在于通过与系统的需求定义作比较,发现软件与系统定义不符合或与之矛盾的地方
依据: 需求分析规格说明来设计
内容:功能测试、性能测试、操作测试、配置测试、外部接口测试、安全性测试
3.5.1 基本概念
3.5.2 软件调试方法
3.5程序的调试
3.5.1 基本概念
任务:诊断和改正程序中的错误
时机:调试主要在开发阶段进行
3.5.1 基本概念(续)
1.基本步骤
错误定位、纠正错误、回归测试
3.5.1 基本概念(续)
2.程序调试原则
确定错误的性质和位置的原则
用头脑去分析思考与错误征兆有关的信息
避开死胡同。
只把调试工具当作辅助手段来使用
避免用试探法,最多只能把它当作最后手段
修改错误的原则
在出现错误的地方,很可能还有别的错误
只修改了这个错误的征兆或这个错误的表现,而没有修改错误的本身。
当心修正一个错误的同时有可能会引入新的错误
修改错误的过程将迫使人们暂时回到程序设计阶段
修改源代码程序,不要改变目标代码
3.5.2 软件调试方法
1.强行排错法
通过内存全部打印来排错(Memory Dump)
在程序特定部位设置打印语句
自动调试工具
2.回溯法
3.原因排除法
演绎法
归纳法
二分法
典型考题分析
【例3-1】下列描述中正确的是______。(2005年4月)
A)程序就是软件
B)软件开发不受计算机系统的限制
C)软件既是逻辑实体,又是物理实体
D)软件是程序、数据与相关文档的集合
答案 D
【例3-2】下列描述中正确的是______。(2005年9月)
A)软件工程只是解决软件项目的管理问题
B)软件工程主要解决软件产品的生产率问题
C)软件工程的主要思想是强调在软件开发过程中需要应用工程化原则
D)软件工程只是解决软件开发中的技术问题
答案 C
【例3-3】下面不属于软件工程的3个要素的是______。
A)工具 B)过程
C)方法 D)环境
答案 D
【例3-4】下列叙述中正确的是______。(2005年9月)
A)软件交付使用后还需要进行维护
B)软件一旦交付使用就不需要再进行维护
C)软件交付使用后其生命周期就结束
D)软件维护是指修复程序中被破坏的指令
答案 A
【例3-5】下列选项中不属于软件生命周期开发阶段任务的是______。(2006年9月)
A)软件测试 B)概要设计
C)软件维护 D)详细设计
答案 C
【例3-6】软件工程学一般包括软件开发技术和软件工程管理两方面的内容。软件工程经济学是软件工程管理的技术内容之一,它专门研究______。
A)软件开发的方法学
B)软件开发技术和工具
C)软件成本效益分析
D)计划、进度和预算
答案 C
【例3-7】下面不属于软件工程原则的是______。
A)抽象 B)模块化
C)自底向上 D)信息隐蔽
答案 C
【例3-8】计算机辅助软件工程,简称为______。
A)SA B)SD
C)SC D)CASE
答案 D
【例3-9】需求分析阶段的任务是确定______。
A)软件开发方法
B)软件开发工具
C)软件开发费用
D)软件系统功能
答案 D
【例3-10】软件需求分析阶段的工作,可以分为四个方面:需求获取,需求分析,编写需求规格说明书,以及______。
A)阶段性报告
B)需求评审
C)总结
D)都不正确
答案 B
【例3-11】结构化分析方法是面向______的自顶向下逐步求精进行需求分析的方法。
A)对象
B)数据结构
C)数据流
D)目标
答案 C
【例3-12】下列工具中为需求分析常用工具的是______。
A)PAD
B)PFD
C)N-S
D)DFD
答案 D
【例3-13】数据流图用于抽象描述一个软件的逻辑模型,数据流图由一些特定的图符构成。下面图符号不属于数据流图的是______。
A)控制流
B)加工
C)数据存储
D)源和潭
答案 A
【例3-14】下列叙述中,不属于软件需求规格说明书的作用的是______。
A)便于用户、开发人员进行理解和交流
B)反映出用户问题的结构,可以作为软件开发工作的基础和依据
C)作为确认测试和验收的依据
D)便于开发人员进行需求分析
答案 D
【例3-15】Jackson方法是一种面向______的结构化方法。
答案 数据结构
结构化分析方法包括:
面向数据结构的Jackson方法(JSD)
面向数据流的结构化分析方法(SA)
面向数据结构的结构化数据系统开发方法(DSSD)
【例3-16】从工程管理角度,软件设计一般分为两步完成,它们是______。(2006年9月)
A)概要设计与详细设计
B)数据设计与接口设计
C)软件结构设计与数据设计
D)过程设计与数据设计
答案 A
【例3-17】两个或两个以上模块之间关联的紧密程度称为______。(2006年4月)
A)耦合度
B)内聚度
C)复杂度
D)数据传输特性
答案 A
【例3-18】为了提高模块的独立性,模块之间最好是______。
A)控制耦合
B)公共耦合
C)内容耦合
D)数据耦合
答案 D
【例3-19】为了使模块尽可能独立,要______。(2005年4月)
A)模块的内聚程度要尽量高,且各模块间的耦合程度要尽量强
B)模块的内聚程度要尽量高,且各模块间的耦合程度要尽量弱
C)模块的内聚程度要尽量低,且各模块间的耦合程度要尽量弱
D)模块的内聚程度要尽量低,且各模块间的耦合程度要尽量强
答案 B
【例3-20】软件的结构化开发过程各阶段都应产生规范的文档,以下______不是在概要设计阶段应产生的文档。
A)集成测试计划
B)软件需求规格说明书
C)概要设计说明书
D)数据库设计说明书
答案 B
【例3-21】软件结构设计的图形工具是______。
A)DFD图 B)程序图
C)PAD图 D)N-S图
答案 B
结构化分析的常用工具:数据流图(DFD)、数据字典(DD)、判定树(决策树)、判定表
描述软件结构的图形工具(概要设计):结构图(SC),又称程序结构图
详细设计中的设计工具:
图形工具:程序流程图、N-S图、PAD图(问题分析图)
表格工具
语言工具:过程设计语言(PDL)
【例3-22】下列软件系统结构图的宽度为______。(2006年9月)
答案 3
【例3-23】数据流图的类型有______和事务型。
答案 变换型
【例3-24】在软件设计中,不属于过程设计工具的是______。(2005年9月)
A)PDL(过程设计语言)
B)PAD图
C)N-S图
D)DFD图
答案 D
【例3-25】程序流程图(PFD)中的箭头代表的是______。
A)数据流
B)控制流
C)调用关系
D)组成关系
答案 B
【例3-26】为了避免流程图在描述程序逻辑时的灵活性,提出了用方框图来代替传统的程序流程图,通常也把这种图称为______。
A)PAD图
B)N-S图
C)结构图
D)数据流图
答案 B
【例3-27】下列对于软件测试的描述中正确的是______。(2005年4月)
A)软件测试的目的是证明程序是否正确
B)软件测试的目的是使程序运行结果正确
C)软件测试的目的是尽可能地多发现程序中的错误
D)软件测试的目的是使程序符合结构化原则
答案 C
【例3-28】为了提高测试的效率,应该______。
A)随机地选取测试数据
B)取一切可能的输入数据作为测试数据
C)在完成编码以后制定软件的测试计划
D)选择发现错误可能性大的数据作为测试数据
答案 D
【例3-29】程序测试分为静态分析和动态测试,其中______是指不执行程序,而只是对程序文本进行检查,通过阅读和讨论,分析和发现程序中的错误。(2006年4月)
答案 静态分析
【例3-30】使用白盒测试方法时,确定测试数据应根据______和指定的覆盖标准。
A)程序的内部逻辑
B)程序的复杂结构
C)使用说明书
D)程序的功能
答案 A
【例3-31】等价类型划分法是______测试常用的方法。
答案 黑盒
【例3-32】在进行模块测试时,要为每个被测试的模块另外设计两类模块:驱动模块和承接模块(桩模块)。其中______的作用是将测试数据传送给被测试的模块,并显示被测试模块所产生的结果。(2005年9月)
答案 驱动模块
【例3-33】检查软件产品是否符合需求定义的过程称为______。
A)系统测试
B)集成测试
C)验收测试
D)单元测试
答案 C
【例3-34】______的任务是诊断和改正程序中的错误。(2006年9月)
答案 调试
【例3-35】下列叙述中正确的是______。(2005年9月)
A)程序设计就是编制程序
B)程序的测试必须由程序员自己去完成
C)程序经调试改错后还应进行再测试
D)程序经调试改错后不必进行再测试
答案 C
【例3-36】以下所述中,______是软件调试技术。
A)错误推断
B)集成测试
C)回溯法
D)边界值分析
答案 C(共69张PPT)
C语言程序设计

第1章 C语言基础知识
第一节 C语言程序的结构
第二节 整型数据
第三节 实型数据
第四节 算术表达式
第五节 赋值表达式
第六节 Turbo C的基本操作
第一节 C语言程序的结构
C语言程序的总体结构
一个完整的C语言程序,是由一个main()函数(又称主函数)和若干个其它函数结合而成的,或仅由一个main()函数构成。
[案例1.1] 仅由main()函数构成的C语言程序。


main()
{
printf(“This is a C program.\n”);
}
程序运行结果:This is a C program.
[案例1.2] 计算两个整数之和的C语言程序。
#include “stdio.h”
main()
{
int a,b,sum;
a=10;
b=20;
sum = a+b;
printf(“a=%d,b=%d,sum=%d\n”, a,b,sum);
}
程序运行情况:
a=10,b=20,sum=30
函数体
以分号结尾,
叫做语句。
编译预处理命令
main()函数
一个C语言程序,总是从main()函数
开始执行,而不论其在程序中的位置。
当主函数执行完毕时,亦即程序执行完毕。
源程序书写格式
任何一个C程序都必须包含main()函数。
C语言的函数体可以分为两个部分:
定义部分和执行部分。其中,定义部分必须在执行部分的前面。
C程序中用到的变量都必须先定义后使用,定义变量必须放在程序的定义部分。
所有语句都必须以分号“;”结束,函数的最后一个语句也不例外。
程序行的书写格式自由,既允许1行内写几条语句,也允许1条语句分写在几行上。
允许使用注释。
C语言的注释格式为:
(1) “” 必须成对使用,且“/”和“*”、以及“*”和“/”之间不能有空格,否则都出错。
(2)注释的位置,可以单占1行,也可以跟在语句的后面。
(3)如果1行写不下,可另起1行继续写。
(4)注释中允许使用汉字。在非中文操作系统下,看到的是一串乱码,但不影响程序运行。
二、标识符
在C语言中用于标识名字的有效字符序列称为标识符。
标识符可以用作常量名、变量名、符号名、函数名和指针名等等。
C语言的命名规则如下:
(1)标识符只能由字母、数字和下划线组成
(2)标识符的第一个字符必须是字母或下滑线。
C语言中字母的大小写是有区别的。
合法的标识符:
a x sum spels _to file_5
非法的标识符:
yes
234a
yes no
yes/no
标识符的分类
(1)关键字
   关键字在程序中代表着固定的含义。如标识符char、float以及for、if等都已有专门的用途,它们不能用作变量名或函数名。
32个关键字:
auto break case char const
continue default do double else
enum extern float for goto
if int long register return
short signed sizeof static struct
switch typedef unsigned union void
volatile while
(2)预定义标识符
预定义标识符在C语言中也有特定的含义,如库函数的名字和预编译处理命令等。
C语言语法允许用户把这类标识符另作他用,但是失去了在系统中规定的原意。
为了避免误解,建议用户不要把这些预定义标识符另作它用。
(3)用户标识符
由用户根据需要定义的标识符称为用户标识符。一般用来给变量、函数、数组或文件等命名。
如果用户标识符与关键字相同,程序会给出出错信息;
若与预定义标识符相同,则预定义标识符将失去原来的含义。
  
※重点提示:
标识符的命名规则:
(1)C语言规定标识符只能由字母(大小写均可,但区分大小写)、数字和下划线3种字符组成
(2)第1个字符必须为字母或下划线
(3)已被C语言本身使用,不能用作变量名、常量名、函数名等。
三、常量
在程序运行过程中,其值不能被改变的量称为常量。
C语言中有4中基本常量:整型常量、实型常量、字符常量和字符串常量。
此外,C语言中还经常使用两种表现形式不同的常量:转义字符常量和符号常量。
1.整型常量
整型常量也称整数,包括正整数、负整数和零。
如:3、10、100、-5、-35等。
2.实型常量
实型常量即实数,又称为浮点数。
如:3.1415926、-15.25等。
整型常量和实型常量又称为数值型常量。
3.字符常量
 字符常量使用一对单引号括起来的一个字符。如‘a’、‘B’、‘ ’等。
以下是关于字符常量的几点说明。
(1)单引号只是作为定界符使用,并不是字符常量的组成部分。
(2)单引号内的字符不允许是单引号或反斜杠。
(3)字符常量具有数值,这个值就是该字符在规定的字符集中的ASCII代码值。P140
(4)字符常量在机器内以整型常量的形式存放,因此字符常量与整型常量等价。
4.字符串常量
  字符串常量是由一对双引号括起来的字符序列,如“hello”、“how are you”等。
注意:C语言中没有专门存放字符串的字符串变量,因此存放时需要放在一个字符型数组中。
5.转义字符常量
  转义字符常量是以一个“\”开头的字符序列。每个转义字符都有其特定的含义和功能。
6.符号常量
  C语言中,允许用一个标识符来代表一个常量,即常量可以用“符号”来代替,代替常量的符号就称为符号常量。
以下是几点说明:
(1)符号常量在使用之前必须先定义,定义方法为用宏替换“#define”使一个标识符与某个常量相对应,其一般形式为:
#define 标识符 常量表达式<或字符串>。
(2)一个#define只能定义一个符号常量。
(3)符号常量定义式的行尾没有分号。
四、变量
变量是指在程序运行期间其值可以发生变化的
一个变量在内存中占据一定的存储单元,在程序中从变量中取值,实际上是通过变量名找到相应的内存地址,从其存储单元中读取数据。
C语言中的任何变量,使用前都必须定义,也就是必须先定义后使用。
  
第二节 整型数据
一、数值转换
2 进制数码:0 和 1,后缀为B
(10,11,12,13,14,15)
8进制数码:0,1,2,3,4,5,6,7,后缀为O
10进制数码:0,1,2,3,4,5,6,7,8,9后缀为D
16进制数码: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,后缀为H
任意进制转换到十进制:
任意R进制的一个数转换为十进制数:
(325.76)8
=3×82+2×81+5×80+7×8-1+6×8-2
= (213.96875)10
十进制与二、八、十六进制的转换
±(kn-1 kn-2 …k0.k-1k-2…k-m)R
=±(kn-1 Rn-1+…+k0R0+k-1R-1+…+k-mR-m)10
(11010.1)2
=1×24+1×23+0×22+1×21+0×20+1×2-1
= (26. 5)10
例如:
2. 十进制转换到其它进制:
整数部分和小数部分分别转换
小数部分采 用基
值重复相乘。直
到乘积为0。即
乘基值取整数法
整数部分采用基
值重复相除。直
到商为0。即除
基值取余数法
2 215
2 107 1
2 53 1
2 26 1
2 13 0
2 6 1
2 3 0
2 1 1
0 1
低位
高位
(215)10 =(11010111)2
(0.6875)10 = 2

0.6875
2
×
1 . 3750
整数
1
0.375
2
×
0 . 750
0
0. 50
2
×
1 . 00
1
0.75
2
×
1 . 50
1
转换结果为:(0.6875)10=(0.1011)2
2 13
2 6 1
2 3 0
2 1 1
0 1
低位
高位
(13)10 =(1101)2
0.8775
×
2
1.7550
整数
1
0.755
×
2
1.510
1
0.510
×
2
1.020
1
(0.8775)10≈(0.111)2
(13.8775)10 = 2 (保留3位小数)

(13.8775)10 ≈(1101.111)2
注意:
实际上将十进制小数转换成二进制、八进制、十六进制小数过程中小数部分可能始终不为零,因此只能限定取若干位为止。
将十进制数转换为八进制、十六进制数的规则和方法与之相同,只是R(基数)的取值不同。

(279.6875)10 = 8
8 279
8 34 7
8 4 2
8 0 4
低位
高位
(279)10 =(427)8
0.6875
×
8
5.5000
×
整数
5
0.5
8
4.0
4
(0.6875)10=(0.54)8
(279.6875)10 =(427.54)8

(172.6875)10 = 16
16 172
16 10 12(C)
0 10(A)
低位
高位
(172)10 =(AC)16
0.6875
×
16
41250
6875
11.0000
整数
11(B)
(0.6875)10 =(0.B)16
(172.6875)10 =(AC.B)16
因为23=8,24=16,
一位十六进制数可由四位二进制数来表示
一位八进制数可由三位二进制数来表示。
作反向转换:
三位二进制数可用一位八进制数表示
四位二进制数可用一位十六进制数来表示
二进制与其他进制数的转换
最后一组
不足用0补!
将二进制整数
从右向左
每隔3位/4位分
为一组
1.二进制到八/十六进制
将每组按二进
制数向十进制
数转换的方法
进行转换
整数
将二进制小数
从左向右
每隔3位/4位分
为一组
将每组按二进
制数向十进制
数转换的方法
进行转换
小数
例:(10110101)2= ( )8
010 110 101
2 6 5
例:(0.1011)2 =( )8
0. 101 100
0. 5 4
最后一组
不足用0补!
例:(10110101)2= ( )16
1011 0101
B 5
例:(0.10111)2 = ( )16
0.1011 1000
0. B 8
最后一组
不足用0补!
2. 八/十六进制到二进制:
0.1011 0111
0. B 7
例:(0.B7)16 = ( )2
0. 101 100
0. 5 4
例:(0.54)8= ( )2
3. 十六进制与八进制的转换
十六/八进制的转换可以按以下规则进行:
十六进制
二进制
八进制
(EA.15)16
=(1110 1010 . 0001 0101)2
=(11101010 . 00010101)2
=(011 101 010.000 101 010)2
=(352.052)8
二、八、十六进制对照表
二进制数 八进制数
000 0
001 1
010 2
011 3
100 4
101 5
110 6
111 7
二进制数 十六进制数 二进制数 十六进制数
0000 0 1000 8
0001 1 1001 9
0010 2 1010 A
0011 3 1011 B
0100 4 1100 C
0101 5 1101 D
0110 6 1110 E
0111 7 1111 F
表1.1 二、八进制数字对照表
表1.2 二、十六进制数字对照表
二、整型常量
1.整型常量的表示形式
 C语言中整型常量有如下三种表示形式:
(1)十进制。用一串连续的数字来表示,如32768,-768,0等。
(2)八进制。用数字0开头,各位用0~7这8个数之一表示,如010,011等。在C程序中不能在一个十进制整数前面加前导零。
(3)十六进制。用0x或0X开头,各位用0~9以及A~F(或a~f)这16个数之一表示,如0x24B,0X34a等。
三、 整型变量
整型变量是用于存储整型值的变量。整型变量可分为基本型、短整型、长整型和无符号型四种。
基本型的整型变量用类型名关键字int进行定义,如 int a; 或 int a, b, c;
基本型整型变量在一般微机中占用2个字节的存储单元,该变量中允许存放的数值范围是:-32768~32767。
在程序中定义变量时,编译程序会为相应的变量开辟内存单元,但并没有在该单元中存放任何初值,这时变量中的值是无意义的,即称为变量值“无定义”。
C语言规定,可以在定义变量的同时给变量赋初值,即变量的初始化。
如:main()
{ int a=1, b=4, i=8;
……}
整型分配的内存空间
00000000 00000110
整数6在内存中的形式
整型数据的分类
整型常量与变量有短整型(short int)、基本整型(int)、长整型(long int)和无符号型(unsigned),以及有符号型的区分。
在表示一个长整型常量时,应该在其后面加一字母后缀L(或l),如123L,345l等。
无论是短整型常量还是长整型常量,C语言系统都默认为是有符号整数。
无符号整数在数字的后面加上一个字母后缀u或者是U。若是长整型无符号整型常量,则应该加后缀lu或者是LU。
例1.8 p9
四、整数在内存中的存储形式
计算机中最小的存储单位是“位(bite)”,8个二进制位组成一个“字节(byte)”,若干个字节组成一个“字(word)”。
C语言中,一个int整数通常用两个字节存放,其中最高位(最左边一位)用来存放整数的符号,正整数时最高位为0,负整数时最高位为1。
1.正整数
   C语言中,正整数以原码形式存放。如整数5在内存中的二进制码为:0000000000000101。 最大有符号正整数是0111111111111111,它对应的十进制整数是215-1=327767。
2.负整数
C语言中,负整数在内存中是以整数的“补码”形式存放。
求某个二进制码的补码,步骤如下:
(1)求原码的反码,即将0→1,1→0
(2)将所得的反码加1,即得原码的补码。
例如:求-6在内存中存放的形式,步骤如下:
(1)6的原码:0000000000000110
(2)6的反码:1111111111111001
(3)-6的补码:1111111111111010
反过来,如何利用补码来计算对应的负整数?
例如,在内存中某一补码1111111111111101
转换步骤:
(1)将补码的各位取反,得到0000000000000010
(2)将得到的结果加上1,得0000000000000011
(3)把该二进制转换为十进制得数为:3.
(4)因为补码的最高位为1,表示为负数。因此,所得的数为-3。
3.无符号正整数
无符号整数的最高位不再用来存放整型的符号,因此无符号数不可能是负数。
对于无符号整数,最高位(最左边一位)不再用来存储整数的符号,全部用来存放整数。 例如,11111111 11111111
※重点提示:
在C语言中,对于有符号整数,用最高位(最左边一位)用来存储整数的符号,若是正整数,最高位为0,若是负数,最高位放置1。对于正整数用“原码”形式存放,对于负整数用“补码”形式存放。
第三节 实型数据
一、实型常量
  在C语言中可以有两种表示形式:小数形式和整数形式。
(1)小数形式。由整数部分、小数点和小数部分组成。小数点的前面或后面可以没有数字,但是不能同时没有数字。
如,3.14159、.36、0.0、12.、0.158
(2)指数形式。由尾数部分、字母E或e和指数部分组成,其格式如下:
  ±尾数部分E(或e)±指数部分
指数部分只能是整数,且三个组成部分均不能省略。
如,
合法:12.34E+9, ﹣5.453e﹣10
非法:e2、.6E3.5、.e2、e、E
注意,在字母E(或e)的前后以及数字之间不得插入空格。
二、 实型变量
实型变量分为两种类型:
(1)单精度型(float),分配4个字节的存储单元,数值范围约为﹣1038~1038,并提供7位有效位;小于1038的数被处理成零值。
(2)双精度型(double),分配8个字节的存储单元,数值范围约为﹣10308~10308,并提供15~16位的有效位;小于10308的数被处理成零值。
  在计算机内存中,实数一律是以指数形式存放的,而不是小数的形式。
 例如: float a; double b;
a=123456.789e5 ; b=123456.789 ;
第四节 算术表达式
 C语言中基本的运算符是:
+、-、*、/、%。这些运算符需要两个运算对象,称为双目运算符。
除求余运算符外,运算对象可以是整型也可以是实型。
说明:
(1)乘号“*”不能省略,也不能写成代数式中的“×”或“.”
(2)算术运算符两边的运算对象类型必须保持一致才能运算。
(3)若双目运算符两边的类型不一致,则系统将自动按照类型转换规则使两边类型一致后再进行运算。
(4)求余运算符“%”要求参与运算的量必须为整数,且求余的结果符号与被除数相同。如14%-3的结果为2,-21%4的结果为-1。
(5)在C语言中,所有实型数的运算均以双精度方式进行。若是单精度数,则在尾数部分补0,使之转化为双精度数。
一、运算符的优先级、结合性和算术表达式
在C语言中,常量、变量、函数调用以及按C语言语法规则用运算符把运算数连起来的式子都是合法的表达式。
1.算术运算符和圆括号的优先级次序如下:
(由高→低)
( )、 ﹢ 、﹣、* 、/ 、% 、+ 、-
2.算术运算符和圆括号的结合性
  以上所列的运算符,只有单目运算符“+”和“-”的结合性是从右到左,其余运算符的结合性都是从左到右。
3.算术表达式
用算术运算符和括号将运算对象连接起来的、符合C语法规则的式子,称C算术表达式。
运算对象可以是常量、变量、函数等。
 
关于算术表达式的几点说明:
(1)在表达式中可以使用多层圆括号,但左右括号必须配对。运算时从内层开始,由内向外依次计算表达式的值。
(2)在求解表达式的值时,若包含不同优先级的运算符,则按运算符的优先级别由高到低运行,若表达式中运算符的级别相同,则按运算符的结合方向进行。
如表达式a+b-c,因为加号和减号的优先级相同,它们的结合性为从左到右,因此先计算a+b,然后把所得的结果减去c。
二、强制类型转换表达式
 强制类型转换表达式的形式:
(类型名)(表达式)
 其中(类型名)称为强制类型转换运算符。
例如:
(float)(x) 将x转换成浮点型
(double)(8%3)将8%3转换成双精度型
 ※重点提示:在求解表达式的值时,若包含不同优先级的运算符,则按运算符的优先级别由高到低运行,若表达式中运算符的级别相同,则按运算符的结合方向进行。
第五节 赋值表达式
一、赋值运算符和赋值表达式
1.赋值运算符
  赋值符号“=”称为赋值运算符,由赋值运算符组成的表达式为赋值表达式。
格式如下: 变量名=表达式;
其作用是将一个表达式的值赋给另一个变量。
例如:a=5的作用是把5赋给变量a,即把5存入变量a对应的存储单元。
关于简单的赋值运算,要注意以下几点:
(1)赋值符“=”左边必须是变量,右边既可以是常量,变量,也可以是函数调用或表达式。如 z=sqrt(3)+2*c。
(2)赋值表达式右边的“表达式”,也可以是一个赋值表达式,即出现多个赋值符号的情况。例如:a=(b=3),它相当于b=3。
(3)赋值运算符的作用是将赋值运算符右边的值赋值给左边的变量,运算后左边变量的值被右边表达式的值代替,而右边的值不会发生变化。
(4)赋值符“=”与数学中的等号“=”外观相同,但含义、作用不同,如i=i+1,在数学中是不成立的,但在C语言中是正确的。它的含义是将i当前值加1再赋给变量i。
(5)如果赋值号两边的运算对象类型不一致,则系统会自动进行类型转换。
转换的规则是:将赋值号右边表达式的值的类型转换成赋值号左边变量的类型。
二、复合的赋值表达式
1.复合的赋值运算符
  在赋值符“=”之前加上其他运算符,可以构成复合的赋值运算符。
例如 +=:
x+=4 它相当于表达式:x=x+4 即先使x加4,再赋给x。
采用这种复合运算符,一是为了简化程序,二是为了提高编译效率。
C语言规定可以使用以下10种复合赋值运算符,如下:
+=、 ﹣=、 *=、 %=、 <<=、 >>=、 &=、 ^=、 \=、 |=
  其中后五种是有关位运算方面的,将在后续章节中介绍。
 注意:复合赋值运算符的优先级与赋值运算符的优先级相同。
例如1.14:如已有变量n,值为8,计算表达式n+=n*=n-2的值。
因为赋值运算符与复合的赋值运算符的优先级相同,且运算方向自右向左.
计算步骤为:
(1)先计算n-2,该表达式值为6。
(2)再计算n*=6,即n=n*6,此时n的值仍为8,所以表达式值为48。
(3)最后计算n+=48,即n=n+48,此时n的值已经改变为48,所以表达式的值为96。
  即表达式n+=n*=n-2最终的值为96,而n最终的值也是96。
2. 赋值运算中的类型转换
  如果赋值运算符两侧的类型不同,在赋值时系统要自动进行类型转换,转换原则是不论赋值号两侧类型级别高低如何,一律将赋值号右边的类型转换成左边变量的类型,然后再赋值,具体规定如下:
1.将实数赋给整型变量时,舍弃实数的小数部分(不是四舍五入)。例如i是整型变量,则i=2.99;结果i的值为2,在内存中以整数形式存储。
2.将整数赋给单、双精度变量时,数值不变,但将整数转换成浮点数形式存放到左边的变量中。例如:float x,x=2,结果是将2转换成2.000000赋给变量x。
3.将字符型赋给整型时,由于字符型为1个字节,而整型是2个字节,故将字符的ASCII码值放到整型量的低8位中,高8位为0。
4.将整型赋给字符型时,只把低8位赋给字符量。
 ※重点提示:赋值运算符的左边只能是变量,不能是常量或表达式。计算表达式的值时,参加运算的各个数据类型都转换成数据长度最长的数据类型,然后再计算,计算的结果是数据长度最长的数据类型。
三、自加、自减运算符
  自增运算符(++)是使运算量增加1,自减运算符(--)是使运算量减1,它们有以下几种形式:
 ++a a值先增加1后再参与其他运算
a++ 先参与其他运算再使a值增加1
--a a值先减少1后再参与其他运算
a-- 先参与其他运算再使a值减少1
前缀增量运算符
后缀增量运算符
关于自增和自减运算,要注意以下几点:
(1)++a和a++的区别。
(2)增量运算符的运算对象只能为变量,不能是常量或是表达式。
例如,++3、++(i+1)等非法。
(3)不论++(或--)在变量的前面还是后面,对于变量本身增加1(或减少1)都具有相同的效果。
(4)但自增、自减运算符和其他运算符混合使用时,应注意其结合性为“自右向左”,如-a++,即相当于-(a++)。
a++作为一个表达式,该表达式的值为变量a没有增加1之前的值,而++a的值为变量a增加1之后的值。
注意区别:j=i++; j=++i;
四、逗号运算符和逗号表达式
  “,”是C语言提供的一种特殊运算符,用逗号将表达式连接起来的式子称为逗号表达式。
其一般形式为:
表达式1,表达式2,……,表达式n
例如:
x=5,y=6,z=7
说明:
1.逗号运算符的结合性为从左到右,先计算表达式1,最后计算表达式n,最后一个表达式的值就是该逗号表达式的值。
2.逗号运算符再所有运算符中优先级最低。
3.表达式可以嵌套,即表达式1和表达式2本身也可以是逗号表达式,如(x=2*5,x-3),x*4,整个表达式的值应为40。
4.并非所有出现逗号的地方就可以作为逗号表达式,如printf(“%d,%d”,a,b)。
 ※重点提示:自增和自减运算符只能用于变量,不能用于常量或表达式。逗号运算符的结合性为从左到右,最后一个表达式的值就是该逗号表达式的值。
1.4 Turbo C V2.0的基本操作
进入Turbo C 2.0集成开发环境中后,显示:
1.运行一个C语言程序的一般过程
运行一个C语言程序的一般过程:
(1)启动TC,进入TC集成环境。
(2)编辑(或修改)源程序。
(3)编译。如果编译成功,则可进行下一步操作;否则,返回(2)修改源程序,再重新编译,直至编译成功。
(4)连接。如果连接成功,则可进行下一步操作;否则,根据系统的错误提示,进行相应修改,再重新连接,直至连接成功。
(5)运行。通过观察程序运行结果,验证程序的正确性。如果出现逻辑错误,则必须返回(2)修改源程序,再重新编译、连接和运行,直至程序正确。
(6)退出TC集成环境,结束本次程序运行。
2.TC的启动、退出与命令菜单
(1)启动Turbo C: tc ←┘
(2)命令菜单的使用
1)按下功能键F10,激活主菜单。如果主菜单已经被激活,则直接转下一步。
2)用左、右方向键移动光带,定位于需要的主项上,然后再按回车键,打开其子菜单(纵向排列)。
3)用上、下方向键移动光带,定位于需要的子项上,回车即可。执行完选定的功能后,系统自动关闭菜单。
注意:菜单激活后,又不使用,可再按F10/Esc键关闭,返回原来状态。
(3)退出Turbo C
退出TC有两种方法:
1)菜单法:File | Quit(先选择File主项,再选择并执行Quit子项)
2)快捷键法:Alt+“X”(先按下Alt键并保持,再按字母键X,然后同时放开)
(4)编辑并保存一个C语言源程序
(5)运行与查看结果
(1)运行当前正在编辑的源程序文件
选择并执行Run | Run项(快捷键:^F9),程序运行结束后, 仍返回到编辑窗口。
当你认为自己的源程序不会有编译、连接错误时,也可直接运行(即跳过对源程序的编译、连接步骤)。这时,TC将一次完成从编译、连接到运行的全过程。
(2)查看运行结果
选择并执行Run | User Screen项(快捷键:Alt+F5)。查看完毕后,按任一键返回编辑窗口。
如果发现逻辑错误,则可在返回编辑窗口后,进行修改;然后再重新编译、连接、运行,直至正确为止。(共34张PPT)
第7章 指针
本章要点
7.1 变量的地址和指针
7.2 指针变量的定义和指针变量的基类型
7.3 给指针变量赋值
7.4 对指针变量的操作
7.5 函数之间地址值的传递
8.1 变量的地址和指针
1.变量及其地址
在C的程序中要定义许多变量,用来保存程序中用到的数据,包括输入的原始数据、加工的中间结果及最终数据。
C编译系统会根据定义中变量的类型,为其分配一定字节数的内存空间(如字符型占1个字节,整型占2字节,实型占4字节,双精度型占8字节等),此后这个变量的地址也就确定了。
程序中: int i;
float k;
计算机的内存是以字节为单位的一片连续的存储空间,每个字节有一个编号-----地址
…...
…...
2000
2001
2002
2005
内存
0
2003
i
k
编译或函数调用时为其分配内存单元
变量是对程序中数据
存储空间的抽象,每个变量的地址是指该变量所占存储单元的第一个字节的地址
地址的概念
变量的地址起到了寻找变量的作用,好象是一个指针指向了变量,所以常把变量的地址称为“指针”。
2.指针变量
若一个变量专用于存放另一个变量的地址(指针),则该变量称为指针变量。
指针的对象:当把变量的地址存入指针变量后, 我们就可以说这个指针指向了该变量。
变量的存取方法:直接存取和间接存取。
直接存取:直接根据变量名存取数据。
间接存取:通过指针变量存取相应变量的数据。
直接存取与间接存取
直接存取:按变量地址存取变量值
间接存取:通过存放变量地址的变量去访问变量
例 i=3; -----直接存取
指针变量
…...
…...
2000
2004
2006
2005
整型变量i
10
变量i_pointer
2001
2002
2003
2000
3
例 *i_pointer=20; -----间接存取
20
指针变量
…...
…...
2000
2004
2006
2005
整型变量i
10
变量i_pointer
2001
2002
2003
2000
整型变量k
例 k=i; --直接存取
k=*i_pointer; --间接存取
10
例 k=i;
k=*i_pointer;
在C程序中,变量的地址可以通过运算符“&”来得到,该运算符称为“取地址”运算符,它的运算对象是变量或数组元素,得到的结果是变量或数组元素的地址。
  例如:int a,b[10];
  &a: 得到的是变量a的地址
  &b[5]: 得到的是数组元素b[5]的地址
在C语言中,指针被广泛应用,它和数组、字符串、函数间数据的传递等有着密不可分的联系。
7.2 指针变量
7.2.1 指针变量的定义
7.2.2 给指针变量赋地址值
7.2.3 给指针变量赋“空”值
合法标识符
指针的目标变量的数据类型
表示定义指针变量
不是‘*’运算符
例 int *p1,*p2;
float *q ;
static char *name;
注意:
1、int *p1, *p2; 与 int *p1, p2;
2、指针变量名是p1,p2 ,不是*p1,*p2
3、指针变量只能指向定义时所规定类型的变量
4、指针变量定义后,变量值不确定,应用前必须先赋值
7.2.1 指针变量的定义
指针变量:专门存放地址的变量叫指针变量,指针即是指针变量的简称。
一般形式: 类型名 *指针变量名1,*指针变量名2,……;
…...
…...
2000
2004
2006
2005
整型变量i
10
变量i_pointer
2001
2002
2003
2000
指针
指针变量
变量的内容
变量的地址
指针变量
变量
变量地址(指针)
变量值
指向
地址存入
指针变量
指针变量与其所指向的变量之间的关系
指向指针的指针变量
int **p,*s,k=20;
s=&k;
p=&s;
9521
2012
4567
9521
2012
2013
p
s
k
一个指针变量可以通过以下三种方式获得一个确定的地址,从而指向一个具体的对象。
(1)通过求地址运算(&)获得地址值
一般形式:数据类型 *指针名=初始地址值;
赋给指针变量,
不是赋给目标变量
例 int i;
int *p=&i;
变量必须已说明过
类型应一致
例 int *p=&i;
int i;
例 int i;
int *p=&i;
int *q=p;
用已初始化指针变量作初值
7.2.2 给指针变量赋地址值
(2)通过指针变量获得地址值
  可以通过赋值运算,把一个指针变量中的地址值赋给另一个指针变量,从而使这两个指针变量指向同一地址。例如,若有以上定义,则语句:
  p=q;
 使指针变量p中也存放了变量k的地址,也就是说指针变量p和q都指向了变量k。
 注意:在赋值号两边指针变量的基类型必须相同。
(3)通过标准函数获得地址值
  可以通过调用库函数malloc和calloc在内存中开辟动态存储单元,并把所开辟的动态存储单元的地址赋给指针变量。
例 main( )
{ int i=10;
int *p;
*p=i;
printf(“%d”,*p);
}
危险!
例 main( )
{ int i=10,k;
int *p;
p=&k;
*p=i;
printf(“%d”,*p);
}
指针变量必须先赋值,再使用
…...
…...
2000
2004
2006
2005
整型变量i
10
指针变量p
2001
2002
2003
随机
零指针与空类型指针
零指针:(空指针)
定义:指针变量值为零
表示: int * p=0;
p指向地址为0的单元,
系统保证该单元不作它用
表示指针变量值没有意义
#define NULL 0
int *p=NULL:
p=NULL与未对p赋值不同
用途:
避免指针变量的非法引用
在程序中常作为状态比较
例 int *p;
......
while(p!=NULL)
{ ...…
}
void *类型指针
表示: void *p;
使用时要进行强制类型转换
例 char *p1;
void *p2;
p1=(char *)p2;
p2=(void *)p1;
表示不指定p是指向哪一种
类型数据的指针变量
7.2.3 给指针变量赋“空”值
2、使用 指针访问变量(所指的)
main()
{ int a;
int *pa=&a;
a=10;
printf("a:%d\n",a);
printf("*pa:%d\n",*pa);
printf("&a:%x(hex)\n",&a);
printf("pa:%x(hex)\n",pa);
printf("&pa:%x(hex)\n",&pa);
}
运行结果:
a:10
*pa:10
&a:f86(hex)
pa:f86(hex)
&pa:f88(hex)
…...
…...
f86
f8a
f8c
f8b
整型变量a
10
指针变量pa
f87
f88
f89
f86
例:
例: 输入两个数,并使其从大到小输出
main()
{ int *p1,*p2,*p,a,b;
scanf("%d,%d",&a,&b);
p1=&a; p2=&b;
if(a{ p=p1; p1=p2; p2=p;}
printf("a=%d,b=%d\n",a,b);
printf("max=%d,min=%d\n",*p1,*p2);
}
运行结果:a=5,b=9
max=9,min=5
…...
…...
指针变量p1
指针变量p
2000
2008
2002
2004
2006
指针变量p2
整型变量b
整型变量a
5
2006
9
2008
2006
2008
2006
7.3 对指针的操作
7.3.1 引用存储单元
7.3.2 移动指针
7.3.3 指针比较
7.3.1 引用存储单元
两个与指针变量有关的运算符:
&:取地址运算符。为取地址运算符,其作用是返回操作对象(变量或数组元素)的地址。
例如,“&x;”返回变量x的地址,“&a[5];”返回的是数组元素a[5]的地址。
*:指针运算符,其作用是返回以操作对象的值作为地址的变量(或内存单元)的内容。
C语言中提供了地址运算符&来表示变量的地址。
其一般形式为:
&变量名;
如&a表示变量a的地址,&b表示变量b的地址。变量本身必须预先说明。
&与*运算符
含义
含义: 取变量的地址
单目运算符
优先级: 2
结合性:自右向左
含义: 取指针所指向变量的内容
单目运算符
优先级: 2
结合性:自右向左
两者关系:互为逆运算
理解
i_pointer-----指针变量,它的内容是地址量
*i_pointer----指针的目标变量,它的内容是数据
&i_pointer---指针变量占用内存的地址
2000
10
i_pointer
*i_pointer
&i_pointer
i
i_pointer &i &(*i_pointer)
i *i_pointer *(&i)
i_pointer = &i = &(*i_pointer)
i = *i_pointer = *(&i)
…...
…...
2000
2004
2006
2005
整型变量i
10
变量i_pointer
2001
2002
2003
2000
指针变量
&、 *运算符的关系及比较
3
变量i
2000
i_pointer
*i_pointer
i
*i_pointer
&i
i_pointer
i=3;
*i_pointer=3
3
变量i
2000
i_pointer
*i_pointer
i
*i_pointer
&i
i_pointer
i=3;
*i_pointer=3
例:有以下语句:
int a=8,b,*p;
p=&a;
则赋值语句:b=*p;的含义是把指针变量p所指向的变量a的值赋给变量b。这里*P意味着取指针变量所指向变量的内容。
若将指针变量所指向的变量a中存放的数据加1之后赋给变量b,以下语句都可以表示:
①b=a+1;;②b=*p+1;;③b=*(&a)+1;;④b=*p+=1;;⑤b=++*p;。
例: main()
{ int a=7,b=8,*p,*q,*r;
p=&a;q=&b;
r=p;p=q;q=r;
printf(“%d,%d,%d,%d\n”,*p,*q,a,b);
}
输出结果:8,7,7,8
  它们都是单目运算符,优先级高于所有的双目运算符,它们的结合性均是自右向左。在使用这两个运算符需要注意以下几点:
(1)如果已经执行了“p=&a;”语句,若有 &*p
  由于“&”和“*”两个运算符的优先级别相同,但按自右向左方向结合,因此先运算*p,它就是变量a,再执行&运算。因此它等同于&a。
(2)*&a的含义是a。因为先进行&a运算,得到a的地址,再进行*运算,即&a所向的变量,因此*&a等价于a。
(3)(*p)++相当于a++。注意括号是必要的,如果没有括号,就成为*(p++),这时使指针变量本身增1,并不是使p所指的存储单元的值增1。
通过指针引用存储单元
 例如: int *p, k=4, q;
p=&i;
 则赋值语句
 q=*p;
  就是把p所指存储单元(k)的内容(整数4)赋予变量q,这里的*p代表p所指向的变量i。因此,上面的语句等价于
 q=k;
  间接访问运算符必须出现在运算对象的左边,其运算对象是地址或者是存放地址的指针变量。即*号右边也可以是地址值,如
  q=*(&k);
  表达式&i求出变量i的地址,以上赋值语句表示取地址&k中的内容赋给q。由于*和&的优先级相同,且自右向左结合,因此表达式中的括号可以省略,即
  q=*&k;
  下面的语句取指针变量p所指向的存储单元中的内容加1后赋给变量q。
  q=*p+1;
7.3.2 移动指针
移动指针就是通过赋值运算,对指针变量加上或减去一个整数,使指针变量指向相邻的存储单元。因此,只有指针变量指向一片连续的存储单元时,指针的移动才有意义。
移动指针时,系统会根据指针的基类型自动地确定移动的字节数。
…...
…...
2000
2002
2004
2006
P
…...
…...
2000
2002
2004
2006
P
p=p+1
7.3.3 指针比较
指针的比较是通过关系运算符来实现的。设p、q是指向同一数据集合的指针变量,如果p>q表达式的结果为“真”,则表明:p指针变量所指向的元素在q指针变量所指向的元素之后。
…...
…...
2000
2002
2004
2006
P
q
7.4 指针与函数
7.4.1 “传值”与“传址”
7.4.2 函数返回地址
7.4.1 “传值”与“传址”
形参为指针变量时,实参与形参之间的数据传递
若在定义函数时,函数的形参为指针变量,则调用该函数时,对应的实参必须是与形参基类型相同的地址值或已指向某个存储单元的指针变量。
例: int f(int *a,int *b)
{ int s;
s=*a+*b;
return s;
}
main()
{ int x=2,y=4,s;
s=f(&x,&y);
printf(“%d\n”,s)
}
输出结果:6
通过传送地址值,在被调用函数中直接改变调用函数中的变量值
通过传送地址,在被调用函数中对调用函数中的变量进行引用,使得通过形参改变对应实参的值有了可能,可把两个或两个以上的数据从被调函数返回到调用函数。
例: void swap(int *a,int *b)
{ int t;
t=*a; *a=*b; *b=t;
}
main()
{ int x=10,y=20;
printf(“(1)x=%d y=%d\n”,x,y)
swap(&x,&y);
printf(“(2)x=%d y=%d\n”,x,y)
}
输出结果:
(1)x=10 y=20
(2)x=20 y=10
例: void f(int y,int *x)
{ y=y+*x; *x=*x+y; }
main()
{ int x=2,y=4;
f(y,&x);
printf(“%d %d\n”,x,y)
}
8 4
7.4.2 函数返回地址
函数值的类型不仅可以是简单的数据类型,而且还可以是指针类型。其一般形式为:
  类型名 *函数名(形式参数说明列表)
 以下是几点说明:
(1)存储类型有两种,static和extern,默认为extern.
(2)“*函数名”不能写成“(*函数名)”,否则就成了指向函数的指针。
(3)此类函数的调用形式通常是:p=函数名(实际参数列表),其中p通常是调用函数中定义的一个指针变量。
例:以下函数把两个整数形参中较小的那个数的地址作为函数值传回。
int *fun(int *,int *)
main()
{ int a,b,*p;
scanf(“%d%d”,&a,&b);
p=fun(&a,&b);
printf(“a=%d,b=%d,*p=%d\n”,a,b,*p);
}
int *fun(int *x,int *y)
{ if(*x<*y) return x;
returny;
}(共38张PPT)
11.1 字符串的存储形式
11.2 指针与字符串
11.3 字符串的输出
11.4 字符串的输入
11.5 字符串数组
11.6 对字符串的操作
第十一章 字符串
11.1.1 字符串常量
11.1.2 使用一维字符数组存放字符串
11.1.3 将字符串赋给字符数组
11.1 字符串的存储形式
11.1.1 字符串常量
字符串常量
定义:以双引号括起来的,由若干个字符所组成的序列即为字符串常量。
存储形式:
在C中,一个字符用一个字节来存放。
字符串在内存中占的存储空间=字符串长度+1;末尾一位存放结尾符’\0’。
说明:在C中没有字符串数据类型,却有”字符串常量”。
例 “hello”共5个字符,在内存占6个字节 字符串长度5 ,其存储形式为:
h
e
l
l
o
\0
104
101
108
108
111
\0
转义字符,ASCII码值为0, 空值
内存存放字符ASCII码
例 char ch[5]={’B’,’o’,’y’,’\0’};
ch[0]
B
o
y
\0
\0
用字符串常量
ch[1]
ch[2]
ch[3]
ch[4]
在C中,可通过一维字符数组处理字符串。每个字符数组的元素都是一个字符,当这些数组元素的最后一个字符是’\0’时,我们就认为该字符数组保存的是一个字符串。
11.1.2 使用一维字符数组存放字符串
11.1.3 将字符串赋给字符数组
通过对单个元素赋值
方式:
在定义字符数组时对单个数组元素进行赋值。
在使用时对字符串进行赋值。
注意:
字符数组大小必须定义足够大,以便能够保存后面给出的字符串常量,同时也要考虑到字符串结尾符’\0’也占用一个字节,因此字符串长度必须小于字符数组的大小
在字符串的末尾必须赋空值’\0’,用以表示字符串结束
若在字符数组中没有元素存放字符串结尾符’\0’,则表示该字符数组保存的不是字符串。
当所赋初值少于所定义数组的元素个数时,将自动给后面的元素补以初值0
例 char ch[5]={’B’,’o’,’y’,’\0’};
//定义字符数组时对单个元素赋值
例 char ch[5];
ch[0]=’B’;
ch[1]=’o’;
ch[2]=’y’;
ch[3]=’\0’;
//使用时对字符串进行赋值
没有‘\0’,不能作为字符串用!
例 char ch[5]={‘H’,’e’,’l’,’l’,’o’};
ch[0]
H
e
l
l
o
逐个字符赋值
ch[1]
ch[2]
ch[3]
ch[4]
例 char ch[5]={’B’,’o’,’y’,’\0’};
等价于:
char ch[5]={’B’,’o’,’y’};
比较:
char ch1[10]={‘H’ ,’e’ ,’l’ ,’l’ ,’o’};
char ch2[]={‘H’,’e’,’l’,’l’,’o’,’\0’};
char ch3[5]={‘H’ ,’e’ ,’l’, ’l’ ,’o’};
h
e
l
l
o
\0

h
e
l
l
o

h
e
l
l
o
\0

\0
\0
\0
\0
字符串
非字符串
直接把字符串常量赋给字符数组
例 char ch[6]={“Hello”};
char ch[6]=“Hello”;
char ch[]=“Hello”;
用字符串常量
ch[0]
H
e
l
l
o
ch[1]
ch[2]
ch[3]
ch[4]
\0
ch[5]
注意:如果不是在定义时赋值,则须单个元素逐个赋值。
例: char str[10];
str=“Hello”
×
说明:
区分字符常量和字符串常量:字符常量是用单引号引起来的单个字符,字符串常量是用双引号引起来的字符序列,当然,字符序列可以为空,也可以是一个字符。
例:
“”表示空串,在内存中保存的是空字符‘\0’,占一个字节。
“A”表示含有一个大写字母A的字符串,在内存中保存的是字母A和空字符‘\0’,占两个字节。
‘A’表示字母A,在内存中占一个字节。
“ABCDEF”表示含有6个字母的字符串,在内存中占7个字节。
处理字符串常量的时候,字符串结束标志是编译系统自动添加的,不用人为的在字符串最后加上‘\0’ 。
字符数组的引用:
【例】从键盘输入一行字符,存放在字符数组中,然后
逆序输出。
main()
{char a[80],c;
int k=0,j;
printf("\nplease input the chars: ");
scanf("%c",&c);
while(c!= '\n')
{a[k++]=c;
scanf("%c",&c);
}
printf("\n");
for(j=k-1;j>=0;j--)
printf("%c",a[j]);
}
11.2 指针与字符串
在C中,可通过一维字符数组处理字符串,也可通过字符指针处理字符串。
用字符数组实现:

main( )
{ char str[]=“I love China!”;
printf(“%s\n”,str);
printf(“%s\n”,str+7);
}
I
l
o
v
e
C
h
i
str[0]
str[1]
str[2]
str[3]
str[4]
str[5]
str[6]
str[7]
str[8]
str[9]
str
str[10]
str[11]
str[12]
str[13]
n
!
a
\0
用字符指针实现
例 main( )
{ char *str=“I love China!”;
printf(“%s\n”,str);
str+=7;
while(*str)
{ putchar(str[0]);
str++;
}
}
I
l
o
v
e
C
h
i
str
n
!
a
\0
字符指针初始化:把字符串首地址赋给str
char *str;
str=“I love China!”;
str
*str!=0
用字符指针实现
注意:
(1)str是一个存放字符串起始地址的指针变量,不是一个字符串变量。
(2)在程序中出现字符串常量时,系统返回的是一个起始地址,因此可以将一个字符串常量赋值给一个字符指针变量。
例: char str[14];
str=“I love China”; 是错误的!
在这里,str是一个字符数组,数组名str是一个指针常量,第二条语句相当于把字符串“I love China”的首地址赋给str,试图改变str的值
I
l
o
v
e
C
h
i
str
n
!
a
\0
字符指针初始化:把字符串首地址赋给str
char *str;
str=“I love China!”;
字符数组和字符指针处理字符串方式的区别:
字符数组的每个元素存放字符串的每一个字符,末尾存放‘\0’。
字符指针是指向为字符串开辟的内存空间,其值存放的是字符串的首地址。
数组str:
h
e
l
l
o
\0
例:char str[]=“hello”,*pstr=“spels”;内存表示形式如下:
指针str:
h
e
l
l
o
\0
11.3.1 使用printf函数输出字符串
11.3.2 使用puts函数输出字符串
11.3 字符串的输出
通过C语言系统提供的库函数,可以对整个字符串进行输出。
使用printf函数输出字符串
在printf的输出格式中,%s代表字符串,可以通过该格式输出字符串。
例 char str[]=“Hello,Spels!”;
printf( “%s”, str)
输出结果:Hello,Spels!
例 char str[]=“Hello,Spels!”;
printf( “%s”, str+6)
输出结果:Spels!
例 char str[]=“Hello\0,Spels!”;
printf( “%s”, str+6)
输出结果:Hello
11.3.1 使用printf函数输出字符串
11.3.2 使用puts函数输出字符串
使用puts函数输出字符串
程序在使用puts来对字符串进行输出时,要在程序中包含头文件stdio.h。
puts函数在输出完字符串后,会自动输出一个换行符。
puts函数的调用格式:
puts(字符串首地址);
例 char *pstr=“Hello,Spels!”;
puts(pstr);
输出结果:Hello,Spels!
例 char str[]=“Hello,Spels!”;
puts(str);
11.4.1 输入字符串的必要条件
11.4.2 使用scanf函数输入字符串
11.4.3 使用gets函数输入字符串
11.4 字符串的输入
与字符串的输出类似,也可以通过库函数来对整个字符串进行输入。
输入字符串的必要条件
输入项可以是一个字符数组名,这样输入的字符串将存放到该数组中,此时数组应该足够大,以能保存输入的字符串。
输入项还可以是字符指针,这里的字符指针必须是已经指向确切的、足够大的存储空间,以便输入的字符串可以存放到该指针所指向的内存空间中。
11.4.1 输入字符串的必要条件
11.4.2 使用scanf函数输入字符串
使用scanf函数输入字符串
在scanf中,可以使用格式%s来输入一个字符串。
例: char str[20];
scanf (“%s” ,str);
说明:
使用scanf通过%s格式进行输入时,输入的空格和回车都会被认为是数据的分隔符,而不作为数据被读入。
如果输入项是数组元素的地址时,输入数据将从这一地址开始存放。
11.4.3 使用gets函数输入字符串
使用gets函数输入字符串
程序在使用puts来对字符串进行输出时,要在程序中包含头文件stdio.h。
gets函数的调用格式:
gets(待输入字符串首地址);
例 char str1[20];
getts(str1);
执行上面语句,若从键盘输入:
Hi spels!(此处CR代表Enter键,且Hi和spels!中间有空格)
则系统将读入10个字符,包括空格和回车符,依次存放在数组str1中,系统自动用’\0’来取代最后的回车符。
字符串的输入输出
逐个字符I/O: %c
整个字符串I/O: %s
例 用%c
main()
{ char str[5];
int i;
for(i=0;i<5;i++)
scanf(“%c”, &str[i]);
for(i=0;i<5;i++)
printf(“%c”, str[i]);
}
例 用%s
main()
{ char str[5];
scanf(“%s”, str);
printf(“%s”, str);
}
用字符数组名,不要加&
输入串长度<数组维数
遇空格或回车结束
自动加‘\0’
用字符数组名,
遇‘\0’结束
应用举例:
例 main( )
{ char a[5]={‘H’,’e’,’l’,’l’,’o’};
printf(“%s”,a);
}
例 main( )
{ char a[ ]=“Hello”;
printf(“%s”,a);
}
结果:Hello#-=*
h e l l o
0
2
3
1
4
结果:Hello
用“%s”输出时,遇‘\0’结束
main()
{
char a[]={'h','e','l','\0','l','o','\0'};
printf("%s",a);
}

输出:hel
h e l \0 l o \0
数组中有多个‘\0’时,
遇第一个结束
main()
{
int i;
char a[5];
scanf("%s",a);
for(i=0;i<5;i++)
printf("%d,",a[i]);
}
运行情况:
(1)若输入 hel , 正常
(2)若输入 hell , 正常
(3)若输入 hello , 用%s 输出时,会出现问题
h e l \0
h e l l \0
h e l l o
输入字符串长度<数组维数
例 字符串输入举例
H o w \0
a r e \0
y o u \0
#include
main()
{ char a[15],b[5],c[5];
scanf("%s%s%s",a,b,c);
printf("a=%s\nb=%s\nc=%s\n",a,b,c);
scanf("%s",a);
printf("a=%s\n",a);
}
运行情况:
输入:How are you
输出:a=How
b=are
c=you
输入:How are you
输出:a=How
scanf中%s输入时,遇空格或回车结束
运行情况:
输入:How are you
例 若准备将字符串“This is a string.”记录下来,
错误的输入语句为:
(A)scanf(“%20s”,s);
(B)for(k=0;k<17;k++)
s[k]=getchar();
(C)while((c=getchar())!=‘\n’)
s[k++]=c;
11.5.1 二维字符数组存放字符串数组
11.5.2 指针数组处理字符串数组
11.5 字符串数组
11.5.1 二维字符数组存放字符串数组
字符串数组,即数组中的每一个元素存放的都是一个字符串,在C中,可以通过二维字符数组或一维指针数组来处理。
二维字符数组存放字符串数组
二维数组可以看作是一个一维数组,该数组中的每一个元素又是一个一维数组。如果这些一维数组中存放的都是字符串,那么这个二维数组保存的就是一个字符串数组。
如:char str[5][20];
可以把这个二维数组看作是一个由5个字符串组成的一维数组,每个字符串的长度最大可以是19个字符。
例 char name[4][20]={“I”, ”am”, ”a” ,”teacher”};
或 char name[][20]= {“I”, ”am”, ”a” ,”teacher”};
二维数组name存放的就是一个字符串数组,name[0]、name[1]、name[2]、name[3]分别存放了“I”, ”am”, ”a” ,”teacher”四个字符串。可以调用puts(name[i])来对以下字符串进行输出,其中i的取值应该是0~3之间。
11.5.2 指针数组处理字符串数组
指针数组处理字符串数组
可以定义一个一维字符指针数组,来处理字符串数组。若字符指针数组中的每一个元素都指向字符串,即可以认为该指针数组处理的是一个字符串数组。
例 char *pname[4]={“I”, ”am”, ”a” ,”teacher”};或
char pname[]= {“I”, ”am”, ”a” ,”teacher”};
pname中的四个元素pname[0]、pname[1]、pname[2]、pname[3]分别指向“I”, ”am”, ”a” ,”teacher”四个字符串。
系统通过无名的一维字符数组来存储这些字符串,各字符串之间并不一定占用连续的存储单元。
一旦pname中的元素被重新赋值,如果没有其他字符指针指向相应的字符串,则相应的字符串将会丢失。
可以通过pname[i]来引用上述字符串或者通过pname[i][j]来引用该字符串中的字符。
11.6.1 字符串拷贝
11.6.2 求字符串长度
11.6.3 字符串链接
11.6.4 字符串比较
11.6 对字符串的操作
11.6.1 字符串拷贝
程序:
char *strcopy(char *d,char *s)
{ char *p=d;
while(*s!=‘\0’)
{ *p=*s;
p++;
s++;
}
*p=‘\0’;
return d;
}
字符串拷贝
例 编写函数strcopy(char *d,char *s),将指针s所指向的字符串复制到指针d所指向的存储空间中。
相应库函数:
C语言系统为用户提供了库函数strcpy可以实现以上函数功能。
调用格式: strcpy(d,s);
功能:把s指向的字符串拷贝复制到d指向存储空间。
说明:
调用此函数时,程序必须包含string.h头文件
d所指向的空间长度必须足够容纳s串
拷贝时‘\0’一同拷贝
不能使用赋值语句为一个字符数组赋值
例 char str1[20],str2[20];
str1={“Hello!”}; ( )
str2=str1; ( )
11.6.2 求字符串长度
程序:
int strcopy(char *str)
{ int i=0;
while(str[i]!=‘\0’)
i++;
return i;
}
求字符串长度
例 编写函数strlength(char *str),求字符串str的字符串长度。
相应库函数:
C语言系统为用户提供了库函数strlen可以实现以上函数功能。
调用格式: strlen(s);
功能:此函数返回s指向的字符串的实际长度,不包括‘\0’在内。
说明:
调用此函数时,程序必须包含string.h头文件
可以把该函数的返回值赋给一个整型变量,如len=strlen(s)
例 对于以下字符串,strlen(s)的值为:
(1)char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’};
(2)char s[ ]=“\t\v\\\0will\n”;
(3)char s[ ]=“\x69\082\n”;
11.6.3 字符串链接
程序:
char *strcatt(char *d,char *s)
{ int len;
len= strlen(d);
d+=len;
while(*s!=‘\0’)
{ *d=*s;
d++;
s++;
}
*d=‘\0’;
return d;
}
字符串链接
例 编写函数strcatt(char *d,char *s),将s所指向的字符串复制到指针d所指向字符串后面。
该函数先求出d字符串的长度,然后把指针d移动到字符串d的末尾,最后通过while循环把s所指向的字符串的字符依次赋给d指向的字符串的后面
相应库函数:
C语言系统为用户提供了库函数strcat可以实现以上函数功能。
调用格式: strcat(d,s);
功能:把s指向的字符串复制到d指向的字符串的后面。
说明:
调用此函数时,程序必须包含string.h头文件
d所指向的空间长度必须足够容纳d字符串以及s字符串
连接前,两串均以‘\0’结束;连接后,串1的‘\0’取消,新串最后加‘\0’
例 strcpy与strcat举例
#include
#include
void main()
{ char str1[25];
char str2[] = " ", c[]= "C++",
str3[] = "str3";
strcpy(str1, str3);
strcat(str1, str2);
strcat(str1, c);
printf("%s\n", str1);
}
T
r
b
o
C
+
+
0
1
2
3
4
5
6
7
8
9
u
\0
24
…….
T
r
b
o
0
1
2
3
4
5
6
7
8
9
u
\0
24
…….
…….
T
r
b
o
\0
0
1
2
3
4
5
6
7
8
9
u
24
…….
…...
11.6.4 字符串比较
程序:
int strcompare(char *s1,char *s2)
{ while(*s1==*s2&&*s1)
{ s1++;
s2++;
}
return (*s1-*s2);
}
字符串比较
例 编写函数strcompare(char *s1,char *s2),将两个字符串s1和s2进行比较,若s1大于s2,函数返回正值;若s1等于s2,函数返回0;若s1小于s2,函数返回负值。
字符串比较,即把两个字符串相对应的字符拿出来进行比较,返回第一个不相等的字符比较出来的大小即可
相应库函数: C语言系统为用户提供了库函数strcmp可以实现以上函数功能。
调用格式: strcmp(s1,s2);
功能:比较两个字符串
比较规则:对两串从左向右逐个字符比较(ASCII码),直到遇到不同字符或‘\0’为止
返值:返回int型整数,a. 若字符串1< 字符串2, 返回负整数
b. 若字符串1> 字符串2, 返回正整数
c. 若字符串1== 字符串2, 返回零
说明:
调用此函数时,程序必须包含string.h头文件
字符串比较不能用“==”,必须用strcmp
#include
#include
main()
{ char str1[] = ”Hello!", str2[] = ”How are you ”,str[20];
int len1,len2,len3;
len1=strlen(str1); len2=strlen(str2);
if(strcmp(str1, str2)>0)
{ strcpy(str,str1); strcat(str,str2); }
else if (strcmp(str1, str2)<0)
{ strcpy(str,str2); strcat(str,str1); }
else strcpy(str,str1);
len3=strlen(str);
puts(str);
printf(”Len1=%d,Len2=%d,Len3=%d\n”,len1,len2,len3);
}
例 strcmp与strlen举例
How are you Hello!
Len1=6,Len2=12,Len3=18
同课章节目录