chapter 5 函数


C语言程序设计

chapter 5 函数

1. 函数初识

1.模块化编程的含义
将重复性的代码封装成一个方法,需要的时候调用这个方法。
一般将这样的方法称为函数,完成方法的过程就叫模块化编程。

2.函数一般划分为自定义函数和系统函数
自定义函数:用户自己定义函数的功能
系统函数:系统函数就是系统自带的函数,它的使用需要引入头文件。

3.函数声明与定义

函数的声明:是指告诉编译器有这样一种方法,但是至于该方法是否已经实现,并不清楚。

函数的定义:是指对方法的实现,可以省略函数的声明,但是不能省略函数的定义。

函数定义格式

返回类型  函数名(参数列表){
    函数体
}
示例:
int abs(int num); //函数声明
int abs(int num){  //函数定义
    if(num<0) return -num;
    else return num;
}

无返回类型  函数名(参数列表){
    函数体
}
示例:
void print(int arr[], int l, int r); //函数声明
void print(int arr[], int l, int r){  //函数定义
    for(int i=l; i<=r; i++){
        printf("%d ", arr[i]);
    }printf("\n");
}

函数定义须知
(1)函数名是标识符,按照变量名的规则命名。
(2)参数列表可以为空,即无参函数,也可以有多个参数,参数之间用逗号隔开,参数列表中的每个参数,由参数类型说明和参数名组成。
(3)函数体是实现函数功能的语句,返回类型是void的函数无返回值,其余函数执行过程中碰到return语句,将在执行完return语句后直接退出函数,不去执行后面的语句。
(4)返回值的类型一般是int,double,char等类型,也可以是数组。有时候函数不需要返回任何值,这时只需要定义函数返回类型为void。

根据以上须知,可得函数定义格式有如下4种:
(1)返回类型 函数名(参数列表)
(2)返回类型 函数名()
(3)void 函数名(参数列表)
(4)void 函数名()

4.调用方法
根据返回类型可以将函数分为两大类:有返回值,没有返回值。
对于有返回值的函数,调用时必须以值的形式出现在表达式中,比如:
int num=abs(-1);
对于没有返回值的函数,直接写:函数名(参数);
调用发生在定义之后,直接调用即可;
调用发生在定义之前,则要在调用之前声明,才可以调用。

2. 形式参数和实际参数

1.形式参数和实际参数的概念
(1)函数定义中的参数名称为形式参数
如long long C(int n, int m)中的 n与 m是形式参数,形式参数变量名可以替换。
(2)实际参数是指实际调用函数时传递给函数的参数的值。
如调用函数 C(6,3),这里 6, 3就是实际参数,其中 6传递给 n,3传递给m。

2.调用函数的执行过程
(1)计算实际参数的值
(2)把实际参数传递给被调用函数的形式参数,程序执行跳到被调用的函数中
(3)执行函数体,执行完后如果有返回值,则把返回值返回给调用该函数的地方继续执行

3.传递参数
函数通过参数来传递输入数据,参数通过传值机制来实现。
前面的程序中的函数都采用了传值参数,采用的传递方式是值传递,函数在被调用时,用克隆实参的办法得到实参的副本传递给形参,改变函数形参的值并不会影响外部实参的值。

#include<stdio.h>

int abs(int num){
    if(num<0) return -num;
    else return num;
}

void print(int arr[], int l, int r){
    for(int i=l; i<=r; i++){
        printf("%d ", arr[i]);
    }printf("\n");
}

int main(){
    int a[]={-1,2,-3,4,-5,6,-7,8,-9};
    int n=sizeof(a)/sizeof(int);
    print(a, 0, n-1);
    for(int i=0; i<n; i++){
        int num=abs(a[i]);
        printf("%d ", num);
    }printf("\n");
    print(a, 0, n-1);
    return 0;
}

