首页 > 其他分享 >重生之霸道C语言爱上我之走入指针(3)

重生之霸道C语言爱上我之走入指针(3)

时间:2024-09-07 12:25:50浏览次数:12  
标签:10 arr int 元素 C语言 数组 霸道 重生 指针

根据前两篇与指针的初步接触后,我们已经了解到了指针里面最基本的知识,而接下来的文章,将会更注重于指针的深入理解和运用。

1.数组名的理解

1.1 arr,即一个数组的数组名的理解

在上一篇文章1.1里面的代码中,我们在用指针访问数组内容的时候,有这样的代码:

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

 我们可以得到的运行结果如下:

我们发现二者的打印结果一模一样,所以更加证明数组名就是数组首元素(第一个元素)的地址。

但是,如果一直是这种结论的话,我们之前使用int sz = sizeof(arr)/sizeof(arr[0]) 来计算数组的元素个数,那里面的arr又该怎么理解呢?下面的代码的结果又该怎么理解呢?

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d", sizeof(arr));

	return 0;
}

 结果是:

 在这里,如果arr还是表示数组首元素的地址的话,那输出的结果应该是4或者8才对呀。

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

1.sizeof(数组名) : 在sizeof中单独放数组名,这里的数组名表示的是整个数组,计算的是整个数组的大小,单位是字节。

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

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

 1.2 arr与&arr的区别

现在我们再来看一下下面这个代码:

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的区别在哪里呢?我们在看一下下面这个代码。

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

 我们再回忆一下重生1的3.2的内容:指针+1或者-1,其实跳过1个指针指向元素的字节大小。

结果如下: 

我们可以发现&arr[0] 与&arr[0]+1相差4个字节,即指针&arr[0]所指的元素大小为4个字节,为1个整型。arr与arr+1相差4个字节,即指针所指arr的元素大小为4个字节,为1个整型。&arr与&arr+1相差40个字节,即指针&arr所指的元素大小为40个字节,为10个整型。(怎么计算相差40个字节?将结果的16进制的18换成10进制的数字a,将16进制的40换成10进制的数字b,b-a就会得到40了)

所以&arr表示的是整个数组的地址,+1是跳过整个数组的。 

2.使用指针访问数组

通过前面的讲解,我们可以很容易通过指针来访问数组元素了。下面写一个代码来通过指针输入输出数组元素:

//写一个代码来通过指针输入输出数组元素
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);//注意不要在%d后面加一个空格,而且后面不要用&符号,因为p本来就是地址
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}

	return 0;
}
//上面代码中的p都可以用arr替换

 那我们知道,数组名arr是数字首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的,我们之前可以使用arr[i]访问下标为i的数组元素,那么p[i]是不是也有相同的作用呢?

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);//在这里改成p[i]
	}

	return 0;
}

 我们发现结果依然可以输入输出数组元素。

所以,将*(p+i)换成p[i]的作用是一样的,所以p[i]本质上是等价于*(p+i)的。

同理,arr[i]也等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

3.一维数组传参的本质

我们知道,数组是可以传递给函数的,现在我们来探讨一下数组传参的本质。

首先我们从一个问题开始,我们之前都是在主函数里面通过sizeof(arr)/sizeof(arr[0]) 来计算数组的元素个数,那我们可以把数组传给一个函数,让这个函数内部来计算数组的个数吗?

我们可能会这样子写代码:

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);//不能写成arr[10],因为这不是数组名

	return 0;
}

 但是我们发现在函数内部没有正确获得数组元素的个数:

这就要涉及到本质了。我们已经知道,数组名就是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的就是数组首元素的地址。所以函数形参部分理论本质上是应该使用指针变量int* p来接受首元素的地址,而这里写成了数组的形式,主要是为了方便理解和使用。所以一维数组传参,形参部分可以写成数组的形式,也可以写成指针的形式。且函数形参部分是不会真实创建数组的,所以就不需要数组的大小。

那么在函数内部我们使用sizeof(arr) 计算的是一个地址的大小而不是一个数组的大小。正是因为函数的参数部分是指针,所以在函数内部是无法去数组的元素个数的。

补充:怎么在函数里面打印数组呢?

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

	return 0;
}

4.冒泡排序

这是其中一个排序算法,就是将一个整型数组里的元素从大到小或者从小到大排序。

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

现在我们来分析一下,如果要将“9 8 7 6 5 4 3 2 1 0”这一组数据进行升序排序,根据冒泡排序的核心,要遵循着前后比较,必要的时候交换的规律。

先将9和8比较,9>8,所以交换,变成了“8 9 7 6 5 4 3 2 1 0”,这为第1次比较。

将9和7比较,9>7,所以交换,变成了“8 7 9 6 5 4 3 2 1 0”,这为第2次比较。

以此内推,则这一次应该需要9次比较,9才可以在最后面。这位第1趟比较。

现在数子变成了“8 7 6 5 4 3 2 1 0 9”

先将8和7比较,8>7,所以交换,变成了“7 8 6 5 4 3 2 1 0 9”,这为第1次比较。

将9和7比较,9>7,所以交换,变成了“7 6 8 5 4 3 2 1 0 9”,这为第2次比较。

以此内推,则这一次应该需要8次比较,9才可以在倒数第二位。这位第2趟比较。

以此内推,可以研究出下列代码:

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 - 1 - i ; j++)//要记得这个比较条件
		{
			if (arr[j] > arr[j + 1])
			{
				int 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,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);//传的是arr,传的是地址,说明形参数组和原数组是同一个数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 现在我们来优化一下。

如果要按照上面的代码,那么就必须要进行(1+9)/ 2 * 9 =45对比较(怎么算的?第一次比较9次,第二次8次,然后7,6,5,4,3,2,1次,一点技巧就可以得出这个式子了),那如果对于“9,0,1,2,3,4,5,6,7,8”这种的话,进行45未免太麻烦,所以我们来对这个代码进行优化一下。

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 - 1 - i; 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)//说明刚刚根本就没有进入if语句里改变flag的值,
			          //就证明这一组数据已经没有哪个元素的后一位大于自己,即已经有序
			break;
	}
}

