函数1
Suzhou 62 0

C语言中函数分为库函数和自定义函数。 库函数是C语言本身提供的函数。 ::: tip 通过http://www.cplusplus.com网站可查询到具体用法。 :::


库函数举例
strcpy

函数1

#include <stdio.h>

int main()
{
    char arr1[] = "Hello!";
    char arr2[20] = "#############";
    strcpy(arr2, arr1);
    printf("%s", arr2);
    return 0;
}
Hello!

函数1 由上图可得,函数strcpy拷贝数组时候,会将字符串的结束标志“\0”也拷贝过去。 ::: tip “\0”是字符串的结束标志。 ::: 函数1 上述代码中,如果没有把“\0”拷贝到arr2中,arr2中原有的“World”就会输出出来。


memset

函数1 函数menset的作用是将指针ptr指向的内存块的前num字节设置为指定值

#include <stdio.h>

int main()
{
    char arr[] = "Hello World!";
    memset(arr, '*', 5);
    printf("%s\n", arr);
    return 0;
}
***** World!

如上代码,将数组arr中的前五个字符用“*”做了替换。


自定义函数

自定义函数和库函数一样,有函数名、返回值类型、函数参数,区别是这些都由程序员自己设计。 函数1

#include <stdio.h>

int get_max(int x, int y)
{
    if (x > y)
        return x;
    else
        return y;
}

int main()
{
    int a = 10;
    int b = 20;
    int max = get_max(a, b);
    printf("max=%d\n", max);
    max = get_max(500, 10);
    printf("max=%d\n", max);
    return 0;
}
max=20
max=500

::: tip 代码思路: 先写主函数,针对比较大小的需求封装函数get_max();将a和b两个参数传进去,get_max(a,b);,返回值给到变量max,然后可以输出。 定义函数时先写出get_max(),函数需要传进去a和b两个变量值,就需要有两个参数接受,格式是整型,使用int x,和int y,即get_max(int x,int y)。函数体中,判断大小后,return返回大的值。 :::


写交换变量值的函数
#include <stdio.h>

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

int main()
{    
    int a = 10;
    int b = 20;
    printf("a=%d b=%d\n", a, b);
    swap1(a, b);
    printf("a=%d b=%d", a, b);
    return 0;
}
a=10 b=20
a=10 b=20

::: warning 通过debug调试可以看到上述的程序中,在函数swap中参数值交换后,并没有传到主函数中,没有改变主函数中参数的值。 x和y在内存中有独立的空间,和a、b并无关系。 :::

#include <stdio.h>

void swap2(int* pa, int* pb)  //不需要返回值,选择void类型
{
    int z;
    z = *pa;
    *pa = *pb;
    *pb = z;
}

int main()
{    
    int a = 10;
    int b = 20;
    printf("a=%d b=%d\n", a, b);
    swap2(&a, &b);
    printf("a=%d b=%d", a, b);
    return 0;
}
a=10 b=20
a=20 b=10

::: tip 上述函数中,主函数中将a和b的地址传到swap函数中,swap函数中需要使用指针变量来接收传入的地址,即int* pa, int* pb。 pa中存放a的地址,pb中存放b的地址,pa、pb前加上,表示解引用,作用是通过地址找到所指向的内容,即pa = a,*pb = b。如下:

int main()
{
    int a = 10;
    int* pa = &a;
    *pa = 20;
    printf("a = %d", a);
}
a = 20

:::


函数参数

实际参数:真实传递给函数的参数,称为实参,可以是常量、变量、表达式、函数。 实参在函数调用时必须有确定的值,以便把值传递给形参。 如上述代码中第17行swap(&a,&b)的&a和&b就是实参。 形式参数:函数名后面括号中的变量,形参只有在函数调用过程中才会实例化(分配内存单元),形式参数在函数调用完成后自动销毁,即只在函数中有效。 如上述代码中第四行swap(int* pa, int* pb)的int* pa和int* pb就是形参。 ::: tip 当上述程序中主函数的函数调用代码不存在时,自定义函数中的参数x、y就没有实际的变量空间,就只是一个符号,即形式参数。在调用过程中,参数&a、&b传递过去,x、y才有内存空间。 ::: ::: warning 实参的值传给形参时,形参实例化后相当于实参的一份临时拷贝,对形参的修改不会改变实参,即swap1不会交换a和b的值。swap2通过变量的内存地址,直接操作修改了实参,所以可以交换a和b的值。 :::


