浅谈指针.

提前说好,我是小白,欢迎大家来喷我(如果有读者的话)

那么我会从以下几个方面来做一个简单的阐述

  1. 指针是什么?
  2. 指针与指针类型
  3. 野指针
  4. 指针运算
  5. 指针和数组
  6. 二级指针
  7. 指针数组

1.指针是什么?

理解指针的2个要点:

(1)指针是内存中一个最小单元的编号,就是地址

(2)平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常指的是指针变量.

那么这里就引出一个关键点:数据在内存中是怎么存储的?

我们知道,在现实当中有一个东西叫做门牌号,我可以通过省—>市—>区—>门牌号,来找到你具体在哪里,其实内存中的存储和现实中的门牌号差不多.

 关键点

1.数据在内存当中存储的其实是这个数据的地址

2.编号—地址—指针

3.&c,取出的是第一个字节的地址,也就是较小字节的地址(在这里就是取出:0X0056ff20)

那么怎么创建一个指针变量呢?

int* pa=&c;

 

在32位的机器上,一个指针变量的大小是4个字节,在64位的机器上,一个指针变量的大小是8个字节 .

2.指针与指针类型

我们在这里讨论一下指针的类型

我们都知道,变量有不同的类型,整型,浮点型等.那么指针有没有类型呢?准确的说:有的.我们来看以下几个例子

                                                              int*类型的例子

 

我在这里调出了内存,我们可以看到int*类型的指针一次可以访问4个字节

char*类型 

 

我们可以看到,char*类型的指针一次可以访问一个字节

short*类型 

我们可以看到short*类型的指针一次可以访问两个字节

那么由以上的规律可以知道

若要访问一个字节,则要用char*类型的指针

若要访问两个字节,则要用short*类型的指针

若要访问四个字节,则要用int*类型的指针

那么此时就有一个问题,float*同样也是会访问四个字节

那么我们究竟需要用哪一个呢?

若我们用int*类型的指针,那么我们会将内存中的数据看为一个整型来处理

若我们用float*类型的指针,那么我们会将内存中的数据看为一个浮点型来处理.

我们现在从地址的角度来看一看这个变化

 

我们由上图可知

int*类型的指针变量pa,pa+1后会跳过四个字节(int*是访问四个字节的,int类型占据四个字节

char*类型的指针变量pb,pb+1后会跳过一个字节(char*是访问一个字节,char类型占据一个字节

下面我们来写一个程序,来向大家更好的理解这个现象

 上面这个程序的功能是将一个整型a(4个字节)一个字节一个字节的将它改为0.

注意:这里并不是说int类型的变量就一定要放在int*类型的指针中去,而是要根据你到底想怎么去访问这一组数据来决定的.

指针类型的意义

不同的指针类型,其实提供了不同的视角去观看和访问内存

char*————一次访问一个字节,+1跳过一个字节

int*————一次访问四个字节,+1跳过4个字节 

3.野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

容易出现野指针的两种情况

(1)未对指针进行初始化

#include<stdio.h>
int main()
{
	int a = 0;
	int* p;
	printf("%d\n", *p);
	*p = 20;
	printf("%d\n", a);
	printf("%d\n", *p);
	return 0;
}

以上那段代码在VS 2013的IDE中会报告这样的一个错误 .

要注意:指针变量它也是一个变量,此时这个变量是一个局部变量,局部变量在未初始化的时候是一个随机值,在这里就是说,我们产生了一个随机值,并在内存中找到了找到了这个随机值所对应的地址,并且我们还将20放入到了这个地址所标注的内存空间中去。注意这样的做法是非常危险的.

 这种写法是完全没有问题的,*p在这里有明确的指向.

(2)指针越界访问

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int*a = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*a = 1;
		a++;
        //当指针指向的范围超出数组arr的范围时,p就是野指针.
	}
	return 0;
}

 此时编译器就会报错,这种错误的原因就是发生了越界访问

 数组越界图解,以此题为例(关于数组越界,我在以后的博客会出)

我们看一下下面这个例子

#include<stdio.h>
int*test(int a)
{
	a = 20;
	return &a;
}
int main()
{
	int a = 0;
	int*p = test(a);
	printf("%d\n", a);
	return 0;
}

 这是它的运行结果

对,没错它不是20,为什么?

