函数是什么?

数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y=kx+b ,k和b都是常数,给⼀个任意的x,y就得到⼀个值。

其实在C语言也引⼊函数(function)的概念,有些翻译为:⼦程序,我认为⼦程序这种翻译更加准 确⼀些。


C语言中函数的分类

1.库函数

2.自定义函数

库函数

为什么会有库函数?

我们在学习c语言的过程中,会经常用到一些基础功能,不是业务性的代码,每个程序员都可能用到,为了支持可移植性和提高程序的效率,所以c语言的基础库中提供了一系列库函数,方便程序员进行开发。

如何学习库函数?

学习库函数的网站:cplusplus.com

     cppreference.com

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

  • O函数 输入输出
  • 字符串操作函数
  • 字符操作函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 其他库函数

自定义函数

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

但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。

函数的组成:

ret_type fun_name(para1, * )    
{
    statement;       //语句项
}

ret_type 返回类型

fun_name 函数名

para1 函数参数

statement 函数体

例 1 (写一个函数可以找出两个整数中的最大值):

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int get_max(int x,int y)
{
   if (x>y)
       return x; 
   if (x<y)
       return y;    
}
int main()
{
    int a = 10;
    int b = 20;
    int max = get_max(a,b);
    printf("max=%d\n",max);
    return 0;
}

例 2 (写一个函数可以交换两个整形变量的内容):

错误案例 :

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Swap1(int x,int y)        //void表示空,无返回值
{
    int tmp = 0;
    tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    printf("a=%d,b=%d\n",a,b);
    Swap1(a,b) ;                       //失败缘由:swap函数创造了x,y最终x,y值互换,但这并不改变a,b的值
    printf("a=%d,b=%d\n",a,b);       //(类似将a,b的值拷贝到x,y中,对x,y的操作对a,b无影响)
    return 0;
}

正确案例 :

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void Swap2(int* pa, int* pb)
{
    int tmp = 0;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    Swap2(&a,&b);
    printf("Swap2:a=%d,b=%d\n", a, b);
    return 0;
}

分析:当实参 ( a , b ) 传递给形参 ( x , y) 的时候,形参是实参的一份临时拷贝,对形参的修改不能改变实参,那么对x和y的值进 行交换是不能 影响a和b的值的用int* pa接收a的地址,int* pb接收b的地址,才能对a和b的值进行修改。

函数参数

实际参数(实参):

真实传给函数的参数,叫实参。

实参可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形式参数(形参):

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数

形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效。


所以:形参是实参的一份临时拷贝


函数调用

传值调用

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参

传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
  • 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量

练习(写函数)

可以判断一个数是不是素数

自己写的
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <math.h>
void shushu(int x)
{
    int i;
    for (i = 2; i <= sqrt(x); i++)
    {
        if (0 == x % i)
        {
            printf("这是一个素数");
            break;
        }
    }
    if (i > sqrt(x))
        printf("这不是一个素数");

}
int main()
{
    int a=0;
    scanf("%d", &a);
          //  "%d"中不能写\n(写了必须手动换行)
    shushu(a);

    return 0;
}
别人写的
int is_prime(int n)
{
    int j = 0;
    for (j = 2; j <= sqrt(n); j++)            //函数要写头文件
    {
        if (n % j == 0)
            return 0;
    }
    return 1;

}
int main()
{
        int count = 0;
        int i = 0;
        for (i = 101; i <= 200; i+=2)            //偶数不用考虑了
        {
            if (is_prime(i))
            {
                printf("%d ",i);
                count++;
            }
                
        }
        printf("\n共有%d个素数", count);
        return 0;
}

判断一年是不是闰年

别人写的
define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int is_leap_year(int y)//是闰年返回1
{
 if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
     return 1;
 else 
     return 0;
}
int main()
{
int year= 0;
for (year = 1000;year<= 2000; year++)
{
    if (is_leap_year(year))
    {
        printf("%d ",year);
        
    }        
}
return 0;
}

实现一个整形有序数组的二分查找

我写的(错误的)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <math.h>
int search(int arr[], int n)                    //此处的arr是一个指针,传过来的是原arr首元素的地址
{
    int right = sizeof(arr) / sizeof(arr[0]);    //right=1
    int left = 0;
    while (right=>left)
    {
        int midsize = (right+left) / 2;
        if (n > arr[midsize])
        {
            left = midsize;
        }
        else if (n > arr[midsize])
        {
            right = midsize;
        }
        else
        {
            printf("找到了,下标是%d\n", midsize);
            return 1;
        }
    }
    if (right = left)
        return 0;
}
int main()
{
    int arr[] = { 1,2,4,6,8,9,10,11,33 };
    int k;
    scanf("%d", &k);
    int a=search(arr, k);
    printf("%d", a);
    return 0;
}