函数调用
#include <stdio.h>

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

void swap2(int* pa, int* pb)
{
    int z = 0;
    z = *pa;
    *pa = *pb;
    *pb = z;
}

int main()
{
    int a = 10;
    int b = 20;
    swap1(a, b);
    printf("a=%d,b=%d\n", a, b);
    swap2(&a, &b);
    printf("a=%d,b=%d", a, b);
    return 0;
}
a=10,b=20  //swap1调用后
a=20,b=10  //swap2调用后

传值调用:上述代码中swap1将a和b的值传到自定义函数中,称为传值调用。 (函数的形参和实参分别占有不同的地址块,对形参的修改不会影响实参。) 传址调用:把函数外部创建变量的内存地址传递给函数的参数。 (在函数和函数外部的变量建立起真正的联系,使得函数内部可以直接操作函数外部的变量。)


实例
列出100到200之间的素数

循环嵌套方法:

#include <stdio.h>

int main()
{
    int a = 10;
    int isPrime = 0;
    for (a = 100;  a < 200; a++){
        int i = 0;
        int isPrime = 1;
        for (i = 2; i < a; i++){
            if (a % i == 0){
                isPrime = 0;
                break;
            }
        }
        if (isPrime == 1) {
            printf("%d\n", a);
        }
    }
    return 0;
}

函数方法:

#include <stdio.h>

int isPrime(int n) //使用形参n接收主函数传来的实参i
{
    int j = 0;
    for (j = 2; j < n; j++)  //判断条件可以改为j <= sqrt(n)来优化程序(添加头文件#include <math.h>)
    {
        if (n % j == 0)
        {
            return 0;  
            //比break的作用更强,break只能跳出当前循环,return0可以结束整个函数
            //return0后直接跳到24行,即:有一个约数就证明不是素数,停止isPrime循环
        }
    }
    return 1;  //当j从2到n-1都试过不可以整除n时,即n为素数,此时j=n,跳出for循环,返回1
}
int main()
{
    //打印100-200之间的素数
    int i = 0;
    for (i = 100; i <= 200; i++) 
    {
        if (isPrime(i) == 1)
        //规定i为素数时isPrime返回1,反之为0
        {
            printf("%d ", i);
        }
    }
    return 0;
}
101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199

::: warning 上述函数中不可以在自定义函数isPrime中的for循环内过早的使用if和else来选择return0还是return1,原因是此时只判断了数字2是否能被整除,并没有判断到n-1。 :::


计算闰年
#include <stdio.h>

