一、指针
1.指针是内存中一个最小单元的编号,也就是地址。
2.平时口语中所说的指针,通常指的是指针变量,就是用来存放内存地址的变量。
总的来说,指针就是地址,通常说的指针就是指针变量。
内存:

指针变量:
通过&(取地址操作符)取出变量存放在内存的起始地址,然后存放到一个变量中,那么这个变量就是指针变量。
cpp
int a = 10;
int* pa = &a; //pa就是指针变量
对于32位的机器,假设有32根地址线,每根地址线都能产生0和1两种信号,那么32根地址线能产生地址就会是:
cpp
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
...
11111111 11111111 11111111 11111111
总共有2^32次方个地址。
给每个地址一个字节,那我们就可以对4GB的空间进行编址。(2^32Byte==2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB==4GB)
注:指针变量在32位平台的大小是4个字节,在64位平台的大小是8个字节。
二、指针和指针类型
指针的定义方式是:type + * 。
因此指针变量相应的类型有:
cpp
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
float* pf = NULL;
double* pd = NULL;
long* pl = NULL;
...
char*类型的指针是为了存放char类型变量的地址。
int*类型的指针是为了存放int类型变量的地址。
float*类型的指针是为了存放float类型变量的地址。
...
2.1 指针+-整数

int*类型的指针在+1时跳过了4个字节。
short*类型的指针在+1时跳过了2个字节。
char*类型的指针在+1时跳过了1个字节。
int*类型的指针在+n时跳过了sizeof(int)*n个字节。
总结:指针的类型决定了指针+/-n时跳过的距离,跳过的距离为n*sizeof(type)个字节。
2.2 指针的解引用


int*的指针解引用访问了4个字节。
char*的指针解引用访问了1个字节。
type* pi:
1.type是pi指向的对象的类型。
2.*说明pi是指针变量。
3.pi解引用的时候访问的字节大小是sizeof(type)。
结论:指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)。
三、野指针
野指针是指指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)。
3.1 野指针成因
1.指针未初始化
指针的初始化是指:
1.1 明确知道要初始化的地址并直接进行了初始化。
1.2 不知道要初始化为什么地址,但暂时初始化为NULL。
指针未初始化是指:
局部指针变量没有初始化,默认为随机值。
cpp
int* pa; //未初始化的指针,默认为随机值
2.指针越界访问
cpp
int arr[10];
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针所指向的位置超出数组arr的范围时,p就是野指针
*(p+i) = 0;
}
3.指针指向的空间已经被释放
cpp
int* test()
{
int a = 10;
return &a;
}
int* p = test();
3.2 规避野指针
1.指针初始化。
2.小心指针越界。
3.指针指向的空间释放后指针及时置NULL。
4.避免返回局部变量的地址。
5.指针使用之前做有效性检查。
四、指针运算
指针+/-整数、指针-指针、指针的关系运算
4.1 指针+/-整数
指针的类型决定了指针+/-n时跳过的距离,跳过的距离为n*sizeof(type)个字节。
4.2 指针-指针
cpp
char str[] = "abcd";
char* ps = str;
while ((*ps) != '\0')
{
ps++;
}
int len = ps - str;
注:指针-指针得到的数值的绝对值是指针和指针之间元素的个数,而不是字节大小。指针相减的前提是两个指针指向了同一块空间。
4.3 指针的关系运算
指针的关系运算有>、<、>=、<=、!=、==等等。
cpp
int arr[10] = { 0 };
int* pa = NULL;
for (pa = &arr[9]; pa >= &arr[0]; pa--)
{
*pa = 1;
}
严格的C99标准规定:允许指向数组元素的指针和最后一个元素后面的那个内存地址进行比较,但不允许和指向第一个元素的前一个内存地址进行比较。

五、指针和数组
指针和数组之间的关系:
指针变量就是指针变量,不是数组,指针变量的大小是4/8个字节,是专门用来存放地址的。
数组就是数组,不是指针,数组是一块连续的空间,可以存放一个或多个类型相同的数据。
数组中数组名一般是指首元素的地址,数组名==地址==指针,数组是可以通过指针来访问的。
cpp
int arr[10] = { 0 };
int* p = arr; //p存放的就是arr首元素的地址
当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以我们就可以通过指针来遍历访问数组。
cpp
int arr[10] = { 0 };
int* p = arr; //p存放的就是arr首元素的地址
int i = 0;
for (i = 0; i < 10; i++)
{
*(p+i) = i; //指针遍历访问数组
}
六、二级指针
指针变量也是变量,是变量的话那也就会有地址,存放一级指针变量的地址的指针就是二级指针。

对于二级指针的运算有:
*ppa通过对ppa进行解引用,找到的就是pa,*ppa访问的其实是pa。
cpp
*ppa == &a; //*ppa和&a是等价的
**ppa通过*ppa找到pa,然后对pa进行解引用找到a,**ppa访问的其实就是a。
cpp
**ppa = 30;
//等价于*pa==30
//等价于a==30
七、指针数组
指针数组本质上是一个数组,用于存放指针变量的数组。
cpp
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* pa[] = { arr1,arr2,arr3 }; //此处的pa就是指针数组,里面存放的类型是int*
我们可以通过指针数组来模拟一个二维数组,但和二维数组有区别的是:
真实的二维数组在空间中是连续的,指针模拟的二维数组在空间中可能是不连续的。
