函数
7.1基本概念
-
函数:对实现特定功能的代码进行封装后的产物
- 解决代码冗余;
- 增强程序的可读性;
-
一般形式:
返回值类型 函数名称 ( 形式参数列表 ) { 函数体代码; }
-
例子:
#include <stdio.h> // 函数的定义语句 // int :返回值类型。 // - 当函数执行完成之后需要返回的数据类型 // - 如果指定了返回值类型,那么在函数体代码中必须 // 通过return语句将指定返回值类型的返回值返回 // 而且return语句要覆盖函数体中所有结束分支 // - 如果函数不需要返回数据,那么返回值类型写 void // 如果返回值类型为void,则函数体代码中return语句 // 不是必须的。可以通过return语句来结束函数执行 // 此时 return后面直接接分号 // add :函数名称,函数名 // - 以后将通过函数名add来进行该函数的调用 // - C语言不允许函数名重复,C语言不允许进行函数重载 // (int a, int b) :形式参数列表,形参列表 // - 在函数调用时,需要给形参列表中的每一个 // 形参传递一个值。 // - 实参的个数、类型、顺序 要与 形参列表 // 完全一致 // {...} :函数体代码,函数中具体执行业务逻辑的代码 int add(int a, int b){ int sum = a+b; return sum; } int main(void){ // add(11, 22) :这是函数调用语句,调用的函数是add() // - 函数的调用方式: // 函数名 ( 实际参数列表 ) // 11,22 :称之为实际参数列表,简称实参列表 // 最终实参列表会根据顺序依次赋值给形参列表 // 当add(11,22)执行结束后,返回值33会代替原函数调用语句 // add(11,22),出现在函数调用语句位置 // int num = add(11,22); -> int num = 33; int num = add(11, 22); printf("%d\n", num); return 0; }
-
实际开发中形式参数和返回值类型都可以设置为
void
,分别表示不接收任何参数 和 不需要返回返回值 -
int fun(void)
:表示不接收任何参数 -
int fun()
:表示接收任意多参数,只不过不用 -
如果返回值类型设为
void
,函数体中依然允许出现return
语句,只不过此时return
语句仅表示跳出函数执行的作用,而且return
语句后面不需要接返回值,直接接分号。
7.2 函数调用
-
主调函数:函数调用语句所在的函数,称为主调函数
-
被调函数:函数调用语句所要调用的函数,称之为被调函数
-
如果想要在被调函数中修改主调函数中变量的值,需要在传递参数的时候,传主调函数中变量的地址来实现。在被调函数中通过指向主调函数中变量的
指针
来修改主调函数中变量的值 -
函数的前置申明:
函数头 + 分号
,通常在函数定义语句出现在函数调用语句之后的情况下,通过 函数前置声明告诉编译器函数是存在的,函数声明相当于函数的名片#include <stdio.h> // 函数的前置声明 void fun1(int a, int b); void fun2(int * p, int * q); int main(void){ int a = 3, b = 4; fun1(a, b); printf("a=%d b=%d\n", a, b); fun2(&a, &b); printf("a=%d b=%d\n", a, b); return 0; } void fun1(int a, int b){ int t = a; a = b; b = t; } void fun2(int * p, int * q) { int t = *p; *p = *q; *q = t; }
-
C语言中无法直接纯涤数组的,如果希望在被调函数中对数组进行操作,则需要传递数组首元素地址和数组长度,来实现对数组进行操作。
-
C语言中只有一种情况下是不需要传递数组长度的,
如果传递的是字符串,那么不需要传递长度
, 因为字符串存在结束符\0。可以通过判断结束符来判断是否结束。#include <stdio.h> // void printArr(int * arr, int len) void printArr(int arr[], int len) { int i; // sizeof(int *) / sizeof(int) //int len = sizeof(arr)/sizeof(arr[0]); for (i=0; i<len; ++i) printf("%d ", arr[i]); puts(""); } int main(void) { int arr1[6] = {1,2,3,4,5,6}; int arr2[10] = {11,22,33,44,55,66,77,88,99,100}; printArr(arr1, 6); printArr(arr2, 10); return 0; }
7.3 递归
-
递归:是一种编程技巧。函数直接或间接的调用函数本身,称之为
递归
。 -
适用问题类型:如果问题的求解可以通过降低问题规模来实现,而小规模的问题求解方式与原始问题求解方式一致,并且可以通过小规模问题得到原始问题的解,这类问题就可以使用递归来解决。
-
编写步骤:
- 确定递归的边界,就是递归的结束条件
- 进行递归调用,递归公式(状态转移方程)
-
代码例子:
①// fun函数返回n的阶乘 // fun(n) = n * fun(n-1) (n>1) // fun(n) = 1 (n==1) int fun(int n) { // 1. 写边界 if (n==1) return 1; // 2. 自我调用 return n * fun(n-1); }
②
#include <stdio.h> // 斐波那契数列 // 1 1 2 3 5 8 13 21 34 .... // 1 2 3 4 5 6 7 8 9 ... // f(n) = f(n-1) + fun(n-2) (n>2) // f(n) = 1 (n<=2) int fib(int n) { if (n<3) return 1; return fib(n-1) + fib(n-2); } int fib2(int n) { int n1=1, n2=2, i; if (n<3) return 1; for (i=3; i<=n; ++i) { int t = n1 + n2; n2 = n1; n1 = t; } return n1; } int main(void){ printf("%d\n", fib(50)); return 0; }
7.4 生存周期 和 作用域
7.4.1 生存周期
-
动态生存周期:变量随着程序执行而创建,随着程序执行而释放
-
静态生存周期:程序一运行,变量就创建了,程序运行结束,变量才会释放
-
普通局部变量都是动态生存周期的,如果想让它的生存周期变成静态生存周期,只需要在变量定义语句前面加上关键字:
static
-
static关键字修饰的局部变量为静态生存周期,程序一运行变量就存在了,程序运行结束时,才会释放
-
静态生存周期的变量,如果没有进行初始化,会默认初始化为0 。动态生存周期变量,如果没有初始值,值为随机值
-
代码例子:
①#include <stdio.h> void fun() { static int a; a += 1; printf("%d\n", a); // 1 2 3 } int main(void){ fun(); fun(); fun(); return 0; }
②
#include <stdio.h> int fun(int n) { static int a; int i; for (i=0; i<n; i++) a += i; return a; } int main(void){ int a = 3; a = fun(a); a = fun(fun(a)); printf("%d\n", a); // 21 return 0; }
7.4.2 作用域
-
局部作用域:一个复合语句内(一个代码块内),当前代码块范围内可以访问
-
全局作用域:当前源文件范围内都可以访问
-
名字查找:在使用某个变量时,优先在当前作用域寻找,如果不在则逐层往更高一级作用域寻找;如果在全局作用域依然没有找到,则编译器就会报变量为定义错误。
-
代码例子:
#include <stdio.h> int a = 10; int main(void) { int a = 11; if (a){ int a = 12; if (a) { int a = 13; printf("1:%d\n", a); // 13 } printf("2:%d\n", a); // 12 } printf("3:%d\n", a); // 11 return 0; }
-
全局变量:定义在函数外面的变量(全局作用域),可见范围为全局范围,称之为全局变量
- 全局变量在程序一运行时就创建了,程序结束时才会释放
- 全局变量生存周期为静态生存周期
- 全局变量默认初始化为0
7.5 动态内存分配
-
内存的几个区域
- 栈区:存放动态生存周期的变量
- 堆区:交给程序员手动分配内存使用
- 静态全局区:存放静态生存周期的变量
- 常量只读区:存放字符串字面值
- 代码区:存放要执行的二进制机器代码
-
堆区的内存需要程序员手动自己来分配和回收
-
malloc()
:根据实习需求在堆区内存中分配一块指定大小的内存区域,将所需内存大小(字节数)作为参数传递给malloc()
函数,它会分配好指定大小的堆区内存,并将内存首地址返回。//申请失败返回NULL
- 返回一个通用指针(
void *
),通常会将通用指针显示类型转换(强制类型转换)为普通指针 - 并不会对内存进行初始化
- 返回一个通用指针(
-
calloc()
:作用跟malloc()
类似,区别有两个:-
calloc()
函数会将申请到的内存每个字节初始化为0 -
calloc()
函数有两个参数- 一个参数表示每个元素多大
- 一个参数表示共有几个参数
(int * )malloc (sizeof (int ) * 10 )
;(int * )calloc (sizeof( int ) , 10 )
;
-
-
free()
:当之前申请的内存不再使用时,可以通过free()
函数来进行释放,只需要将之前申请内存时返回的地址传递给free()
函数即可。 -
代码例子:
#include <stdio.h> #include <stdlib.h> // 输入一个n,接下来输入n个正整数,请逆序打印 // 5 // 11 33 22 66 55 int main(void) { int n, i; int * arr; scanf("%d", &n); arr = (int *)malloc(sizeof(int) * n); for (i=0; i<n; ++i) scanf("%d", arr+i); // &arr[i] &*(arr+i) puts("-------------"); for (i=n-1; i>=0; --i) printf("%d ", arr[i]); // *(arr+i) free(arr); return 0; }
7.6 typedef
-
typedef
:给一个已知的类型起别名- 简化已有类型名称
- 增强程序的可读性
-
代码例子:
#include <stdio.h> typedef long long LL; typedef int SEX; int main(void) { long long n = 10; LL m = 10; SEX zhangsan = 1; return 0; }