在上一次的博客中我了解到了指针变量、const的作用、指针之间的运算、还有野指针的介绍以及assert断言

指针(一)

一、数组名的理解

见如下代码

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

这里我们使用&arr[0]的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,⽽且是数组⾸元素的地址,我们来做个测试:

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 return 0;
}

结果:

【C语言】指针(二)_指针

  • 我们发现数组名和数组⾸元素的地址打印出的结果⼀模⼀样,数组名就是数组⾸元素(第⼀个元素)的地址

紧接着我们再看下列代码

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("%d\n", sizeof(arr));
 return 0;
}

该代码输出结果为40,如果arr是数组首元素的地址,那输出应该为4/8才对。😮😮😦

因此在这有一个非常重要的知识点:

其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节
  • &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素的地址。

扩展1:

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 printf("&arr = %p\n", &arr);
 return 0;
}

结果:

【C语言】指针(二)_指针_02

扩展2:

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("&arr[0]+1 = %p\n", &arr[0]+1);
 printf("arr = %p\n", arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr = %p\n", &arr);
 printf("&arr+1 = %p\n", &arr+1);
 return 0;
}

结果:

【C语言】指针(二)_指针_03

可见2中的&arr[0]和4中的arr均表示元素首地址,而&arr表示整个数组。因此&arr+1跳过整个数组。

2、使用指针访问数组

#include <stdio.h>

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);
 //scanf("%d", arr+i);//也可以这样写 
 }
 //输出 
 for(i=0; i<sz; i++)
 {
 printf("%d ", *(p+i));//*(p+i)等同于p[i],*(解引用)用来访问地址。
 }
 return 0;
}

3、一维数组传参的本质

⾸先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给⼀个函数后,函数内部求数组的元素个数吗?

#include <stdio.h>
void test(int arr[])
{
 int sz2 = sizeof(arr)/sizeof(arr[0]);
 printf("sz2 = %d\n", sz2);
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int sz1 = sizeof(arr)/sizeof(arr[0]);
 printf("sz1 = %d\n", sz1);
 test(arr);
 return 0;
}

结果:

【C语言】指针(二)_指针_04

可见在函数内部是没有正确获得数组的元素个数。

这就要学习数组传参的本质了,上个⼩节我们学习了:数组名是数组⾸元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址。

所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。

  • 那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
void test(int arr[])//参数写成数组形式,本质上还是指针 
{
 printf("%d\n", sizeof(arr));
}

void test(int* arr)//参数写成指针形式 
{
 printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩ 
}

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

以上代码结果均为4,均输出数组首元素地址。

总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

*4、冒泡排序

void bubble_sort(int arr[], int sz)//参数接收数组元素个数 
{
 	int i = 0;
 	for(i=0; i<sz-1; i++)//每次一大轮结束,都会找出一个最大或最小的
 	{
 		int j = 0;
 		for(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[] = {3,1,7,5,8,9,0,2,4,6};
 int sz = sizeof(arr)/sizeof(arr[0]);
 bubble_sort(arr, sz);

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

上面的代码不管你的数组是有序还是无序都会给你循环九趟(当遇见一个有序数组或者部分有序数组就会出现许多没有必要的步骤),显得有一点智障。

优化:

void bubble_sort(int arr[], int sz)//参数接收数组元素个数 
{
 int i = 0;
 for(i=0; i<sz-1; i++)
 {
 		int flag = 1;//假设这⼀趟已经有序了 
 		int j = 0;
 		for(j=0; j<sz-i-1; j++)
 		{
 			if(arr[j] > arr[j+1])
 			{
 				flag = 0;//发⽣交换就说明,⽆序 
 				int tmp = arr[j];
 				arr[j] = arr[j+1];
 				arr[j+1] = tmp;
 			}
 		}
 		if(flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了 
 		break;
 	}
}
int main()
{
 	int arr[] = {3,1,7,5,8,9,0,2,4,6};
 	int sz = sizeof(arr)/sizeof(arr[0]);
 	bubble_sort(arr, sz);
 	for(i=0; i<sz; i++)
 	{
 		printf("%d ", arr[i]);
 	}
 	return 0;
}

5、二级指针

指针变量也是变量,是变量就会有指针,那指针变量的地址存放在哪里?

如图:

【C语言】指针(二)_指针_05

对于二级指针的运算有:

  • *ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b; 
  • **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a
**ppa = 30;
//等价于*pa = 30; 
//等价于a = 30; 

6、指针数组

指针数组和整型数组、字符数组大致相同,只是存放的东西不一样。

指针数组即是用来存放指针的数组,如图:

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

7.指针数组模拟二位数组

#include <stdio.h>

int main()
{
 int arr1[] = {1,2,3,4,5};
 int arr2[] = {2,3,4,5,6};
 int arr3[] = {3,4,5,6,7};
 //数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中 
 int* parr[3] = {arr1, arr2, arr3};
 int i = 0;
 int j = 0;
 for(i=0; i<3; i++)
 	{
 		for(j=0; j<5; j++)
 		{
 			printf("%d ", parr[i][j]);
 		}
 		printf("\n");
 	}
 return 0;
}

【C语言】指针(二)_指针_06


parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。

但是上述二维数组的每一行并非连续的,因此并非完全为二维数组


🤔🤔🤔😊