《C++初阶之类和对象》【命名空间 + 输入&输出 + 缺省参数 + 函数重载】 - 教程

在这里插入图片描述

往期《C++初阶》回顾:

/------------ 入门基础 ------------/
【C++的前世今生】

前言:

(。・ω・。)ノ♡hi~ 亲爱的小伙伴们!(≧∇≦)ノ 当芒种的风✨呼呼吹过,田间地头满是农人播种希望的身影,今天我们也迎来了正式学习 C++ 知识的新旅程了(★ω★)
那么,就让我们像芒种时节播撒种子一样,播撒 C++ 这颗种子,期待未来收获满满的编程硕果 ✨

---------------hello world---------------

比较C语言和C++的第一个程序:hello word

/*------------使用C语言编写第一个 hello world 代码------------*/
#
include <stdio.h>
  int main(
  )
  {
  printf("hello world"
  )
  ;
  return 0
  ;
  }
  /*------------使用C++编写第一个 hello world 代码------------*/
  #
  include <iostream>
    //1.使用#include包含C++的库
    using
    namespace std;
    //2.使用命名空间
    int main(
    )
    {
    cout <<
    "hello world" << endl;
    //3.使用输出流对象cout向终端控制窗口(小黑窗口) 输出字符串
    return 0
    ;
    }

---------------命名空间---------------

什么是命名空间?

命名空间(Namespace):是一种将标识符(如:变量、函数、类等)组织成不同作用域,用于组织代码、避免命名冲突的机制

怎么使用命名空间?

使用命名空间整体上主要是分为两步:

第一步:定义命名空间

第二步:访问命名空间

怎么定义命名空间?

namespace 命名空间名称
{
// 可包含的内容:
// - 变量
// - 函数
// - 类/结构体
// - 其他命名空间
类型 变量名;
返回类型 函数名(参数列表) {
/* 函数体 */
}
class 类名 {
/* 类定义 */
}
;
struct 结构体名 {
/* 结构体定义 */
}
;
namespace 子命名空间名 {
/* ... */
}
}

怎么访问命名空间?

在 C++ 中,命名空间(namespace)的使用方式非常灵活,以下是博主精心挑选的三个最实用的命名空间的使用方法,涵盖从基础到高级的所有场景:

  1. 直接通过作用域解析符 :: 访问
  2. 使用 using 声明(引入特定成员)
  3. 使用 using namespace 指令(引入整个空间)

1. 直接通过作用域解析符 :: 访问

  • 适用场景:精确控制访问路径,避免歧义。
/*----------------------------命名空间的使用----------------------------*/
#
include <iostream>
  #
  include <string>
    /*---------------第一步:命名空间的定义---------------*/
    namespace my_space
    {
    string str = "这是我定义的命名空间中的字符串"
    ;
    void fun(
    )
    {
    cout <<
    "这是我定义的命名空间中的函数"
    ;
    }
    }
    int main(
    )
    {
    /*---------------第二步:演示如何使用自定义的命名空间中的成员---------------*/
    /*-------------第一种的使用方法:使用作用域解析符访问-------------*/
    cout << my_space::str << endl;
    //printf("%s", my_space::str); 注意:这里你可别使用C语言的printf进行输出C++风格的字符串
    //string是C++的字符串对象,printf不能输出对象这种类型,(仔细回想一下C++中是没用字符串这种类型的,C语言是使用字符数组存储字符串的)
    printf("%s"
    , my_space::str.c_str(
    )
    )
    ;
    //如果你执意要使用printf进行输出的话,可以将string通过的函数c_str()转化为C风格的字符串
    return 0
    ;
    }

在这里插入图片描述

2. 使用 using 声明(引入特定成员)

  • 适用场景:需要频繁使用某个命名空间的少数成员
/*----------------------------命名空间的使用----------------------------*/
#
include <iostream>
  #
  include <string>
    using
    namespace std;
    /*---------------第一步:命名空间的定义---------------*/
    namespace my_space
    {
    string str = "这是我定义的命名空间中的字符串"
    ;
    void fun(
    )
    {
    cout <<
    "这是我定义的命名空间中的函数"
    ;
    }
    }
    using my_space::str;
    // 注意:使用 using 声明(引入特定成员)必须写在自定义的命名空间的后面
    int main(
    )
    {
    /*---------------第二步:演示如何使用自定义的命名空间中的成员---------------*/
    /*-------------第二种的使用方法:使用 using 声明(引入特定成员)-------------*/
    cout << str << endl;
    my_space::fun(
    )
    ;
    return 0
    ;
    }

