深入理解指针(2)

一 , 野指针

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

野指针成因:

1.指针未初始化

2.指针越界访问

3.指针指向的空间释放 

1.1 指针未初始化

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

 

1.2 指针的越界访问

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

 1.3 指针指向的空间释放

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

 这里的 n 的局部变量,在进入test 的这个函数时,被创建,但是当出了这个函数 , n 就把空间还回去给内存,我们对n 原有的空间没有访问权限了,如果再去访问,就造成的非法访问; 

1.4 规避野指针

指针初始化: 

如果能够明确知道指针指向哪里,就应该直接赋值地址,但是如果不知道指针应该指向哪里,可以给指针赋值NULL , NULL是C语言中定义的一个常识符常量,值是0,0也是地址,但是这个地址是无法使用的,读写的时候会报错。 

int main()
{
	int a = 10;
	int* pa =&a;
	*pa = 50;
	int* pb = NULL;
	*pb = 20;
	return 0;
}

小心指针越界: 

一个程序向内存申请了那些空间,通过指针也能访问那些空间,不能超出范围访问。 

指针变量不再使用的时候,即使置NULL,指针使用之前检查有效性 

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL。 我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起来。不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使用
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<10; i++)
 {
 *(p++) = i;
 }
 //此时p已经越界了,可以把p置为NULL
 p = NULL;
 //下次使⽤的时候,判断p不为NULL的时候再使⽤
 //...
 p = &arr[0];//重新让p获得地址
 if(p != NULL) //判断
 {
 //...
 }
 return 0;
}

 避免返回局部变量的地址!

二 , 数组名的理解

 我们一般说,数组名是数组首元素的地址

但是!在以下两种情况下,数组名表示的是整个数组

  • sizeof(数组名),sizeof 中单独放数组名时,这里的数组名表示的时整个数组,计算的是整个数组的大小。
  • &数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址(整个数组的地址,与首元素的地址是有区别的)!

运行以下代码:

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

 从中我们知道:数组名本来就是地址,是数组首元素的地址 

 2.1  sizeof(数组名)

这里我们输出的结果是40,但是如果是arr数组的首元素应该是4/8才对,所以我们知道,在sizeof(数组名)里面的数组名表示的是整个数组。 

2.2 &数组名

int main()
{
	int arr[10] = { 0 };
	printf("&arr      = %p\n", &arr);
	printf("&arr +1   = %p\n", &arr+1);
	printf("\n");
	printf("arr       = %p\n", arr);
	printf("arr  +1   = %p\n", arr+1);
	printf("\n");
	printf("&arr[0]   = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
	return 0;
}

  

//指针类型决定了,指针+-整数时跳过的步长
//&arr [0] ----> int*
//arr      ----> int*
//&arr     ----> 类型? 

三 , 使用指针访问数组

 在访问数组的时候:我们可以采用数组下标来访问,也可以采用指针来访问

//使用指针访问数组
int main()
{
	//使用下标的方式访问数组
	int arr[10] = {0};
	int i = 0;
	//输入
	for (i = 0; i < 10; i++)
	{
		scanf("%d", arr+i);
	}
	//输出
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

//用指针来访问数组元素
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	//输入
	for (i = 0; i <sz; i++)
	{
		scanf("%d", p+i);//p+i 就是下标为i元素的地址
	}
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}

 *(i+p)          *(i+arr)

 *  (p+i)           *(arr+i)

     p[i]               arr[i]

     i[p]               i[arr]       

这些都是正确的写法,因为arr[i] 与 *(arr+i) 完全等价

arr[i]  的这种写法,编译器在执行的时候也会转成*(arr+i)的方式执行 

但是我们一般不会用i[arr]的这一种写法,可读性不强  

/arr[i] --- *(arr+i)  -- *(i+arr) -- i[arr]
//i[arr]  可读性弱,不建议这么写,但是可以了解
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", arr[i]);
		//printf("%d ", *(arr + i));
		//printf("%d ", *(i + arr));
		printf("%d ", i[arr]);
	}
	return 0;
}




