《C语言程序设计项目化教程》课件第3章_第1页
《C语言程序设计项目化教程》课件第3章_第2页
《C语言程序设计项目化教程》课件第3章_第3页
《C语言程序设计项目化教程》课件第3章_第4页
《C语言程序设计项目化教程》课件第3章_第5页
已阅读5页,还剩404页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

项目3学生成绩管理系统的3.1问题情境3.2问题分析3.3项目设计与实施3.4知识拓展3.5应用实践

本项目要求通过指针和数组配合使用的方式,完成学生成绩管理系统中的学生成绩录入、成绩查询、成绩修改等操作。将各个操作功能设计成独立的函数,各函数间设计良好的接口参数,通过数组存储学生的学号和成绩信息,通过指针实现对学生学号和成绩信息的录入、查找和修改等操作。3.1问题情境

项目2通过数组基本实现了学生成绩管理系统的常用功能。但是,数组作为函数参数在函数调用、参数传递过程中显得比较繁琐,而且数组采用数组名和下标的方式引用数据元素对于数据元素处理起来也显得不便。有没有更为简便的实现方法呢?那就是采用指针类型。使用指针类型能够更为简便和高效地解决函数调用过程中地址的传递问题,通过指针还能方便地访问数组中的元素。通常在C语言程序设计中,指针和数组配合使用是一种常用的方式。3.2问题分析

根据本项目的任务需求,在设计思路上,首先,通过自顶向下、逐步求精的结构化设计思想对整个程序进行功能模块划分;其次,根据各个功能模块进行函数接口的设计和函数调度关系的设计;最后,在各个函数功能实现过程中应注意:3.3项目设计与实施

(1)对用户输入的可能错误检查是否严格?程序的容错性如何。(如学号重复、成绩不符合规定等。)

(2)如何实现插入(添加)、删除、排序等的一般处理功能。

(3)如何实现多门课程的处理。

(4)如何保存输入及修改结果。

(5)菜单如何组织。

【项目分析】

学生成绩管理系统从功能上来讲,包含学生成绩的录入、修改、查询、输出等一系列具体任务,因此,本系统在设计思路上仍然按照模块化程序设计的基本思想去完成各功能模块的程序设计,在数据处理上引入了指针,主要通过指针实现数据的传递与操作,完成系统各项功能的实现。

【项目实施】

程序实现所需要的预处理命令及函数声明语句如下:

#include<stdio.h>

#include<stdlib.h>

#include<conio.h> //控制台相关函数定义

#include<windows.h>

#defineMAX_NUM45

voidinput(int*StudentID,int*StudentScore,intMax); //成绩输入

intfindStudentID(intStudentID[],intfindID,intMax); //查找指定学号的学生

voidDisplayScore(int*StudentID,int*StudentScore,intMax); //显示所有记录

voidDlspMainMenu(); //显示主菜单

voidDlspQueryMenu(); //显示查询子菜单

charchoice(); //获取用户在菜单中的选择

intScoreInput(int*ID,int*Score);

voidQueryScore(int*ID,int*Score,intMax);

voidEditScore(int*ID,int*Score,intMax);

功能模块1:主菜单设计模块

(1)模块功能:主菜单操作界面程序设计。

(2)接口函数:DispMainMenu()。

(3)程序代码如下:

voidDispMainMenu()

{

printf("*************学生成绩管理系统V1.0*************\n");

printf("*\t1--成绩录入2--成绩修改*\n");

printf("*\t3--成绩查询0--退出*\n");

printf("***********************************************\n");

printf("请选择(0--3):");/*显示菜单信息*/

}

程序运行结果如图3-1所示。图3-1主菜单显示程序运行结果功能模块2:成绩录入模块

(1)模块功能:完成录入学生学号及成绩的C语言程序设计。

(2)接口函数:ScoreInput(int*ID,int*Score),input(int*StudentID,int*StudentScore,intMax)。

(3)程序代码如下:

voidinput(int*StudentID,int*StudentScore,intMax)

{

inti;

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

{

printf("\n请输入第%d个学生的学号:\t",i+1);

scanf("%d",StudentID+i);

printf("\t\t成绩:\t");

scanf("%d",StudentScore+i);

}

printf("\n您的输入信息是:\n");

DisplayScore(StudentID,StudentScore,Max);

}

intScoreInput(int*ID,int*Score)

{

intnumber;

printf("\n请输入本次录入的学生人数:");

scanf("%d",&number);

if(number>MAX_NUM)

{

printf("您输入的人数太多,大于%d人\n",MAX_NUM);

return(0);

}

input(ID,Score,number);//number是局部变量

return(number);

}图3-2录入学生成绩的运行图功能模块3:成绩查询模块(查询所有学生成绩)

(1)模块功能:显示所有学生的成绩信息。

