• 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
    ", 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
    ", fun(12, 13));
    
        //通过函数指针调用
        //疑惑:C语言中只有函数指针  p和*p等价的,其它类型的指针不是这样子
        printf("p(12,13)结果:%d
    ", p(12, 13));
        printf("(*p)(12,13)结果:%d
    ", (*p)(12, 13));
    
        //改变指针的指向
        p = fun2;
        printf("结果:%d
    ", p(12, 13));
    
        p = fun3;
        printf("结果:%d
    ", p(12, 13));
    
        p = fun4;
        printf("结果:%d
    ", 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
    ", *p); //正常情况下打印不了45
    
        char *q = newfun1(); //关键问题是:buf在fun1执行完毕的一瞬间出栈释放掉了
        printf("fun1返回的指针中存放的是:%s
    ", 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]!=''; 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
    ");
    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
    ",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("我停车!
    ");
    12     if(strcmp(ledcolor,"green")==0) //绿灯
    13         printf("我踩油门!
    ");
    14     if(strcmp(ledcolor,"yellow")==0) //黄灯
    15         printf("我缓慢行驶,随时停车!
    ");    
    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 }
  • 相关阅读:
    第五节: EF高级属性(一) 之 本地缓存、立即加载、延迟加载(不含导航属性)
    第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法
    第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery )
    Android ListView常见配置说明
    如何配置IIS使其支持APK文件的下载
    Android scrollview和GridView混合使用
    WCF配置后支持通过URL进行http方式调用
    转战博客园
    Android Intent参数传递
    Android 使用SQLite
  • 原文地址:https://www.cnblogs.com/xiaojian98/p/12482312.html
Copyright © 2020-2023  润新知