C语言入门学习 --- 3.函数

第三章函数

本章将着重讲以下几点:

1.函数是什么

2.库函数

3.自定义函数

4.函数参数

5.函数调用

6.函数的嵌套调用和链式访问

7.函数的声明和定义

8.函数递归

1.函数是什么?

函数是实现特定功能的一个模块,一个C程序由多个函数构成。通过main函数调用其他函数实现需求。它由一个或多个语句块组成,负责完成某个特定任务,相较于其他代码,具有相对的独立性。一般会有输入参数,并且有返回值,提供对过程的封装和细节的隐藏。这些代码通常被称为软件库。C语言要求,变量要‘先定义’,‘再使用’,函数也是如此。

2.C语言中函数的分类

  1. 库函数

  2. 自定义函数

2.1库函数

为什么要有库函数?

  1. 我们要将信息打印到屏幕上。

  2. 我们会做一些字符串的拷贝工作(strcpy)。

我们上面所描述的基础功能,我们在开发过程中都可能会使用到,为了提高程序的效率和可移植性,所以C语言提供了一系列的库函数,方便程序员进行开发。

怎么学习库函数呢?

可以简单看看:www.cplusplus.com

简单总结一下,C语言常用的库函数有:

  • IO函数

  • 字符串操作函数

  • 字符操作函数

  • 内存操作函数

  • 时间/日期函数

  • 数学函数

  • 其他库函数

在用库函数的时候不需要全部记住,只需学会使用一下这些查询工具如何使用:

MSDN(Microsoft Developer Network)

www.cplusplus.com

http://en.cppreference.com(英文版)

http://zh.cppreference.com(中文版)

2.2自定义函数

库函数不能干所有的事情,所以需要程序员进行开发。

自定义函数跟库函数一样,都有函数名,返回值类型和函数参数。

不一样就在这些是由我们自己来设计开发,给程序员很大的发挥空间。

函数语法结构:

返回值类型 函数名([形参列表])
{
    函数体
}

//例:
void print(int n)
{
    printf("%d ", n); //语句项
}
//void 返回类型
//print 函数名
//n 函数参数

举个例子:

写一个函数找出两个整数中的最大值。

#include <stdio.h>

void get_max(int x, int y);

int main()
{
    int a = 10;
    int b = 25;
    get_max(a, b);
    return 0;
}

void get_max(int x, int y)
{
    int max = 0;
        if (x > y)
        {
            max = x;
        }    
        else
        {
            max = y;
        }
        printf("最大值=%d", max);
}

再举个例子叭:

写个函数让两个整形变量内容进行交换

#include <stdio.h>

void swap2(int* pa, int* pb);
void swap1(int x, int y);

int main()
{
    int a = 10;
    int b = 25;
    swap1(a, b);
    printf("swap1:a=%d,b=%d\n", a, b); //打印结果swap1:a=10,b=25
    swap2(&a, &b);
    printf("swap2:a=%d,b=%d\n", a, b); //打印结果swap2:a=25,b=10
    return 0;
}

void swap1(int x, int y)
{
    int z = 0;
    z = x;
    x = y;
    y = z;
}

//正确的版本
void swap2(int* pa, int* pb)
{
    int c = 0;
    c = *pa;
    *pa = *pb;
    *pb = c;
}

3.函数参数

3.1实际参数(简称实参):

在调用该有参函数时,函数名后面括号里的参数,真实传给函数的参数称为实际参数。

实参可以是常量,变量,表达式、函数等,但必须要有确定的值。

3.2形式参数(简称形参):

函数名后面括号里的参数称为形式参数。

形式参数只有函数被调用的过程中才分配内存单元。

形式参数在函数调用完毕就自动销毁了,只在该函数中有效。

4.函数调用

函数传递方式:

  • 值传递
    地址传递

4.1传值调用

函数的形参和实参分别占用不同的内存块,由此形参的改变不会影响实参。

4.2传址调用

传址调用是是函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式。实参和形参都指向一个内存空间。

这种传递参数的方式可以让函数外部的变量与函数内部建立起真正的联系,函数内部可以直接操作函数外部的变量。

4.3练习

1.写一个可以判断某个数为素数的函数。

2.写一个判断某一年是不是闰年的函数。

3.写一个实现整型有序数组的二分查找函数。

4.写一个每调用一次该函数,num值就加1的函数。

//代码1
#include <stdio.h>

void is_prime(int a);

int main()
{
    int x = 0;
    scanf("%d", &x);
    is_prime(x);
    return 0;
}

void is_prime(int a)
{
    int i = 0;
    for (i = 2; i < a; i++)
    {
        if (a % i == 0)
        {
            printf("%d不是素数\n", a);
            break;
        }
        else
        {
            printf("%d是素数\n", a);
            break;
        }
    }
}


