在这篇文章中,我们将深入探讨C语言中的一个非常基础却极其重要的功能——函数。从最基础的概念开始,逐步深入到复杂的应用,我们将一步一步解锁C函数的奥秘。我们的旅程将从函数的定义开始,探索其语法和结构,然后通过实例学习如何在实际编程中有效地使用函数。
一,函数的定义和重要性
在C语言中,函数是执行特定任务的独立代码块。它们可以接受输入(参数),执行特定的操作,并产生输出(返回值)。函数的使用有助于提高代码的可读性和可重用性,使得程序结构更加清晰,更容易理解和维护。
二,函数的基本语法
C语言中的函数可以分为两大类:标准库函数和用户定义函数。标准库函数是C语言自带的,例如 “printf()”
,而用户定义函数则是由程序员根据需要自行定义的。一个基本的用户定义函数的语法结构如下:
返回类型 函数名(参数列表)
{
函数体
}
- 返回类型:指定函数返回值的数据类型。如果函数不返回任何值,则使用 “
void”
。 - 函数名:函数的唯一标识,用于在其他地方调用此函数。
- 参数列表:函数接收的输入值,参数之间用逗号分隔。如果函数不接受任何参数,则参数列表为空或写为 “
void”
。 - 函数体:包含执行特定任务的一系列语句,这是函数的核心部分。
一个简单的函数示例
#include <stdio.h>
// 函数定义
int add(int a, int b)
{
return a + b;
}
int main()
{
int result = add(5, 3); // 调用函数
printf("结果是:%d\n", result);//结果是:8
return 0;
}
这就类似于我们之前在数学中学习的函数y=kx+b —>result=a+b,你们可以好好体会一下是不是这样的。
三,参数传递和返回值
函数的参数可以按值传递,也可以通过指针传递以允许函数修改参数的值。返回值则是函数执行完成后返回给调用者的结果,它的类型必须与函数定义中的返回类型相匹配。
按值传递参数的例子
在按值传递中,函数接收参数的原始值的副本。在函数内对参数的任何修改都不会影响原始数据。
#include <stdio.h>
// 函数定义,按值传递参数
void addTen(int num) {
num += 10; // 对副本进行操作
printf("在函数内部,值变为:%d\n", num);//在函数内部,值变为:15
}
int main() {
int original = 5;
addTen(original); // 调用函数,传入original的值
printf("在函数外部,原始值仍为:%d\n", original); // original的值未改变,在函数外部,原始值仍为:5
return 0;
}
在这个例子中,即使在 addTen
函数中对 num
变量增加了10,原始的 original
变量的值在函数外部仍然未改变,因为我们是按值传递的。
通过指针传递参数的例子
通过指针传递允许函数直接修改原始数据的值,因为传递的是数据地址的副本,而不是数据值的副本。
#include <stdio.h>
// 函数定义,通过指针传递参数
void addTen(int *numPtr) {
*numPtr += 10; // 通过指针修改原始值
printf("在函数内部,值变为:%d\n", *numPtr);在函数内部,值变为:15
}
int main() {
int original = 5;
addTen(&original); // 传入original的地址
printf("在函数外部,原始值变为:%d\n", original); // original的值已改变,在函数外部,原始值变为:15
return 0;
}
这个输出表明,通过指针传递参数允许 addTen
函数直接修改原始变量 original
的值。因此,当我们在函数外打印 original
的值时,可以看到它已经被修改。
函数返回值的例子
函数返回值允许函数将结果传回给调用者。返回值的类型必须与函数声明的返回类型相匹配。
#include <stdio.h>
// 函数定义,返回两数之和
int sum(int a, int b) {
return a + b; // 返回结果
}
int main() {
int result = sum(5, 3); // 调用函数,并接收返回值
printf("两数之和为:%d\n", result);//两数之和为:8
return 0;
}
四,函数的进阶使用
随着你对C语言的深入了解,你会遇到更多高级主题,如递归函数、指针作为函数参数、函数指针以及内联函数等。这些都是提高你C语言功力的重要知识点。
我会通过介绍更多的函数体使用示例,以及一些进阶概念,来扩展我们对函数的理解。我们将通过几个不同的例子,展示函数在解决实际问题中的强大能力。
递归函数
递归函数是一种自我调用的函数,它可以用来解决分而治之的问题,如阶乘计算、斐波那契数列生成等。递归函数必须有一个明确的终止条件,以防止无限递归导致程序崩溃。
示例:计算阶乘
#include <stdio.h>
// 递归函数定义
int factorial(int n)
{
if (n == 0)
return 1; // 终止条件
else
return n * factorial(n - 1); // 递归调用
}
int main()
{
int num = 5;
printf("%d 的阶乘是:%d\n", num, factorial(num));
return 0;
}
为了更好地理解递归函数 factorial
如何工作,我们可以通过画图的方式展示函数调用的过程。以下是对给定代码的图解,它展示了计算 5
的阶乘 (5!
) 的递归调用过程。
1.初始调用:开始时,我们从 main
函数调用 factorial(5)
。
main
|
factorial(5)
2.递归调用过程:factorial(5)
需要计算 5 * factorial(4)
,但在计算 factorial(4)
之前,无法完成计算。这一过程会一直持续,直到达到递归的基准情况(即 factorial(0)
)。
factorial(5)
|
factorial(4)
|
factorial(3)
|
factorial(2)
|
factorial(1)
|
factorial(0)
3.达到基准情况:当调用 factorial(0)
时,根据函数定义,直接返回 1
,无需进一步的递归调用。
factorial(0) -> returns 1
4.递归返回:一旦达到基准情况,每个递归调用开始依次返回其结果到上一层调用,直到最初的调用 factorial(5)
factorial(0) -> returns 1
factorial(1) -> returns 1 * 1 = 1
factorial(2) -> returns 2 * 1 = 2
factorial(3) -> returns 3 * 2 = 6
factorial(4) -> returns 4 * 6 = 24
factorial(5) -> returns 5 * 24 = 120
5.完成计算并返回最终结果:最终,factorial(5)
返回 120
给 main
函数,并被打印出来。
main
|
factorial(5) -> returns 120
通过这种方式,递归函数将一个大问题分解为更小、更易于管理的问题(在这个例子中,是通过减少 n
的值来实现的),直到达到一个简单的情况可以直接解决(即 n == 0
时)。每一步的计算结果都依赖于下一步的结果,直到最后一步计算完成,然后逐步返回到最初的调用处。
需要注意的递归必须要有条件,否则他就会无限的递归下去,从而导致程序崩溃
就类似下面:
五,指针和函数
通过指针,函数可以修改调用者的变量。这在处理数组、字符串和动态数据结构时尤其有用。
示例:交换两个变量的值
#include <stdio.h>
// 使用指针参数交换两个变量的值
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 10, y = 20;
printf("交换前:x = %d, y = %d\n", x, y);
swap(&x, &y); // 调用函数
printf("交换后:x = %d, y = %d\n", x, y);
return 0;
}
这段代码演示了如何使用C语言中的指针来交换两个变量的值。下面是对这段代码的逐行详解:
包含标准输入输出库
#include <stdio.h>
这一行代码包含了标准输入输出库 stdio.h
,它允许我们使用输入输出函数,如 printf
。
定义交换函数
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void swap(int *a, int *b)
定义了一个名为swap
的函数,它接受两个整型指针a
和b
作为参数。这意味着,a
和b
存储的是两个整数变量的地址。int temp = *a;
声明了一个名为temp
的整型变量,并将a
指向的值(即a
指针所指向的变量的当前值)赋给temp
。这一步实际上保存了变量a
指向的值。*a = *b;
将b
指向的值赋给a
指向的位置。这一步实际上将b
的值复制到了a
的位置。*b = temp;
最后,将temp
(即原先a
指向的值)赋给b
指向的位置。这一步完成了两个值的交换。
主函数
int main()
{
int x = 10, y = 20;
printf("交换前:x = %d, y = %d\n", x, y);
swap(&x, &y); // 调用函数
printf("交换后:x = %d, y = %d\n", x, y);
return 0;
}
int main()
定义了程序的入口点,即主函数。int x = 10, y = 20;
声明了两个整型变量x
和y
,并分别初始化为10
和20
。printf("交换前:x = %d, y = %d\n", x, y);
打印出交换前x
和y
的值。swap(&x, &y);
调用了swap
函数,传入x
和y
的地址。这允许swap
函数直接操作x
和y
变量的内存,从而交换它们的值。printf("交换后:x = %d, y = %d\n", x, y);
再次打印x
和y
的值,展示它们已经被交换。return 0;
表示程序正常结束。
通过上述解析,可以看到,这个程序通过将变量地址传递给 swap
函数,并在函数内部通过指针操作来直接修改变量的值,从而实现了两个变量值的交换,而不需要使用第三个变量在主函数中进行中间存储。
六,函数指针
函数指针是指向函数的指针,通过它可以实现回调函数和高度灵活的编程模式。函数指针允许将函数作为参数传递给其他函数,增加了程序的灵活性。
示例:使用函数指针排序
#include <stdio.h>
#include <stdlib.h>
// 比较函数,用于整数升序排序
int compare(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}
int main()
{
int arr[] = {10, 5, 15, 12, 90, 80};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare); // 使用函数指针
for(int i = 0; i < n; i++)
printf("%d ", arr[i]);
return 0;
}
这段代码演示了如何使用 C 语言的 qsort
函数对一个整数数组进行排序。qsort
是 C 标准库中提供的一个通用排序函数,可以用来对任意类型的数组进行排序。下面是对这段代码的详细解释:
这两行代码包含了两个头文件:stdio.h
和 stdlib.h
。stdio.h
上面已经说了;而 stdlib.h
提供了一系列通用的工具函数,包括 qsort
函数。
定义比较函数
int compare(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}
compare
函数是一个比较函数,用于确定数组中两个元素的顺序。- 函数接受两个参数
a
和b
,它们的类型是const void *
,意味着可以接受任意类型的指针。在函数内部,这些指针被转换成int *
类型,然后通过解引用操作*
获取它们指向的整数值。 - 函数通过计算
*(int *)a - *(int *)b
来确定a
和b
指向的整数的相对大小。如果a
指向的整数小于b
指向的整数,返回值将为负;如果相等,返回值为零;如果a
指向的整数大于b
指向的整数,返回值为正。 qsort
函数使用这个比较函数来决定数组元素的排序顺序。
主函数和数组排序
int main()
{
int arr[] = {10, 5, 15, 12, 90, 80};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare); // 使用函数指针
arr
是一个包含整数的数组,需要被排序。n
是数组arr
的长度,通过sizeof(arr) / sizeof(arr[0])
计算得出。qsort
函数用于对数组arr
进行排序。它接受四个参数:排序的数组arr
,数组元素的数量n
,每个元素的大小sizeof(int)
,以及一个比较函数compare
。- 在这个例子中,
compare
函数作为qsort
的参数传递,用于指导如何比较数组中的元素。
打印排序后的数组
for(int i = 0; i < n; i++)
printf("%d ", arr[i]);
return 0;
- 这个循环遍历排序后的数组
arr
,使用printf
函数打印出每个元素。 - 最后,
main
函数返回0
,表示程序成功结束。
总结来说,这段代码演示了如何使用 qsort
和自定义的比较函数对一个整数数组进行升序排序,并展示了排序后的结果
很多人应该会很好奇,为什么这个qsort这个函数能够排序,下面会给你qsort底层的基本的思路
void Swap(char* buf1, char *buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int Cmp_Int(const void* I1, const void* I2)
{
return *(int*)I1 - *(int*)I2;
}
void Bubble_Sort(void * base,size_t size,size_t width,int(*cmp)(const void *p1,const void*p2))
{
for (int i = 0; i < size - 1; i++)
{
for (int j = 0; j < size - i - 1; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void Print_Int(int* p, int size)
{
int i = 0;
for (i= 0; i < size;i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[] = { 2,4,8,1,10,98,3,454,435, };
int size = sizeof(arr) / sizeof(arr[0]);
Bubble_Sort(arr, size, sizeof(int), Cmp_Int);
Print_Int(arr, size);
return 0;
}
这个就是它的底层逻辑,学有余力的可以看看
通过上述示例,我们展示了C函数的多样化应用,从基本的递归函数到高级的概念如函数指针和内联函数。每种技术都有其适用场景和优势,理解并掌握这些知识,将使你能够更加灵活和高效地使用C语言。