C语言学习——函数详解
C语言学习——函数详解
1 模块程序化设计
基本思想: 将一个大的程序按功能分割成一些小模块
特点:
a. 各模块相对独立、功能单一、结构清晰、接口简单、容易理解
b. 控制了程序设计的复杂性:每个模块可以独立设计算法,单独编写和测试
c. 提高元件的可靠性:一个模块中的错误不易扩散和蔓延到其它模块
d. 缩短开发周期:可同时进行集体性开发
e. 避免程序开发的重复劳动
f. 易于维护和功能扩充
开发方法: 自上向下,逐步分解,分而治之
- 一个C程序总由一个main函数和若干个子函数构成;
- C程序执行总是从main函数开始,调用其他子函数后回到main函数,在main函数中结束整个程序的运行。
- 子函数不能嵌套定义,可以互相调用。但不能调用main函数。
- 一个C程序由一个或多个源文件组成;一个源文件由一个或多个函数组成,它是一个独立编译单元。
函数分类:
使用库函数应注意:
1、函数功能、类别
2、函数参数的数目和顺序,及各参数意义和类型
3、函数返回值意义和类型
4、需要使用的包含文件
2 函数的定义、说明与调用
一. 定义:
二. 函数说明:
type functionname([ type [var],……]);
告诉编译系统函数类型、参数个数及类型,以便检验
说明:
- 库函数通过 #include <*.h>形式说明;
- 用户自定义函数在调用前必须说明,除非以下三种情况:
a. 函数返回值是int /char/void型(系统自动按int型处理)
b. 被调用函数定义出现在调用函数之前
c. 已在程序开始/函数外部/调用之前加以说明。
#include <stdio.h>
void main(void)
{
float add(float,float); //函数说明
float a,b,c;
scanf("%f,%f",&a,&b);
c=add(a,b);
printf("sum is %f",c);
}
float add(float x, float y) //函数定义
{
float z;
z=x+y;
return(z);
}
三. 函数定义和函数说明的比较
- 函数定义是一个完整的、独立的函数单位
- 函数说明仅包含函数类型、参数个数及类型,不包含函数体
四. 选用函数的原则
- 简化main函数,增加它的可读性;
- 程序中存在若干相同或变量不同但算法、语句排列相同的语句组;
- 程序中需要选择、跳转时;
- 程序较长,不易分析、调试时。
五. 函数调用
函数名(实参表列);
说明:
- 实参与形参个数相等,类型一致,按顺序一一对应
- 实参必须有确定值,一般按自右向左顺序求值。
调用方式小结:
- 语句调用: printf(“Hello,World!\n”);
- 表达式调用: m=max(a,b)*2;
- 参数调用: m=max(a,max(b,c));
- 形式参数:定义函数时函数名后括弧中的变量名,简称形参
- 实际参数:调用函数时函数名后括弧中的表达式,简称实参
关于形参、实参的说明:
⑴ 形参在函数被调用前不占内存,函数调用时为形参分配内存;调用结束,内存释放;
⑵ 形参是函数的内部变量,只在函数内部才有意义;
⑶ 对每个形参必须指明其名字和数据类型;
⑷ 实参可以是常量、变量或表达式,并且必须有确定的值;
⑸ 实参个数、类型必须与对应的形参一致;若形参与实参类型不一致,自动按形参类型转换;
⑹ 实参对形参的数据传递是值传递,即单向传递,
只由实参传递给形参,反之不可。调用结束后,形参单元被释放,实参单元中的值不变。
3 函数间的数据传递
一. 形实结合值传递(赋值调用)
方式: 函数调用时,为形参分配内存,将实参的值复制到形参中;调用结束,形参被释放,实参维持不变;
特点: 单向传递,形参与实参占用不同的内存,形参的变化不影响实参。
#include <stdio.h>
void swap( int a, int b)
{
int tmp;
tmp=a;
a=b;
b=tmp;
printf("调用函数内%d, %d\n", a, b );
}
void main( void )
{
int a=15, b=5;
swap(a,b);
printf("主函数中%d, %d\n", a, b );
}
二. 地址传递(引用调用)
方式: 函数调用时,将数据的存储地址作为实参复制给形参;
特点: “双向”传递:实参和形参必须是地址常量或变量,均指向同一个的存储单元,数据可同步更新。
#include <stdio.h>
void swap( int *m, int *n)
{
int tmp;
tmp=*m;
*m=*n;
*n=tmp;
printf("调用函数内%d, %d\n", *m, *n );
}
void main( void )
{
int a=15, b=5;
swap(&a,&b);
printf("主函数中%d, %d\n", a, b );
}
- 数组元素作函数实参——值传递
- 数组名作函数参数——地址传递
// 设计一个能求指定字符串长度的函数stringlength。
// 形参用字符数组实现!
// Description: return length of a string
#include <stdio.h>
int stringlength( char str[ ] ) //形参str是字符数组名,在子函数内部是数组名,常量
{
int i=0;
while( str[i] !=0 ) i++;
return i;
}
void main( void )
{
char *STR="This is a test";
printf("The length of \"%s\" is %d.\n", STR, stringlength(STR));
}
// 通过函数输入10个整数,利用冒泡法从小到大排序,最后输出排序结果。
#include <stdio.h>
void Input( int a[10], int n)
{
int i;
printf("Input %d int: ", n);
for( i=0; i<n; i++ )
scanf("%d", a+i);
}
void Sort( int a[], int n)
{
int i, j, tmp;
for( i=0; i<n-1; i++ )
for( j=0; j<n-i-1; j++ )
if( a[j]>a[j+1] )
tmp=a[j], a[j]=a[j+1], a[j+1]=tmp;
}
void Print( int *a, int n)
{
int i;
printf("The sorted number is : ");
for( i=0; i<n; i++ )
printf(" %d ", a[i] );
printf("\n");
}
void main( void )
{
int a[10], SIZE=10;
Input(a,SIZE);
Sort(a,SIZE);
Print(a,SIZE);
}
三、函数的返回值
形式: return(表达式);
**功能:** 从被调用函数返回调用函数,同时把返值带给调用函数
说明:
- 使用return语句只能传递一个返回值给主调函数,函数中可有多个return语句,但每次访问调用函数时只执行其中的一个return语句。
- 若函数与return表达式的类型不一致,按前者为准自动转换
- void型函数无return语句,遇 } 时自动返回调用函数
// Description: 试写一函数实现两个字符串的比较。
#include <stdio.h>
int stringcmp(char *s1,char *s2)
{
while(*s1&&*s2&&*s1==*s2)
s1++,s2++;
return *s1-*s2;
}
void main( void )
{
char s1[80],s2[80];
printf("字符串s1="); gets(s1);
printf("字符串s2="); gets(s2);
printf("stringcmp(s1,s2)=%d\n",
stringcmp(s1,s2));
}
// 符号函数 int sign(int x)返回 x 的符号。
#include "stdio.h"
int sign( int x )
{
if(x>0) return 1;
if(x==0) return 0;
if(x<0) return -1;
}
void main(void)
{
int a=3;
printf("%d",sign(a));
}
四. 外部变量(全局变量)
功能:一经说明,可在随后的所有函数中引用。
说明:一般不建议使用★★★
// Program: EG06-09.c
// Description: bubble sort
#include <stdio.h>
#define SIZE 11
int a[SIZE]={ 0, 2, 9, 6, 8, 7, 4, 5, 3, 1, 0 };
void sort( void )
{
int i, j, tmp;
for( i=1; i<SIZE; i++ )
for( j=1; j<SIZE-i; j++ )
if(a[j]>a[j+1])
tmp=a[j], a[j]=a[j+1], a[j+1]=tmp;
}
void main( void )
{
int i;
printf("Normal numbers: ");
for( i=1; i<SIZE; i++ )
printf(" %d ", a[i] );
printf("\n");
sort();
printf("Sorted numbers: ");
for( i=1; i<SIZE; i++ )
printf(" %d ", a[i] );
printf("\n");
}
4 函数的嵌套与递归调用
一、嵌套调用
函数定义不可嵌套,函数之间是平行的,但允许在一个函数的定义(或调用)中出现另一个函数的调用,这样就出现函数的嵌套调用,即在调用函数中又调用了其他函数。
#include "stdio.h"
int f1(int m, int n) //函数定义
{
int p;
p=m+n;
return(p);
}
int f2(int x, int y) //函数定义
{
int z;
z=f1(x,1)+f1(y,1);
return(z);
}
void main(void )
{
int a,b,c;
a=2,b=5;
c=f2(a,b); //函数调用
printf("c=%d",c);
}
// Description: 输入2个字符串,将二者连接后输出结果。
#include <stdio.h>
int strlen( char str[] )
{
int i=-1;
while( str[++i] );
return i;
}
char *StrCat( char *ptr1, char *ptr2 )
//指针函数(返回值为指针)
{
int len=strlen(ptr1); //函数嵌套调用
ptr1+=len;
len+=strlen(ptr2); //函数嵌套调用
while(*ptr1=*ptr2)
ptr1++, ptr2++;
return ptr1-len;
}
void main( void )
{ char str1[20], str2[20];
printf("请输入2个字符串:\n");
gets(str1);
gets(str2);
printf("合并字符串为%s\n", StrCat(str1,str2)); }
// 简化的解法:
#include <stdio.h>
int strlen( char str[] )
{
int i=-1;
while( str[++i] );
return i;
}
void StrCat( char *ptr1, char *ptr2 ) //此时不是指针函数
{
int len=strlen(ptr1); //函数嵌套调用
ptr1+=len;
while(*ptr1=*ptr2)
ptr1++, ptr2++;
}
void main( void )
{ char str1[20], str2[20];
printf("请输入2个字符串:\n");
gets(str1);
gets(str2);
StrCat(str1,str2));
printf("%s\n", str1);
}
二. 递归调用(温习数学归纳法)
a. 定义:函数直接或间接的调用了自身
int factorial(int n)
{
if(n<0)
{
printf("n<0,input error");
return -1;
}
if(n==0||n==1)
return 1;
if(n>1)
return factorial(n-1)*n;
}
b. 优点:程序清晰、简洁,可读性强,代码紧凑
c. 缺点:速度 慢、占用内存大,必须收敛,每调用函数一次,在内存堆栈区分配空间,用于存放函数变量、返回值等,所以递归次数过多,可能引起堆栈溢出。
// 递归法计算 n!
#include "stdio.h"
int factorial(int); // 函数说明
void main(void)
{
int i;
printf("请输入一个整数:");
scanf("%d",&i);
printf("%d的阶乘是%d",i,factorial(i));
}
int factorial(int n)
{
if(n<0)
{
printf("n<0,input error");
return -1;
}
if(n==0||n==1)
return 1;
if(n>1) //或者用 else ,也可!
return factorial(n-1)*n;
}
// Description: 递归法计算字符串长度:
#include <stdio.h>
int strlen( char *str )
{
if (*str==NULL) //NULL='/0'
{
return 0;
}
else
return strlen( ++str) +1;
}
void main( void )
{
char str[20];
printf("请输入 1个字符串:\n");
gets(str);
printf("字符串长度为%d\n", strlen(str));
}
// Description: 递归法求解Hanoi塔问题
#include <stdio.h>
void move(int num, char one,char two,char three)
{
if(num==1)
printf("%c-->%c\n", one, three);
else
{
move( num-1, one, three, two);
printf("%c-->%c\n", one, three);
move( num-1, two, one, three);
}
}
void main( void )
{
int Num=4;
printf("Step to moving %d diskes:\n", Num);
move(Num,'A','B','C');
}
5 指针函数与函数指针
一、指针函数
语法: 类型名 *函数名(形参列表)
返回值: 返回某一数据的存储地址
存储类型:extern、static
数据类型:对应数据的数据类型 两者类型必须一致
注:调用函数不能使用地址常量接受指针函数返回值
指针函数不能返回局部寿命的数据地址
#include <stdio.h>
int *f(int *x, int *y) //定义指针函数,函数的返回值是指针
{
if(*x<*y)
return x;
else
return y;
}
void main(void)
{
int a=7, b=8, *p, *q, *r;
p=&a;
q=&b;
r=f(p, q);
printf("%d,%d,%d\n", *p, *q, *r);
}
结果:7,8,7
二、函数指针:指向函数的指针
C 语言程序运行时,每一个函数都占用一段内存区域,而函数名就是指向函数所占内存区的起始地址的函数指针(地址常量) 。前面都是通过引用函数名这个函数指针让正在运行的程序转向该入口地址执行函数的函数体,现在还可以把函数的入口地址赋给一个指针变量,使该指针变量指向该函数,然后通过指针变量找到并调用这个函数。这种指向函数的指针变量称为函数指针变量。
函数指针定义: type (*FuctionPointerName)();
其中 type 表示被指函数的返回值的类型; “(*FuctionPointerName)”表示定义了一个指针变量; “()”表示该指针变量指向的函数。
函数指针定义: type (*FuctionPointerName)();
其中 type 表示被指函数的返回值的类型; “(*FuctionPointerName)”表示定义了一个指针变量; “()”表示该指针变量指向的函数。
例如:
int max(int x,int y);
int (*ptr)();
ptr=max;
printf(“Max(%d,%d)=%d\n”, 12, 18, (*ptr)(12, 18) );
函数指针变量 ptr指向 max 函数入口,调用 ptr指向的函数将会返回一个整型数据。
函数指针变量调用函数步骤如下:
(1)先定义函数指针变量:int (*ptr)();
(2)把一个函数的函数名(入口地址)赋予该函数指针变量: ptr=max;
(3)用函数指针变量形式调用函数:(*ptr)(12, 18)。
注意:
函数指针变量 int (*p)( )、指针型函数 int *p( ) 是两个完全不同的量:
int (*p)() 说明 p 是一个指向函数入口的指针变量,该函数的返回值是整型量,(*p)的两边的括号不能少;
int *p() 说明 p 不是一个变量,而是一个指针型函数,其返回值是一个指向整型量的指针,*p 两边没有括号。对照指针型函数的定义,int *p()只是函数头的一部分,一般还应该有形参列表和函数体。
6 main函数与命令行参数
到目前为止,所介绍的 main 函数都是不带参数的,因此 main 的形参都是 void 或直接写成空括号。实际上,main 函数可以带两个形参:argc 和argv,argc 是一个整型变量,argv 是一个指向字符串的指针数组。
main 函数可写为:
void main (int argc,char *argv[])
{
… //函数体
}
7 局部变量和全局变量
一. 局部变量/内部变量
定义: 在函数内定义,只在本函数内有效
说明:
- 局部变量的存储类型:auto(默认) register static
- 不同函数中同名局部变量,占不同内存单元,互不干扰
- 形参属于局部变量
- 复合语句中也可定义局部变量,其作用域只在复合语句范围内
二.全局变量/外部变量
定义:在函数外定义,有效范围从定义变量的位置开始到本文件结束,及有extern说明的其它源文件
说明:
- 全局变量的存储类型:extern(默认) static
- 若外部变量与局部变量同名,则在局部变量的作用范围内,外部变量被屏蔽
- 外部变量定义与外部变量说明不同
#include <stdio.h>
void main(void)
{
int x=1;
{
int x=2;
{
int x=3;
printf("x=%2d\n",x);
}
printf("x=%2d\n",x);
}
printf("x=%2d\n",x);
}
结果:
x=3
x=2
x=1
// Description: 外部变量与局部变量同名测试。
#include <stdio.h>
int a=12, b=6; //定义外部变量a,b
int max(int a,int b) //a,b 为外部变量
{
return a>b?a:b;
}
void main( void )
{
int a=30;
printf("%d\n",max(a,b));
}
结果:30
## 8 变量的存储类型和作用域
一. 若干概念:
- 作用域:变量的使用范围(由存储区域和说明的位置决定)
- 可见性:变量可被作用域内语句调用的属性
- 存在性:变量所分配存储空间的可使用属性
- 存储类型:auto–自动型 register–寄存器型
static–静态型 extern -----外部型 - 静态存储:程序运行期间分配固定存储空间
- 动态存储:程序运行期间根据需要动态分配存储空间
- 用户内存:
二. auto——自动型/堆栈型变量
(1)存储在堆栈区/动态存储区。
(2)auto型变量在函数体内说明或缺省说明;形参缺省说明。
(3)值的暂时性:进入函数时分配堆栈空间,退出函数后,空间自动释放,变量不再存在,不得继续访问。
(4)auto型变量未初始化时,其值无意义,必须先赋初值再引用。
(5)作用域局部性,仅限于定义它的模块。
(6)可见性与存在性基本一致,但具有一定的独立性(在重名变量的作用域上不可见)
三. register——寄存器型变量
- register型变量直接将数值存储在CPU的通用寄存器中,程序运行时无需读写内存即可使用,高效、便捷。
- register型变量只能在函数体内说明、使用;
- 值的暂时性: register型变量进入函数时临时分配寄存器,退出函数后,寄存器自动释放,变量不再存在,不得继续访问。
- 变量未初始化时,其值无意义。必须先赋值再使用!
- 一些系统的通用寄存器字长有限,register型变量不能为double, float型。
- 由于CPU中的通用寄存器个数有限,register型变量数目太多会自动转为auto型。
- 优化的编译系统自动将使用频繁的变量放在寄存器中,不再需要程序员指定谁是register型变量。
四. static——静态型变量
- 静态型变量存储在静态存储区
- 静态型变量定义在函数内部时——局部静态变量,作用域仅限于函数内部;静态型变量定义在函数外部时——外部静态变量,作用域限于本源程序文件(寿命全局);
- 值的永久性:一经说明分配存储空间就一直占用该空间直到程序运行结束;
- 变量未初始化时自动赋0值!
- 可见性与存在性不一致;
- 编译时赋初值(仅一次:每次调用时不再赋初值,保留上次调用结束时的值!)
五. extern——外部型变量
extern型变量存储在静态存储区,一般用于在多个编译单位之间传送数据;
(1)extern型变量定义时缺省存储类型,说明时用extern扩展作用域;
(2)作用域全局性、值的永久性、可见性与存在性一致;
(3)extern型变量未初始化时自动赋0值;
(4)extern型变量可多次说明,但只分配一次空间;
(5)基于全局变量的副作用,建议少用或不用extern型变量。
上一篇: C语言学习——运算符详解