//代码2
#include <stdio.h>

void leap_year(int x);

int main()
{
    int year = 0;
    scanf("%d", &year);
    leap_year(year);
    return 0;
}

void leap_year(int x)
{
    if (x % 4 == 0 && x % 100 != 0)
    {
        printf("%d是闰年", x);
    }
    else if (x % 400 == 0)
    {
        printf("%d是闰年", x);
    }
    else
        printf("%d不是闰年", x);
}

//代码3
#include <stdio.h>

int bineary_search(int a[], int key, int length);

int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int x = 0;
    int len = sizeof(arr) / sizeof(arr[0]);
    scanf("%d", &x);
    int num = bineary_search(arr, x , len);
    if (num == -1)
    {
        printf("找不到%d\n", x);
    }
    if (num == 1)
    {
        printf("%d找到了\n", x);
    }
    return 0;
}

int bineary_search(int a[],int key, int length)
{
    int left = 0;
    int right = length - 1;
    while (left<=right)
    {
        int mid = (left + right) / 2;
        if (a[mid] > key)
        {
            right = mid - 1;
        }
        else if (a[mid] < key)
        {
            left = mid + 1;
        }
        else

        {
            return 1;
        }
    }
    return -1;
}


/代码4
#include <stdio.h>

void add(int* pa);

int main()
{
    int Num = 0;
    for (int i = 0; i < 10; i++)
    {
        add(&Num);
    }
    printf("%d ", Num);
    return 0;
}

void add(int* pa)
{
    (*pa)++;
}

5.函数的嵌套调用和链式访问

函数和函数之间可以根据需求进行组合,也就是互相调用的。

5.1嵌套调用

函数可以嵌套调用,但不能嵌套定义。

#include <stdio.h>

void print()
{
    printf("Hello World!\n");
}

int main()
{
    print();
    return 0;
}
5.2链式访问

把一个函数的返回值作为另一个函数的参数。

#include <stdio.h>
#include <string.h>

int main()
{
    char arr[] = "Hello World!";
    printf("%d", strlen(arr));
    return
 0;

}


#include <stdio.h>

int main()
{
    printf("%d", printf("%d", printf("%d", 43))); //可以想想结果是什么
    return 0;
}

结果:4321

第一次printf先打印了43,第二次printf因为43两个字符打印了2,第三次printf因为2是一个字符打印了1。

6.函数声明和定义

6.1函数声明

1.告诉编译器你有这个函数,这个函数叫什么,返回值类型是什么,参数是什么。具体是否存在,声明决定不了。

2.函数声明一般在使用之前。要满足先声明后使用。

3.函数声明一般要放在头文件中的。

6.2函数定义

告诉编译器这个函数要做什么事情,交代函数要实现什么功能,具体怎么实现。

sub.h:

放置函数声明

#pragma once
//函数的声明
int sub(int x, int y);

sub.c:

放置函数定义

#include "sub.h"
//函数的实现
int sub(int x, int y)
{
    return x + y;
}

分文件书写/分模块书写。

7.函数递归

7.1递归的定义

程序调用自身的编程技巧称为递归( recursion)。

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

7.2递归的两个必要条件

存在限制条件,当满足这个限制条件时,递归将不再继续。

每次递归调用之后越来越接近这个限制条件。

7.2.1练习

接收一个整形值(无符号),按顺序打印它的每一位。

比如:

输入:1234,输出:1 2 3 4

#include <stdio.h>

void print(unsigned int n);

int main()
{
    int n = 0;
    scanf("%u", &n);
    print(n);
    return 0;
}

void print(unsigned int n)
{
    if (n > 9)
    {
        print(n / 10);
        printf("%d ", n % 10);
    }
    else
        printf("%d ", n);

}

编写函数时不允许创建临时变量,并求字符串的长度。

#include <stdio.h>

int main()
{
    char arr[] = "Hello World";
    printf("%d", Strlen(arr));
    return 0;
}

int Strlen(char* str)
{
    if (*str != '\0')
    {
        return 1 + Strlen(str + 1);
        str++;
    }
    else
    {
        return 0;
    }
}
7.3递归与迭代
7.3.1练习

求n的阶乘。(不考虑溢出)

#include <stdio.h>

int fac(int n);

int main()
{
    int n = 0;
    scanf("%d", &n);
    int sum = fac(n);
    printf("%d", sum);
    return 0;
}

int fac(int n)
{
    if (n > 1)
    {
        return n * fac(n - 1);
    }
    else
        return 1;
}

求第n个斐波那契数。(不考虑溢出)

#include <stdio.h>

int fib(int n);

int main()
{
    int n = 0;
    scanf("%d", &n);
    int sum = fib(n);
    printf("%d", sum);
    return 0;
}

