初阶指针
指针
本篇重点:认识指针及其用法,以内存的方式看代码
- 指针是什么?
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
1.指针是什么?
要想了解指针是什么,就需要了解内存。
内存:电脑上的存储设备,程序运行时会加载到内存中,占用内存空间,可以按电脑上Alt+Ctrl+Delete,点击任务管理器查看运行的程序所占内存空间。
- 我们买手机或者买电脑时,都会看到8G+256G,16G+512G,这里的8G,16G就是内存;
- 在vs中我们可以看到有x86和x64环境,这里的x86指的是32位平台(32根地址线),x64指的是64位平台(64根地址线)。
我们以x86机器(32位平台下)举例
总结:指针是内存单元的编号(地址),在32位平台下是4个字节,在64位平台下是8个字节。
电脑是怎么产生编号呢(以32位平台为例)?
1.1如何在VS中查看地址,如何存放地址?
- 首先看代码
#include<stdio.h>
int main()
{
int a = 0x44332211;//int在内存中占4个字节
int* p = &a;//p是指针变量,用来存放a的第一个地址(最小的那个地址)
*p = 20;//解引用操作,通过p中存放的地址找到a,可以理解为*p等价于a
printf("%d %d", *p, a);
return 0;
}
运行结果:
20 20
vs中查看地址:按F10调试----在最上方的调试中找到窗口----内存
2.指针和指针类型
平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
指针(指针变量)类型:和数据类型一样,有整型,浮点型,字符型。
int main()
{
int a = 10;
int* p = &a;//int
int b = 20;
char* pr = &b;//char
int c = 30;
short* ps = &c;//short
int d=25;
float* pa=&d;//float
//这里的指针变量大小都为4个字节,因为在x86平台下,指针大小为4个字节
return 0;
}
那么指针类型的意义是什么呢?
2.1指针±整数
- 不同指针类型访问的大小不一样
int main()
{
int a = 0x44332211;
int* p1 = &a;
*p1 = 0;
printf("%d %d", *p1,a);
return 0;
}
运行结果:
0 0
整型指针类型一次访问4个字节
int main()
{
int a = 0x44332211;
char* p1 = &a;
*p1 = 0;
printf("%d %d",*p1,a);
return 0;
}
运行结果
0 1144201728
char类型的指针一次访问1个字节
2.不同的指针类型±整数的值不一样,即跨步不一样
int main()
{
int a = 10;
int* p1 = &a;
printf("%p\n", p1);
printf("%p\n", p1+1);
int b = 20;
char* p2 = (char *)&b;//最好加一下强制类型转换,不加虽然不报错,但也有警告
printf("%p\n", p2);
printf("%p\n", p2+1);
return 0;
}
运行结果:
0085FB50
0085FB54
0085FB38
0085FB39
总结:
- char*,一次访问1个字节,+1跳过1个字节;
- int*,一次访问4个字节,+1跳过4个字节;
- 其他类型同理;
3.野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),野指针很危险。
3.1野指针的成因
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
编译器报错
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
int i = 0;
for(i=0; i<=10; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
printf("%d ",*(p++));
}
return 0;
}
运行结果:
1 2 3 4 5 6 7 8 9 10 -858993460
- 指针指向的空间释放
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d ", *p);
return 0;
}
运行结果:
10
但是10只是巧合,我们稍微改改:
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("hehe\n");
printf("%d ", *p);
return 0;
}
运行结果:
hehe
4
原因:函数return后,会释放空间,即a的地址被释放了(还给内存了),那p接收的就是随机值,即野指针;
3.2如何规避野指针
- 指针初始化;
- 小心指针越界;
- 指针指向空间释放,及时置NULL(空指针,即0);
- 避免返回局部变量的地址;
- 指针使用之前检查有效性,如果不是NULL再使用;
4.指针运算
指针运算:
- 指针±整数
- 指针-指针
- 指针的关系运算
4.1指针±整数
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp < &values[N_VALUES]; vp++)
{
*vp = 0;
}
int i = 0;
for (i = 0; i < N_VALUES; i++)
printf("%.1f ", values[i]);
return 0;
}
运行结果:
0.0 0.0 0.0 0.0 0.0
上面讲过;
4.2指针-指针
两个指针要指向同一块空间,|指针-指针|=其之间的元素个数
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%d ", &arr[4] - &arr[0]);
printf("%d ", &arr[0] - &arr[4]);
return 0;
}
运行结果:4 -4
分析:
模拟strlen函数
int my_strlen(char* str)
{
char* start = str;//创建新的指针用来存放初始地址
while(*str != '\0')
str++;
return str - start;//指针-指针为其之间的元素个数
}
int main()
{
char arr[] = "abcdef";
int ret = my_strlen(arr);
printf("%d ", ret);
return 0;
}
运行结果:
6
4.3指针的关系运算
比较指针的大小
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;//vp=vp-1,--vp=vp
}
int i = 0;
for (i = 0; i < N_VALUES; i++)
printf("%.1f ", values[i]);
return 0;
}
运行结果:0.0 0.0 0.0 0.0 0.0
代码简化, 这将代码修改如下:
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES-1]; vp >=&values[0];vp--)
{
*vp = 0;
}
int i = 0;
for (i = 0; i < N_VALUES; i++)
printf("%.1f ", values[i]);
return 0;
}
运行结果:0.0 0.0 0.0 0.0 0.0
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行.
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
1.指针和数组是不同的对象
- 指针是一种变量,用来存放地址的,大小是4或8个字节;
- 数组是一组相同元素的集合,是可以放多个元素的,大小取决于元素个数和元素类型;
2.数组名是首元素地址(除那两种情况外);
- 地址可以放在指针变量中,可以通过指针访问数组;
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
//赋值
int i = 0;
for (i = 0; i < sz; i++)
{
*(p + i) = i + 1;//arr[i]=i+1//*p=i+1,p++
}
//打印
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%d ", *(p + j));
}
return 0;
}
注意:arr[i]---->*(p+i)
运行结果:
1 2 3 4 5 6 7 8 9 10
6.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
接下来就要介绍二级指针
int main()
{
int a = 0;
int* pa = &a;//pa放a的地址
int** ppa = &pa;//ppa放pa的地址
**ppa = 50;//*ppa访问pa----*pa访问a,即**ppa访问a
printf("%d %d", **ppa, a);
return 0;
}
运行结果:
50 50
注意:地址本身不占内存,但把它存在一个变量中就占内存,大小取决于是32位台(4B),还是64位平台(8B)。
7.指针数组
指针数组是指针还是数组?
答案:数组,存放指针(地址)的数组
int main()
{
int a = 1;
int b = 3;
int c = 5;
int d = 7;
int* arr[4] = { &a,&b,&c,&d };//指针数组
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
运行结果:
1 3 5 7
注意:
单个变量在内存中不一定是连续的,但是放在数组中就连续了;
趁热打铁:用一维数组模拟二维数组
int main()
{
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 5,6,7,8 };
int arr3[4] = { 9,10,11,12 };
int* arr[3] = { arr1,arr2,arr3 };//数组名是首元素地址
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);//*(arr[i]+j)---*(*(arr+i)+j)
}
printf("\n");
}
return 0;
}
运行结果:
1 2 3 4
5 6 7 8
9 10 11 12
总结
以上就是本篇的所有内容了,如果喜欢本篇,不妨点点赞,如果想持续了解更多c语言知识,不妨点个关注,我会每周更新博客,让我们一起努力,一起进步,路虽远行则将至。
-----------------------------------------------------------------好了,拜拜!!!