目录
注:本文 设p为指针变量,进行讲解
一、指针变量就是存放地址的变量
<1>、通俗讲:
就好比如:变量是一个盒子,用来存放数值,可以随意替换和自由取出;指针变量则就是一个用来存放地址的盒子;相当于把变量盒子的地址给放到了指针变量的盒子里,我可以用指针的盒子种放的地址找到右边的盒子
<2>、指针变量建立
如下:int *为类型,p为对象(变量);
![]()
初始化:取a的地址存入到 p 中,可以理解为 p = &a;
<3>、理解
*p 可以当做是a的别称,对*p做出修改,a的值也会发生改变;举个例子:高启强每次需要做一些“脏活”时,会对老莫说想吃🐟了,这时老莫执行这个指令,最终完成了这个任务,则最后我我们的结果就是解决了,那么相当于高启强的事情解决了,这件事👉由老莫来行动的,但是高启强也因此收益了;
二、指针变量的访问以及对指针的更改
* 解引用操作符(间接访问操作符)——引用(访问)它指向的内容(通过这个*,我们可以追溯地址到所指向的位置)
>>>比如:学校要求班主任家访,她手上有一本花名册(“ * ”),写了每个同学地址,通过花名册(解引用“*”)就能找到每个同学的家,去同学家里。(举例子是为了便于理解)
1、更改地址:
如上图,取出b的地址,放入b中;从该图也可以看出 对象p 代表的是地址
在访问地址时即可写成以下两种方式》》都是b的地址:(%p是表示转换地址的转换说明)
printf("%p\n",p);
printf("%p\n",&b);
2、访问指向的内容
int main()
{
int a = 10;
int* p = &a;//将a的地址存入p(指针)中
*p = 20;//将a的内容改为20
printf("a = %d\n", a);
printf("a = %d\n", *p);
return 0;
}
a的值和*p的值相等,这里对p用了解引用操作符,就是说访问p所存放的地址里面的内容
三、指针变量的大小和地址的组合个数
大小和地址组合个数都与编译环境密切相关
1、大小:
在32位环境下,指针变量大小为32个比特位即4个字节
在64位环境下,指针变量大小为64个比特位即8个字节
2、组合个数:
在32位环境下,地址线有32个,因为在计算机中内存以二进制形式存在,所以每一个地址线可以从0和1中选一个,则为2的32次方个组合方式;同理,64位下有2的64次方个组合方式。
四、关于const(锁住,常数性的意思)
1、修饰变量
int main()
{
const int a = 10;//a具有了常数性
int arr[a] = { 0 };//为何不能将a代入表示数组元素个数?因为a本质上还是变量
return 0;
}
根据情况看:
在c语言中,a只是具有了常数性,不可更改,但是a的本质仍为变量,不可以当成常量来应用;
但是在c++中,变量a被const修饰就是变成了常量
2、修饰指针变量
1)、限制指针变量本身
如下:const放在了*的右边,则限制了p,因为p相当于&a也就是说限制了p中存放的地址只能是a,不可以改变p里面存放的地址,若选取下面的p = &b,vs中会报错,你也可以自行测试;但是*p=20可以改变你只是锁住了地址,并没有锁住那个地址里面的内容
int main()
{
int a = 10;
int b = 20;
int* const p = &a;
*p = 20;
p = &b;
return 0;
}
特别声明:
如下:利用指针变量则可以将const int a = 10,a=10变成a = 20;举例:高启强想要杀一个人灭口,但是他表面上被安欣监视了,这时他自己是不能去杀人的,会暴露,但是这个时候他可以让老莫代替他去杀掉那个人;下面程序也是如此,既然a不能改变,则可以利用*p来改变指向的内容,从而改变a的值
int main()
{
const int a = 10;
int* const p = &a;
*p = 20;
printf("%d", a);
return 0;
}
2
2)、限制指针变量指向的内容:
如下,将const放在*右边,相当于const *p,锁住了*p指向的内容,但是存放地址可以改变(p没有被锁,它本身的功能就是存放地址)还一种写法:const int *p;
int main()
{
int a = 10;
int b = 20;
int const * p = &a;
*p = 20;//出现报错,无法更改内容
p = &b;//可以改变存放的地址
printf("%d", a);
return 0;
}
3、总结:
const在*的右边就是锁住 p 所存放的地址;
const在*的左边就是锁住 *p 所指向的内容;
四、指针的特征:
1、p自己本身就是一个地址
建立一个盒子也是需要占用内存的,它的建立本身就需要向栈申请一块内存
2、可以存放一个地址
指针变量是一个存放地址的盒子(它的功能)
3、可以指向一个值(*p——>a(访问a的内容))
指针变量存放一个地址,然后这个地址的里面内容也间接被套入盒子里面;通过*就可以去范文(*是打开指针变量种存放的那个盒子的钥匙)
五、指针的运算:
1、指针加减整数
如:p+1——>跳过了sizeof(type)*1的字节{type表示的是类型}
2、指针加减指针的绝对值
相当于日期减去日期可以求出间隔天数,指针减去指针也能求出2个指针间元素的个数(但是两个指针要是指向同一个空间才可以),一般用于【*p = &arr(arr是数组)】数组元素个数计算
3、指针的相关运算
五、野指针:
1、概念:
指针指向的位置是不可知的、随机的、不正确的,没有明确的限制的就是野指针;(一条没有绳子的野狗)
2、出现野指针的原因:
1)、未初始化
2)、指针越界
3)、指针指向的访问空间释放
int *p = lest();错的;函数只有在使用时才会在栈区生成空间,结束就销毁了;就好比如我今天睡酒店,张三知道我的房间号,第二天我退房了,而张三想要直接来我的房间睡,找上门却没有进去睡的资格,因为从第二早上退房后,这个房间就不归我使用了。
(一般如果要用指针接受函数返回值的话,在被调用的函数里要用到动态内存空间的申请,因为你申请的动态空间是在堆区申请的,销毁的是函数的变量,动态申请的空间还是存在的)
3、如何避免:
1)、设置空指针:int *p = NULL;
2)、明确指针指向哪里,直接赋值或者初始化;
六、数组与指针:
1、数组名是首元素地址:(一般情况都是如此)
arr = &arr[0];
特殊情况:1)、sizeof(arr)——>单独一个数组名为整个数组;
2)、& 在arr(数组)前面,则取整个数组的地址;
举例: int arr[10]为例子,&arr+1———>跳过了40个字节(整形为4个字节*10个元素=40)
&arr[0]+1(相当于&arr[1]的地址)——>跳过了4字节(&arr可以看出P,&arr+1则就是P+1,指针p为数组首元素地址,加1就是数组第二个元素地址)
2、数组与指针的关系:
1)、(i表示第几个元素)
*(p+i)== *(arr+i) == arr[i]==p[i ] == *(i+arr);
由p==&arr[0] == p==arr替换上面arr即可得到;
注意:数组是块连续存放的空间,指针变量就是一个变量,并不等于数组;指针变量只是存放了数组的地址
2)、数组传参:
形参可以写成数组形式,但本质上是个指针变量;
如:函数int add(int arr[]);我传参一个arr上去,传的就是arr[]数组首元素地址;
当我们要求数组长度时(元素个数),注意要在main函数中计算后在传给函数,不要到调用的函数里面在进行计算
七、二级指针:
顾名思义就是用来存放一级指针的地址的指针;如果想要对一级指针的进行操作,就需要二级指针来接受一级指针的地址
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
return 0;
}
关于地址:p存的是变量的地址,*p就是自己的地址
八、指针数组:
本质是数组 ,是用来存放指针的数组;int *arr[]则arr先于[]结合(优先级),可以理解为为int * 的数组,
(i表示二维数组行,j表示列)
一般用来模拟二维数组;*(*(arr+i)+j)
二维数组的首元素为arr[0],表示第一行
*(arr+i)——>arr[i] *(arr[i]+j)——>arr[i][j]
所以说可以通过指针数组模拟实现二维数组
九、字符指针:
例:char arr[] = {"abcdef};
char * p = arr;//首元素地址;
打印时不要用解引用;占位符%s为打印字符串转换说明,只需要向%s提供首元素地址即可
若想要*p则就是提供首元素地址的内容,用%c来打印,打印的就是首元素的字符;
注意:将字符串直接赋值给字符指针的话,此时字符串为字符串常量,char * p ="abcde";无法在更改内容,因为常量字符串不可以被修改,常量字符串放在代码块区域
十、数组指针变量:
指向数组的指针——存放数组的地址;
用于二维数组传参,看下面笔记:因为二维数组可以将每一行看成一个一维数组,即每行有几个元素就是数组指针指向的元素个数;
要明确二维数组的列,因为指针存放的数组首元素地址,那么存放进去以后数组
二维数组在内存中也是连续存放的存放的,如下可以理解为由元素个数为3的2个一维数组组成;
二维数组数组名也是二维数组首元素地址;这里我们将二维看成一维数组,那么第一行就是首元素
十一、函数指针变量:
专门存放函数地址的指针,在这里函数名和&函数名都是函数的地址;具体如下:
使用时可以 p(x,y);传参数
十二、函数指针数组:
专门存放函数的数组(存放的是相同类型的函数):
一般用于转移表的使用
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输⼊有误\n");
}
} while (input);
return 0;
}
十三、补充下指针类型:
指针类型决定了指针进行解引用操作时,访问的字节个数(即访问权限)
如:int* 访问的就是4个字节;char*访问的就是1个字节
int* + 1,则行走4字节 char * + 1,则行走1个字节
看下图:
pa与pb地址一样,当时pa加1与pb加1的地址就根据访问权限来计算了