探索指针的奇妙世界,程序中的魔法箭头(上)

目录

  • 一.指针是什么
  • 二.指针和指针类型
    • 1.指针加减整数
    • 2.指针的解引用
  • 三.野指针
    • 1.野指针形成的原因
      • (1)指针未初始化
      • 指针越界访问
    • 2.如何规避野指针
      • (1)指针初始化
      • (2)小心指针越界
      • (3)指针指向的空间释放,及时置NULL
      • (4)避免返回局部变量的地址
      • (5)指针使用之前检查有效性
  • 四.指针运算
    • 1.指针加减整数
    • 2.指针减指针
    • 3.指针的关系运算
  • 五.指针和数组
  • 六.二级指针
  • 七.指针数组

一.指针是什么

指针是内存中最小单元(字节)的编号,也就是地址
我们平时口中所说的指针,通常说的是指针变量。
总结:指针就是地址,平时口头说的指针是指针变量

指针变量:我们通过取地址操作符取出变量的内存起始地址,把地址存放到一个变量中,这个变量就是指针变量。

#include

int main()
{
	int a = 1;
	int* pa = &a; //pa是专门用来存放地址(指针)的,pa被称为指针变量。
	return 0;
}

总结:
指针变量就是用来存放地址的变量。(存放在指针中的值都被当成地址处理)
一个内存单元有唯一的地址。
地址的大小在32位平台上是4个字节,在64位平台上是8个字节

二.指针和指针类型

变量有不同的类型,比如整型,浮点型等等。指针也有这样的类型。

1.指针加减整数

#include

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	printf("%p\n", pa);
	printf("%p\n", pa + 1);

	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第1张图片

所以指针的类型决定了指针向前或向后走一步有多大距离

#include

int main()
{
	char a = 0x11223344;
	char* pa = &a;
	printf("%p\n", pa);
	printf("%p\n", pa + 1);

	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第2张图片

2.指针的解引用

#include

int main()
{
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(long*));
	printf("%d\n", sizeof(float*));
	printf("%d\n", sizeof(double*));

	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第3张图片

我们通过计算不同类型指针变量的大小,发现在64位平台上均是8个字节。
那么指针类型的意义是什么呢?

探索指针的奇妙世界,程序中的魔法箭头(上)_第4张图片
探索指针的奇妙世界,程序中的魔法箭头(上)_第5张图片
如果改为char类型,44 33 22 11不会变成00 00 00 00,而是会变成 00 33 22 11

总结:
int*的指针解引用可以访问4个字节
char*的指针解引用可以访问1个字节
所以指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)

三.野指针

指向的位置不可知(随机,不正确,没有明确限制)的指针被称为野指针。

1.野指针形成的原因

(1)指针未初始化

#include

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

指针越界访问

#include

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

2.如何规避野指针

(1)指针初始化

1.明确知道指针初始化为谁的地址,就直接初始化
2.不知道指针初始化为什么值,暂时初始化为NULL

//指针初始化
#include
int main()
{
	int a = 10;
	int* p1 = &a;
	int* p2 = NULL;
	return 0;
}

(2)小心指针越界

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

当释放空间后赶紧置空,避免成为野指针。大街上一条狗脱离主人,要把它栓到一棵树上,避免成为野狗。

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

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

if (p != NULL)
{
	//使用
}

四.指针运算

1.指针加减整数

#include

int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		*p = i;
		p++;
	}
	p = arr;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
	//for (i = 0; i < sz; i++)
	//{
	//	printf("%d\n", arr[i]);
	//}
	return 0;
}

2.指针减指针

我们知道指针实际上就是地址,指针减指针也就是地址减去地址

>#include<stdio.h>

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

探索指针的奇妙世界,程序中的魔法箭头(上)_第6张图片

结论:指针减去指针得到的数值的绝对值是两个指针之间的元素个数
指针和指针相减的前提是两个指针指向同一块空间

//计算字符串长度
#include

//int length(char* p) 
//{
//	int count = 0;
//	while (*p != '\0')
//	{
//		count++;
//		p++;
//	}
//	return count;
//}

//int length(char* p)
//{
//	if (*p == '\0')
//		return 0;
//	else
//		return 1 + length(p + 1);
//}

int length(char* p)
{
	char* start = p;
	while (*p != '\0')
	{
		p++;
	}
	return p - start;
}

int main()
{
	char arr[] = "abcdef";
	int len = length(arr);
	printf("%d\n", len);
	return 0;
}

3.指针的关系运算

指针的关系运算就是指针与指针之间比大小。

看这样一段代码

#define N 5
float v[N];
float* vp;
for (vp = &v[N]; vp > &v[0]; )
{
	*--vp = 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第7张图片

将上述代码稍作修改

#define N 5
float v[N];
float* vp;
for (vp = &v[N]; vp >= &v[0]; vp--) //这里变成了大于等于
{
	*vp = 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第8张图片

这段代码没什么问题也更容易理解。但是标准规定允许指向数组元素的指针与指向数组最后一个元素后面位置的指针比较,但是不允许与指向第一个元素前面位置的指针比较
探索指针的奇妙世界,程序中的魔法箭头(上)_第9张图片

五.指针和数组

我们通常所说的指针是指针变量,不是数组,是专门用来存放地址的。
数组是一块连续的空间,用来存放相同类型的数据。
联系:数组名是数组首元素的地址,地址就是指针。当我们知道数组首元素的地址,又因为数组是连续存放的,所以可以通过指针访问数组

//通过指针访问数组
#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\t%d\n", *(p + i), arr[i]);
	}
	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第10张图片

我们可以看到 *(p + i)与arr[i]是等价的

六.二级指针

#include
int main()
{
	int a = 10;
	int* p = &a;

	return 0;
}

在这里插入图片描述
探索指针的奇妙世界,程序中的魔法箭头(上)_第11张图片

#include
int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;//pp就是二级指针变量
	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第12张图片
二级指针变量就是用来存放一级指针变量的地址
一级指针变量用来存放变量的地址,这里就是a的地址

探索指针的奇妙世界,程序中的魔法箭头(上)_第13张图片

七.指针数组

指针数组就是存放指针的数组,也就是说指针数组是数组,并不是指针。

#include
int main()
{
	char arr1[] = "ni hao";
	char arr2[] = "hello";
	char arr3[] = "happy";

	char* parr[] = {arr1,arr2,arr3};//指针数组
	return 0;
}

探索指针的奇妙世界,程序中的魔法箭头(上)_第14张图片

打印这三个字符串

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", parr[i]);
	}

探索指针的奇妙世界,程序中的魔法箭头(上)_第15张图片

数组parr里面有3个元素,分别是数组arr1,arr2,arr3
通过指针数组,将这3个数组联系起来了

#include

int main()
{
	int arr1[] = { 11,12,13,14,15 };
	int arr2[] = { 21,22,23,24,25 };
	int arr3[] = { 31,32,33,34,35 };

	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

这里类似于二维数组,并不是真正的二维数组。真正的二维数组的存储在内存中是连续的。

上述代码的结果:
探索指针的奇妙世界,程序中的魔法箭头(上)_第16张图片

这很像是二维数组打印出来的结果,区别:
探索指针的奇妙世界,程序中的魔法箭头(上)_第17张图片
在这里插入图片描述
探索指针的奇妙世界,程序中的魔法箭头(上)_第18张图片

你可能感兴趣的:(C语言,c语言)