第二章 C++对C的核心拓展

目录

[一、 C++命名空间](#一、 C++命名空间)

[1.1 命名空间的定义](#1.1 命名空间的定义)

[1.2 命名空间成员的访问](#1.2 命名空间成员的访问)

[1.3 命名空间只能全局范围内定义(以下错误写法)](#1.3 命名空间只能全局范围内定义(以下错误写法))

[1.4 命名空间的嵌套定义](#1.4 命名空间的嵌套定义)

[1.5 可以将命名空间的声明和实现分开](#1.5 可以将命名空间的声明和实现分开)

[1.6 命名空间别名](#1.6 命名空间别名)

[二、 引用](#二、 引用)

[2.1 引用的概念](#2.1 引用的概念)

[2.2 引用的使用](#2.2 引用的使用)

2.3引用的本质

[2.4 引用作为函数的参数](#2.4 引用作为函数的参数)

[2.5 引用的意义](#2.5 引用的意义)

[2.6 引用的使用场景及实际应用](#2.6 引用的使用场景及实际应用)

[三、 内联函数](#三、 内联函数)

[3.1 内联函数的基本概念](#3.1 内联函数的基本概念)

[3.2 注意事项](#3.2 注意事项)

[四、 函数的默认参数](#四、 函数的默认参数)

[五、 函数重载](#五、 函数重载)

[5.1 函数重载概念](#5.1 函数重载概念)

[5.2 实现函数重载的条件](#5.2 实现函数重载的条件)

[5.3 函数重载的实现原理](#5.3 函数重载的实现原理)


C++作为C的超集,核心拓展围绕"解决C语言痛点、提升代码安全性与工程性"展开,重点新增面向对象基础、类型安全机制及语法优化:

一、 C++命名空间

创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。在C语言中,可以通过static关键字来使得名字仅在本编译单元内可见,在C++中我们将通过命名空间来控制对名字的访问。

  1. 在C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。

  2. std是C++标准命名空间,C++标准程序库中的所有标识符都被定义在std中,比如标准库中的类iostreamvector等都定义在该命名空间中,使用时要加上using声明(using namespace std)或using指示(如std::cout)。

1.1 命名空间的定义

cpp 复制代码
namespace 名称
{
    // 定义变量、函数、类型、对象...
}

1.2 命名空间成员的访问

使用作用域操作符::,格式为:空间名::成员

cpp 复制代码
#include <iostream>
using namespace std;
namespace A{
    int a = 10;
} //空间A
namespace B{
    int a = 20;
} //空间B
int main(){
    cout << "A::a : " << A::a << endl;
    cout << "B::a : " << B::a << endl;
    return 0;
}

1.3 命名空间只能全局范围内定义(以下错误写法)

cpp 复制代码
void test(){
    namespace A{
        int a = 10;
    }
    namespace B{
        int a = 20;
    }
    cout << "A::a : " << A::a << endl;
    cout << "B::a : " << B::a << endl;
}

1.4 命名空间的嵌套定义

cpp 复制代码
namespace A{
    int a = 10;
    namespace B{
        int a = 20;
    }
}
void test(){
    cout << "A::a : " << A::a << endl;
    cout << "A::B::a : " << A::B::a << endl;
}

1.5 可以将命名空间的声明和实现分开

在test.h中声明命名空间:

cpp 复制代码
#ifndef TEST_TEST_H
#define TEST_TEST_H
namespace MySpace{
    void func1();
    void func2(int);
}
#endif //TEST_TEST_H

在test.cpp中实现命名空间:

cpp 复制代码
#include<iostream>
#include"test.h"
using namespace std;
void MySpace::func1(){
    cout << "MySpace::func1" << endl;
}
void MySpace::func2(int x){
    cout << "MySpace::func2 : " << x << endl;
}

在main.cpp中使用命名空间:

cpp 复制代码
#include <iostream>
#include"test.h"
using namespace std;
int main(){
    std::cout << "Hello, World!" << std::endl;
    MySpace::func1();
    MySpace::func2(100);
    return 0;
}

1.6 命名空间别名

cpp 复制代码
namespace veryLongName{
    int a = 10;
    void func(){
        cout << "hello namespace" << endl;
    }
}
void test(){
    namespace shortName = veryLongName;
    cout << "veryLongName::a : " << shortName::a << endl;
    veryLongName::func();
    shortName::func();
}

二、 引用

2.1 引用的概念

  1. 引用可以看作一个已定义变量的别名。

  2. 引用的语法:

    cpp 复制代码
    Type& name = var;
  3. 注意:

    1. &在此不是求地址运算,而是起标识作用。

    2. 类型标识符是指目标变量的类型。

    3. 必须在声明引用变量时进行初始化。

    4. 引用初始化之后不能改变。

    5. 不能有NULL引用,必须确保引用是和一块合法的存储单元关联。

2.2 引用的使用

cpp 复制代码
void test01(){
    int a = 10;
    // 给变量a取一个别名b
    int& b = a;
    cout << "a : " << a << endl;
    cout << "b : " << b << endl;
    cout << "------------" << endl;
    // 操作b就相当于操作a本身
    b = 100;
    cout << "a : " << a << endl;
    cout << "b : " << b << endl;
    cout << "------------" << endl;
    // 一个变量可以有n个别名
    int& c = a;
    c = 200;
    cout << "a : " << a << endl;
    cout << "b : " << b << endl;
    cout << "c : " << c << endl;
    cout << "------------" << endl;
    // a、b、c的地址都是相同的
    cout << "a : " << &a << endl;
    cout << "b : " << &b << endl;
    cout << "c : " << &c << endl;
}

// 2.使用引用注意事项
void test02(){
    // int& ref; // 报错:必须初始化引用
    // 1)引用必须初始化
    // 2)引用一旦初始化,不能改变引用
    int a = 10;
    int b = 20;
    int& ref = a;
    ref = b; // 不能改变引用,此处是将b的值赋给ref(即a)
    cout << a << endl; // a的值变为20
}

2.3引用的本质

  1. 引用的本质在C++内部实现是一个常指针:Type& ref = val; 等价于 Type* const ref = &val;

  2. C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。

    1. aRef = 20; 内部发现aRef是引用,自动转换为:*aRef = 20;

    2. int& aRef = a; 自动转换为 int* const aRef = &a;,这也能说明引用为什么必须初始化。

  3. 验证引用占用内存空间大小:

cpp 复制代码
struct Teacher{
    int &a;
    int &b;
};
int main(){
    cout << sizeof(Teacher) << endl; // 输出8(32位系统)或16(64位系统),与指针占用空间一致
    return 0;
}

2.4 引用作为函数的参数

普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。

cpp 复制代码
#include<iostream>
using namespace std;
typedef struct Teacher {
    char name[64];
    int age;
}Teacher;

void printfT(Teacher *pT) {
    cout << pT->age << endl;
}

// pT是t1的别名,操作pT相当于修改t1
void printfT2(Teacher &pT) {
    pT.age = 33;
}

int main() {
    Teacher t1;
    t1.age = 35;
    printfT(&t1); // 输出35
    printfT2(t1); // pT是t1的别名,修改pT.age即修改t1.age
    printfT(&t1); // 输出33
    return 0;
}

2.5 引用的意义

  1. 引用作为其它变量的别名而存在,因此在一些场合可以代替指针。

  2. 引用相对于指针来说具有更好的可读性和实用性。

cpp 复制代码
int swap1(int &a, int &b) {
    int t = a;
    a = b;
    b = t;
    return 0;
}

int swap2(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
    return 0;
}

2.6 引用的使用场景及实际应用

如果引用的对象是普通的数据类型,其实与指针差不多;引用在函数形参为对象的引用以及函数返回值为对象的引用上使用非常广泛。

三、 内联函数

在C中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。但宏替代函数可能出现问题:

cpp 复制代码
#define ADD(x,y) x+y
int main() {
    int ret1 = ADD(10,20)*10; // 希望的结果是300
    cout << "ret1 : " << ret1 << endl; // 实际输出210
    return 0;
}

原因:宏定义在预处理的时候是文本替换,ret1 = ADD(10,20)*10; 被展开后为 ret1 = 10+20*10;,按照运算符优先级计算结果为210。

  1. 为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,C++引入了内联函数(inline function)。

  2. 内联函数的优势:继承宏函数的效率(无函数调用开销),同时具备普通函数的参数、返回值类型安全检查功能,还可作为成员函数。

3.1 内联函数的基本概念

  1. 在C++中,预定义宏的功能可通过内联函数实现,内联函数本身是一个真正的函数,具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,无需函数调用开销。因此推荐使用内联函数替代宏。

  2. 内联函数的声明:在普通函数(非成员函数)前面加上inline关键字,且必须将函数体和声明结合在一起,否则编译器将其作为普通函数对待。

    1. 无效写法:inline void func(int a);(仅声明,无函数体)

    2. 有效写法:inline int func(int a) { return ++a; }

  3. 编译器会检查内联函数的参数列表使用是否正确,并对返回值进行必要的类型转换,这些是预处理器无法完成的。

3.2 注意事项

  1. C++中推荐使用内联函数替代宏代码片段。

  2. 内联函数在最终生成的代码中没有函数定义,C++编译器直接将函数体插入在函数调用的地方,因此没有普通函数调用时的额外开销(压栈、跳转、返回)。

  3. 内联函数的作用域仅在定义的文件内。若在a.cpp中定义了inline int func(int a) { return ++a; },在b.cpp中需要调用该函数时,需在b.cpp中重新定义该内联函数。

  4. inline只是对编译器的一个内联请求,C++内联编译有一些限制,以下情况编译器可能不将函数进行内联编译:

    1. 存在任何形式的循环语句。

    2. 存在过多的条件判断语句。

    3. 函数体过于庞大。

    4. 对函数进行取址操作。

  5. 编译器可能会对未声明为内联的小而简单的函数进行内联编译,内联最终由编译器决定。

四、 函数的默认参数

C++在声明函数原型时可为一个或多个参数指定默认的参数值,当函数调用时未指定该参数值,编译器会自动用默认值代替。

cpp 复制代码
void TestFunc01(int a = 10, int b = 20) {
    cout << "a + b = " << a + b << endl;
}
int main() {
    // 1. 未传参数,使用默认参数
    TestFunc01(); // 输出30
    // 2. 传一个参数,第二个参数使用默认值
    TestFunc01(100); // 输出120
    // 3. 传入两个参数,使用传入的参数
    TestFunc01(100, 200); // 输出300
    return EXIT_SUCCESS;
}

函数的默认参数需从左向右设置,若一个参数设置了默认值,那么该参数之后的所有参数都必须设置默认值。

若函数声明和函数定义分开写,默认参数只能在声明中设置,定义中不能重复设置。

cpp 复制代码
// 1. 形参b设置默认值,后续的形参c也必须设置默认值
void TestFunc02(int a, int b = 10, int c = 10) {}

// 2. 函数声明设置默认参数,定义中不设置
void TestFunc03(int a = 0, int b = 0);
void TestFunc03(int a, int b) {}

五、 函数重载

5.1 函数重载概念

函数重载(Function Overload):用同一个函数名定义的不同函数,当函数名和不同的参数搭配时,函数的含义不同。

5.2 实现函数重载的条件

  • 同一个作用域。

  • 参数个数不同。

  • 参数类型不同。

  • 参数顺序不同。

cpp 复制代码
void func(){}
void func(int x){}
void func(char x){}
void func(int x, int y){}
void func(int x, char y){}
void func(char y, int x){}

5.3 函数重载的实现原理

  1. 编译器在编译程序时,会将变量和函数转换为符号并存储在符号表中。

  2. 查看函数符号的方法:

    1. 编译命令:g++ -c main.cpp(生成.o目标文件)

    2. 查看符号表命令:nm main.o

  3. 示例符号(g++编译器):

    1. void func(char)_Z4funcc

    2. void func(int, char)_Z4funcic

    3. void func(int)_Z4funci

    4. void func(int, int)_Z4funcii

    5. void func()_Z4funcv

  4. 原理:g++编译器将函数转换为符号时,会结合函数名和形参类型(参数个数、类型、顺序),因此同名不同参数的函数会生成不同的符号,从而实现重载。而C语言编译器仅根据函数名生成符号,无法实现函数重载。

相关推荐
君义_noip1 小时前
信息学奥赛一本通 2134:【25CSPS提高组】道路修复 | 洛谷 P14362 [CSP-S 2025] 道路修复
c++·算法·图论·信息学奥赛·csp-s
liulilittle2 小时前
OPENPPP2 Code Analysis One
网络·c++·网络协议·信息与通信·通信
Morwit2 小时前
*【力扣hot100】 647. 回文子串
c++·算法·leetcode
AI视觉网奇2 小时前
audio2face mh_arkit_mapping_pose_A2F 不兼容
笔记·ue5
天赐学c语言2 小时前
1.7 - 删除排序链表中的重要元素II && 哈希冲突常用解决冲突方法
数据结构·c++·链表·哈希算法·leecode
w陆压2 小时前
12.STL容器基础
c++·c++基础知识
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [fs]super
linux·笔记·学习
日更嵌入式的打工仔3 小时前
单片机基础知识:内狗外狗/软狗硬狗
笔记·单片机
KhalilRuan3 小时前
数据结构与算法-笔记
笔记
龚礼鹏3 小时前
Android应用程序 c/c++ 崩溃排查流程二——AddressSanitizer工具使用
android·c语言·c++