概述: C++是C++之父本贾尼博士在C语言的基础上创造出来的一种更为完善的计算机语言,解决了C语言的很多漏洞与缺陷。是现代社会生活中最重要的计算机语言之一,C++从70年代末诞生,到1998年首次标准化,再到2011年开启"现代C++"时代,已成长为功能强大的多范式编程语言。如今,它依然是系统软件、游戏开发、嵌入式系统、高频交易等高性能领域的中流砥柱。
文章目录
- [一. 命名空间(namespace)](#一. 命名空间(namespace))
-
- [1.1 命名空间要解决的问题](#1.1 命名空间要解决的问题)
- [1.2 域](#1.2 域)
-
- [1.2.1 全局域(Global Scope)](#1.2.1 全局域(Global Scope))
- [1.2.2 函数局部域(Function Local Scope):(标准术语:块作用域 + 函数作用域)](#1.2.2 函数局部域(Function Local Scope):(标准术语:块作用域 + 函数作用域))
- [1.2.3命名空间域(Namespace Scope)](#1.2.3命名空间域(Namespace Scope))
- [1.2.4 类域(Class Scope):(成员变量、成员函数、嵌套类型所在的域)](#1.2.4 类域(Class Scope):(成员变量、成员函数、嵌套类型所在的域))
- [1.3 域访问限定符](#1.3 域访问限定符)
- [1.4 namespace的定义](#1.4 namespace的定义)
- [1.5 命名空间使用](#1.5 命名空间使用)
- [二 . C++ 输入&输出](#二 . C++ 输入&输出)
-
- [2.1 C++的输入输出主要解决C语言的3大问题:](#2.1 C++的输入输出主要解决C语言的3大问题:)
- [2.2 输出流和流插入](#2.2 输出流和流插入)
- [2.3 输入流和流提取](#2.3 输入流和流提取)
- [2.4 输入输出补充:](#2.4 输入输出补充:)
- [三. 缺省参数](#三. 缺省参数)
-
- [3.1 全缺省和半缺省](#3.1 全缺省和半缺省)
- [3.2 缺省参数逻辑和调用给参逻辑](#3.2 缺省参数逻辑和调用给参逻辑)
- [3.3 缺省参数的细节](#3.3 缺省参数的细节)
- [四. 函数重载](#四. 函数重载)
-
- 4.1参数类型不同
- [4.2 参数个数不同](#4.2 参数个数不同)
- [4.3 参数类型顺序不同](#4.3 参数类型顺序不同)

一. 命名空间(namespace)
1.1 命名空间要解决的问题
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
例:
cpp
#include<stdio.h>
#include<stdlib.h>
int rand=10;
int main()
{
printf("%d",rand);
return 0;
}
因为stdlib.h头文件中有rand函数(生成随机数)的定义,此时我们再定义一个rand变量并使用,就会出现编译错误。"error:重定义:以前的定义为函数"。
1.2 域
C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。
1.2.1 全局域(Global Scope)
位置:定义在所有函数、类、命名空间之外。
可见范围:从声明点开始,一直到文件末尾都有效。如果被其他文件通过 extern 声明,甚至可以跨文件可见。
生命周期:静态存储期。程序一启动就分配内存,程序结束才销毁。
核心特性(隐藏与强制访问):
- 如果局部域有同名变量,全局变量会被隐藏(优先用局部的)。
- 想强行访问被隐藏的全局变量,必须用 ::(作用域解析符)。
1.2.2 函数局部域(Function Local Scope):(标准术语:块作用域 + 函数作用域)
这是最常用、也最容易踩坑的域,实际上可以拆成两小块:
- 块作用域(Block Scope)------ 花括号 {} 内部,包括:函数体、if、for、while、switch 以及任何单独的复合语句。
生命周期:自动存储期(栈内存)。执行到声明处创建,执行出右花括号 } 时立即销毁。
隐藏规则:内层块可以隐藏外层块(包括同名的全局变量)。
陷阱:for 循环的初始化语句中声明的变量,属于循环体所在的块作用域,而非外层函数块。
- 函数作用域(Function Scope)------ 专指 goto 标签
这是C++中唯一一种不遵循"先声明后使用"的作用域。标签(如 error:)在整个函数体内任何位置都可见,哪怕标签写在函数末尾,写在开头的 goto error; 也能跳转过去。
1.2.3命名空间域(Namespace Scope)
这是除了全局域之外,最重要的大型代码隔离工具
位置:显式通过 namespace 关键字定义。
作用:解决命名冲突(就像把名字装进不同的文件夹)。
可见范围:从声明点开始,到该命名空间结束;且可以通过 using 或 :: 在外部访问。
访问方式(3种):
cpp
namespace A { int x = 10; }
// 方式1:完全限定名(最安全)
int a = A::x;
// 方式2:using 声明(引入单个名字)
using A::x;
int b = x;
// 方式3:using 指令(全部引入,慎用!容易污染)
using namespace A;
int c = x;
后面我们会详细展开这3种方法
1.2.4 类域(Class Scope):(成员变量、成员函数、嵌套类型所在的域)
位置:class 或 struct 的花括号内部。
最大的特殊性(异于函数域):成员不依赖于声明顺序!
函数局部域必须"先声明后使用",但在类域中,成员函数体内可以访问在类体后面声明的成员变量,因为编译器会先完整扫描整个类体,再编译成员函数。
cpp
class Test {
public:
void func() {
data = 100; // 合法!虽然 data 声明在后面,但类域内全可见
}
private:
int data;
};
访问权限 vs 作用域:private/protected/public 影响的是能不能访问(权限),而作用域决定的是能不能找到名字。在类域内,所有成员名字(包括私有)都是可见的,只是类外部不能访问私有成员。
同名冲突解决:当局部变量与成员变量同名时,用 this->成员名 访问成员,或者用 类名::成员名(针对静态成员)
1.3 域访问限定符
1. 访问全局域(Global Scope
当局部变量和全局变量同名时,用 :: 强制访问被隐藏的全局变量。
cpp
int value = 100; // 全局域
int main() {
int value = 200; // 局部域(隐藏了全局)
printf("%d\n", value); // 输出 200
printf("%d\n", ::value); // 输出 100(::前面为空,代表全局域)
}
2. 访问命名空间域(Namespace Scope)
用来访问特定命名空间中的成员。
cpp
namespace Star_Rail {
namespace Little_Gray {
int rand = 1;
}
}
int main() {
// 路径式访问:外层::内层::成员
printf("%d\n", Star_Rail::Little_Gray::rand);
}
3. 访问类域(Class Scope)------ 两大核心用途
这是 :: 在面向对象中最关键的应用:
- 访问类的静态成员(静态变量或静态函数,因为静态成员属于类域,不属于对象):
cpp
class Test {
public:
static int count; // 静态成员
};
int Test::count = 0; // 类外定义时必须加 Test:: 指明所属域
int main() {
printf("%d\n", Test::count); // 通过类域访问
}
- 在类外定义成员函数(告诉编译器这个函数属于哪个类)
cpp
class Student {
public:
void show(); // 声明
};
// 类外定义:前面的 Student:: 告诉编译器这是 Student 类域的成员函数
void Student::show() {
printf("Hello\n");
}
4. 访问嵌套域(多层嵌套)
当你定义了嵌套的命名空间或嵌套类时,:: 可以一层层穿透
cpp
class A {
public:
class B { // 嵌套类
public:
enum Color { Red, Green };
};
};
int main() {
A::B::Color c = A::B::Red; // 层层访问
}
注意::: 只能用于访问"域"本身拥有的名字,绝不能用于访问对象的非静态成员变量。
访问非静态成员必须用 .(对象)或 ->(指针)。

补充:编译器的访问规则:
1. 访问对象只在全局域中出现直接访问全局域
2. 访问对象在全局域和函数局部域同时出现就近访问原则,优先访问局部
3. 使用域访问限定符时直接访问域访问限定符限制区域
总结::: 是"域的身份证",用来告诉编译器:"去这个域里找名字,别去别的地方"。 之前总结的四大域(全局、局部、命名空间、类),除了局部域(因为局部变量直接可见,不需要也无法用 :: 指定),其余三个域都可以用 :: 精确锁定。
1.4 namespace的定义
方法:定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
1. namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不再冲突了。
cpp
#include <stdio.h>
#include <stdlib.h>
namespace Little_Gray
{
int rand = 10;
int Add(int left, int right) { return left + right; }
struct Node {
struct Node* next;
int val;
};
}
int main()
{
// 全局函数 ::rand 的地址
printf("%p\n", rand);
// 命名空间中的变量
printf("%d\n", Little_Gray::rand);
return 0;
}
2. namespace 不能定义在局部域(函数内)和类域中,只能定义在全局域(Global Scope)或另一个命名空间域(Namespace Scope)中(即支持嵌套)
cpp
#include <stdio.h>
namespace Star_Rail
{
namespace Little_Gray // 嵌套命名空间
{
int rand = 1;
int Add(int left, int right) { return left + right; }
}
namespace Dan_Heng
{
int rand = 2;
int Add(int left, int right) { return (left + right) * 10; }
}
}
int main()
{
// 必须通过完整路径访问
printf("%d\n", Star_Rail::Little_Gray::rand); // 输出 1
printf("%d\n", Star_Rail::Dan_Heng::rand); // 输出 2
printf("%d\n", Star_Rail::Little_Gray::Add(1, 2)); // 输出 3
printf("%d\n", Star_Rail::Dan_Heng::Add(1, 2)); // 输出 30
return 0;
}
3. 多个文件可以只创建一个命名空间,会认为是一个命名空间。
场景一: "拆分声明"------多文件合并是安全的(日常写代码)
这是标准库和大型项目的常规做法。假设我们有一个项目,分三个文件来扩展同一个命名空间 MyLib:
- math.h(数学模块)
cpp
namespace MyLib {
int add(int a, int b); // 声明,不分配内存
}
- string.h(字符串模块)
cpp
namespace MyLib {
void print(const char* s); // 声明,不分配内存
}
- main.cpp(主程序)
cpp
#include "math.h"
#include "string.h"
int main() {
MyLib::add(1,2); // 编译器在合并后的 MyLib 域里找到了声明
MyLib::print("hello");
}
编译链接结果:完美通过,编译器会把这两个头文件中的 namespace MyLib 合并成一个逻辑域。这里绝对安全。
场景二:"拆分定义"------多文件合并是危险的(初学者常犯错误)
如果你尝试在两个不同的 .cpp 文件里,给合并后的 MyLib 域里填充实体(变量定义或函数实现),就会引爆链接器炸弹:
- math.cpp(数学模块)
cpp
#include "math.h"
namespace MyLib {
int add(int a, int b) { return a + b; } // 定义函数实现,占用代码段
int count = 0; // 定义全局变量,占用数据段
}
- string.cpp(字符串模块)
cpp
#include "string.h"
namespace MyLib {
void print(const char* s) { printf("%s", s); } // 定义函数实现
int count = 0; // ⚠️ 红线:又定义了一个 MyLib::count!
}
总结:多个文件里的同名 namespace 确实会合并成一个域,编译器对此百分百赞成;但链接器(负责合并物理代码)坚决反对在这个合并的域里出现两份同名的'实体(变量/函数实现)。
1.5 命名空间使用
编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
错误示范:
cpp
#include<stdio.h>
namespace N
{
int a = 0;
int b = 1;
}
int main()
{
//编译报错:error C2065: "a": 未声明的标识符
printf("%d\n", a);
return 0;
}
1. 作用域解析运算符 ::(最安全、最推荐)
直接通过 命名空间::成员 显式指定路径。不污染当前域,绝对精确。
项目中推荐这种方式。
cpp
#include <stdio.h>
#include <stdlib.h>
namespace Little_Gray
{
int rand = 10;
int Add(int a, int b)
{
return a + b;
}
}
int main()
{
int x = Little_Gray::rand; // 变量
int sum = Little_Gray::Add(1, 2); // 函数
return 0;
}
2. using 声明(using declaration)
将命名空间中的特定成员引入当前作用域。精确导入,污染受控。
项目中经常访问的不存在冲突的成员推荐这种方式。
cpp
#include <stdio.h>
#include <stdlib.h>
namespace Little_Gray
{
int rand = 10;
int Add(int a, int b)
{
return a + b;
}
}
int main()
{
using Little_Gray::rand; // 把 rand 引入当前局部域
using Little_Gray::Add; // 把 Add 引入当前局部域
printf("%d\n", rand); // 现在可以直接使用了
printf("%d\n", Add(1, 2));
return 0;
}
3. using namespace 指令(using directive)
将命名空间中的所有成员一股脑导入当前作用域。全量导入,高度污染(不推荐在头文件中使用)。
项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
cpp
#include <stdio.h>
#include <stdlib.h>
namespace Little_Gray
{
int rand = 10;
int Add(int a, int b)
{
return a + b;
}
}
int main()
{
using namespace Little_Gray; // 把 Little_Gray 中的所有名字暴露到当前域
printf("%d\n", rand); // 可以直接用
printf("%d\n", Add(1, 2));
return 0;
}
4. 区分展开头文件和展开命名空间
这是初学者最容易混淆的一对概念。两者都跟 #include 和 using 有关,但作用对象完全不同:
-
展开头文件(
#include <xxx.h>):把整个文件的内容"复制粘贴"进来,属于预处理阶段。 -
展开命名空间(
using namespace xxx;):把某个域里的名字"暴露"到当前域,属于编译阶段。
一句话记忆:#include 管的是"文件有没有",using namespace 管的是"名字找不找得到"。
下面用一个对比实验说明:
cpp
#include <stdio.h>
namespace Little_Gray {
int value = 10;
}
int main() {
// 编译报错:'value' was not declared in this scope
// 编译器只在全局域找 value,不会主动去 Little_Gray 域里找
printf("%d\n", value);
return 0;
}
对比:
cpp
#include <stdio.h>
namespace Little_Gray {
int value = 10;
}
using namespace Little_Gray; // 【关键】把 Little_Gray 里的名字暴露到当前全局域
int main() {
//编译通过,现在编译器能在 Little_Gray 域里找到 value 了
printf("%d\n", value);
return 0;
}
两者的本质区别总结:
| 对比项 | 展开头文件(#include) | 展开命名空间(using namespace) |
|---|---|---|
| 作用阶段 | 预处理阶段(编译前) | 编译阶段 |
| 作用对象 | 文件(把整个文件内容搬进来) | 域(把域里的名字暴露出来) |
| 是否引入实体 | 是,头文件里的声明/定义都被引入 | 否,只是让名字"可见可查找" |
| 典型风险 | 头文件重复包含(需 #pragma once 或头文件守卫) | 名字污染(同名冲突) |
| 记忆口诀 | 管"文件有没有" | 管"名字找不找得到" |
补充一个高频坑:#include 只是把 rand 的声明搬进来了,但 rand 住在 std 这个命名空间里。如果不 using namespace std;,编译器依然找不到它------这就是为什么很多初学者写了 #include <stdlib.h> 却依然报"未声明的标识符"。
5. 工程建议和补充:
1. 在头文件(.h)中,绝对不要写 using namespace std;因为头文件会被多个 .cpp 包含,强行展开 std 会导致所有包含该头文件的文件都被"污染",极易引发命名冲突。
2. 在源文件(.cpp)中,using namespace std; 可以写,但不推荐
更推荐的做法是:用 std:: 显式限定(最安全)或用 using std::cout; 只导入你需要的单个名字(受控污染)
3. #include 是"硬拷贝",using 是"软链接"
前者在预处理阶段就把代码物理复制过来了;后者只是告诉编译器"去那个域里找找看",并没有复制任何代码。
4. 与标准库 < iostream > 的对应关系
如果你在学习 C++ 标准库时看到这样的代码:
cpp
#include <iostream> // 展开头文件:把 iostream 文件内容拷贝进来
using namespace std; // 展开命名空间:把 std 域里的名字暴露到当前域
int main() {
cout << "Hello"; // 现在能找到 cout 了(它在 std 域里)
return 0;
}
如果只 #include 而不 using namespace std;,就必须写成:
cpp
#include <iostream>
int main() {
std::cout << "Hello"; // 每次都要加 std:: 前缀
return 0;
}
5. 命名空间域中的变量生命周期仍然是全局的,它本质上还是全局变量。只是在查找的时候受到了限制:命名空间域中的变量,生命周期和链接属性本质上与全局变量完全一致(静态存储期 + 外部链接性)。命名空间并没有改变变量的物理存储方式,它改变的仅仅是'名字的可见范围(查找规则)'------即把变量从'全局默认可见'变成了'必须通过域限定符或using指令才能访问。
6. 命名空间后面没有分号。using展开语句后面有分号。
7. 一个域能定义很多东西:变量、函数、类型、模板都可以。
8. 命名空间实际上可以反复无限嵌套的,但是不建议这么用。
二 . C++ 输入&输出
使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的支持自定义类型对象的输入输出。
2.1 C++的输入输出主要解决C语言的3大问题:
1. 类型不安全(最致命的缺陷)
在 C 语言中,printf 和 scanf 靠格式化占位符(%d, %s, %f)来告诉编译器数据的类型。如果你写的占位符和实际变量类型不匹配,编译器不报错,但程序运行时会产生未定义行(UB),比如乱码或直接崩溃。
2. 不可扩展(无法处理自定义类型)
C 语言的 printf 只认识基本类型(int, float, char * )。如果你定义了一个结构体 struct Student{ char name20; int age; };,你想打印它,必须手动把每个成员拆开写,非常繁琐且无法封装。
3. 命名空间污染(全局域暴露)
C 语言的 printf 和 scanf 被定义在 <stdio.h> 中,它们直接被放在全局域(::)里。如果你的项目里有个业务函数也叫 printf,或者定义了一个全局变量 int printf;,就会发生重命名冲突
2.2 输出流和流插入
什么是"流"(Stream)?
流就是数据在内存和外部设备(如屏幕、文件)之间传输的管道。
流插入(Insertion):<< 运算符在 I/O 语境下叫流插入运算符,意思就是把右边的数据"插入"到左边的输出流管道里。
形象比喻:std::cout 是一个水管(输出流),<< 是把数据(水)倒进水管的动作。数据顺着水管流到屏幕上
-
输出流介绍:std::cout 是 C++ 标准库中 std::ostream 类的一个全局对象,主要面向窄字符的标准输出流,它住在 std 命名空间里。所以必须通过 std::cout 或 using namespace std; 才能找到它。生命周期:它是全局对象,程序启动时构造,程序结束时析构(静态存储期)。这正是之前总结的"命名空间里的变量本质是全局,只是查找受限"的实战体现。
-
输出流的使用:
cpp
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl; // 输出到屏幕
return 0;
}
-
流插入运算符(<<)介绍:<< 被称为流插入运算符(或左移运算符的重载),用于将数据"插入"到输出流中。它能够处理基本类型(int、double、char*等),并支持链式调用(连续插入)。
-
流插入运算符的使用:
cpp
int age = 25;
std::cout << "Age: " << age << " years old." << std::endl;
补充:
1. 无所谓格式并且可以随意多的插入,这是C++好用的地方。如代码:
cpp
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
2. 插入" "表示空格,插入endl表示换行加刷新缓冲区。(不要忘记endl被定义在std命名空间里面)
换行的另外几种方法(基于C++兼容C):
cpp
cout << a << " " << b << " " << c << "\n" << '\n' << endl;
2.3 输入流和流提取
-
输入流(Input Stream)介绍:输入流是 std::istream 类的对象,用于从外部设备(键盘、文件等)读取数据。标准库预定义的主要输入流对象是:std::cin:标准输入流(通常对应键盘),带缓冲区。
-
输入流的使用:
cpp
#include <iostream>
int main() {
int age;
std::cin >> age; // 从键盘读取一个整数
return 0;
}
- 流提取运算符(>>):>> 被称为流提取运算符(或右移运算符的重载),它将数据从输入流中"提取"出来,并格式化为指定变量的类型
核心行为特点:
1. 跳过前导空白:默认情况下,>> 会自动跳过输入流中的空白字符(空格、制表符 \t、换行符 \n 等),直到遇到第一个非空白字符。
2. 遇到空白停止:对于字符串(char * 或 std::string),它读取到空白字符时停止。
3. 返回流引用:返回 std::istream&,因此支持链式提取。
4. cin后面不能用endl!
cpp
int a, b;
std::cin >> a >> b; // 输入: 10 20 (或换行输入)
// 等价于 (std::cin >> a) >> b;
2.4 输入输出补充:
1. IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++IO流的用法,后面我们会有专门的一个章节来细节讲述IO流库。
2. 使用注意:cout/cin/endl等都属于C++标准库,C++标准库都放在一个std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们
3. 一般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std
4. 精度控制:C++可以控制小数点,需要精度控制函数:调用cout这个对象------>.调用精度控制函数precision

但是C++兼容C,所以可以使用C语言的那一套更加方便的方法
cpp
#include <iostream>
int main()
{
double a=2.23333;
printf("%.2lf",a);
return 0;
}
5. 提升效率小技巧:C++为了兼容C语言,在一些方面需要付出一些代价。如缓冲区绑定,缓冲区多次刷新。影响性能。在高IO需求的情况下可能不通过。
解决办法一:直接使用printf和scanf。
解决办法二:加入以下三行代码,具体什么原理以后会讲
cpp
#include<iostream>
using namespace std;
int main()
{
// 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中加上以下3⾏代码
// 可以提⾼C++IO效率
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
三. 缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)
cpp
#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func();//没有传参时,使⽤参数的默认值
Func(10);//传参时,使⽤指定的实参
return 0;
}
在函数形参的后面加上=0默认值,如果我们显式传了实参那么就使用指定的实参,如果没有传递任何参数,就使用这个缺省参数。
3.1 全缺省和半缺省
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
cpp
#include <iostream>
using namespace std;
//全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
//半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1,2);
Func1(1,2,3);
Func2(100);
Func2(100, 200);
Func2(100, 200, 300);
return 0;
}
3.2 缺省参数逻辑和调用给参逻辑
1. 设置默认值(设参逻辑):
- 默认值必须从右边开始连续地给。比如:func(a, b, c=4, d = 5) 或 func(a, b=2, c=4, d = 5) 是对的。func(a=1, b, c=4, ) 是错的,因为跳过了 b。
2. 传参数调用(调用逻辑):
- 传参数的时候,从左边开始一个个给。比如:func(1, 2) 就是给 a 和 b 赋值,c 和 d 用默认值。你想给 d 赋值,就必须先把 a, b, c 都给了(或者 a, b 用默认值),不能跳着给。
3.3 缺省参数的细节
1. 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值(如果只有函数定义,那就在函数定义处给缺省参数)。
这是因为声明和定义都给了,如果不一样会存在歧义
2. 方便传递参数
情况一,不知道要开辟多少空间:直接给一个缺省参数,不用去管n传递多少。
情况二,已经知道要插入1000个数据:这个时候直接传递n为1000。否则------可能不可避免要反复开辟空间(影响效率)。
两者配合起来------达到了"不知道就不传递,知道就传递"的效果。
cpp
// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n = 4);
// Stack.cpp
#include"Stack.h"
//缺省参数不能声明和定义同时给
void STInit(ST* ps, int n)
{
assert(ps && n > 0);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
//test.cpp
#include"Stack.h"
int main()
{
ST s1;
STInit(&s1);
//确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容
ST s2;
STInit(&s2, 1000);
return 0;
}
四. 函数重载
C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同一作用域中出现同名函数的。
4.1参数类型不同
cpp
#include<iostream>
using namespace std
// 参数类型不同
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;
}
4.2 参数个数不同
cpp
#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;
}
4.3 参数类型顺序不同
本质上还是类型不同
cpp
#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. 返回值不同不能作为重载条件,因为调用时也无法区分
cpp
void fxx()
{}
int fxx()
{
return 0;
}
2. 不建议缺省参数和重载共同使用
cpp
// 下⾯两个函数构成重载
// 但是调⽤f()时,会报错,存在歧义,编译器不知道⽤谁
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
int main()
{
f1(1);//显式传参没有问题
f2();//这里就会造成调用歧义
}

如果内容对你有帮助,不要忘了三连支持哦。