当前位置:网站首页>从零开始C语言精讲篇5:指针
从零开始C语言精讲篇5:指针
2022-08-03 11:58:00 【高邮吴少】
一、指针是什么?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址
的内存单元。
我们的内存是类型下图这样的空间:
我们在使用的过程中,把内存划分成一个个小的字节,每个字节称为一个内存单元。我们给每个内存单元进行编号,每个编号唯一对应一个内存单元。类似身份证,每人一个身份证,我有这个身份证就可以唯一确定你这个人。这里也是类似的,地址指向了一个确定的内存空间,所以地址也被形象的被称为指针。
举个例子:
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
int* pa=&a,也就是pa里面放的a的地址(指针),pa是(整形)指针变量,它是用来存放指针的
二、指针和指针类型
int main()
{
int a = 0x11223344;//0x表示16进制
//一位16进制对应四位2进制:2*2*2*2=16
//我们这里a一共8位16进制,也就是正好把32位2进制占满了(8*4=32)
int* pa = &a;
*pa = 0;
return 0;
}
我们按f10进行调试,监视一下,可以看到我们pa里面存放的就是a的地址
监视一下内存,可以看到,我们内存中经过代码pa = 0; 32位是全部变成了0
ps:下图中我们是16进制一共8位,对应2进制32位
经过pa = 0;赋值后
那么问题来了,我们之前说过,32位下指针大小都是4字节,64位下指针大小都是8字节。
假如我们都在32位下,我能否用char*来接收这个int型的地址(指针)呢?
我们仅把上面代码中的int * 改成 char * 来测试一下
可以看到放进去是没有问题的,但是如果要对内容修改呢?
经过*pa = 0;赋值后
为什么我这里只改动了一个字节呢?因为我是char * 的指针,我只负责我指向地址开始往后的1个字节。同理,如果我是int * 的指针,我只负责我指向地址开始往后的4个字节。
指针类型的意义1:指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)
由于这个性质,我们来看看下面这个题目
int main()
{
int a = 10;
int* pa = &a;
char* pa2 = &a;
printf("%p\n", pa);
printf("%p\n", pa2);
printf("%p\n", pa+1);
printf("%p\n", pa2+1);
return 0;
}
可以看到,我们的pa虽然和pa2都是一个地址,但是当他们加1的时候,就受到了指针类型的管控。char * 指针+1只会跳1个字节,而int * 的指针+1则会跳4个字节。
指针类型的意义2:指针类型决定了指针±整数时的步长(跳过几个字节)
三、野指针
野指针就是指针指向的位置是不可知的
(随机的、不正确的、没有明确限制的)
3.1野指针成因
1. 指针未初始化
int main()
{
int* p;//没有初始化
*p = 20;
return 0;
}
如果你没有初始化p,那么指针p里面放的是随机的地址,你*p=20是想把随机地址里面的值改成20。但是我们这个随机地址是不属于我们当前程序的,就会造成非法访问,p就是野指针。
2. 指针越界访问
int main()
{
int arr[10] = 0;
int i = 0;
int*p = arr;
for (int i = 0;i <= 10;i++)
{
//数组元素下标是0-9,你如果访问了10,这个是不属于我们的空间
//这就是越界访问
*p = i;
p++;
}
return 0;
}
3. 指针指向的空间释放
这个知识点笔者会在后面的动态内存释放进行详细讲解,这里简单提一下
int* test()//指针作为函数返回值
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d",*p);
return 0;
}
我们在main函数里调用test函数,test函数返回a的地址。
需要注意的是,一旦返回了a的地址,test函数结束,a的生命周期也结束了
也就是说,a所在的空间返还给了操作系统。那么你在main函数中通过p再访问a之前的地址,这就是非法访问了。
打个比方:你和你好朋友出去旅游,然后你们开了一间房。在使用期内,你们想在房间里干啥都行。但是房间到期后,你再访问,旅店要告你的。
3.2规避野指针的方法
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 指针使用之前检查有效性
示例如下:
#include <stdio.h>
int main()
{
int *p = NULL;//不知该该指向哪里,可以先初始化为NULL
//....
int a = 10;
p = &a;//明确地初始化
if(p != NULL)
{
*p = 20;
}
return 0;
}
四、指针运算
4.1指针±整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
上面这段代码啥意思,请看下图的解释
我们在for循环中判断vp指向的地址是否小于&values[N_VALUES]
ps:地址本质是一个16进制数,可以用来比较
发现vp指向的地址小于&values[N_VALUES],我们就进行 *vp++,
需要注意的是,++的优先级是高于 * 的,所以我们本应先进行++再 *,
但我们这里是后置++也就是说vp在本轮还是指向数组0下标元素,然后 * 解引用赋值为0,
vp因为++了,所以指向了数组1下标元素
然后重复for循环,后面的都一样了,就是把数组元素赋为0,然后vp++直到vp指向3停止
(vp < &values[4])
4.2指针-指针
#include<stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[2]);
printf("%d\n", &arr[2] - &arr[9]);
return 0;
}
指针-指针,得到的数字的绝对值,是指针之间元素的个数
注:肯定有一些杠精会问:“啊,你这个是两个相同类型的指针啊,如一个char类型指针-int类型呢?”
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
char brr[10] = {
0 };
printf("%d\n", &arr[9] - &brr[2]);
return 0;
}
像上面这种代码,你自己想想有多亏贼?
首先你两个地址都是随机的,你怎么知道两个随机地址之间隔了多远?
还有一个问题就是,你两个不同类型指针,到时候划分元素个数是按1字节来化还是4字节来化?
指针-指针的前提:两个指针指向同一空间(比如指向同一数组)
具体应用实例:求字符串长度
法一:使用库函数strlen
//求字符串长度
//法一:使用库函数strlen
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
法二:使用指针
int my_strlen(char* p)
{
int count = 0;
while (*p != '\0')//字符串末尾都是默认有一个\0
{
count++;
p++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
法三:使用指针-指针
int my_strlen(char* p)
{
char* tmp = p;//标记arr[0]的位置
while (*p != '\0')//字符串末尾都是默认有一个\0
{
p++;
}//走完while,p指向arr的末尾元素
int count = p - tmp;
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
五、指针和数组
区别:
数组是一块连续的空间,里面放的是相同类型的元素。
数组大小和元素类型,元素个数有关系,比如int arr[10],该数组大小=4*10
指针是一个变量,里面存放地址
指针变量的大小是4/8byte,这个取决于是32位系统还是64位
联系:
数组每个内存单元都有自己的内存地址,而我们指针可以存放这些地址
重点!!!:数组名是什么?
#include <stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见数组名和数组首元素的地址是一样的。
这里的一样,不仅仅是数值上,是各种意义上!
ps:数组名确实是首元素地址,但是有两个例外
1.sizeof(数组名),这里的数组名表示的是整个数组
2.&数组名,这里的数组名也是整个数组,&数组名表示整个数组的地址
#include <stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
printf("---------我是分界线----------\n");
printf("%p\n", arr+1);//地址+4,跳过1个int型
printf("%p\n", &arr[0]+1);//地址+4,跳过1个int型
printf("%p\n", &arr+1);//地址+40,跳过一个数组int arr[40]
return 0;
}
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组元素就成为可能。
#include <stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
return 0;
}
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
int main()
{
int a = 10;
int* pa = &a;//pa里面放的是a的地址
int** ppa = &pa;//ppa里面放的是pa的地址
return 0;
}
示意图如下:
对于二级指针,我们也是可以解引用的
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
当然了,你也可以无限套娃,就会有三级指针、四级指针。。。
七、指针数组
指针数组是指针还是数组?
这个问题你就想,好男孩,好男孩的本质是男孩啊
指针数组本质还是数组
举个例子:
int main()
{
int arr1[5];//整形数组,存放整形的数组就是整形数组
char arr2[3];//字符数组,存放字符的数组就是字符数组
//指针数组,存放指针的数组就是指针数组
int* parr[5];//整形指针数组
char* pbrr[4];//字符指针数组
return 0;
}
示意图如下
应用实例:
int main()
{
int a = 123;
int b = 213;
int c = 312;
int* arr[3] = {
&a,&b,&c };
for (int i = 0;i < 3;i++)
{
printf("%d\n", *arr[i]);
//arr[i]是一个元素地址,*arr[i]对该地址解引用,得到地址指向的元素
}
return 0;
}
总结
本文介绍了指针和指针类型(着重掌握)、野指针及其成因、指针运算、指针数组等相关知识。作为C语言的大头,指针这块知识必须要拿下,最后祝读者学业有成,奥利给!
边栏推荐
猜你喜欢
随机推荐
[错题]电路维修
fast planner中拓扑路径搜索
CDH6.3.2开启kerberos认证
fastposter v2.9.0 程序员必备海报生成器
深度学习跟踪DLT (deep learning tracker)
微信小程序获取用户手机号码
常用lambda表达式
bash if条件判断
-找树根2-
Fastjson反序列化
从零开始Blazor Server(6)--基于策略的权限验证
[Wrong title] Circuit maintenance
html+css+php+mysql实现注册+登录+修改密码(附完整代码)
距LiveVideoStackCon 2022 上海站开幕还有3天!
Realize 2d characters move left and right while jumping
小身材有大作用——光模块基础知识(一)
深度学习中数据到底要不要归一化?实测数据来说明!
第3章 搭建短视频App基础架构
Explain the virtual machine in detail!JD.com produced HotSpot VM source code analysis notes (with complete source code)
我在母胎SOLO20年