(2)接口函数:DispMainMenu(),voidDisplayScore(int*StudentID,int*StudentScore,intMax)。

(3)程序代码如下:

//查询菜单设计

voidDispQueryMenu()

{

printf("***************请选择查询方式*********************\n");

printf("*\tl--按学号查询2--查询全部记录*\n");

printf("**************************************************\n");

printf("请选择(1--2):");//显示菜单信息

}

/*取用户对菜单的选择,返回用户选择的对应字符键ASCII值*/

charchoice()

{

charselect;

while(!kbhit()); //kbhit()等待用户输入,非0有击键,适用Microsoft平台

select=getche(); //取用户输入,回显,getch()则不回显,适用Microsoft平台

return(select);

}

/*显示所有的学生成绩信息*/

voidDisplayScore(int*StudentID,int*StudentScore,intMax)

{

inti;

printf("\n序号\t学号\t\t成绩\n");

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

printf("%d\t%d\t%d\n",i+1,*(StudentID+i),*(StudentScore+i));

printf("\n");

}

voidQueryScore(int*ID,int*Score,intMax)

{

charselect;

inti,

findID;

DispQueryMenu();

select=choice();

switch(select)

{

case'1':

printf("\n按学号查询\n请输入学生的学号:");

scanf("%d",&findID);

if((i=findStudentID(ID,findID,Max))!=-1)

{

printf("\n查找结果如下:\n");

printf("\t学号\t\t成绩\n");

printf("\t%d\t%d\n",ID[i],Score[i]);

}else //没有找到

printf("您输入的学号不存在!\n");

break;

case'2':

printf("\n查询全部学生信息!\n");

DisplayScore(ID,Score,Max);

break;

default:

printf("选择错误!\n");

}

}图3-3所有学生成绩查询运行结果功能模块4:成绩查询模块(按学号查询学生成绩)

(1)模块功能:按照指定的学生学号查询学生成绩信息。

(2)接口函数:intfindStudentID(int*StudentID,intfindID,intMax)。

(3)程序代码如下:

intfindStudentID(int*StudentID,intfindID,intMax)

{

inti;

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

if(*(StudentID+i)==findID)break;

if(i<Max)//找到i<Max,说明是执行break后结束循环,即找到指定的学生

returni;

elsereturn-1;

}图3-4按学号查询程序运行结果功能模块5:学生成绩修改模块

(1)模块功能:按照输入的学生学号,修改指定的学号所对应的成绩信息。

(2)接口函数:voidEditScore(int*ID,int*Score,intMax)。

(3)程序代码如下:

voidEditScore(int*ID,int*Score,intMax)

{

inti,findID; //findID--要查找的学生的学号

printf("\n请输入学生的学号:");

scanf("%d",&findID);

if((i=findStudentID(ID,findID,Max))!=-1)

{

printf("原成绩:%d\n",*(Score+i));

printf("请输入新成绩:");

scanf("%d",Score+i);

}

elseprintf("您输入的学号不存在!\n"); //没有找到

}

程序运行结果如图3-5所示。图3-5修改成绩程序运行结果功能模块6:主函数模块

(1)模块功能:实现系统变量的初始化和各个子函数程序模块的调度。

(2)接口函数:voidmain(intargc,char*argv[])。

(3)程序代码如下:

voidmain(intargc,char*argv[])

{

charselect;

intID[MAX_NUM],

Score[MAX_NUM]; //保存学生学号及姓名的数组

intcurrent_number; //number指本程序当前正在处理的学生人数

charcmdLine[]="winmine.exe";/*WINDOWS应用程序名,并在系统的PATH

中能找到,否则要指定路径,如C:\WINDOWS\System32\winmine.exe*/

select=0;

while(select!='0')

{

system("cls"); //执行DOS系统命令,括号内参数是系统命令名

DispMainMenu();

select=choice(); //取用户输入

switch(select)

{

case'0':

printf("\n您选择的是退出测试!\n");//beep(300,400);

continue;

case'1':

printf("\n您选择的是成绩录入!\n");

current_number=ScoreInput(ID,Score);

break;

case'2':

printf("\n您选择的是成绩修改!\n");

EditScore(ID,Score,current_number);

break;

case'3':

printf("\n您选择的是成绩查询!\n");

QueryScore(ID,Score,current_number);

break;

default:

printf("\n选择错误!请重新选择!\n");//小于0,大于4

}

system("PAUSE");//用户的输入是<字符>+Enter,清除<Enter>

}

}

选择“0—退出”,程序运行结果如图3-6所示。图3-6退出程序运行图

【项目总结】

从项目3的程序设计可以看出,该项目程序设计时需注意以下几点:

(1)本系统使用模块化程序设计思想。

(2)各个模块的功能设计虽然相互独立,但应注意各个模块相互之间的衔接关系。

(3)指针的概念及在程序设计中的灵活应用是本系统完成的关键。

