C语言指针(三):数组传参本质、冒泡排序与二级指针详解

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. 数组名的理解

在上⼀篇blog中我们在使⽤指针访问数组的内容时,有这样的代码:
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;
}
输出结果:数组名和数组⾸元素的地址打印出的结果相同,数组名就是数组⾸元素(第⼀个元素)的地址
但是,数组名如果是数组⾸元素的地址,这句话不够严谨:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%zu\n", sizeof(arr));
return 0;
}
输出的结果是40,如果arr是数组⾸元素的地址,那输出应该的应该是8才对(x64)
数组名的理解有两个特殊情况:
sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的大小
&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址数值上相同,但意义不同)

#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;
}

&arr和arr值相同,意义如下;

#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;
输出结果:
&arr[0] = 0077F820
&arr[0]+1 = 0077F824
相差4个字节
arr = 0077F820
arr+1 = 0077F824
相差4个字节
&arr = 0077F820
&arr+1 = 0077F848
两者相减得0x28,40byte,是整个数组的大小,&arr+1跳过的是整个数组。这个原理和上一篇blog中介绍的不同类型的指针的意义相似,类型不同,单位不同,所能操作的内存空间就不同

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址

2. 使用指针访问数组

int main()
{
	int arr[10] = { 0 };
	//给数组中存值
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", *(arr+i));
	}

	//打印数组
	for (int i = 0; i < 10; i++)
	{
		printf("%d", *(arr+i));
	}
	return 0;
}

 不用指针打印数组元素时,一般这样写arr[i],这里用指针的表达方式是这样,二者等价

arr[i] == *(arr + i) 
//加法交换律
arr[i] == *(arr + i) == *(i+arr) == i[arr] ???

发现了一个有趣的点

是否可以这样表示呢??? 

int main()
{
	int arr[10] = { 0 };
	//给数组中存值
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", (arr + i));
	}

	//打印数组
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", i[arr]);//没毛病6666
	}
	return 0;
}

通过运行发现,编译通过。布什戈门,有点逆天了哈哈哈哈哈

这说明:[]仅仅是一个操作符而已,这是这样子表示的,在计算机内部表示的就是*(i+arr)

3. 一维数组传参的本质

通过一段代码进行观察:
//一维数组传参的本质
size_t test(int a[])
{
	size_t sz = sizeof(a) / sizeof(a[0]);
	return sz;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7 };
	printf("1. %zu\n", sizeof(a) / sizeof(a[0]));
	printf("2. %zu\n", test(a));
}

运行结果:两次打印的结果并不相同,第一种就是7,元素个数;第二种则为2(x64)

倒推一下,a[0]为int大小是4,结果是2说明sizeof(a)为8,这就说明这是一个指针。而我们实参就是数组名,也就是首元素的地址

//一维数组传参的本质
size_t test(int a[])//本质上是指针
{
	size_t sz = sizeof(a) / sizeof(a[0]);
	//传进来的是a,不是&a,不是sizeof(a)
	//因此并不能算出来整个数组的大小
	return sz;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7 };
	printf("1. %zu\n", sizeof(a) / sizeof(a[0]));
	printf("2. %zu\n", test(a));
}
//本质上传的是首元素地址
函数的参数部分是本质是指针,在函数内部是没办法求的数组元素个数的
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

4. 冒泡排序

冒泡排序的核⼼思想就是:两两相邻的元素进⾏⽐较
//⽅法1
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);
    int i = 0;
    for(i = 0; i < sz; i++)
    {
    printf("%d ", arr[i]);
    }
    return 0;
}

在改基础上,还可以进行改良:

如果排序到下一趟时,发现后面已经是升序了,就没必要在依次检索一遍了;若还想知道进行交换了多少次,也可以进行记录 

//⽅法2 - 优化
//冒泡排序
int bubble_sort(int arr[],int sz)
{
	int cnt = 0;
	for (int i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		for (int j = 0; j < sz - i -1; j++)
		{
			int tmp = 0;
			if (arr[j] > arr[j + 1])
			{
				cnt++;//每交换一次,记录一下
				tmp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = tmp;
				flag = 0;
			}
		}
		if (flag)//排完一趟发现并没有实现交换,则不用再排序了,解释:
                 //因为冒泡排序是从前到后依次比较的,没有实现交换,那么则都是整齐的
		{
			return cnt;
		}
	}
	return cnt;
}

int main()
{
	int arr[] = { 3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int cnt = bubble_sort(arr,sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	printf("%d\n", cnt);
	return 0;
} 

5. ⼆级指针

指针的指针
#include <stdio.h>

int main(){
    int a = 100;
    int * pa = &a;
    int* * ppa = &pa;
    return 0;
}

pa是一个指针变量,势必也会有开辟内存空间,该空间也势必有自己的指针,即使这块空间中存放的就是一个指针

6. 指针数组

//指针数组
//类比 字符数组 整形数组
//存放指针的数组
是存放指针的数组,指针数组的每个元素都是⽤来存放地址(指针)的
指针数组的每个元素是地址,⼜可以指向⼀块区域

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

//使用指针数组模拟一个二维数组
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };//二维数组

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}
数组arr中放着各个数组的首元素地址,通过这些地址又可以找到他们数组中的其他元素,可以看成是一个二维数组,但:
上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。

网站公告

今日签到

点亮在社区的每一天
去签到