输出结果:
-1 2 -3 4 -5 6 -7 8 -9
1 2 3 4 5 6 7 8 9
-1 2 -3 4 -5 6 -7 8 -9

4.引用参数
函数定义时在变量类型符号之后形式参数名之前加“&”,则该参数是引用参数,把参数声明成引用参数,实际上改变了缺省的按值传递参数的传递机制,引用参数会直接关联到其所绑定的对象,而并非这些对象的副本,形参就是对应形参的别名,形参的变化会保留到实参中。

#include<stdio.h>
void swap(int &a, int &b){//引用,改变的是原地址的值
    int temp=a;
    a=b;
    b=temp;
}
void print(int arr[], int l, int r){
    for(int i=l; i<=r; i++){
        printf("%d ", arr[i]);
    }printf("\n");
}

int main(){
    int a[]={-1,2,-3,4,-5,6,-7,8,-9};
    int n=sizeof(a)/sizeof(int);
    print(a, 0, n-1);
    for(int i=0; i<n; i++){
        for(int j=i+1; j<n; j++){
            if(a[i]>a[j]) swap(a[i], a[j]);
        }
    }
    print(a, 0, n-1);
    return 0;
}

输出结果:
-1 2 -3 4 -5 6 -7 8 -9
-9 -7 -5 -3 -1 2 4 6 8

3. 作用域

程序中所用到的变量并不总是有效可用的,而限定它可用范围就是它的作用域。
由于作用域的不同,我们把变量分为两种:全局变量和局部变量。

1.全局变量
定义在函数外部的变量称为全局变量。
全局变量的作用域是从变量定义的位置开始到文件结束。
(1)变量x,y定义在所有的花括号外部,具有全局作用域,是全局变量
(2)全局变量的作用域使得函数间多了一种传递信息的方式
(3)过多的使用全局变量,会增加调试的难度,会降低程序的通用性
(4)全局变量在函数执行的全过程中一直占用内存单元
(5)全局变量在定义时若没有赋初值,则为0

2.局部变量
定义在函数内部作用域为局部的变量称为局部变量。
函数的形参和在该函数内定义的变量都称为该函数的局部变量。
(1)局部变量只在块内可见,块外无法访问
(2)不同函数的局部变量相互独立,不能访问其他函数的局部变量
(3)局部变量的存储空间是临时分配的,函数执行完毕,该空间就被释放
(4)定义在全局作用域中的变量可以在局部作用域中使用,变量还可以在内部作用域中被重新定义,定义在内部作用域的名字将会自动屏蔽定义在外部作用域的相同的名字。

4. 变量的存储方式和生存期

从变量的作用域(也就是空间),可以将变量分为全局变量和局部变量。
从变量的生存期(也就是时间),可以将变量分为动态存储方式(自动变量,寄存器变量,形式参数)和静态存储方式(静态局部变量,静态外部变量,外部变量)。

1.自动变量:auto int a=1; //在动态存储区内分配内存单元,auto可以省略,省略后隐含指定为"自动存储类比"
2.静态变量:static int a=1; //在静态存储区内分配内存单元,在整个程序运行期间不释放。
3.寄存器变量:register int a=1;//在CPU的寄存器中分配内存单元,不用多次存取,提高执行效率
4.外部变量:extern a; //将一定义的外部变量 a的作用域扩展至此

5. 内部函数和外部函数

1.内部函数:又称静态函数,只能在本文件中调用:static int fun(int a, int b);
2.外部函数:可以被其它文件调用:extern int fun(itn a, int b); //C语言规定,如果省略 extern 则默认为外部函数。

6. 函数案例练习

题目传送

【题目描述】在程序中定义一函数 digit(n,k),它能分离出整数 n 从右边数第 k 个数字(n≤10^9,k≤10)。

输入格式:正整数 n 和 k。
输出格式:一个数字。
输入样例:31859 3
输出样例:8

