【C语言】指针(二)

发布于:2024-06-01 ⋅ 阅读:(171) ⋅ 点赞:(0)

目录

一、传值调用和传址调用

二、数组名的理解

三、通过指针访问数组

四、一维数组传参的本质

五、指针数组

六、指针数组模拟实现二维数组


一、传值调用和传址调用

指针可以用在哪里呢?我们看下面一段代码:

#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;
}

传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。

  • 未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。
  • 如果函数内部要修改主调函数中的变量的值,就需要传址调用。

二、数组名的理解

数组名就是数组首元素(第一个元素)的地址。

代码如下:

但有两个例外:

  1. sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  2. &数组名,这里的数组名表示整个数组,所以&数组名取出的是整个数组的地址

除此之外,任何地方使用数组名,数组名都表示首元素的地址。

可能会有疑问,为什么首元素地址(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]就是整型一维数组中的元素。

上述的代码模拟出二维数组的效果,本质上其实不是二维数组。

因为二维数组在内存中是连续存放的,而这三个数组在内存可不一定连续存放。


网站公告

今日签到

点亮在社区的每一天
去签到