位置:首页 > C > 【C语言基础】:深入理解指针(三)

【C语言基础】:深入理解指针(三)

时间:2026-08-27  |  作者:多维游侠  |  阅读:0

目录

  1. 深入理解指针
  2. 一、冒泡排序
  3. 二、二级指针
  4. 三、指针数组
  5. 3.1 指针数组模拟二维数组
  6. 四、字符指针变量
一、冒泡排序 对应的技术说明图
一、冒泡排序概括一、冒泡排序的核心概念、关键要点与实践提示。
深入理解指针 对应的技术说明图
深入理解指针概括深入理解指针的核心概念、关键要点与实践提示。

前言

本文深入探讨C语言指针的高级应用,从冒泡排序的优化切入,通过引入标志变量提升排序效率,避免无效循环。随后详细解析二级指针与指针数组的概念及区别,重点阐述如何利用指针数组模拟二维数组结构,以简化多维数据的内存访问与操作逻辑。此外,文章还辨析了字符指针变量与数组指针变量的本质差异,帮助开发者在复杂场景下精准选择合适的数据类型,从而更灵活地操控内存地址,提升代码的执行效率与可读性。

【C语言基础】:深入理解指针(三) 的核心流程信息图
【C语言基础】:深入理解指针(三)用简体中文信息图概括【C语言基础】:深入理解指针(三)的核心流程、关键规则与实践要点。

深入理解指针

一、冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

#include

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 = 0;
				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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

可以看到,这段代码对arr数组进行了排序,但这个代码还有一些缺陷,那就是无论数组内部的元素是否有序,他都会循环9次,这样肯定是不合理的,要进行优化一下。

我们在bubble_sort函数的第一层循环里面定义一个变量flag,进入第二层循环就修改flag的值,第二层循环结束时给变量flag来个判断,如果变量flag没有发生改变,说明没有进入第二层循环,也就是说这时数组里的元素是有序的,就会直接跳出第一层循环。另外,我们还可以在最外面定义一个全局变量用来计数,每进行一次元素交换就会自增1。

#include
int count = 0;
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++)
		{
			count++;
			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[] = { 0,9,8,6,5,3,1,2,4,7 };
	int sz = sizeof(arr) / sizeof(arr[0]);  // 求出数组中的元素个数
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("n");
	printf("%dn", count);
	return 0;
}

运行之后可以发现,代码的运行效率明显提升了,理论来说上述数组元素在未优化的代码下运行是要进行45次交换的,现在明显次数减少了。

二、二级指针

通过前面的知识我们知道,指针变量也是一个变量,只要是一个变量就会有地址,那么,指针变量的地址放在哪里呢?我们把存放地址的指针称为二级指针,也就是指针的指针

#include
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	return 0;
}

而三级指针就是存放二级指针变量的地址,四级指针、五级指针以此类推...

对于二级指针的运算有:

  • *ppa通过对ppa进行解引用,就可以找到pa,*ppa其实访问的就是pa
int b = 20;
*ppa = &b;  // 等价于pa = &b
  • **pa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,找到的就是a。
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

三、指针数组

首先我们要弄懂一个问题:指针数组到底是一个指针还是一个数组?这里我们可以类比一下:

整数数组:就是一个存放整型的数组字符数组:就是一个存放字符的数组

那指针数组呢?没错,就是存放指针的数组。指针数组里的每个元素都是用来存放地址(指针)的。

3.1 指针数组模拟二维数组

通过上图可以清晰地看到,指针数组中的每一个元素本质上都是一个指针变量。而关键在于,这些指针变量中的每一个都可以独立地指向内存中的一块特定区域。这种结构允许我们构建类似二维数组的逻辑模型,尽管其底层存储机制与真正的二维数组存在显著差异。

在具体的访问操作中,parr[i] 用于访问指针数组 parr 的第 i 个元素。当我们找到这个元素时,发现它指向了一个整型一维数组。因此,parr[i][j] 实际上就是访问该整型一维数组中的第 j 个元素。需要特别注意的一点是,这仅仅是利用指针数组对二维数组进行的模拟,它并非真正的二维数组。其核心区别在于每一行的地址在内存中并非连续排列,这与传统二维数组连续分配的内存空间有着本质的不同。