那么至于为什么打印出的结果是0(这里好像与函数栈帧有关?我下去再看看,求大佬指点)

那么 我们该怎么规避野指针呢?

1.指针初始化

2.小心指针越界

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

4.避免返回局部变量的地址

5.指针使用之前检查有效性.

关于NULL

 我们转到NULL的定义我们可以知道(如上图所示)

4.指针运算

(1)指针+-整数

(2)指针-指针

(3)指针的关系运算

(1)指针+-整数

其实我们在前面我们讲到指针类型的时候用到了,不过我们这里再看一个例子

#define VP 5
#include<stdio.h>
int main()
{
	int arr[VP] = { 0 };
	int*i= 0;
	for (i = &arr[0]; i < &arr[VP];)
	{
		*(i++) = 1;
	}
	return 0;
}

 

我们可以看到,此程序是修改数组的每一个元素的内容,也从内存中得以看到指针+-整数是决定了这个指针怎么移动.

(2)指针-指针

直接拿代码说话,这里我们会自主实现strlen

int my_strlen(char*p)
{
	char*a = p;
	while (*p != '\0')
	{
		p++;
	}
	return p-a;
}
int main()
{
	int sum = 0;
	char arr[] = "abcdefg";
	sum=my_strlen(arr);
	printf("%d\n", sum);
	return 0;
}

 

由此可见:指针-指针得到的是两个指针之间的元素的个数

注意:指针-指针的前提是两个指针必须要指向同一块空间.

(3)指针的关系运算(说白了就是比大小

直接拿代码说话

#define VP 5
int main()
{
	int arr[VP] = { 0 };
	int*p = 0;
	int i = 0;
	for (p = &arr[VP-1];p>=&arr[0];p--)
	{
		*p = 1;
	}
	return 0;
}

这里肯定会有人吼:哦哦哦哦,你看你看,越界访问!哦哦哦哦,你看你看,野指针!

可是这里真的越界访问了吗?p真的是野指针了吗?

  再次强调:指针会最终指向第一个元素前面的内存空间中去,但请注意,虽然此时p到了这个数组的外面,但是我们并没有对它进行一个解引用的操作,所以并没有进行一个越界访问,所以这里的p并不是一个野指针.

5.指针和数组

直接上代码

在这里我们要实现用指针来访问数组

#include<stdio.h>
int fb(int *p)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = 1;
		p++;
	}
}
int main()
{
	int sum = 0;
	int arr[10] = { 0 };
	fb(arr);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行截图如上.

注意:数组名是数组首元素的地址. 

6.二级指针

我们之前讲到,指针变量是用来存放变量地址的,那么指针变量本身身为一个变量有没有地址?

指针变量的地址该怎么表示呢?

此时就要用到我们的二级指针

拿代码说话

#include<stdio.h>
int main()
{
	int a = 10;
	int*pa = &a;
	int**ppa = &pa;
	**ppa = 20;
	printf("%d\n", a);
	return 0;
}

注意*的个数不是随便加的而是有规律的,但最终还是要理解规律的,具体请看上图图解.

7.指针数组

指针数组是指针还是数组?

答案:是数组,是存放指针的数组.

上代码

#include<stdio.h>
int main()
{
	int a = 100;
	int b = 200;
	int c = 300;
	int d = 400;
	int*arr[5] = { &a, &b, &c, &d };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *arr[i]);
	}
	return 0;
}

 其实指针数组的使用与一般的数组没什么不同,就是要注意,指针存放的是地址,所以不要将&给忘记,还有,若要打印出指针数组,千万不要忘记*.

指针数组原理图解如下

 

但是话又说回来,我们若是想要实现上述代码的功能有何必动用指针呢?

接下来我们讲一个实例:用一维数组来模拟一个二维数组

#include<stdio.h>
int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6 };
	int b[] = { 7, 8, 9, 10, 11, 12 };
	int c[] = { 13, 14, 15, 16, 17, 18 };
	int d[] = { 19, 20, 21, 22, 23, 24 };
	int*k[4] = { &a, &b, &c, &d };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		for (j = 0; j < 6; j++)
		{
			printf("%d ", k[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 

代码详解

 最后总结

本人萌新一枚,欢迎各路大神前来吐槽,若文章中有什么不对的,也感谢各位的提点.

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值