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 |
在命令行中,将当前代码编译为汇编代码查看如下:
C | C++ |
.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" 添加一对大括号{},并将要修饰的代码囊括到括号内即可。
标签:函数,cfi,int,esp,c++,ebp,C++,重载 来源: https://blog.csdn.net/amyliyanice/article/details/123608939