C++ 基础入门篇(一):命名空间,输入&输出,缺省参数,函数重载

概述: 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. 区分展开头文件和展开命名空间

这是初学者最容易混淆的一对概念。两者都跟 #includeusing 有关,但作用对象完全不同:

  • 展开头文件(#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();//这里就会造成调用歧义
}

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

相关推荐
Zixhy1 小时前
多点巡检机器人项目技术总结
linux·c++·机器人·自动驾驶
Pointer Pursuit3 小时前
C++11特性(二)
前端·c++·算法
tudousisi2224 小时前
CSP 第3题训练 Day 5|CSP202509C HTTP 头信息|Blog A
c++
闻缺陷则喜何志丹5 小时前
【动态规划】P10726 [GESP202406 八级] 空间跳跃|普及+
c++·算法·动态规划·洛谷
Chester_19995 小时前
CSP202312C.树上搜索
开发语言·数据结构·c++·蓝桥杯·stl
见叶之秋6 小时前
【C++】String类的使用(含模拟实现)
java·开发语言·c++
wuminyu7 小时前
Linux内核线程调度与虚拟线程调度机制系统级深度剖析
java·linux·c语言·jvm·c++
xxwxx__8 小时前
深入理解 C++ 继承:从基础语法到底层原理全解析
c++
键盘会跳舞8 小时前
【C++多线程】多线程性能调优:伪共享、缓存行对齐与吞吐优化
c++·多线程·多线程调优