【C++基础】常量引用、指针常量与常量指针 ; 顶层const与底层const

本文详细阐述了C++中常量引用和指针常量的概念与应用。常量引用用于确保函数不修改对象,同时保持引用的效率优势;指针常量则分为不能修改对象的指针常量和不能改变指向的常量指针。通过实例解析了它们在参数传递中的差异,强调了顶层const和底层const的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、常量引用

我们用引用作形参往往是为了能修改对象的值,但有时候又用“对const的引用”,如const int& a,作为形参,这样就使得该对象不能被函数修改了,那这样用引用的意义不就没有了,和传值(如int a)作形参不就一样了?

事实上,一方面引用避免了拷贝,对于占用内存大的对象来说,传引用依然比传值会更有效率,所以传引用作形参还是有意义的。另一方面,如果我们不希望被引用绑定的对象被该函数修改,所以我们就会采用“对常量的引用”,简称常量引用,如const int& r = i;
这样引用及其对应的对象都是常量,因为首先C++不允许随意改变引用所绑定的对象,所以引用本身是一种常量,而经过const修饰后的变量i也成为了常量(这指的是不能通过引用去修改它)。

而且,使用引用而非常量引用也会极大地限制函数所能接受的实参类型,比如我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。
C++ primer 5th第六章有这样一个例子

string::size_type find_char(string &s, char c, string::size_type &occurs){
//具体实现不写
//第一个形参为string &s
}//string::size_type是size()函数的返回类型,size()返回string对象的长度,见C++ primer 5th P79.

可见,第一个形参是普通引用,而非常量引用。那么在调用函数时,如果遇到“hello word”这种const型的实参(它默认是const char*型),就会报错,如

find_char("hello world", 'a', ctr);

而将第一个形参改成const string &s则不会报错。

补充:
前面说过,常量引用仅仅是说不能通过引用去修改该对象,对象本身是非常量的话,也可以通过其它途径改变,如下

int i = 42;//i没有被声明为const int i = 42,所以可以改变
int &r1 = i;//引用r1绑定对象i,且不是常量引用
const int &r2 = i;//r2也绑定对象i,但不允许通过r2修改i的值
r1 = 0;//正确
r2 = 0;//错误

二、指针常量与常量指针

(1)指针常量
指针常量是“指向常量的指针”的简称,英文是 pointer to const。顾名思义,指针常量不能用于改变其所指对象的值,并且想存放常量对象的地址,只能使用指向常量的指针,即指针常量。
写作:const int *p 或 int const *p
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。

(2)常量指针
常量指针(const pointer)是该指针不能改变指向,即地址值不能变。常量指针必须初始化,而且一旦初始化完成,它的值就不能再改变了。写作:int *const p,从形式上就能看出const修饰的是p,即指针。不过常量指针所指向的变量的值倒是可以变。

int errNumb = 0;
int *const curErr = &errNum;//curErr将一直指向errNumb

const double pi = 3.14;//变量pi是常量
const double *const pip = π//pip左边有个const限定,所以也是一个常量指针,外面还有一个const是因为他指向的是pi是个常量

*pip = 4.12;//错误,因为pip指向的pi是常量
*curErr = 2;//正确,因为errNumb不是常量
curErr = pip;//错误,因为curErr是常量指针

上面的const double *const pip = &pi中有两个const,我们把靠近指针pip的叫作顶层const (top-levep const),它表示指针本身是个常量,而最左边的叫作底层const (low-level const)表示指针所指的对象是个常量。
当然,顶层const和底层const具有一般性的应用,顶层const可以表示任意的对象是常量,这一点对任何数据类型都有用,如算数类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。不过指针类型既可以是顶层,也可以是底层const,这一点和其它类型区别明显。

int i = 0;
const int ci = 42;//对ci来说,这是个顶层const,它使得ci的值不可变
const int *p = &ci;//对p来说,这是个底层const,p可以变,只是不能通过p去修改它指向的值
int &r = ci;//错误,普通的int型引用不能绑定到int常量上
const int &r2 = i;// 正确,即const型的引用可以绑定到普通的int上

因为 非常量 可以转成 常量,反之则不行
如非常量i可以被常量r2引用,而常量ci不能赋值给常量r,否则就可以通过引用r去修改ci,但ci是常量。不可被修改。而r2不能修改i,这也没事,i可以通过其他方式修改,这与第一部分是自洽的。
这也是为什么在以const修饰的引用作为形参的函数如fun(const int& a)中,可以将 非常量 (如int b = 0;fun(b);)作为实参去调用该函数。但是反过来就不行,就像第一部分例子中的“hello world”不能作为find_char的实参。

