跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 核心特性详解:函数重载、引用、内联函数、auto 与 nullptr

C++ 核心特性涵盖函数重载、引用、内联函数、auto 与 nullptr。函数重载依赖参数列表差异及名称修饰机制实现多态;引用作为别名可避免拷贝开销并支持修改实参,需注意权限控制与生命周期;内联函数通过编译器优化减少调用开销,优于宏定义;auto 关键字实现类型自动推导简化代码;nullptr 提供类型安全的空指针表示。掌握这些基础特性有助于理解 C++ 底层机制,提升代码质量与面试表现。

忘忧发布于 2026/3/21更新于 2026/6/924 浏览
C++ 核心特性详解:函数重载、引用、内联函数、auto 与 nullptr

C++ 核心特性详解

C++ 作为一门兼容 C 语言特性且融入现代编程范式的语言,是系统开发与应用高性能应用的重要工具。本文聚焦其核心基础——引用、函数重载、nullptr、内联函数和 auto 等关键特性,帮你快速掌握入门要点,夯实基础。

函数重载

定义

重载在自然语言中意味着一个词有多种意思,可以通过上下文判断。函数重载则是 C++ 允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,函数重载对返回值没有要求,即函数重载与返回值是否相同没有影响。常用来处理实现功能类似但数据类型不同的问题。

条件

参数类型不同
#include <iostream>
using namespace std;

void print(int num) {
    cout << "void print(int num)" << endl;
}

void print(double num) {
    cout << "void print(double num)" << endl;
}

int main() {
    print(1);
    print(1.1);
    return 0;
}

函数参数类型不同,函数名字相同,构成函数重载。编译器会自动匹配类型。

参数数量不同
#include <iostream>
using namespace std;

void sum(int a, int b) {
    cout << "void sum(int a, int b)" << endl;
}

void sum(int a, int b, int c) {
    cout << "void sum(int a, int b, int c)" << endl;
}

int main() {
    sum(1, 2);
    sum(1, 2, 3);
    return 0;
}

函数参数类型相同,函数名字相同,参数个数不同,构成函数重载。编译器会自动匹配类型。

注意,如果存在缺省参数调用时可以不传参,两个函数都可以,存在调用歧义。

#include <iostream>
using namespace std;

void f() {
    cout << "f()" << endl;
}

void f(int a = 0) {
    cout << "f(int a = 0)" << endl;
}

int main() {
    f();
    return 0;
}

函数名字相同,参数个数不同,构成函数重载。但会报错,是因为缺省参数调用时可以不用传参,两个函数都可以,存在调用歧义。

参数顺序不同
#include <iostream>
using namespace std;

void process(int a, double b) {
    cout << "void process(int a, double b)" << endl;
}

void process(double a, int b) {
    cout << "void process(double a, int b)" << endl;
}

int main() {
    process(1, 1.1);
    process(1.2, 1);
    return 0;
}

函数参数类型相同,函数名字相同,参数顺序不同,构成函数重载。

注意,函数重载与函数的返回类型和参数名字是否相同没有关系。

#include <iostream>
using namespace std;

void f(int a, char b) {
    cout << "f(int a, char b)" << endl;
}

void f(int b, char a) {
    cout << "f(int b, char a)" << endl;
}

int main() {
    f(1, 'x');
    return 0;
}

函数参数类型相同,函数名字相同,函数参数名字不同,不构成函数重载,程序会报错。

C++ 支持函数重载的原因

这里以 Linux 环境为例演示 C 语言为什么不支持函数重载,C++ 为什么支持函数重载。

在 Linux 环境下,采用 gcc 编译完成后,函数名字的修饰没有发生改变,这意味着同名函数无法区分。而采用 g++ 编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中,这就是名称修饰(Name Mangling)。只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

引用

定义

引用是一项重要特性,它为变量提供了一个别名,让你能够通过这个别名来访问和操作原始变量。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