【知识总结】

完成项目3需用到的C语言主要知识点有:

(1)自定义函数及其调用规则。

(2)模块化程序设计的基本思想。

(3)结构体数据类型的定义及结构体变量的使用。

(4)分支结构与循环结构程序设计。

(5)指针的概念及基本用法。

本项目相关知识总结如下。3.3.1指针的概念

1.指针和指针变量的概念

1)指针的基本概念

计算机中的所有数据都是顺序存放在存储器中的。一般把存储器中的一个字节称为一个内存单元(亦称存储单元),不同数据类型的值所占用的内存单元数亦不同。为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址,通常把这个地址称为指针。内存单元的指针和内存单元的内容是两个不同的概念。可以用一个通俗的例子来说明它们之间的关系。我们到银行去存(或取)款时,银行工作人员将根据我们的帐号去查找存款单,找到之后在存单上写入存(或取)款的金额。在这里,帐号就是存单的指针,存款数是存单的内容。

2)指针变量的基本概念

对于一个内存单元来说,单元的地址即为指针,其中存放的数据是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址,或称为指向某内存单元的指针。图3-7指向变量C的指针变量P如图3-7所示,设有字符变量C,其内容为字符‘K’(ASCII码为十进制数75),C占用了0110H号存储单元(地址用十六进制表示)。设有指针变量P,内容为0110H。这种情况下,我们称为“P指向变量C”或者“P是指向变量C的指针”。

严格地说,一个指针是一个地址,是一个常量,而一个指针变量却可以被赋予不同的指针值,是变量。但通常把指针变量简称为“指针”。为了避免混淆,我们约定:“指针”是指地址,是常量;“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。

既然指针变量的值是一个地址,那么这个地址不仅可以是变量的地址,也可以是其他数据结构。在一个指针变量中存放一个数组或一个函数的首地址有何意义呢?因为数组或函数在内存中都是连续存放的,通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。这样一来,凡是出现数组、函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可。这样做,将会使程序的概念十分清楚,程序本身也精练、高效。在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。用“地址”这个概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。这也是引入“指针”概念的一个重要原因。

2.指针变量的定义与应用

1)指针变量的定义

指针变量定义的一般形式为

类型说明符*指针变量名;

其中:*为说明符,表示这是一个指针变量;指针变量名为用户自定义标识符;类型说明符表示该指针变量所指向的变量的数据类型。例如:

int*p1;该定义表示p1是一个指针变量,它的值是某个整型变量的地址,或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量,应由向p1赋予的地址来决定。

例如:

staicint*p1; /*p1是指向静态整型变量的指针变量*/

float*p2; /*p2是指向浮点变量的指针变量*/

char*p3; /*p3是指向字符变量的指针变量*/

应该注意的是,一个指针变量只能指向同类型的变量,如p2只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。

2)指针变量的赋值

指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。同时,指针变量的赋值只能赋予地址,决不能赋予任何其他数据,否则将引起错误。

在C语言中,所有初始变量的地址都是由编译系统自动和随机分配的,对用户完全透明,用户不知道变量的具体地址。

C语言中提供了取地址运算符&来表示变量的地址,其一般形式为

&变量名

如&a变示变量a的地址,&b表示变量b的地址。变量本身必须预先说明或定义。

设有指向整型变量的指针变量p,如要把变量a的地址赋予p,可以有以下两种实现

方法:

(1)指针变量初始化的方法。即在初始定义指针变量的同时赋予其初值,如:

inta;

int*p=&a;

(2)赋值语句的方法。即在指针变量定义后,利用赋值语句形式完成对指针变量的赋值操作,如:

inta;

int*p;

p=&a;

不允许把一个数值直接赋予指针变量,如下面的赋值是错误的:

int*p;

p=1000;

被赋值的指针变量前不能再加“*”说明符。如写为

*p=&a也是错误的。

3)指针变量的运算

指针变量可以进行某些运算,但其运算的种类是有限的,它只能进行赋值运算和部分算术及关系运算。

(1)取地址运算符“&”。

取地址运算符“&”是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf()函数及前面介绍指针变量赋值中,我们已经了解并使用了“&”运算符。

(2)取内容运算符“*”。

取内容运算符“*”是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在“*”运算符之后必须跟指针变量。

需要注意的是,指针运算符“*”和指针变量说明中的指针说明符“*”不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型;而表达式中出现的“*”则是一个运算符,用以表示指针变量所指的变量。

【例3.1】指针运算符*和&的使用。

程序如下:

/*程序功能:通过指针变量的定义、赋值及简单应用说明指针运算符*和&的使用*/

#include<stdio.h>

voidmain()

{inta=5,*p; /*定义整型变量a和指针变量p*/

p=&a; /*利用&取得变量a的地址并给指针变量p赋值*/

printf("%d\n",*p); /*利用*取得指针变量所指向变量的内容*/

}

