在c语言中传数值和传指针的区别,真正搞懂传值引用和传指针引用

通过一个基本原则和两个例子讲述传值引用和传指针引用的区别,并且从系统底层解析这两种调用的区别

acmore

2017.11.14

1. 问题概述

在C语言调用函数时,有两种传参数的方法,一种是传值,另一种是传参数。对于C语言的初学者(甚至是一些C语言的熟练使用者)而言,区别这两种形式往往比较困难,再加上C语言有指针,以及指针的指针等等,复杂多变的形式会使得这个过程更加困难。

很多初学者会尝试通过形式来记忆这两种方式,比如函数声明中有*的就是传指针,没有的就是传值,但是如果传入的参数本身就是一个指针,无论是传值还是传指针,函数声明都会有*,这种时候原有的形式记忆不再有效,需要再次记忆另外一套形式。形式是无穷无尽的,只有明白指针类型的本质,以及在函数调用过程中系统里到底发生了什么,才能真正理解这两种方式。

2. 简单认识

现在我们有两个函数,作用都是修改一个int类型的值,分别如下:

#include

void modify1(int a) {

a = 10086;

}

void modify2(int *a) {

*a = 10086;

}

int main() {

int x = 1;

printf("%d\n", x);

modify1(x);

printf("%d\n", x);

int y = 1;

printf("%d\n", y);

modify2(&y);

printf("%d\n", y);

return 0;

}

运行程序,会输出什么呢?

1

1

1

10086

这个程序很简单,我们可以轻易看出modify1是传值引用,modify2是传指针引用。传值引用不会改变调用者的值,而传指针会改变调用者的值,并且在形式上,函数声明中有*,函数调用有&。因此我们这样记忆:只要改变了调用者的值,并且有*的函数就是传指针引用。

真的是这样吗?

3. 深入理解

下面我们定义一个结构体,这个结构体含有两个整数值,然后类似地写出两个修改函数:

#include

#include

typedef struct Value {

int a;

int b;

}Value;

void modify1(Value *v) {

v->a = 10086;

v->b = 10010;

}

void modify2(Value **v) {

(*v)->a = 10086;

(*v)->b = 10010;

}

int main() {

Value *v1 = (Value *) malloc(sizeof(Value));

v1->a = v1->b = 1;

printf("%d\t%d\n", v1->a, v1->b);

modify1(v1);

printf("%d\t%d\n", v1->a, v1->b);

Value *v2 = (Value *) malloc(sizeof(Value));

v2->a = v2->b = 1;

printf("%d\t%d\n", v2->a, v2->b);

modify2(&v2);

printf("%d\t%d\n", v2->a, v2->b);

return 0;

}

输出结果如下:

1 1

10086 10010

1 1

10086 10010

这次我们再按照之前的经验去判断,好像有点行不通,因为在对modify1和modify2的调用上虽然像是一个传值一个传指针,但是两个函数的声明中都有*,并且v1和v2的值都被改变了,经验不再适用!

这种时候我们应该回归底层,老老实实从系统层面理解在程序执行过程中到底发生了什么,我们首先应该记住的一个最重要的原则是:

- 指针类型和基本类型一样,本质没有什么区别

我们拿类型int和Value *做比较,根据上述原则,便有以下几条原则:

int在内存中占有一块空间,那么Value *同样会在内存中占有一块空间

可以得到一个指针指向int,那么也可以有一个指针指向Value *

使用&可以取得int的指针,那么同样可以使用&取得Value *的指针

传值引用时,有变量int a和Value* v,下边两种写法都是传值引用

操作int时,函数的参数类型就是int,调用函数时传入a

同理在操作Value *时,函数的参数类型就是Value *,调用函数时传入v

传指针引用时,下边两种写法都是传指针引用

操作int时,函数的参数类型是int *,调用函数时传入&a

操作Value *时,函数的参数类型是Value **,调用函数时传入&v

根据以上原则,不要把指针当做一个特殊的类型,把指针类型那一堆当成一个整体,传值时直接丢进去,传指针时就在后边加一个*,在对应的调用前加上&即可。

4. 问题拓展

现在我们要完成一个这样的函数:它可以代替malloc函数来对我们刚刚声明的一个指针进行赋值,在第三部分中我们了解到,在操作指针类型时,传值引用一样可以改变调用者的值,所以我们写出了下边的程序:

#include

#include

typedef struct Value {

int a;

int b;

}Value;

void create(Value *v) {

v = (Value *) malloc(sizeof(Value));

v->a = 10086;

v->b = 10010;

}

int main() {

Value *v1;

create(v1);

printf("%d\t%d", v1->a, v1->b);

return 0;

}

结果输出如下:

-16219251 -970326017

并没有符合我们的期待!说好的操作指针就可以高枕无忧了呢?这个时候我们需要从系统底层了解传值和传指针时到底发生了什么,如下图所示

022d616ed65b5097198c6aa5a3113d5b.png

首先v1的值是(p1p2p3p4p5p6p7p8)(32位系统为例),当调用create函数时,就是图上的①过程,这个时候v1的值被拷贝到v中,这时候如果直接对v操作是没有问题的,可以改变v1的值,因为它们俩指向的是同一块内存空间(事实上此时还没有指向任何有意义的空间),但是在随后的赋值语句中,即②过程,v的值立刻被替换为(k1k2k3k4k5k6k7k8),之后对v进行的操作其实是对(k1k2k3k4k5k6k7k8)指向的空间的操作,这时候根本没v,也就是(p1p2p3p4p5p6p7p8)指向的空间什么事,因此不能达到我们想要的效果。如果想要完成修改,仍需要传指针引用,修改后的代码如下:

#include

#include

typedef struct Value {

int a;

int b;

}Value;

void create(Value **v) {

(*v) = (Value *) malloc(sizeof(Value));

(*v)->a = 10086;

(*v)->b = 10010;

}

int main() {

Value *v1;

create(&v1);

printf("%d\t%d", v1->a, v1->b);

return 0;

}

5. 最后总结

指针没有什么特殊的,基本数据类型怎么对待,指针就怎么对待

要理解函数调用过程中进程地址空间中的变化,它能直观地体现传值引用和传指针引用的区别,并解决在使用过程中出现的问题

不要死记硬背形式,任何问题尝试回归底层理解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值