跳到主要内容
C++核心特性解析:函数重载、引用、内联函数、auto 与 nullptr | 极客日志
C++ 算法
C++核心特性解析:函数重载、引用、内联函数、auto 与 nullptr C++ 基础特性涵盖函数重载、引用、内联函数、auto 关键字及 nullptr。函数重载通过参数列表区分同名函数;引用作为别名可避免拷贝开销并支持修改实参;内联函数减少调用开销但受编译器限制;auto 自动推导类型简化代码;nullptr 替代 NULL 提供类型安全。掌握这些特性有助于提升代码效率与安全性,应对面试基础考点。
Pythonist 发布于 2026/3/28 更新于 2026/4/23 2 浏览C++核心特性解析:函数重载、引用、内联函数、auto 与 nullptr
函数重载
定义
重载 :在自然语言中,一个词有多种意思,可以通过上下文来判断该词真正的意思。
函数重载 是函数的一种特殊情况,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;
}
{
cout << << endl;
}
{
( , );
( , , );
;
}
void sum (int a, int b, int c)
"void sum(int a, int b, int c)"
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++ 为什么支持函数重载。
使用 mkdir + 空格 + 文件名创建文件夹。可以使用 ls 显示当前文件夹中的文件。如下图创建的文件夹为 C。
使用 cd + 空格 + 文件名这条指令可以进入当前文件夹。如下图所示,当前文件夹中没有内容为空。
使用 nano + 空格 + 文件名这条指令可以编辑并创建文件。在 test.c 中写的代码如下图所示。
代码完成后,按住 ctrl + x 出现如下的页面,输入 Y 保存文件后,按回车退出编辑。
可以输入 cat + 空格 + 文件名来查看文件中的内容。
输入 gcc + 空格 + 文件名编译 C 语言文件,此时默认生成的可执行程序为 a.out。可以通过 gcc + 空格 + -o + 空格 + 文件名 + 空格 + test.c 将生成的可执行程序重命名。
使用 objdump + 空格 + -S + 空格 + testc 查看 C 语言代码的汇编代码。
结论 :在 linux 环境下,采用 gcc 编译完成后,函数名字的修饰没有发生改变。
使用 cp + 空格 + 原文件 + 空格 + 新文件复制并重命名原文件。
使用 nano 指令将 test.cpp 中的注释部分删掉即可。
和上面的类似使用 gcc 编译并将生成的可执行程序重命名为 testcpp。
结论 :在 linux 环境下,采用 g++ 编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
总结 C 语言没办法支持重载,因为同名函数没办法区分。而 C++ 是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
引用
定义 引用是一项重要特性,它为变量提供了一个别名,让你能够通过这个别名来访问和操作原始变量。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
使用方法
引用是已存在变量的别名,通过在变量名前使用 & 符号声明。
引用必须在声明时初始化,且一旦初始化后不能再引用其他变量。
一个变量可以有多个引用。
引用在同一个域不能同名,在不同的域可以同名。
#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 ;
}
#include <iostream>
using namespace std;
int main () {
int a = 0 ;
int & d = a;
int x = 11 ;
d = x;
cout << x << endl;
cout << d << endl;
cout << &x << endl;
cout << &d << endl;
return 0 ;
}
在上面的代码中 d = x 是赋值,而不是引用。赋值需要开辟空间,引用是多个变量在同一份空间。
使用场景
引用传递允许函数直接修改实参的值,避免值传递的拷贝开销。(输出型参数 )
#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);
cout << x << " " << y << endl;
return 0 ;
}
#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 ;
}
#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 ;
}
#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;
SLModify (&s, 0 , 1 );
cout << SLGet (&s, 0 ) << endl;
int ret = SLGet (&s, 0 );
SLModify (&s, 0 , ret + 5 );
SLAt (&s, 0 ) = 1 ;
cout << SLAt (&s, 0 ) << endl;
SLAt (&s, 0 ) += 5 ;
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 () {
const int a = 0 ;
const int & b = a;
int c = 0 ;
int & d = c;
const int & e = 42 ;
return 0 ;
}
#include <iostream>
using namespace std;
int main () {
const int c = 0 ;
int d = c;
return 0 ;
}
这种情况可以,这是赋值,c 拷贝给 d,没有放大权限,因为 d 的改变不影响 c。
#include <iostream>
using namespace std;
int main () {
int x = 0 ;
int & y = x;
const int & z = x;
return 0 ;
}
在引用过程中,权限可以缩小或平移,但不能放大。上面的代码中因为 const 修饰的变量的值不能发生改变,所以 z 不能 ++,x 可以 ++。
#include <iostream>
using namespace std;
int main () {
double e = 1.11 ;
const int & rii = e;
return 0 ;
}
上面的代码中发生了隐式类型转换,类型转换产生的临时对象默认具有常量性,因此只能用 const 引用绑定。
发生类型转换 (强制类型转换,截断,隐式类型转换),函数会产生临时变量。相同类型不产生临时变量。
#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++ 中,引用的权限不能超过原始对象的权限。也就是说,不能通过引用去获得比原始对象更多的修改权限。常量对象只能被常量引用,而变量对象则可以被常量引用或者非常量引用。
引用和指针的不同点
引用概念上定义一个变量的别名,指针存储一个变量地址。
引用在定义时必须初始化,指针没有要求。
引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
没有 NULL 引用,但有 NULL 指针。
在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数 (32 位平台下占 4 个字节)
引用自加即引用的实体增加 1,指针自加即指针向后偏移一个类型的大小。
有多级指针,但是没有多级引用。
访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
引用比指针使用起来相对更安全,但引用不能完全代替指针。指针有野指针和空指针,但引用没有。
内联函数
定义 内联函数是一种特殊函数,通过编译器优化减少函数调用开销,提高执行效率。
基本语法 使用 inline 关键字声明,建议编译器将函数体直接替换调用处(类似宏展开):
inline int add (int a, int b) {
return a + b;
}
宏函数(C 语言)
优点 :不需要建立栈帧,提高调用效率。
缺点 :复杂,容易出错,可读性差,不能调试。
#define Add(x, y) ((x + y) * 10)
内联函数 VS 宏 特性 内联函数 宏 类型安全 遵循 C++ 类型检查 简单文本替换,无类型检查 调试支持 可调试 不可调试(预处理阶段展开) 副作用 无 可能多次求值 代码膨胀风险 由编译器决定是否展开 强制展开,可能导致代码冗余
核心作用 - - - 减少函数调用开销
保存调用位置上下文
跳转到函数地址执行
返回时恢复上下文
内联函数 :直接将函数体代码复制到调用处,避免上述开销,适合短小频繁调用的函数。
编译器处理规则 inline 是建议,非强制 :编译器可能忽略 inline 声明(如函数体复杂、递归调用等)。
展开条件 :函数体短小,无循环、递归等复杂结构。
定义位置 :内联函数必须在调用点前被定义(通常放在头文件中),否则链接时可能报错,内联函数的声明和定义不能分离。
auto 关键字
定义 auto 是一个功能强大的类型说明符,它能让编译器 自动推导变量的类型 ,从而显著简化代码。
基本语法 auto 的基本语法是在声明变量时,用 auto 关键字替代具体的类型,编译器会根据初始化表达式自动推导变量的类型。可以使用 typeid 函数输出变量的类型。
#include <iostream>
using namespace std;
int main () {
auto x = 42 ;
auto y = 3.14 ;
auto z = "hello" ;
auto & ref = x;
const auto & cref = x;
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 (auto e : arr) {
cout << e << " " ;
}
cout << endl;
for (auto & e : arr)
{
e *= 2 ;
}
for (auto e : arr) {
cout << e << " " ;
}
return 0 ;
}
关键点总结
引用是修改的关键
使用 for(auto& e : arr)(带引用)可直接修改原数组元素。
使用 for(auto e : arr)(值拷贝)只能读取数据,修改无效。
auto 的优势
自动推导元素类型,无需手动指定。
数组类型变化时无需调整,代码更健壮。
范围 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)
在 C++ 里,NULL 一般被定义成整数 0,它的本质是个整数类型。
nullptr :这是 C++11 新引入的关键字,它的类型是 std::nullptr_t。nullptr 能够隐式地转换为任意指针类型,但不能转换为整数类型。
在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 作为新关键字引入的。
sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。
总结 函数重载、引用、内联函数、auto 和 nullptr 是 C++ 基础核心。重载让同名函数依参数适配场景;引用以别名简化操作、提升效率;内联平衡调用开销与复用;auto 减少类型声明冗余;nullptr 规范空指针。这些特性体现 C++ 在兼容与创新间的平衡,既承 C 语言高效,又添安全便捷。吃透并活用它们,是进阶高阶特性的基石,也能轻松应对面试基础考点,助你扎实迈入 C++ 之门。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online