一、const引⽤
C++规定临时对象具有常性
这是非常重要的一句话。
可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
可以权限的缩小,但是不能权限的放大
这样⼀些场景下结果会保存在⼀个临时对象中, int& ra= 10
也是类似,在类型转换中会产⽣临时对象存储中间值,也就是,ra引用的都是临时对象,⽽C++规定临时对象具有常性
,所以,在这种情况下,就触发了一种权限放大的机制。
所谓临时对象,是指在程序执行过程中,当编译器需要一个临时空间来暂存表达式的求值结果时,会自动创建的一个未命名的对象。在C++编程语言中,这种由编译器自动生成且未明确命名的对象,被特别地称为“临时对象”。
临时对象的存在,主要是为了支持表达式的计算,尤其是在进行复杂的数据类型运算时。例如,在函数返回非引用类型的对象时,或者在执行某些运算符重载函数时产生的中间结果,都可能需要借助临时对象来存储。
int main()
{
double d = 12.34;
int& rd = d;
return 0;
}
这样会报错,因为在类型转换中会产⽣临时对象存储中间值,也就是,rd引用的是临时对象,⽽C++规定临时对象具有常性
,所以,在这种情况下,就触发了一种权限放大的机制。
应该这样写:
double d = 12.34;
const int& rd = d;
这种预防权限放大的机制,往往是为了增强系统的安全性,防止未经授权的访问或操作。通过要求使用常引用来进行某些敏感操作,系统可以更加严格地控制权限的分配和使用,从而确保只有经过授权的用户或程序才能访问或修改关键数据。
对于某些特定的操作或访问,原本可能不需要太高级别的权限,但由于某些特殊条件或设置的触发,现在必须要求使用常引用(或者说是常规引用、标准引用)才能进行相应的操作或访问。
在C++中,const关键字用于声明一个变量为常量,意味着这个变量的值一旦被初始化之后就不能被修改。
int main()
{
const int a = 10;
int& ra = a;
return 0;
}
a被声明为一个const int,意味着它的值是不可变的。然而,ra被声明为一个非常量引用(int&),它允许通过ra来修改所引用的值。如果允许这样的绑定,那么通过ra修改a的值将会违反a作为常量的初衷,因此编译器会阻止这种操作,并报告一个错误。
二、指针和引用的关系
C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功
能有重叠性,但是各有⾃⼰的特点,互相不可替代。
语法概念上:引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下
占4个字节,64位下是8byte)
指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
三、nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
• C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种
定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的
f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL);
调⽤会报错。为了改变这种现状,C++11中引⼊nullptr
,nullptr
是⼀个特殊的关键字,是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被
隐式地转换为指针类型,⽽不能被转换为整数类型。
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f(NULL);//原本想调用第二个函数,由于NULL被定义成0,所以会调用第一个函数
f((int*)NULL);
f(nullptr);
return 0;
}
四、inline
inline修饰的变量
称之为编译时常量,编译期间C++编译器会直接将编译时常量的值代入到使用它的表达式中,这样使用编译时常量就无需在运行时分配内存空间,从而能够提升性能。
#include<iostream>
using namespace std;
inline void ADD(int x, int y)
{
cout << x + y << endl;
}
int main()
{
int a = 10, b = 20;
ADD(a, b);
return 0;
}
inline对于编译器而言只是一个请求,也就是说,即便你加了inline编译器也可以选择在调用的的地方不展开,不同编译器对于inline何时进行值代入的处理方式不尽相同,因为C++标准并未对此做出硬性规定。
- vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。
- inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。(大概是10行代码左右)
以下为两个相反的例子来说明上文:
#include<iostream>
using namespace std;
inline int Add(int x, int y)//简短的函数
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
int ret = Add(1, 2);
cout << Add(1, 2) * 5 << endl;
return 0;
}
注意,这里没有调用ADD函数的指令,因为ADD函数简短,没有展开
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
int ret = Add(1, 2);
cout << Add(1, 2) * 5 << endl;
return 0;
}
这里调用了ADD函数,所以系统展开了inline修饰的ADD函数,所以系统并不是一味的只要遇见了inline就一定会代替宏函数。
C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调试,C++设计了inline目的就是替代C的宏函数。
inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时跳转不到函数地址,就会出现报错。