c++之函数与函数重载

2022/3/20 11:27:33

本文主要是介绍c++之函数与函数重载,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、函数默认参数


1.1 函数默认参数简介

在C++中,定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值(没有对应的实参),那么就使用这个默认的值。

比如:我们要实现一个 分页 插件,在使用时可以由用户指定当前是第几页以及每页显示多少条数据。
如果用户没有指定每页显示多少条默认1页显示10条,打印出每一页显示的信息的下标。

#include<iostream>

using namespace std;

void pagination(int page,int pagesize=10)
{
    int i;
    for(i=0;i<pagesize;i++)
    {
        cout << (page-1)*10 + i << " ";
     }
     cout << endl;
}

int main()
{
    pagination(1);
    pagination(2,15);
    return 0;
}


注意:C++规定,默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。


因为实参和形参的传值是从左到右依次匹配的,默认参数的连续性是保证正确传参的前提。

1.2 指定函数默认参数的位置


C++ 规定,在给定的作用域中只能指定一次默认参数。


例如下面代码:

#include <iostream>
using namespace std;
void func(int a, int b = 10, int c = 36);
int main(){
    func(99);
    return 0;
}
void func(int a, int b = 10, int c = 36){
    cout<<a<<", "<<b<<", "<<c<<endl;
}


编译时代码会报错,因为func() 的定义和声明位于同一个源文件,它们的作用域也都是整个源文件,这样就导致在同一个文件作用域中指定了两次默认参数,违反了 C++ 的规定。

针对于函数的定义和声明在同一作用域内的默认值参数,要么设置在定义处,要么设置在声明处,二选一。

在多文件编程时,C++允许将函数的声明放在主文件中,将函数实现放在其他文件中。


比如:Main.Cpp

#include <iostream>
using namespace std;
void func(int a, int b = 10, int c = 36);
int main(){
    func(99);
    return 0;
}
Moudle.cpp中:
#include <iostream>
using namespace std;
void func(int a, int b = 10, int c = 36){
    cout<<a<<", "<<b<<", "<<c<<endl;
}


上述代码运行时没有问题的,因为func的声明和定义的作用域一个在main.cpp中一个在moudle.cpp中,相互之间不影响。

在使用中建议在函数声明处指定函数默认参数值。

二、*函数占位参数


2.1 占位函数简介


1、C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。


2、C++中函数的参数列表中可以实现占位参数,调用函数时,必须把该位置进行补全。
示例:占位参数

#include <iostream>   //C++标准头文件不含有.h
#include <stdio.h>
using namespace std;
void fun(int A,int B,int)
{
    cout<<A<<"   "<<B<<"    "<<endl;
}
int main()
{
    fun(1,2,10);
    return 0;
}


2.2 占位参数应用


在重置运算符++时,前置++ 使用operator++()和后置++重载 使用operator++(int),期中函数中参数是没有任何意义的,它的存在只是为了区分是前置形式还是后置形式。
 

// 前置++ 进行重载
Test operator++()
    {
        this->a += 1;
        this->b += 1;

        return *this;
    }

    // 后置++ 进行重载
    Test operator++(int)
    {
        Test temp = *this;

        // this指向的数据进行改变;
        this->a += 1;
        this->b += 1;

        return temp;
    }

三、函数重载及原理


3.1 函数重载简介


在实际开发中,有时候我们需要实现几个功能类似的函数,只是有些细节不同。

例如希望交换两个变量的值,这两个变量有多种类型,可以是 int、float、char等,我们需要通过参数把变量的地址传入函数内部。

在C语言中,程序员往往需要根据不同的灵分别设计出三个不同名的函数,如下:

void swap1(int *a, int *b);      //交换 int 变量的值
void swap2(float *a, float *b);  //交换 float 变量的值
void swap3(char *a, char *b);    //交换 char 变量的值

但是C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading)。借助重载,一个函数名可以处理不同数据类型的相同处理逻辑。

参数列表包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同。

上述代码在C++中可以实现如下:

#include <iostream>
using namespace std;
//交换 int 变量的值
void Swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}
//交换 float 变量的值
void Swap(float *a, float *b){
    float temp = *a;
    *a = *b;
    *b = temp;
}
//交换 char 变量的值
void Swap(char *a, char *b){
    char temp = *a;
    *a = *b;
    *b = temp;
}

