- 1.复合类型:从指针到引用
- 1.1.指针
- 1.2.引用
- 2.常量类型与常量表达式
- 2.1.常量指针
- 2.2.常量引用
- 2.3.常量表达式
注意 :既然指针具有相同的尺寸,为什么还要区分不同类型的指针呢?比如int* p1; char* p2,这是因为指针中存储的是一个内存地址,内存地址的长度都是一样的,所以指针是具有相同的尺寸的。但是它存储的内存地址存储的变量是具有类型的,我们只是得到了这个变量的首地址,并不知道这个变量的尺寸,这样就无法访问这个变量了。比如int*要从内存地址开始访问4个字节,char*从内存地址开始访问一个字节。所以指针类型就是规定了指针所指向对象的长度。
注意:
- 常量指针可以指向变量的解释
// 1.常量指针可以指向变量,其中包含了一次隐式类型转换 // &x的类型是int*, 然后给他加一个const限制是合法的,所以这中间 // 有一个隐式类型转换,即int* 变成 const int* int x = 0; const int* p = &x; // 2.反之不可以,即(变量)指针不能指向常量 // 下面这样&x的类型是const int*, 而p的类型是int*,这样隐式类型转换相 // 当于去掉了限制,因为后面可以用p对x进行写操作了,而原来x的定义是 // 没有写操作的,因此这种隐式类型转换是错误的 const int x = 0; int* p = &x;
- 对const int*和int* const的理解可以这样看:
- const int*对其解引用的话,得到的就是const int,因此这个指针所指向的内容是一个常量,是不能改的。
- int* const对齐解引用的话,得到的就是int const,这个好像就没法解释了。所以只记住上面那个解释即可,上面那个解释是合理的。
- const int&(也可绑定变量)
- 这个地方和常量指针是一样的,因为引用的底层实现就是指针。
但是和指针不同的是,只存在常量引用(const int&),不存在引用常量(int& const),因为引用的本身定义就是一个变量的别名,不能指向其他变量。 - 同理常量引用可以绑定变量,因为这相当于给变量增加了限制。但是反过来,普通的引用不能绑定常量,即如下是非法的:
// 合法:常量引用绑定变量,相当于给变量提升限制 int x = 0; const int& ref = x; // 非法:普通引用绑定了常量,相当于给常量去掉了限制 const int x = 0; int& ref = x;
- 可读但不可写
这个很显然 - 主要用于函数形参
首先明确为什么要按引用传递:
(1)自定义的数据类型,比如类和结构体可能比较大,如果使用普通的按值传递,那么赋值造成的成本很大。所以要避免复制。
(2)如果使用指针传入,可以避免复制,但是指针不太安全,比如传入的可能是野指针等
(3)使用引用解决了上述两个问题
其次为什么要使用常量引用:
防止在函数内部修改传入的量,保证安全性。
注意:不要把普通的int``bool类型也用引用来传递,因为这些内置类型拷贝的成本很低,如果使用引用,其内部实现还是指针,反而拷贝指针的时候需要8字节,其次访问指针的时候需要经过两次内存访问操作,性能更低。
- 可以绑定字面值
- 首先明确普通的引用是不能绑定字面值的,因为字面值没有内存地址,所以引用无法起作用。
// 错误:普通引用不能绑定字面值 int& ref = 3;
- 但是常量引用就可以绑定字面值,这个主要是C++中为了函数接口来使用的。比如下面的程序:
void fun(const int& param)
{
}
int main()
{
int x = 3;
fun(x);
// 很多时候都会直接传入一个3调用,为了让这种代码合法,所以C++规定常量引用可以绑定字面值
fun(3);
2.3.常量表达式
- 使用 constexpr 声明
如下程序所示,程序中确定一个常量,可以在不同的阶段确定。比如可以在程序编译期间就确定,也可以在程序的运行期间确定。
// 1.在运行期间确定常量 int x; std::cin >> x; const int y = x; // 2.在编译期间确定常量 const int y = 3;
- 声明的是编译期常量
从上面可以看出来,如果仅仅使用const,那么常量也可以在程序的运行期间定义。
为了更加明确常量直接在编译期间确定,所以引入限定符constexpr,这样就明确的表示我定义的常量是在编译期间就确定的。
注意:constexpr只是一个限定符,它并属于一种对象类型。比如const int是常量的int类型,但是constexpr int它的类型仍然是const int,而constexpr的作用,除了声明了这个类型是const int之外,它还限定了这个常来类型的值是在编译期间就确定的,而不是运行期间。所以也可以简单的理解为,constexpr修饰的是定义变量的类型,也就是它直接修饰的是x变量。
// x的类型仍然是const int, 而不是constexpr int,因为constexpr并不是一种类型 constexpr int x = 1;
- 编译器可以利用其进行优化
比如如果定义的是编译期间就确定的常量,那么很多代码在编译期间就可以被优化:
constexpr int y = 3; // const int y = 3;
if(y == 3)
{
......
}
// 上面代码在编译期间可以优化成:
{
.....
}
- 常量表达式指针: constexpr 位于 * 左侧,但表示指针是常量表达式
constexpr const int* p1 = nullptr; constexpr const char* p2 = "123";
这里理解类型的时候和上面的2是一样的理解方法,注意3点:
(1)consexpr是限定符,不是类型;
(2)看类型的时候,constexpr等价于const;
(3)合起来看总的类型的时候,constexpr(等价于const类型)直接修饰的是变量x,所以要放在变量左边。
即上面的类型是const int* const和const char* const,前一个const是原来明确写的,后面的const是constexpr限定p1/p2得到的。
这里也可以发现,可能平常写的const int x,写成int const x意义更加明确一些,实际上在C++中这两种写法都是合法的,但是前者的写法更加常见。



