欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > C语言 ——— 函数

C语言 ——— 函数

2025/5/6 23:59:35 来源:https://blog.csdn.net/weixin_55341642/article/details/147465323  浏览:    关键词:C语言 ——— 函数

目录

函数是什么

库函数

学习使用 strcpy 库函数

自定义函数

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

写一个函数交换两个整型变量的内容

牛刀小试

写一个函数判断一个整数是否是素数

写一个函数判断某一年是否是闰年

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

函数的嵌套调用

函数的链式访问

一段有趣的代码

函数的声明、调用和定义

函数的定义

函数调用

函数声明 

函数递归 

什么是递归

递归的两个必要条件 

一个简单的递归

牛刀小试

要求使用函数递归实现:接受一个无符号整型值,按照顺序打印他的每一位

要求写一个函数:能实现求字符串的长度 

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


函数是什么

在 C 语言里,函数和数学中的函数有相似之处

以数学函数 f(x)=2x+1 为例,当 x 取值不同时,函数会有不同的结果。比如 x=2 时,将 x=2 代入函数可得 f(2)=2×2+1=5;当 x=3 时,代入后得到 f(3)=2×3+1=7

同样,这个数学函数也能用 C 语言中的函数来表示。我们可以定义一个 C 语言函数,让它接收一个参数,在函数内部按照 2x+1 的规则进行计算并返回结果,从而实现与这个数学函数相同的功能

代码演示:

int f(int x)
{return 2*x + 1;
}int main()
{int x = 0;scanf("%d", &x);printf("%d\n", f(x));return 0;
}

库函数

在 C 语言里,为了方便使用常用功能,会将这些功能封装成一个个函数,这些函数被称为库函数,像 printf 函数、scanf 函数、strlen 函数等都属于库函数

需要注意的是,C 语言本身并没有直接实现这些库函数,而是制定了 C 语言的标准以及库函数的约定。比如对于 scanf 函数,C 语言标准规定了它的功能、函数名、参数和返回值等

而库函数的具体实现通常由编译器来完成,常见的编译器如 VS2022 编译器、gcc 编译器等,它们会依据 C 语言标准对库函数进行具体的编码实现,这样开发者就能在编程时直接使用这些库函数了

学习使用 strcpy 库函数

C/C++ 中的库函数信息,都可以在 cplusplus.com 网站 上查询到

如果想学习或了解某个库函数的用法、参数含义及功能,直接在这个网站搜索对应的函数名即可,它是编程中查阅库函数的实用资源

strcpy 库函数

“destination” 代表目的地字符串,“source” 代表源头字符串。当一个函数的返回值类型为 char* 时,意味着该函数返回的是目的地字符串的首地址

strcpy 是一个库函数,从其文档可知,它的功能是进行字符串拷贝。具体来说,就是把源头字符串的数据复制到目的地字符串,并且会覆盖目的地字符串原有的内容,同时还会将源头字符串末尾的结束符 '\0' 也一同复制过去

若要在代码中使用 strcpy 函数,需要包含相应的头文件,头文件代码如下:

#include<string.h>

代码演示:

char source[] =  "hello world";
char destination[] =  "xxxxxxxxxxxxxxxx";strcpy(destination, source);printf("%s\n", destination);

代码验证:


自定义函数

在编程中,自定义函数指的是开发者根据实际需求自己编写的函数。它和 C 语言中的库函数(如printfstrlen)一样,都遵循相同的基本结构,包括:

  • 返回类型:函数执行完毕后输出的数据类型(如intchar*);
  • 参数:函数执行时需要的输入信息(可以没有参数,也可以有多个);
  • 返回值:通过return语句返回的结果(若无需返回结果,返回类型为void

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

代码演示: 

int get_max(int a, int b)
{return a > b ? a : b;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);int max = get_max(a, b);printf("%d\n", max);return 0;
}

写一个函数交换两个整型变量的内容

代码演示:

void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a = %d;b = %d\n", a, b);Swap(&a, &b);printf("交换后:a = %d;b = %d\n", a, b);return 0;
}

在编写函数交换两个整型变量的值时,不能直接传递实参。这是因为在函数调用时,实参的值会被复制给形参,实参和形参分别占用不同的内存空间,也就是各自独立的空间。在函数内部对形参进行操作,只会改变形参的值,而不会影响到实参原本的值

