C语言--指针详解(中)--指针的基本使用、指针在数组中的应用

1. 指针的基本使用

1.1 传值调用和传址调用

1.1.1 传值调用

学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?
例如:写⼀个函数,交换两个整型变量的值⼀番思考后,我们可能写出这样的代码:

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

结果如下:
在这里插入图片描述
我们发现其实没产⽣交换的效果,这是为什么呢?调试⼀下
在这里插入图片描述
我们发现在main函数内部,创建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在调⽤Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,

简单理解就是x和y是a和b的拷贝,对拷贝x和y的改变,自然不能影响原先的数据,即Swap1函数内部交换x和y的值,⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b是没法交换的。

Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调⽤

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。

1.1.2 传址调用

我们现在要解决的就是当调⽤Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接将a和b的值交换了。那么就可以使⽤指针了,在main函数中将a和b的地址传递给Swap函数,Swap函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

#include <stdio.h>
void Swap2(int*px, int*py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{    
	int a = 10;
	int b = 20;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

交换后结果为:
在这里插入图片描述
我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传递给了函数,这种函数调⽤⽅式叫:传址调⽤

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

2. 指针在数组中的应用

2.1 数组名的理解

我们在使⽤指针访问数组的内容时,有这样的代码:

int arr[10] = {1,2,3,4,5,6,7,8,9,10};

int *p = &arr[0];

这⾥我们使⽤ &arr[0]的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,⽽且是数组⾸元素的地址,我们来做个测试。

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

结果为:
在这里插入图片描述
我们发现数组名和数组⾸元素的地址打印出的结果⼀模⼀样,数组名就是数组⾸元素(第⼀个元素)的地址。

这时候可能有疑问?数组名如果是数组⾸元素的地址,那下⾯的代码怎么理解呢?

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

输出的结果是:40,如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对。其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:
sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节
&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。

试⼀下这个代码:

#include <stdio.h>
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);
	printf("&arr= %p\n", &arr);
	return 0;
}

结果是三个打印结果⼀模⼀样,这时候⼜纳闷了,那arr和&arr有啥区别呢?

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

结果如下:

&arr[0] = 0077F8202
&arr[0]+1 = 0077F8243
arr = 0077F8204
arr+1 = 0077F8245
&arr = 0077F8206
&arr+1 = 0077F848

这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是⾸元素的地址,+1就是跳过⼀个元素。

但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的。

结论数组名 arr 其实表示的就是整个数组的起始位置,而 arr [ i ] 则表示的是第 i 个元素的地址,相当于访问的是 arr + i 地址的数据。
在以下两种情况arr 并不表示数组的首地址:

1. sizeof(arr)此处arr表示整个数组的地址
2.&arr此处arr 表示整个数组的地址

除了以上两个例外意外,其余情况arr 均表示数组的首地址。

2.2 使⽤指针访问数组

分析⼀下,数组名arr是数组⾸元素的地址,可以赋值给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?

#include <stdio.h>
int main()
{
	int arr[10] = {0};
	//输⼊  
	int i = 0;
	int sz = sizeof(arr)/sizeof(arr[0]);
	//输⼊
	int* p = arr;
	for(i=0; i<sz; i++)
	{
	scanf("%d", p+i);
	//scanf("%d", arr+i);//也可以这样写 } 
	//输出 
	for(i=0; i<sz; i++)
	{
	 printf("%d ", p[i]);
	}
	 return 0;
}

在第18⾏的地⽅,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]是等价于 *(p+i)

同理arr[i] 应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引⽤来访问的。

上面的结果可以等于以下的结果:

*(p + i) = p[i] = arr[i] =*(arr + i) = i[arr]

2.3 一维数组传参的本质

那可以把函数传给⼀个函数后,函数内部求数组的元素个数吗?

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

结果如下:
在这里插入图片描述
我们发现在函数内部是没有正确获得数组的元素个数。

数组名是数组⾸元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址

所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr)计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

2.4 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
这就是⼆级指针
在这里插入图片描述
对于⼆级指针的运算有:
*ppa通过对ppa中的地址进⾏解引⽤,这样找到的是 pa,*ppa其实访问的就是 pa .

int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa先通过 *ppa找pa ,然后对 pa进⾏解引⽤操作:*pa,那找到的就是 a

**ppa = 30;
//等价于*pa = 30; 
//等价于a = 30; 

3. 总结

指针主要用于需要改变实参的数值的函数里面。数组的数组名其实就是整个数组的首地址,通过首地址就可以很好的访问到数组中的所有元素。

  • 31
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值