在 C++ 中,指针、const 和数组之间有一些重要的关系。指针和 const 可以用于限定指针的可变性和所指向的值的可变性,而指针可以与数组进行关联,方便对数组进行遍历和操作。
指针和 const
指针可以和 const 修饰符结合,常见的有两种形式:
- 一种是指针指向的是一个常量
- 另一种是指针本身是一个常量
(1)指向常量的指针
指针指向的是一个常量,所以只能访问数据,不能通过指针对数据进行修改。不过指针本身是变量,可以指向另外的数据对象。这时应该把 const 加在类型前。
const int a = 10, b = 20;
//int* pa = &a; // 错误,类型不匹配
const int* pa = &a; // 正确,pa是指向常量的指针,类型为const int*
pa = &b; // pa可以指向另一个常量
int i = 1024;
pa = &i; // pa也可以指向变量
*pa = 1000; // 错误,不能通过pa更改数据对象
// const修饰的是指针,指针指向可以改,指针指向的值不可以更改
这里发现,pc
是一个指向常量的指针,但其实把一个变量 i 的地址赋给它也是可以的;编译器只是不允许通过指针 pc
去间接更改数据对象。
(2)指针常量(const指针)
指针本身是一个数据对象,所以也可以区分变量和常量。如果指针本身是一个常量,就意味它保存的地址不能更改,也就是它永远指向同一个对象;而数据对象的内容是可以通过指针改变的。这种指针一般叫做“指针常量”。
指针常量在定义的时候,需要在星号 * 后、标识符前加上 const。
int* const cp = &i;
*cp = 2048; // 通过指针修改对象的值
cout << "i = " << i << endl;
//cp = &c; // 错误,不可以更改cp的指向
// 套娃组合:既修饰指针又修饰常量
const int* const ccp = &c; // ccp是一个指向常量的常量指针
//cpp = &i; //错误
//*cpp = 100; //错误
// const修饰的是常量,指针指向不可以改,指针指向的值可以更改
这里也可以使用两个 const,定义的是“指向常量的常量指针”。也就是说,ccp 指向的是常量,值不能改变;而且它本身也是一个常量,指向的对象也不能改变。
总结:看 const 右侧后面跟的是指针还是常量, 是指针就是常量指针,是常量就是指针常量。
指针和数组
作用: 利用指针访问数组中元素。
(1)数组名
用到数组名时,编译器一般都会把它转换成指针,这个指针就指向数组的第一个元素。所以我们也可以用数组名来给指针赋值。
int arr[] = {1, 2, 3, 4, 5};
cout << "arr = " << arr << endl;
cout << "&arr[0] = " << &arr[0] << endl;
int* pia = arr; // 可以直接用数组名给指针赋值
cout << "* pia = " << *pia << endl; // 指针指向的数据,就是arr[0]
也正是因为数组名被认为是指针,所以不能直接使用数组名对另一个数组赋值,数组也不允许这样的直接拷贝:
int arr[] = {1, 2, 3, 4, 5};
//int arr2[5] = arr; // 错误,数组不能直接拷贝
(2)指针运算
如果对指针 pia
做加 1 操作,我们会发现它保存的地址直接加了 4,这其实是指向了下一个 int 类型数据对象:
pia + 1; // pia + 1 指向的是arr[1]
*(pia + 1); // 访问 arr[1]
所谓的“指针运算”,就是直接对一个指针加/减一个整数值,得到的结果仍然是指针。新指针指向的数据元素,跟原指针指向的相比移动了对应个数据单位。
(3)指针和数组下标
在 C 学习中就知道,数组名 arr 其实就是指针。这就带来了非常有趣的访问方式:
*arr; // arr[0]
*(arr + 1); // arr[1]
这是通过指针来访问数组元素,效果跟使用下标运算符 arr[0]
、arr[1]
是一样的。进而我们也可以发现,遍历元素所谓的“范围 for 循环”,其实就是让指针不停地向后移动依次访问元素。
for (int num : arr)
cout << num << '\t';
(4)指针数组和数组指针
指针和数组这两种类型可以结合在一起,这就是“指针数组”和“数组指针”。
- 指针数组:一个数组,它的所有元素都是相同类型的指针
- 数组指针:一个指针,指向一个数组的指针
int arr[] = {1, 2, 3, 4, 5};
int* pa[5]; // 指针数组,里面有5个元素,每个元素都是一个int指针
int(* ap)[5]; // 数组指针,指向一个int数组,数组包含5个元素
cout << "指针数组pr的大小为:" << sizeof(pa) << endl; // 40
cout << "数组指针ap的大小为:" << sizeof(ap) << endl; // 8
pa[0] = arr; // pa中第一个元素,指向arr的第一个元素
pa[1] = arr + 1; // pa中第二个元素,指向arr的第二个元素
ap = &arr; // ap指向了arr整个数组
cout << "arr =" << arr << endl;
cout << "* arr =" << *arr << endl; // arr解引用,得到arr[0]
cout << "arr + 1 =" << arr + 1 << endl;
cout << "ap =" << ap << endl;
cout << "* ap =" << *ap << endl; // ap解引用,得到的是arr数组
cout << "ap + 1 =" << ap + 1 << endl;
这里可以看到,指向数组 arr 的指针 ap
,其实保存的也是 arr 第一个元素的地址。arr 类型是 int*,指向的就是 arr[0]
;而 ap
类型是 int(*)[5]
,指向的是整个 arr 数组。所以 arr + 1
,得到的是 arr[1]
的地址;而 ap + 1
,就会跨过整个 arr 数组。