#include<stdio.h>
int digit(int n, int k){
    for(int i=1; i<k; i++){
        n/=10;
    }
    return n%10;
}
int main(){
    int n,k; scanf("%d%d", &n, &k);
    printf("%d\n", ans);
    return 0;
}

题目传送

已知:

\[m=\dfrac{\max(a,b,c)}{\max(a+b,b,c) \times \max(a,b,b+c)};|a|,|b|,|c| ≤ 50 \]

输入 a,b,c, 求 m 。
把求三个数的最大数 max(x,y,z) 分别定义成函数和过程来做。

输入格式:输入只有一个行三个整数,分别为 a, b, c.
输出格式:输出一行一个小数,为答案,保留三位小数。
输入样例:1 2 3
输出样例:0.200

#include<stdio.h>
int max(int a, int b, int c){
    int ans=a;
    if(ans<b) ans=b;
    if(ans<c) ans=c;
    return ans;
}
int main(){
    int a,b,c;
    scanf("%d%d%d", &a, &b, &c);
    double ans=1.0*max(a,b,c)/max(a+b,b,c)/max(a,b,b+c);
    printf("%.3lf\n", ans);// %n.mlf
    return 0;
}

题目传送

【题目描述】求 11 到 n 之间(包括 n),既是素数又是回文数的整数有多少个。
回文数指左右对称的数,如:11,22,121。

输入格式:一个大于 11 小于 10000 的整数 n。
输出格式:11 到 n 之间的素数回文数个数。
输入样例:23
输出样例:1

#include<stdio.h>
int isprime(int n){
    for(int i=2; i*i<=n; i++){
        if(n%i==0) return 0;
    }
    return 1;
}
int ishuiwen(int n){
    char a[100], cnt=0;
    while(n){
        a[++cnt]=n%10+'0';
        n/=10;
    }
    for(int i=1; i<=cnt/2; i++){
        if(a[i]!=a[cnt-i+1]) return 0;
    }
    return 1;
}
int main(){
    int n; scanf("%d", &n);
    int ans=0;
    for(int i=11; i<=n; i++){
        if(isprime(i) && ishuiwen(i)){
            ans++;
        }
    }
    printf("%d\n", ans);
    return 0;
}

题目传送

【题目描述】输入 x,y (1582≤x<y≤3000) ,输出 [x,y] 区间中闰年个数,并在下一行输出所有闰年年份数字,使用空格隔开。

输入样例:
1989 2001
输出样例:
3
1992 1996 2000
#include<stdio.h>
#define N 3000
int ans[N], num=0;
int isleap(int year){
    if(year%400==0 || year%4==0 && year%100!=0) {
        return 1;
    }
    return 0;
}
int main(){
    int x,y; scanf("%d%d", &x, &y);
    for(int i=x; i<=y; i++){
        if(isleap(i)){
            ans[++num]=i; //num=num+1; ans[num]=i;
        }
    }
    printf("%d\n", num);
    for(int i=1; i<=num; i++){
        printf("%d ", ans[i]);
    }
    return 0;
}

7. 函数的递归调用

递归:何为递归?就是不断的调用自身。

递归有两个特点:
(1)递归关系式:对问题进行递归形式的描述。
(2)递归终止条件(递归出口):当满足该条件时以一种特殊的情况处理,而不是用递归关系式来处理。

【题目描述】递归实现输入 n, 输出 1~n。

递归出口:i>n
递归关系式:i<=n, 输出 i

#include<stdio.h>

int n;
void dfs(int i){
    if(i>n) return; //递归出口
    printf("%d ", i);
    dfs(i+1);      //继续递归
}

int main(){
    scanf("%d", &n);
    dfs(1);
    return 0;
}

8. 最大公约数与最小公倍数

【题目描述】输入 a,b, 求他们的最大公约数与最小公倍数。

求最大公约数的三种方法:
1.最小递减法
先找a,b的最小值,判断该值能否同时被a,b整除,如果可以该数就是答案,否则每次-1,继续判断,直到找到答案。

