指针入门(超详解)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

指针是C语言的一个重要知识点,本文对指针进行初步的探索,相信会对初期学习的朋友们有些许帮助


一、指针是什么

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

在这里插入图片描述

代码演示

int main()
{
	int a = 10;//在内存中开辟一块空间,来存放该数值
	int *p = &a;//我们对变量a取出它的地址,可以使用&操作符
	            //将a的地址存放在p变量中,p就是一个之指针变量
	return 0;
}

存在的问题

  1. 一个小的单元到底多大(一个字节)
  2. 如何编址

分析

  1. 经过仔细的计算和权衡,我们发现一个字节给一个对应的地址是比较合适的
  2. 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生一个电信号正电/负电(1或者0)
    那么32根地址线产生的地址会是:
    00000000000000000000000000000000
    00000000000000000000000000000001
    00000000000000000000000000000010
    00000000000000000000000000000011

    10000000000000000000000000000000

    11111111111111111111111111111111
    2^32Byte == 2^32/1024KB == 2^32/1024/1024MB == ^32/1024/1024/1024GB == 4GB

总结

  1. 内存被划分为一个个内存单元,每个内存单元的大小是1个字节
  2. 每个字节的内存单元都有一个编号,这个编号就是地址,地址即指针
  3. 每个内存单元都有唯一的地址来标识
  4. 指针的大小在32位平台是4个字节,在64位平台是8个字节

二、指针和指针类型

指针是有类型的,下面给出指针变量相应的类型

	char   *pc = NULL;
	int    *pi = NULL;
	short  *ps = NULL;
	long   *pl = NULL;
	float  *pf = NULL;
	double *pd = NULL;

这里可以看到,指针的定义方式是:type + *
char类型的指针是为了存放char类型变量的地址
short
类型的指针是为了存放short类型变量的地址
int*类型的指针是为了存放int类型变量的地址

所以,指针类型的意义是什么呢

2.1 指针±整数

代码演示

int main()
{
	int a = 10;
	char *pc = (char*)&a;
	int *pi = &a;
	printf("%p\n", &a);
	printf("%p\n", pc);
	printf("%p\n", pc+1);
	printf("%p\n", pi);
	printf("%p\n", pi+1);
	return 0;
}

运行结果
在这里插入图片描述

总结:
指针的类型决定了指针向前或者向后走一步的距离
char*+1跳过1个字节
short*+1跳过2个字节
int*+1跳过4个字节
double*+1跳过8个字节

2.2 指针的解引用

演示如下:
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

>总结:

指针的类型决定了对指针解引用时有多大的权限(能操作几个字节)
char的指针解引用访问1个字节
short
的指针解引用访问2个字节
int*的指针解引用访问4个字节

三、野指针

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

3.1 野指针成因

1. 指针未初始化
代码演示

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

2. 指针越界访问
代码演示

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

3. 指针指向的空间释放
代码演示

int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	//p得到地址之后,地址指向的空间已经释放,所以这个时候p就是野指针
	int *p = test();
	printf("love \n");
	printf("%d\n", *p);
	return 0;
}

3.2 如何规避野指针

1. 指针初始化
①如果明确指针指向哪里,则初始化正确的地址
int a=10;
int *p=&a;
②若没有明确指向,则指向空指针
int *p=NULL;
2. 小心指针越界
3. 指针指向的空间释放时及时置NULL
代码演示

int main()
{
	int arr[10] = { 0 };
	int p = arr;
	//p...
	//p不再用来指向arr
	p = NULL;
	return 0;
}

4. 指针使用前检查有效性

代码演示

int main()
{
	int *p = NULL;//NULL指向的空间是不能访问的
	//...
	/*int a = 10;
	p = &a;*/
	//使用错误
	if (p != NULL)
	{
		*p = 20;
	}
	//正确使用
	return 0;
}

四、指针运算

4.1 指针±整数

1. 方式一
代码演示

int main()
{
	int arr[5] = { 0 };
	int* p = arr;
	int i = 0;
	//p --> arr
	//p == arr
	//p+i == arr+i
	//*(p+i) == *(arr+i) == arr[i]
	//*(arr+i) == arr[i]
	//*(i+arr) ==i[arr]//[]仅为操作符
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
		//printf("%d ", *(arr + i));
		//printf("%d ", arr[i]);
		//printf("%d ", i[arr]);
		//p指向的是数组首元素
		//p+i是数组下标为i的元素地址
		//p+i起始时跳过i*sizeof(int)个字节
	}
	return 0;
}

运行结果
在这里插入图片描述
分析
在这里插入图片描述
2. 方式二
代码演示

int main()
{
	int arr[5] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p++ = i;
	}
	p = arr;//重中之重
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *p++);
	}
	return 0;
}

运行结果
在这里插入图片描述
分析
在这里插入图片描述

4.2 指针-指针

代码演示

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", &arr[0] - &arr[9]);
	return 0;
}

运行结果
在这里插入图片描述

指针-指针得到的绝对值是指针和指针之间的元素个数
计算前提条件:两个指针指向的是同一块连续的空间
应用案例:my_strlen函数

4.3 指针的关系运算

1. 方式一
代码演示

#define N_VALUES 5
int main()
{
	float values[N_VALUES];
	float* vp;
	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 0;
	}
	return 0;
}

分析
在这里插入图片描述

2. 方式2
代码演示

//代码2(代码1修改如下):
#define N_VALUES 5
int main()
{
	float values[N_VALUES];
	float* vp;
	for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
	{
		*vp = 0;
	}
	return 0;
}

分析
在这里插入图片描述

方式一和方式二在绝大部分的编译器上是可以顺利完成任务的,然而我们应该避免方式二的写法,因为标准不保证它可行
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

五、指针和数组

数组名表示数组首元素地址,但有两个例外
① sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节
② &数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素地址的值是一样的,但是类型和意义是不同的

则可以把数组名当成地址存放在指针中,也可以使用指针来访问数组

代码演示

int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int i = 0;
	int *p = arr;//指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("&arr[%d]=%p <====> p+%d=%p\n", i, &arr[i], i, p + i);
	}
	return 0;
}

运行结果

分析

p+i其实计算的是数组arr下标为i的地址
则我们可以直接通过指针来访问数组

代码演示

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

六、二级指针

指针变量也是变量,是变量就有地址,则指针变量的地址存放的地方就是二级指针

代码演示

int main()
{
	int a = 10;
	int* pa = &a;//pa是指针变量,一级指针变量
	int* * ppa = &pa;//ppa指针变量,二级指针变量
	**ppa = 20;//*ppa得出pa-->**ppa==*pa==a
	printf("%d", a);
	return 0;
}

运行结果
在这里插入图片描述
分析
在这里插入图片描述

*ppa通过对ppa中的地址进行解引用,这样找到的是pa,ppa其实访问的是pa
**ppa先通过
ppa找到pa,然后对pa进行解引用操作:*pa,找到a

七、指针数组

指针数组是数组,用来存放指针

代码演示

int main()
{
	char* arr[] = { "abcdef", "hello", "love" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

运行结果
在这里插入图片描述

总结

指针是很重要的一个知识点,只是初次见面,后面还有很多知识需要我们学习,坚持学习,努力进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值