int is_leap_year(int a)
{
    if ((a % 4 == 0 && a / 100 != 0) || a % 400 == 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

int main()
{
    int year = 0;
    for (year = 2000; year <= 2020; year++)
    {
        if (is_leap_year(year) == 1)
        {
            printf("%d " ,year);
        }
    }
}
2000 2004 2008 2012 2016 2020

::: warning 自定义函数中一般不做输出,只完成纯粹的功能如上述代码判断闰年,是返回1不是返回0,主函数中根据自定义函数的返回值来写条件判断输出语句。 单一功能原则,增加了代码复用性。 :::


整形有序数组二分查找
#include <stdio.h>

int binary_search(int m, int n, int arr1[])
{
    int left = 0;  //最左边元素下标
    int right = n - 1;  //最右边元素下标
    while (left <= right)  //左右下标相等表示锁定某一个元素,还需要进入循环判断是否等于m
    {
        int mid = (left + right) / 2;  //中间元素的下标
        if (m < arr1[mid])
        {
            right = mid - 1;
        }
        else if (m > arr1[mid])
        {
            left = mid + 1;
        }
        else
        {
            return mid;
        }
    }
    return -1;
}

int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    int a = 7;
    int b = binary_search(a, sz, arr);
    if (b == -1)
    {
        printf("没有找到");
    }
    else 
    {
        printf("找到了,在第%d位",b);
    }
    return 0;
}
找到了,在第6位

::: warning 上述代码第9行注意不要写在while循环外,否则变量mid不会改变。 上述代码第32行,写成“=”是赋值,写成“==”是判断。 ::: ::: warning 形参是实参的一部分临时拷贝。但数组传参时候,为了避免空间浪费,传过去的只是数组第一个元素的地址,即自定义函数中数组名代表首元素的地址自定义函数中的arr1[]用来接收主函数中数组的首元素地址,本质上是一个指针。 上述代码在vs2022中调试可以看到,自定义函数中数组arr1内只有一个元素“1”。 :::

::: danger sizeof不能用来计算指针大小。 原因如下: sizeof(指针):返回计算机系统的地址字节数,如果是64为系统,返回8;32位系统,返回4;16位系统,返回2。 sizeof(arr1[0])第一个元素是int类型,大小是4。 两个相除的结果是2或1或0。 ::: ::: danger 函数内部求参数部分数组元素个数是做不到的。 上述代码中第29行放在函数binary_search中无法求出正确结果。 解决方法是在函数外面计算出数组结果后再传递进去。 :::


写一个函数,每次调用函数后,num值+1
#include <stdio.h>

void Add(int* p)  //使用指针接收地址
{
    (*p)++;  //“*p”即主函数中的num。
    //“++”的优先级高于指针“* ”,“* p”需要使用括号
}

int main()
{
    int num = 0;
    Add(&num);  //传址调用
    printf("num=%d\n", num);  //1
    Add(&num);
    printf("num=%d\n", num);  //2
    Add(&num);
    printf("num=%d\n", num);  //3
    return 0;
}

::: tip 函数内部改变num变量值(函数内操作函数外的变量,需要使用“传址”的方式) :::


函数嵌套调用和链式访问
函数嵌套调用

函数嵌套调用是指函数调用函数。

#include <stdio.h>

Printf()
{
    printf("hello world\n");
}

PRINTF()
{
    int i = 0;
    for (i; i < 3; i++)
    {
        Printf();
    }
}

int main()
{
    int num = 0;
    PRINTF();
    return 0;
}
hello world
hello world
hello world
函数链式访问

函数链式访问是指把一个函数的返回值作为另外一个函数的参数。

#include <stdio.h>

int main()
{
    int len = 0;
    len = strlen("abc");
    printf("%d\n", len);
    printf("%d\n", strlen("abc"));  函数strlen的返回值做函数printf的参数
    return 0;
}
3
3

举例
#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}
4321

函数1 ::: tip 上图printf返回值的介绍中,每一个这样的函数返回的都是打印字符的个数。 上述程序最内层的printf函数打印的内容为“43”,共两个字符,即最内层的printf函数的返回值为2。从内向外第二层printf打印的内容为“2”,共一个字符,即从内向外第二层printf的返回值为1,最外层printf打印的内容“1”。 即第4行代码共打印内容为“4321”。 :::


函数声明和定义
函数声明

一个加法的程序:

#include <stdio.h>
int Add(int, int);  //函数声明(声明函数类型、参数类型)

int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);  //函数调用
    printf("%d", c);
    return 0;
}

int Add(int x, int y)  //函数定义(函数的具体实现,交代函数功能实现)
{
    int z = x + y;
    return z;
}

::: warning 头文件中放函数声明,源文件中放函数定义。 ::: ::: tip 在VS中,新建的头文件.h和源文件.c构成一个模块,如下述程序的“加法模块”。 需要使用该“加法模块”时,在主函数所在的文件中主函数上面,添加【#include "add.h"】就可以了。(引用自己写的头文件使用双引号,引用函数库中的头文件使用尖括号。) ::: 函数1 函数1 函数1 函数1 功能太过复杂可以使用分模块写法: 函数1 ::: tip 在正规工程中,上述计算机的各个模块需要使用时,写一个.h和.c文件,把函数声明放在.h文件中,把函数的定义放在.c文件中,在需要用的文件中引用头文件即可。(函数先声明后使用) :::

评论区

索引目录