使用方法

  1. 引用是已存在变量的别名,通过在变量名前使用 & 符号声明。
  2. 引用必须在声明时初始化,且一旦初始化后不能再引用其他变量。
  3. 一个变量可以有多个引用。
  4. 引用在同一个域不能同名,在不同的域可以同名。
#include <iostream>
using namespace std;

int main() {
    int a = 0;
    int& b = a;
    int& c = b;
    int& d = a;
    cout << &a << endl;
    cout << &b << endl;
    cout << &c << endl;
    cout << &d << endl;
    b++;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    cout << d << endl;
    d++;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    cout << d << endl;
    return 0;
}

在上面代码中 d = x 是赋值,而不是引用。赋值需要开辟空间,引用是多个变量在同一份空间。

使用场景

  1. 引用传递允许函数直接修改实参的值,避免值传递的拷贝开销。(输出型参数)
#include <iostream>
using namespace std;

void Swap(int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void Swap(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int x = 0;
    int y = 1;
    cout << x << " " << y << endl;
    //Swap(&x, &y);
    Swap(x, y);
    cout << x << " " << y << endl;
    return 0;
}
  1. 通过引用传递指针与通过指针的指针传递。
#include <iostream>
using namespace std;

void Swap(int*& a, int*& b) {
    int* tmp = a;
    a = b;
    b = tmp;
}

void Swap(int** a, int** b) {
    int* tmp = *a;
    *a = *b;
    *b = tmp;
}

int main() {
    int x = 0;
    int y = 1;
    int* px = &x;
    int* py = &y;
    cout << *px << " " << *py << endl;
    Swap(px, py);
    cout << *px << " " << *py << endl;
    Swap(&px, &py);
    cout << *px << " " << *py << endl;
    return 0;
}
  1. 通过引用传递头指针,确保可以修改原指针。
#include <iostream>
using namespace std;
typedef struct ListNode {
    struct ListNode* next;
    int val;
} LTNode, *PLTNode;

void ListPushBack(PLTNode& phead, int x) {}

int main() {
    PLTNode plist = NULL;
    ListPushBack(plist, 1);
    return 0;
}
  1. 引用做返回值

传值返回

#include <iostream>
using namespace std;

int Count() {
    static int n = 0;
    n++;
    return n; //会产生临时变量,关注的是类型
}

int main() {
    int ret = Count();
    cout << ret << endl;
    return 0;
}

传值返回时一定会生成临时变量。

传引用返回

#include <iostream>
using namespace std;

int& Count() {
    static int n = 0;
    n++;
    return n;
}

int main() {
    int ret = Count();
    cout << ret << endl;
    return 0;
}

传引用返回的是 n 的别名,n 的引用。不会产生临时变量。价值:减少拷贝,提高效率。

注意,永远不要返回局部变量的引用或指针。

#include <iostream>
using namespace std;

int& Count() {
    int n = 0;
    n++;
    return n;
}

int main() {
    int ret = Count();
    cout << ret << endl;
    return 0;
}

上面的代码中,返回了局部变量的引用,即返回了局部变量 n 的地址,但是当函数返回时栈帧被销毁,n 的内存空间不再有效,此时 ret 的值是不确定的。这是因为 Count 函数结束后,没有清理栈帧,ret 的结果侥幸是正确的,如果清理栈帧,ret 的结果就是随机值。如果要安全返回则需要谨慎使用静态变量、动态内存分配或者返回值。

总结:基本任何场景都可以使用引用返回。谨慎使用引用做返回值,出了函数作用域,对象不在了,就不能使用引用返回,否则还在,就可以使用引用返回。命名空间不会影响生命周期。

传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。 引用做参数可以提高效率 (大对象/深拷贝类对象)。大对象通常指占用大量内存或包含动态分配资源(如堆内存、文件句柄等)的对象。

#include <time.h>
#include <iostream>
using namespace std;

struct A {
    int a[10000];
};

void TestFunc1(A a) {}

void TestFunc2(A& a) {}

void TestRefAndValue() {
    A a;
    //以值作为函数参数
    size_t begin1 = clock();
    for (size_t i = 0; i < 10000; ++i) {
        TestFunc1(a);
    }
    size_t end1 = clock();
    //以引用作为函数参数
    size_t begin2 = clock();
    for (size_t i = 0; i < 10000; ++i) {
        TestFunc2(a);
    }
    size_t end2 = clock();
    //分别计算两个函数运行结束后的时间
    cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
    cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main() {
    TestRefAndValue();
    return 0;
}

TestFunc1 的耗时会显著高于 TestFunc2,这是因为值传递需要频繁复制大对象,涉及大量内存操作。而引用传递只需传递指针,无需复制数据。

引用在顺序表的应用

#include <assert.h>
#include <iostream>
using namespace std;

typedef struct SeqList {
    int a[100];
    int size;
} SeqList;

int SLGet(SeqList* ps, int pos) {
    assert(pos < 100 && (pos >= 0));
    return ps->a[pos];
}

void SLModify(SeqList* ps, int pos, int x) {
    assert(pos < 100 && (pos >= 0));
    assert(ps);
    ps->a[pos] = x;
}

int& SLAt(SeqList* ps, int pos) {
    assert(pos < 100 && pos >= 0);
    return ps->a[pos];
}

int& SLAt(SeqList& ps, int pos) {
    assert(pos < 100 && pos >= 0);
    return ps.a[pos];
}

int main() {
    SeqList s;
    //方法 1
    SLModify(&s, 0, 1);
    cout << SLGet(&s, 0) << endl;
    //对第 0 个位置 +5
    int ret = SLGet(&s, 0);
    SLModify(&s, 0, ret + 5);
    //方法 2
    //引用 (读写)
    SLAt(&s, 0) = 1;
    cout << SLAt(&s, 0) << endl;
    SLAt(&s, 0) += 5;
    //方法 3
    SLAt(s, 0) = 1;
    cout << SLAt(s, 0) << endl;
    SLAt(s, 0) += 5;
    return 0;
}

在方法 1 中如果想要改变某一位置的值,需要使用 SLGet 和 SLModify 2 个函数,方法 2 只需要使用引用做返回值可以修改返回值。方法 3 是对方法 2 进行了简化。

常引用

常引用是指向常量对象的引用。它的主要作用是在引用对象时,禁止通过该引用来对对象的值进行修改,从而保证数据的安全性。

  1. 情况 1
#include <iostream>
using namespace std;

int main() {
    //不可以
    //引用过程中,权限不能放大
    const int a = 0;
    int& b = a;
    return 0;
}

const 的主要功能是对对象的不变性进行声明,即 const 变量一旦被初始化,其值就不能再被更改。引用过程中权限不能放大。

#include <iostream>
using namespace std;

int main() {
    //方法 1
    //将引用 b 也声明为 const 类型
    const int a = 0;
    const int& b = a;
    //权限保持一致或缩小
    //方法 2
    //如果需要修改值,就不要使用 const
    int c = 0;
    int& d = c;
    //c 本身是变量
    //方法 3
    //使用常量引用绑定临时对象
    const int& e = 42;
    //常量引用可绑定临时对象
    return 0;
}
  1. 情况 2
#include <iostream>
using namespace std;

int main() {
    const int c = 0;
    int d = c;
    return 0;
}

这种情况可以,这是赋值,c 拷贝给 d,没有放大权限,因为 d 的改变不影响 c。

  1. 情况 3
#include <iostream>
using namespace std;

int main() {
    int x = 0;
    int& y = x;
    //权限的平移
    const int& z = x;
    //缩小 z 的权限
    return 0;
}

在引用过程中,权限可以缩小或平移,但不能放大。上面的代码中因为 const 修饰的变量的值不能发生改变,所以 z 不能 ++,x 可以 ++。

  1. 情况 4
#include <iostream>
using namespace std;

int main() {
    double e = 1.11;
    //int& rii = e;
    //产生 int 的临时变量
    const int& rii = e;
    //临时变量具有常性
    return 0;
}

上面的代码中发生了隐式类型转换,类型转换产生的临时对象默认具有常量性,因此只能用 const 引用绑定。发生类型转换(强制类型转换,截断,隐式类型转换),函数会产生临时变量。相同类型不产生临时变量。

  1. 情况 5
#include <iostream>
using namespace std;

int func1() {
    static int x = 0;
    return x;
}

int& func2() {
    static int x = 0;
    return x;
}

int main() {
    int ret1 = func1(); //拷贝
    int& ret1 = func1();
    const int& ret1 = func1(); //权限的平移
    int& ret1 = func2(); //权限的平移
    const int& ret1 = func2(); //权限的缩小
    return 0;
}

函数返回的是临时变量,临时变量具有常性。由传值返回变为引用,这是权限的放大。

总结:在 C++ 中,引用的权限不能超过原始对象的权限。也就是说,不能通过引用去获得比原始对象更多的修改权限。常量对象只能被常量引用,而变量对象则可以被视为常量引用或者非常量引用。

引用和指针的不同点

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求。
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
  4. 没有 NULL 引用,但有 NULL 指针。
  5. 在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数 (32 位平台下占 4 个字节)。
  6. 引用自加即引用的实体增加 1,指针自加即指针向后偏移一个类型的大小。
  7. 有多级指针,但是没有多级引用。
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
  9. 引用比指针使用起来相对更安全,但引用不能完全代替指针。指针有野指针和空指针,但引用没有。

内联函数

定义

内联函数是一种特殊函数,通过编译器优化减少函数调用开销,提高执行效率。

基本语法

使用 inline 关键字声明,建议编译器将函数体直接替换调用处(类似宏展开):

inline int add(int a, int b) {
    return a + b;
}

宏函数(C 语言) 优点:不需要建立栈帧,提高调用效率。 缺点:复杂,容易出错,可读性差,不能调试。

#define Add(x, y) ((x + y) * 10)

内联函数 VS 宏

特性内联函数宏
类型安全遵循 C++ 类型检查简单文本替换,无类型检查
调试支持可调试不可调试(预处理阶段展开)
副作用无可能多次求值
代码膨胀风险由编译器决定是否展开强制展开,可能导致代码冗余

核心作用 —— 减少函数调用开销

普通函数调用流程:

  1. 保存调用位置上下文
  2. 跳转到函数地址执行
  3. 返回时恢复上下文

内联函数:直接将函数体代码复制到调用处,避免上述开销,适合短小频繁调用的函数。

编译器处理规则

inline 是建议,非强制:编译器可能忽略 inline 声明(如函数体复杂、递归调用等)。 展开条件:函数体短小,无循环、递归等复杂结构。 定义位置:内联函数必须在调用点前被定义(通常放在头文件中),否则链接时可能报错,内联函数的声明和定义不能分离。

auto 关键字

定义

auto 是一个功能强大的类型说明符,它能让编译器自动推导变量的类型,从而显著简化代码。

基本语法

auto 的基本语法是在声明变量时,用 auto 关键字替代具体的类型,编译器会根据初始化表达式自动推导变量的类型。可以使用 typeid 函数输出变量的类型。

#include <iostream>
using namespace std;

int main() {
    auto x = 42; //推导为 int
    auto y = 3.14; //推导为 double
    auto z = "hello"; //推导为 const char*
    auto& ref = x; //推导为 int&
    const auto& cref = x; //推导为 const int&
    cout << typeid(cref).name() << endl;
    cout << typeid(ref).name() << endl;
    return 0;
}

auto 在数组中的应用

#include <iostream>
using namespace std;

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        arr[i] *= 2;
    }
    //for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); p++)
    //{
    //    cout << *p << " ";
    //}
    //cout << endl;
    //范围 for
    //依次取数组中的数据赋值给 e
    //自动迭代,自动判断结束
    //数组都可以
    for (auto e : arr) {
        //e *= 2;
        cout << e << " ";
    }
    cout << endl;
    for (auto& e : arr) //auto 可以适用于任何数组,也可以使用 int
    {
        e *= 2;
    }
    for (auto e : arr) {
        cout << e << " ";
    }
    return 0;
}