重载就是在一个作用范围内(同一个类、同一个命名空间等)有多个名称相同但参数不同的函数。重载的结果是让一个函数名拥有了多种用途,使得命名更加方便(在中大型项目中,给变量、函数、类起名字是一件让人苦恼的问题),调用更加灵活。

3.2 函数重载的规则


1、函数名称必须相同。
2、参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
3、函数的返回类型可以相同也可以不相同。
4、仅仅返回类型不同不足以成为函数的重载。

3.3 函数重载原理


分别将C语言和C++语言的源代码编译为汇编代码:

#include <stdio.h>

int  add(int A,int B)

{

return A+B;

}

float  add(float A,float B)

{

return A+B;

}

int main()

{

add(10,10);

return 0;

}

#include <iostream>   //C++标准头文件不含有.h

#include <stdio.h>

using namespace std;

int  add(int A,int B)

{

return A+B;

}

float  add(float A,float B)

{

return A+B;

}

double  add(double A,double B)

{

return A+B;

}

int main()

{

cout<<add(10.0,10.0)<<endl;;

return 0;

}

编译器采用GNU计划中的gcc编译器

编译器采用GNU计划中的g++编译器

gcc 1.c -o app

g++ 1.cpp -o app

gcc编译器选项参数

编译:预处理、编译、汇编、链接

预处理:

替换#include <stdio.h>  #define

gcc -E xxx.c -o xxx.i

编译:由C语言编译为汇编指令

gcc -S  xxx.i  -o  xxx.s

汇编:将汇编指令编译为机器指令01

gcc -c 1.s -o 1.o

只当前源文件进行编译;

改变的进行编译;

全部重新编译;

gpio.o lcd.o key.o

链接

gcc bc.o add.o sub.o -o app

参数:

-o 对生成文件进行重命名./重命名

-I指定头文件目录

g++ -S xxx.cpp -o xxx.s

在命令行中,将当前代码编译为汇编代码查看如下:

CC++

.file "1.c"

.text

.globl add

.type add, @function

add:

.LFB0:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

movl 8(%ebp), %edx

movl 12(%ebp), %eax

addl %edx, %eax

popl %ebp

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE0:

.size add, .-add

.globl main

.type main, @function

main:

.LFB1:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

pushl $10

pushl $10

call add

addl $8, %esp

movl $0, %eax

leave

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1:

.size main, .-main

.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"

.section .note.GNU-stack,"",@progbits

.file "1.cpp"

.local _ZStL8__ioinit

.comm _ZStL8__ioinit,1,1

.text

.globl _Z3addii

.type _Z3addii, @function

_Z3addii:

.LFB1021:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

movl 8(%ebp), %edx

movl 12(%ebp), %eax

addl %edx, %eax

popl %ebp

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1021:

.size _Z3addii, .-_Z3addii

.globl _Z3addff

.type _Z3addff, @function

_Z3addff:

.LFB1022:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

flds 8(%ebp)

fadds 12(%ebp)

popl %ebp

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1022:

.size _Z3addff, .-_Z3addff

.globl _Z3adddd

.type _Z3adddd, @function

_Z3adddd:

.LFB1023:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

subl $16, %esp

movl 8(%ebp), %eax

movl %eax, -8(%ebp)

movl 12(%ebp), %eax

movl %eax, -4(%ebp)

movl 16(%ebp), %eax

movl %eax, -16(%ebp)

movl 20(%ebp), %eax

movl %eax, -12(%ebp)

fldl -8(%ebp)

faddl -16(%ebp)

leave

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1023:

.size _Z3adddd, .-_Z3adddd

.globl main

.type main, @function

main:

.LFB1024:

.cfi_startproc

leal 4(%esp), %ecx

.cfi_def_cfa 1, 0

andl $-16, %esp

pushl -4(%ecx)

pushl %ebp

.cfi_escape 0x10,0x5,0x2,0x75,0

movl %esp, %ebp

pushl %ecx

.cfi_escape 0xf,0x3,0x75,0x7c,0x6

subl $4, %esp

fldl .LC1

leal -8(%esp), %esp

fstpl (%esp)

fldl .LC2

leal -8(%esp), %esp

fstpl (%esp)

call _Z3adddd

addl $16, %esp

subl $4, %esp

leal -8(%esp), %esp

fstpl (%esp)

pushl $_ZSt4cout

call _ZNSolsEd

addl $16, %esp

subl $8, %esp

pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_

pushl %eax

call _ZNSolsEPFRSoS_E

addl $16, %esp

movl $0, %eax