本文大多参考自C++ primer 5th
对应中文版页数为:p54 - p58 、p79 、p190 - p192

<think>好的,我现在需要帮助用户理解C++const指针常量const常量指针的区别及使用场景。首先,我得回忆一下const指针中的应用规则。根据用户提供的引用,特别是引用1提到的“左数右指原则”,也就是const在*左边还是右边,这会影响指针的性质。 用户提到的引用中有几个关键点需要整合。比如,引用2解释了顶层const底层const的概念,顶层const表示指针本身是常量,而底层const表示指针指向的数据是常量。然后引用3和引用4进一步说明了常量指针指针常量的不同声明方式以及使用场景。例如,常量指针const int *p)用于函数参数,确保不修改指向的数据,而指针常量(int *const p)用于指针本身不能改变的情况,比如引用中的例子类似于引用的行为。 我需要先明确两者的定义。指针常量是指指针本身的值(即指向的地址)不可变,但可以通过指针修改所指向的内容。而常量指针则是不能通过指针修改所指向的内容,但指针可以指向其他地址。然后,当两者结合时,比如const int *const p,就既不能修改指针的指向,也不能修改指向的内容。 接下来要考虑如何将这些概念清晰地传达给用户。可能需要用例子来对比说明。例如,指针常量的声明是int *const p = &a;,这里const在*右边,所以p的地址不能变,但*p可以修改。而常量指针const int *p = &a;,这里const在*左边,所以p可以指向不同的地址,但不能通过p修改a的值。 同时,引用5提到当指向常量时,必须使用指向常量指针,否则会编译错误,比如用非常量指针指向常量变量是不允许的。这也需要注意在例子中说明,避免用户犯错。 最后,总结使用场景:常量指针适合用于函数参数,保护数据不被修改;指针常量用于需要固定指针指向的情况,比如引用。而两者的结合则用于需要完全不可变的场合。需要确保这些例子和解释符合用户提供的引用内容,并且正确无误。</think>### C++ const 指针常量 vs const 常量指针 区别使用场景 #### 一、核心区别(左数右指原则) 1. **常量指针底层const)** - **定义**:`const int* p` 或 `int const* p`(`const` 在 `*` 左侧) - **特性**:指针指向的内容不可修改,但指针本身可以指向其他地址 - **示例**: ```cpp int a = 10, b = 20; const int* p = &a; // ✅ p可以指向a p = &b; // ✅ 允许修改指针指向 // *p = 30; // ❌ 禁止通过p修改内容 ``` 2. **指针常量顶层const)** - **定义**:`int* const p`(`const` 在 `*` 右侧) - **特性**:指针本身不可修改(固定指向一个地址),但可以通过指针修改内容 - **示例**: ```cpp int a = 10; int* const p = &a; // ✅ 初始化后p固定指向a *p = 20; // ✅ 允许修改内容 // p = &b; // ❌ 禁止修改指针指向 ``` 3. **双重const顶层+底层)** - **定义**:`const int* const p` - **特性**:指针指向的地址和内容均不可修改 - **示例**: ```cpp const int x = 5; const int* const p = &x; // ✅ 指向常量常量指针 // *p = 10; // ❌ 禁止修改内容 // p = &y; // ❌ 禁止修改指向 ``` #### 二、使用场景对比 | 类型 | 典型场景 | 类似行为 | |--------------------|------------------------------------------------------------------------|-------------------| | 常量指针 (`const int*`) | 函数参数保护数据:`void print(const int* arr)`[^3] | 类似 `const int&` | | 指针常量 (`int* const`) | 固定内存管理:`char* const buffer = malloc(1024)`[^4] | 类似引用 (`int&`) | | 双重const | 硬件寄存器访问:`volatile const uint32_t* const REG = 0xFFFF0000;`[^5] | 完全只读操作 | #### 三、易错点编译规则 1. **常量指针变量混用** ```cpp const int x = 10; int* p = &x; // ❌ 错误:非常量指针指向常量 const int* p2 = &x; // ✅ 正确 ``` 2. **指针常量的初始化** ```cpp int a = 5; // int* const p; // ❌ 错误:必须初始化 int* const p = &a; // ✅ 正确 ``` 3. **类型转换规则** - 常量指针可隐式转换为非常量指针(需显式强制转换) - 指针常量无法隐式转换类型(地址绑定固定类型)[^2] #### 四、底层实现视角 1. **引用指针常量的关系** ```cpp int a = 10; int& ref = a; // 等价于 int* const ptr = &a; ref = 20; // 等价于 *ptr = 20; [^4] ``` 2. **内存访问保护** - 常量指针通过编译器检查阻止写入操作 - 指针常量通过固定地址实现稳定的内存访问[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值