关键点总结 引用是修改的关键 使用 for(auto& e : arr)(带引用)可直接修改原数组元素。 使用 for(auto e : arr)(值拷贝)只能读取数据,修改无效。

auto 的优势 自动推导元素类型,无需手动指定。 数组类型变化时无需调整,代码更健壮。

变量名随意 e 可替换为任意合法标识符。

范围 for 循环使用的条件

for 循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin 和 end 的方法,begin 和 end 就是 for 循环迭代的范围。

#include <iostream>
using namespace std;

void TestFor(int array[]) {
    for (auto& e : array) {
        cout << e << endl;
    }
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    TestFor(arr);
    return 0;
}

当数组作为函数参数传递时,它会自动退化为指向首元素的指针。指针没有保存数组大小信息(丢失了 sizeof(array)),范围 for 需要知道数组的起始和结束位置。

指针空值(nullptr)

  1. 在 C++ 里,NULL 一般被定义成整数 0,它的本质是个整数类型。
  2. nullptr:这是 C++11 新引入的关键字,它的类型是 std::nullptr_t。nullptr 能够隐式地转换为任意指针类型,但不能转换为整数类型。
  3. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 作为新关键字引入的。
  4. sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。

总结

函数重载、引用、内联函数、auto 和 nullptr 是 C++ 基础核心。重载让同名函数依参数适配场景;引用以别名简化操作、提升效率;内联平衡调用开销与复用;auto 减少类型声明冗余;nullptr 规范空指针。这些特性体现 C++ 在兼容与创新间的平衡,既承 C 语言高效,又添安全便捷。吃透并活用它们,是进阶高阶特性的基石,也能轻松应对面试基础考点,助你扎实迈入 C++ 之门。

