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