指针是什么
指针理解的2个要点:
①. 指针是内存中一个最小单元的编号,也就是地址。
②. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
我们可以这样理解指针:
内存 | 内存单元的编号 |
一个字节 | 0xFFFFFFFF |
一个字节 | 0xFFFFFFFE |
... | .. |
... | ... |
一个字节 | 0x00000001 |
一个字节 | 0x00000000 |
内存是一个大空间,为了方便管理,我们将内存化为一个一个的内存单元,我们取一个字节为一个基础的内存单元,为了很好的管理内存单元,我们将内存单元一个一个的编号,内存单元的编号也叫内存单元的地址,这样我们就可以通过地址很好的找到内存单元,地址指向内存单元,所以我们称地址为指针。
编号 -- 地址 -- 指针
2. 内存单元的编号怎样产出?
32位机器通电后会产出电信号,电信号有正电和负电,转换为数字信号(32个0/1)组成的二进制序列,其中有32个全0到32个全1的可能性,这个中间产生的编号即为内存单元的编号.
3.指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量
所以指针有两层含义①地址②指针变量(当要存起来的时候)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 10;//a占四个字节,在内存中开辟一块空间
int * pa = &a;//对变量a,取出它的地址,可以使用&操作符。拿到的是a的4个字节中第一个字节的地址
//pa是指针变量 它的类型是int * *解引用操作符,代表指向变量 int是因为pa指向的对象a是整型类型的
*pa = 20;
printf("%d\n", a);
return 0;
}
总结:指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
但一个小的单元到底是多大?(1个字节)
如何编址?经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。 所以在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。 总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。
4. 指针和指针类型
4.1指针类型
我们知道变量有不同的类型,整形,字符型、浮点型等。那
指针有没有类型呢?准确的说:有的。
当有这样的代码:
int num = 10;
p = #
要将&num(num的地址)保存到p中,那么p则为指针变量,那它的类型是怎样的呢?我们给指针变量相应的类型。
char *p1 = NULL;
int *p2 = NULL;
short *p3 = NULL;
long *p4 = NULL;
float *p5 = NULL;
double *p6 = NULL;
这里可以看到,指针的定义方式是: type + * 。
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
4.2 指针类型的意义
① 指针类型决定了:指针解引用的权限有多大
② 指针类型决定了,指针走一步,能走多远(步长)
int main()
{
int a = 0x1223344;//1个16进制位表示4个二进制位 8个二进制位占一个字节
//11 22 33 44分别占一个字节
int *pa = &a;
*pa = 0;//解引用访问4个字节
//char * pa = &a;
//*pa = 0;//解引用访问一个字节
//int 和 char 访问字节数不同,说明解引用有意义
return 0;
}
5. 指针+ - 整数
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
6. 指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
7. 野指针
7.1 什么是野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
7.2 野指针成因
①指针未初始化
#include <stdio.h>
int main()
{
//p为野指针
int *p;//p为局部变量,局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
② 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
③ 指针指向的空间释放
//③ 指针指向的空间释放
int * test()
{
int a = 10;//创建变量a 里面放个10
return &a;//返回a的地址
}
int main()
{
int*p = test();//通过p找到a,并放入a的地址,但调用完成后,此时a在函数中生命周期已经结束,a已经还给操作系统
*p = 20;//p无法再在a中放值
return 0;
8. 如何避免野指针
① 指针初始化
//指针初始化
int main()
{
//当前不知道p应该初始化为什么地址的时候,直接初始化为NULL
int * p = NULL;//NULL包含在头文件#include<stdio.h>
//明确知道指针应该初始化为什么
int a = 10;
int *ptr = &a;
return 0;
}
② 小心指针越界
C语言本身不会检测越界行为,需要自己注意
③ 指针指向空间释放及时置为NULL
④ 避免返回局部变量的地址
⑤ 指针使用之前检查有效性
#include <stdio.h>
int main()
{
int *p = NULL;
int a = 10;
p = &a;
//不能直接用,需要判断p是否为空指针 如果不为空指针才可以使用
if (p != NULL)
{
*p = 20;
}
return 0;
}
9. 指针的运算
指针+- 整数
指针-指针
指针的关系运算
9.1 指针+-整数
#define N_VALUES 5//定义 N_VALUES 值为5
float values[N_VALUES];//定义 values 数组 里面有5个元素
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)//给vp 0元素的地址
{
*vp++ = 0;
}
//有一个5个元素的数组values
//vp指向values[0]
//vp小于取地址values[5]
//*vp++ = 0; 进入循环将数组5个元素全变为0
9.2 指针-指针
指针-指针是两个元素之间的元素个数
//指针-指针
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%d\n", &arr[8] - &arr[0]);
return 0;
}//结果为8
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| |
arr[0] arr[8]
注意:指针和指针相减的前提是两个指针指向同一块空间
9.3 指针的关系运算
比较关系
将9.1代码简化
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}//从后一直往前挪
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
10. 指针和数组
10.1 数组名是什么
#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;
}
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。
所以我们可以把代码改成这样
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
既然数组名作为首元素的地址存放到一个指针中,那么我们可以使用指针来访问。
//访问数组元素
#include <stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
//首元素地址+i 产生的就是下标为i这个元素的地址
}
return 0;
}
所以 p+i 就是计算的数组 arr 下标为i的地址。
11. 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放的地方就是二级指针。
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
int* *ppa = &pa;//pa也是个变量,&pa取出pa在内存中起始地址
//第一个*是因为ppa指向pa pa的类型是int* 第二个*是代表ppa是指针
return 0;
}
引用比特鹏哥
12. 指针数组
指针数组是数组。是存放指针的数组。
int arr1[1];//整型数组--存放的是整型
char arr2[2];//字符数组--存放的是字符
//指针数组--存放的是指针
int* arr3[5];//arr3是数组 有五个元素 五个元素的类型是int*