跳至主要內容

函数重载

张威大约 11 分钟c/c++c++基础

函数重载

在实际开发中,有时候需要实现几个功能类似的函数,只是细节有所不同。如交换两个变量的值,但这两种变量可以有多种类型,short, int, float等。在C语言中,必须要设计出不同名的函数,其原型类似于:

void swap1(short *, short *);
void swap2(int *, int *);
void swap3(float *, float *);

但在C++中,这完全没有必要。C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数重载(Function Overloading)。借助重载,一个函数名可以有多种用途。 函数重载是指在,可以有一组的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。 C++进行名字改编(name mangling),具体的规则是:

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

关注重点🍖🍖🍖

c++为什么支持函数重载,c语言不支持函数重载?

  • c++代码在产生函数符号的时候,是由组成的
  • c代码产生函数符号的时候,仅由决定!
  • 由于编译产生符号的规则不同,因此c语言不支持函数重载
    • c++代码用c语言编译器编译会出现链接错误

其最主要原因是编译器产生代码的规则不同。C++下我们可以编译通过它,但是C下面却不行。主要原因是C++代码在产生函数符号时候,由函数名 + 参数列表类型组成的,参数列表不同,编译时产生不同的符号;C代码在产生函数符号时,符号只由函数名决定,链接时会产生链接错误,

函数重载需要注意什么?

  1. 函数重载一定要先处于同一作用域
bool compare(int a, int b)  // compare_int_int
{
	cout << "compare_int_int" << endl;
	return a > b;
}
bool compare(double a, double b) // compare_double_double
{
	cout << "compare_double_double" << endl;
	return a > b;
}
bool compare(const char *a, const char *b) // compare_const char*_const char*
{
	cout << "compare_char*_char*" << endl;
	return strcmp(a, b) > 0;
}
int main()
{
	bool compare(int a, int b); // 函数的声明,导致编译器不会去外面找别的compare定义,
	compare(10, 20); // call compare_int_int
	compare(10.0, 20.0); // double -> int
	compare("aaa", "bbb"); // const char* => int,error!!!
	return 0;
}
  1. 函数名称必须相同 ,参数列表必须不同(参数的类型不同、个数不同、顺序不同)
  2. constvolatile 的情况会影响形参类型(后面会涉及 )
#include <typeinfo> //typeid()
//虽然第二个函数的参数中包含了const修饰符,但是在函数参数中const修饰符并不影响函数的签名,因此两个函数的参数列表是相同的,会被视为重定义
void func(int *a) {}  // int
void func(int *const a) {}  // 错误,重定义

//下面的是函数重载,因为参数列表不同
//虽然都是指向int类型的指针,但第二个函数的参数是指向常量int的指针,因此参数列表不同
void func(int *a);	//_Z4funcPi
void func(const int *a);	//_Z4funcPKi

int main()
{
	int a = 10;
	const int b = 10;

	cout << typeid(a).name() << endl;  // int	
	cout << typeid(b).name() << endl; // int

	return 0;
}
  1. 仅仅返回类型不同不是函数重载
    • 因为函数符号的生成与返回值没有关系

什么是多态(一个东西有多种多样的形态)/怎么理解多态?

  • 静态多态(编译时):函数重载、模板

  • 动态多态(运行时)

c++和c语言代码之间如何相互调用?

在linux上测试时,c文件链接用gcc,c++文件用g++,不然没效果

C++调用C函数

如果c代码明确表示用c方式编译
// main.cpp
extern "C"{	
  int sum(int a, int b);
}

int main()
{
  int ret = sum(10, 20);
  cout << "ret = " << ret << endl;
  return 0;

}

// test.c
extern "C" {
  int sum(int a, int b)
  {
    return a + b;
  }
}
g++ test.c main.cc -o main
已经使用c编译方式编译成了可重定向的文件

因为C++中函数声明生成符号带参数类型,而C文件中函数生成符号不带参数类型,两者不匹配,故在函数声明处加extern "C ":

// main.cpp

extern "C"{	
  int sum(int a, int b);
}

int main()
{
  int ret = sum(10, 20);
  cout << "ret = " << ret << endl;
  return 0;

}

// test.c
int sum(int a, int b)

{
  return a + b;
}

可看出此时以C方式生成符号。再对test.c 编译得到目标文件并查看目标文件符号表

g++ -c main.cpp #main.o
gcc -c test.c	#test.o
g++ mian.o test.o -o main #使用g++链接

注意:

g++ -c main.cpp test.c #这样写会导致test.c会按照c++方式编译成test.o
直接使用c++兼容c
// main.cc
#include <iostream>
using std::cout;
using std::endl;

int sum(int a, int b); // 去掉extern "C"声明

int main()
{
  int ret = sum(10, 20);
  cout << "ret = " << ret << endl;
  return 0;
}

// test.c
int sum(int a, int b)

{
  return a + b;
}
g++ test.c main.cc -o main

C调用C++的函数代码

即函数定义写在cpp文件中

// main.cpp
extern "C"
{
    int sum(int a, int b)
    {
        return a + b;
    }
}
// test.c
#include <stdio.h>
int sum(int, int);
int main()
{
    int ret = sum(10, 20);
    printf("ret = %d\n", ret);

    return 0;
}

编译执行成功:

gcc -o main main.cpp test.c

如果不把定义放在extern "C"中则提示:

image-20240201150931334
image-20240201150931334

注意:这个extern "C"是写在cpp文件中的!!!!

补充

#ifdef __cplusplus
extern "C" {
#endif
	int sum(int a, int b) // sum  .text
	{
		return a + b;
	}
#ifdef __cplusplus
}
#endif

