第三章 模块化程序设计
3.1 模块化程序设计的方法与特点
3.2 函数的定义
3.3 无返回值函数的定义与调用
3.4 有返回值函数的定义与调用
3.5 函数嵌套调用和函数声明
3.6 函数的递归调用
3.8 全局变量和局部变量
3.9 指针和指针作为函数参数
3.10 返回指针值的函数
3.12 典型例题
3.1 模块化程序设计的方法与特点
什么是模块化程序设计?
模块化程序设计就是将一个复杂的大问题,分解为一个个独立的简单的小问题 (即模块 ),分别解决简单的小问题,进而解决复杂的大问题。
在 C语言中,这些独立的简单的模块就是函数。
模块分解
功能分解是一个自顶向下、逐步求精的过程。
模块划分的原则是:高聚合、低耦合。
功能分解法 -- 基础面向对象法 -- 主流例 3-1:从键盘输入 10个正整数,编程求它们的阶乘
3.1 模块化程序设计的方法与特点求 n! 的程序代码,
#include <stdio.h>
void main ( )
{ int i,n ; float fact;
fact=1;
scanf(“%d”,&n);
for ( i=1; i<=n; i++)
fact=fact*i;
printf(“fact=%.0f\n”,fact);
}
方法 1,用循环嵌套实现,
外层循环控制求 10次阶乘,
内层循环控制求阶乘
#include <stdio.h>
void main ( )
{ int i,j,n ; float fact;
for(j=1; j<=10; j++)
{ fact=1;
scanf(“%d”,&n);
for ( i=1; i<=n; i++)
fact=fact*i;
printf(“fact=%.0f\n”,fact);
}
}
void main ( )
{ int i,n;
for(i=1; i<=10; i++)
{ scanf(“%d”,&n);
func(n);
}
}
void func( int m )
{ int i ; float fact=1 ;
for( i=1; i<=m; i++ )
fact=fact*i;
printf(“%d!=%.0f\n”,m,fact);
}
#include <stdio.h> 方法 2,用函数实现
编写一个求阶乘的函数,
假设函数名为 func
main函数中用一层循环,
在循环体内通过调用函数
func来计算阶乘
3.1 模块化程序设计的方法与特点图书管理系统功能菜单 图书查询图书信息修改新书入库按作者查询按书名查询按类别查询模块 1 模块 2 模块 3 模块 4 模块 5 模块 6
还书管理图书信息管理 借书管理按时归还过期罚款模块 7 模块 8 模块 9
3.1 模块化程序设计的方法与特点例,编程实现一个图书管理系统
模块化程序设计的特点:
(1)模块相对独立,功能单一编写相对简单,可以独立编写调试
(2)可集体开发,缩短开发周期。不同的模块可以由不同的人员开发,最终能够合成完整的程序
(3)开发出的模块,可在不同的应用程序中多次使用,
减少重复劳动,提高开发效率
(4)测试、更新以模块为单位进行而不会影响其他模块
3.1 模块化程序设计的方法与特点
3.2 函数的定义
1、定义格式,函数类型 函数名 ( 形式参数表 )
{ 函数体 ; }
2、说明,
(1) 函数名,用户定义的合法标识符
(2) 函数类型,函数返回值的类型 ( 函数计算结果的类型 )
若返回值为 int或 char类型,函数定义时可省略不写若函数无返回值,函数定义时应写上 void 类型
(3) 形式参数
① 书写格式,每一个参数都要写上数据类型和参数名,
参数之间以逗号分隔,无参数时应写上 void
② 作用,表示将从主函数中接收哪些类型的数据信息例 3-2,编程输出信息 ******
good!
******
(4) 函数体,由变量声明和语句组成
① 函数体内定义的变量是局部量,只在函数被执行时存在
② 函数体可以为空 (即存在空函数 )
void f (void)
{ }
#include <stdio.h>
3.2 函数的定义
void list( void )
{ printf(“******\n”); }
void main( )
{ list( ) ;
printf(,good \n”);
list ( );
}
分析:
因为输出信息中有两行星号,考虑将输出一行星号写成一个单独的函数,
然后再 main函数中调用这个函数两次
输出一行星号的函数很简单,只写一个
printf函数就行了,所以该函数没有参数和返回值函数定义函数调用例 3-1的程序代码,
#include <stdio.h>
void func( int m )
{ int i ; float fact=1 ;
for( i=1; i<=m; i++ )
fact=fact*i;
printf(“%d!=%.0f\n”,m,fact);
}
void main ( )
{ int i,n;
for(i=1; i<=10; i++)
{ scanf(“%d”,&n);
func(n);
}
}
说明:
1,C程序从 main函数开始执行,
调用其他函数后再返回到
main 函数
2,所有函数都是平行的,
定义函数时是互相独立的函数之间可以互相调用,
但是不能调用 main函数
3.2 函数的定义注意:
函数定义和函数调用不同
函数定义实际上就是编写一个小程序,它可以完成一定的功能
函数调用是在需要时使用已经定义好的函数
一个函数应先定义,再调用
3.3 无返回值函数的定义与调用例 3-2的程序
#include <stdio.h>
void list(void)
{ printf(“******\n”); }
void main( )
{ list( ) ;
printf(,good \n”);
list ( );
}
1、无返回值的无参函数的定义形式
void 函数名 (void)
{ 函数体 ; }
表示无返回值表示无参数
无参函数的调用形式:
函数名 ( ) ;
注意:
函数名后面的小括号不能省略
3.3 无返回值函数的定义与调用
2、无返回值的有参函数的定义形式例 3-1的程序代码,
#include <stdio.h>
void func( int m )
{ int i ; float fact=1 ;
for( i=1; i<=m; i++ )
fact=fact*i;
printf(“%d!=%.0f\n”,m,fact);
}
void 函数名 ( 类型名 形式参数 1,类型名 形式参数 2,… )
{ 函数体 ; }
有参函数的调用形式:
函数名 (实际参数 1,实际参数 2,… ) ;
形式参数
void main ( )
{ int i,n;
for(i=1; i<=10; i++)
{ scanf(“%d”,&n);
func(n);
}
} 实际参数
#include <stdio.h>
void main( )
{ int a,b,t;
printf(,input a,b:,);
scanf(“%d %d”,&a,&b);
t=a; a=b; b=t;
printf(“%d,%d\n”,a,b);
}
例 3-3 用函数实现两个数的交换
3.3 无返回值函数的定义与调用
void swap(int a,int b)
{ int t;
t=a; a=b; b=t;
printf(“%d,%d\n”,a,b);
}
void main( )
{ int x,y;
printf(,input x,y:,);
scanf(“%d %d”,&x,&y);
swap(x,y);
}
#include <stdio.h>
思考:将 swap中 printf语句去掉,
把它写在 main函数的最后,行吗?
这样写的输出结果是什么?
形式参数实际参数
x
y
a
b
t5
9
main swap
59
5
5
9
例 3-3 的执行过程
#include <stdio.h>
void swap(int a,int b)
{ int t;
t=a; a=b; b=t;
printf(“%d,%d\n”,a,b);
}
void main( )
{ int x,y;
printf(,input x,y:,);
scanf(“%d %d”,&x,&y);
swap(x,y);
}
3.3 无返回值函数的定义与调用说明:
(1) 当函数被调用时,形式参数才被分配存储空间,在调用结束后,形参所占的空间将被释放
(2) 实际参数可以是常量,变量或表达式
(3) 实际参数和形式参数的类型应相同或赋值相容
(4) 实参对形参的数据传递是
“值传递”即单向传递
3.4 有返回值函数的定义与调用
1、有返回值的函数的定义形式函数类型 函数名 ( 形式参数表 )
{ 函数体 ; }
2,函数的返回值
函数的返回值通过函数中 return语句 获得
return的作用,① 使流程返回主调函数
② 将函数值送回到调用表达式中
函数类型决定函数返回值的类型一般函数值的类型和 return语句中表达式的类型应一致,如果二者不一致,则以函数值的类型为准
float fun( int x)
{ int y;

return( y );
}
一个函数允许有一个或多个 return语句每个 return后的表达式类型要相同 ; 当执行到其中任何一个 return语句时会立即返回主调函数
int fun( int n )
{ if ( n >10 )
return( n ) ;
else
if ( n==10 )
return( 2*n ) ;
return( n+10 ) ;
}
3.4 有返回值函数的定义与调用
3.4 有返回值函数的定义与调用例 3-1:求 10个正整数的阶乘
#include <stdio.h>
void func( int m ) // 无返回值
{ int i ; float fact=1 ;
for( i=1; i<=m; i++ )
fact=fact*i;
printf(“%d!=%.0f\n”,m,fact);
}
void main ( )
{ int i,n;
for(i=1; i<=10; i++)
{ scanf(“%d”,&n);
func(n);
}
}
一个题目是编写带返回值的函数,还是编写一个无返回值的函数,取决于程序员自己
#include <stdio.h>
float func( int m ) // 有返回值
{ int i ; float fact=1 ;
for( i=1; i<=m; i++ )
fact=fact*i;
return( fact );
}
void main ( )
{ int i,n; float r ;
for(i=1; i<=10; i++)
{ scanf(“%d”,&n);
r = func(n);
printf(“%d!=%.0f\n”,n,r );
}
}
3.4 有返回值函数的定义与调用例 3-4 求两个数的最大公约数和最小公倍数
求最大公约数的方法,展转相除法给出两个数,用大数作被除数,小数做除数,如果两数相除的余数不是 0,则除数作为新的被除数,余数作为新的除数,继续相除,当两数相除的余数是 0时结束,
此时除数就是最大公约数。
求最小公倍数的方法,用两数的乘积除以最大公约数例,a=25 ; b=15; s=a%b=10;
a=b; (a=15) b=s; (b=10) s=a%b=5;
a=b; (a=10) b=s; (b=5) s=a%b=0;
b就是最大公约数最小公倍数,
25*15/5=75
3.4 有返回值函数的定义与调用例 3-4 如果题目要求在 main函数中输出计算结果,则我们需要编写带返回值的函数
编写求最大公约数的函数
int gys(int a,int b) //a为被除数,b为除数
{ int t,s; // t用于交换的中间变量,s存放余数
if(b>a) // 若除数大于被除数,则交换两数
{ t=a; a=b; b=t; }
s=a%b;
while(s!=0)
{ a=b; b=s; s=a%b; }
return (b);
}
while((s=a%b)!=0)
{ a=b; b=s; }
3.4 有返回值函数的定义与调用方法 1,编写一个 main函数,在 main中调用 gys函数,
然后再计算最小公倍数
void main( )
{ int p,q,r,z ;
printf("please input 2 numbers,");
scanf("%d,%d",&p,&q);
r=gys(p,q);
printf(“Greatest common divisor is %d\n",r);
z=p*q/r;
printf("lease common multiple is %d\n",z );
}
#include<stdio.h>
int gys(int a,int b)
{ … }
void main( )
{ … }
3.4 有返回值函数的定义与调用
编写求最小公倍数的函数方法 2,编写求最小公倍数的函数 gbs,编写 main函数,
在 main中调用 gys函数和 gbs函数
int gbs(int a,int b,int h)
{ return(a*b/h); }
说明,参数 a为被除数,b为除数,
h为求出的最大公约数
void main( )
{ int p,q,r ;
printf("please input 2 numbers,");
scanf("%d,%d",&p,&q);
r=gys(p,q);
printf(“Greatest common divisor is %d\n",r);
printf("lease common multiple is %d\n",gbs(p,q,r));
}
3.5 函数嵌套调用和函数声明
1、函数调用的一般形式,函数名 ( 实参列表 ) ;
说明:
形式参数,定义函数 时写在函数名后括号内的变量,
形参前必须有数据类型 int gys(int a,int b)
实在参数,调用函数 时写在函数名后括号内的变量,
实参仅写变量名 r=gys(p,q);
实参列表可包含多个实参,各个实参之间用逗号分隔
实参与形参的 个数应相等,类型一致,顺序一一对应
用无参函数时格式为,函数名 ( ) 注意 ( )不能省略
BC31中按从左至右的顺序对实参求值
2、函数调用的方式
(1) 函数语句
(2) 函数表达式
(3) 函数参数
3.5 函数嵌套调用和函数声明
#include <stdio.h>
int max( int x,int y )
{ int z;
if (x>y) z=x ;
else z=y;
return(z);
}
void main( )
{ int a,b,c,s1,s2;
scanf(“%d %d %d”,&a,&b,&c);
s1 = 2 * max( a,b ) ;
s2 = max(c,max( a,b ) ) ;
printf(“%d,%d\n”,s1,s2);
}
#include <stdio.h>
void list(void)
{ printf(“******\n”); }
void main( )
{ list( ) ;
printf(,good \n”);
list ( );
}
3、函数的调用过程,
(1) 形参与实参各自占有一个独立的存储空间
(2) 形参的存储空间在函数被调用时才分配
(3) 函数返回时,形参占据的临时存储区被撤消注意,函数中对形参变量的操作不会影响到实参
#include <stdio.h>
int f (int x,int y)
{ x = x+2 ; y = y*2 ;
return( x+y ) ;
}
void main ( )
{ int a,b,c ;
scanf(“%d%d”,&a,&b) ;
c = f(a,b) ;
printf(“%d\n”,c) ;
}
假设输入为,3 5
main函数 f 函数
a
b
c
x
y
3
5
3
5
5
10
3.5 函数嵌套调用和函数声明
15
4、函数的嵌套调用在调用一个函数的过程中调用另一个函数
void main( )
{,
fun( ) ;
:
}
void fun( void )
{,
g( ) ;
:
}
main函数
fun函数
g函数调用调用嵌套调用
3.5 函数嵌套调用和函数声明例,函数的嵌套调用
#include <stdio.h>
void fa (void)
{ putchar(?a? ) ; }
void fb (void)
{ fa( ) ;
putchar(? t? ) ;
}
void main (void)
{ putchar(?c? );
fb( ) ;
}
main
输出 c
调用 fb
结束
fb
调用 fa
输出 t
fa
输出 a
结束结束输出结果,cat
3.5 函数嵌套调用和函数声明
3.5 函数嵌套调用和函数声明例 3-5 编程求,输入 k和 n的值n
k
x=1
x?
分析,
假设输入 k=3,n=5,即求 13+23+33+43+53
可分为以下 4步求解:
① 输入 n和 k的值
② 乘方运算,即计算 xk
③ 求和运算,即计算 1k+2k+…n k
④ 输出结果需要编写三个函数:
乘方函数、求和函数 main 函数注意:
main函数调用求和函数,而求和函数又调用乘方函数
main函数中将 n和 k
作为实在参数,把它们的值传给求和函数
3.5 函数嵌套调用和函数声明例 3-5 编程求 n
k
x=1
x?
void main( ) /*主函数 */
{ int k,n;
float sum; /* 结果可能很大,因此用 float型定义变量 */
printf("input,k,n "); /*提示信息 */
scanf("%d%d",&k,&n);
sum=sop(n,k); /*调用函数 sop,n和 k是实参,将它们的值传给 sop的形参,sop的返回值赋给 sum*/
printf("%.0f\n",sum);
}
3.5 函数嵌套调用和函数声明例 3-5 编程求 n
k
x=1
x?
float sop( int m,int t) /*求和函数,m,t 为形参,main函数 中实参 n的值传给 m,k的值传给 t */
{ int i;
float sum,p;
sum=0; /*初始化累加器 */
for (i=1; i<=m; i++ )
{ p=power(i,t); /*调用 power函数,返回值赋给 p*/
sum=sum+p; /* 累加 */
}
return (sum) ; /*返回 sum的值 */
}
例 3-5 编程求 n
k
x=1
x?
3.5 函数嵌套调用和函数声明
float power(int p,int q) /*乘方函数,p,q 为形参,sop函数中实参 i的值传给 p,t的值传给 q(实际上 q的值就是 k) */
{ int i;
float product; /*乘方结果可能很大,因此用实型变量 */
product=1; /*初始化累乘器 */
for(i=1; i<=q; i++)
product=product*p; /*累乘 */
return(product); /*返回 product的值 */
}
3.5 函数嵌套调用和函数声明
void main( )
{ int k,n; float sum;
printf("input,k,n ");
scanf("%d%d",&k,&n);
sum=sop(n,k);
printf("%.0f\n",sum);
}
float sop( int m,int t)
{ int i; float sum,p;
sum=0;
for (i=1; i<=m; i++ )
{ p=power(i,t);
sum=sum+p; }
return (sum) ;
}
float power(int p,int q)
{ int i; float product;
product=1;
for(i=1; i<=q; i++)
product=product*p;
return(product);
}
#include<stdio.h>
例 3-5完整程序:
参数值的传递过程,
main sop power
n? m
k? t? q
i? p
5、函数声明
(1) 在调用函数前,一般要对函数事先作出声明声明有利于编译系统对函数调用的合法性进行检查
(2) 声明格式,函数类型 函数名 ( 形参表 ) ;
(3) 注意事项,
① 函数声明 末尾要加分号
② 形参表中可以只写数据类型如 int f ( int,float ) ;
③ 形参的先后次序不能写错
④ 当被调用的函数定义写在主调函数之前时,允许省略函数声明
⑤ 建议将程序中用到的函数都在程序的前面加以声明
C程序的一般格式,
# 命令函数声明
main函数的定义其他函数的定义
3.5 函数嵌套调用和函数声明函数定义
#include <stdio.h>
void main ( )
{ int f (int x,int y);
int a,b,c ;
scanf(“%d%d”,&a,&b) ;
c = f(a,b) ;
printf(“%d\n”,c) ;
}
int f (int x,int y)
{ x = x+2 ;
y = y*2 ;
return( x+y ) ;
} 函数定义
#include <stdio.h>
int f (int x,int y);
void main ( )
{ int a,b,c ;
scanf(“%d%d”,&a,&b);
c = f(a,b) ;
printf(“%d\n”,c) ;
}
int f (int x,int y)
{ x = x+2 ;
y = y*2 ;
return( x+y ) ;
}
(4) 声明的位置,
① 在 main 函数中声明只能被 main 函数调用函数声明
3.5 函数嵌套调用和函数声明
② 所有函数进行外部声明可以被 main函数和其他函数调用
3.5 函数嵌套调用和函数声明例 3-6 求 1—n之间所有素数的和分析,
该问题的主体结构还是求和运算,而参与求和的数是 1--n之间的素数可以分 3步求解,
① 输入 n的值
② 求和运算
③ 输出结果求和计算时先要判断当前的数是否为素数,把判断素数编成一个函数在求和计算中调用这个函数
3.5 函数嵌套调用和函数声明例 3-6 求 1—n之间所有素数的和
void main( )
{ int n; long s;
scanf("%d",&n);
s=sum(n);
printf("%ld",s);
}
/* 主函数的定义 */
/* 结果可能超出 32767,因此 s用 long int型 */
/* 调用求和函数 sum,返回值赋给变量 s */
/* 注意长整型输出时必须用 %ld */
long sum(int n)
{ int i; long s=0;
for(i=1; i<=n; i++)
if ( isPrime(i)==1 )
s=s+i;
return (s);
}
/*函数 sum的定义 */
/* 调用 isPrime函数,判断当前的 i 是否为素数,如果 i是素数则进行累加计算 */
3.5 函数嵌套调用和函数声明
int isPrime(int m)
{ int i,k;
k=sqrt(m);
for(i=2; i<=k; i++)
if(m%i==0)
return(0);
return(1);
}
/*函数 isPrime的定义 */
例 3-6 求 1—n之间所有素数的和
/*如果 m可以整除 i,说明 m不是一个素数,
结束函数,返回值 0*/
/*当 for循环执行完,执行到这里时,
表示 m是素数,结束函数,返回值 1*/
3.5 函数嵌套调用和函数声明例 3-6 完整程序
int isPrime(int m)
{ int i,k; k=sqrt(m);
for(i=2; i<=k; i++)
if(m%i==0)
return(0);
return(1);
}
void main( )
{ int n; long s;
scanf("%d",&n);
s=sum(n);
printf("%ld",s);
}
long sum(int n)
{ int i; long s=0;
for(i=1; i<=n; i++)
if ( isPrime(i)==1 )
s=s+i;
return (s);
}
#include<stdio.h>
#include<math.h>
long sum(int);
int isPrime(int);
函数声明
3.6 函数的递归调用
1,函数递归调用的概念,
在调用一个函数的过程中又直接或间接地调用该函数自己
2.用递归求解问题的特点
(1)存在递归的终止条件
(2)存在导致问题求解的递归方式
3,优点,程序简洁,代码紧凑缺点,占用存储空间较多,时间效率较差例 3-7,用递归的方法求 n!
1 n=1
n*(n-1) ! n>0n!=
递归的终止条件递归方式
4!=4*(4-1)! 返回值 6
返回值 2
返回值 1
3!=3*(3-1)!
2!=2*(2-1)!
1!=1
主调函数返回值 24调用
3.6 函数的递归调用
#include <stdio.h>
float fac( int n ) ;
void main( )
{ int m ; float y ;
scanf(“%d”,&m );
y=fac(m);
printf(“%d!=%.0f \n”,m,y ) ;
}
float fac(int n)
{ float f ;
if ( n<0 ) { printf(“error!”); f=-1; }
else if ( n==1 ) f=1;
else f=n*fac( n-1 ) ;
return(f ) ;
}
函数声明
3.6 函数的递归调用函数定义递归调用例 3-7:
main函数输入 m 3
y=fac(m)
输出 y 6
调用 fac
m?n 3
因 3!=1
f=3*fac(3-1)
返回 f=6
调用 fac
m?n 2
返回 f=2 返回 f=1
因 2!=1
f=2*fac(2-1)
调用 fac
m?n 1
因 1==1
f=1
结束例 3-7的递归调用过程,
3.6 函数的递归调用
3.8 全局变量和局部变量一、局部变量,
指在一个函数内部定义的变量,它只在本函数的范围内有效,在此函数之外不能使用这些变量说明,
1,main函数中定义的变量也是局部变量,它们只能在
main函数中使用
2,不同函数中可以使用相同名字的变量,它们互不干扰
3,函数的形式参数也是局部变量
4,可在一个复合语句中定义变量,它们只在复合语句的内部有效二、全局变量,在所有函数之外定义的变量 ( 外部变量 )
它的有效范围从定义变量的位置开始到本源文件结束#include <stdio.h>
int p=1,q=5 ;
float f1( int a )
{ float r ;:
}int s[10] ;
int f2( int b,int c )
{ int sum ;:
}float m,n ;
void main( )
{ float x,y ;:
}
全局变量 m和
n的有效范围全局变量数组
s的有效范围全局变量 p和
q的有效范围
3.8 全局变量和局部变量
全局变量的使用增加了函数间数据联系的渠道由于在同一文件中的所有函数都能使用全局变量,所以可以利用全局变量从函数中得到一个以上的返回值例 3-8,求某班成绩的平均分,最高分和最低分
#include <stdio.h>
float max=0,min=100 ; //全局变量
float average( int n ); //函数声明
void main( )
{ int m; float ave2 ;
printf(“input m:”);
scanf(“%d”,&m);
ave2=average(m); //函数调用
printf(“ave=%6.2f\n”,ave2);
printf(“max=%6.2f\n”,max);
printf(“min=%6.2f\n”,min);
}
float average( int n )
{ int i;
float s,ave1,sum=0;
for(i=1; i<=n ; i++)
{ scanf(“%f”,&s);
if (s>max) max=s;
if (s<min) min=s;
sum=sum+s;
}
ave1=sum/n;
return(ave1);
}
3.8 全局变量和局部变量
建议不要过多的使用全局变量
(1) 全局变量在程序的执行过程中一直占用存储单元
(2) 它使函数的通用性降低
(3) 它会降低程序的清晰性
在一个源文件中如果局部变量和全局变量同名,在局部变量起作用的范围内,全局变量不起作用
#include <stdio.h>
int x=10; //全局的 x
void f(void)
{ int x=1; //函数 f的 x
x=x+1;
printf(“x=%d\n”,x );
}
void main( )
{ x=x+1;
printf(“x=%d\n”,x);
f( );
}
输出结果,
x=11
x=2
3.8 全局变量和局部变量
3.8 全局变量和局部变量三、变量的存储类别
1、变量的存储方式
变量从空间上分为局部变量、全局变量。
从变量存在的时间的长短 (即变量生存期 )来划分,
变量可以分为:动态存储变量、静态存储变量。
动态存储变量:用动态存储方式存储的变量。
特点是函数开始调用时为变量分配存储空间,
函数结束时释放这些空间。
静态存储变量:用静态存储方式存储的变量。
特点是在静态存储区分配存储单元,整个程序运行期间都不释放,在程序结束时才释放空间。
3.8 全局变量和局部变量
2、变量的存储类型
auto (自动的 ),动态存储变量,如果局部变量不作存储类型说明,均为自动变量
register(寄存器的 ),可以提高“存取”速度,为从寄存器存取数据比从内存存取数据快
static (静态的 ),静态存储变量,它的存储空间在程序的运行期间都固定不变。 该类变量在其函数调用结束后仍然保留着变量的值,
下次调用该函数,静态局部变量中仍保留上次调用结束时的值。
extern(外部的 ),外部变量声明,可以扩展全局变量的作用域,或引用其它文件的全局变量
3.8 全局变量和局部变量例:静态变量和动态变量的对比
#include <stdio.h>
void f( int c )
{ int a=0;
static int b=0;
a++; b++;
printf("%d,a=%d,b=%d\n",c,a,b);
}
void main( )
{ int i;
for (i=1; i<=3; i++)
f( i );
}
main f
i c
a
b
123 1
01
0123
23
1
输出结果,
1,a=1,b=1
2,a=1,b=2
3,a=1,b=3
3.9 指针和指针作为函数参数
1、变量的地址每个变量在内存中都占有一定字节数的存储单元。
程序编译时,根据程序中定义的变量类型,在内存中为其分配了相应字节数的存储空间。
变量在内存中所占存储空间的首地址,称变量的地址。
该地址存储的内容,就是变量的数值 (变量的内容 )。
2、变量的访问方式
直接访问,通过变量名或地址访问变量的存储区例,scanf (,%d”,&x ) ;
x = x+5 ;
printf (,%d”,x ) ;
一、指针的概念
#include<stdio.h>
void main( )
{ int x,y; float a,b;
x=1; y=2; a=1.5; b=2.4;
printf("%p,%d\n",&x,x);
printf("%p,%d\n",&y,y);
printf("%p,%f\n",&a,a);
printf("%p,%f\n",&b,b);
} 输出结果:
900E:0FFE,1
900E:0FFC,2
900E:0FF8,1.500000
900E:0FF4,2.400000
900E:0FFE
900E:0FFF
900E:0FFC
900E:0FFD
900E:0FF4
900E:0FF5
900E:0FF6
900E:0FF7
900E:0FF8
900E:0FF9
900E:0FFA
900E:0FFB
b
a
x
y
1
2
1.5
2.4
变量名变量的内容变量的地址
3.9 指针和指针作为函数参数
2、变量的访问方式
间接访问,将一个变量的地址存放在另一个变量中例如将变量 x 的地址存放在变量 p 中,访问 x 时首先找到 p,再由 p 中存放的地址找到 x
int x; int *p;
p=&x;
x=3;
printf(“x=%d\n”,*p);
1010
1011
1008
1009
xp
1010 3
3、指针与指针变量
指针,一个变量的指针就是该变量的地址,即指针就是地址
指针变量,存放变量地址的变量,它用来指向另一个变量
3.9 指针和指针作为函数参数二,指针变量的定义
1、指针变量的定义形式,
基 类型 * 指针变量名 ;
2、说明,
(1)基类型 是指针变量指向的变量的数据类型
(2)在变量定义时,* 号 表示该变量是指针变量
( 注意,指针变量是 p1,p2,不是 *p1,*p2 )
(3)指针变量定义后,系统为变量分配一个存储单元,
用来存放地址,根据存储单元的长度分为,
大存储模式 (长指针,4 Byte)和小存储模式 (短指针,2 Byte)
例 int *p1 ;
float *p2 ;
char *cp;
3.9 指针和指针作为函数参数注意,指针变量 p的值是随机值,此时 p 和 x 并无关联
(5) 使指针变量指向一个确定的变量必须进行赋值
p=&x
int x,*p ;
x = 5 ; 10081009
p
2158 10101011
x
51010
3.9 指针和指针作为函数参数例,int x,*p ;
x = 5 ;
(4) 定义指针变量后,系统为其分配存储空间,用以存放其他变量的地址,但在对指针变量赋值前,它并没有确定的值,也不指向一个确定的变量
1010
1011
x
1008
1009
p
2158 5
3.9 指针和指针作为函数参数三、指针变量的赋值
1、指针变量初始化 int x,*p=&x;
2、指针变量的赋值
int x,*p;
p=&x;
int x; float *p;
p=&x; 错注意,只能用同类型变量的地址给指针变量赋值四、指针变量的引用
1、指针运算符 *
(1) p与 *p不同,
p是指针变量,p的值是 p所指向的变量的地址
*p 是 p 所指向的变量,*p的值是 p所指向的变量的值
1008
1009
p
1010 10101011
x
5
3.9 指针和指针作为函数参数
(2) 引用指针变量时的 * 与 定义指针变量时的 * 不同定义变量时的 * 只是表示其后的变量是指针变量
int a,*p ;
p = &a ;
scanf (,%d”,p ) ;
printf (“%d\n”,*p ) ;
*p = 12 ;
printf (“%d\n”,*p ) ;
让 p指向 a
对 a 重新赋值等价于 a=12
即 &a
2,& 与 *
p = &a ;
*&a? *(&a)? *p? a
&*p? &(*p)? &a? p
1008
1009
p
1010
1011
a
3121010
3.9 指针和指针作为函数参数五、指针作为函数参数例 3-9 交换两个变量的值程序 1:
#include <stdio.h>
void swap1(int x,int y)
{ int t;
t=x; x=y; y=t;
printf(“%d,%d\n”,x,y);
}
void main( )
{ int a,b;
scanf(“%d %d”,&a,&b);
swap1(a,b);
}
a
b
x
y
t5
9
main swap1
59
5
5
9
输出结果,
9,5
3.9 指针和指针作为函数参数例 3-9 程序 2:
#include <stdio.h>
void swap2(int x,int y)
{ int t;
t=x; x=y; y=t;
}
void main( )
{ int a,b;
scanf(“%d %d”,&a,&b);
swap2(a,b);
printf(“%d,%d\n”,a,b);
}
a
b
x
y
t5
9
main swap2
59
5
5
9
输出结果,
5,9
注意,虽然在 swap2函数中 x和 y
的值进行了交换,但形参的变化并不会影响到实参,所以 a和 b的值没有发生变化
3.9 指针和指针作为函数参数例 3-9 程序 3 用指针变量作参数
#include <stdio.h>
void swap3(int *px,int *py)
{ int t;
t = *px ;
*px = *py;
*py = t;
}
void main( )
{ int a,b,*p1,*p2 ;
scanf(“%d%d”,&a,&b);
p1 = &a ; p2 = &b ;
swap3( p1,p2) ;
printf(“a=%d,b=%d\n”,a,b);
}
a
b
px
py
t
&b
&ap1
p2
5
说明,该方法是交换 px和
py所指向的变量的值,即交换 main函数中 a 和 b的值所以输出为,a=9,b=5
main swap3
5
9
&a
&b
9
5
3.9 指针和指针作为函数参数例 3-9 程序 4 用指针变量作参数
#include <stdio.h>
void swap4(int *px,int *py)
{ int *t;
*t = *px ;
*px = *py;
*py = *t;
}
void main( )
{ int a,b,*p1,*p2 ;
scanf(“%d%d”,&a,&b);
p1 = &a ; p2 = &b ;
swap4( p1,p2) ;
printf(“a=%d,b=%d\n”,a,b);
}
a
b
px
py
t
&b
&ap1
p2
main swap4
5
9
&a
&b
9
5 随机值
5
说明,该方法可能会破坏系统正常的工作状态,因为 t是一个指针变量,但在函数中并没有给 t 一个确定的地址,这样它所指向的内存单元是不可预见的,而对 *t 的赋值可能带来危害
3.10 返回指针值的函数
函数的返回值可以是一个指针类型数据 (即地址 )
返回指针值函数的定义格式,
数据类型 * 函数名 ( 形参列表 )
{ 函数体 ; }
说明,
定义一个返回指针值的函数与定义普通函数的格式基本类似,只是在函数名前加 *,表明该函数返回一个指针值例,int * fun ( int a,int b )
{ 函数体 ; }
例 3-10 求某班成绩的平均分,最高分和最低分 (用指针实现 )
3.10 返回指针值的函数方法 1,指针变量作参数实现分析:
编写 main函数和一个求平均分,
最高分和最低分的函数 func
main函数中设 3个变量表示平均分,
最高分和最低分
main
aver1
x
y
func
aver
max
min
&aver1
&x
&y
*aver=82
*max=95
*min=66
82
95
66?func函数中用 3个指针变量作形参
将 main函数中的 3个变量的地址传给 func函数的 3个形参,即 func函数的形参指向了 main函数中的变量
在 func函数中通过形参指针变量可以改变 main中变量的值
void func(int n,float *aver,float *max,float *min)
{ int i; float s;
for(i=1; i<=n; i++)
{ printf("input s,");
scanf("%f",&s);
if(s>*max) *max=s;
if(s<*min) *min=s;
*aver=*aver+s;
}
*aver=*aver/n;
}
void main( )
{ int n;
float x=0,y=100,aver1=0;
printf("input n:");
scanf("%d",&n);
func(n,&aver1,&x,&y);
printf("ave=%6.2f,",aver1);
printf("max=%6.2f,",x);
printf("min=%6.2f\n",y);
}
3.10 返回指针值的函数
3.10 返回指针值的函数例 3-10 方法 2,用返回指针值的函数实现分析,
通过函数 average的返回值得到保存平均分变量的地址,进而得到平均分,函数 average中将平均分变量定义成静态的,这样函数结束后该变量仍然存在。
最高分和最低分仍用方法 1得到
main
p
average
aver1
静态的
aver&aver1
&aver1
void main( )
{ float *p;
:
p=average(n,&x,&y);
printf("ave=%6.2f ",*p);
:
}
float *average( … )
{ float *aver;
static float aver1;
aver=&aver1;
:
aver1=sum/n;
return(aver);
}
82
float *average(int n,float *max,float *min)
{ int i; float s,*aver,sum=0;
static float aver1;
aver=&aver1;
for(i=1; i<=n; i++)
{ printf(“input s:");
scanf("%f",&s);
if(s>*max) *max=s;
if(s<*min) *min=s;
sum=sum+s;
}
aver1=sum/n;
return(aver);
}
void main( )
{ int n;
float x=0,y=100,*p;
printf(“input n:”);
scanf("%d",&n);
p=average(n,&x,&y);
printf("ave=%6.2f,",*p);
printf("max=%6.2f,",x);
printf("min=%6.2f\n",y);
}
3.10 返回指针值的函数
3.12 典型例题例 3-11,输入某年某月某日,判断这一天是这一年的第几天分析:
假设输入 2006年 3月 5日,应该把 1,2月的天数加起来,
再加上 5天,就求出该天是这一年的第几天
一年中每个月的天数是固定的,如果该年是闰年,并且输入的月份大于 2,则应再多加一天
编写一个判断闰年的函数 leap_year
编写一个计算总天数的函数 mon,mon调用 leap_year
主函数调用 mon 函数例 3-11 判断闰年的函数 leap_year
int leap_year( int year )
{ int leap; // leap值为 1代表是闰年,值为 0不是
if ( year%400==0 || (year%4==0&&year%100!=0) )
leap=1;
else
leap=0;
return (leap);
}
3.12 典型例题例 3-11 计算总天数的函数 mon
int mon( int year,int month,int day)
{ int sum,leap;
switch (month) /*先计算某月以前月份的总天数 */
{ case 1,sum=0; break;
case 2,sum=31; break;
case 3,sum=59; break;
:,,
case 12,sum=334; break;
default,printf(“data error!\n”);
}
sum=sum+day; /*再加上某天的天数 */
leap=leap_year(year);
if (leap==1 && month>2) /*如果是闰年且月份大于 2*/
sum++;
return (sum);
}
3.12 典型例题函数定义函数声明例 3-11
3.12 典型例题
// 注意函数声明最后加分号
#include<stdio.h>
int mon(int,int,int);
int leap_year(int);
void main( )
{ int d,m,y,sum;
printf("\nplease input year,month,day\n");
scanf(" %d,%d,%d",&y,&m,&d);
sum=mon( y,m,d );
printf("It is the %d th day.",sum);
}
int mon( int year,int month,int day)
{ … }
int leap_year(int year)
{ … }
3.12 典型例题例 3-12,利用指针方法输入 3个数 a,b,c,按大小顺序输出
#include<stdio.h>
void swap(int *,int *);
void main( )
{ int n1,n2,n3; int *pt1,*pt2,*pt3;
printf("please input 3 number:n1,n2,n3:");
scanf("%d,%d,%d",&n1,&n2,&n3);
pt1=&n1; pt2=&n2; pt3=&n3;
if(n1>n2) swap(pt1,pt2);
if(n1>n3) swap(pt1,pt3);
if(n2>n3) swap(pt2,pt3);
printf("sorted numbers,%d,%d,%d\n",n1,n2,n3);
}
void swap( int *p1,int *p2)
{ int p;
p=*p1; *p1=*p2; *p2=p;
}
// 注意函数声明时形式参数的写法
// 注意函数定义时形参的格式
3.12 典型例题例 3-13 编写函数,输入 n为偶数时,计算 1/2+1/4+...+1/n
当输入 n为奇数时,计算 1/1+1/3+...+1/n
#include <stdio.h>
void main( )
{ float peven(int n);
float podd(int n);
float sum; int n;
while (1)
{ scanf("%d",&n);
if ( n>1) break;
}
if(n%2==0) { printf("Even="); sum=peven(n); }
else { printf("Odd="); sum=podd(n); }
printf("%f\n ",sum);
}
float peven(int n)
{ float s=0; int i;
for(i=2; i<=n; i+=2) s=s+1/(float)i;
return(s);
}
float podd(int n)
{ float s=0; int i;
for(i=1; i<=n; i+=2) s=s+1/(float)i;
return(s);
}
3.12 典型例题例 3-13 只编写一个函数
#include <stdio.h>
float pfun (int n);
void main( )
{ float sum; int n;
while (1)
{ scanf("%d",&n); if ( n>1) break; }
sum=pfun(n);
printf(“sum= %f\n ",sum);
}
float pfun(int n)
{ float s=0; int i;
for(i=n; i>0; i=i-2) s=s+1.0/i;
return(s);
}
// 函数声明放在 main函数的外面
// 从 1/n开始倒着加