在这里插入图片描述

3. 使用 using namespace 指令(引入整个空间)

  • 适用场景:短代码片段或源文件中(头文件中禁止使用
/*----------------------------命名空间的使用----------------------------*/
#
include <iostream>
  #
  include <string>
    using
    namespace std;
    /*---------------第一步:命名空间的定义---------------*/
    namespace my_space
    {
    string str = "这是我定义的命名空间中的字符串"
    ;
    void fun(
    )
    {
    cout <<
    "这是我定义的命名空间中的函数"
    ;
    }
    }
    using
    namespace my_space;
    // 注意:使用 using namespace 指令(引入整个空间)必须写在自定义的命名空间的后面
    int main(
    )
    {
    /*---------------第二步:演示如何使用自定义的命名空间中的成员---------------*/
    /*-------------第三种的使用方法:使用 using namespace 指令(引入整个空间)-------------*/
    cout << str << endl;
    fun(
    )
    ;
    return 0
    ;
    }

在这里插入图片描述

看到这里我相信有不少的小伙伴们会想到这行代码:using namespace std;

这行代码官方的回答是:将 std 命名空间中的所有成员引入到当前作用域,允许直接使用这些标识符而无需添加 std:: 前缀

简单点说就是:我们使用namespace定义一个命名空间,就像是在全局作用域这片广袤的大地上面使用围墙围起来一片属于自己的土地,这片土地有了主人(命名空间的名字)

以后谁再想访问围墙里面的东西,必须得到这片土地的主人的同意(指定命名空间的名字进行访问)

其中有一片土地简直就是世外桃源,土地的主人名叫std,很多人想去哪里但是每次都要征得同意(添加 std:: 前缀),是不是很麻烦,因此我们使用using namespace std 的感觉就像是:偷了了std的家——暴力将这片土地上的围墙给拆除了(大型项目中不建议这么做,这将会导致命名空间失去意义),从此大家去世外桃源就是想去就去了!!!

C++为什么要引入命名空间?

当项目规模较大时,不同库或模块可能使用相同的名称 (尤其是当两名在同一个项目组的程序员合并他俩的代码的时候,合并之后往往他俩有可能就会打起来),可能会导致命名冲突。

小案例:我写的变量和库函数的函数名引发命名冲突

#
include <iostream>
  #
  include <stdlib.h>
    /*------------任务:定义变量 + 在终端上打印该变量存储的值------------*/
    int rand = 100
    ;
    int main(
    )
    {
    printf("%d"
    , rand)
    ;
    //error:“rand”: 重定义;以前的定义是“函数”
    return 0
    ;
    }

在这里插入图片描述

而为了让大家能和睦相处,因此C++ 引入了命名空间机制,它通过逻辑隔离的方式,确保不同作用域内的同名标识符不会互相干扰,从而让代码协作更加和谐高效。

小案例:使用命名空间避免命名冲突

#
include <iostream>
  #
  include <stdlib.h>
    /*------------任务:定义变量 + 在终端上打印该变量存储的值------------*/
    namespace my_space
    {
    int rand = 100
    ;
    }
    int main(
    )
    {
    printf("%d"
    , rand)
    ;
    //error:“rand”: 重定义;以前的定义是“函数”
    return 0
    ;
    }

在这里插入图片描述

命名空间怎么就解决了同名标识符互相干扰的难题?

为了回答这个问题,我们要对 作用域 有一定的理解,那先从C语言说起吧:

关于C语言的作用域有哪些,不同的人会给出不同的划分种类,但是从本质上理解C语言就两个作用域:函数局部作用域全局作用域

因为一个C程序中所有的变量、函数存在的位置无非就是:函数局部作用域全局作用域

(其中函数只能存储于全局作用域中)


而这时候我们就要仔细思考:是C语言的哪些不足导致了上面的命名冲突的情况,而使得C++不得不引入命名空间

回答:由于C语言的作用域过于单一,当项目规模较大时,会有大量的变量、函数都存在于全局作用域中,这样的话就可能会导致很多命名冲突。

(在同一个函数作用域中一般我们不会使用相同的标识符进行命名)

所以C++的思路是:可不可以从全局作用域中再分出去一些域,所以就有了C++四大作用域:

  1. 函数局部作用域
  2. 命名空间作用域
  3. 类域
  4. 全局作用域

为解决这个问题,我们还要了解:

  • 命名冲突的本质是什么?
  • C++的作用域的访问顺序是什么?

命名冲突的本质是什么?

命名冲突(Name Conflict):在同一作用域内使用相同的标识符:变量名、函数名、类名等),导致编译器无法区分它们的情况。