为了真正实现交换两个实参的值,我们需要传递实参的地址。由于实参是 int 类型,所以函数的形参要使用 int* 类型来接收这些地址。int* 类型的变量可以存储整型变量的地址。在函数内部,通过 * 这个解引用关键字,我们可以根据存储的地址找到对应的实参,进而对实参的值进行修改,这样就能实现两个实参值的交换


牛刀小试

写一个函数判断一个整数是否是素数

代码演示:

int is_prime(int tmp)
{if (tmp <= 1)return 0;for (int i = 2; i <= sqrt(tmp); i++){if (tmp % i == 0)return 0;}return 1;
}int main()
{int input = 0;scanf("%d", &input);if (is_prime(input))printf("is prime\n");elseprintf("not is prime\n");return 0;
}

代码解析:

我们要编写一个函数来判断一个整数是否为素数。素数是指大于 1 且只能被 1 和自身整除的正整数。所以在判断之前,首先要排除小于等于 0 的整数,因为它们显然不符合素数的定义

该函数的返回规则是:返回 0 表示这个数不是素数,返回 1 表示这个数是素数

对于输入的变量 input,我们需要判断它是否为素数。判断的方法是,用 input 对 2 到 input - 1 之间的数进行取模运算。可以使用 for 循环来遍历这个区间内的所有数。如果在遍历过程中,input 对某个数取模的结果为 0,那就说明 input 除了 1 和它本身之外,还能被其他数整除,那么它就不是素数,此时函数直接返回 0

不过,其实并不需要从 2 遍历到 input - 1,只需要遍历 2 到 sqrt(input)sqrt 是开平方函数)之间的数即可。这是因为如果一个数 input 不是素数,那么它一定可以分解为两个因数 m 和 n,即 input = m * n,其中 m 和 n 中至少有一个小于等于 sqrt(input)。所以,只要检查到 sqrt(input) 就可以判断 input 是否为素数了

如果 for 循环执行完毕都没有找到能整除 input 的数,那就说明 input 只能被 1 和它本身整除,即 input 是素数,此时函数返回 1

写一个函数判断某一年是否是闰年

代码演示:

int is_leap_year(int year)
{if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))return 1;elsereturn 0;
}int main()
{ int year = 0;scanf("%d", &year);is_leap_year(year);if (is_leap_year(year))printf("is leap year\n");elseprintf("not is leap year\n");return 0;
}

代码解析:

闰年的判断规则是能被 4 整除但不能被 100 整除,或能被 400 整除的年份

根据以上的规则编写出对应的逻辑代码,就能判断某一年是否是润年

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

代码演示:

int binary_search(int* parr, int size, int number)
{int left = 0;int right = size - 1;while (left <= right){int mid = (left + right) / 2;if (parr[mid] < number){left = mid + 1;}else if(parr[mid] > number){right = mid - 1;}else{return mid;}}return -1;
}int main()
{int arr[] = { 1,2,4,6,7,9,12,32,45,77,90 };int size = sizeof(arr) / sizeof(arr[0]);int input = 0;scanf("%d", &input);int ret = binary_search(arr, size, input);if (ret == -1)printf("number not found\n");elseprintf("number index is: %d\n", ret);return 0;
}

代码解析:

我们要实现一个二分查找函数,二分查找是一种高效的查找算法,它的前提是数组必须是有序的。在函数调用时,需要传递三个参数:数组的指针 arr,用于访问数组元素;数组的元素个数 size,这样能确定查找范围;要查找的整数 input,即我们要在数组中找到的目标值

函数的返回值规则是:如果在数组中找到了目标值 input,就返回该值在数组中的下标;如果没有找到,就返回 -1,因为数组的下标最小是从 0 开始的, -1 可以作为一个明确的未找到的标识

  1. 初始化查找范围:定义两个变量,left 作为左下标,初始值设为 0,它指向数组的起始位置;right 作为右下标,初始值设为 size - 1,它指向数组的末尾位置
  2. 开始循环查找:使用 while 循环来进行查找,循环的条件是 left <= right。只要满足这个条件,就说明还有元素没有被检查过,查找过程可以继续
  3. 计算中间下标:在每次循环内部,计算中间元素的下标 mid,计算公式为 mid = (left + right) / 2。通过这个中间下标,我们可以将数组分成两部分
  4. 比较中间元素与目标值
    • 如果 input 大于中间元素 arr[mid],说明目标值在数组的右半部分,此时更新 left 为 mid + 1,缩小查找范围到右半部分
    • 如果 input 小于中间元素 arr[mid],说明目标值在数组的左半部分,此时更新 right 为 mid - 1,缩小查找范围到左半部分
    • 如果 input 等于中间元素 arr[mid],说明已经找到了目标值,直接返回 mid,也就是目标值在数组中的下标
  5. 未找到目标值:如果 while 循环结束后还没有找到目标值,说明目标值不在数组中,此时返回 -1 即可