2.更相减损法
a-b=c,则a,b的最大公约数就是b,c的最大公约数,如果c=0,a就是答案。

3.辗转相除法
a/b=q...r,则a,b的最大公约数就是b,r的最大公约数,如果r=0,则b就是答案。

辗转相除法&更相减损法的证明,点击跳转

求最小公倍数的两种方法:
1.最大递增法
先找a,b的最大值,判断该值能否同时整除a,b,如果可以该数就是答案,否则每次+1,继续判断,直到找到答案。

2.定理法:两个数的乘积等于这两个数的最大公约数与最小公倍数的乘积。

#include<stdio.h>

//返回两个数的最小值
int min(int a, int b){
    return a<b ? a : b;
}

//返回两个数的最大值
int max(int a, int b){
    return a>b ? a : b;
}

//最小递减法求最大公约数
int gcd1(int a, int b){
    for(int i=min(a,b); i>=1; i--){
        if(a%i==0 && b%i==0) return i;
    }
    return 1;
}

//通过引用传参,交换两个数
void swap(int &a, int &b){
    int temp=a; a=b; b=temp;
}

//更相减损法求最大公约数
int gcd2(int a, int b){
    if(a<b) swap(a, b);
    if(a-b==0) return a;
    return gcd2(b,a-b);
}

//辗转相除法求最大公约数
int gcd3(int a, int b){
    if(a%b==0) return b;
    return gcd3(b, a%b);
}

//最大递增法求最小公倍数
int lcm1(int a, int b){
    for(int i=max(a,b); ; i++){
        if(i%a==0 && i%b==0) return i;
    }
}

//定理法:两个数的乘积等于这两个数的最大公约数与最小公倍数的乘积
int lcm2(int a, int b){
    return a*b/gcd3(a,b);
}

int main(){
    int a,b; scanf("%d%d", &a, &b);

    printf("gcd1(%d,%d) = %d\n", a, b, gcd1(a,b));
    printf("gcd2(%d,%d) = %d\n", a, b, gcd2(a,b));
    printf("gcd3(%d,%d) = %d\n", a, b, gcd3(a,b));
    printf("lcm1(%d,%d) = %d\n", a, b, lcm1(a,b));
    printf("lcm2(%d,%d) = %d\n", a, b, lcm2(a,b));
    return 0;
}

输入:12 20
输出:
gcd1(12,20) = 4
gcd2(12,20) = 4
gcd3(12,20) = 4
lcm1(12,20) = 60
lcm2(12,20) = 60

9. 汉诺塔 Hanoi Tower

【题目描述】法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:
在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。
印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的 64 片金片,这就是所谓的汉诺塔。
不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。
僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。

现在有 n 片金片以及ABC三根宝石针,且 n 片金片都穿在 A 针上,问你移动全部金片到 C 针的步骤和移动的次数。
移动 A 最上一片金片到 C,输出显示为:move A to C
移动 A 最上一片金片到 B,输出显示为:move A to B
移动 B 最上一片金片到 C,输出显示为:move B to C
.......

输入样例:4
输出样例:
move A to B
move A to C
move B to C
move A to B
move C to A
move C to B
move A to B
move A to C
move B to C
move B to A
move C to A
move B to C
move A to B
move A to C
move B to C
15
#include<stdio.h>
int ans=0;
void move(char a, char b, char c, int n){
    if(n==1){
        printf("move %c to %c\n", a, c);
        ans++;
        return; //返回到入口处的下一行
    }
    move(a, c, b, n-1);//将 A中 n-1转移到 B
    move(a, b, c, 1);  //将 A中剩余 1个转移到 C
    move(b, a, c, n-1);//将 B中 n-1转移到 C
}

int main(){
    int n; scanf("%d", &n);
    move('A', 'B', 'C', n);//借助 B,从 A转移到 C
    printf("%d",ans);
    return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM