主要掌握函数的基本使用和递归
[toc]
函数是什么
==子程序==,负责完成某项特定任务,具备相对独立性。
分类
库函数
方便调用频繁大量使用的功能,推动标准化、模块化
printf
打印
strcpy
字符串拷贝
pow
计算n的k次方
MSDN(Microsoft Developer Network)
c语言文档网站cpp
常用的库函数有
- IO函数
- printf scanf getchar putchar
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间日期函数
- 数学函数
- 其他库函数
以下例子:
strcpy
拷贝字符串
1 2
| char * strcpy (char * destination, const char * source) 返回类型 (参数 )
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> #include <string.h>
int main() { char arr1[20] = { 0 }; char arr2[] = "Hello"; strcpy(arr1, arr2); printf("%s", arr1); return 0; }
|
memset
内存设置memory set
1
| void * memset (void * ptr, int value, size_t num)
|
ptr指向的所指向的内存前num的内容全部设置成value值
1 2 3 4 5 6 7
| int main() { char arr[] = "Hello bit"; memset(arr, 'x', 5); printf("%s\n", arr); return 0; }
|
效果是 把arr前5个字符设置成x
自定义函数
一些案例
基本组成形式
1 2 3 4 5 6 7
| ret_type fun_name(paral, *) { statement; } ret_type 返回类型 fun_name 函数名 paral 函数参数
|
获取较大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int get_math(int x, int y) { int z = 0; if (x > y) { z = x; } else { z = y; } return z; }
int main() { int a = 10; int b = 20;
int max = get_max(a, b); printf("max= %d\n", max); return 0; }
|
交换2个整型变量的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void Swap1(int x, int y) { int z = 0; z = x; x = y; y = z; }
void Swap2(int* pa, int* pb) { int z = 0; z = *pa; *pa = *pb; *pb = z; }
int main() { int a = 10; int b = 20;
printf("交换前:a=%d b=%d\n", a, b); Swap1(&a, &b); printf("交换后:a=%d b=%d\n", a, b); return 0; }
|
函数的参数
实参
真实传递给函数的参数,可以是常量、变量、表达式。函数调用时,必须要有确定的值,以便把这些值传递给形参
例如调用函数时int max = get_max(2 + 5, get_max(4, 7))
传递给函数的,是确定的、真实存在的值
形参
函数名后括号内的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元)所以叫形式参数。形式参数在函数调用完以后就自动销毁,因此形参只在函数中有效(生命周期)。
例如定义一个函数,函数名后面的就是形参,下面int x
就是一个形参
1 2 3 4 5 6 7
| void Swap1(int x, int y) { int z = 0; z = x; x = y; y = z; }
|
形参实例化后,是实参的一份临时拷贝
函数的调用
传值调用
实参形参占有不同的内存块,对形参的修改不会影响实参,例如上面的Swap1
传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。例如上面的
Swap2
- 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
练习
- 打印100~200之间的素数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| int is_prime(int n) { int j = 0; for (j = 2; j < n; j++) { if (n % j == 0) { return 0; } } return 1; } int main() { int i = 0; int count = 0; for (i = 100; i <= 200; i++) { if (is_prime(i) == 1) { count++; printf("%d ", i); } } printf("%d ", count); return 0; }
|
注意到这个地方把判断和打印分成两个部分来完成。
实际写代码时,这是体现了模块化的思想。
- 写一个函数,判断是不是闰年
写法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int is_leap_year(int x) { if ((x % 4 == 0) && (x % 100 != 0) || (x % 400 == 0)) { return 1; } else { return 0; } } int main() { int y = 0; for (y = 1000; y < 2000; y++) { if (is_leap_year(y) == 1) { printf("%d ", y); } } return 0; }
|
判断闰年部分还可以这样写
1 2 3 4
| int is_leap_year(int x) { return ((n % 4 && n % 100 != 0) || (n % 400 == 0)); }
|
- 写一个函数实现整形有序数组的二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| int binary_search(int a[], int k, int s) { int left = 0; int right = s - 1; while (left <= right) { int mid = (left + right) / 2; if (a[mid] > k) { right = mid - 1; } else if (a[mid]< k) { left = mid + 1; } else { return mid; } } return -1; } int main() { int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int key = 7; int sz = sizeof(arr) / sizeof(arr[0]); int ret = binary_search(arr, key, sz); if (ret == -1) { printf("找不到\n"); } else { printf("找到了,下标是:%d\n", ret); } return 0; }
|
- 写一个函数,每调用一次这个函数,就会将num的值增加1
思路:传值调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| void Add(int*p) { (*p)++; }
int main() { int num = 0; printf("%d", num); Add(&num); printf("%d", num); return 0; }
|
函数的嵌套调用和链式访问
嵌套调用
函数不能嵌套定义,但函数和函数之间是可以嵌套调用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int test3() { printf("卢本伟牛逼"); }
int test1() { test3(); }
int main() { return 0; }
|
链式访问
一个函数的返回值作为另一个函数的参数
1 2 3 4 5 6 7 8 9 10 11 12
| #include <string.h> int main() { int len = strlen("abc"); printf("%d\n", len); printf("%d\n", strlen("abc")); char arr1[20] = 0; char arr2[] = "bit"; printf("%s\n", strcpy(arr1, arr2)); printf("%d", printf("%d", printf("%d", 43))); return 0; }
|
要注意链式调用是从上到下、从左到右,所以假如调用一个自定义函数,有两种方式:
- 把自定义函数放在调用语句所在的函数之前,相当于先定义
- 在调用语句前先进行声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int main() { int a = 1; int b = 2; int Add(int x, int y); Add(a, b); return 0; }
int Add(int x, int y); { return x+y; }
|
函数的声明和定义
函数声明
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但具体是不是存在无关紧要
- 函数的声明一般出现在函数的使用之前。要满足先声明再使用
- 函数的声明一般要放在头文件中的
函数定义
函数的具体实现,交代函数的功能实现
可以认为定义是一种强有力的声明
函数递归
函数调用梓森的编程技巧称为递归。递归作为一种算法在程序语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化成一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可以描述出解题过程需要的多次重复计算,大大减少了程序的代码量。递归的主要思考方式在于:把大事化小
递归有两个必要限制条件
- 存在限制条件,当满足这个限制条件时候,递归便不再继续
- 每次递归调用之后越来越接近这个限制条件
- 接受一个整型值(无符号),按照顺序打印它的每一位,例如:输入1234,输出1 2 3 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void print(unsigned int n) { if (n > 9) { print(n / 10); } printf("%d ", n % 10); }
int main() { unsigned int num = 0; scanf("%u", &num);
print(num);
return 0; }
|
原理是:
进行四次 “递”(向下传递),分别得到4321,最后到1时跳出if,开始 “归” 返回去各层打印
递归过程一直入栈直到递归结束才出栈,因此可能会遇到栈溢出的问题,因此在此处补充关于栈区的知识
区域 |
放的东西 |
栈区 |
局部变量、函数形参 |
堆区 |
动态内存分配的malloc/free/calloc/realloc |
静态区 |
全局变量、静态变量 |
编写递归时要注意:
- 不能死递归,要有跳出条件,每次递归逼近跳出条件
- 递归层次不能太深