C++编译器都内置了__cplusplus宏名,保证了若是以C++编译该该代码,可以保证以C方式生成符号,使得不管是在C还是C++文件中定义该函数,都能够在C文件中使用该函数。

课堂代码

#include <stdlib.h>
#include <string.h>
#include <iostream>

using std::cout;
using std::endl;

//C++支持函数重载,C语言不支持函数重载
//函数重载原理:对函数名字做了改编(name mangling)
//函数重载步骤:在同一个作用域中,当函数名字相同的时候,根据参数的
//个数、顺序、类型对函数名字进行改编

#ifdef __cplusplus//C和C++的混合编程

extern "C"
{
#endif

int add(int x, int y)
{
    return x + y;
}

#ifdef __cplusplus
}//end of extern "C"
#endif

int add(int x, int y, int z)
{
    return x + y + z;
}

float add(float x, float y)
{
    return x + y;
}

float add(float x, float y, float z)
{
    return x + y + z;
}

float add(float x, int y, float z)
{
    return x + y + z;
}

int main(int argc, char **argv)
{
    int *pInt = static_cast<int *>(malloc(sizeof(int)));
    memset(pInt, 0, sizeof(int));

    free(pInt);
    pInt = nullptr;
    return 0;
}

nm查看二进制文件

$gcc -c add.cc #生成add.o的二进制文件
$file add.o#查看文件信息
add.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped#ELF 二进制文件
$file add.cc
add.cc: C++ source, UTF-8 Unicode text
#对于二进制文件不能用vi看(看不懂),需要用nm查看
$nm add.o
0000000000000000 T add	#以c语言的方式编译的int add(int x, int y)
                 U __cxa_atexit
                 U __dso_handle
                 U free
                 U _GLOBAL_OFFSET_TABLE_
0000000000000127 t _GLOBAL__sub_I_add
0000000000000090 T main
                 U malloc
                 U memset
0000000000000030 T _Z3addff	#c++,根据参数的个数、顺序、类型对函数名字进行改编
000000000000004a T _Z3addfff
000000000000006e T _Z3addfif
0000000000000014 T _Z3addiii
00000000000000de t _Z41__static_initialization_and_destruction_0ii
                 U _ZNSt8ios_base4InitC1Ev
                 U _ZNSt8ios_base4InitD1Ev
0000000000000000 r _ZStL19piecewise_construct
0000000000000000 b _ZStL8__ioinit

extern 和extern"C"关键字

extern关键字

外部变量(全局变量)extern----全局静态存储区

extern 可以声明一个变量,作用是用来说明“此变量/函数是在别处定义的,要在此处引用

定义:表示创建变量或分配存储单元

声明:说明变量的性质,但并不分配存储单元

头文件中只存放“”,而不存放“定义”

int a = 10;如果这个头文件被多次引用的话,a会被重复定义

  • 也不要在头文件中定义函数,只写函数声明
extern int i; //用于变量的声明,没有分配内存
int i; //注意,这是定义

extern 的使用场景1

比如:创建两个文件 test.c 和 main.c文件;在test.c文件中定义一个全局变量:

//test.c
int i = 20; //定义一个全局变量
//main.c文件
#include<stdio.h>
extern int i; //声明变量i,当编译链接时候,main.c文件就可以访问到test.c文件的i的值了;
int main()
{
	printf("%d",i);
	return 0;
}

extern 的使用场景2

但是上述的使用方式并不好,假如我一个大工程,多个文件都要访问test.c文件的变量 i,只能在这些文件的都声明变量 i,有好多其他变量呢?这会使得书写难度很繁琐,并且维护成本也大

所以一般,我们都把声明语句放到头文件使用,即我定义一个test.h的头文件;

//test.h

extern int i;
extern int j;
extern int k;
//...
//声明很多很多变量

然后,假如你在其他文件要使用改变量i,直接包含该头文件即可

# include<stdio.h>
# include"test.h"
//extern int i; 不用写了
//extern int j;不用写了
//extern int k;不用写了
//...
//声明很多很多变量,都不用写了,因为包含了头文件,声明都在头文件中
int main()
{
	printf("%d %d %d",i,j,k);
	return 0;
}

extern “C” 的理解和用法

加上extern "C"后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中;而C语言并不支持函数重载,因此

这个功能主要用在下面的情况:

  1. C++代码调用C语言代码
  2. 在C++的头文件中使用
  3. 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh

#ifdef __cplusplus   //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{ //C要大写
#endif
 
#include"test.h"//c编写的代码
/*…*/	//被extern "C"限定的函数或变量是extern类型的
 
#ifdef __cplusplus
}
#endif

#endif /*end of __INCvxWorksh*/

注意:

未加extern "C"声明

//模块A头文件 moduleA.h
#idndef _MODULE_A_H
#define _MODULE_A_H
 
int foo(int x, int y);
#endif 
//模块B实现文件 moduleB.cpp
#include"moduleA.h"
foo(2,3); 

实际上,,因此会出现链接错误(g++不会报错)。

在C++代码中,包含纯C的头文件而没有显式使用 extern "C" 并不一定会导致编译错误。这是因为C++编译器通常能够正确处理C语言的函数声明,即便缺少 extern "C" 包装。

C++编译器在处理纯C头文件时,会默认按照C的链接规范进行处理。这是因为C++的链接规范与C的规范在很多方面是相似的。因此,对于纯C的函数声明,通常不需要显式使用 extern "C"

然而,显式地使用 extern "C" 是为了确保在更多情况下的兼容性。特别是在一些复杂的场景或者涉及到C++和C混合编程的时候,最好添加 extern "C" 来确保正确的链接规范。

总的来说,虽然未加 extern "C" 声明不会导致编译错误,但在混合使用C和C++的情况下,最好使用 extern "C" 来确保正确的链接规范,以避免潜在的问题。