注意:这里我们要抓住核心的的名词:“同一作用域 + 相同的标识符”

所以这也就好理解为什么:C++又在C语言的基础上又创建了两个作用域

C++的作用域的访问顺序是什么?

在 C++ 中,当访问一个标识符时,编译器会按照特定的顺序查找该标识符的定义,这个过程称为 名称查找(Name Lookup)

理解这个顺序对于避免命名冲突和理解代码行为至关重要。

名称查找顺序:

  1. 局部作用域(Block Scope):首先在当前函数的局部作用域中查找标识符,包括在当前块(如:if 语句、for 循环等)中定义的变量。
  2. 类作用域(Class Scope):如果在局部作用域中没有找到标识符,那么会在包含当前函数的类的成员作用域中查找。(如果当前函数是类的成员函数的话)
  3. 命名空间作用域(Namespace Scope):如果在类作用域中没有找到标识符,那么会在当前命名空间中查找。(如果定义的有命名空间的话)
  4. 全局作用域(Global Scope):如果在上面的作用域中都没有找到标识符,那么就会在全局作用域中查找。
  5. std 命名空间:在 C++ 中,std 是一个特殊的命名空间,包含了标准库的所有内容。如果在全局作用域中没有找到标识符,那么会检查 std 命名空间。

综上所述:这样做我们就是实现了:对标识符的名称进行本地化,以避免命名冲突或名字污染。也就是说:虽然咱俩写的标识符的名称相同,但是代码合并后重名的标识符不再是在同一个作用域:全局作用域 ,而是现在在不同的作用域:不同的 命名空间作用域,所以不构成命名冲突。

所以合并完代码之后咱俩还是好朋友

命名空间中可以放什么?

/*------------C++的命名空间使用------------*/
#
include <stdio.h>
  #
  include <stdlib.h>
    /*------------定义一个的命名空间:my_utils------------*/
    namespace my_utils
    {
    //1.命名空间中可以定义:变量
    int rand = 100
    ;
    //定义变量(故意与标准库rand函数同名)
    //2.命名空间中可以定义:函数
    int add(
    int left,
    int right)
    {
    return left + right;
    }
    //3.命名空间中可以定义:自定义数据类型
    struct SingleListNode
    {
    int data;
    struct SingleListNode* next;
    }
    ;
    }
    int main(
    )
    {
    //1.使用默认访问(全局命名空间访问):这里访问的是全局的标准库中rand函数地址
    printf("%p\n"
    , rand)
    ;
    printf("%p\n"
    , ::rand)
    ;
    //2.使用指定域(my_utils命名空间)访问:这里访问是my_utils命名空间的rand变量的值
    printf("%d\n"
    , my_utils::rand)
    ;
    return 0
    ;
    /*
    * 关键点说明:
    * 1. ::rand - 访问全局命名空间的rand(标准库函数)
    * 2. my_utils::rand - 访问my_utils命名空间的rand变量
    * 3. 实际开发中应避免与标准库同名
    */
    }

命名空间怎么进行嵌套使用?

