目录
一、传值调用和传址调用
二、数组名的理解
三、通过指针访问数组
四、一维数组传参的本质
五、指针数组
六、指针数组模拟实现二维数组
一、传值调用和传址调用
指针可以用在哪里呢?我们看下面一段代码:
#include <stdio.h>
void Swap(int x, int y)
{
int z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
运行结果如下:
我们发现,并没有完成交换,调试发现:
出现这样的原因:a和b是实参(是真实传递给函数的),x和y是形参,当实参ab传给形参xy时候,形参把实参数据临时拷贝了一份,x,y创建自己的独立空间,因为有自己独立的空间,跟a,b无关,所以修改形参不会影响实参。这种交换方式也叫传值调用。
所以说:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
措施:可以将a,b的地址传给Swap函数,Swap函数里通过地址间接的操作main函数中的a和b,达到交换的效果。也就是传址调用。
代码如下:
#include <stdio.h>
void Swap(int* pa, int* pb)//用指针变量来接收
{
int z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
Swap(&a, &b);//传地址
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。
- 未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。
- 如果函数内部要修改主调函数中的变量的值,就需要传址调用。
二、数组名的理解
数组名就是数组首元素(第一个元素)的地址。
代码如下:
但有两个例外:
- sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
- &数组名,这里的数组名表示整个数组,所以&数组名取出的是整个数组的地址
除此之外,任何地方使用数组名,数组名都表示首元素的地址。
可能会有疑问,为什么首元素地址(arr)跟整个数组地址(&arr)相同呢?
代码如下:
从值的角度来讲,地址就是一个编号,它们打印出来的地址一模一样,但是意义却不相同,操作arr和&arr带来的结果也是不一样的。
如下代码:
可以看出,&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,类型是int*,+1就是跳过一个整型(元素)。
但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的。
也可以这么理解:
arr与&arr都是指针,指针有两个要素:
- 第一个是地址值,也就指向的位置,打印出来的就是地址值,arr与&arr的地址值是一样的。
- 第二个是类型(所指向的数据类型),arr指向数组第一个元素,&arr指向数组arr,arr+1后的地址值会偏移一个元素的长度,&arr+1后的地址值会偏移一整个数组的长度,所以arr与&arr类型是不一样的。
三、通过指针访问数组
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;//p存放的数组首元素的地址
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
//scanf("%d", arr+i);//也可以这样写
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
//输出可以写成:
// *(p+i) <==> arr[i] <==> *(arr+i) <==> *(i+arr) <==> i[arr]
}
return 0;
}
可以发现,数组的下标引用操作符([ ])效果相当于解引用操作符(*)
其实数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
四、一维数组传参的本质
之前我们都是在函数外部计算数组的元素个数,那可以把函数传给一个函数后,在函数内部求数组的元素个数吗?
我们来看一段代码:
#include <stdio.h>
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);
return 0;
}
运行结果如下:
发现sz1计算错误,函数内部没有正确获得数组的元素个数。
原因:
数组名是数组首元素地址,传递的数组名,本质上数组传参传递的是数组首元素地址。
所以函数的形参部分理论上应该用指针来接收首元素的地址。那么在函数内部写sizeof(arr)计算的是一个地址的大小(单位字节),而不是数组的大小(单位字节)。
正因为函数的参数部分本质是指针,所以在函数内部是没办法求数组元素个数的。
代码如下:
void test(int arr[10]) //==>arr[] 参数写成数组形式,本质上还是指针
{
printf("%d\n", sizeof(arr)); //计算⼀个指针变量的⼤⼩
}
void test(int* arr) //参数写成指针形式
{
printf("%d\n", sizeof(arr));
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
test(arr);
return 0;
}
上面数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组。
形参的数组是不会单独再创建数组空间的,所以形参的数组是可以省略掉数组大小的。
总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
五、指针数组
数组是一组相同类型元素的集合。数组有整形数组,字符数组.....
指针数组:存放指针(地址)的数组。
int* arr1[6];//存放整型指针的数组
char* arr2[10];//存放字符指针的数组
如下图:
指针数组的每个元素是地址,又可以指向一块区域。
六、指针数组模拟实现二维数组
代码如下:
#include <stdio.h>
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数组中
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]);
//parr[i][j]<==>*(parr[i]+j)<==>*(*(parr+i)+j)
}
printf("\n");
}
return 0;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。
上述的代码模拟出二维数组的效果,本质上其实不是二维数组。
因为二维数组在内存中是连续存放的,而这三个数组在内存可不一定连续存放。