函数的嵌套调用

在编程中,当我们定义了多个函数后,有时会需要在一个函数的执行过程中,调用另一个函数来完成特定任务。这种 在一个函数内部调用另一个函数的方式,就是函数的嵌套调用

代码演示:

void print()
{printf("hello world\n");
}void three_print()
{for (int i = 0; i < 3; i++){print();}
}int main()
{three_print();return 0;
}

在 three_print 函数中调用了 print 函数,这就是函数的嵌套调用 


函数的链式访问

函数的链式访问是一种编程技巧,它指的是将一个函数的返回值直接作为另一个函数的参数来使用

代码演示:

printf("%d\n", strlen("abcdef"));

把 strlen 函数的返回值作为 printf 函数的参数,这就是函数的链式访问 

函数的链式访问可以让代码更加简洁和紧凑,避免创建中间变量。不过,在使用链式访问时也要注意代码的可读性,如果链式访问的函数过多,可能会让代码变得难以理解和调试。所以,在实际编程中要根据具体情况合理使用函数的链式访问 

一段有趣的代码

代码演示:

printf("%d", printf("%d", printf("%d", 43)));

问:最后在控制台上输出的结果是多少?

需明确 printf 函数的返回值为其输出的字符个数:

如:printf("%d", 1); 的返回值就是 1,printf("%d", 123); 的返回值就是 3

具体执行过程如下:

  1. 执行最内层 printf("%d", 43),会先在控制台输出 43(共 2 个字符),返回值为 2
  2. 中间层 printf("%d", 2) 接收内层返回值,输出 2(1 个字符),返回值为 1
  3. 最外层 printf("%d", 1) 接收中间层返回值,输出 1(1 个字符)

综上,控制台输出结果为 4321

代码验证:


函数的声明、调用和定义

函数的定义

int Add(int a, int b)
{return a + b;
}

函数 Add 的功能为计算两个整数的和,其定义是实现该功能的具体代码逻辑,即通过设定参数接收两个整数输入,在函数体内执行加法运算,并将运算结果作为返回值输出,从而完成两个整数相加功能的程序实现

函数调用

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

在 main 函数里使用 Add 函数的操作,在编程中被称作函数调用,此操作可触发 Add 函数执行其预设功能

函数声明 

// 函数声明
int Add(int a, int b);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 a, int b)
{return a + b;
}

在 C 程序设计中,若自定义函数的定义位于 main 函数之后,则需在 main 函数执行前进行函数声明

这是由于程序遵循从上至下的执行逻辑,编译器在处理 main 函数时若未预先知晓自定义函数的存在,会因无法识别函数名称而报错

按照模块化编程规范,函数声明通常被放置在头文件(.h)中,用于告知编译器函数的参数类型和返回值类型;而函数的具体实现(定义)则集中存储在对应的源文件(.c)中,这种分离式设计有助于代码的组织、维护及复用,确保程序在编译阶段能够正确解析函数调用关系


函数递归 

什么是递归

递归是函数直接或间接调用自身的编程方式。需定义基线条件(终止递归的条件)和递归条件(逐步逼近基线的逻辑),通过重复调用解决可分解为相似子问题的任务

递归的核心思维在于:把大事化小

递归的两个必要条件 

  1. 基线条件(终止条件):必须存在至少一个无需递归调用的终止条件,用于退出递归过程,避免无限循环;
  2. 递归条件:函数需通过自身调用逐步分解问题,且每次递归调用必须更接近基线条件,确保问题规模递减直至满足终止条件

一个简单的递归

int main()
{printf("hello\n");main();return 0;
}

若 main 函数直接调用自身,构成直接递归。由于该递归未设置基线条件(终止条件),程序会无限次递归调用自身,导致调用栈不断增长

当栈空间被耗尽时,将引发栈溢出错误,造成程序异常终止。这一现象体现了递归中终止条件的必要性 —— 缺少该条件会破坏递归的收敛性,最终导致内存资源耗尽

牛刀小试

要求使用函数递归实现:接受一个无符号整型值,按照顺序打印他的每一位

例如:

输入:1234 ;输入:1 2 3 4 

代码演示:

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

代码解析:

该函数实现按顺序打印无符号整数每一位的功能,核心通过数学运算 %10(取个位)和 /10(去除个位)分解数字,并结合递归逐层处理

算法思路:

数字分解原理

  1. 对于任意整数 nn % 10 可获取其个位数字(如 123 % 10 = 3
  2. n / 10 可去除个位,得到高位数字(如 123 / 10 = 12

通过重复这两步,可逐位拆解整数的每一位

递归过程分析(以 n = 123 为例):

首次调用与递归展开

  • 初始调用 print_every_one(123),因 123 > 9,触发递归调用 print_every_one(123 / 10 = 12)
  • 第二次调用 print_every_one(12),因 12 > 9,继续递归调用 print_every_one(12 / 10 = 1)

基线条件触发(递归终止)

  • 第三次调用 print_every_one(1),此时 1 <= 9,不满足递归条件,跳过 if 语句,直接执行 printf("%d ", 1 % 10),输出 1

逐层返回与后续打印

  • 返回上一层(第二次调用,n = 12),执行 printf("%d ", 12 % 10),输出 2
  • 再返回初始层(首次调用,n = 123),执行 printf("%d ", 123 % 10),输出 3

最终输出结果

控制台按递归返回顺序打印 1 2 3,实现从高位到低位的顺序输出

核心逻辑总结:

  • 递归特性:通过不断将问题规模缩小(n / 10),直至满足基线条件(n <= 9)时终止递归,再逐层返回处理当前层的打印逻辑
  • 执行顺序:递归调用时先处理高位(通过不断剥离个位),返回时再依次打印低位,利用调用栈的后进先出特性,自然实现从高位到低位的顺序输出

要求写一个函数:能实现求字符串的长度 

代码演示:

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

代码解析:

此函数 my_strlen 用于计算字符串的长度。参数 s 是一个指向字符串首字符的指针

在 C 语言里,字符串以 '\0' 作为结束标志。函数利用 while 循环来遍历字符串,只要当前指针 s 所指向的字符不是 '\0',就表明还有字符需要统计

在循环内部,count 变量用于统计字符串中字符的数量,每统计一个字符,count 的值就加 1。同时,指针 s 通过 s++ 操作指向下一个字符,从而实现对字符串的逐字符遍历

当 s 指向 '\0' 时,意味着已经遍历完整个字符串,此时循环结束,count 变量中存储的数值就是字符串中字符的总个数,也就是该字符串的长度,最后将其作为函数的返回值返回

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

代码演示:

int my_strlen(const char* s)
{if (*s == '\0')return 0;return 1 + my_strlen(s+1);
}

代码解析:

函数参数与返回值

该函数接受一个指向 const char 类型的指针 s 作为参数,const 表明此指针指向的字符串内容不能被修改。函数返回一个 int 类型的值,即字符串的长度

递归终止条件

在函数内部,首先会检查指针 s 所指向的字符是否为 '\0'。在 C 语言中,字符串以 '\0' 作为结束标志。如果当前字符是 '\0',则意味着已经到达字符串的末尾,此时函数将返回 0。这是递归的终止条件,它确保了递归调用不会无限进行下去,避免出现栈溢出的错误

递归调用逻辑

若当前字符不是 '\0',说明还未遍历完整个字符串。函数会将指针 s 向后移动一位(s + 1),使其指向下一个字符,然后再次调用 my_strlen 函数,以计算从下一个字符开始的子字符串的长度。由于当前字符也是字符串的一部分,所以在递归调用返回的结果上加 1,表示当前字符的长度。最终将这个累加后的结果作为当前字符串的长度返回

递归调用示例

假设传入的字符串为 "abc",递归调用过程如下:

  • 第一次调用 my_strlen("abc"),当前字符为 'a',不是 '\0',则进行递归调用 1 + my_strlen("bc")
  • 第二次调用 my_strlen("bc"),当前字符为 'b',不是 '\0',继续递归调用 1 + my_strlen("c")
  • 第三次调用 my_strlen("c"),当前字符为 'c',不是 '\0',再次递归调用 1 + my_strlen("")
  • 第四次调用 my_strlen(""),此时当前字符为 '\0',满足终止条件,返回 0
  • 返回到第三次调用,my_strlen("c") 返回 1 + 0 = 1
  • 返回到第二次调用,my_strlen("bc") 返回 1 + 1 = 2
  • 返回到第一次调用,my_strlen("abc") 返回 1 + 2 = 3

通过这种递归的方式,函数逐步缩小问题规模,直到满足终止条件,最终计算出整个字符串的长度

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词