int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	//arr是数组名,数组名是数组首元素的地址
	//p            是数组首元素的地址

	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", *(p + i));
		//printf("%d ", *(arr + i));
		//printf("%d ", p[i]);
		printf("%d ", arr[i]);
	}
	//arr[i] ---- *(arr+i) 完全等价
	//arr[i] 这种写法,编译器在执行的时候也会转化成*(arr+i)的方式执行
	return 0;
}

四 ,一维数组的传参本质

数组名是数组首元素的地址;而在数组传参的时候,传递的是数组名 ,也就是说,数组传参传递的是数组首元素的地址

运行如下代码:

void test(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d \n", sz);
}
int main()
{
	int arr[10] = { 0 };
	test(arr);
	return 0;
}

 

我们发现结果并不是我们想要的数组元素个数,而是得到了2。所以我们知道,这里形参是指针变量,在x64环境下,大小为2;所以,函数形参的部分理论上应该使用指针变量来接受首元素的地址。那么我们在函数内部我们写sizeof(arr)计算的是一个地址的大小,而不是数组的大小。正是因为函数的参数部分的本质是指针,所以在函数内部是无法计算数组元素的个数 

 如果想要使用数组元素个数,我们可以计算完后,在传递给函数

void test(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	test(arr, sz);
	return 0;
}

一维数组传参,形参部分可以写成数组形式,也可以写成指针形式 。

五 , 冒泡排序

核心思想:两两相邻的元素进行比较

//冒泡排序
//void bubble_sort(int* arr, int sz)
//{
//	int i = 0;
//	//趟数
//	for (i = 0; i < sz-1; i++)
//	{
//		//一趟的冒泡排序
//		for (int j = 0; j < sz - i - 1; j++)
//		{
//			if (arr[j] > arr[j + 1])
//			{
//			
//				int tmp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = tmp;
//			}
//		}
//	}
//}
//
//int main()
//{
//	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort(arr, sz);
//	for (int i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//	return 0;
//}

//优化
//void bubble_sort(int* arr, int sz)
//{
//	int i = 0;
//	//趟数
//	for (i = 0; i < sz - 1; i++)
//	{
//		//一趟的冒泡排序的细节
//		int flag = 1;//假设已经有序了
//		for (int j = 0; j < sz - i - 1; j++)
//		{
//			if (arr[j] > arr[j + 1])
//			{
//
//				int tmp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = tmp;
//				flag = 0;
//			}
//		}
//		//效率提高,如果一次都没有交换,flag 为1
//		if (flag == 1)
//		{
//			break;
//		}
//	}
//}
//
//int main()
//{
//	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort(arr, sz);
//	for (int i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//	return 0;
//}
// 

六 , 二级指针

指针变量也是变量,是变量就有地址。而存放一级指针的变量就是二级指针。 

int main()
{
	int a = 10;
	int* pa = &a;  //pa是指针变量,一级指针变量

	int** ppa = &pa;  //ppa 就是二级指针变量,二级指针变量用来存放一级指针变量的地址
	printf("%p\n", &a);
	printf("%p\n", pa);
	
	printf("%p\n", &pa);
	printf("%p\n", ppa);

	printf("%d\n", **ppa);
	//int*** pppa = &ppa;
	return 0;
}

 

 

七 , 指针数组

 在了解指针数组之前,我们先类比一下:

整型数组  ----  int arr[5]     ---- 存放整型的数组

字符数组  ----  char arr[6]  ----存放字符的数组

所以

指针数组   ----  是用来存放指针(地址)的数组

int main()
{
	//int arr1[5];
	//char arr2[6];
	char *  int* double*
	//char* arr3[10];  //存放字符指针的数组
	//int* arr4[7];    //存放整型指针的数组

	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[] = { &a,&b,&c };  //指针数组
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

指针数组的每一个元素都是地址,又可以指向一块区域

八 ,指针数组模拟二维数组

//指针数组模拟二维数组
int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[] = { 2,3,4,5,6,7 };
	int arr3[] = { 3,4,5,6,7,8 };
	//             int*  int* int*   --->数组名是首元素的地址
	int* arr[] = { arr1, arr2, arr3 };
	//指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的

 

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值