目录
1、const 和 指针 的关系
1.1 基本情况
const 指针的关系主要存存在三种:
1、常指针(const pointer)
int* const p;
2、指针类型的常量(const to pointer)
const int *p;
int const *p;
3、指向常量的常指针
const int * const p;
那么这三种不同的类型是如何使用的呢?首先我们来看下面的代码:
#include<iostream>
using namespace std;
//1、const 和 指针 的关系
int main()
{
int a = 10;
int b = 20;
int *p1 = &a;
//1、const pointer
int * const p2 = &a;
//2、const to pointer
//这两种方式等价
const int * p3 = &a;
//int const * p3 = &a;
//3、
const int * const p4 = &a;
return 0;
}
1、常指针的修改
int main()
{
cout << *p2 << endl; //10;
*p2 = b; //rigth
cout << *p2 << endl; //20
p2 = &b; //error
}
我们可以发现,在const 修饰指针本身的时候(int *const p2)。我们可以修改指针解引用后的值,但是不能修改指针本身所指向的地址。
2、指针类型的常量的修改
int main()
{
cout << *p3 << endl;
//*p3 = b; //error
cout << *p3 << endl;
p3 = &b; //right
cout << *p3 << endl;
}
当指针是一个指向常量的指针时(const int *p3)。我们可以改变指针所指向的对象,但是不能修改指针解引用后的值。
3、指向常量的常指针修改
*p4 = b; //error
p4 = &b; //error
当指针是指向常量的常指针的时候,我们既不能修改指针本身,又不能修改指针解引用后的值。
1.2 常指针或者指针类型的常量
1.2.1 常指针 const int *p = &a;
int main()
{
int a = 10;
int b = 20;
const int *p = &a;
//int *s0 = p; //error,可以通过s0修改p指向的内容的值。
const int *s1 = p; //right,类型匹配
//int * const s2 = p; //error,s2的指向的地址不能改变,但是可以解引用来改变p的值,错误。
const int *const s3 = p; //right
return 0;
}
1.2.2 指针类型的常量 int * const p = &a;
int main()
{
int a = 10;
int b = 20;
int * const p = &a;
int *s0 = p; //right
const int *s1 = p; //right
int * const s2 = p; //right
const int *const s3 = p; //right
system("Pause");
return 0;
}
int *const p = &a; 这条代码表示p是一个常指针,这个指针所指向的是地址不可改变的。这个常指针存放的是a的地址。其他的指针指向的也是a的地址,这些指针的赋值是正确的。因为常指针仅仅只是规定指针p不可以指向其他的地址,并没有规定其他指针不能改变这个指针解引用的值。况且其他指针的修改也仅仅只是修改这些指针本身(s0、s1、s2、s3)解引用的值(p的值)或者指向的地址,编译可以正常通过。
1.3 const_cast
首先来看一个概念:只读变量(常变量)。
const int n = 5;
int a[n];
//C99:error
//C++:right
//ANSI C规定数组的大小必须是一个常量,常量不等于“不可变的变量”,
//但是C++中没有规定。
//常量的定义:enum枚举类型和#define宏定义定义的。
//只读变量:const 来定义。
//原因:常量被编译器存放在内存中的只读区域,不可修改。
// 只读变量会在内存中申请一块空间来存放它的值,只不过是编译器限定的不可修改。
所以说,在C++中我们会将常变量看作为常量。
int main()
{
const int a = 10;
int b = 20;
int *p = &a; //error:这样改变解引用的值会修改原先a的值,但是a是 //const类型不可修改,报错。
*p = b;
//int *p = const_cast<int *>(&a);
}
这段代码编译器会报错,原因如下:
要解决可以使用 C++的标准转换运算符const_cast。
C++提供了四个转换运算符来移除变量的const限定符:
- const_cast (expression)
- static_cast <new_type> (expression)
- reinterpret_cast <new_type> (expression)
- dynamic_cast <new_type> (expression)
只需将代码改为 int *p = const_cast<int *>(&a); 即可移除变量a的const 限定符,实现初始化。
2、const 和引用 的关系
2.1 什么是引用
引用就是某个变量的别名,对引用的操作对与变量的直接操作完全相同。
格式为:类型 & 引用变量名 = 已定义过的变量名。
2.2 引用和指针的区别
引用 | 指针 |
---|---|
必须初始化 | 可以不初始化 |
不可为空 | 可以为空 |
不能更换目标 | 可以更换目标 |
没有二级引用 | 存在二级指针 |
1、引用必须初始化,而指针可以不初始化
int &s; //error:引用没用初始化
int *p; //right:指针不强制初始化
2、引用不可以为空,指针可以为空
int &s = NULL; //error:引用不可以为空,右值必须是已经定义过的变量名
int *p = NULL; //right:可以定义空指针。
int fun_p(int *p)
{
if(p != NULL) //因为指针可以为空,所以在输出前需要判断。
{
cout << *p << endl;
}
return *p;
}
int fun_s(int &s)
{
cout << s << endl; //引用不为空,可以直接输出
return s;
}
3、引用不能改变目标,指针可以更换目标
int main()
{
int a = 20;
int b = 10;
int &s = a;
int *p = &a;
s = b; //引用只能指向初始化时的对象,如果改变,原先对象也跟着改变。
p = &b; //指针可以改变指向的对象,不改变原先对象。
cout << s << endl;
cout << a << endl;
system("pause");
return 0;
}
2.3 指针和引用性能差异
指针:
void fun_p(int *p)
{
*p = 10;
return;
}
int main()
{
int a = 20;
int *p = &a;
fun_p(p);
}
通过objdump -d 命令来查看指针的反汇编代码:
引用:
void fun_s(int &s)
{
s = 10;
return;
}
int main()
{
int a = 20;
int &s = a;
fun_s(s);
}
通过objdump -d 命令来查看指针的反汇编代码:
我们可以清除的看出指针和引用的反汇编代码时基本一致的。这说明C++编译器在编译程序的时候将指针和引用编译成了完全一样的机器码。所以C++中的引用只是C++对指针操作的一个“语法糖”,在底层实现时C++编译器实现这两种操作的方法完全相同。
2.4 指针的引用
2.4.1 int *p = &a;
int main()
{
int a = 10;
int b = 20;
int *p = &a;
int *s = p;
//1、
//int *&pref1 = p; //指针的引用,p的别名叫做pref1。
//2、
//int &*pref2 = p; //error,引用不具有地址。
//3、
const int *&pref3 = p; //p的另一个名字叫pref3。
//由于是对指针解引用后的值添加const ,
//所以p(pref3)无法修改解引用的值
//但是可以改变p(pref3)所指向内容的地址
//pref3 = b; //error;
p = &b; //right
//pref3 = &b;
//4、
int *const &pref4 = p; //首先p的别名是pref4,const 修饰的是指针本身。表示指针p(pref4)无法对指针的指向进行修改。
//但是可以修改指针p(pref4)解引用后的值。
//p = &b; //error
*p = b; //right
//5、
const int *const &pref5 = p; //首先pref5是指针p的别名,前面的const修饰的是解引用的值,后面const修饰的是指针本身。
//所以它们解引用后的值和指针本身的地址都不可修改
system("pause");
return 0;
}
2.4.2 const int *p = &a;
int main()
{
int a = 20;
int b = 10;
const int *p = &a; //const修饰的是解引用后的值,表示p解引用后的值不可修改,但是可以修改P指向的地址。
int* &pref1 = p; //error :pref1是P的别名,但是如果这样命名,我们就可以通过*pref1来修改a的值,与我们定义的const 相违背。
*pref1 = b;
const int* &pref2 = p; //right:pref2是p的别名,通过*pref2也不可以修改p的值,从而改变a的值。但是我们可以通过改变p指向的地址,来指向不同的地址。这和const int *p的理念一致,所以是正确的
int* const &pref3 = p; //error:pref3是P的别名,const修饰了pref3不可指向其他的地址,但是可以通过*pref3来修改*p产生的值。
const int* const &pref4 = p; //pref4是p4的别名,两个const 限定了它的解引用和修改指针指向的地址。并且遵循能力收缩的原则。
return 0;
}