前言
这是一篇跟着FishC大佬的C++指针部分学习笔记,以前一直对指针折腾的云里雾里,希望这篇能对大家有所帮助。如有纰漏,望各位大佬指正。
指针(一)
想了解指针,必须先了解地址。
首先需要了解:程序是在硬盘上以文件的形式存在的,但它们的运行却是发生在计算机的内存中。
演示一下变量在内存中的存放情况。
- int a = -12;
- char b = M;
- float c =3.14;
这里需要来讨论一下对齐,为什么上图中c要从8开始存储呢??
其实,在C++里,变量类型是根据它们的自然边界进行对齐的!不过这个我们只需要知道即可,因为编辑器会自动帮我们处理这类问题。另外,对齐问题会因为系统平台不同而不同。
接下来谈一下寻址,对于变量可以通过两种方法来对它进行索引。
一种是通过变量名
一种是通过地址。
这里引入一个新的操作符,叫做:取址操作符:&,它的作用是获得变量的地址。
我们习惯这么使用:
- int var = 123;
- std::cout <<"Address is:"<<&var;
使用指针
上边我们对地址这个概念是理解和使用指针的基础。地址是计算机内存中的某个位置,而指针是专门用来存放地址的特殊类型变量。一般情况下,我们用下面的形式来声明指针变量:
type *pointerName;
例如:
- int *p;
- int pp = 123;
- p = &pp;
在创建指针时,空格放在哪里都是没关系的,下边的语法都是可以接受的。
- int *p1;
- int * p1;
- int* p1;
指针变量前边的类型是用来说明指针指向的数据的类型,请务必匹配来使用。
另外,允许void类型的指针变量:void*p
指针(二)
温故而知新:
- 创建变量时,系统会分配一些内存块用来存储它们的值;
- 每个内存块都拥有一个独一无二的地址;
- 变量的地址可以用&variablename语法来取得;(注:&我们称为“取地址”操作符)
- 可以把地址赋值给一种称为指针的特殊变量;
- 指针的类型必须与由它保存其地址的变量的类型一致。
接下来举一个栗子:
- int a = 456;
- char b = ‘C’;
- int *aPointer = &a;
- char *bPointer = &b;
这会让程序保留四个内存块,两个为变量保留,两个为指针保留。变量a和变量b中存放的是变量的值(456和‘C’的ASCII码值);两个指针变量存放着的是指针的值,这些值是其他变量的地址。
为什么aPointer指针变量中存储的是0而不是0-3呢,因为既然知道存储的a为整形变量,那么只需要存储它的首地址再加上sizeof(a)即可
当我们知道了某个变量在内存中的地址(通过指针),就可以利用指针访问位于改地址的数据。
这需要对指针进行“解引用(Deference)”处理:即在指针名的前面加上型号(*)。
例如:std::count << *aPointer;
这里我们理解一下:把整数变量a的地址存在在aPointer指针之后,*aPointer和变量a将代表同一个值
因此:*aPointer = 123;将会导致下图所示结果:
一定要牢记:指针所保存的是内存中的一个地址。它并不保存指向的数据的值的本身。因此,务必确保指针对应一个已经存在的变量或者一块已经分配的内存。
重点部分:关于星号的用途
第一种是用于创建指针:
int *myPointer = &myInt;
第二种是对指针进行解引用:
*myPointer = 3998;
Tips:
C++允许多个指针指向同一个地址,就是多个指针有相同的值,任意一个进行了修改,其从放的值都会发生修改。
C++支持无类型(void)指针,就是没有被声明为某种特定类型的指针,例如
void *vPointer;
注意:对一个无类型指针进行解引用前,必须先把它转换成一种适当的数据类型。
指针和数组(三)
在上述两讲中关于地址和指针的栗子中,我们使用的都是标量类型:整数、实数和字符。
所以当我们遇到一个标量类型的变量时,我们可以创建一个与其类型相同的指针来存放它的地址。如果我们遇到的是数组(是一组数,而不是一个数)该怎么办呢?
我们知道,计算机把数组是以一组连续的内存块保存的
例如:int myArray[3] = {1,2,3};
存储形式如下图所示:
这就说明了数组拥有很多个地址,每个地址对应着一个元素。数组的名字其实也是一个指针(指向数组的基地址,也就是第一个元素的地址)
比如上述那个栗子,一下两句话做的事同一件事情:
- int *ptr1 = &myArray[0];
- int *ptr2 = myArray;
我们可以轻易地将数组的基地址用指针变量保存起来,那如果我们要通过指针访问其他数组元素,如何办到呢?
试试:ptr1++;
以上运算并不是将地址值简单的做+1处理,而是安装指向的数组的数据类型来递增的,也就是说+sizeof(int)
思考:
如有:
int Array[5] = {1,2,3,4,5};
int *ptr = Array;
则
*ptr + 1;
*(ptr + 1);
二者有什么区别呢??
#include <iostream>
int main()
{
int Array[5] = {1,2,3,4,5};
int *ptr = Array;
std::cout << *ptr + 1 << '\n';
std::cout << *(ptr + 1) << '\n';
return 0;
}
程序运行之后得到的的结果为2,难道二者没有区别吗?
答案肯定是有区别的。
*ptr+1是指针变量ptr(数组)指向的首地址(第一个)元素的值加1。
*(ptr+1)是指针变量ptr(数组)指向的首地址后的(第二个)元素的值;
注:有括号先算括号里面的,也就是*(ptr+1)中先将指针指向ptr+1,再求值;而*ptr+1,先求*ptr,再加1。所以二者值相等仅仅是巧合而已~
小结:
指针运算的重要性在高级和抽象的程序设计工作中体现的非常明显,就目前而言,只需要记住数组的名字同时也是指向其第一个元素(基地址)的指针。
数组可以是任意一种数据类型,这意味着我们完全可以创建一个以指针为元素的数组。
未完待续……
参考资料
FishC的《C++快速入门》大家可以在B站上搜到
https://www.bilibili.com/video/av7595819?from=search&seid=12323634976003104863