int fib(int n)
{
    if (n > 2)
    {
        return fib(n - 1) + fib(n - 2);
    }
    else
        return 1;
}

但是发现它有问题:

在使用fib函数时如果我们要计算如50个斐波那契数的时候需要耗费许多时间。

使用fac函数求10000的阶乘,不考虑程序结果正确性的情况下,程序会崩溃。

为什么?

fib函数在调用的过程中很多计算其实在一直重复。
我们可以定义个变量,记录计算了多少次

#include <stdio.h>

int fib(int n);

int count = 0;

int main()
{
    int n = 0;
    scanf("%d", &n);
    int sum = fib(n);
    printf("%d", sum);
    return 0;
}

int fib(int n)
{
    if (n == 3)
        count++;
    if (n > 2)
    {
        return fib(n - 1) + fib(n - 2);
    }
    else
        return 1;
}

输出看结果,count会是个非常大的值。

如何改进呢?
  • 在调试fac函数时,如果参数比较大,就会报错:stack overflow(栈溢出)这样的信息。

系统分配给程序员的空间是有限的,如果出现了死循环或者死递归,这样可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象被称为栈溢出。

如何解决上述问题:

1.可将递归改写为非递归。

2.使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为

各个调用层所访问。

以下代码采用非递归的方式实现:

#include <stdio.h>

int fib(int n);

int count = 0;

int main()
{
    int n = 0;
    scanf("%d", &n);
    int sum = fib(n);
    printf("%d", sum);
    return 0;
}

int fib(int n)
{
    int a = 1;
    int b = 1;
    int c = 1;
    while (n > 2)
    {
        c = a + b;
        a = b;
        b = c;
        n--;
    }
    return c;
}

提示:

1.许多问题以递归的形式进行解释,只是因为它比非递归的形式更为清晰。

2.但这些问题的迭代实现往往比递归效率高,虽然代码可读性稍微差些。

3.当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

7.4练习

1.编写程序数一下1到100的所有整数中出现多少个数字9。

//编写程序数一下1到100的所有整数中出现多少个数字9。 9 19 29 39>.91 92 93....99
#include <stdio.h>

int main()
{
    int count = 0;
    for (int i = 0; i <= 100;i++)
    {
        if (i % 10 == 9)
        {
            count++;
        }
        else if (i / 10 == 9)
        {
            count++;
        }
    }
    printf("%d\n",count);
    return 0;
}

2.计算1/1-1/2+1/3-1/4+1/5…+1/99-1/100的值,打印出结果。

//2.计算1/1-1/2+1/3-1/4+1/5....+1/99-1/100的值,打印出结果。

#include <stdio.h>

int main()
{
    double sum = 0.0;
    for (int i = 1;i <= 100;i++)
    {
        if (i % 2 == 0)
        {
            sum -= 1.0 / i;
        }
        else
        {
            sum += 1.0 / i;
        }
    }
    printf("%lf\n",sum);
    return 0;
}

//优化版
#include <stdio.h>

int main()
{
    double sum = 0.0;
    int flag = 1;
    for (int i = 1;i <= 100;i++)
    {
        sum += flag * 1.0 / i;
        flag = -flag;
    }
    printf("%lf\n", sum);
    return 0;
}

3.求10个整数中最大值。

//3.求10个整数中最大值。

#include <stdio.h>

int main()
{
    int arr[] = {-1,-2,-5,7,8,20,40,6,48,10};
    int max = arr[2];
    for (int i = 0; i < 10; i++)
    {
        if (arr[i] > max)
            max = arr[i];
    }
    printf("%d\n",max);
    return 0;
}

4.在屏幕上输出9*9乘法口诀表

//4.在屏幕上输出9*9乘法口诀表

#include <stdio.h>

int main()
{
    for (int i = 1;i <= 9;i++)
    {
        for (int j = 1;j <= i;j++)
        {
            printf("%d*%d=%d ",i,j,i*j);
        }
        printf("\n");
    }
    return 0;
}

1.实现一个函数,打印乘法口诀表,口诀表的行和列数自己指定

如:输入9,输出9 X 9口诀表,输入12,输入12 X 12口诀表。

//1.实现一个函数,打印乘法口诀表,口诀表的行和列数自己指定
//如:输入9,输出9 X 9口诀表,输入12,输入12 X 12口诀表。

#include <stdio.h>

int main()
{
    int n = 0;
    scanf("%d",&n);
    for (int i = 1;i <= n;i++)
    {
        for (int j = 1;j <= i;j++)
        {
            printf("%d*%d=%-2d ",i,j,i*j);
        }
        printf("\n");
    }
    return 0;
}

2.字符串逆序(递归实现)

