C语言学习(10)

一、C语言的函数

第五个: 递归函数(递归思想)

经验:分析问题的时候,发现问题解决步骤后面的步骤跟前面的步骤有数学上的关系,就可以用递归

1+.........+n //以前是用循环实现

特点:自己调用自己,这一类函数就是递归函数

缺点:递归函数如果递归的次数太多,有可能导致栈溢出

练习: 使用递归函数,实现求1到n的和

int fun(n) n+fun(n-1)-->n+n-1+fun(n-2)

#include <stdio.h>
int fun(n) //求1---n的和
{
    //初始条件
    if (n == 1)
        return 1;
    else
        return n + fun(n - 1);
}
int main()
{
    printf("fun(100) is:%d\n", fun(100));
}

 

第六个:函数调用的过程(理论)

计算机的内存分为不同的区域: 栈内存 堆内存

 堆内存(heap):C语言中malloc calloc realloc三个函数申请的内存空间就是堆空间

 特点: 申请需要用到三个函数,使用完毕必须使用free释放掉(如果不释放会引起内存泄漏),不会随着你的程序的退出而自动释放

 内存泄漏:一直申请堆内存,但是都没有释放,导致堆内存越来越少,最后没有可以使用的堆内存

 栈内存(stack): 函数调用进栈出栈,局部变量保存在栈空间

 特点:先进后出

 栈空间是很小,不同的系统有所差别,大致8M左右

 数据段(.data):细分为3个部分

.bss段

.data段

.rodata段

代码段(.text):细分为2个部分

.text段 专门用来存放C语言程序中的代码语句 printf strcpy

.init段 存放的是系统的一段启动代码(帮你找到main函数的入口地址,然后启动执行main函数)

总结: 函数调用就是一个不断地进栈(调用的时候),出栈(函数执行完毕)

 如果代码中要定义比较大的数组,建议你使用堆空间(堆空间比栈空间大很多)

 

练习:

#include <stdio.h> 代码段 .text

int n; //未初始化的全局变量--》 .bss

int m = 99; //初始化的全局变量 --》 .data

void fun(int n) //--》局部变量 栈

{

    printf("world"); //代码段 .text

    if (n < 10) //代码段 .text

        n += 8; //

    return 0; //代码段 .text
}

int main()

{

    char *p = malloc(100); // p是局部变量 100字节堆空间

    char buf[10] = "yueqian"; //局部变量 栈

    fun(88); //栈 常量88 .rodata
}
View Code

 

第七个:内联函数(简单的代码,多次使用的,你就定义成内联)

作用: 解决函数调用时候入栈出栈的资源消耗

如果函数的代码很简单,你需要反复经常使用(建议你定义成内联函数,节约函数调用时候入栈出栈的时间耗费,提高效率)

 语法:inline 返回值 函数名字(参数)

{

 

}

缺点: 由于内联函数的底层原理: 编译器遇到内联函数,不会让它入栈,编译器会把内联函数的源码直接搬到你的main中,类似于这个代码直接写在了主函数里面(增加了主函数占用的内存),耗费了比较多的内存

 

 

 

第八个:函数指针和指针函数(跟数组类似的概念)

函数指针: 该指针指向一个函数

类型 * 指针名

C语言规定:函数名就是这个函数在内存中入口地址(首地址)

函数名就是个指针

char a[4]; char [4]

int fun(int a); //类型为 int (int)

int (*p)(int)=fun;

void fun(); // 类型 void ()

void (*p)()=fun;

作用:跟其它类型的指针一样,通过函数指针去调用函数

#include <stdio.h>
int fun(int a, int b) //int  (int,int)
{
    return a + b;
}

int fun2(int a, int b) //int  (int,int)
{
    return a - b;
}

int fun3(int a, int b) //int  (int,int)
{
    return a * b;
}

int fun4(int a, int b) //int  (int,int)
{
    return a / b;
}

int main()
{
    //定义函数指针指向fun
    int (*p)(int, int) = fun; //函数指针指向同一类型的函数

    //传统方法  通过函数名调用
    printf("结果:%d\n", fun(12, 13));

    //通过函数指针调用
    //疑惑:C语言中只有函数指针  p和*p等价的,其它类型的指针不是这样子
    printf("p(12,13)结果:%d\n", p(12, 13));
    printf("(*p)(12,13)结果:%d\n", (*p)(12, 13));

    //改变指针的指向
    p = fun2;
    printf("结果:%d\n", p(12, 13));

    p = fun3;
    printf("结果:%d\n", p(12, 13));

    p = fun4;
    printf("结果:%d\n", p(12, 13));
}

 

 

指针函数:只要一个函数的返回值类型是个指针,这个函数就是指针函数

char *fun(); //指针函数

char fun(char *buf); //不是指针函数

注意: 指针函数把指针作为返回值,需要注意千万不要返回局部变量的地址(很危险,局部变量会随着函数调用完毕自动释放地址)