/*------------命名空间的嵌套使用------------*/
#
include <stdio.h>
  /*------------定义一个的命名空间:school------------*/
  //1.外层命名空间:school
  namespace school
  {
  //2.内层命名空间1:teacher_a
  namespace teacher_a
  {
  //2.1:定义变量
  int rand = 50
  ;
  //2.2:定义函数
  int add(
  int left,
  int right)
  {
  return left + right;
  }
  }
  //2.内层命名空间2:teacher_b
  namespace teacher_b
  {
  //2.1:定义变量
  int rand = 100
  ;
  //2.2:定义函数
  int add(
  int left,
  int right)
  {
  return (left + right) * 10
  ;
  //将结果放大原来的10倍
  }
  }
  }
  int main(
  )
  {
  /*------------访问嵌套命名空间中的成员------------*/
  // 1. 访问不同命名空间中的同名变量
  printf("teacher_a的rand值: %d\n"
  , school::teacher_a::rand)
  ;
  // 输出50
  printf("teacher_b的rand值: %d\n"
  , school::teacher_b::rand)
  ;
  // 输出100
  // 2. 调用不同命名空间中的同名函数
  printf("teacher_a的加法结果(1+2): %d\n"
  ,
  school::teacher_a::add(1
  , 2
  )
  )
  ;
  // 输出3(1+2)
  printf("teacher_b的加法结果(1+2): %d\n"
  ,
  school::teacher_b::add(1
  , 2
  )
  )
  ;
  // 输出30((1+2)*10)
  return 0
  ;
  }

在这里插入图片描述

命名空间的其他注意事项

命名空间还有很多可以单独拎出来讲的东西:博主这里就精挑一些我们平时使用命名空间需要注意事项来讲一下:

命名空间与头文件的关系

  • 避免在头文件中使用 using namespace:可能导致命名冲突扩散到包含该头文件的所有代码。

    // myheader.h (错误示例!)
    using
    namespace std;
    // ❌ 污染所有包含该头文件的源文件

命名空间的全局可见性

  • 不同文件的同名命名空间会自动合并

    // File1.cpp
    namespace Shared
    {
    void func1(
    )
    ;
    }
    // File2.cpp
    namespace Shared //与File1中的Shared合并
    {
    void func2(
    )
    ;
    }

---------------输入&输出---------------

C++的输入&输出是什么?

cincout:是 C++ 标准库中用于标准输入输出的流对象,用于实现控制台程序的输入和输出操作。

  • 它们定义在 <iostream> 头文件中。
  • 它们属于 std 命名空间。

标准输入流 (cin):对应键盘输入,类型为 istream

  • cin 通过 提取运算符 (>>) 从键盘读取数据到变量

标准输出流 (cout):对应控制台输出,类型为 ostream

  • cout 通过 插入运算符 (<<) 将数据输出到控制台

它们是面向对象的 I/O 方式,比 C 语言的 scanfprintf 更安全、更灵活。

C++的cin和cout相较于C语言的输入和输出有什么优势?

#
define _CRT_SECURE_NO_WARNINGS
#
include <iostream>
  using
  namespace std;
  int main(
  )
  {
  //1.定义各种类型的变量
  int a;
  double b;
  char c;
  printf("-------------使用C风格进行输入&输出-------------\n"
  )
  ;
  //2.使用C风格进行输入赋值
  scanf("%d %lf %c "
  , &a, &b, &c)
  ;
  //3.使用C风格进行输出
  printf("%d %lf %c\n"
  , a, b, c)
  ;
  cout <<
  "-------------使用C++风格进行输入&输出-------------" << endl;
  //4.使用C++风格进行输入赋值
  cin >> a >> b >> c;
  //5.使用C++风格进行输出
  cout << a <<
  " " << b <<
  " " << c <<
  " " << endl;
  return 0
  ;
  }

在这里插入图片描述

特性scanf / printfcin / cout
类型安全❌ 需手动匹配格式符✔️ 自动类型推导
扩展性❌ 仅支持基本类型✔️ 支持自定义类型的 <<>>
性能✔️ 更快⚠️ 稍慢(同步问题)

关于cin和cout还有哪些需要注意事情?

<<: 运算符可连续使用,按顺序输出表达式的值

endl: 是一个函数,流插入输出时,相当于插入一个换行符,同时刷新输出缓冲区(立即显示内容)

\n: 仅换行,不强制刷新缓冲区(性能略优,但需注意输出时机)