别人写的(正确的)

define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int binary_search(int arr[], int k, int sz)
{
    int left = 0;
    int right = sz - 1;
    
    while(left<=right)
    {
        int mid = left + (right - left) / 2;
        if (arr[mid] < k)
        {
            left = mid + 1;
        }
        else if (arr[mid] > k)
        {
            right = mid - 1;
        }
        else
        {
            return mid;//找到了,返回下标mid
        }
    }
    return -1;//没有找到
}
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int k = 7;
    int sz = sizeof(arr) / sizeof(arr[0]);          //在此处改动即可
    int ret = binary_search(arr, k,sz);
    if (ret == -1)
        printf("找不到\n");
    else
        printf("找到了,下标是:%d\n", ret);
    return 0;
}

每调用一次这个函数,就会将num的值增加1

define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int Add(int *p)
{
    (*p)++;                   //++优先级高于*,若为*p++,结果为0,0.
}
int main()
{
    int num = 0;
    Add(&num);
    printf("%d\n", num);//1
    Add(&num);
    printf("%d\n", num);//2
    return 0;
}

函数的嵌套调用与链式访问

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

嵌套调用

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void new_line()
{
    printf("hehe\n");
}
void three_line()
{
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        new_line();
    }
}
int main()
{
    three_line();
    return 0;
}

注意:函数可以嵌套调用,但是不能嵌套定义,每个函数地位都是相等的,并列的关系

链式访问

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

链式访问的条件:函数必须有返回值

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
    int len = strlen("abcdef");
    printf("%d\n", len);

    printf("%d\n", strlen("abcdef"));                //链式访问
    
    printf("%d", printf("%d", printf("%d", 43)));    //printf的返回值为打印在屏幕上字符的个数
    return 0;
}

函数的声明与定义

函数声明

  • 函数声明是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了
  • 函数的声明一般出现在函数的使用之前。要满足先声明后使用
  • 函数的声明一般要放在头文件中的
int Add(int x, int y);                //函数的声明,结尾加分号
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    //加法
    int sum = Add(a, b);
    printf("%d\n", sum);
    return 0;
}
//函数的定义(如果不进行函数声明,程序可以编译运行,但是会出现警告)
int Add(int x, int y)
{
    return x + y;
}

以上用法不常见,声明往往放在头文件中,定义与执行(#include "文件名" 不用<>)在不同源文件中

函数定义

#ifndef __ADD_H__
#define __ADD_H__

int Add(int x,int y);
#endif                //


函数递归

什么是递归?

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

C语言初阶3 -函数_库函数


递归的必要两个条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续
  • 每次递归调用之后越来越接近这个限制条件

练习

1.接受一个整型值(无符号),按照顺序打印它的每一位 。例如:输入:1234 输出 1 2 3 4

void print(unsigned int n)
{
    if (n > 9)
    {
        print(n/10);
    }
    printf("%d ", n % 10);
}
int main()
{
    unsigned int num=0;
    scanf("%u", &num);
    print(num);//接受一个整型值(无符号),按照顺序打印它的每一位。
    return 0;
}

2.编写函数不允许创建临时变量,求字符串的长度(也就是让我们模拟strlen函数功能功能)

define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
int my_strlen(char* str)//参数部分写成指针形式
{
    if (*str != '\0')
        return 1 + my_strlen(str + 1);//str++不可以,传进去的还是a地址
    else
        return 0;
}
int main()
{
    char arr[] = "abc";
    //char*
    int len = my_strlen(arr);
    
    printf("%d\n", len);//3
    return 0;
}


递归与迭代

1

//练习4:求第n个斐波那契数。(不考虑溢出)
//代码演示:
define _CRT_SECURE_NO_WARNINGS
include<stdio.h>
//递归实现
int Fib(int n)
{
    if (n >= 3)
        return Facl(n - 2) + Facl(n - 1);
    else
        return 1;
}
//迭代实现
int Fib(int n)
{
    int a = 1;
    int b = 1;
    int c = 0;
     
    while (n >= 3)
    {
        c = a + b;
        a = b;
        b = c;
        n--;
    }
    return c;//n=1 n=2时返回c
    
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    int ret = Fib(n);
    printf("ret = %d\n", ret);
    return 0;
}