泉州大型网站建设,范湖网站建设哪家便宜,泰安网站建设价格,南通网站推广排名文章目录#x1f490;专栏导读#x1f490;文章导读#x1f337;函数是什么#xff1f;#x1f337;函数的分类#x1f33a;库函数#x1f33a;自定义函数#x1f337;函数的参数#x1f337;函数的调用#x1f337;函数的嵌套调用和链式访问#x1f33a;嵌套调用专栏导读文章导读函数是什么函数的分类库函数自定义函数函数的参数函数的调用函数的嵌套调用和链式访问嵌套调用链式访问函数的声明和定义函数声明函数定义函数递归什么是递归递归的两个必要条件递归与迭代专栏导读 作者简介花想云在读本科生一枚致力于 C/C、Linux 学习。 专栏简介本文收录于 C语言初阶专栏本专栏主要内容为C语言的初阶知识的全套讲解包含初识C语言、函数、数组、分支与循环、操作符、指针初阶的讲解。 相关专栏推荐C语言进阶系列 、数据结构与算法。 文章导读
本章为大家介绍了C语言中函数的相关概念。如函数是什么、函数的分类、函数的形参与实参、函数的调用、函数的递归、函数的递归与迭代等等。
函数是什么
数学中我们常见到函数的概念那么C语言中的函数是什么呢 来看看维基百科中对于函数的定义 在计算机科学中函数 子程序英语Subroutine, procedure, function, routine, method,subprogram, callable unit是一个大型程序中的某部分代码由一个或多个语句块组成。它负责完成某项特定任务而且相较于其他代码具备相对的独立性。一般会有输入参数并有返回值提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。png) 我是这么理解函数的函数——具有某种功能的代码块。
一个程序中我们经常会用到某种功能如两数相加如果每次都在需要用到时实现那么就显得过于繁杂。此时我们就可以将两数相加的功能封装起来在需要使用的地方进行函数调用即可。
函数的分类
库函数 我们知道在我们学习C语言编程的时候总是在一个代码编写完成之后迫不及待的想知道结果想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能将信息按照一定的格式打印到屏幕上printf。 在编程的过程中我们会频繁的做一些字符串的拷贝工作strcpy。 在编程是我们也计算总是会计算n的k次方这样的运算pow。
像上面我们描述的基础功能它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到为了支持可移植性和提高程序的效率所以C语言的基础库中提供了一系列类似的库函数方便程序员进行软件开发。
那么库函数都有哪些呢这里我给大家提供一个使用频率非常高的网站 http://www.cplusplus.com简易轻便缺点是全英文但并不影响阅读 在里面我们可查询到各种各样的库函数及其使用方法。
库函数数量很多无法一一列举只能对其分类 IO函数 字符串操作函数 字符操作函数 内存操作函数 时间/日期函数 数学函数 其他库函数
我们参照文档简单认识几个库函数
strlen求字符串长度的函数
size_t strlen(const char* str)//size_t本质就是unsigned int#include stdio.h//包含库函数printf所对应的头文件stdio.h
#include string.h//包含库函数strlen所对应的头文件string.hint main()
{int str1[] zhangsan;int str2[] lisi;int len strlen(str1);printf(%d,len);return 0;
}strcpy将一个字符串中的内容拷贝到另一个字符串
char* strcpy(char* destination, const char* source);#include stdio.h//包含库函数printf所对应的头文件stdio.h
#include string.h//包含库函数strcpy所对应的头文件string.hint main()
{int str1[] zhangsan;int str2[] lisi;int len strlen(str1);strcpy(str1, str2);printf(%d,len);return 0;
}注意使用库函数必须包含 #include 对应的头文件。
自定义函数
如果库函数能干所有的事情那还要程序员干什么
所以更加重要的是自定义函数。
自定义函数和库函数一样有函数名返回值类型和函数参数。
但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
函数的构成
函数返回值类型 函数名 参数1参数2参数3......)
{//...函数体
}举例
写一个函数可以计算两个数之和
#include stdio.h
//add函数的设计
int add(int x, int y) {return x y;
}写一个函数可以找出两个整数中的最大值
#include stdio.h
//get_max函数的设计
int get_max(int x, int y) {return (x y) ? (x) : (y);
}
函数的参数
C语言中函数的参数一般分为两种 实际参数实参 真实传给函数的参数叫实参。 实参可以是常量、变量、表达式、函数等。 无论实参是何种类型的量在进行函数调用时它们都必须有确定的值以便把这些值传送给形参。 形式参数形参 形式参数是指函数名后括号中的变量因为形式参数只有在函数被调用的过程中才实例化分配内存单元所以叫形式参数。 形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
举例交换两个变量num1和num2中的值
#include stdio.h
//实现成函数但是不能完成任务
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;
}运行结果如图所示至于为什么两个函数的运行结果不相同原因如下
首先 Swap1 和 Swap2 函数中的参数 xypxpy 都是形式参数。在main函数中传给 Swap1 的 num1 num2 和传给 Swap2 函数的 num1 num2 是实际参数。在调用函数Swap1时形参 x 和 y 拥有自己的空间同时拥有了和实参一模一样的内容。 此时x1y2当函数执行完之后x2y2。x和y确实交换了值但与num1和num2并没有什么关系。Swap2不同形参 px、py是指针变量int*为指针类型后期会讲对它俩进行解引用后再将值交换实际上交换的就是num1和num2所在空间的内容。
所以我们可以简单的认为形参实例化之后其实相当于实参的一份临时拷贝。
函数的调用
函数的调用分为两种
传值调用
函数的形参和实参分别占有不同内存块对形参的修改不会影响实参。例如Swap1
传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系也就是函数内部可以直接操作函数外部的变量。例如Swap2
函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的也就是互相调用的。
嵌套调用
我中有你你中有我
在一个函数中可以调用另外一个函数。
举例
#includestdio.h
int add(int x, int y)//函数1
{return x y;
}int addplus(int x, int y,int z)//函数2
{int sum 0;sum add(x, y);//调用函数1return sum z;
}
int main()
{int num1 1;int num2 2;int num3 3;int praddplus(num1, num2, num3);printf(%d\n, pr);return 0;
}链式访问
把一个函数的返回值作为另外一个函数的参数。
举例
#includestdio.h
int add(int x, int y)//add函数返回值是int类型
{return x y;
}#include stdio.h
int main()
{int num1 1;int num2 2;printf(%d\n, add(num1,num2));printf(%d, printf(%d, printf(%d, 43)));//结果是啥//注printf函数的返回值是打印在屏幕上字符的个数return 0;
}函数的声明和定义
函数声明 告诉编译器有一个函数叫什么参数是什么返回类型是什么。但是具体是不是存在函 数声明决定不了 函数的声明一般出现在函数的使用之前。要满足先声明后使用 函数的声明一般要放在头文件中的。
举例
#pragma once
#includestdio.h
#includestdlib.h
#includeassert.h//申请一个结点
SLTNode* BuySLTNode(SLTDataType data);
//创建一个链表包含数据为0~n
SLTNode* CreateSList(int n);
//释放内存
void SLTDestroy(SLTNode** pphead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType data);
//尾删
void SLTPopBack(SLTNode** pphead);
//...函数定义
函数的定义是指函数的具体实现交待函数的功能实现。
举例
SLTNode* BuySLTNode(SLTDataType data)
{SLTNode* newNode (SLTNode*)malloc(sizeof(SLTNode));//检查是否申请成功if (newNode NULL){perror(malloc fail);exit(-1);}//对newNode进行初始化newNode-data data;newNode-next NULL;//返回申请成功的结点return newNode;
}SLTNode* CreateSList(int n)
{...//过程省略
}void SLTDestroy(SLTNode** pphead)
{...//过程省略
}void SLTPushBack(SLTNode** pphead, SLTDataType data)
{...//过程省略
}
void SLTPopBack(SLTNode** pphead)
{...//过程省略
}函数递归
什么是递归
程序调用自身的编程技巧称为递归 recursion。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解 递归策略 只需少量的程序就可描述出解题过程所需要的多次重复计算大大地减少了程序的代码量。
递归的主要思考方式在于把大事化小。
递归的两个必要条件 存在限制条件当满足这个限制条件的时候递归便不再继续 每次递归调用之后越来越接近这个限制条件。
举例 接受一个整型值无符号按照顺序打印它的每一位例如输入1234输出 1 2 3 4。
#include stdio.h
void print(int n)
{if (n 9){print(n / 10);}printf(%d , n % 10);
}
int main()
{int num 1234;print(num);return 0;
}递归与迭代
函数在被调用时会建立函数栈帧简单理解为建立函数栈帧就是在内存中申请了一块中间来运作函数执行。有些情况下递归并不是特别高效例如遇到斐波那契数列的问题时就不再那么实用虽然递归的实现方式易于理解代码。
举例
//求斐波那契数列中第n个数
int fib(int n) {if (n 2)return 1;elsereturn fib(n - 1) fib(n - 2);
}这段代码理论上可以求出任何n的结果但是实际上当n等于50左右程序就会挂掉。原因是这段代码的算法对内存的消耗巨大。 在调试 fib 函数的时候如果你的参数比较大那就会报错 stack overflow栈溢出这样的信息。系统分配给程序的栈空间是有限的但是如果出现了死循环或者死递归这样有可能导致一直开辟栈空间最终产生栈空间耗尽的情况这样的现象我们称为栈溢出。 那如何解决上述的问题
将递归改写成非递归使用static对象替代 nonstatic 局部对象。在递归函数设计中可以使用 static对象替代nonstatic局部对象即栈对象这不 仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销而且static 对象还可以保存递归调用的中间状态并且可为各个调用层所访问
举例 下面代码就采用了非递归的方式来实现
//求第n个斐波那契数
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;
}许多问题是以递归的形式进行解释的这只是因为它比非递归的形式更为清晰。 但是这些问题的迭代实现往往比递归实现效率更高虽然代码的可读性稍微差些。 当一个问题相当复杂难以用迭代实现时此时递归实现的简洁性便可以补偿它所带来的运行时开销。 点击下方个人名片可添加博主的个人QQ交流会更方便哦~ *** ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓***