一、数组名的理解
在指针第一课中,我们在指针运算那一节中使用指针访问数组的内容时,有这样的代码:
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int* p = &arr[0];//取出了数组的首元素的地址
这里我们使用 &arr[0] 的方式拿到了数组第⼀个元素的地址,但是其实数组名arr本来就是地址,而且是数组首元素的地址,我们来做个测试。
//代码一
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p1 = &arr[0];
int* p2 = &arr;
printf("arr[0]首元素的地址: %p\n", &arr[0]);
printf("arr数组名的地址: %p", arr);
return 0;
}
通过测试,我们可以知道,arr数组名确实是首元素的地址
这时候有人会有疑问?数组名如果是数组首元素的地址,那下面的代码二怎么理解呢?
//代码二
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));//结果为40
return 0;
}
相信有人觉得结果是4,毕竟arr是数组名,是首元素的地址,而地址的大小取决于在哪个环境下的,在x86(默认)中地址的字节大小为4字节,那么arr的大小也为4字节。
可是真的如此吗?通过测试,我们得到的结果为40,那么这里我们把数组名作为首元素地址就理解不了这个例子。那么我们该如何理解?
其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:
• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节,如代码二
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址。如代码三
//代码三
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" , &arr+1);
return 0;
}
从上面的代码三和运行结果,我们可以看到&arr (&数组名)的地址与数组首元素地址一样相等,但是含义不一样,因为指针类型不一样。如果我们还记得指针类型的意义,我们就知道指针的类型会影响步长(指针±整数),即影响指针的偏移量,看代码三,&arr+1后跳过了整个数组即40个字节,而arr+1跳过了一个元素即4个字节。所以&arr和arr的指针类型不一样。 比如&arr[0]和arr的类型都为int * ,而&arr的类型为 int (*) [10] 。
我们想一想,我们取出的地址是要干什么?当然是为了存放起来方便使用它,而指针变量又是存放地址的变量,那么指针变量和存放的地址就是同一个意思。看代码:
int a = 10;//其中a在内存中是不会存放a的
//a这个变量只是让我们程序员知道此时的a就是代表着10,即10的数据类型是int
int* p = &arr[0];//同理:此时p就代表着&arr[0](也就是首元素的地址)
//那么此时指针类型(地址类型)就是 int *
int* p = arr;//与上面一样
(这就和数组的类型以及数组元素的类型有关了,这里就先给出结果)
二、使用指针访问数组
有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针访问数组了。
//代码四
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
for(i=0; i<sz; i++)
{
scanf("%d", &arr[i]);
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
代码四中,我们先使用最初的方式写入和访问数组
//代码五
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
int* p = &arr[0];//也可以使用arr赋值给p
for(i=0; i<sz; i++)
{
scanf("%d", p+i);//这里不需要&符号了,因为p+i就是地址
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
代码五,我们使用了指针来写入和访问数组
比较代码四和五,我们可以深层得到一定理解,我们在用指针访问数组时可以用下面的代码来进行访问:
*(arr+i)= arr[i];//编译器在编译的时候,也是把arr[i]转化为*(arr+i)来计算的
*(p+i)= p[i];//因为p指针和arr指针都指向数组的首元素,那么可以用arr[i]索引的方式来访问数组
//那么就可以使用p[i]索引的方式来访问数组
//在计算机中,我们知道+支持交换律
*(p+i) = p[i]= *(i+p) = i[p];
即p就是arr,arr就是p
总结:
三、一维数组传参的本质
#include <stdio.h>
void test(int arr[]) //那么形参用数组接受,[]里可以写数字,也可以写任何整数(10,1000,2)
//还可以不写,因为本质是指针,不是数组,写了也没用,没有意义,
{
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); //实参为数组名
return 0;
}
通过数组名传值,我们可以看到,计算的元素个数居然不一样,一个为10,另一个为1。那我们反推一下看,既然sz2 = 1,那么sizeof(arr)的大小就应该是4个字节,4 / 4 = 1。可为什么函数test()里的sizeof(arr)的大小为4个字节呢?那么这就和一维数组传参的本质有关。
数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。 所以形参即使写成数组的形式,本质上也是一个指针变量。
所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
函数参数部分int arr[]本质上是 int * arr
四、 冒泡排序
#include<stdio.h>
#include<string.h>
input_arr(int* p, int sz)//输入函数
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
}
output_arr(int* p, int sz)//打印函数
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
}
paixu_arr(int* p, int sz)//排序的基本思想是:相邻的二个数比较大小,符合条件的交换,
{ //一趟完成一个数,10个数字的排序,需要9趟,因为9个数排序好位置,第10个数自动排序好
int i = 0; //并且每次完成一趟后,下一趟需要比较的数字就少一个
for ( i = 0; i < sz-1; i++)//每一趟
{
int j = 0;
for( j=0;j<sz-1-i;j++)//每一趟完成一个数字的排序
{
if (*(p+j) > *(p + 1+j))//升序排序;注意不是*p > *(p+j)
//因为开始时j=0,即*p和*(p+j)是一样的意思,如果是写成数组形式就不容易错,p[j]>p[j+1]
{
int temp = *(p+j);
*(p+j) = *(p + 1+j);
*(p +1+ j) = temp;
}
}
}
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
input_arr(arr, sz);//输入
output_arr(arr, sz);//输出
paixu_arr(arr, sz);//排序
output_arr(arr, sz);//输出
return 0;
}
下面对代码进行一些健壮
#include<stdio.h>
#include<string.h>
input_arr(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
}
output_arr(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
}
int count = 0;//计算比较了几次
bubble_arr(int* p, int sz)
{
int i = 0;
int flag = 1;//假定已满足排序
for (i = 0; i < sz - 1; i++)//每一趟
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//每一趟完成一个数字的排序
{
count++;
if (p[j] > p[j+1])//升序排序
{
int flag = 0;//说明没有排序好
int temp = p[j];//交换
p[j] = p[j + 1];
p[j + 1] = temp;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
input_arr(arr, sz);//输入
output_arr(arr, sz);//输出
bubble_arr(arr, sz);//排序
output_arr(arr, sz);//输出
printf("%d", count);//计算比较了几次
return 0;
}
添加代码flag的意义是,假如数据本来就是已经给定排序好的(升序),那么我们如果还是按最初的代码进行,比较浪费时间,会进行45次的比较次数,而添加flag后,我们先假定flag =1 ,假如没有进入if条件里,则flag一直等于1,不会等于0,那么在一趟中,没有数据交换,也就代表着数据已经排序好了。此时就可以跳出循环,避免再下一趟比较排序。
五、二级指针
我们前面学习的都是一级指针,那么什么是二级指针呢?
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针 。
int main()
{
int a = 10;
int* pa = &a;//pa是一级指针变量
int** ppa = &pa;//paa是二级指针变量
return 0;
}
图片中就是对二级指针的基本理解
那么怎么对二级指针变量进行使用呢?其实很简单,我们在一级指针时使用了* pa可以找到pa所指向的空间的值10,那么 *paa找到的是paa所指向空间的值(该值是一个地址,指向a),那么再次解引用得到 **paa找到的是 *paa所指向的空间的值为10。
* ppa = pa;//说白了就是这个等式
** paa = *pa = a;
提个小点:二级指针和二维数组没有对应关系!!!
六、 指针数组
指针数组是什么?是指针?还是数组? 不急先让我们看看代码
char arr[10];//字符数组 - 存放字符的数组
int arr[5];//整型数组 - 存放整型的数组
通过上面的代码类比,我们应该知道了,指针数组 — 存放指针的数组,数组的每一个元素是指针。
char* arr[5];//存放char* (字符指针)的数组
int* arr[6];//存放 int* (整型指针)的数组
那么如何来应用指针数组呢?
int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
如果我们想把a,b,c的地址都存放起来,使用pa,pb, pc指针变量来存放就显得有些繁琐了。这时就可以使用指针数组来快捷存放地址。
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a , &b , &c};
存放后,我们也可以使用学过的知识来把值打印出来
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a , &b , &c};
int i = 0;
for(i = 0;i < 3;i++)
{
printf("%d ",*(arr[i]));//结果是: 10 20 30
}
return 0;
}
七、用指针数组模拟二维数组
int main()
{
int arr1[9] = { 1,2,3,4,5,6,7,8,9 };
int arr2[9] = { 4,5,6,7,8,9,1,2,3 };
int arr3[9] = { 7,8,9,4,5,6,1,2,3 };
int* parr[3] = { arr1,arr2,arr3 };//指针数组,存放了三个一维数组的各个首元素地址
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 9; j++)
{
printf("%d ", parr[i][j]);//*(*(prr+i)+j)
}
printf("\n");
}
return 0;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型⼀维数组中的元素。
上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。
谢谢观看!希望以上内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。
因作者水平有限,有错误还请指出,多多包涵,谢谢!