目录
1. 指针是什么
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
指针是个变量,存放内存单元的地址(编号)。
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int* p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//将a的地址存放在p变量中,p就是一个指针变量。
return 0;
}
总结: 指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
问题:
- 一个小的单元到底是多大?(1个字节)
- 如何编址?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址时产生一个电信号 ,正电/负电(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有 232 个地址。
每个地址标识一个字节,那我们就可以给(232 Byte == 232/1024 kB == 232/1024/1024 MB == 232/1024/1024/1024 GB == 4GB)4G的空间进行编址。
同样的方法,那64位机器,如果给64根地址线,那么能编址多大空间?
- 在 32 位的机器上,地址是32 个 0 或者 1 组成的二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是 4 个字节。
- 如果在 64 位机器上,如果有64 个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
- 指针是用来存放地址的,地址是唯一标识一块地址空间的。
- 指针的大小在 32 位平台是 4 个字节,在64位平台是 8 个字节。
2. 指针和指针类型
2.1 指针 + - 整数
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("%p\n", pa); // 0000004EDF8FF4F4
printf("%p\n", pa+1); // 0000004EDF8FF4F8
printf("%p\n", pc); // 0000004EDF8FF4F4
printf("%p\n", pc+1); // 0000004EDF8FF4F5
}
总结:
- 指针类型决定了,指针
+-
整数的时候的步长(指针±整数的时候,跳过几个字节)。int* p;
—>p+1
—> 4char* p;
—>p+1
—> 1double* p;
—>p+1
—> 8
2.2 指针的解引用
int main()
{
int a = 0x11223344;
int* pa = &a;
//*pa = 0;
char* pc = (char*)&a;// char* pc = &a; -- 会报警告 -- “初始化”:从“int"到“char *”的类型不兼容。
//printf("%p\n", pa); // 000000ACB0DFFB14
//printf("%p\n", pc); // 000000ACB0DFFB14 -- 警告 "初始化":从"int*"到"char*"的类型不兼容
return 0;
}
总结:
- 指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。
int* p;
—>*p
能够访问 4个字节char* p;
—>*p
能够访问1个字节double* p;
—>*p
能够访问8个字节
举例:
int main()
{
int arr[10] = { 0 };
int* p = arr;//数组名 -- 首元素的地址
// char* p = arr; //不能将数组全部改成1,只能将前十个字节改成1
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
for (i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
3. 野指针
概念:野指针 是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针的成因
-
指针未初始化
int main() { int a; // 局部变量不初始化,默认是随机值 int* p; // 局部的指针变量,就被初始化随机值 // *p = 20; // 报错 // 通过 p 中存放的随机值作为地址,找到一个空间, // 这个空间不属于我们当前程序,就造成了非法访问,p就是野指针。 return 0; }
-
指针越界访问
int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 12; i++) { // 当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
-
指针指向的空间释放
int* test() { int a = 10; return &a; } int main() { int* p = test(); *p = 20; return 0; }
3.2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间及时置
NULL
- 指针使用之前检查有效性
// 1. 指针初始化
int main()
{
int a = 10;
int* pa = &a; // 初始化
int* p = NULL; // 当不知道一个指针变量该初始化什么值的时候就初始化成 NULL
// NULL -- (void *)0 -- 本质上还是 0
return 0;
}
// 3. 指针指向空间及时置 NULL
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
//
pa = NULL;
// 4指针使用之前检查有效性
// *pa = 10; // 报错
// 正确写法:
if (pa != NULL)
{
*pa = 10;
}
}
4. 指针运算
- 指针±整数
- 指针-指针
- 指针的关系运算
4.1 指针±整数
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//int* p = arr;
//for (i = 0; i < sz; i++)
//{
// printf("%d\n", *p++);
// // printf("%d\n", *p);
// // p = p + 1;
//}
int* p = &arr[9];
for (i = 0; i < sz; i++)
{
printf("%d ", *p--);
}
return 0;
}
4.2 指针 - 指针
指针 - 指针的前提是:两个指针指向同一块区域。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9.10 };
char ch[5] = { 0 };
printf("%d\n", &arr[9] - &arr[0]); // 9 指针 - 指针 得到的数字的绝对值是中间的元素个数
printf("%d\n", &arr[0] - &arr[9]); // -9
// printf("%d\n", &arr[9] - &ch[0]); // 错误写法,结果不可预知
return 0;
}
// strlen -- 求字符串长度
// 模拟实现 strlen -- 计数器的方式1,递归的方式2
// int my_strlen(char* str)
// {
// int count = 0;
// while(*str != '\0')
// {
// count++;
// s++;
// }
// return count;
// }
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while (*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
printf("%d\n", len);
}
4.3 指针的关系运算
#define NUM 5
int main()
{
float arr[NUM];
float* pa;
// 方法一
for (pa = &arr[NUM]; pa > &arr[0];)
{
*--pa = 0;
}
// 方法二
for(pa = &arr[NUM-1]; pa >= &arr[0]; pa--)
{
*pa = 0;
}
return 0;
}
-
方法二会在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。选择方法一比较合适。
-
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
在内存中开辟一块连续的空间,比如开辟int arr[10]
,这片空间的个数怎么记录呢?
有些编译器里,会在前面一块空间记录下来这个10。
当向后越界的时候不会影响到这个10,但是向前越界的时候,有可能就会操作到这块空间,这时向前越界的风险更大一些。
5. 指针和数组
数组 – 是一块连续的空间,放到是相同类型的元素。
数组的大小和元素类型,元素个数有关。
指针(变量) – 是一个变量,放地址 。
指针变量的大小 是4(32bit) / 8(64bit) 个byte。
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr); // 地址 -- 首元素的地址
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr); // 00D3FD4C -- 整个数组的地址
printf("%p\n", &arr+1);
// 1. &arr -- &数组名 -- 数组名不是首元素的地址 -- 数组名表示整个数组 -- &数组名取出的是整个数组的地址
// 2. sizeof(arr) -- sizeof(数组名) -- 数组名表示整个数组 -- 计算的是整个数组的大小
return 0;
}
// 运行结果如图所示
可见数组名和首元素地址是一样的。
结论:
数组名表示的是数组首元素的地址。
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%p ===== %p\n", p + i, &arr[i]);
}
return 0;
}
// 运行结果如图所示
6. 二级指针
对于二级指针的运算有:
-
*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 a = 10;
int* pa = &a; // 一级指针
int** ppa = &pa; // 二级指针
// int*** pppa = &ppa; // 三级指针
printf("%d\n", **ppa); // 10
**ppa = 20;
printf("%d\n", **ppa); // 20
printf("%d\n", a); // 20
return 0;
}
7. 指针数组
// 好孩子 : 是 孩子。
指针数组 :是 数组,是 存放指针的数组。
数组指针: 是 指针。
int main()
{
int a = 10;
int b = 20;
int c = 30;
//int* pa = &a;
//int* pb = &b;
//int* pc = &c;
// 整型数组 -- 存放整型
// 字符数组 -- 存放字符
// 指针数组 -- 存放指针
// int arr[10];
int* arr[3] = { &a, &b, &c }; // 指针数组
for (int i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
希望可以对大家有所帮助,如果有什么不对的地方,还烦请指教,谢谢!