c语言指针3

文章目录


前言

前面两章内容,小编已经讲述了指针和内存,指针之间的关系运算等有关内容,今天就给大家详细讲述一下指针与数组之间的联系了。


提示:以下是本篇文章正文内容,下面案例可供参考

一、数组名的理解

1.数组名正常情况是首元素的地址

数组名是首元素的地址

在前面内容我们就讲述了定义一个数组,数组名就是首元素地址,今天我们就用代码给大家证明一下。

代码部分

c 复制代码
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };

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


分析

数组在内存中存储如下

此时我们分别打印首元素的地址和数组名的地址,发现两个打印结果相同,所以我们同时都指向数组的首元素地址。


因此我们可以得出,数组名正常情况下就是首元素的地址

2.数组名不是首元素地址的情况

2. 1 sizeof(arr)中的数组名

在sizeof(arr)中的数组名不是数组首元素的地址,此时的arr代表整个数组,接下来让我们验证一下

c 复制代码
int main()
{
	int arr[10] = { 0 };

	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(&arr[0]));
	return 0;
}

代码分析

在上面代码中,定义了一个存储10个整型的数组,接下来,分别对arr和首元素的地址进行求字节数,并将字节数打印出来。如果此时arr代表首元素的地址进行求字节数,在32位环境下,指针变量的大小为四个字节,输出结果因为两个4,结果却是第一个结果为40,第二个结果为4。这是因为在sizeof(arr)中,此时arr代表整个数组,而不是首元素的地址,整个数组为10个整型的值,所以为40。

所以在sizeof中的arr代表整个数组,而不是首元素的地址。

2. 2 &arr中的arr代表整个数组

在c语言中,&arr中的arr也是代表整个数组 ,而不是单一的数组首元素的地址

代码引入

c 复制代码
int main()
{
	int arr[10] = { 0 };

	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还是 &arr 都是数组arr的地址,这样直接打印不好判断arr是否是代表首元素地址还是整个数组。看过小编前两节小内容,指针变量进行±可以判断跳过几个字节。arr代表首元素地址,变量类型是int*,整型指针+1跳过是一个整型的元素也就是一个字节。&arr代表整个元素,整个元素是10个整形数据,&arr+1代表跳过整个数组,也就是40个字节。如下图显示,因为是16进制数进行打印的地址,转换成10进制,可以得到,arr+1跳过四个字节,&arr+1则是跳过40个字节。

3. 结论

结论

综上:数组名就是数组首元素的地址,但是存在两种例外:

  1. sizeof(arr),sizeof中单独存放数组名,此时这里的数组名代表整个数组。计算的是整个数组的大小,单位是字节。

  2. &arr,这里的数组名代表整个数组,取出的是整个数组的地址(整个数组的地址和首元素的地址是有区别的 )。

除了上述两种情况,任何地方使用数组名,数组名都代表首元素的地址。


二、使用指针访问数组

1.使用指针输入输出数组中的数

代码如下(示例):

c 复制代码
int main()
{
	int arr[5] = { 0 };
	int* p = arr; //数组名为数组首元素的地址

	//输入
	for (int i = 0; i < 5; i++)
	{
		scanf("%d", p+i);
	}

	//输出
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}

代码分析

数组在内存中的存储如下:


输入数据到数组中:

将第一个数组的地址存入数组中,此时p = &arr[0];通过指针访问数组后面的元素,只需要进行指针+偏移量的做法。

输出数据到数组中:

在数组学习的时候,我们打印数组为arr[ i ];在这里我们用指针进行访问则是:p是首元素的地址,通过p访问第一个元素则是 * p,访问后面的元素,则是通过指针+1进行访问---*(p+i)进行访问。

思考

打印部分原先用arr[ i ]进行打印,等价于*(arr+i),此时我们可以发现, 下标引用操作符[ ]作用等价于解引用,这时我们是否也可以 p[ i ]进行打印数组中的元素呢?下面来实践一下

经过代码运行,我们发现是可以的,因此打印的方式又多了一种:

  1. arr[ i ]
  2. p[ i ]
  3. *(p + i)
  4. *(arr + i)

以上四种方法皆可以用作访问数组内的元素,他们的本质其实都是通过指针的方式来访问数组,数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。


三、一维数组传参的本质

一维数组传参的本质就是传递首元素的地址

代码验证

c 复制代码
int test(int arr[10])   
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);   
	return sz2;   
}
int main() intmain()  
{
	int arr[10] = { 0 };   
	int sz1 = sizeof(arr) / sizeof(arr[0]);   
	printf("sz1 = %d\n", sz1);   
	int sz2 = test(arr);   
	printf("sz2 = %d\n", sz2);   

	return 0; 返回0;  
}

