
函数的概念数学中我们其实就⻅过函数的概念⽐如⼀次函数ykxb k和b都是常数给⼀个任意的x就得到⼀个y值。其实在C语⾔也引⼊函数function的概念有些翻译为⼦程序⼦程序这种翻译更加准确⼀些。 C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的也可以说⼀个⼤的计算任务可以分解成若⼲个较⼩的函数对应较⼩的任务完成。同时⼀个函数如果能完成某项特定任务的话这个函数也是可以复⽤的提升了开发软件的效率。在C语⾔中我们⼀般会⻅到两类函数库函数自定义函数1.库函数前⾯内容中的 printf、scanf都是库函数库函数的也是函数不过这些函数已经是现成的我们只要学会就能直接使⽤了。有了库函数⼀些常⻅的功能就不需要程序员⾃⼰实现了⼀定程度提升了效率同时库函数的质量和执⾏效率上都更有保证。各种编译器的标准库中提供了⼀系列的库函数这些库函数根据功能的划分都在不同的头⽂件中进⾏了声明。库函数相关头⽂件https://zh.cppreference.com/w/c/header库函数的学习和查看⼯具很多⽐如C/C官⽅的链接https://zh.cppreference.com/w/c/headercplusplus.comhttps://legacy.cplusplus.com/reference/clibrary/必须先声明包含库函数的头文件才能使用相应的库函数#include stdio.h #include math.h int main() { double d 16.0; double r sqrt(d); printf(%lf\n, r); return 0; }运⾏结果2.自定义函数了解了库函数我们的关注度应该聚焦在⾃定义函数上⾃定义函数其实更加重要也能给程序员写代码更多的创造性。ret_type fun_name(形式参数) { }1. ret_type 是函数返回类型2. fun_name 是函数名3.括号中放的是形式参数4.{}括起来的是函数体例如#include stdio.h int Add(int x, int y) { int z 0; z xy; return z; } int main() { int a 0; int b 0; //输⼊ scanf(%d %d, a, b); //调⽤加法函数完成a和b的相加 //求和的结果放在r中 int r Add(a, b); //输出 printf(%d\n, r); return 0; }3.形参和实参在函数使⽤的过程中把函数的参数分为实参和形参。3.1实参在上⾯代码中第2~7⾏是Add函数的定义有了函数后再第17⾏调⽤Add函数的。我们把第17⾏调⽤Add函数时传递给函数的参数a和b称为实际参数简称实参。实际参数就是真实传递给函数的参数。3.2形参在上⾯代码中第2⾏定义函数的时候在函数名 Add后的括号中写的x和y称为形式参数简称形参。为什么叫形式参数呢实际上如果只是定义了 Add 函数⽽不去调⽤的话Add函数的参数x和 y只是形式上存在的不会向内存申请空间不会真实存在的所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值才向内存申请空间这个过程就是形式的实例化。3.3形参和实参的关系虽然我们提到了实参是传递给形参的他们之间是有联系的但是形参和实参各⾃是独⽴的内存空间。我们在调试的可以观察到x和y确实得到了a和b的值但是x和y的地址和a和b的地址是不⼀样的所以我们可以理解为形参是实参的⼀份临时拷⻉。4.return语句1. return后边可以是⼀个数值也可以是⼀个表达式如果是表达式则先执⾏表达式再返回表达式的结果。2.return后边也可以什么都没有直接写 return; 这种写法适合函数返回类型是void的情况。3.return返回的值和函数返回类型不⼀致系统会⾃动将返回的值隐式转换为函数的返回类型。4.return语句执⾏后函数就彻底返回后边的代码不再执⾏。5.如果函数中存在if等分⽀的语句则要保证每种情况下都有return返回否则会出现编译错误。5.数组做函数参数在使⽤函数解决问题的时候难免会将数组作为参数传递给函数在函数内部对数组进⾏操作。 ⽐如写⼀个函数对将⼀个整型数组的内容全部置为-1再写⼀个函数打印数组的内容。 简单思考⼀下基本的形式应该是这样的#include stdio.h void set_arr(int arr[], int sz) { int i 0; for(i0; isz; i) { arr[i] -1; } } void print_arr(int arr[], int sz) { int i 0; for(i0; isz; i) { printf(%d , arr[i]); } printf(\n); } int main() { int arr[] {1,2,3,4,5,6,7,8,9,10}; set_arr(arr, sz);//设置数组内容为-1 print_arr(arr, sz);//打印数组内容 return 0; }6.函数嵌套调⽤和链式访问嵌套调⽤就是函数之间的互相调⽤每个函数就⾏⼀个乐⾼零件正是因为多个乐⾼的零件互相⽆缝的配合才能搭建出精美的乐⾼玩具也正是因为函数之间有效的互相调⽤最后写出来了相对⼤型的程序。int is_leap_year(int y) { if(((y%40)(y%100!0))||(y%4000)) return 1; else return 0; } int get_days_of_month(int y, int m) { int days[] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int day days[m]; if (is_leap_year(y) m 2) day 1; return day; } int main() { int y 0; int m 0; scanf(%d %d, y, m); int d get_days_of_month(y, m); printf(%d\n, d); return 0; }所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数像链条⼀样将函数串起来就是函数的链式访问。#include stdio.h int main() { printf(%d, printf(%d, printf(%d, 43))); return 0; }这个代码的关键是明⽩printf函数的返回是啥int printf ( const char * format, ... );printf函数返回的是打印在屏幕上的字符的个数。上⾯的例⼦中我们就第⼀个printf打印的是第⼆个printf的返回值第⼆个printf打印的是第三个 printf的返回值第三个printf打印43在屏幕上打印2个字符再返回2第⼆个printf打印2在屏幕上打印1个字符再放回1第⼀个printf打印1所以屏幕上最终打印43217.函数的声明和定义函数定义放函数调用前面就可以直接调用要是在后面就需要声明然后才能调用。函数的定义也是⼀种特殊的声明所以如果函数定义放在调⽤之前也是可以的。7.1多个⽂件⼀般在企业中我们写代码时候代码可能⽐较多不会将所有的代码都放在⼀个⽂件中我们往往会根据程序的功能将代码拆分放在多个⽂件中。⼀般情况下函数的声明、类型的声明放在头⽂件.h中函数的实现是放在源⽂件.c⽂件中。如下add.c//函数的定义 int Add(int x, int y) { return xy; }add.h//函数的声明 int Add(int x, int y);test.c#include stdio.h #include add.h int main() { int a 10; int b 20; //函数调⽤ int c Add(a, b); printf(%d\n, c); return 0; }结果7.2作用域和生命周期作⽤域scope是程序设计概念通常来说⼀段程序代码中所⽤到的名字并不总是有效可⽤ 的⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。1. 局部变量的作⽤域是变量所在的局部范围。2. 全局变量的作⽤域是整个⼯程项⽬。⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。1.局部变量的⽣命周期是进⼊作⽤域变量创建⽣命周期开始出作⽤域⽣命周期结束。2.全局变量的⽣命周期是整个程序的⽣命周期。*7.3static 和 externstatic和extern都是C语⾔中的关键字。static 是 静态的 的意思可以⽤来修饰局部变量修饰全局变量修饰函数static修饰局部变量改变了变量的⽣命周期⽣命周期改变的本质是改变了变量的存储类型本来⼀个局部变量是存储在内存的栈区的但是被static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的⽣命周期就和程序的⽣命周期⼀样了只有程序结束变量才销毁内存才回收。但是作⽤域不变的。⼀个全局变量被static修饰使得这个全局变量只能在本源⽂件内使⽤不能在其他源⽂件内使⽤。其实static 修饰函数和 static 修饰全局变量是⼀模⼀样的⼀个函数在整个⼯程都可以使⽤被static修饰后只能在本⽂件内部使⽤其他⽂件⽆法正常的链接使⽤了。extern是⽤来声明外部符号的。如果⼀个全局的符号在A⽂件中定义的在B⽂件中想使⽤就可以使⽤ extern 进⾏声明然后使⽤。8.函数递归和迭代8.1 函数递归什么是函数递归在C语⾔中递归就是函数⾃⼰调⽤⾃⼰。#include stdio.h int main() { printf(hehe\n); main();//main函数中⼜调⽤了main函数 return 0; }上述就是⼀个简单的递归程序只不过上⾯的递归只是为了演⽰递归的基本形式不是为了解决问 题代码最终也会陷⼊死递归导致栈溢出所以递归必须加两个限制条件。1.递归存在限制条件当满⾜这个限制条件的时候递归便不再继续。2.每次递归调⽤之后越来越接近这个限制条件。那我们就可以写出函数Fact求n的阶乘假设Fact(n)就是求n的阶乘那么Fact(n-1)就是求n-1的阶乘。#include stdio.h int Fact(int n) { if(n0) return 1; else return n*Fact(n-1); } int main() { int n 0; scanf(%d, n); int ret Fact(n); printf(%d\n, ret); return 0; }8.2函数迭代递归程序会不断的展开在展开的过程中我们很容易就能发现在递归的过程中会有重复计算⽽且递归层次越深冗余计算就会越多。所以如果不想使⽤递归就得想其他的办法通常就是迭代的⽅式通常就是循环的⽅式迭代效率比递归高很多。int Fact(int n) { int i 0; int ret 1; for(i1; in; i) { ret * i; } return ret; }