C++-引用,inline,nullptr

一,引用

1.1引用的概念与定义

         引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。

引用的使用方式如下:

类型& 引用别名 = 引用对象。

1.2引用的特性

1.引用在定义时必须初始化。

2.⼀个变量可以有多个引用。

3.引用⼀旦引用⼀个实体,再不能引用其他实体。

int main()
{
	int a = 10;
	int c = 20;
	int& b = a;
	int& b = c;//编译器报错b多次重定义
	return 0;
}

        但这里我们需要注意,引用无法代替指针,因为引用无法改变指针指向,引用表面是传值,但本质也是传地址,然而这个工作由编译器来做,所以并不能由引用来执行。

1.3引用的使用

1.引用传参

之前我们数据结构部分的传参(拿栈来举例),我们在改变栈的各项数据时通常需要传栈的地址,有时候还需要去传二级指针,这就有些许麻烦,但引用可以适当简化:

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);//指针形式:Swap(&x,&y)
	cout << x << " " << y << endl;
	return 0;
}

还有这样:

void change(ST** st);
void change(PST& st);//PST为栈结构的指针

当然第二条我们也可以写成 ST*& st 也是可以的。

2.引用做返回值中减少拷贝提高效率(同时改变被引用对象)

我们先来对比下以下几种返回方式:

假设我的栈此时顶部数据为2,一个返回的是栈顶部的值,一个返回的是别名,乍一看好像并没有什么区别,引用返回值的场景相对比较复杂,我们在这里简单介绍⼀下,由于在返回值的时候,编译器会创建一个临时变量去储存返回值,所以第一种方式返回就会消耗一个整形的空间去临时存储,但引用不占实际空间,所以返回的仅仅是别名,不占用空间。

而且,返回引用还有个优点。就是可以改变此时的栈顶值,有人会说那我返回指针不也可以,请看如下代码示例:

STTop(st1) += 10;//引用改变值
*(STTop(st1)) += 10;//指针改变值

一目了然,明显引用的复杂程度更低,同时,如果我们返回的是指针,我们返回的时候还需要去创建临时变量去存放指针。

 4.const引用

这里我们需要非常注意权限放大的问题,对于const对象的引用,我们也必须要用const来修饰该引用对象,比如:

const int a = 10;
const int& ra = a;//正确引用

const int a = 10;
int& ra = a;//权限放大,引用对象可以改变被引用对象

还有当我们的右值直接等于表达式,或进行了强制类型转换,此时编译器会对右值先进行计算储存到临时变量中,然后再将临时变量赋给左值:

double b = 12.34;
int& a = b;//这里并非无法强转,而是强转的结果放在了一个const int类型的临时变量中
             导致编译报错

int c = 10;
int& e = c * 3;//这里与上面类似。临时变量为const int类型

当然,权限无法放大但可以缩小:

int a = 10;
const int& b = 0;

5.指针与引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

1.语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。

2.引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。引⽤在初始化时引⽤⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。

3.引用可以直接访问指向对象,指针需要解引引才是访问指向对象。

4.sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)。

5.指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。

二,inline内联函数 

        内联函数主要解决的是C中的宏定义的问题,我们先来回顾下宏的一些注意事项,比如我们定义一个两数相加的宏:

#define add(x,y) ((x)+(y));//正确写法

#define add(x,y) (x+y);//易错写法,如果传的是(x|y,x&y),由于
                       //位操作符的优先级低于+,所以无法达到预期目的

这里如果是宏,我们就需要多次的去加括号,显得很难受,但是我们写一个内联函数就可以解决这个问题:

inline int add(x,y)
{
   int a = x + y;
   return a;
}

但需要注意,inline对于编译器只是一个建议,如果你的函数定义代码过长(我们的vs一般是在9行左右) 他会屏蔽掉你的inline,不对函数进行展开。同时假如你使用的是vs2022,那么它默认是直接无视inline的,所以我们需要这样去设置我们的vs:

这时编译器便会将内联函数展开像调用宏一样去调用我们的宏,我们这里再用反汇编来证明它没有被展开:

展开的情况:                                                   没有被展开:

我们的反汇编结果可以看到它并没有右图中的函数地址的传递(函数的地址就是07FF6CAD313D9,h为十六进制的后缀)。

除此之外,我们还需要注意的就是,如果我们内联函数有声明的话,不能放在不同的文件之中,这样会报错,只能放在同一个文件中或内联函数直接定义在头文件中:

正确做法:

或:

亦或者是这样:

三,nullptr

在传统的C头文件(stddef.h)中我们可以看到:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

在C++中,我们的NULL只是单纯的表示为0了,不能再表示空指针了,我们看下面一个例子就可以解释这种原因:

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

void f(int* ptr)
{
	cout << "f(int* ptr)" << endl;
}

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

如果是C++的重载,我们会得到以下结果:

可以看到同时调用的是第一个F函数,如果我们将第二个改为((void *)0):

很遗憾C++不支持让void*转换成任意类型的指针,所以这里我们就需要用nullptr来代替我们C中的空指针,更换完后结果如下:

也就是说,这里的nullptr其实代替了我们C中的空指针NULL。

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值