一.函数
上一专栏我们学过函数,我们来补充一点知识。
数学中,f(x) = 2*x+1、f(x, y) = x + y 是函数...
在计算机中,函数是一个大型程序中的某部分代码,由一个或多个语句块组成;
它负责完成某项特定任务,并且相较于其他代码,具备相对的独立性;
注意事项:
1. 函数设计应追求“高内聚低耦合”;
(即:函数体内部实现修改了,尽量不要对外部产生影响,否则:代码不方便维护)
2. 设计函数时,尽量做到谁申请的资源就由谁来释放;
3. 关于return,一个函数只能返回一个结果;
4. 不同的函数术语不同的作用域,所以不同的函数中定义相同的名字并不会造成冲突;
5. 函数可以嵌套调用,但是不能嵌套定义,函数里不可以定义函数;
7. 函数的定义可以放在任意位置,但是函数的声明必须放在函数的使用之前;
建议:
1. 函数参数不宜过多,参数越少越好;
2. 少用全局变量,全局变量每个方法都可以访问,很难保证数据的正确性和安全性;
- 主函数
注意事项
1. C语言规定,在一个源程序中,main函数的位置可任意;
2. 如果在主函数之前调用了那些函数,必须在main函数前对其所调用函数进行声明,
或包含其被调用函数的头文件;
2.库函数
为什么会有库函数?
“库函数虽然不是业务性的代码,但在开发过程中每个程序员都可能用得到,
为了支持可移植性和提高程序的效率,所以C语言基础库中提供了库函数,方便程序员进行软件开发”
注意事项:库函数的使用必须要包含对应的头文件;
3.自定义函数
何为自定义函数?
“顾名思义,全部由自己设计,赋予程序员很大的发挥空间”
自定义函数和其他函数一样,有函数名、返回值类型和函数参数;
1. ret_type 为返回类型;
2. func_name 为函数名;
3. paral 为函数参数;
4.函数的参数
实际参数(实参)
1. 真实传给函数的参数叫实参(实参可以是常量、变量、表达式、函数等);
2. 无论实参是何种类型的量,进行函数调用时,必须有确定的值,以便把这些值传送给形参;
形式参数(形参)
1. 形参实例化后相当于实参的一份临时拷贝,修改形参不会改变实参;
2. 形式参数只有在函数被调用的过程中才实例化;
3. 形式参数在函数调用完后自动销毁,只在函数中有效;
注意事项:
1. 形参和实参可以同名;
2. 函数的形参一般都是通过参数压栈的方式传递的;
3. “形参很懒”:形参在调用的时才实例化,才会开辟内存空间;
5.函数的调用
传值调用
1. 传值调用时,形参是实参的一份临时拷贝;
2. 函数的形参和实参分别占用不同内存块,对形参的修改不会影响实参;
3. 形参和实参使用的不是同一个内存地址;
传址调用
1. 传址调用时可通过形参操作实参;
2. 传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式;
3. 使函数内部可以直接操作函数外部的变量(让函数内外的变量建立起真正的联系);
相关知识:
链式访问:
把一个函数的返回值作为另外一个函数的参数。
函数可以嵌套调用,但是不能嵌套定义。
函数声明:
1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,
函数声明决定不了。
2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
3. 函数的声明一般要放在头文件中的。
函数定义:
函数的定义是指函数的具体实现,交待函数的功能实现。
test.h的内容
放置函数的声明
test.c的内容
放置函数的实现
这种分文件的书写形式,才是程序员用的。据说其中一个原因是为了不给买家看到源码/抄袭。
二.函数的递归
递归的定义
程序调用自身称为递归(recursion)
1. 递归策略只需要少量的程序就可以描述解题过程所需要的多次重复计算,大大减少代码量;
2. 递归的主要思考方式在于:把大事化小;
注意事项:
1. 存在跳出条件,每次递归都要逼近跳出条件;
2. 递归层次不能太深,避免堆栈溢出;
什么是递归?
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件
三 . 编程练习:
1.函数判断素数
实现一个函数,判断一个数是不是素数。
利用上面实现的函数打印100到200之间的素数。
#include<stdio.h> #include<math.h> int is_prime(int n) { int i = 0; for (i = 2; i <= sqrt(n); i++) { if (0 == n % i) { return 0; } } return 1; } int main() { for (int i = 100;i <= 200;i++) { if (is_prime(i)) printf("%d\n", i); } return 0; }
2. 实现函数判断year是不是润年。
#include<stdio.h> 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; scanf("%d", &year); if (is_leap_year(year)) printf("%d是闰年\n", year); else printf("%d不是闰年\n", year); return 0; }
3. 实现一个函数来交换两个整数的内容。(涉及指针)
#include<stdio.h> void swap(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } int main() { int x = 0, y = 0; scanf("%d%d", &x, &y); printf("交换前:%d %d\n", x, y); swap(&x, &y); printf("交换后:%d %d\n", x, y); return 0; }
4.乘法口诀表
实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定
如:输入9,输出9*9口诀表,输出12,输出12*12的乘法口诀表。
#include<stdio.h> void print_multiplication_table(int n) { for (int i = 1;i <= n;i++) { for (int j = 1;j <= i;j++) { printf("%d*%d=%2d ", j, i, j * i); } printf("\n"); } } int main() { int n = 0; scanf("%d", &n); print_multiplication_table(n); return 0; }
5.递归打印无符号整形
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4
#include<stdio.h> void print(unsigned int n) { if (n > 9) { print(n / 10); } printf("%u ", n % 10); } int main() { unsigned int n = 0; scanf("%u", &n); print(n); return 0; }
6.非递归和递归模拟实现strlen(strlen求长度不包括\0)
编写一个函数求字符串的长度。和编写另一个函数不允许创建临时变量,求字符串的长度。
#include<stdio.h> #include<string.h> int my_strlen1(const char* str) { int k = 0; while (*str != '\0') { k++; str++; } return k; } int my_strlen2(const char* str) { if (*str != '\0') return 1 + my_strlen2(str + 1); else return 0; } //左下右上转圈地看 //my(hello) //my(hello) 1+my(ello) =5 //my(ello) 1+my(llo) =4 //my(llo) 1+my(lo) =3 //my(lo) 1+my(o) =2 //my(o) return 1+my(\0) =1 //my(\0):return 0; int main() { char arr[] = "hello"; printf("%d\n", my_strlen1(arr)); printf("%d\n", my_strlen2(arr)); return 0; }
7.迭代和递归求n的阶乘(循环是一种迭代)
求n的阶乘。(不考虑溢出)
#include<stdio.h> int fac1(int n) { int r = 1; for (int i = 1;i <= n;i++) { r *= i; } return r; } int fac2(int n) { if (n == 0) return 1; else return n*fac2(n - 1); //n=4时 //fac2(4):return 4*fac(3); 4*2*1 //fac2(3):return 3*fac(2); 3*2*1 //fac2(2):return 2*fac(1); 2*1 //fac1(0):return 1; } int main() { int n = 0; scanf("%d", &n); printf("%d的阶乘为:%d\n", n, fac1(n)); printf("%d的阶乘为:%d\n", n, fac2(n)); return 0; }
以下问题用递归可能不是最好的方法,但只是锻炼写递归的能力
8.迭代和递归求前n个斐波那契数。(不考虑溢出)
此题用递归如果n很大,比如50,计算器要算很久,因为要调用很多次
可以试试两个函数在n=45时的速度比较,很有趣。
#include<stdio.h> //求得数据过大时可以把int换成long long int fib1(int n) { int r = 1, arr[95] = { 0,1,1 };//定义数组长度为95(比90稍多一点) for (int i = 3;i <= n;i++) { arr[i] = arr[i - 1] + arr[i - 2]; } return arr[n]; } int fib2(int n) { if (n == 1 || n == 2) return 1; else return fib2(n - 1) + fib2(n - 2); } int main() { int n = 0; scanf("%d", &n); for (int i = 1;i <= n;i++) { printf("第%d个斐波那切数为%d\n",i, fib1(i)); } return 0; }
9.递归实现字符串逆序
【题目内容】
编写一个函数 reverse_string(char * string)(递归实现)
实现:将参数字符串中的字符反向排列,不是逆序打印。
要求:不能使用C函数库中的字符串操作函数。
比如:
char arr[] = "abcdef";
逆序之后数组的内容变成:fedcba
#include<stdio.h> #include<string.h> int my_strlen2(const char* str) { if (*str != '\0') return 1 + my_strlen2(str + 1); else return 0; } void reverse_string(char* str,int left, int right) { if (left < right) { int tmp = str[left]; str[left] = str[right]; str[right] = tmp; reverse_string(str, left + 1, right - 1); } } int main() { char arr[20] = { 0 }; scanf("%s", arr); int left = 0, right = my_strlen2(arr) - 1; printf("逆序前:%s\n", arr); reverse_string(arr, left, right); printf("逆序后:%s\n", arr); return 0; }
10 .递归计算一个数的每位之和
【题目内容】
写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19
输入:1729,输出:19
#include<stdio.h> int digit_sum(int n) { if (n > 9) return n % 10 + digit_sum(n / 10); else return n; } //d(1234):4+d(123) //d(123):3+d(12) //d(12):2+d(1) //d(1):1 int main() { unsigned int n = 0; scanf("%d", &n); printf("%d", digit_sum(n)); return 0; }
11 .递归实现n的k次方
#include<stdio.h> double my_pow(int n, int k) { if (k == 0) return 1; else if (k > 0) return n * my_pow(n, k - 1); else return 1.0 / (my_pow(n, -k)); //p(3,4):return 3*p(3,3); 81 //p(3,3):return 3*p(3,2); 27 //p(3,2):return 3*p(3,1); 9 //p(3,1):return 3*p(3,0); 3 //p(3,0):return 1; } int main() { int n = 0, k = 0; scanf("%d%d", &n, &k); printf("%lf\n", my_pow(n, k)); return 0; }