假设数组传参传递的是首元素的地址,那么在测试函数中,sizeof(arr)中的arr就是数组首元素的地址,求出来的值在32位环境下因该为4。sz2的值应该为1,sz1的值应该为10。那么结果是不是这样的呢?

很明显,答案如我们预期一样,因此可以得出数组传参的本质就是传递首元素的地址 。所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。


四、冒泡排序

冒泡排序的核心思想就是两个相邻的数之间进行比较

将十个数的排序进行排序成升序

假设输入数据:

9 8 7 6 5 4 3 2 1 0

输出数据应为:

0 1 2 3 4 5 6 7 8 9

思维导图

在这里一趟冒泡排序就让一个数回到了应该的位置,那么让10个数回到正确的位置只需要9趟,然后在一趟冒泡排序中,第一次我们要交换9次,然后9到了原有的位置,9保持不变,然后下一趟冒泡排序就只需要交换8次就可以,因为9已经到了因该去的位置,此次类推下去,每趟冒泡排序都会减少一个交换的数。

结论

n个数 需要n-1趟冒泡排序,使用循环变量i,从0开始到n-1

接下来定义每一趟冒泡排序交换的个数

每一趟冒泡排序需要交换n-1-i,每一趟冒泡排序就会减少一个需要交换的数

代码实现

c 复制代码
void bubble_sort(int arr[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (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;     
}

五、二级指针

5.1 二级指针的定义

在这之前我们讲述了一级指针,一级指针就是存储变量的地址,那么指针变量也有地址,这我们我们定义二级指针进行存储指针变量地址。

c 复制代码
int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	return 0;

}


分析

在这里创建变量a此时系统空间会开辟一段空间进行存储a,此时a所在空间地址假设为0x12ff40,此时我们将a的地址取出放入指针变量p中,然后指针变量p也开辟一段空间进行存储a的地址,通过指针变量p我们可以找到a所在空间,指针变量p也有地址,它的地址为0x45ff87,我们取出它的地址放入二级指针变量pp中。

也就是我们可以将二级指针变量类型int **进行拆分成int*和*,前面的int*代表二级指针指向的那块空间的地址的变量类型是int*,*代表pp是一个二级指针变量。

5.1 二级指针的运用

二级指针的运用主要有如下两种:

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

六、指针数组

6.1指针数组的定义

在c语言中,我们常见到的数组有整型数组,字符数组

整型数组,存放的都是整型变量的数组

字符数组,存放的都是字符变量的数组

指针数组,存放的都是指针变量的数组

指针数组的每个元素都是⽤来存放地址(指针)的。

指针数组的每个元素是地址,⼜可以指向⼀块区域

6.2指针数组模拟二维数组

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[] = { 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;
}

分析

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

注意

在这里只是利用指针数组进行模拟二维数组,数组在内存中存储都是连续的,而arr1,arr2,arr3三个数组所处的空间是不同的,所以在利用指针数组模拟二维数组,这个不是真的二维数组

相关推荐
Komorebi.py4 分钟前
【Linux】-学习笔记05
linux·笔记·学习
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
搬砖的小码农_Sky7 小时前
C语言:数组
c语言·数据结构
朝九晚五ฺ8 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
猫爪笔记9 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
pq113_610 小时前
ftdi_sio应用学习笔记 3 - GPIO
笔记·学习·ftdi_sio
澄澈i10 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
爱米的前端小笔记11 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
alikami11 小时前
【前端】前端学习
学习
一只小菜鸡..11 小时前
241118学习日志——[CSDIY] [ByteDance] 后端训练营 [06]
学习