
🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。
继命名空间、缺省参数等基础内容之后,本篇主要讲述三个核心特性:引用、内联函数与 nullptr。它们是简化代码结构、优化程序性能的重要手段,也是不能轻易理解的知识点,尤其是引用。如何正确使用引用避免权限问题?内联函数的适用场景是什么?为何推荐用 nullptr 替代 NULL?带着这些疑问,本文彻彻底底帮你弄清楚这些概念。
概念:给已有的变量“新名字”(别名)
使用:类型&引⽤别名=引⽤对象;
案例:在需要传指针的地方,可以用引用代替,不需要调用该指针,让形参就叫别名,改变形参就是改变实参
特性:
int a = 10;
int& ra; // 编译报错:“ra”: 必须初始化引⽤
int& b = a;
int c = 20;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值 主要应用:
引用返回值能不能随便用?
int& func()
{
int a = 0;
return a;//错的:a是局部的,返回时候已经销毁,类似野指针
}
int& func1(int& ra)
{
ra = 3
return ra;//对的:ra是外部传入的引用,出函数依旧存在
}const 引用规则:权限可缩小不可放大,仅限制引用本身的访问权限
int main()
{
const int a = 0;
int& ra = a;//错的
const int& ra = a;//对的
//权限放大
//注意分辨:
const int a = 0;
int ra = a;//这是可以的,不是权限放大
int b = 0;
const int& rb = b;//对的
//权限缩小
//这个地方没有缩小b的权限,b依旧能该改变
b++;//对
rb++;//错
//类比:公共场合,添加一个身份,做不了一些事情(权限缩小)
int& rc = 30;//错
const int& rc = 30;//对
//const引用可以给常量取别名,单纯的引用不可以
//这是因为const修饰的变量具有常性,所以不可更改,这也就是const修饰能够对常量取别名的原因
//编译器需要⼀个空间暂存表达式的求值结果(中间值)时临时创建的⼀个未命名的对象
int rd = (a+b);//可以对临时对象赋值
int& rd = (a+b);//不可以对临时对象单纯引用(没法修改)
const int& rd = (a+b);//可以用const引用(不修改)
//这也是因为临时对象具有常性
double d = 12.34;
int i = d;//是隐式类型转换,这个过程有临时对象
int& i = d;//不可以
const int& i = d;//可以
//下面介绍一个很好用的东西
//函数模板,T可以是任意类型
template <class T>
void func(T val)
{
//注意看这里传入的T是任意类型
//所以程序员对它进行一个引用(&T val)(防止传入的值太大拷贝成本高)
//这里传常量/临时对象/带有类型转换可以吗?不行
//最终:const &T val,这样子就什么都能传
// 使用const引用可接收:
// 1. 普通变量
// 2. 常量
// 3. 临时对象
// 4. 类型转换结果
// 类似void*的通用性
}
return 0;
}int* ptr = nullptr;
int& rptr = *ptr;
rptr++;
#define定义的宏函数,为了防止,宏函数的使用往往小心翼翼,要加很多层括号//正确
#define ADD(a,b) ((a)+(b))
int main()
{
int add = ADD(3,2);
return 0;
}
//错误加分号
#define ADD(a,b) ((a)+(b));
int main()
{
if (ADD(3, 2) > 0)
{
//执行某操作
}
return 0;
}
#define ADD(a,b) (a+b)
//错误不加内层括号
int main()
{
int add = ADD(1 ^ 1, 2 ^ 2);
//替换以后 =》1^(1+2)^2
return;
}
#define ADD(a,b) (a)+(b);
//错误外层无括号
int main()
{
int add = ADD(3,2) * ADD(3,2);
//替换以后 =》3+(2*3)+2
return 0;
}以上是宏函数的使用
上面宏函数的二条提到了内联函数,inline是一个关键字,他所修饰的函数称之为内联函数,上文提到宏函数是为减少函数栈帧的开销,inline也有这个作用
栈帧(也叫「活动记录」)是程序运行时,栈内存中为单个函数调用分配的一块独立内存区域,用于存储该函数的:
main() 调用 funcA(),funcA() 又调用 funcB(),栈中会依次压入:main 栈帧 → funcA 栈帧 → funcB 栈帧(栈顶)。比如说:在main函数中调用一个inline int add()的函数,如果去掉inline,他会创建栈帧;而加上inline变为内联函数以后就在main函数的栈帧上创建自己的栈帧,省去了跳转等操作,简单了许多
当函数 A 调用函数 B 时:
那内联函数好处这么多,能不能所有函数都直接前面加inline使其变成内联函数呢?
答案是 不可以 ,因为内联函数还是在调用者的内部把函数展开了,有点类似与你没有使用函数,而是直接给函数内部的代码 粘贴 到了原函数处,这大大增加了可执行文件的内存占用量。
除此之外,内联函数还有很重要的一个缺点:声明和定义不能分离在两个文件中,会导致链接失败。
值得注意的一点是,在有些编译器(如vs2022)中,并非程序员添加inline他就变成了内联函数,这得编译器的心情(其实就是debug下,编译器会识别内联函数的代码量,如果代码量很大,就不会展开函数,而是作为一般函数调用)
如果想要调试的话,按照下方的操作可以在debug模式下调试:


NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endifNULL,被定义为了0,是一个int类型。nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。从此以后,C++里面的空指针使用
nullptr,不使用NULL。