zoukankan      html  css  js  c++  java
  • C语言的灵魂(函数)

    关于C语言的基础知识:常量、流程控制等等

    函数的定义

      函数:一组一起执行一个任务的语句。

    这里有必要说一下:通常一个较大的程序中会分为若干个模块,一个模块用来实现一个特定的功能,在大多数的编程语言中都有子程序的概念,通常都用子程序来实现模块的功能,在C语言中,子程序的作用就是由函数来完成。在程序设计中通常将一些常用的功能编写成函数,并放在函数库中供别人调用,例如C语言自带一些比较(strcmp)、拷贝(strcopy)等。

      学习C语言有两个知识点是必须要学的:

        1、函数:理解面向过程和面向对象的切入点

        2、指针:帮助我们灵活操作数据,甚至访问硬件资源

    函数的声明

      函数声明会告诉编译器函数名称及如何调用函数,下面是函数声明的原型:

    extern return_type func_name (para_list);
    
    // 1、extern:是C语言的关键字,表明一个函数的声明,可加可不加
    // 2、return_type: 返回值类型
    

      

    函数的参数

    C语言中,参数分为实参和形参

      实参:在调用是传递给函数的参数,实参可以是常量、变量、表达式、函数等。

      形参: 它不是实际存在的变量,所以又称为虚拟变量,在定义函数名和函数体时使用,目的是用来接收调用该函数时传入的参数,形参是只在被调用时才分配内存单元,结束后会被释放回收。

    形参是函数被调用时用于接收实参值的变量,在调用函数时,有3种向函数传递参数的形式:

      1、传值调用:该方法把参数的实际值复制给函数的形参,这种情况修改函数内的形参不会影响实参。

      2、传地址调用:通过指针传递方式,形参为指向实参地址的指针,当对形参做指向操作时,就相当于对实参本身进行操作。

      3、传引用调用:引用“&”就是别名(相当于每个人都有大名和小名,这里区别于go语言,取址符号),所以程序对引用作出改动,其实就是对目标的改动。

    实例:

    #include <stdio.h>
    
    void swap (int a, int b) {  // 传值交换
        int temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    void swap2(int *a, int *b) {// 传地址交换, 
        int temp;
        temp = *a;
        *a = *b;
        *b = temp;
    }
    
    /* 特此说明C语言不支持这种引用传递,C++可以
    void swap5(int &a,int &b) {
        int temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    */
    
    int main() {
        int a=1, b=2;  // 值传递
        swap(a,b);
        printf("a=%d,b=%d
    ",a,b);
    
        int c=10,d=20;
        swap2(&c, &d);
        printf("a=%d,b=%d
    ",c,d); //
    
        // int e=100, f=200;
        // swap5(e,f);
    }
    

      

    函数的调用过程

    首先完成一个简单的C程序:

    #include <stdio.h>
    int plus(int a, int b) {
        int c = a + b;
        return c;
    }
    
    int main() {
        int a =1,b=2;
        int c=0;
        c = plus(a,b);
        printf("%d",c);
        return 0;
    }
    

      使用:gcc -o demo demo.c;来生成可执行文件demo,再通过命令:objdump -d demo > demo.txt,得到该可执行程序的反汇编指令,如下

    生成的汇编文件分析:

    demo:	file format Mach-O 64-bit x86-64
    
    Disassembly of section __TEXT,__text:
    __text:
    100000f20:	55 	pushq	%rbp   // 在linux C中所有的main函数都是被__libc_start_main调用,这里把rbp的地址压栈,每次压栈后,rsp都是指向最新的栈顶
    100000f21:	48 89 e5 	movq	%rsp, %rbp   // rsp要么在内存空间中处于一个段的低地址,要么和rbp重合,这里rbp也指向栈顶,重一起表示函数的栈底地址
    100000f24:	89 7d fc 	movl	%edi, -4(%rbp)
    100000f27:	89 75 f8 	movl	%esi, -8(%rbp)
    100000f2a:	8b 75 fc 	movl	-4(%rbp), %esi
    100000f2d:	03 75 f8 	addl	-8(%rbp), %esi
    100000f30:	89 75 f4 	movl	%esi, -12(%rbp)
    100000f33:	8b 45 f4 	movl	-12(%rbp), %eax
    100000f36:	5d 	popq	%rbp
    100000f37:	c3 	retq
    100000f38:	0f 1f 84 00 00 00 00 00 	nopl	(%rax,%rax)
    100000f40:	55 	pushq	%rbp
    100000f41:	48 89 e5 	movq	%rsp, %rbp
    100000f44:	48 83 ec 20 	subq	$32, %rsp
    100000f48:	c7 45 fc 00 00 00 00 	movl	$0, -4(%rbp)
    100000f4f:	c7 45 f8 01 00 00 00 	movl	$1, -8(%rbp)
    100000f56:	c7 45 f4 02 00 00 00 	movl	$2, -12(%rbp)
    100000f5d:	c7 45 f0 00 00 00 00 	movl	$0, -16(%rbp)
    100000f64:	8b 7d f8 	movl	-8(%rbp), %edi
    100000f67:	8b 75 f4 	movl	-12(%rbp), %esi
    100000f6a:	e8 b1 ff ff ff 	callq	-79 <_plus>
    100000f6f:	89 45 f0 	movl	%eax, -16(%rbp)
    100000f72:	8b 75 f0 	movl	-16(%rbp), %esi
    100000f75:	48 8d 3d 36 00 00 00 	leaq	54(%rip), %rdi
    100000f7c:	b0 00 	movb	$0, %al
    100000f7e:	e8 0d 00 00 00 	callq	13 <dyld_stub_binder+0x100000f90>
    100000f83:	31 f6 	xorl	%esi, %esi
    100000f85:	89 45 ec 	movl	%eax, -20(%rbp)
    100000f88:	89 f0 	movl	%esi, %eax
    100000f8a:	48 83 c4 20 	addq	$32, %rsp
    100000f8e:	5d 	popq	%rbp
    100000f8f:	c3 	retq     // 以上都是完成_libc_start_main函数的汇编
    
    _plus:    // plus函数的汇编部分
    100000f20:	55 	pushq	%rbp  // 将rbp的地址压栈
    100000f21:	48 89 e5 	movq	%rsp, %rbp  // 将rsp和rbp重叠表示整个函数的栈底
    100000f24:	89 7d fc 	movl	%edi, -4(%rbp)
    100000f27:	89 75 f8 	movl	%esi, -8(%rbp)
    100000f2a:	8b 75 fc 	movl	-4(%rbp), %esi
    100000f2d:	03 75 f8 	addl	-8(%rbp), %esi
    100000f30:	89 75 f4 	movl	%esi, -12(%rbp)
    100000f33:	8b 45 f4 	movl	-12(%rbp), %eax
    100000f36:	5d 	popq	%rbp
    100000f37:	c3 	retq
    100000f38:	0f 1f 84 00 00 00 00 00 	nopl	(%rax,%rax)
    
    _main:  // main函数部分汇编
    100000f40:	55 	pushq	%rbp  
    100000f41:	48 89 e5 	movq	%rsp, %rbp  // 初始化压栈和函数栈顶
    100000f44:	48 83 ec 20 	subq	$32, %rsp  // 为main函数开辟空间
    100000f48:	c7 45 fc 00 00 00 00 	movl	$0, -4(%rbp)   // 将变量1压栈(a)
    100000f4f:	c7 45 f8 01 00 00 00 	movl	$1, -8(%rbp)   // 将变量2压栈(b)
    100000f56:	c7 45 f4 02 00 00 00 	movl	$2, -12(%rbp)  // 将变量3压栈(c)
    100000f5d:	c7 45 f0 00 00 00 00 	movl	$0, -16(%rbp)  
    100000f64:	8b 7d f8 	movl	-8(%rbp), %edi   // 设置寄存器edi保存实参
    100000f67:	8b 75 f4 	movl	-12(%rbp), %esi  // 设置寄存器esi保存实参
    100000f6a:	e8 b1 ff ff ff 	callq	-79 <_plus>
    100000f6f:	89 45 f0 	movl	%eax, -16(%rbp)  // 形参入栈
    100000f72:	8b 75 f0 	movl	-16(%rbp), %esi
    100000f75:	48 8d 3d 36 00 00 00 	leaq	54(%rip), %rdi
    100000f7c:	b0 00 	movb	$0, %al
    100000f7e:	e8 0d 00 00 00 	callq	13 <dyld_stub_binder+0x100000f90>   // 调用plus函数
    100000f83:	31 f6 	xorl	%esi, %esi
    100000f85:	89 45 ec 	movl	%eax, -20(%rbp)
    100000f88:	89 f0 	movl	%esi, %eax
    100000f8a:	48 83 c4 20 	addq	$32, %rsp
    100000f8e:	5d 	popq	%rbp
    100000f8f:	c3 	retq
    Disassembly of section __TEXT,__stubs:
    __stubs:
    100000f90:	ff 25 6a 10 00 00 	jmpq	*4202(%rip)
    Disassembly of section __TEXT,__stub_helper:
    __stub_helper:
    100000f98:	4c 8d 1d 69 10 00 00 	leaq	4201(%rip), %r11
    100000f9f:	41 53 	pushq	%r11
    100000fa1:	ff 25 59 00 00 00 	jmpq	*89(%rip)
    100000fa7:	90 	nop
    100000fa8:	68 00 00 00 00 	pushq	$0
    100000fad:	e9 e6 ff ff ff 	jmp	-26 <__stub_helper>
    

      

    函数递归

    我们以最常见的斐波拉契数列为例:

    #include <stdio.h>
    
    long Fibonacci (int n) {
        if (n < 0){
            return -1;
        }else if(0==n){
            return 0;
        }else if(1==n){
            return 1;
        }else{
            return Fibonacci(n-1) + Fibonacci(n-2);
        }
    }
    
    int main() {
        int* n;
        printf("请输入n的值:");
        scanf("%d",n);
        printf("第n项的值为:%ld
    ",Fibonacci(*n));
        return 0;
    }
    

      小编第一次跑这个程序的时候输入了70,发现半天没有响应,打开性能统计cpu占了99%,后来发现在40的时候y都已经上千万了...

    可变参数列表

    当我们无法列出传递函数的所有实参类型和数目时,我们可以使用省略号参数表例如:

    int  printf(const char* format ...);
    // 这就是printf的函数原型
    

      我们之前介绍过参数是以数据结构栈的方式进行存取的,从右到左入栈,如之间将的压栈图,如之前我们调用的额函数(先b参数入栈,再a参数入栈);理论上来说我们只要知道一个参数的地址,那么其他的变量地址都可以进行捕捉到,这里我们讲解哈如果实现可变参数的功能:因为参数是从右到左压栈,当我们不知道参数个数的时候,应该如何开辟栈?通过<stdarg.h>中定义的宏来解决:

    typedef  char* va_list  // 该变量用于存储参数
    void va_start (....)   // 当前参数靠右边的一个参数
    type va_arg (...)  // 获取到参数
    void va_end (...)  // 左后一个参数
    

      代码实例:

    #include <stdio.h>
    #include <stdarg.h>
    
    int average(int n,...) {
        va_list arg; // 返回一个参数列表
        int i = 0;
        int sum = 0;
        va_start(arg,n); // 制定起始值
        for (i=0;i<n;i++){
            sum += va_arg(arg,int); // 制定参数类型返回该参数
        }
    
        va_end(arg); // 结束
        return sum/n;
    }
    
    int main() {
        int a = 10;
        int b = 20;
        int c = 30;
    
        int avg1 = average(2,a,b);
        int avg2 = average(3,a,b,c);
        printf("avg1=%d
    ",avg1);
        printf("avg2=%d
    ",avg2);
    }
    

      

  • 相关阅读:
    数据库连接池实现
    Linux array_vpnc
    MVC小结
    Linux和Windows下 classpath 的差异
    无法删除DLL文件解决方法(转)
    电信工程管理方法
    常用设计思想
    MAX262使用说明
    基于FPGA的FIR滤波器(草稿)
    数字存储示波器(草稿)
  • 原文地址:https://www.cnblogs.com/double-W/p/12775468.html
Copyright © 2011-2022 走看看