movl -4(%ebp), %ecx

.cfi_def_cfa 1, 0

leave

.cfi_restore 5

leal -4(%ecx), %esp

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1024:

.size main, .-main

.type _Z41__static_initialization_and_destruction_0ii, @function

_Z41__static_initialization_and_destruction_0ii:

.LFB1031:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

subl $8, %esp

cmpl $1, 8(%ebp)

jne .L11

cmpl $65535, 12(%ebp)

jne .L11

subl $12, %esp

pushl $_ZStL8__ioinit

call _ZNSt8ios_base4InitC1Ev

addl $16, %esp

subl $4, %esp

pushl $__dso_handle

pushl $_ZStL8__ioinit

pushl $_ZNSt8ios_base4InitD1Ev

call __cxa_atexit

addl $16, %esp

.L11:

nop

leave

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1031:

.size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

.type _GLOBAL__sub_I__Z3addii, @function

_GLOBAL__sub_I__Z3addii:

.LFB1032:

.cfi_startproc

pushl %ebp

.cfi_def_cfa_offset 8

.cfi_offset 5, -8

movl %esp, %ebp

.cfi_def_cfa_register 5

subl $8, %esp

subl $8, %esp

pushl $65535

pushl $1

call _Z41__static_initialization_and_destruction_0ii

addl $16, %esp

leave

.cfi_restore 5

.cfi_def_cfa 4, 4

ret

.cfi_endproc

.LFE1032:

.size _GLOBAL__sub_I__Z3addii, .-_GLOBAL__sub_I__Z3addii

.section .init_array,"aw"

.align 4

.long _GLOBAL__sub_I__Z3addii

.section .rodata

.align 8

.LC1:

.long 858993459

.long 1076114227

.align 8

.LC2:

.long 0

.long 1076101120

.hidden __dso_handle

.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"

.section .note.GNU-stack,"",@progbits

由图可知:C++代码在编译时会根据参数列表对函数进行重命名,例如int add(int a, int b)会被重命名为_Z3addii,float add(float x, float y)会被重命名为_Z3addff。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。

函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

3.4 实现C++和C语言的混合编程


在 C++ 出现之前,很多实用的功能都是用 C 语言开发的,很多底层的库也是用 C 语言编写的。这意味着,如果能在 C++ 代码中兼容 C 语言代码,无疑能极大地提高 C++ 程序员的开发效率。

C++ 和 C 可以进行混合编程。但需要注意的是,由于 C++ 和 C 在程序的编译、链接等方面都存在一定的差异,而这些差异往往会导致程序运行失败。

例如:

//myfun.h
void display();
//myfun.c
#include <stdio.h>
#include "myfun.h"
void display(){
   printf("C语言:myfunc函数");
}
//main.cpp
#include <iostream>
#include "myfun.h"
using namespace std;
int main(){
   display();
   return 0;
}

主程序是cpp,display是c语言代码,运行时报错:

In function `main': undefined reference to `display()'


C 语言是不支持函数重载的,它不会在编译阶段对函数的名称做较大的改动。
C++支持函数重载,在程序的编译阶段对函数的函数名进行“再次重命名”。

因此使用 C 和 C++ 进行混合编程时,考虑到对函数名的处理方式不同,势必会造成编译器在程序链接阶段无法找到函数具体的实现,导致链接失败。

借助 extern "C",就可以轻松解决 C++ 和 C 在处理代码方式上的差异性。

extern "C" 既可以修饰一句 C++ 代码,也可以修饰一段 C++ 代码,它的功能是让编译器以处理 C 语言代码的方式来处理修饰的 C++ 代码。

为了避免 display() 函数以不同的编译方式处理,我们以如下的方式修改 myfun.h

#ifdef __cplusplus
extern "C" void display();
#else
void display();
#endif


那么当 myfun.h 被引入到 C++ 程序中时,会选择带有 extern "C" 修饰的 display() 函数;反之如果 myfun.h 被引入到 C 语言程序中,则会选择不带 extern "C" 修饰的 display() 函数。由此,无论 display() 函数位于 C++ 程序还是 C 语言程序,都保证了 display() 函数可以按照 C 语言的标准来处理。

当仅修饰一句 C++ 代码时,直接将其添加到该函数代码的开头即可;如果用于修饰一段 C++ 代码,只需为 extern "C" 添加一对大括号{},并将要修饰的代码囊括到括号内即可。



这篇关于c++之函数与函数重载的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程