今天我们来探讨C/C++中const、指针和引用的相关问题。这些概念是编程中的重要组成部分,它们的正确使用对于代码的可读性和可维护性至关重要。通过深入了解const的不可变性、指针的灵活性以及引用的简洁性,我们能够更好地掌握编程的精髓,并写出更加健壮和高效的代码😊😊😊
浅谈C/C++
- (1)const + 一级指针
- (2)const + 二级(多级)指针
- (3)引用操作符 &
先来看看对const的描述,C/C++中的const关键字用于声明一个常变量,即一个“不可修改的值”,通过使用关键字const,我们可以明确地指定一个变量、函数参数、函数返回值或类成员函数为只读,从而禁止对其进行修改,增强程序的健壮性。可见我们对于 const 的描述,常见为一个不可修改!!!,但在C和C++中,编译器对于 const 的反应也是 有差异的
1、我们先来看看.c文件 -》 C语言中的const,猜猜有何问题?
#include<stdio.h> int main() { const int a; int array[a] = { 0 }; const int b = 0; b = 5; int* p = (int*)&a; *p = 30; printf("%d %d %d\n", a, *p, *(&a)); }
我们不难想到
array[a]
会报错,因为a没有被初始化,定义数组需要有一个常量值作为数组的长度,而a不可推测;b = 5
,变量b为常变量,不可被修改- 然而,对于涉及到变量
p
的语句,朋友是否有些疑惑🤔🤔🤔,p
是指向变量a
地址的普通指针,而*p = 30
就是将它指向的地址上对应的值改为30,即分配给变量a
的地址空间中的值 被改了🤣🤣🤣,我们去掉错误语句,加上a = 20
后,看看执行的结果
由此可见,我们可以归纳出 C语言编译器 下有关
const
的特性在C语言编译器中 a) const修饰可以不进行初始化,被看作常变量 b) 被const修饰的变量只是不能作为左值被修改,通过指针或引用的方式能够修改
2、我们再来看看.cpp文件 -》C++语言中的const,猜猜有何问题?
int main() { const int b; int c = 5; const int a = c; //const int a = 20; int array[a] = { 0 }; int before = a; int* p = (int*)&a; *p = 30; int after = *(int*) & a; cout << before << endl << after << endl; cout << a << " " << *p << " " << *(&a) << endl; return 0; }
不难想到
const int b;
会报错,因为在C++中const修饰的变量被看作常量,不能够不对它进行初始化- 在
array[a]
报错,变量a
为变量c
赋值而来,而c
是一个变量,赋给变量a
后,变量a
也作为变量,而array[]里需要一个常量,所以出现错误,如下图
除去错误语句,把const int a = 20
后的执行结果由此我们可以了解到
在C++语言编译器中 a) const修饰的变量必须初始化,不能作为左值 b) 被const修饰的变量只是不能作为左值被修改,通过指针或引用的方式能够修改 c) 被const修饰的变量被叫做常量,若直接赋值一个变量,不能直接作为数组的长度 c) 在C++编译器中,执行程序遇到const变量的时候,会直接被看作是常量,直接用初值替换,即*(&a) -> 20
本来我们使用const修饰变量的目的就是保证变量不被修改,然上面我们能够看到,如果我们把一个常量的地址泄露给了一个普通指针或者引用,那么就会有被修改的风险,因此我们需要对此问题做相应的处理,也就是下面我们将要介绍的 const + 指针
的结合
📢📢📢注意:在C++语言规范中,const修饰的类型是离它最近的类型
(1)const + 一级指针
我们先来看看一级指针 + const
的情况,如下:
//根据上面的注意项,有
const int *p; -> const修饰int,即值*p不可修改,指针可以任意指向p = &a
int const *p; -> const修饰int,同上
int* const p; -> const修饰int*,即指针指向p不可修改,值*p可以任意
int* const *p; -> const同时修饰int*和int,都不能修改
看到这,我们就能够自己解决:当泄露了常量的指针或引用时,常量可能被修改的问题。但是对于const
和指针
的各种组合类型中,也并非都能够相互进行强制转化的。
📢📢📢这里又需要注意一点:const右边如果没有指针*的话,const是不会参与进推测的类型
int* q1 = nullptr;
int* const q2 = nullptr;
const int* q3 = nullptr;
int const* q4 = nullptr;
int* const* q5 = nullptr;
cout << typeid(q1).name() << endl; // -> int*
cout << typeid(q2).name() << endl; // -> int*
cout << typeid(q3).name() << endl; // -> const int* / int const*
cout << typeid(q4).name() << endl; // -> const int* / int const*
cout << typeid(q5).name() << endl; // -> int* const*
//以上代码,朋友可以自己去验证一下是否正确
这里我们再给出一段代码,来看看是否正确🤔🤔🤔
#include<iostream> using namespace std; int main() { int a = 5; const int* pp = &a; //const int* pp = nullptr; int* qq = p; //cout << typeid(pp).name() << endl; char s = 'a'; const char* ss = &s; char* sss = ss; return 0; }
整形常量指针
p
指向的是普通变量a
的地址,整形变量指针p
,也是普通变量a
的地址,既然是普通变量,那么普通变量a
的值应该是能被修改是吧🤫🤫,然当我们放在visual studio 2022上时,发现它报错了!!!,其实这更变量a
无关,若我们将pp
指向nullptr
,仍然是一样的结果。因为在类型上,编译器是禁止将const int*
类型的数据转换成int*
类型的,使用char
等其他类型也是如此
(2)const + 二级(多级)指针
我们再来看看const + 二级(多级)指针
的情况,如下:
const int**q; -> **q 不能被赋值
int* const *q; -> *q 不能被赋值
int** const q; -> q不能被赋值
📢📢📢注意:对于const + 多级指针
的结合,涉及到类型转化时
错误:
const int* -> int* ;
int** -> const int** ;
int* const* -> int** //const修饰一级指针
《=》 const* -> *
《=》 const int* -> int* 是错误的!
const int** -> int** ;
正确:
int* -> const int* ;
int** -> int* const*
《=》 * -> const*
《=》 int* -> const int* 是正确的!
这里我们给出一段代码,来看看是否正确🤔🤔🤔
#include<iostream> using namespace std; int main() { int a = 10; int* p = &a; const int **q = &p; // const int** <- int** return 0; }
这里我们可以把换换const int* *q
,即*q
是整形常量的指针 《=》p
,所以当我们把一个整形常量的地址赋值给*q
时,const int b = 20; *q = &b
,相当于直接就修改了p
,使其指向了常量的地址,把变量b
的指针间接地泄露给了普通指针*p
,系统报错!!!
—>
解决方法:const int* const*q = &p;
- 我们让其不能够给
*q
赋值,即禁用*q = &b
(3)引用操作符 &
引用是C++种的一种特殊数据类型,它提供了对现有别名的访问,通过引用可以使用相同的变量名来访问同一个内存地址的内容,一旦被初始化后,它将一致引用同一个对象,并且不能再引用其他对象,因此引用也被叫做更安全的指针!对比一下指针和引用的区别:
- 引用是必须初始化的,指针可以不被初始化
- 引用只有一级,而指针有多级
- 在汇编层面上,定义或修改引用变量和指针变量是一样的
从以下代码即图例可以看出:
#include<iostream> using namespace std; void swap(int& a, int& b) { int t = a; a = b; b = t; } //引用底层还是转化为指针>实现 void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; } int main() { int a = 10, b = 20; swap(&a, &b); swap(a, b); int *p = &a; int &q = a; int array[5] = {}; int (&vv)[5] = array; return 0;
引用又可分为两种:左值引用 和 右值引用
- 左值引用: 对象的一个命名引用,它绑定到左值(如一个具名变量),即有名字、有内存,通过左值引用,可以修改被引用对象的值。
- 右值引用: 对临时对象或将要销毁的对象的引用,它绑定到右值(如一个临时对象,一个匿名对象,或一个将要销毁的对象),通过右值引用,可以实现资源的高效转移和移动语义
#include<iostream>
using namespace std;
{
int a = 10;
//int &&b = a; //错误右值引用变量无法绑定到左值
int &&c = 20; //c++11提供了右值引用,汇编指令上产生临时量
const int &d = a; //左值引用可以指向左值
int &e = a;
int &f = c; //右值引用本身是一个左值
return 0;
📢📢📢注意:左值引用变量能够拥有右值和左值,但右值引用变量只能引用右值
上面就是关于const 、 指针 、 引用
的问题,学而不思则罔,思而不学则殆,下面给出几道题目,来检验一下我们的学习成果
A) int a = 10
const int *p = &a
int *q = p
xxx
B) int a = 10
int* const p = &a
int *q = p ggg
C) int a = 10
int* const p = &a
const int *q = p ggg
D) int a = 10
int *p = &a
const int **q = &p xxx
E) int a = 10
int *p = &a
int* const* q = &p ggg
F) int a = 10
int *p = &a
int ** const q = &p ggg
G) int a = 10
int* const p = &a
int **q = &p int** <- int* const*
xxx
H) int a = 10
const int*p = &a
int* const* q = &p int* const* <- const int**
《=》 int* <- const int*
xxx
引用是不参与推测类型的
I) int a = 10
int *p = &a
int *&q = p 《=》 int **q = &p
ggg
J) int a = 10
int* const p = &a
int *&q = p 《=》 int **q = &p
xxx
K) int a = 10
int *p = &a
const int* &q = p; 《=》 const int**q = &p
ggg
🌻🌻🌻以上就是浅谈C/C++的常量const、指针和引用的有关问题,如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏 🌻🌻🌻