C++入门——缺省参数|函数重载

简介: C++入门——缺省参数|函数重载

前言:

       C++入门我们主要是补充C语言的不足,为后续类和对象学习打基础。在前面我们学了命名空间、输入输出,今天我们继续学习。

上期链接:

C++入门——关键字|命名空间|输入&输出_wangjiushun的博客-CSDN博客


一、缺省参数(也叫默认参数)

1、缺省参数概念

      缺省参数是声明或定义函数时为函数参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

代码演示:

#include<iostream>
using namespace std;
//在缺省参数是在声明或定义函数时为函数的参数指定一个缺省值。
//如Func函数,在定义时为形参a提供了默认实参,默认实参作为形参的初始值出现在形参列表中。
void Func(int a = 0)
{
  cout << a << endl;
}
int main()
{
  Func();//①没有传参时,使用参数的默认值
  Func(10);//②传参时,使用指定的实参
  return 0;
}


说明:

1、函数参数指定缺省值时,可以不传参(使用参数缺省值),也可以传参(使用指定的实参)。

2、函数参数没有指定缺省值时,必须传参。

2、缺省参数分类

(1)全缺省参数 —— 所有形参都提供缺省值

       代码实例:

#include<iostream>
using namespace std;
void Func(int a = 10, int b = 20, int c = 30)
{
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
}
int main()
{
  Func();
  Func(1);
  Func(1, 2);
  Func(1, 2, 3);
  return 0;
}

我们参数的传递是怎么传递的呢?能跳着传吗?

      答案是:语法规定实参传实参的顺序是从左向右依次传递的。注意:不能跳着传参,因为祖师爷不喜欢(即规定是这样,祖师爷最大)。

(2)半缺省参数 —— 是形参缺省一部分,不是就说缺省一半。并且规定缺省值是从右向左依次缺省的。

       代码演示:

#include<iostream>
using namespace std;
void Func(int a, int b = 20, int c = 30)
{
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
}
int main()
{
  //参数的传递规定是从左向右依次传递的,不能跳着传。
  //所以标准规定缺省值是从右向左依次缺省的。
  //因为Func的第一个形参a没有缺省,所以Func的第一个参数必须传
  Func(1);
  Func(1, 2);
  Func(1, 2, 3);
  return 0;
}

(3)使用场景示例

       代码演示:栈的初始化

分析:

       ①静态的栈,给小了不够用,给大了浪费,但是效率比较高(因为不存在扩容问题)。

       ②当我们明确知道栈要插入几个数据时,传参;当我们不知道栈要插入几个数据时,那就不传参了。


①C语言实现:

#define DEFAULT_CAPACITY 4
void StackInit(struct Stack* pst)
{
  pst->a = (int*)malloc(sizeof(int) * DEFAULT_CAPACITY);
  if (pst->a == NULL)
  {
    perror("malloc fail:");
    return;
  }
  pst->top = 0;
  pst->capacity = DEFAULT_CAPACITY;
}

说明:

1、C语言有几个形参就必须传几个实参,一一对应。(即C语言不支持缺省参数)

2、C语言使用宏替代不灵活  ——定义不同栈的时,他们的初始化都相同。

   ②C++实现:

#include<iostream>
using namespace std;
struct Stack
{
  int* a;
  int top;
  int capacity;
};
//初始化栈
void StackInit(struct Stack* pst,int defaultCapacity = 4)
{
  pst->a = (int*)malloc(sizeof(int) * defaultCapacity);
  if (pst->a == NULL)
  {
    perror("malloc fail:");
    return;
  }
  pst->top = 0;
  pst->capacity = defaultCapacity;
}
int main()
{
  struct Stack st1;
  //插入100个数据
  StackInit(&st1, 100);
  struct Stack st2;
  //不知道要插入多少数据
  StackInit(&st2);
  return 0;
}

说明:

1、缺省参数——①传参时,使用指定的实参;②没有传参时,使用参数的缺省值。

2、对比C语言,有了缺省参数就灵活了一些。

3、缺省参数的注意事项(易错点)

1、半缺省参数必须从右往左依次给出,不能间隔着给

2、缺省参数不能在函数声明和定义中同时出现

3、缺省值必须是常量或者全局变量

4、C语言不支持缺省参数

讲解:

(1)缺省参数不能在函数声明和定义中同时出现

              答案是:语法规定只能一个给,不能两个同时给(就像你小时候,学校让你交资料费时,你只有向你妈或你爸要,不能两边都要,否则就要被打了)。

代码演示:

#include<iostream>
using namespace std;
void Func(int a, int b = 20, int c = 30);
int main()
{
  Func(1);
  Func(1, 2, 3);
  return 0;
}
void Func(int a, int b = 20, int c = 30)
{
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
}

报错信息:

(2) 缺省参数不能在定义和声明同时出现,那给谁呢?

答案是:给函数声明,应该在函数声明中指定缺省值,并将该声明放在合适的头文件中。

代码演示:

Func.h文件:

#include<iostream>
using namespace std;
void Func(int a, int b = 20, int c = 30);

Func.cpp文件:

#include"Func.h"
void Func(int a, int b , int c )
{
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
}

test.cpp文件:

1. #include"Func.h"
2. 
3. int main()
4. {
5.  Func(1);
6.  Func(1, 2, 3);
7.  return 0;
8. }

说明:

1、详解编译+链接:

       程序的从源文件到执行程序,是经过翻译环境才到执行环境的,翻译环境又分为编译和链接。编译时源文件各走各的通过编译过程分别转化成目标代码,每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。

       (1)编译又分为预编译、编译、汇编三个部分:①预编译时,它会完成头文件的包(即:它会打开指定的头文件,并将其中的代码插入到包含该指令的源文件中,然后再进行编译);②编译时进行语法分析、词法分析、语义分析、符号汇总,将C++代码转换成汇编代码;③汇编时生成符号表,把汇编代码转换成机器指令(二进制指令)。

       (2)链接(链接器):把多个目标文件和链接库进行链接:①合成段位;②符号表的合并和重定位。

2、缺省参数一般在函数声明中指定缺省值,并将该声明放在合适的头文件中。因为编译时每个源文件都是独立转换为目标文件的,如果不在函数声明中指定缺省值在函数定义中指定缺省值,那test.cpp文件在编译过程的第二个步骤,编译时语法不通过,如图:

在声明的时候,有缺省值,函数调用时没有传参,就编译时就默认使用指定的缺省值,没有缺省值,函数调用时就必须传参。


二、函数重载

       自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真正的含义,即该词被重载了。

1、函数重载概念

      函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类型的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

(1)参数类型不同

int Add(int left, int right)
{
  cout << "int Add(int left, int right)" << endl;
  return left + right;
}
double Add(double left, double right)
{
  cout << "double Add(double left, double right)" << endl;
  return left + right;
}
int main()
{
  Add(10, 20);
  Add(10.1, 20.2);
  return 0;
}


运行结果:

(2)参数个数不同

#include<iostream>
using namespace std;
void f()
{
  cout << "f()" << endl;
}
void f(int a)
{
 cout << "f(int a)" << endl;
}
int main()
{
  f();
  f(10);
  return 0;
}

运行结果:

(3)参数类型顺序不同

#include<iostream>
using namespace std;
void f(int a, char b)
{
  cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
  cout << "f(char b, int a)" << endl;
}
int main()
{
  f(10, 'a');
  f('a', 10);
  return 0;
}

运行结果:

说明:

1、调用重载函数时有三种可能的结果:

       (1)编译器找到一个与实参最佳匹配(匹配重载函数的顺序:首先寻找一个精确匹配,如果能找到,调用该函数;其次进行提升匹配,通过内部类型转换(窄类型到宽类型的转换)寻求一个匹配,如char到int、short到int等,如果能找到,调用该函数;最后通过强制类型转换寻求一个匹配,如int到double等,如果能找到,调用该函数。)的函数,并生成调用该函数的代码。

   (2)找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。

   (3)有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用。

2、尽管函数重载能在一定程度上减轻我们为函数起名字、记名字的负担,但是最好只重载那些确实非常相似的操作。


注意(混淆点):

1、函数重载对返回值没有要求:返回值不同,其他所有要素(函数名、形参列表)都相同,不构成重载。

2、二义性调用:可构成重载函数,但是对于重载函数的调用不明确。


讲解:

       (1)二义性调用:可构成重载函数,但是对于重载函数的调用不明确。

       代码演示:当函数重载使用缺省参数时,注意二义性调用

#include<iostream>
using namespace std;
void f()
{
  cout << "f()" << endl;
}
void f(int a = 10)
{
 cout << "f(int a)" << endl;
}
int main()
{
  f();//err,对函数重载的调用不明确
  f(10);//ok
  return 0;
}
int Add(int x, int y)
{
  return x + y;
}
double Add(double x, double y)
{
  return x + y;
}


Add.h文件:

int Add(int x, int y);
double Add(double x, double y);

test.cpp文件:

#include"Add.h"
int main()
{
  Add(1, 2);
  Add(1.1, 2.2);
  return 0;
}


说明:

1、实际项目通常是由多个头文件和多个源文件构成的,去上面两张图,我们可以知道,在预处理、编译、汇编三个阶段源文件独立完成,各走各的分别转化成目标代码,在链接阶段每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。

2、我们发现在编译后链接前,test.o的目标文件中没有Add的函数地址,因为Add定义在Add.cpp中的,所以Add的地址在Add.o中。所以链接阶段就是专门处理这种问题,链接器看到test.o调用Add,但是没有Add的地址,就会到Add.o的符号表中找Add的地址,然后链接到一起。

3、为什么test.i阶段,test.i没有Add函数没有报错了,因为有Add函数的声明——声明就像承诺。链接——找到Add的定义,兑现承诺。

4、我们发现Add函数的名字在编译过程发生了改变——这里每个编译器都有自己的函数名修饰规则。

5、由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用g++演示了这个修饰的名字。

6、通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰变成【_Z+函数长度+函数名+类型首字母】。

(1)采用C语言编译器编译后的结果:

  结论:在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

       (2)采用C++编译器后结果:


 结论:在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

7、通过这里我们就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持重载。

8、如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没有办法区分。  


今天我们讲解了C++的缺省函数和重载函数,后续再有一节讲解引用、内联函数、auto关键字等我们就结束C++入门了。

相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
23天前
|
程序员 C++
C++中的函数重载有什么作用
【10月更文挑战第19天】C++中的函数重载有什么作用
17 3
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
23天前
|
编译器 程序员 C++
C++中的函数重载是什么
【10月更文挑战第19天】C++中的函数重载是什么
19 0
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0
|
1月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
1月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
33 4
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4