int main()
{
	int arr[10] = { 9,0,1,2,3,4,5,6,7,8 };
	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;
}

5.二级指针

指针变量也是变量,是变量就会有地址,那么指针变量的地址应该存到哪里呢?

int**为ppa的类型,ppa为二级指针变量,为了存放一级指针变量的地址。一级二级指针都是指针,大家都是地址。

咱对二级指针的运算有:

1.*ppa通过ppa中的地址进行解引用,找到的是pa,所以*ppa其实访问的就是pa。

int a = 20;
int* pa = &a;
int** ppa = &pa;
*ppa = &a;

2. **ppa先通过*ppa找到pa,然后再对pa进行解引用:*pa,那找到的就是a。

**ppa = 30;
//等价于*pa = 30,等价于a = 30

总结:*p啥就是找啥。 

6.指针数组

指针数组是指针还是数组呢?

我们类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。所以指针数组,就是存放一堆指针类型的数组。

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

所以每个元素,都指向自己特定的某个区域。表达方式例如int* arr[5],一样的元素类型+数组名+(元素个数)。char* arr[7], 这个就是存放字符指针的数组。

int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};//指针数组

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

首先,我们在回忆一下,一个数组int arr[], arr为其首元素的地址,若设int* p = arr,则p[i]等于arr[i], 指向的就是下标为i的数组元素。

 所以就可以写下面的代码:

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

结果为

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了各个整型数组的首元素地址,所以通过parr[i][j]就是整型一维数组中的元素。

上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行的位置并非连续的。

感谢大家的捧场~~~

标签:10,arr,int,元素,C语言,数组,霸道,重生,指针
From: https://blog.csdn.net/lulu_gh_yu/article/details/141981103

相关文章

  • C语言常量和字面量
    目录引言1.字面量1.1字符字面量1.2整型字面量1.3浮点字面量2.常量2.1使用预处理器指令#define定义常量2.1.1语法格式2.1.2使用举例2.2使用const关键字定义常量2.3使用#define和const定义常量的区别引言        字面量是直接在代码中......
  • 全国计算机二级考试C语言篇3——选择题
    C语言部分——C语言概述1.程序模块化的优点程序模块化的优点在于它可以使程序的开发、维护和复用变得更简单。下面是一些主要的优点:降低复杂度:模块化可以将复杂的问题分解成更小的、更易管理的部分。可维护性:模块化使得代码更易于维护,因为修改一个模块的影响被限制在该......
  • c语言内存函数
    今天来学习C语言中的内存函数目录1.memcpy代码形式示例运行结果2.memmove代码形式示例运行结果3.memset代码形式示例运行结果4.memcmp形式示例运行结果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b7d8d59577b248deaa7b869d014d8b4f.png#pic_center)5......
  • 学习笔记|鹏哥C语言——关键字
    一、 关键字typedeftypedef顾名思义是类型定义,这里应该理解为类型重命名。比如:二、 关键字static在C语言中:static是用来修饰变量和函数的1.修饰局部变量-称为静态局部变量2.修饰全局变量-称为静态全局变量3.修饰函数-称为静态函数三、 修饰局部变量......
  • E31.【C语言】练习:指针运算习题集(上)
    Exercise1求下列代码的运行结果#include<stdio.h>intmain(){ inta[5]={1,2,3,4,5}; int*ptr=(int*)(&a+1); printf("%d",*(ptr-1)); return0;}答案速查:分析:Exercise2 求下列代码的运行结果//在x86环境下//假设结构体的大小是20个字节......
  • C语言中有关函数的知识
        前言        C语言函数是一种函数,用来编译C语言,一般包括字符库函数,数学函数,目录函数,进程函数,诊断函数,操作函数等    这里这个函数和我们高中时期学的函数类似,高中的函数是这样    F(x)=5x+21                   ......
  • C语言程序设计(初识C语言后部分)
    不要重来,不要白来,不要重来。5.指针和数组数组:一组相同类型元素的集合指针变量:是一个变量,存放的地址要理解数组名大部分情况下是数组的首元素地址6.二级指针先了解一级指针变量二级指针变量(二级指针变量是用来存放一级指针变量的地址的)7.指针数组指针数组是......
  • C语言数据类型和变量
    引言好久不见大家,最近因为在忙开学的事情很久没有更新,很感谢大家的支持,我会继续努力滴!!!前篇链接:http://#小程序://CSDN/Rz9Z9VlUkPV8ttg那我们长话短说:开始我们的这一节--------C语言数据类型和变量数据类型介绍C语⾔提供了丰富的数据类型来描述⽣活中的各种数据。......
  • C语言——使用回调函数模拟实现qsort
    同学们还记得之前我们已经学过一种排序方法叫“冒泡排序“嘛。代码直接附上咯voidbubble_sort(intarr[],intsz){ inti=0;//趟数 for(i=0;i<sz-1;i++) { intj=0; for(j=0;j<sz-i-1;j++) { if(arr[j]>arr[j+1]) { inttmp=......
  • C语言-第七章:字符和字符串函数、动态内存分配
    传送门:C语言-第六章-加餐:其他自定义类型目录第一节:字符和字符串函数    1-1.strlen函数和sizeof关键字    1-2.memcpy内存拷贝函数    1-3.memmove内存拷贝函数    1-4.memset内存设置函数    1-5.strtok字符串切割函数......