四、字符指针变量

在探讨指针的类型时,我们知道存在一种特殊的指针类型,即字符指针 char*。在常规使用中,我们通常直接定义一个字符指针变量来指向字符串常量或字符数组。

此外,还有一种常见的初始化方式如下所示:

这里有一个极易引起误解的地方:const char* pc = "hello world"; 这一语句特别容易让人误以为是将整个字符串 "hello world" 完整地拷贝并放置到了字符指针 pc 所指向的空间里。然而,其本质并非如此。实际上,是将字符串 "hello world" 的首字符 'h' 的地址赋值并存放到了指针变量 pc 中。

换句话说,上述代码的含义是将一个常量字符串的首字符地址存储到指针变量 pc 中,而非字符串内容本身。

为了深入理解这一概念,下面通过一道经典的字符串相关题目来进行分析:

运行程序后,我们会发现打印输出的是序号 2 和序号 3 的语句。这背后的原因是什么呢?关键在于 str3 和 str4 指向的是同一个常量字符串。在 C/C++ 语言中,编译器会将常量字符串存储在一个单独的、特定的内存区域(通常称为只读数据段)。当多个指针指向同一个字符串常量时,它们实际指向的是内存中的同一块区域。然而,如果使用相同的常量字符串去初始化不同的字符数组时,编译器会为每个数组开辟出独立的内存块来存储副本。因此,str1 和 str2 指向不同的内存地址,而 str3 和 str4 则指向相同的地址。

五、数组指针变量

5.1 数组指针变量是什么?

在前文学习指针数组时,我们明确了一个概念:指针数组是数组元素为指针的数组。那么,与之相对的“数组指针”究竟是什么呢?为了厘清这一概念,我们可以借鉴之前的类比方法,通过对比来深入理解数组指针的定义与特性。

3/4

  • 整型指针变量int * pint; 存放的是整型变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那么数组指针就是存放数组的地址,能够指向数组数据的指针变量

我们判断一下下面那个是数组指针

int *p1[10];
int (*p2)[10];

int * p1[10]; 是一个指针数组,是 10 个整型指针的数组。 int ( * p2)[10]; 是数组指针,表示一个指向包含 10 个整型数据的数组的指针。因为 p2 是一个指针,所以 p2 是一个指针类型。

数组指针变量

int (*p2)[10];

这里p会先和 * 号结合,说明p是一个指针变量,然后指向的是一个大小为10个整型数据大小的数组,所以p是一个指针,指向一个数组,叫数组指针。 注意:[]的优先级是高于 * 号的,所以必须要加上()来保证 p会先和 * 结合。

5.2 数组指针变量的初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?其实就是我们之前学的 &数组名

int main()
{
	int arr[10] = { 0 };
	&arr;  //得到的就是数组的地址
	return 0;
}

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int main()
{
	int arr[10] = { 0 };
	int(*pa)[10] = &arr;
	return 0;
}

在调试的过程中我们也可以看到 &arrpa的类型是完全一致的。

数组指针类型解析:

int (*p) [10] = &arr;
 |    |    |
 |    |    |
 |    |    | p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

六、二维数组传参的本质

有了数组指针的理解,我们就能够讲下二维数组传参的本质了。 过去我们有一个二维数组的需要传参给个函数的时候,我们是这样写的:

#include

void test(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

二维数组传参的指针形式

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?首先我们再次理解一下二维数组,二维数组其实可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是一个一维数组。

如下图:

#include

void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *((*p + i) + j));
		}
		printf("n");
	}
}

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

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:

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

#include
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[] = { 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;
}
#include
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}
#include
int main()
{
	const char* pc = "hello world";
	printf("%sn", pc);//这里是把一个字符串放到pc指针变量里了吗?
	return 0;
}
#include
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
		printf("str1 and str2 are samen");  // 序号1
	else
		printf("str1 and str2 are not samen");  // 序号2
	if (str3 == str4)
		printf("str3 and str4 are samen");  // 序号3
	else
		printf("str3 and str4 are not samen");  // 序号4
	return 0;
}

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多