程序运行结果如下:

5

程序说明:在定义了指针变量p后,利用指针变量p取得整型变量a的地址,并在最后输出中,利用指针变量取得变量a的值进行输出。

赋值运算:指针变量的赋值运算有以下几种形式:

①指针变量初始化赋值。例如:

inta=5,*p1=&a;②把一个变量的地址赋予指向相同数据类型的指针变量。例如:

inta,*pa;

pa=&a;/*把整型变量a的地址赋予整型指针变量pa*/

③把一个指针变量的值赋予指向相同类型变量的另一个指针变量。例如:

inta,*pa=&a,*pb;

pb=pa;/*把指针变量pa实际存储的地址赋予指针变量pb*/由于pa、pb均为指向整型变量的指针变量,因此可以相互赋值,并且赋值后,指针变量pa、pb均指向整形变量a。

④利用指针形式引用其所指变量。例如:

intn1=0,n2,*p=&n2,*q=&n1;

*p=*q;/*等价于n2=n1;*/

⑤把数组的首地址赋予指向数组的指针变量。例如:

inta[5],*pa;

pa=a;/*数组名表示数组的首地址,故可直接赋予指向数组的指针变量pa*/也可写为

pa=&a[0];/*数组第一个元素的地址也是整个数组的首地址,也可赋予pa*/

当然也可采取初始化赋值的方法:

inta[5],*pa=a;

⑥把字符串的首地址赋予指向字符类型的指针变量。例如:

char*pStr;

pStr="cLanguage";或用初始化赋值的方法写为

char*pc="CLanguage";

这里需要说明的是,指针指向字符串,并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。在后面还将详细介绍。

⑦把函数的入口地址赋予指向函数的指针变量。例如:

int(*pf)();

pf=f;/*其中f为函数名*/

加减算术运算:对于指向数组的指针变量,可以进行整数类型的加减法运算。设pa是指向数组a的指针变量,则pa+n、pa-n、pa++、++pa、pa--、--pa运算都是合法的。当然,参与加减算术运算的变量n只能为整型。

指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某个数组元素)向前或向后移动n个位置。应当注意,数组指针变量向前或向后移动一个位置和地址值加1或减1在概念上是完全不同的。如果指针变量加1,表示指针变量移动1个位置指向下一个数据元素的地址,而不是在原地址值的基础上真实地加整型数值1。例如:

inta[5],*pa;

pa=a; /*pa指向数组a,也是指向数组元素a[0]*/

pa=pa+2; /*pa指向数组元素a[2],即pa的值为&pa[2]*/指针变量的加减运算只适用于指向数组的指针变量,对指向其他类型变量的指针变量作加减运算是毫无意义的,并容易导致灾难性的后果。

两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则运算也毫无意义。

①两个指针变量相减。两个指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。

例如,pf1和pf2是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H。由于浮点数组每个元素各占用4个字节,所以pf1-pf2的结果为(2010H-2000H)/4=4,表示pf1和pf2所指数组元素之间相差4个元素。两个指针变量不能进行加法运算。例如,pf1+pf2就毫无实际意义。

②两个指针变量进行关系运算。指向同一数组的两个指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:

pf1==pf2表示pf1和pf2指向同一数组元素

pf1>pf2表示pf1处于高地址位置

pf1<pf2表示pf2处于高地址位置

指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如:

#defineNULL0

int*p=NULL;

对指针变量赋0值和不赋值是不同的。指针变量未赋值时,其初始化结果可能是任意值,是不能使用的,否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。

4)指针变量应用示例

以下C程序示例用以增强对本节内容的理解及巩固。通读这些程序,并编译运行它们,以掌握指针变量的基础应用。

【例3.2】指针变量的应用。

/*程序功能:熟悉指针变量的基础应用*/

#include<stdio.h>

voidmain()

{

intnNumber; /*定义普通整型变量*/

int*pPointer; /*定义指向整型变量的指针变量pPointer*/

nNumber=15;

pPointer=&nNumber; /*给指针变量赋值*/

printf("nNumberisequalto:%d\n",nNumber); /*输出变量nNumber的值*/

*pPointer=25; /*通过指针改变nNumber的值*/

printf("Now,nNumberisequalto:%d",nNumber);/*证明nNumber值已被改变*/

}程序运行结果如下:

nNumberisequalto:15

Now,nNumberisequalto:25

【例3.3】指针变量的应用。

/*程序功能:熟悉指针变量的基础应用*/

#include<stdio.h>

voidmain()

{

inta=10,b=20,s,t,*pa,*pb;

pa=&a;pb=&b;

s=*pa+*pb;/*对于指针变量所指内容的引用*/

t=*pa**pb; /*指针运算符优先级高于算术运算符*/

printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b);

printf("s=%d\nt=%d\n",s,t);/*两种算式运算结果相同*/

}程序运行结果如下:

