C语言指针深入浅出3

目录


1.数组名理解

之前我们用指针来访问数组时,有以下的代码:

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

我们使用了&arr[0]来访问数组第一个元素的地址,但是数组名本来就是地址,而且是数组首元素的地址。

c 复制代码
int main()
{
	//数组名是数组首元素的地址
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", &arr[0]);
	printf("%p\n", arr);
	printf("%d\n", *arr);
	return 0;
}

这段代码的结果:

我们发现数组名数组首元素的地址打印出来的结果一摸一样,数组名就是数组首元素(第一个元素)的地址

我们再来看一段代码:

c 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%zu\n", sizeof(arr));
	return 0;
}

输出的结果是:40, 假设数组名是数组首元素的地址,那应该是4 / 8 个字节才没问题。

数字名就是数组首元素的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放是数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • &arr ,这里的数组名表示整个数组,表示的是整个数组的地址

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

我们再看一下这段代码:

c 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", &arr[0]);
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

打印出的结果:

三个打印出的结果又一样,arr和&arr到底有什么区别?

我们来看一下下面的代码:

c 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}

输出的结果:

虽然 &arrarr 打印出来的地址的编号可能一样,都是数组的起始地址,但它们的含义不同。

表达式 含义 +1 后移动距离
arr 首元素地址 跳过 1 个元素
&arr[0] 首元素地址 跳过 1 个元素
&arr 整个数组的地址 跳过整个数组

它们的地址值可能一样,但指针类型不同,所以 +1 后移动的距离也不同,下面我们通过一张图片的方式来对三者进行对比:


2. 使用指针来访问数组

结合前面的知识,我们就可以用指针来访问数组了。

c 复制代码
int main()
{
	int arr[10] = { 0 };
	int* p = arr; //arr就是数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	//给数组赋值为1-10
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i + 1; //访问下标为i的元素
	//也可以这么写*(arr + i) , p[i]  , arr[i]
	}
	for (int i = 0; i < 10; i++)
		printf("%d ", *(p + i));
	return 0;
}

3. 一维数组传参的本质

数组是可以传递给函数的,于是我们就来讨论一下数组传参的本质。

我们之前都是在函数外部计算数组元素,我们可以把数组传给一个函数后,函数内部求数组的元素个数吗?

c 复制代码
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;
}

输出结果:

通过上述代码我们发现,在函数内部没有正确获得数组的元素个数。

我们来分析一下代码:

虽然参数写成了 int arr[],但在函数内部它本质上等价于:

c 复制代码
int* arr

也就是说,arr 实际上是一个 指针 。在函数内部:sizeof(arr)计算的是 计算的是⼀个首元素地址的⼤⼩(单位字节) ,不是整个数组的大小(单位字节)。因为此时 arr 已经不是完整数组,而是指向首元素的指针。从本质上来说,数组传参传递的是数组首元素的地址。

为什么sz2 = 2,而不是1呢?

数组传参后,函数里的 arr 是指针;(X64)中指针占 8 字节,int 占 4 字节,所以 sz2=2。

指针变量的大小相关知识点可点击下面的链接:C语言指针深入浅出1


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


4.冒泡排序

4.1 讲解算法原理

冒泡排序的核心思想是:两两元素相互比较。

下面我们来举个例子,假设一个arr数组中有这样一堆数:

c 复制代码
int arr[10] =  {9,8,7,6,5,4,3,2,1,0};

我们就可以有这样的算法思想,每一趟处理一个数字,将当前数字与其他未排序元素进行比较,最大的放在最右边(默认是升序)。

4.2 核心代码实现

c 复制代码
void bubble_sort(int arr[], int sz)
{
	for (int i = 0; i < sz - 1 ; i++) //确定趟数
	{
		for (int j = 0; j < sz - 1 - i; j++) //处理每一趟内部的元素
		{
			if (arr[j] > arr[j + 1]) //默认处理为升序
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

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

思考一下,对于某些接近有序的数组,传统冒泡排序仍然会继续执行剩余趟数,造成不必要的时间开销,比如看一下这个数组:

c 复制代码
int arr[10] = { 9,0,1,2,3,4,5,6,7,8 };

当第一趟交换完之后,数组就有序了,可以设置一个标记变量,如果某一趟排序过程中没有发生任何交换,说明数组已经有序,可以提前退出循环,这样就可以极大的节省时间成本。

优化过后的数组:

c 复制代码
void bubble_sort(int arr[], int sz)
{
	for (int i = 0; i < sz - 1; i++) //确定趟数
	{
		int flag = 1; //设置一个标记来判断,假设本趟没有发生交换
		for (int j = 0; j < sz - 1 - i; j++) //处理每一趟内部的元素
		{
			if (arr[j] > arr[j + 1]) //默认处理为升序
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 0; //发生了交换,说明数组还不一定有序
			}
		}
		if (flag == 1)
			break; //本趟没有交换,数组已有序,提前结束
	}
}

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

5. 二级指针

指针变量也是变量,变量也有地址,变量的地址存放在二级指针中了。

c 复制代码
int main()
{
	int a = 0;
	int* pa = &a; //一级指针
	int** ppa = &pa; //二级指针(用来存储一级指针变量的地址)
	return 0;
}

对二级指针运算有:

c 复制代码
int main()
{
	int a = 0;
	int* pa = &a;
	int** ppa = &pa;
	*ppa -> ppa 存放的是 pa 的地址,所以* ppa 访问的是 pa。
	**ppa -> **ppa 等价于*pa,最终访问的是 a 的值。
	return 0;
}

6.指针数组

我们进行指针数组学习之前类比一下,整型数组,存放的是整型的数组,字符数组,存放的是字符的数组,那么,指针数组就是存放指针的数组,数组的每一个元素类型都是指针类型。

指针数组中的每个元素都是用来存放地址(指针)的,指针数组每个元素是地址,可以指向一块区域,这样就可以用来模拟二维数组了。


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

c 复制代码
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数组中
	int* arr[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j)); //j就是偏移量
			//也可以这么写arr[i][j]
		}
		printf("\n");
	}
	return 0;
}

arr[i] 访问的是指针数组中的第 i 个元素,这个元素保存的是某个一维数组的首地址;因此 arr[i][j] 就可以访问对应一维数组中的第 j 个元素。这种方式可以模拟二维数组的访问效果,但本质上并不是真正连续的二维数组,因为每一行数据可能存放在不同的内存位置,而在二维数组中,每个数组元素的地址都是连续的


相关推荐
Shadow(⊙o⊙)1 小时前
初识Qt+经典方式实现hello world!的交互
开发语言·c++·后端·qt·学习
88号技师1 小时前
2026年4月一区SCI-狒狒优化算法Baboon optimization algorithm-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
平凡但不平庸的码农2 小时前
Go context 包详解
开发语言·后端·golang
隐士Xbox2 小时前
c++ 指针的用法
开发语言·c++·计算机视觉
江南十四行2 小时前
Python元类编程——从type到metaclass的深度探索
开发语言·python
众乐乐_20082 小时前
PHP 的进程 fork 机制
开发语言·php
yujunl2 小时前
U9 WCF调试的一个坑
开发语言
lly2024062 小时前
Scala 模式匹配
开发语言
2zcode2 小时前
基于MATLAB卷积神经网络的多颜色车牌识别系统设计与实现
开发语言·matlab·cnn