【C语言初阶】C语言函数全解析:编写高效代码的秘密武器

Eternity._ 2024-07-21 14:35:01 阅读 100

📝个人主页🌹:Eternity._

⏩收录专栏⏪:C语言 “ 登神长阶 ”

🤡往期回顾🤡:C语言分支与循环语句

🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

函数

📒1. C语言中函数的分类🎩库函数🎈自定义函数

📙2. 函数的参数🔍实际参数(实参)🔄形式参数(形参)

📕3. 函数的调用🌞传值调用🌙传址调用⭐调用练习

📚4. 函数的嵌套调用和链式访问🌸嵌套调用🌺链式访问

📜5. 函数的声明和定义🍁函数声明🍂函数定义

📝6. 函数递归🏞️递归的两个必要条件🧩递归练习

🌄递归与迭代🧩递归练习

📖7. 总结


前言: 在探索编程世界的浩瀚星图中,C语言无疑是一颗璀璨夺目的星辰,它不仅奠定了现代计算机编程语言的基础,更是无数软件与系统背后的基石。自其诞生以来,C语言以其高效、灵活、接近硬件的特性,赢得了开发者们的广泛青睐与深厚情感。而在这门语言的浩瀚海洋中,函数(Function)则是航行者手中的罗盘与风帆,指引着代码的方向,驱动着程序的运行

函数,作为C语言中最基本也是最强大的构建块之一,它不仅仅是一段可以重复使用的代码集合,更是模块化编程思想的集中体现。通过定义函数,我们能够将复杂的任务分解成一系列简单、易于理解和维护的子任务,这不仅提高了代码的可读性和可维护性,还促进了代码的重用,使得软件开发过程更加高效有序

在本文中,我们将踏上一场关于C语言函数的探索之旅,揭开其神秘面纱,一窥其内部结构与工作原理。我们将从函数的基本概念出发,逐步深入到函数的定义、声明、调用以及参数传递等核心知识点,通过生动的例子和详细的解析,帮助读者构建起对C语言函数全面而深刻的理解

让我们一同踏上这段充满挑战与收获的C语言函数之旅吧!


📒1. C语言中函数的分类

库函数自定义函数


🎩库函数

库函数是将一些常用到的函数编完放到一个文件(通常是库文件,如lib文件)里,供其他人或程序进行调用的方式。这些函数一般是编译器提供的,可以在C源程序中直接调用。库函数极大地方便了用户,同时也补充了C语言本身的不足

在这里插入图片描述

C语言常用的库函数

IO函数字符串操作函数字符操作函数内存操作函数时间/日期函数数学函数其他库函数

在这里插入图片描述

函数必须知道的一个秘密就是:使用库函数,必须包含 <code>#include 对应的头文件

推荐一个C/C++查询库函数的网址:库函数

在这里插入图片描述


🎈自定义函数

自定义函数是编程中非常重要的一个概念,它允许程序员根据自己的需求创建特定的函数来执行一系列操作或计算。这些函数可以在程序中多次调用,提高了代码的重用性和可维护性,给程序员一个很大的发挥空间

函数的组成:

<code>ret_type fun_name(para1, * )

{

......;//语句项

}

ret_type 返回类型

fun_name 函数

para1 函数参数

我们来简单定义几个函数

函数定义代码示例:

//获取最大值函数的设计

int get_max(int x, int y)

{

return (x > y) ? (x) : (y);

}

int main()

{

int num1 = 10;

int num2 = 20;

int max = get_max(num1, num2);

printf("max = %d\n", max);

return 0;

}

// 实现数据交换的函数

// 实现成函数,但是不能完成任务

void Swap1(int x, int y)

{

// 这里涉及到函数的形参,当需要修改变量的值的时候,我们在数据传输中,需要传递它的指针

int tmp = 0;

tmp = x;

x = y;

y = tmp;

}

//正确的版本

void Swap2(int* px, int* py)

{

int tmp = 0;

tmp = *px;

*px = *py;

*py = tmp;

}

int main()

{

int num1 = 1;

int num2 = 2;

Swap1(num1, num2);

printf("Swap1::num1 = %d num2 = %d\n", num1, num2);

Swap2(&num1, &num2);

printf("Swap2::num1 = %d num2 = %d\n", num1, num2);

return 0;

}


📙2. 函数的参数

🔍实际参数(实参)

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参


🔄形式参数(形参)

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效

上面 Swap1 和 Swap2 函数中的参数 x,y,px,py 都是形式参数。在main函数中传给 Swap1 的 num1 ,num2 和传给 Swap2 函数的 &num1 , &num2 是实际参数

在这里插入图片描述

内存分配

在这里插入图片描述

这里可以看到 Swap1 函数在调用的时候, <code>x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝


📕3. 函数的调用

函数的调用是编程中的一个基本操作,它涉及到执行已经定义好的函数代码块。在调用函数时,程序会暂停当前执行点的代码,跳转到被调用函数的开始处执行,执行完毕后再返回到调用点继续执行后续的代码


🌞传值调用

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


🌙传址调用

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


⭐调用练习

写一个函数判断一年是不是闰年

代码示例 (C语言):

// 写一个函数判断一年是不是闰年

int is_leap_year(int year)

{

if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)

{

return 1;

}

else

{

return 0;

}

}

int main()

{

int year = 0;

scanf("%d", &year);

if (is_leap_year(year) == 1)

{

printf("%d是闰年\n", year);

}

else

{

printf("%d不是闰年\n", year);

}

return 0;

}

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

代码示例 (C语言):

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

