该文有以下内容:
1.指针是什么 | 2.指针具有指针类型的2个意义 | 3.野指针 |
---|---|---|
4.指针运算 | 5.指针与数组 | 6.二级指针 |
7.指针数组 |
1.指针是什么?
官方语言:
在计算机科学中,指针( Pointer )是编程语言中的一个对象,利用地址,它的值直接指向( points to )存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针"。意思是通过它能找到以它为地址的内存单元。
简而言之,指针就是一个变量,而变量里面存储的地址,因此,指针就是**“地址”**,下面以代码为例子,进行演示官方语言的意思;
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20;
printf("%d", a);
return 0;
}
/*运行结果
20
*/
指针变量p就可以叫做指针,他就是一个对象,p的值指向存在 电脑存储器中另一个地方的值(a),所以当解引用p时,*p= 20,p利用地址,使其所指向的a变成了20;
所以p可以叫做指针,p的值是地址;
另外这里强调一下,在内存中一块空间大小是一个字节,并且用16进制编号方便人类观看(在讲述指针第二个意义时候会用到)
2.指针具有类型的2个意义
引言:
#include <stdio.h>
int main()
{
int a = 20;
int* intp = &a;
char* charp = &a;
double* doublep = &a;
printf("%d\n", sizeof(intp));
printf("%d\n", sizeof(charp));
printf("%d\n", sizeof(doublep));
return 0;
}
/*运行结果:
4
4
4
*/
你会看到结果全是4,为何?因为sizeof()测量的是指针大小,其实在32位平台上,指针的大小是4字节,在64位上,指针的大小是8字节**,其中指针大小和指针类型无关,全部是一样**,因为指针是地址,地址就是4个字节或者8个字节;
问:既然指针大小都是一样?为何要弄出这么多指针类型?下面用几个例子进行演示
意义1:指针类型决定了指针解引用操作的时候有多大的权限(能操作几个字节)
#include <stdio.h>
int main()
{
int a = 0x44332211;/*a存储的是一个16进制的数字,16进制中的一个数字代表4位,2个数字代表一个字节*/
int b = 0x44332211;
int c = 0x44332211;
int* intp = &a;
char* charp = &b;
short* shortp = &c;
*intp = 0;
*charp = 0;
*shortp = 0;
printf("%#x\n", a);
printf("%#x\n", b);
printf("%#x\n", c);
return 0;
}
/*
运行结果:
0
0x44332200
0x44330000
*/
a变为0 ,改变了四个字节
b变为0x44332200,变了一个字节
c变为0x44330000,变了两个字节;
- 因为指针intp是整型指针,具有四个字节权限.charp是字符指针,具有一个字节权限,shortp是短整型指针,具有两个字节权限;
意义2:指针类型决定了指针向前向后能走多远的路程;
举例:
#include <stdio.h>
int main()
{
int a = 10;
int* intp = &a;
char* charp = &a;
double* doublep = &a;
printf("%p",&a);
printf("%p",intp+1);
printf("%p",charp+1);
printf("%p",doublep+1);
return 0;
}
/*运行结果:
00B6FB44
00B6FB48
00B6FB45
00B6FB4C
*/
可以看到整型指针intp加一以后地址向后移动了4个字节;
字符指针charp加一以后地址向后移动了一个字节;
双精度指针doublep加一以后向后移动了八个字节;
所以这就是不同的指针类型的意义,决定了你可以网前后移动的路程,这主要用于数组的访问,以及数据结构
3.野指针
野指针形成有三个原因:
- 指针指向的位置不可知;
- 指针访问数组时候越界;
- 指针指向的空间被释放;
原因1: 指针指向的位置不可知;
我们知道在创建一个变量的时候如果不初始化,那么变量是一个随机值
- 例如; int a; 那么a是一个随机值;同理,创建指针的时候如果不初始化就会是一个随机地址(这很危险,以为你不知道他会指向谁)
代码示例:
#include <stdio.h> int main() { int a = 10; int* p; *p = 20; /*这就很恐怖,因为你不知道指针p是个什么*/ return 0; }
这个时候就是野指针
原因2:指针访问数组时候越界;
|----|----|----|----|----|----|----|----| (假设左边是数组格子,可以看到有8个格子,索引是0 1 2 3 4 5 6 7)
代码演示:
#include <stdio.h> int main() { int num[8] = {2,4,5,8,4,3,13,31}; int* p = num; /*数组名是首元素地址*/ for (int i = 0;i<=8;i++) { printf("%d\n", *(p+i)); /*利用解引用指针访问数组*/ } return 0; } /* 运行结果: 2 4 5 8 4 3 13 31 -858993460 */
我们可以看到,会多出一个负数,这是就是因为当i = 8的时候,p+i就代表往后移动32个字节,即8个整形,但是往后移动7个后就到达终点了;当i是8就会越界;越界以后,就会指向我们不知名的一个随机地址
因此现在的p也是野指针
原因3:指针指向的空间被释放;
我们在学习指针之前,知道在创建函数的时候,函数一旦被调用完毕,函数的所有参数就会被销毁;创建的空间就会被释放
#include <stdio.h>
int* test()
{
int a = 20;
return &a;
}
int main()
{
int* p = test();
printf("%d", *p)
return 0;
}
这样子写就会有问题.因为test()参数里面的a在出函数时候就被销毁了,a的空间就还给系统了;这里执行很可能还是20,这个空间的内容还没有清除;在c语言陷阱中有这个解释(有的时候可能已经清除),总之不建议这样写
因此现在的p也是野指针
总结: 如何避免野指针?
- 指针最好初始化
- 小心指针越界
- 指针指向空间被释放或者不知道怎么初始化指针就赋值NULL
- 指针使用之前检查有效性
4.指针运算
1.指针+ - 运算(前面也演示过一部分)
#include <stdio.h>
int main()
{
int num[15] = { 21,2,1,4,5,3,6,9,5,4,7,8,2,1,12 };
int i = 0;
int* p = num;
for (i = 0; i < 15; i += 2)
{
printf("%d ", *(p + i)); /*因为p是整型指针,所以加2就是跳8个字节*/
}
return 0;
}
/*
运行结果:
21 1 5 6 5 7 2 12
*/
2.指针减去指针(在同一块空间)
#include <stdio.h> int main() { int arr[10] = {12,30,14,12,2,3,4,32,12,16}; printf("%d", &arr[9] - &arr[2]); return 0; } /* 运行结果: 7 */
所以指针减去指针就是指针间的元素个数
出题: 自己编写函数,用三种方法测量字符串的长度,不准用strlen();
方法一是计数法 方法二是函数递归,但是也运用了指针加减整数 方法三是指针相减
3.指针关系运算
通过大小比较进行运算
#include main()
{
float values[5];
float* p = NULL;
for(p = &values[5];p>&values[0];)
{
*--p = 0;
}
return 0;
}
当指针p等于 &values[5]时候已经越界了,但是不怕,因为我们只是比较的地址,并没有弄值出来
所以按照代码意思就可以全部初始化为0了
也有人这样写
#include main()
{
float values[5];
float* p = NULL;
for(p = &values[5];p>&values[0];p--)
{
*p = 0;
}
return 0;
}
但是这样不推荐,因为最后终止条件是p的地址小于等于&values[0],也就是说最后p
跑到数组前面去了,
他们的过程是这样:
而C语言标规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针与数组
一般来说数组名就是首元素地址,但是有例外;
&arr_name & + 数组名形式代表的是整个数组,只是显示的时候,显示的是首元素地址,一下代码可以区分
#include <stdio.h> int main() { int arr[10] = {0}; printf("%p", arr); printf("%p", arr+1); printf("%p", &arr); printf("%p", &arr+1); return 0; } 003AFE90 003AFE94 003AFE90 003AFEB8
- 运行结果:
- 003AFE90
003AFE94
003AFE90
003AFEB8可以看到&arr+1地址增加了40个字节,也就是数组大小
- sizeof(arr_name)时候代表的是整个数组;
#include <stdio.h> int main() { int num[20] = {0}; printf("%d", sizeof(num)); return 0; } /* 运行结果: 80 */
6.二级指针
存放变量地址的是以及指针,那么类推,存放指针地址的就是二级指针
那么二级指针表示方法是什么呢?
就是两颗* 后面还有三颗 * ,四颗 * …依次类推
#include <stdio.h>
int main()
{
int a = 20;
int* p = &a;
int** pp = &p;
return 0;
}
7.指针数组
注意我说的是指针数组,是数组,是存放指针的数组
#include <stdio.h>
int main()
{
int a = 20,b = 30,c = 10,d = 40;
int* num[4] = {&a,&b,&c,&d};
for(int i = 0;i<4;i++)
{
printf("%d\n",*num[i]);
}
return 0;
}