>>:自动跳过空白字符(空格、制表符、换行符),并尝试将输入转换为目标类型。

  • 读取字符串时,cin 默认在遇到空格时停止,因此无法读取包含空格的完整名称(Alice Smith 会被截断为 Alice
功能语法说明
输出cout << value;使用 << 插入运算符,支持链式调用
输入cin >> variable;使用 >> 提取运算符,跳过空白字符
读取整行getline(cin, str);读取包含空格的字符串
格式化输出cout << setprecision(2);需要 <iomanip> 头文件

coutcin 为保证类型安全,性能略低于 C 的 printfscanf

可通过以下三句话可以将cin和cout的输入输出速度提升2-3倍

ios_base::sync_with_stdio(false
)
;
cin.tie(
nullptr
)
;
cout.tie(
nullptr
)
;

下面是算法竞赛中常用的解绑三件套的模板和详细的注释:

#
include <iostream>
  using
  namespace std;
  int main(
  )
  {
  /* ==============================================
  * 【IO性能优化关键代码】
  * 以下三行用于提高C++标准IO流的执行效率
  * 适用于需要高速IO的场景(如算法竞赛)
  * ==============================================
  */
  /*----------第一步:关闭C++标准流与C标准库的同步----------*/
  // - 默认情况下,C++的cin/cout与C的stdin/stdout保持同步以保证混用时的安全性
  // - 关闭后可以提高速度,但不能再混用C/C++的IO函数(如:printf/cin混用)
  ios_base::sync_with_stdio(false
  )
  ;
  /*----------第二步:解绑cin与cout的关联----------*/
  // - 默认情况下,cin操作会先自动刷新cout的缓冲区(保证交互式程序的正确性)
  // - 解绑后进一步提升速度,但需要手动控制缓冲区刷新(如:用endl或flush)
  cin.tie(
  nullptr
  )
  ;
  /*----------第三步:解绑cout与cerr/clog的关联(非必须,但某些编译器需要)----------*/
  // - 进一步减少不必要的缓冲区同步
  cout.tie(
  nullptr
  )
  ;
  /* ==============================================
  * 【注意事项】
  * 使用这些优化后:
  * 1. 禁止混用C/C++的IO函数(如不能用printf + cin)
  * 2. 需要显式刷新缓冲区(如cout << endl;)
  * 3. 适用于纯C++ IO且数据量大的场景(如:10万+次读写)
  * ==============================================
  */
  return 0
  ;
  }

---------------缺省参数---------------

什么是缺省参数?

缺省参数(Default Arguments):是指在函数声明定义时为参数指定一个默认值。

  • 当函数调用时,如果没有为该参数传递实际参数,就会使用这个默认值。

缺省参数的语法返回类型 函数名(类型 参数名 = 默认值);

缺省参数的使用案例:

/*--------------------------C++的缺省参数--------------------------*/
#
include <iostream>
  using
  namespace std;
  /*------------简单的了解缺省参数------------*/
  void Func(
  int a = 0
  ) // 参数a的默认值为0
  {
  // 输出参数a的值
  cout <<
  "a = " << a << endl;
  }
  int main(
  )
  {
  //情况1:不传递参数,使用默认值
  cout <<
  "调用Func():" << endl;
  Func(
  )
  ;
  // 等效于Func(0)
  // 输出结果:a = 0
  //情况2:传递明确参数值
  cout <<
  "\n调用Func(10):" << endl;
  Func(10
  )
  ;
  // 覆盖默认值,a被赋值为10
  // 输出结果:a = 10
  return 0
  ;
  }

在这里插入图片描述

缺省参数的有哪几种?

全缺省参数:函数的所有参数都有默认值

半缺省参数:函数的部分参数有默认值,且有默认值的参数必须放在形参列表的右侧

  • 从右往左依次连续缺省,不能间隔跳跃给缺省值

全缺省参数和半缺省参数的使用案例:

/*------------------全缺省参数and半缺省参数------------------*/
/**
* @brief 演示全缺省参数的函数
* @param a 第一个参数,默认值为10
* @param b 第二个参数,默认值为20
* @param c 第三个参数,默认值为30
* @note
* - 全缺省参数:所有参数都有默认值
* - 调用时可传递0~3个参数
* - 缺省参数必须从右向左连续定义
*/
void Func1(
int a = 10
,
int b = 20
,
int c = 30
)
{
cout <<
"a = " << a;
cout <<
" b = " << b;
cout <<
" c = " << c << endl;
}
/**
* @brief 演示半缺省参数的函数
* @param a 必须传入的参数(无默认值)
* @param b 可选参数,默认值为10
* @param c 可选参数,默认值为20
* @note
* - 半缺省参数:部分参数有默认值
* - 必须从右向左连续缺省
* - 调用时至少传递1个参数(对应a)
*/
void Func2(
int a,
int b = 10
,
int c = 20
)
{
cout <<
"a = " << a;
cout <<
" b = " << b;
cout <<
" c = " << c << endl;
}
int main(
)
{
// 测试Func1的全缺省调用
cout <<
"----- Func1全缺省测试 -----" << endl;
Func1(
)
;
// 使用全部默认值:a=10, b=20, c=30
Func1(1
)
;
// a=1, b和c用默认值:b=20, c=30
Func1(1
, 2
)
;
// a=1, b=2, c用默认值:c=30
Func1(1
, 2
, 3
)
;
// 覆盖所有默认值:a=1, b=2, c=3
// 测试Func2的半缺省调用
cout <<
"----- Func2半缺省测试 -----" << endl;
Func2(100
)
;
// a必须传值:a=100, b和c用默认值:b=10, c=20
Func2(100
, 200
)
;
// a=100, b=200, c用默认值:c=20
Func2(100
, 200
, 300
)
;
// 覆盖所有参数:a=100, b=200, c=300
return 0
;
}

在这里插入图片描述

使用缺省参数时的注意事项有哪些?

特性说明示例
从右向左连续缺省参数必须从参数列表最右侧开始连续定义void func(int a, int b=1, int c=2) ✔️
void func(int a=0, int b)
调用时从左匹配实参按从左到右顺序匹配形参,不能跳过参数func(10)a=10,b=1,c=2
func(10,20)a=10,b=20,c=2
单次定义原则如果函数在头文件中声明并指定了缺省参数,在源文件中定义时不能再重复指定缺省参数,否则会导致重复定义错误。头文件:void foo(int x=5);
源文件:void foo(int x) {...}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

C++中引入缺省参数有什么好处?

疑问:可能有很多的小伙伴们在学习了缺省参数后,会感慨它有什么用处呢?

有这样的疑问是很正常的,接下来博主将使用C++的缺省参数重新实现一下我们在《数据结构初阶》中学习的顺序栈的其中的一个接口函数:顺序栈的初始化,带你领略一下缺省参数的牛逼厉害之处。

------------------------------Stack.h-------------------------------
#
include <iostream>
  #
  include <assert.h>
    using
    namespace std;
    typedef
    int STKDataType;
    typedef
    struct Stack
    {
    STKDataType* a;
    int top;
    int capacity;
    }STK;
    void STKInit(STK* pstk,
    int n = 4
    )
    ;
    //这里稍微注意一下;由于单次定义原则所以我们一般在函数声明时指定缺省参数
    ------------------------------Stack.cpp-----------------------------
    #
    include "Stack.h"
    void STKInit(STK* pstk,
    int n) //缺省参数不能声明和定义同时给,所以定义时候我们没有给缺省参数
    {
    assert(pstk)
    ;
    assert(n >
    0
    )
    ;
    pstk->a = (STKDataType*
    )malloc(n *
    sizeof(STKDataType)
    )
    ;
    pstk->top = 0
    ;
    pstk->capacity = n;
    }
    ------------------------------Test.cpp-----------------------------
    #
    include "Stack.h"
    int main(
    )
    {
    STK s1;
    STKInit(&s1)
    ;
    //确定知道要插入1024个数据, 初始化时一把开好, 避免扩容
    STK s2;
    STKInit(&s2, 1024
    )
    ;
    return 0
    ;
    }

通过阅读上面的代码,小伙伴们你们是不是已经体会到缺省参数的好处了呢?

是不是在之前无论我们是否知道栈的初始空间的大小,栈的空间都要经过从4、8、16、32、……、二倍二倍的进行扩容的一个过程。

但是如果我们事先已经知道了栈的存储空间,例如是1024字节,那么是不是我们可以在定义栈的时候就一次性的将空间开辟好,也就相当于省去了10次扩容的时间消耗对吧,所以说缺省参数是很有价值的。

---------------函数重载---------------

什么是函数重载?

函数重载(Function Overloading):是指在同一个作用域内,可以有多个同名函数,但是这些函数的参数列表(参数个数参数类型参数顺序)必须不同 。

  • 编译器根据函数调用时提供的实参信息来确定具体调用哪个函数。
  • 函数的返回值类型不能作为函数重载的依据。

满足函数重载的条件有哪些?

规则说明正确案例
参数个数不同在同一作用域内,同名函数的参数个数必须有差异,这样编译器才能根据调用时传入参数的数量来区分调用哪个函数int add(int a, int b);
int add(int a, int b, int c);
参数类型不同同名函数的参数类型至少有一个不一样,编译器依据实参类型匹配对应的函数版本int add(int a, int b);
double add(double a, double b);
参数顺序不同同名函数的参数顺序不一致,也可构成重载,编译器会根据参数传入顺序判断调用哪个函数void print(int num, char ch);
void print(char ch, int num);

注意事项仅返回值类型不同不能构成函数重载

在这里插入图片描述

三种函数重载的示例:

/*-------------------------三种函数重载的案例-------------------------*/
#
include <iostream>
  using
  namespace std;
  /*------------------重载案例1:参数个数不同------------------*/
  /**
  * @brief 无参版本函数
  */
  void f(
  )
  {
  cout <<
  "调用 f()" << endl;
  }
  /**
  * @brief 单参版本函数
  * @param a 整型参数
  */
  void f(
  int a)
  {
  cout <<
  "调用 f(int a)" << endl;
  }
  /*------------------重载案例2:参数类型不同------------------*/
  /**
  * @brief 函数重载示例2:参数类型不同
  * @param left 第一个整数操作数
  * @param right 第二个整数操作数
  * @return 两数之和
  * @note 与下面的double版本构成重载
  */
  int add(
  int left,
  int right)
  {
  cout <<
  "调用 int add(int left, int right)" << endl;
  return left + right;
  }
  /**
  * @brief 函数重载示例2:参数类型不同
  * @param left 第一个浮点数操作数
  * @param right 第二个浮点数操作数
  * @return 两数之和
  * @note 与上面的int版本构成重载
  */
  double add(
  double left,
  double right)
  {
  cout <<
  "调用 double add(double left, double right)" << endl;
  return left + right;
  }
  /*------------------重载案例3:参数顺序不同------------------*/
  /**
  * @brief 参数顺序:int在前,char在后
  */
  void f(
  int a,
  char b)
  {
  cout <<
  "调用 f(int a, char b)" << endl;
  }
  /**
  * @brief 参数顺序:char在前,int在后
  */
  void f(
  char b,
  int a)
  {
  cout <<
  "调用 f(char b, int a)" << endl;
  }
  int main(
  )
  {
  // 测试参数个数不同的重载
  f(
  )
  ;
  // 调用无参版本
  f(10
  )
  ;
  // 调用int参数版本
  // 测试类型不同的重载
  add(10
  , 20
  )
  ;
  // 调用int版本
  add(10.1
  , 20.2
  )
  ;
  // 调用double版本
  // 测试参数顺序不同的重载
  f(10
  , 'a'
  )
  ;
  // 调用(int, char)版本
  f('a'
  , 10
  )
  ;
  // 调用(char, int)版本
  return 0
  ;
  }

在这里插入图片描述

还有哪些特殊的函数重载?

规则说明正确案例
const 修饰符差异函数参数的顶层 const(如const int)不能构成重载,但底层 const(如int* const vs const int*)可以void func(int* ptr);
void func(const int* ptr);
(底层 const 不同)
指针类型差异函数参数为不同类型的指针(如普通指针与常量指针)可以构成重载void func(int* ptr);
void func(int* const ptr);
(顶层 const 不同)
引用与值类型差异函数参数为值类型和引用类型可以构成重载void func(int a);
void func(int& a);
成员函数的 const 重载类的成员函数是否为 const 可以构成重载,用于区分对常量对象和非常量对象的调用int getValue() const;
int getValue();
模板参数实例化不同函数模板通过不同的模板参数实例化可以生成重载函数template<typename T> void func(T a);
template<> void func(int a);(显式特化)

注意:对于初学C++的萌新这里可以先作为了解,不作为掌握的内容。

函数重载和缺省参数有什么不同?

函数重载与缺省参数二者都能实现灵活调用函数,但原理不同。

  • 函数重载:是多个同名函数,参数列表不同。

  • 缺省参数:是一个函数,参数有默认值。

特性缺省参数函数重载
实现方式单一函数扩展参数多个同名函数
适用场景参数有明确默认值参数类型/数量有本质差异
灵活性行为一致,仅参数值变化可完全改变函数行为

在这里插入图片描述

posted @ 2025-07-18 19:14  yfceshi  阅读(23)  评论(0)    收藏  举报