编写一个函数reverse_string(char *string)

实现:将参数字符串中的字符反向排列,不是逆序打印。

要求:不能使用C函数库中的字符串操作函数。

比如:char arr[] = “abcdef”

逆序之后数组的内容变成:fedcba

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

void reverse_string(char* str);
int my_strlen(char* str);

int main()
{
    char arr[] = "abcdef";
    reverse_string(arr);

    printf("%s\n", arr);
    return 0;
}


//该方式为循环的方式实现,接下来我们用递归的方式实现
//void reverse_string(char* str)
//{
//    int left = 0;
//    int right = my_strlen(str) - 1;
//    char tmp = 0;
//    while (left < right)
//    {
//        char tmp = str[left]; //*(str + left)
//        str[left] = str[right]; //*(str + left) = *(str + right)
//        str[right] = tmp; //*(str + right) = tmp
//        left++;
//        right--;
//    }
//}

void reverse_string(char* str)
{
    char tmp = *str;
    int len = my_strlen(str);
    *str = *(str + len - 1);
    *(str + len - 1) = '\0';
    if (my_strlen(str+1) >= 2)
    {
         reverse_string(str + 1);
    }
    *(str + len - 1) = tmp;
}

int my_strlen(char* str)
{
    int count = 0;
    while (*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}

3.计算一个数的每位之和(递归实现)

写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和

例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19

输入:1729,输出:19

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int DigitSum(int n);

int main()
{
    int  x = 0;
    scanf("%d", &x);
    int sum = DigitSum(x);
    printf("%d\n", sum);
    return 0;
}

int DigitSum(int n)
{
    if (n > 9)
    {
        return DigitSum(n / 10) + n % 10;
    }
    else
    {
        return n;
    }
}

4.编写一个函数实现n的k次方,使用递归实现。

#include <stdio.h>

double Pow(int n, int k);

int main()
{
    int n = 0;
    int k = 0;
    scanf("%d %d", &n, &k);
    double ret = Pow(n, k);
    printf("%lf\n", ret);
    return 0;
}

double Pow(int n, int k)
{
    if (k == 0)
        return 1;
    else if (k > 0)
        return n * Pow(n, k - 1);
    else
        return 1.0 / (Pow(n, -k));
}

函数递归的几个经典题目(自主研究):

  1. 汉诺塔问题

    //汉诺塔问题(递归实现)
    //思路:1.将A中n-1个盘子放入B中,剩下最后一个(第n个)盘子放入C中。2.将B中n-1个盘子放入A中,剩下最后一个(第n个)放入C中。
    
    #include <stdio.h>
    
    void move(char x, char y)
    {
        printf("%c -> %c\n", x, y);
    }
    
    void Hanoi(int n, char a, char b, char c)
    {
        if (n == 1)
        {
            move(a, c);
        }
        else
        {
            Hanoi(n - 1, a, c, b);
            move(a, c);
            Hanoi(n - 1, b, a, c);
        }
    }
    
    int main()
    {
        int n = 0;
        scanf("%d", &n);
        Hanoi(n, 'a', 'b', 'c');
        return 0;
    }
    
  2. 青蛙跳台阶问题

    
    //青蛙跳台阶问题(递归实现)
    //一只青蛙一次可以跳一级台阶或二级台阶和三级台阶。
    //当N=1时,有1种跳法。
    //当N=2时,首先可以跳两个一级台阶或一个二级台阶,有2种跳法
    //当N=3时,首先可以跳一次一级台阶,剩下二级台阶,N=2时,有两种跳法。跳一次三级台阶,一种跳法。跳一次二级台阶,剩下一级台阶,N=1时,有1种跳法,则共四种方法
    //当N=4时,首先可以跳一次一级台阶,剩下三级台阶,N=3时,有4种跳法。跳一次二级台阶,剩下二级台阶,N=2时,有2种跳法。跳一次三级台阶,剩下一级台阶,N=1时,1种跳法。则共7种方法.
    #include <stdio.h>
    
    int dance_step(int n)
    {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        if (n == 3)
            return 4;
        if (n > 3)
            return dance_step(n - 1) + dance_step(n - 2) + dance_step(n - 3);
    }
    
    int main()
    {
        int n = 0;
        scanf("%d", &n);
        int sum = dance_step(n);
        printf("%d\n", sum);
        return 0;
    }
    
    

上一章:C语言入门学习 — 2.分支与循环语句

配套练习:

C语言练习题110例(一)
C语言练习题110例(二)
C语言练习题110例(三)
C语言练习题110例(四)
C语言练习题110例(五)
C语言练习题110例(六)
C语言练习题110例(七)
C语言练习题110例(八)
C语言练习题110例(九)
C语言练习题110例(十)
C语言练习题110例(十一)

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值