a=10

b=20

a+b=30

a*b=200

s=30

t=200

【例3.4】输出三个任意输入整型数值的最大数和最小数。

/*程序功能:输出三个任意输入整型数值的最大数和最小数*/

#include<stdio.h>

voidmain()

{

inta,b,c,*pmax,*pmin;

printf("inputthreeintnumbers:");

scanf("%d%d%d",&a,&b,&c); /*任意输入三个整型数值*/

if(a>b)

{pmax=&a;pmin=&b;} /*比较a和b*/

else

{pmax=&b;pmin=&a;}

if(c>*pmax)pmax=&c; /*比较c和a、b的最大值*/

if(c<*pmin)pmin=&c; /*比较c和a、b的最小值*/

printf("max=%d\nmin=%d\n",*pmax,*pmin);

}程序运行结果如下:

inputthreeintnumbers:234567↙

max=67

min=233.3.2指针变量作函数参数

在函数应用中,函数的参数不仅可以是整型、实型、字符型、数组等数据,也可以是指针类型,以实现将地址传送到另一函数中参与操作

【例3.5】指针的应用。

/*程序功能:利用AddFive()函数实现对所传递变量加5的操作*/

#include"stdio.h"

AddFive(intN) /*定义函数AddFive()*/

{N=N+5;

}

main()

{intv=20;

printf("Myoriginalvalueis%d\n",v);

AddFive(v); /*调用函数AddFive()实现对变量v加5的操作*/

printf("Mynewvalueis%d",v);

}程序运行结果如下:

Myoriginalvalueis20

Mynewvalueis20

在这里应说明的是,本例并没有得到预想的结果25。问题出在哪儿?其实本例程描述了一个函数调用中的“值传递”,因此,AddFive()函数中“N=N+5”这一行虽改变了N值,但原始的变量v在主函数main()里依然没变,所以,程序输出显示主函数中的变量v的实际值前后并没有发生变化。如果要实现正确的程序功能,可以通过传递指针到函数来达到目的。下面就是修改过的程序,在AddFive()函数形参说明要加*号,函数调用时要用&号,以表示传递的是指针。

#include"stdio.h"

AddFive(int*N) /*定义函数AddFive()*/

{*N=*N+5; /*实现对指针所指的变量加5*/

}

main()

{intv=20;

printf("Myoriginalvalueis%d\n",v);

AddFive(&v); /*注意变量前加&,表示传递的是变量v的指针*/

printf("Mynewvalueis%d",v);

}

程序运行结果如下:

Myoriginalvalueis20

Mynewvalueis25

注意:本例程序是把指针所指的变量加5,而并不是指针自己加5。

【例3.6】指针变量作函数参数参与应用。

/*程序功能:指针变量作为函数的参数参与传递*/

#include<stdio.h>

outval(int*p1,float*p2) /*函数形参为指针类型*/

{printf("Theintvalueis%d\n",*p1);

printf("Thefloatvalueis%f\n",*p2);

}

voidmain()

{inta=5,*p_int;

floatb=4.5,*p_float;

p_int=&a;

p_float=&b;

outval(p_int,p_float); /*实参传递的是指针类型的值*/

}

程序运行结果如下:

Theintvalueis5

Thefloatvalueis4.500000说明:

(1)本例利用指针变量p_int和p_float作为实参对函数outval()进行调用。

(2)在函数outval()的定义中,必须使用相同类型、相同个数的形式参数和实际参数相对应。

(3)在函数outval()调用开始时,实参变量p_int和p_float利用“值传递”的方式将它们的值(分别指向变量a和b的地址)传送给形参变量p1和p2。

(4)在函数outval()调用开始后,可利用形参变量p1和p2所指向的变量内容参与各种运算。本例中只是对其指向的内容进行输出显示。

(5)在函数outval()调用结束后,形参变量p1和p2将被释放,实参变量p_int和p_float保留原指向。

(6)如果在函数outval()中,利用指针对变量a和b的值进行了变化,函数调用结束后该变化将会影响至主函数。

【例3.7】指针变量作函数参数参与应用。

/*程序功能:指针变量作为函数参数参与传递,并对主函数的变量值产生影响*/

#include<stdio.h>

outval(int*p1,float*p2) /*函数形参为指针类型*/

{*p1=*p1*10;

*p2=*p2*10; /*对指针所指变量进行运算变化*/

}

voidmain()

{inta=5,*p_int;

floatb=4.5,*p_float;

p_int=&a;

p_float=&b;

printf("The1thoutput__beforeuseoutval()function\n");

printf("Theintvalueis%d\n",*p_int);

printf("Thefloatvalueis%f\n",*p_float);

outval(p_int,p_float);

printf("The2thoutput__afteruseoutval()function\n");

printf("Theintvalueis%d\n",*p_int);

printf("Thefloatvalueis%f\n",*p_float);

}程序运行结果如下:

