C++基础语法篇二 ——引用、内联和空指针
关于C++的一些皮毛语法已经了解的差不多了,那么让我们来看看更为广阔的语法结构吧!
引用
引用的概念和定义:
引用不是新定义⼀个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
具体来说是给变量加了乳名,比如就像仙逆里面的王林,别人有叫他曾牛的,也有叫他王麻子的,其实是一个人(变量)。
语法结构是这样的:
类型& 引用别名 = 引用对象;
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
引用的特性
• 引用在定义时必须初始化
• 一个变量可以有多个引用(乳名)
• 引用一旦引用一个实体,再不能引用其他实体
#include<iostream>
using namespace std;
int main()
{
int a = 10;
//int& ra;// 编译报错:“ra”: 必须初始化引⽤
int& b = a;
int c = 20;
//这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
b = c;//所以这⾥是⼀个赋值
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
引用和指针用法类似,引用更方便
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x <<" " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
这样我传递的参数不用取地址了,也不用指针接收而且可以改变实参的值,引用接收就可以了,所以方便。
相对于结构体也是这样的,就比如下面修改栈顶元素,函数也可以引用
#include<iostream>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
// 栈顶
void STPush(ST& rs, STDataType x)
{
assert(ps);
// 满了, 扩容
if (rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
// int STTop(ST& rs)
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top-1];
}
int main()
{
// 调⽤全局的
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;//通过引用修改栈顶元素
STTop(st1) += 10;
cout << STTop(st1) << endl;
return 0;
}
const引用
• 可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
• 不需要注意的是类似 int& rb = a3 ; double d = 12.34;
int& rd = d; 这样一些场景下a3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。
• 所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,
C++中把这个未命名对象叫做临时对象
int main()
{
const int a = 10;
//注意临时对象是有常属性的// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”
// 这样才可以
const int& ra = a;
//ra++; 编译报错:error C3892: “ra”: 不能给常量赋值int b = 20;
const int& rb = b;// 这⾥的引⽤是对b访问权限的缩⼩
//rb++; 编译报错:error C3892: “rb”: 不能给常量赋值return 0;
}
还有一些引用用于表达式,表达式的结果存储在临时对象中,临时对象具有常属性,所以在用于表达式的引用也要加const.当然用于常量也是,用于算数类型转换时也要加const.
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;//用于常量的时候也要加const;const int& rb = a*3;//用于表达式时也要加const
// int& rb = a * 3; 编译报错: “初始化”: ⽆法从“int”转换为“int &”double d = 12.34;
// int& rd = d;// 编译报错:“初始化”: ⽆法从“double”转换为“int &”const int& rd = d;
return 0;
指针和引用的关系
你可以把它们想象成哥哥弟弟或者姐姐妹妹一样,都有各自的作用,语法概念上引用是一个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
• 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
• 引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
• sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
• 指针很容易出现空指针和野指针的问题,引用很少出现,引 用、使用起来相对更安全一些。
inline
inline是C++中一个关键字,意思是内连,用于内联函数
用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
• inline对于编译器而言只是⼀个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
• C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。
• vs编译器 debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置⼀下以下两个地方。
• inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。


#include<iostream>
using namespace std;
inline int Mul(int a,int b)
{int ret=a*b;return ret;
}int main()
{int ret=Mul(1,5); //可以通过汇编查看如果有call,说明没展开,没有call说明展开了cout<<Mul(1,5)<<endl;return 0;
}
总的来说设计内联函数是为了代替宏(这里不再详解);
nullptr
nullptr是一个关键字,在c++中代表空指针的意思
nullptr 是 C++11 引入的空指针常量,专门用于表示 “空指针”,解决了传统 NULL 的二义性问题,是现代 C++ 中表示空指针的标准且推荐的方式。
//NULL 的二义性问题
#include <iostream>
using namespace std;// 重载函数:一个接收 int,一个接收 char*
void func(int x) {cout << "调用 int 版本:x = " << x << endl;
}
void func(char* p) {cout << "调用 char* 版本:p 是空指针" << endl;
}int main() {func(NULL); // 编译无报错,但调用的是 func(int),而非预期的 func(char*)// func(0); // 与上面等价,进一步证明 NULL 本质是 0return 0;
}
新代码优先用 nullptr:避免 NULL 的二义性,语义更清晰,是 C++ 标准推荐的空指针表示方式;
旧代码兼容:若项目需兼容 C++11 之前的版本,可暂时保留 NULL,但建议逐步替换为 nullptr;
避免用 0 表示空指针:0 的本质是整数,仅在无 nullptr 时临时使用,现代 C++ 中禁止这种写法;
判断指针为空:推荐用 if (p == nullptr) 或 if (!p)(后者更简洁,且对 nullptr 和 NULL 均兼容)。
