初识指针

时间:2023-01-06 09:59:59
  • 指针是什么
  • 指针和指针类型
  • 野指针
  • 指针运算
  • 指针和数组
  • 二级指针
  • 指针数组

自学b站“鹏哥C语言”笔记。

一、指针是什么

指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”,意思是通过它能找到以它为地址的内存单元。

总结指针是个变量,存放内存单位的地址(编号)。存放在指针中的值都被当成地址处理。

int main()
{
int a = 10;
int* p = &a;//指针变量p
return 0;
}

问题1:一个小的单元有多大?

答:1个字节。

问题2:如何编址?

答:每个地址标识一个字节。对于32位的机器,假设有32根地址线,每根地址线在寻址的时产生一个电信号(正电/负电,即1或0),那么一共会产生2^32个地址,而每个地址标识一个字节,那就可以给2^32Byte=4GB的空间进行编址。对于64位的机器同理。

问题3:一个指针变量的大小(存储空间)是多少?

答:对于32位的机器,地址由32个0/1组成,那地址就得用4个字节的空间存储,所以此时一个指针变量的大小是4个字节。同理,对于64位的机器,一个指针变量的大小是8个字节。总结:32位平台上一个指针变量是4字节,64位平台上一个指针变量是8字节。

二、指针和指针类型

虽然在同一个平台上,所有类型的指针所占存储空间大小相同,但区分指针类型仍然是有意义的。

1.指针的解引用操作

指针类型决定了指针进行解引用操作时能访问的空间大小

​int* p;​​​,​​*p​​能够访问4个字节

​char* p;​​​,​​*p​​能够访问1个字节

​double* p;​​​,​​*p​​能够访问8个字节

2.指针+-整数

指针类型决定了指针向前/后走一步走多远(指针的步长)

​int* p;​​​,​​p+1​​向后走4个字节

​char* p;​​​,​​p+1​​向后走1个字节

​double* p;​​​,​​p+1​​向后走8个字节

#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;

printf("%p\n", pa);
printf("%p\n", pa+1);

printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
//输出结果:
//0095FB58
//0095FB5C
//0095FB58
//0095FB59

三、野指针

野指针就是指向的位置是不可知的(随机的/不正确的/没有明确限制的)指针。

1.野指针成因

(1)指针未初始化

局部变量不初始化,默认是随机值。

int main()
{
int* p;//p是局部的指针变量,不初始化,默认是随机值
*p = 20;
return 0;
}

(2)指针越界访问

指针指向的范围超出数组arr的范围。

int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i=0; i<12; i++)
{
p++;//指针指向的范围超出数组arr的范围
}
return 0;
}

(3)指针指向的空间释放

当函数结束时,参量的空间就会被释放。

int* test()
{
int a = 10;
return &a;
}

int main()
{
int* p = test();
*p = 20;//当test()函数结束时,空间已经被释放,所以*p指向的是释放的空间
return 0;
}

2.如何规避野指针

(1)指针要初始化

int main()
{
int a = 10;
int* pa = &a;//初始化
int* p = NULL;//初始化,置空NULL
return 0;
}

(2)小心指针越界

(3)指针指向空间释放,及时置空(NULL)

int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
pa = NULL;//不再用a了,置空NULL
return 0;
}

(4)指针使用之前检查有效性

四、指针运算

1.指针+-整数

#include <stdio.h>
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", *p);
p++;//指针+整数
}
return 0;
}
//输出结果:1 2 3 4 5 6 7 8 9 10

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[0]);//9
printf("%d\n", &arr[9] - &arr[0]);//-9
return 0;
}

例1:用指针实现strlen函数(求字符串长度)

#include <stdio.h>
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);
return 0;
}

3.指针的关系运算

关系运算就是比较大小

#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for(vp = &values[N_VALUES]; vp > &values[0];)//">"就是指针的比较大小
{
*--vp = 0;
}
}

注意:标准规定,C语言中允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向数组第一个元素之前的那个内存位置的指针进行比较。

五、指针和数组

数组名就是数组首元素的地址,但有两个例外。详见文章【数组】

例外1:sizeof(数组名),此时数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小(单位:字节)。

例外2:&数组名,此时数组名表示整个数组,&数组名取出的是整个数组的地址。

因此,我们可以直接通过指针来访问数组。​​p+i​​​表示的就是数组中下标为​​i​​的元素。

int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i=0; i<10; i++)
{
*(p + i) = i;
}
for(i=0; i<10; i++)
{
printf("%d", *(p + i));
}
return 0;
}
//输出结果为0 1 2 3 4 5 6 7 8 9

六、二级指针

指针是一种变量,自然需要地址存放,存放指针的变量就叫二级指针。以此类推,存储n级指针的变量就叫n+1级指针。

int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;//ppa就是二级指针
int*** pppa = &ppa;//pppa就是三级指针

printf("%d\n", **ppa);//输出10,即a的值
//**ppa等价于*pa等价于a
return 0;
}

七、指针数组

注意区分指针数组和数组指针:

指针数组:是数组,是存放指针的数组。

数组指针:是指针。

指针数组示例:

int main()
{
int a = 10;
itn b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
return 0;
}

可以改写成

int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};

int i = 0;
for(i=0; i<3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
//输出结果:10 20 30