The1thoutput__beforeuseoutval()function

Theintvalueis5

Thefloatvalueis4.500000

The2thoutput__afteruseoutval()function

Theintvalueis50

Thefloatvalueis45.000000

可见,当利用指针变量作为实参对函数进行调用的过程中,如果函数对指针所指变量的值进行了变化,函数运行结束后,该变化将会影响主函数中实参的值。3.3.3指针与数组

1.概述

指针和数组有着密切的关系,任何能由数组下标完成的操作也都可用指针来实现,但程序中使用指针可使编程代码更紧凑、更灵活。

一个数组是由连续的一块内存单元组成的,数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的,每个数组元素按数据类型的不同占有几个连续的内存单元。一个指针变量既可以指向一个数组,也可以指向一个数组元素。如果要使一个指针变量指向一个数组,可把数组名或数组第一个元素的地址赋予它;如果要使指针变量指向某个数组的第n个元素,可以把该数组第n个元素的地址赋予它或把数组名加n赋予它。例如

charstr[80],*pl;

pl=str;这里,将数组名str赋予指针变量pl。在C程序中,不带下标的数组名为数组的起始地址。因此,数组名也是指向数组的指针。

语句“pl=str;”还可以这样表示为pl=&str[0];,即把数组str的第一个元素的地址赋予指针变量pl。如果希望访问str中的第5个元素,可以这样写:

str[4]

或*(pl+4)两种表示形式都将返回第5个元素的值。因为数组下标是从0开始的,所以要访问数组的第5个元素,str的下标应为4。还可以将指针pl加4,以存取第5个元素。例如:

#include<stdio.h>

voidmain()

{inta[10]={1,2,3,4,5,6,7,8,9,10},*p=&a[3],*q=p+2;

/*指针变量p指向a[3],指针变量q指向a[5]*/

printf("%d,%d\n",*p,*q);

}程序运行结果如下:

4,6

2.通过指针引用数组元素

数组指针变量说明的一般形式为

类型说明符*指针变量名;

其中,类型说明符表示该指针所指数组的类型。从一般形式可以看出,指向数组的指针变量和指向普通变量的指针变量的说明是相同的。

设有数组a,指向a的同类型指针变量为pa,则通过对指针概念的理解就存在以下关系:

pa、a、&a[0]均指向同一单元。它们都是数组a的首地址,也是数组元素a[0]的地址。

pa+1、a+1、&a[1]均指向数组元素a[1]。类似可知pa+i、a+i、&a[i]指向数组元素a[i]。

应该说明的是,指针pa是变量,而a、&a[i]都是常量(程序运行中,数组所分配的内存单元地址固定不变),在编程时应予以注意。

引入指针变量后,就可以使用两种方法来访问数组元素了。

1)下标法

下标法是用a[i]或*(a+i)形式访问数组元素,在前面介绍数组时都是采用这种方法。

例如:

#include<stdio.h>

voidmain()

{

inta[5],i;

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

{

a[i]=i; /*给数组元素赋值*/

printf("a[%d]=%d\n",i,*(a+i)); /*输出数组元素*/

}

printf("\n");

}

程序运行结果如下:

a[0]=0

a[1]=1

a[2]=2

a[3]=3

a[4]=4

2)指针法

指针法是采用

*(pa+i)或pa[i]形式,用间接访问的方法来访问数组元素。

【例3.8】指针变量访问数组元素。

/*程序功能:利用指针变量访问数组各个元素,并输出所有数组元素值*/

#include<stdio.h>

voidmain()

{

inta[5],i,*pa;

pa=a; /*指针变量指向数组a首地址*/

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

{*pa=i+1; /*给数组元素赋值*/

pa++; /*指针指向下一个元素*/

}

pa=a; /*重新使pa指向数组a首地址*/

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

{printf("a[%d]=%d\n",i,*(pa+i)); /*顺序输出各数组元素的值*/

}

}说明:

(1)程序中有两个语句pa=a;。因为在主函数中,当第一个循环结束后,指针pa已指向a[5],而后面需要重新对数组a进行引用,所以须重新使指针变量指向数组首地址。

(2)在实现数组内容输出时,也可采用下面的实现方法:

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

{

printf("a[%d]=%d\n",i,*pa);

pa++;

}

甚至,整个程序更可简写为例3.9所示。

【例3.9】指针变量访问数组元素。

/*程序功能:利用指针变量访问数组各个元素,并对内容进行输出*/

#include<stdio.h>

voidmain()

{

inta[5],i,*pa=a;

for(i=0;i<5;)

{*pa=i;

printf("a[%d]=%d\n",i++,*pa++);/*注意++运算符的合理使用*/

}

}

程序运行情况同例3.8。