解决方法:指针函数返回指针--》一定要使用堆空间的指针

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
    指针函数只有一个点需要重点注意:
        
*/
/* int *fun()  //只要返回值是指针,fun就是指针函数
{
    int m=45;  
    return &m; //warning: function returns address of local variable (局部变量)
} */

int *newfun() //只要返回值是指针,fun就是指针函数
{
    int m = 45;
    int *p = malloc(4); //堆空间,不主动释放
    *p = m;
    return p;
}

/* char *fun1()
{
    char buf[10]="hello"; //局部变量--》栈空间
    return buf; //warning: function returns address of local variable 
} */

char *newfun1()
{
    char buf[10] = "hello"; //局部变量--》栈空间
    char *p = malloc(10);   //堆空间,不主动释放
    strcpy(p, buf);
    return p;
}

int main()
{
    int *p = newfun();
    printf("fun返回的指针中存放的是:%d\n", *p); //正常情况下打印不了45

    char *q = newfun1(); //关键问题是:buf在fun1执行完毕的一瞬间出栈释放掉了
    printf("fun1返回的指针中存放的是:%s\n", q);

    //释放堆空间
    free(p);
    free(q);
}

 

 

第九个:变参函数(原理)和回调函数(理解概念,实现代码)

变参函数:函数的参数个数,类型是不确定的

int printf(const char *format, ...);

语法格式:任何变参函数,第一个参数必须是明确,后面的参数无所谓了

变参函数必须要有 ...(三个小数点,不能多不能少)

变参函数的原理: 由确定的参数(第一个参数),通过指针(va_list va_start()从起始位置开始)遍历,推导不确定的参数(后面的变参)

 1 #include <stdio.h>
 2 #include <stdarg.h> //实现变参函数必须包含此头文件
 3 double sumup(char *format,...)
 4 {
 5     va_list p; //定义一个指向参数栈顶的指针
 6     va_start(p,format); //让该指针指向第一个参数(变参函数第一个参数必须是确定的)
 7     char args[10];
 8     int i,argnum=0;
 9     for(i=0; format[i]!='\0'; i++)
10     {
11         if(format[i]=='#')
12         {
13             args[argnum]=format[++i];
14             argnum++;
15         }
16     }
17     double result=0.0;
18     int argint;
19     double argdouble;
20     for(i=0; i<argnum; i++)
21     {
22         //注意:char和short会被自动提升为int   float会被提升为double
23         switch(args[i])
24         {
25             case 'i':  //整数  char和short都会被提升为int,不能分开写
26             case 'c': 
27             case 's': 
28                 //va_arg返回参数的值
29                 argint=va_arg(p,int); //va_arg是系统定义的宏,让指针p回溯,并告诉p这个参数是int类型的
30                 result+=argint;
31                 break;
32             case 'f':
33                 argdouble=va_arg(p,double); //va_arg是系统定义的宏,让指针p回溯,并告诉p这个参数是int类型的
34                 result+=argdouble;
35                 break;
36             default:
37                 printf("format error\n");
38                 break;
39         }
40     }
41     va_end(p); //释放掉这个指针
42     return result;
43 }
44 int main()
45 {
46     int i=100;
47     char c='w';
48     short s=88;
49     float f=3.14;
50     
51     double answer=sumup("#i#c#s#f",i,c,s,f); //自定义#i表示整数 #c表示字符以此类推
52     printf("answer is:%lf\n",answer);
53     return 0;
54 }

 

回调函数: 我通过调用A函数,A函数又帮我自动去调用B函数,B函数被称为回调函数

关键点:必须要有函数指针作为A的参数,通过这个函数指针指向B

void (*signal(int sig, void (*func)(int)))(int); //不理会它的原型

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 /*
 4     用代码模拟开车遇到红绿灯
 5     
 6 */
 7 //B函数 --》信号灯,回调函数
 8 void led(char *ledcolor)  //void  (char *)
 9 {
10     if(strcmp(ledcolor,"red")==0) //红灯
11         printf("我停车!\n");
12     if(strcmp(ledcolor,"green")==0) //绿灯
13         printf("我踩油门!\n");
14     if(strcmp(ledcolor,"yellow")==0) //黄灯
15         printf("我缓慢行驶,随时停车!\n");    
16 }
17 
18 //A函数 --》开车
19 /*
20     参数:ledcolor ---》红绿灯的颜色
21           p ---》指向B函数的一个指针
22 */
23 void drive(void (*p)(char *),char *ledcolor) //p=chef  name=name
24 {
25     //A函数里面自动调用B函数,根据不同的老师,有不同的选择
26     p(ledcolor); //函数指针调用对应的函数
27 }
28 
29 int main()
30 {    
31     //我直接调用的是A函数
32     drive(led,"red"); //函数名就是指针,当成实参传递
33 }

 

posted @ 2020-05-30 14:17  Geek_Jian  阅读(229)  评论(0编辑  收藏  举报