文章目录
- 前言
- 一、数组名的理解
-
- 1.数组名正常情况是首元素的地址
- 2.数组名不是首元素地址的情况
-
- [2. 1 sizeof(arr)中的数组名](#2. 1 sizeof(arr)中的数组名)
- [2. 2 &arr中的arr代表整个数组](#2. 2 &arr中的arr代表整个数组)
- [3. 结论](#3. 结论)
- 二、使用指针访问数组
- 三、一维数组传参的本质
- 四、冒泡排序
- 五、二级指针
-
- [5.1 二级指针的定义](#5.1 二级指针的定义)
- [5.1 二级指针的运用](#5.1 二级指针的运用)
- 六、指针数组
前言
前面两章内容,小编已经讲述了指针和内存,指针之间的关系运算等有关内容,今天就给大家详细讲述一下指针与数组之间的联系了。
提示:以下是本篇文章正文内容,下面案例可供参考
一、数组名的理解
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. 结论
结论
综上:数组名就是数组首元素的地址,但是存在两种例外:
-
sizeof(arr),sizeof中单独存放数组名,此时这里的数组名代表整个数组。计算的是整个数组的大小,单位是字节。
-
&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 ]进行打印数组中的元素呢?下面来实践一下
经过代码运行,我们发现是可以的,因此打印的方式又多了一种:
- arr[ i ]
- p[ i ]
- *(p + i)
- *(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三个数组所处的空间是不同的,所以在利用指针数组模拟二维数组,这个不是真的二维数组