使用下标法的程序执行速度比使用指针法的慢。原因是利用下标操作数组元素所使用的时间比使用指针操作数组元素所花费的时间长。尤其在顺序访问数组的过程中,使用指针来引用数组元素的程序段效率会更高。但如果是随机访问数组,则采用下标法引用更好,因为它通常是与计算一个复杂的指针表达式一样快,而且易于编码和理解。

下面程序段是较为典型的数组元素运算操作,分析程序段的输出结果是什么?

#include<stdio.h>

voidmain()

{

inta[]={2,4,6,8,10},y=0,x,*p;

p=&a[1]; /*注意:指针p指向数组第二个元素*/

for(x=1;x<3;x++) /*循环段*/

y+=p[x]; /*累计求和:p[x]等价于*(p+x)*/

printf("%d\n",y);

}综上所述,若有定义“inta[10],*pa=a;”,则利用指针给出数组元素地址和内容的表示形式主要包括:

(1)

pa+i和a+i均表示a[i]的地址,它们均指向数组a的第i个元素,即指向a[i]。

(2)

*(pa+i)和*(a+i)都表示pa+i和a+i所指对象的内容,即为a[i]。

(3)指向数组元素的指针,也可以表示成数组的形式。如pa[i]与*(pa+i)等价,a[i]与*(a+i)等价。也就是说,它允许指针变量带下标。假若pa=a+5;,则pa[2]就相当于

*(pa+2),由于pa指向a[5],所以pa[2]就相当于a[7]。而pa[-3]就相当于*(pa-3),它表示a[2]。

3.指向数组的指针变量作函数参数

前面已经介绍了利用指针变量引用数组元素,下面介绍指向数组的指针变量作为函数参数的使用,具体如下例所示。

【例3.10】键盘输入5门功课成绩,自定义函数完成平均分的计算。

/*程序功能:指向数组的指针变量作为函数参数的使用*/

#include<stdio.h>

floataver(float*pa)

{

inti;

floatave,s=0;

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

s=s+*pa++; /*利用指针引用数组元素,求和*/

ave=s/5;

returnave; /*返回平均值*/

}

voidmain()

{

floatscore[5],ave,*sp;

inti;

sp=score; /*指针指向数组score的首地址*/

printf("\input5scores:\n");

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

scanf("%f",&score[i]);

ave=aver(sp); /*将数组首地址传递到函数aver()*/

printf("averagescoreis%5.2f",ave);

}程序运行结果如下:

input5scores:

8687969776↙

averagescoreis88.40

说明:

(1)例3.10主函数中,指针sp指向数组score的首地址,并在调用函数aver()时,对指向数组的指针变量sp进行了引用,表示传递给函数aver()的是一个地址值。自定义函数aver()形参说明也是一个指针类型的变量,以接收主函数传递过来的指针值。在程序设计中,为了保证函数的正确调用,应保证形参、实参数据类型的一致性。在这里,实参、形参都是指向符点类型的指针变量。

(2)函数aver()中,由于利用指针变量pa接收了sp所指地址值,且sp指向主函数数组score的首地址,所以,指针变量pa也指向主函数数组score的首地址。

(3)程序行“s=s+*pa++;”中,由于运算符

*

++

运算符优先级高,所以就等同于“s=s+*pa;pa++;”,即先引用指针pa所指对象求和,而后使指针pa指向下一个元素。

下面程序段将在自定义函数sum()中,利用指针变量接收主函数传递过来的一个数组元素的地址,并利用指针对数组元素进行相关操作。

#include<stdio.h>

sum(int*p)

{p[0]=p[-1]+p[1]; /*利用指针形式引用数组元素并进行操作*/

}

voidmain()

{

inta[10]={1,2,3,4,5,6,7,8,9,10};

sum(&a[2]); /*调用函数sum()*/

printf("%d",a[2]);

}程序运行结果如下:

6

说明:主函数调用sum()函数时,传递的实参为a[2]的地址,所以,在函数sum()中,指针变量p指向数组元素a[2]。函数sum()中的语句“p[0]=p[-1]+p[1];”是以指针变量引用数组元素,该语句等同于语句“*p=*(p-1)+*(p+1);”或“a[2]=a[1]+a[3];”,其结果对数组元素a[2]的值产生了影响。

“值传递”与“地址传递”有何区别呢?如下例所示。

#include<stdio.h>

swap1(intc0[],intc1[]) /*数组名形参*/

{intt;

t=c0[0];c0[0]=c1[0];c1[0]=t; /*交换结果影响到主函数*/

}

swap2(int*c0,int*c1) /*指针形参*/

{intt;

t=*c0;*c0=*c1;*c1=t; /*交换结果影响到主函数*/

}

swap3(intc0,intc1) /*普通变量形参*/

{intt;

t=c0;c0=c1;c1=t; /*交换结果不影响到主函数*/

}

voidmain()

