第3课 - 函数的升级(内联函数,默认参数,占位参数) - 上

第3课 - 函数的升级 - 上

    一.内联函数的使用

        1.C++推荐使用内联函数替代宏代码片段

        2.C++使用关键字inline关键字声明内联函数

            注意:inline关键字必须放在函数定义的地方,不能放在函数声明的地方,否则编译器会直接忽略内联请求

        inline int func (int a, int b)

        {

            return (a < b ? a : b );

        }

        3.内联函数的具体说明

            3.1 C++可以将一个函数进行内联编译

            3.2 被C++编译器内联编译的函数叫做内联函数

            3.3 内联函数在最终生成的代码中是没有定义的

            3.4 C++编译器直接将函数体插入函数调用的地方

                (例如当函数调用上面的func函数的时候会直接将该函数的函数体 (a < b ? a : b)插入调用的地方)

                调用func (1,2)就相当于 1 < 2 ? 1 : 2

            3.5 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)

            

                注意:C++编译器不一定会准许函数的内联请求,如果不允许 !

            

           3.6 内联函数是一种特殊的函数,具有普通函数的特征

        4.内联函数与宏代码片段的区别

            4.1 内联函数有编译器处理,直接将编译后的函数体插入到调用的地方,宏代码片段有预处理器处理,进行简单的文本替换,没有任何编译过程

            4.2 效率上内联函数与宏代码片段一样,但是内联函数更安全。 具体见Source Example 4.1:

              

         
                    #include <iostream>

                    #define FUNC(a,b) ((a) < (b) ? (a) : (b))

                    inline int func (int a, int b)
                    {
                        return (a < b ? a : b );
                    }

                    int main(int argc, char** argv) {
                        
                        int a = 1;
                        int b = 3;
                        //int c = func(++a,b);
                        int c =FUNC(++a,b);
                        
                        printf ("a = %d\n", a);
                        printf ("b = %d\n", b);
                        printf ("c = %d\n", c);
                        
                        return 0;
                    }

 

                当使用内联函数时,相当于 2 < 3 ? 2 : 3

                输出结果a=2,b=3,c=2,正常。

                当使用宏定义时,相当于 ++a < b ? ++a : b,即 3 < 3 ? 3 : 3

                输出结果变为a=3,b=3,c=3,a自增了两次,并不是我们所想要的结果,这就是宏定义的副作用。

            4.3 关于内联函数是否会被内联编译的具体说明

                a.C++编译器能够进行编译优化,因此一些函数即使没有inline声明也可能被编译器内联编译

                b.一些现代C++编译器提供了扩展语法,能够对函数进行强制内联,例如g++中的__attribute__((always_inline))属性

            4.4 内联函数的深度示例

              

         Source Example 4.4:
                    #include <iostream>

                    /* run this program using the console pauser or add your own getch, system("pause") or input loop */

                    inline int f_inline(int a, int b);
                    int g_no_inline(int a, int b);

                    int main(int argc, char** argv) {
                        
                        int r1 = f_inline(1,2);
                        int r2 = g_no_inline(1,2);
                                
                        return 0;
                    }

                    int f_inline(int a, int b)
                    {
                        return a < b ? a : b;
                    }

                    int g_no_inline(int a, int b)
                    {
                        return a < b ? a : b;
                    }

                    将该文件通过g++编译器编译,得到汇编文件。

                    在main函数的汇编代码中

                

            main:
                    pushq    %rbp
                    .seh_pushreg    %rbp
                    movq    %rsp, %rbp
                    subq    $48, %rsp
                    .seh_stackalloc    48
                    .seh_setframe    %rbp, 48
                    .seh_endprologue
                    movl    %ecx, 16(%rbp)        
                    movq    %rdx, 24(%rbp)
                    call    __main
                    movl    $2, %edx            /* 将2压入栈中 */
                    movl    $1, %ecx            /* 将1压入栈中 */
                    call    _Z8f_inlineii        /* 调用f_inline函数 */
                    movl    %eax, -4(%rbp)
                    movl    $2, %edx            /* 将2压入栈中 */
                    movl    $1, %ecx            /* 将1压入栈中 */
                    call    _Z11g_no_inlineii    /* 调用g_no_inline函数 */
                    movl    %eax, -8(%rbp)
                    movl    $0, %eax
                    addq    $48, %rsp
                    popq    %rbp

                    从上面的汇编代码就可以发现,f_inline函数并没有被内联编译,因为内联函数没有压栈,跳转,返回等开销

 

                    修改cpp文件,声明内联函数修改为

                    inline int f_inline(int a, int b)__attribute__((always_inline));即附加一个强制内联编译的属性

                    同样使用g++编译器进行编译得到汇编文件main函数如下

                    main:
                    pushq    %rbp
                    .seh_pushreg    %rbp
                    movq    %rsp, %rbp
                    subq    $48, %rsp
                    .seh_stackalloc    48
                    .seh_setframe    %rbp, 48
                    .seh_endprologue
                    movl    %ecx, 16(%rbp)
                    movq    %rdx, 24(%rbp)
                    call    __main                /* f_inline函数体直接被插入到函数调用的地方 */
                    movl    $1, -12(%rbp)        /* 没有压栈,跳转,返回等开销 */    
                    movl    $2, -16(%rbp)
                    movl    -12(%rbp), %eax
                    cmpl    -16(%rbp), %eax        
                    jge    .L2
                    movl    -12(%rbp), %eax
                    jmp    .L3
                .L2:
                    movl    -16(%rbp), %eax
                .L3:
                    movl    %eax, -4(%rbp)
                    movl    $2, %edx            /* 将2压入栈中 */
                    movl    $1, %ecx            /* 将1压入栈中 */
                    call    _Z11g_no_inlineii    /* 调用g_no_inline函数 */
                    movl    %eax, -8(%rbp)
                    movl    $0, %eax
                    addq    $48, %rsp
                    popq    %rbp
                    ret

                   
                    阅读代码发现并没有调用f_inline函数,很明显f_inline函数被内联编译了。
        

      5.C++中内联函数的实现机制

            当编译器编译到内联函数时,首先进行判断是否符合内联函数的要求,如果符合就会将内联函数放到符号表里面去。

                                                                                                    (符号表就是C++在编译的过程中创建的,里面存放了一些名字,是编译器自己用的东西,不会进入最终生成的可执行程序)

            当使用到内联函数,编译器首先会对函数的参数进行检查(预处理器不会这么做),如果参数正确,会在符号表里面将函数的函数体拿出来插入调用的地方。

            
        

      6.C++中内联编译的限制

            6.1 不能存在任何形式的循环语句

            6.2 过多的条件判断语句(例如switch)

            6.3 函数体不能过于庞大(函数体最好不要超过5句)

            6.4 不能对函数进行取址操作(一旦对函数进行取址操作编译器就会拒绝对函数的内联编译)

            6.5 函数的内联声明必须在调用语句之前,否则C++也可能会拒绝函数的内联编译

            
            注意:编译器对内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时的开销(压栈,跳转,返回)。
                  因此,当函数的函数体执行开销远大于压栈,跳转,和返回所用的开销时,那么内联将没有意义。(编译器有可能会智能的判断是否有意义进行内联编译,从而决定是否进行内联编译)
            
    

    二.函数的默认参数

        1. 函数默认参数的使用

            C++可以在函数声明时为函数提供一个默认值,但函数调用时没有指定这个参数的值,编译器会自动用默认值代替。

              

             Source Example 1:
                             
              #include <iostream>

                /* run this program using the console pauser or add your own getch, system("pause") or input loop */

                int mul(int x = 0);

                int main(int argc, char** argv) {
                    
                    printf ("mul(2) = %d\n", mul(2));
                    printf ("mul(-2) = %d\n", mul(-2));
                    printf ("mul() = %d\n", mul());
                            
                    return 0;
                }

                int mul(int x)
                {
                    return x * x;
                }

            输出结果如下:

            当没有指定函数的参数时就会使用函数声明时指定的默认参数。

        2. 函数默认参数的规则

            2.1 只有参数列表后面部分的参数才可以提供默认参数值

                一旦一个函数中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数值。

                例如f(int a, int b, int c)函数

                    一旦b开始使用默认参数值,那么c就必须使用默认参数值。

             

           Source Example 2.1:
                    #include <iostream>

                    /* run this program using the console pauser or add your own getch, system("pause") or input loop */

                    int add(int a, int b = 0, int c = 0)
                    {
                        return a+b+c;
                    }

                    int main(int argc, char** argv) {
                        
                        printf ("add(2) = %d\n", add(2));
                        printf ("add(1,2) = %d\n", add(1,2));
                        printf ("add(1,2,3) = %d\n", add(1,2,3));
                                
                        return 0;
                    }

                    输出结果如下

                    一旦参数b使用了默认参数值,参数c就必须也使用默认参数值。
                    例如 调用add(2),参数b,c都使用了默认参数值,是合法的。
                         假设想要a=2,b使用默认参数值,c=1,调用的时候add(2,,1);很明显是不合法的。
                         因此 一旦有一个参数开始使用默认参数值,那么这个参数后面的参数都必须使用默认参数值。
                

    三.函数的占位参数

        1.占位参数定义

            占位参数只有参数类型声明,而没有参数名的声明
          

         Source Example 3.1:
            
            #include <iostream>
            /* run this program using the console pauser or add your own getch, system("pause") or input loop */

            int func (int a, int b, int)
            {
                return a + b;
            }

            int main(int argc, char** argv) {
                
                printf ("func(1,2,3) = %d\n", func(1,2,3));

                return 0;
            }            

            输出结果为3

            在调用func函数的时候必须提供三个参数,但是占位参数不会被使用。    

        

        2.占位参数的使用

            占位参数可以与默认参数结合起来使用
          

          Source Example 3.2.1:
            #include <iostream>
            /* run this program using the console pauser or add your own getch, system("pause") or input loop */

            int func (int a, int b, int = 0)
            {
                return a + b;
            }

            int main(int argc, char** argv) {
                
                printf ("func(1,2,3) = %d\n", func(1,2));

                return 0;
            }

 

 

            这样在调用的时候使用两个参数就合法了。

 

 

            这么做的意义在于为以后程序的扩展留下线索,同时也是为了兼容C语言程序中可能出现的不规范的写法,具体见下例。

 

           Source Example 3.2.2:
                #include <stdio.h>
                /* run this program using the console pauser or add your own getch, system("pause") or input loop */

                int func ()
                {
                    return 1;
                }

                int main(int argc, char** argv) {
                    
                    printf ("func()=%d\n", func());
                    printf ("func(1)=%d\n", func(1));
                    
                    return 0;
                }

            这个C程序在C语言中是可以编译的过的,func没有指定形参,表示可以接受任意类型,个数的参数

            但是放到C++中进行编译则编译不过。

            
            一般直接在函数定义的地方加上占位参数,并且加上一个默认值就可以编译通过了int func (int = 0),避免修改很多次。
            

    四.小结

        4.1 C++中可以通过inline声明内联函数

        4.2 内联函数在编译时直接将函数体插入函数调用的地方

        4.3 inline只是一种请求,编译器不一定允许这种请求

        4.4 内联函数省去了普通函数调用时的压栈,跳转和返回的开销

        4.5 C++中声明函数的时候可以指定参数的默认值

        4.6 C++可以声明占位符参数,占位符参数一般用于程序扩展和对C代码的兼容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值