指针概念
程序运行的数据都是存储在内存中的不同区域,因此访问某个变量的数据时就得先能找到对应的内存区域,然后取出数据,为了便于内存的操作,因此将内存以字节为单位进行存储区域的划分,并且给每个区域进行了编号——地址。
指针:内存地址。
指针变量拥有一块空间,空间内部可以存储一个内存地址(指向另一块空间),通过对指针变量进行解引用,进而访问所指向空间中的数据。
🔴类型
指针自身是指针类型,有一块空间保存地址;
指针变量保存的地址所指向的空间类型,描述了指向的空间可以有多大。
int a=10;
int *p=&a;
![](https://i-blog.csdnimg.cn/blog_migrate/52597c7bac2ced05c7ff39ff221bc8ab.png)
p指针指向的就是a空间的起始地址。
🔵指针的大小
32位平台下,指针变量所占空间大小是4字节;
64位平台下,指针变量所占空间大小是8字节;
在计算机中,CPU访问内存是通过地址总线,告诉内存要访问哪一块内存。
32位平台,CPU地址总线共有32根,以高低电平表示01组合共有2^32种,因此所能访问的地址范围就是0~2^32-1;
64位平台,CPU地址总线共有64根,以高低电平表示01组合共有2^64种,因此所能访问的地址范围就是0~2^64-1;
因此,指针变量的空间想要保存一个完整的地址,在32位平台下就需要32个比特位-4字节,64位平台下就需要64个比特位-8字节。
🔴指针指向空间的访问
通过对指针解引用进行访问。
![](https://i-blog.csdnimg.cn/blog_migrate/40b53faf850f01181f7ad9d7650e2e03.png)
🔵野指针
指针所指向的空间不能被正常访问。
危害:野指针的访问随时有可能
1.定义一个指针变量后,没有对这个指针变量进行初始化;
定义一个指针变量,为指针开辟了内存空间,空间中如果不进行初始化,里边有什么数据是未知的。因此,这个指针变量所指向的位置也就是未知的,是大概率不能访问的。
int a = 10;
int *p ;
*p = 20;
printf("%d %d\n", a, *p);
![](https://i-blog.csdnimg.cn/blog_migrate/120e929c38a8577606b3119e8fced921.png)
2.指针的越界访问
在访问连续空间时,控制不当导致访问了不该访问的位置。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int arr[3] = { 1, 3, 2 };
int *p = arr;//数组名就是数组首元素的地址,首元素是int类型,则int类型的地址就是int*类型
printf("%p %p %d\n", &arr[0], p, *p);
printf("%p %p %d\n", &arr[1], p + 1, *(p + 1));
printf("%p %p %d\n", &arr[2], p + 2, *(p + 2));
printf("%p %p %d\n", &arr[3], p + 3, *(p + 3));
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/0f4732fe4cb7190c589813677ef2c6cc.png)
p指向了数组首元素, p+1指向了数组的第1个元素……
p是一个指针变量,保存的是地址,对p进行+1,就是p加上一个指向类型大小。
比如,int *p; p+1实际上就是对p所保存的地址+4,因为指向int类型大小为4字节。
注:这里的&arr[3]不算越界访问,因为这里只是获取了指定位置元素的地址,并没有访问这个地址所指向的空间的数据。但是*(p+3)是越界访问,因为访问了越界位置的数据。
有时候程序的越界访问并不会立即导致程序崩溃,因为是否发生内存访问错误取决于编译器对于越界访问的检测机制。编译器设置了一个 哨兵位作为越界检测位置(不一定就在正常空间的结束位置)。
3.指针所指向的空间被释放
#include<stdio.h>
#include<stdlib.h>
int *func()
{
int a = 10;
return &a;//返回a变量的地址
}
int main()
{
int *p = func();
printf("%d\n", *p);
system("pause");
return 0;
}
a变量是在func函数中被定义的,所属的作用域就是在func函数中,函数一旦退出运行,则a变量的空间就会被释放掉。因此,一个函数如果返回的是一块局部变量的地址,是没有意义的,因为函数运行结束空间就会被释放掉。上面代码返回的就是野指针。
如果修改为:
static int a=10;
这时,static修饰局部变量,修改了局部变量的生命周期,导致a变量的空间不会被释放,不会出现野指针。
🔴指针的运算
指针的加减并不是以字节为单位进行加减,而是以指向空间的类型大小为单位进行偏移或者加减。
int *p;//p+1就是向后偏移一个int大小的空间
char *p;//p+1就是向后偏移一个char大小的空间
short *p;//p+1就是向后偏移一个short大小的空间
关系运算:比大小。
示例:通过指针实现字符串逆置。
#include<stdio.h>
#include<stdlib.h>
char *reverse(char *str)
{
char *left = str;
char *right = str + strlen(str) - 1;
for (int i = 0; left < right; left++, right--)
{
char ch = *left;
*left = *right;
*right = ch;
}
return str;
}
int main()
{
char buf[] = "hello world";
printf("%s\n",reverse(buf));
system("pause");
return 0;
}
运行结果:dlrow olleh
🔵指针和数组
#include<stdio.h>
#include<stdlib.h>
int main()
{
int arr[] = { 1, 3, 2, 4, 5 };
int sz = sizeof(arr) / sizeof(arr[0]);
int *p = arr;
for (int i = 0; i < sz; i++)
{
//printf("%d\n", *(arr+i));
//printf("%d\n", *(p++));
//后置++先试用再加一,因此先对p解引用,再加一。但是arr数组名不允许++
printf("%d\n", p[i]);
}
system("pause");
return 0;
}
数组名不允许++,数组名是首元素的地址,可以看做一个数值常量,不允许被修改;同时,数组名数组名不仅代表数组首元素的地址,也代表数组,因此不能进行自增自减运算。
数组名是数组首元素的地址,可以当做指针使用,通过指针+-偏移后解引用进行访问;
同样 指针变量也可以当做一个数组名来使用,通过 下标进行访问。
![](https://i-blog.csdnimg.cn/blog_migrate/4a9b4e67260dcda9c5b938aafcfc1412.png)
指针在转换为数组访问的时候,[0]是从自己指向的位置进行计算的。
![](https://i-blog.csdnimg.cn/blog_migrate/53a6f30e03c4c09e9ef48a2f86981dff.png)
虽然实参看起来传递的是数组,但实际上是数组首元素的地址,因此,形参就是对应首元素地址的指针变量。形参不能通过sizeof获取到数组的大小,而是获取到了指针的大小,所以,通过数组传参时,通常会多加一个参数来传递数组中的元素个数,
🔴二级指针
表示的是一个指向指针变量空间的指针(有几个*就是几级指针)。
int a=10;
int *p=&a;
int **pp=&p;
多级指针的大小本质上还是指针大小。
🔵指针数组
指针数组就是一个或多个指针数据的集合。
指针数组就是存储指针的数组,数组中的元素类型都是指针类型。
定义:int *arr[10];
[ ]的优先级比*高,因此arr先与[ ]结合,表示arr是个数组,数组中有10个元素;
arr[10]再与*结合,表示数组中的元素是指针类型;
最后 *arr[10]再与int结合,表示数组中的元素是指针,指针指向的空间类型是int。
arr是个数组,数组中有10个int*类型的元素。
![](https://i-blog.csdnimg.cn/blog_migrate/cfe1de998b6969dccee52534a0521afd.png)
示例:编写一个函数交换两个int*类型的数据。
#include<stdio.h>
#include<stdlib.h>
void myswap(int **ppa, int **ppb)
{
int *temp = *ppa;
*ppa = *ppb;
*ppb = temp;
}
int main()
{
int a = 10, b = 20;
int *pa = &a,*pb = &b;
printf("a=%d b=%d pa:%p pb:%p\n", a, b, pa, pb);
myswap(&pa, &pb);
printf("a=%d b=%d pa:%p pb:%p\n", a, b, pa, pb);
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/2efc9ab23b390070f567cd1e2b2c89b4.png)
如果要通过传参的形式修改一个变量的数据,那就传递这个变量的地址——传址操作。