指针(Pointer)的深度理解(2)

上一篇博客给大家讲了一部分的指针,指针的内容很多但也很重要,SO现在继续!希望大家多多支持!


指针的使用和传址调用

下面我们先看一段代码:

#include
int my_strlen(const char* str)
{
	int count = 0;
	assert(str);
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

这是一段模拟库函数strlen的代码实现,代码中就将字符串的首个字符指针传给了函数,进而完成代码的实现。

接下来我们再看一段Swap函数的代码:

#include 
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int i = 0;
	int j = 0;
	scanf("%d %d", &i, &j);
	printf("交换前:a=%d b=%d\n", i, j);
	Swap1(i, j);
	printf("交换后:a=%d b=%d\n", i, j);
	return 0;
}

运行之后的结果为:


不难发现其实 i 和 j 的值并未发生转换,这是为什么呢,我们继续往下看:指针(Pointer)的深度理解(2)_第1张图片


通过监视窗口我们发现虽然 i,j 的值与x,y的值对应相等但他们的地址却不相等,这是为什么呢?因为x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值, 自然不会影响a和b,当Swap函数调用结束后回到main函数,a和b的没法交换。Swap函数在使用 的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这 种叫传值调用。

所以我们得出一个结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。 所以Swap是失败的了。


所以我们就需要运用到指针的传址调用了!我们只需要把 i 和 j 的地址传给函数即可:

#include 
void Swap1(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int i = 0;
	int j = 0;
	scanf("%d %d", &i, &j);
	printf("交换前:a=%d b=%d\n", i, j);
	Swap1(&i, &j);
	printf("交换后:a=%d b=%d\n", i, j);
	return 0;
}

指针(Pointer)的深度理解(2)_第2张图片


我们可以看到实现成Swap的方式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这种函数调用方式叫:传址调用。

结论:传址调用,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调用。如果函数内部要修改 主调函数中的变量的值,就需要传址调用。 

数组名与指针

数组名和指针有啥关系呢?我们看下面代码:

#include 
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr     = %p\n", arr);
	return 0;
}

指针(Pointer)的深度理解(2)_第3张图片

我们发现数组名和数组首元素的地址打印出的结果⼀模⼀样,数组名就是数组首元素(第⼀个元素)的地址。但需要注意的是在两种情况下这两个还是有所区别的:

  • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

下面给大家展示代码理解一下:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));
	return 0;
}

指针(Pointer)的深度理解(2)_第4张图片


我们可以看到这里显示40个字节,SO这里就印证了上面的说法·

下面我们在思考一下,下面的代码:

#include 
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\n", &arr + 1);
	return 0;
}

指针(Pointer)的深度理解(2)_第5张图片


是不是发现其中的奥妙了呢!

&arr[0]      = 004FFC14     数组首元素地址
&arr[0]+1  = 004FFC18     第二个元素的地址
arr             = 004FFC14     数组首元素地址
arr+1         = 004FFC18     第二个元素的地址
&arr           = 004FFC14     数组首元素地址
&arr+1      = 004FFC3C     跳过整个数组的地址

这里我们就需要注意一个问题了:

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

指针(Pointer)的深度理解(2)_第6张图片


可已看出我们将数组传到函数时此时arr是一个地址所以其大小只能是4(在32位机器中)所以

sizeof(arr) / sizeof(arr[0]) 的值为1。

二级指针

二级指针也称为指向指针的指针,是指针的指针,即指针变量的值是指向另一个指针变量的地址。

我们知道指针变量也是变量那么,变量就会有地址,那么二级指针便是用来储存指针的地址。

#include
int main()
{

	int a = 10;
	int* p = &a;
	int** pa = &p;
	printf("%p  %p", p, pa);
	return 0;


可以看出地址确实是存在的。

指针数组

我们可以联想到之前,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?那当然是存放指针的数组了。

指针(Pointer)的深度理解(2)_第7张图片

看了上图是否清晰明了了呢!!!

最后我们来讲一下:指针数组模拟二维数组

代码的实现可以看下面的代码:

#include 
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]);
		}
		printf("\n");
	}
	return 0;
}

指针(Pointer)的深度理解(2)_第8张图片

这样我们就可以用指针数组模拟出一个二位数组了。

好了今天到此结束!喜欢的可以三连加关注哦!

指针(Pointer)的深度理解(2)_第9张图片


你可能感兴趣的:(算法,c#,c语言)