C语言 | chap7 函数

函数

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;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑眼圈子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值