文章目录
前言
前面两章内容,小编已经讲述了指针和内存,指针之间的关系运算等有关内容,今天就给大家详细讲述一下指针与数组之间的联系了。
提示:以下是本篇文章正文内容,下面案例可供参考
一、数组名的理解
1.数组名正常情况是首元素的地址
数组名是首元素的地址
在前面内容我们就讲述了定义一个数组,数组名就是首元素地址,今天我们就用代码给大家证明一下。
代码部分
#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代表整个数组,接下来让我们验证一下
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也是代表整个数组 ,而不是单一的数组首元素的地址。arr也是指针,不过它指向的是一整个数组,此时arr是数组指针。
代码引入
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,原因很简单,这是因为
数组在内存中是连续存放的
2,指针加减整数运算方便获得每一个元素的地址
1.使用指针输入输出数组中的数
代码如下(示例):
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)
以上四种方法皆可以用作访问数组内的元素,他们的本质其实都是通过指针的方式来访问数组,数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
2.数组和指针的区别
- 数组就是数组,是一块连续的空间(数组的大小和数组元素的个数和元素类型都有关系)
- 指针就是指针,是一个变量,一个变量的大小(4/8个字节) ,指针变量存放的就是地址
- 数组名是地址,是首元素的地址
- 可以使用指针来访问数组
三、一维数组传参的本质
一维数组传参的本质就是传递首元素的地址
代码验证
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;而主函数中,sizeof中的arr则是整个数组,求出来的值在32位环境下应该为40。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和8进行比较,9比8大,于是两个交换顺序,然后我们继续两两之间的数进行比较,后面还需要交换9次,交换9次后9才到了该去的位置,也就是最大的数排到最后去了。
然后在下一趟冒泡排序中9保持不变,然后下一趟冒泡排序就只需要交换8次就可以,因为9已经到了应该去的位置,此次类推下去,每趟冒泡排序都会减少一个交换的数。
结论
n个数 需要n-1趟冒泡排序,使用循环变量i,从0开始到n-1
接下来定义每一趟冒泡排序交换的个数
每一趟冒泡排序需要交换n-1-i,每一趟冒泡排序就会减少一个需要交换的数
代码实现
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;
}
代码改进
假设该数组元素是有序的,则就不需要交换;如果需要交换,那么这组数组则是无序的
void bubble_sort(int arr[10], 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])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;
}
}
if(i)
break;
}
}
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 二级指针的定义
在这之前我们讲述了一级指针,一级指针就是存储变量的地址,那么指针变量也有地址,这我们我们定义二级指针进行存储指针变量地址。
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.
int a = 10;
*pp = &a;//等价于 p = &a;
- **pp 先通过 *pp 找到 p ,然后对 p 进⾏解引⽤操作: *p ,最终找到的是 a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
六、指针数组
6.1指针数组的定义
在c语言中,我们常见到的数组有整型数组,字符数组
整型数组,存放的都是整型变量的数组
字符数组,存放的都是字符变量的数组
指针数组,存放的都是指针变量的数组
指针数组的每个元素都是⽤来存放地址(指针)的。
指针数组的每个元素是地址,⼜可以指向⼀块区域
6.2指针数组模拟二维数组
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 ]就是整型一维数组中的元素。也就是说假如我们arr[ 0 ],那我们拿到的就是arr1的数组首元素地址,我们要想访问arr1数组中的元素,则是arr[ 0 ][ j ]进行访问的。
注意
在这里只是利用指针数组进行模拟二维数组,真正的二维数组在内存中存储都是连续的,而arr1,arr2,arr3三个数组所处的空间是不同的,所以在利用指针数组模拟二维数组,这个不是真的二维数组。