{inta[2]={3,5},b[2]={3,5},c[2]={3,5};

swap1(a,a+1); /*以数组名形式传递地址*/

swap2(&b[0],&b[1]); /*利用&运算符取址传递*/

swap3(c[0],c[1]); /*数组元素值传递*/

printf("%d%d\n%d%d\n%d%d",a[0],a[1],b[0],b[1],c[0],c[1]);

}

程序运行结果如下:

53

53

35说明:函数swap1()、swap2()都是“地址传递”,利用指针接收主函数传递过来的数组元素地址(注意函数形参类型说明,以及函数调用时传递的地址形式),所以,在函数内部进行所指向数组元素值的交换后,其结果对主函数产生了影响。而swap3()函数是“值传递”,在主函数调用开始后,变量c0和c1分别取得主函数c[0]和c[1]的值,虽然swap3()函数对c0和c1的值进行了交换,但函数运行结束后,由于变量c0、c1、t会被释放,其交换并未对主函数数组c的两个元素产生影响。另外,不能企图通过改变指针形参的地址值,而使指针实参的值也改变。例如:

#include<stdio.h>

swap(int*p1,int*p2)

{int*p;

p=p1;p1=p2;p2=p;

}

voidmain()

{inta,b;

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

swap(&a,&b);

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

}

程序运行结果如下:

10,20↙

1020/*swap()函数交换结果不影响a和b的值*/

3.4.1宏定义

在C语言源程序中,允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。3.4知识拓展宏提供了一种词法符号替换机制,它们可以带也可以不带类似函数中的形式参数。所以,宏定义也分为无参宏定义和带参宏定义两种。

1.无参宏定义

无参宏的宏名后不带参数。其定义的一般形式为

#define标识符字符串

其中的“#”表示这是一条预处理命令,凡是以“#”开头的均为预处理命令;define为宏定义命令;标识符为所定义的宏名;字符串可以是常量、表达式、格式串等。

在前面经常使用过的符号常量的定义就是一种无参宏定义。

【例3.11】利用无参宏定义形式定义各种类型符号常量并输出。本例通过定义各种数值及字符串,完成一个给定半径情况下圆面积的计算输出。

程序如下:

#include"stdio.h"

#definePI3.14159 /*定义π值*/

#defineR4 /*定义半径值*/

#defineINFO"Theareais:" /*定义面积输出提示*/

main()

{printf("%s%f\n",INFO,PI*R*R); /*以给定提示字符串输出面积值*/

}

程序运行结果如下:

Theareais:50.265440说明:

(1)根据一般C语言程序中变量的命名规则,符号常量的定义一般习惯使用大写字母表示,主要是因为通常在一般变量的定义中常使用小写字母的形式。当然,符号常量也可以以小写字母命名。

(2)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,属于一种简单的代换。其中,所表示的字符串可含任意字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开源程序的过程中发现问题。

(3)宏定义常用于程序中反复使用的常量或表达式。例如,利用符号常量M表示表达式(y*y+3*y)。在编写源程序时,所有使用到表达式(y*y+3*y)的地方都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(y*y+3*y)表达式去置换所有的宏名M,然后再进行编译。

(4)宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。

【例3.12】宏名表示一个表达式的应用。

程序如下:

#include"stdio.h"

#defineM(y*y+3*y)/*定义宏名M表示指定表达式*/

voidmain()

{

ints,y;

printf("inputanumber:");

scanf("%d",&y);

s=3*M+4*M+5*M;/*M在预处理过程中被替换为所表示的表达式*/

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

}

程序运行结果如下:

inputanumber:12↙

s=2160本例中首先进行宏定义,定义M表示表达式(y*y+3*y),在语句“s=3*M+4*M+5*M;”处作了宏调用。在预处理时,经宏展开后该语句变为

s=3*(y*y+3*y)+4*(y*y+3*y)+5*(y*y+3*y);

需要注意的是,在宏调用中,表达式(y*y+3*y)两边的括号不能省略,否则会发生错误。如果在例3.12中,将M的宏定义变为

#difineMy*y+3*y

则在宏展开时将得到下述语句:

s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y;

当输入y值12后,程序运行结果为s=1836。

此结果显然与加有括号的原题计算结果差异很大。因此,在作宏定义时必须十分注意,应保证在宏代换之后不发生错误。

【例3.13】宏名所表示表达式末尾带分号的实际使用。

/*程序功能:程序设计中宏名所表示表达式末尾带分号的实际使用情况*/

#include"stdio.h"

#defineAAA500+1000;

voidmain()

{

inti;

i=AAA/*注意该命令行未加;*/

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

}

程序运行结果如下:

i=1500

本例所定义宏名AAA在实际宏代换过程中,将表达式500+1000连同所带分号一起置换。在命令行i=AAA中,因所替换表达式已带有分号,所以现命令行无须再增加末尾

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论