void add_num(int* num)

{

(*num)++;

}

int main()

{

int num = 0;

for (int i = 0; i < 10; i++)

{

add_num(&num);

printf("num: %d\n", num);

}

return 0;

}


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

函数的嵌套调用和链式访问是编程中常见的两种概念,它们在不同的编程语言和上下文中有着不同的表现形式,但核心思想都是为了提高代码的可读性和复用性

🌸嵌套调用

代码示例 (C语言):

void test1()

{

printf("hehe\n");

}

void test2()

{

int i = 0;

for (i = 0; i < 3; i++)

{

test1();

}

}

int main()

{

test2();

return 0;

}

注意:函数可以嵌套调用,但是不能嵌套定义


🌺链式访问

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

代码示例 (C语言):

#include <string.h>

int main()

{

char arr[20] = "hello";

int ret = strlen(strcat(arr, "world"));

// strlen用于计算字符串的长度,遇到'\0'停止, 头文件是string.h

// strcat用于将两个字符串连接起来 头文件是string.h

printf("%d\n", ret);

return 0;

}

int main()

{

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

return 0;

}

注意:printf函数的返回值是打印在屏幕上字符的个数


📜5. 函数的声明和定义

在编程中,函数的声明(Declaration)和定义(Definition)是两个相关但不同的概念,它们共同支持函数的使用。这两个概念在不同的编程语言和上下文中可能有所差异,但基本概念是相似的


🍁函数声明

告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数

声明决定不了函数的声明一般出现在函数的使用之前。要满足先声明后使用函数的声明一般要放在头文件中的

🍂函数定义

函数的定义是指函数的具体实现,交待函数的功能实现

代码示例 (C语言):

// test.h

int Add(int x, int y);

// test.c

#include "test.h"

int main()

{

printf("%d\n", Add(1, 2));

}

//函数Add的实现

int Add(int x, int y)

{

return x + y;

}


📝6. 函数递归

函数递归是一种在函数中调用自身来解决问题的编程技术。递归通过将问题分解成更小的、更易于解决的子问题,直到达到一个基本的、无需递归即可解决的边界情况(称为基准情况或基本情况)。递归函数必须有一个或多个基准情况,以避免无限递归调用导致的栈溢出错误


🏞️递归的两个必要条件

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


🧩递归练习

接受一个整型值(无符号),按照顺序打印它的每一位

代码示例 (C语言):

在这里插入图片描述

<code>// 接受一个整型值(无符号),按照顺序打印它的每一位

void print(int n)

{

if (n > 9)

{

print(n / 10);

}

printf("%d ", n % 10);

}

int main()

{

int num = 1234;

print(num);

return 0;

}


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

代码示例 (C语言):

int Strlen(const char* str)

{

// 递归出口

if (*str == '\0')

return 0;

// 统计数据

else

return 1 + Strlen(str + 1);

}

int main()

{

char* p = "abcdef";

int len = Strlen(p);

printf("%d\n", len);

return 0;

}


🌄递归与迭代

🧩递归练习

求n的阶乘

代码示例 (C语言):

int factorial(int n)

{

if (n <= 1)

return 1;

else

return n * factorial(n - 1);

}

int main()

{

printf("%d\n", factorial(5));

return 0;

}


求第n个斐波那契数

在这里插入图片描述

代码示例 (C语言):

<code>int fib(int n)

{

if (n <= 2)

return 1;

else

return fib(n - 1) + fib(n - 2);

}

int main()

{

printf("%d\n", fib(10));

return 0;

}

但是我们在使用这两个递归函数时,当数据过大时,特别耗费时间,且有可能程序会崩溃,其实是有许多重复的步骤让代码冗余

在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息,系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出

代码示例 (C语言):

int count = 0;//全局变量

int fib(int n)

{

if (n == 3)

count++;

if (n <= 2)

return 1;

else

return fib(n - 1) + fib(n - 2);

}

int main()

{

printf("%d\n", fib(20));

printf("count: %d", count);

return 0;

}

在这里插入图片描述

在输出第20位斐波那契数时,就调用了2584次第3位斐波那契数

解决办法:

将递归改写成非递归使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 <code>static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问


注意:

许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销

非递归斐波那契代码示例:

int fib(int n)

{

int result;

int pre_result;

int next_older_result;

result = pre_result = 1;

while (n > 2)

{

n -= 1;

next_older_result = pre_result;

pre_result = result;

result = pre_result + next_older_result;

}

return result;

}

int main()

{

printf("%d\n", fib(10));

return 0;

}


📖7. 总结

在结束这篇关于C语言函数的介绍文章之际,我们不禁感慨于C语言作为编程基石的深远影响与强大功能。函数,作为C语言程序设计的核心构件之一,不仅极大地提升了代码的可读性、可维护性和重用性,还为我们解决复杂问题提供了模块化、结构化的思维方式

通过深入学习C语言中的函数定义、声明、调用以及参数传递等关键概念,我们不仅能够编写出更加高效、清晰的代码,还能逐步培养出良好的编程习惯和问题解决能力。从简单的输入输出函数到复杂的算法实现,每一个函数的编写与调用都是对编程技艺的一次锤炼与提升

更重要的是,C语言函数的学习为我们后续探索更高级、更专业的编程语言和技术领域打下了坚实的基础。无论是面向对象的程序设计、系统级编程还是嵌入式系统开发,对函数的理解和应用都是不可或缺的一部分

让我们在掌握C语言函数的基础上,继续保持对编程的热情与好奇心,勇于探索未知,不断挑战自我!!!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!

谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。