目录

  1. C++ 核心特性详解
  2. 函数重载
  3. 定义
  4. 条件
  5. 参数类型不同
  6. 参数数量不同
  7. 参数顺序不同
  8. C++ 支持函数重载的原因
  9. 引用
  10. 定义
  11. 使用方法
  12. 使用场景
  13. 传值、传引用效率比较
  14. 引用在顺序表的应用
  15. 常引用
  16. 引用和指针的不同点
  17. 内联函数
  18. 定义
  19. 基本语法
  20. 内联函数 VS 宏
  21. 核心作用 —— 减少函数调用开销
  22. 编译器处理规则
  23. auto 关键字
  24. 定义
  25. 基本语法
  26. auto 在数组中的应用
  27. 范围 for 循环使用的条件
  28. 指针空值(nullptr)
  29. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 时序数据库 Apache IoTDB 基于 Kubernetes 的部署运维指南
  • Python 开发环境搭建与安装指南(Windows 版)
  • Flutter 三方库 huggingface_client 的鸿蒙化适配指南
  • 本地大模型构建知识库:Ollama + LobeChat + AnythingLLM
  • Python 智能 PDF 文档助手开发指南
  • FPGA PCIe XDMA Link Up 失败调试:基于 LTSSM 状态机定位问题
  • C++ 哈希表原理与实现详解
  • Mac 安装 Navicat Premium v17.1.9 指南
  • Golang 构建豆包大模型 2.0 企业级 Agent 架构与成本优化
  • MySQL 动态分区管理:自动化与优化实践
  • PP-DocLayoutV3 WebUI 自定义 CSS 注入与企业 UI 规范适配
  • C/C++变量命名规范:提升代码可读性的关键
  • Jetpack Compose 核心特性与实战指南
  • 本地知识库搭建指南:基于 Llama3 与 MaxKB
  • 基于 Leaflet 和天地图的免费运动场所 WebGIS 可视化
  • DeepSeek-R1-Distill-Llama-8B 部署实战:从零搭建推理服务
  • 大模型项目实战经验:数据、模型与业务侧总结
  • 算法基础:双指针法处理数组分块问题
  • C++ 多态详解:从实现条件到底层原理
  • AI 时代重读《人人都是产品经理》:核心内核与实战路径

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online