1. 程式人生 > >真正搞懂傳值引用和傳指標引用

真正搞懂傳值引用和傳指標引用

通過一個基本原則和兩個例子講述傳值引用和傳指標引用的區別,並且從系統底層解析這兩種呼叫的區別

  • acmore
  • 2017.11.14

1. 問題概述

在C語言呼叫函式時,有兩種傳引數的方法,一種是傳值,另一種是傳引數。對於C語言的初學者(甚至是一些C語言的熟練使用者)而言,區別這兩種形式往往比較困難,再加上C語言有指標,以及指標的指標等等,複雜多變的形式會使得這個過程更加困難。

很多初學者會嘗試通過形式來記憶這兩種方式,比如函式宣告中有*的就是傳指標,沒有的就是傳值,但是如果傳入的引數本身就是一個指標,無論是傳值還是傳指標,函式宣告都會有*,這種時候原有的形式記憶不再有效,需要再次記憶另外一套形式。形式是無窮無盡的,只有明白指標型別的本質,以及在函式呼叫過程中系統裡到底發生了什麼,才能真正理解這兩種方式。

2. 簡單認識

現在我們有兩個函式,作用都是修改一個int型別的值,分別如下:

#include <stdio.h>

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

這次我們再按照之前的經驗去判斷,好像有點行不通,因為在對modify1modify2的呼叫上雖然像是一個傳值一個傳指標,但是兩個函式的宣告中都有*,並且v1v2的值都被改變了,經驗不再適用!

這種時候我們應該回歸底層,老老實實從系統層面理解在程式執行過程中到底發生了什麼,我們首先應該記住的一個最重要的原則是:
- 指標型別和基本型別一樣,本質沒有什麼區別

我們拿型別intValue *做比較,根據上述原則,便有以下幾條原則:

  1. int在記憶體中佔有一塊空間,那麼Value *同樣會在記憶體中佔有一塊空間

  2. 可以得到一個指標指向int,那麼也可以有一個指標指向Value *

  3. 使用&可以取得int的指標,那麼同樣可以使用&取得Value *的指標

  4. 傳值引用時,有變數int aValue* v,下邊兩種寫法都是傳值引用

    • 操作int時,函式的引數型別就是int,呼叫函式時傳入a
    • 同理在操作Value *時,函式的引數型別就是Value *,呼叫函式時傳入v
  5. 傳指標引用時,下邊兩種寫法都是傳指標引用
    • 操作int時,函式的引數型別是int *,呼叫函式時傳入&a
    • 操作Value *時,函式的引數型別是Value **,呼叫函式時傳入&v

根據以上原則,不要把指標當做一個特殊的型別,把指標型別那一堆當成一個整體,傳值時直接丟進去,傳指標時就在後邊加一個*,在對應的呼叫前加上&即可。

4. 問題拓展

現在我們要完成一個這樣的函式:它可以代替malloc函式來對我們剛剛宣告的一個指標進行賦值,在第三部分中我們瞭解到,在操作指標型別時,傳值引用一樣可以改變呼叫者的值,所以我們寫出了下邊的程式:

#include <stdio.h>
#include <malloc.h>

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

並沒有符合我們的期待!說好的操作指標就可以高枕無憂了呢?這個時候我們需要從系統底層瞭解傳值和傳指標時到底發生了什麼,如下圖所示

傳值引用時程式地址空間圖示

首先v1的值是(p1p2p3p4p5p6p7p8)(32位系統為例),當呼叫create函式時,就是圖上的①過程,這個時候v1的值被拷貝到v中,這時候如果直接對v操作是沒有問題的,可以改變v1的值,因為它們倆指向的是同一塊記憶體空間(事實上此時還沒有指向任何有意義的空間),但是在隨後的賦值語句中,即②過程,v的值立刻被替換為(k1k2k3k4k5k6k7k8),之後對v進行的操作其實是對(k1k2k3k4k5k6k7k8)指向的空間的操作,這時候根本沒v,也就是(p1p2p3p4p5p6p7p8)指向的空間什麼事,因此不能達到我們想要的效果。如果想要完成修改,仍需要傳指標引用,修改後的程式碼如下:

#include <stdio.h>
#include <malloc.h>

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. 指標沒有什麼特殊的,基本資料型別怎麼對待,指標就怎麼對待
  2. 要理解函式呼叫過程中程序地址空間中的變化,它能直觀地體現傳值引用和傳指標引用的區別,並解決在使用過程中出現的問題
  3. 不要死記硬背形式,任何問題嘗試迴歸底層理解

相關推薦

Java中的形參實參的區別以及呼叫引用呼叫

原文地址:http://blog.csdn.net/miniminiyu/article/details/52061401  名詞解析: 1.形參:用來接收呼叫該方法時傳遞的引數。只有在被呼叫的時候才分配記憶體空間,一旦呼叫結束,就釋放記憶體空間。因此僅僅在方法內有效

C語言中交換兩個整數的呼叫址呼叫

       在C語言中,一說到交換兩個整數的值,大家第一反應可能是這樣的程式碼。定義一個第三方變數來輔助交換。 #include<stdio.h> int main() { int n

scala 引數名引數 ():=>:=>

傳值引數程式碼示例:def test1(code: ()=>Unit){ println("start") code() //要想呼叫傳入的程式碼塊,必須寫成code(),否則不會呼叫。 println("end") } test1

真正引用指標引用

通過一個基本原則和兩個例子講述傳值引用和傳指標引用的區別,並且從系統底層解析這兩種呼叫的區別 acmore 2017.11.14 1. 問題概述 在C語言呼叫函式時,有兩種傳引數的方法,一種是傳值,另一種是傳引數。對於C語言的初學者(甚至是

C++引用指標

我們需要傳資料而不改變資料儲存,直接傳值,如int a; 我們需要傳資料,並且改變值大小,需要傳地址,如  int * pa; 我們需要傳資料,並改變數結構中指標的指向,需要傳二級指標,如連結串列中的 node * * l; ... 以前是passl-by-val

從彙編高階語言的角度理解方式,引用指標的本質機制與區別。白話通俗易懂。

函式的傳參與返回值的方式有傳值和傳遞引用,c語言中就是傳值,而c++擴充套件傳引用。 而傳值分為傳遞值(實參的值,此時形參是實參在記憶體中的一份拷貝,形參在使用時分配記憶體,結束時釋放,實參和形參在記憶體中的地址不同,因此對形參的改變不會改變實參) 傳值的另外一種是傳指標

PHP傳遞引用傳遞的區別。什麼時候什麼時候引用

(1)按值傳遞:函式範圍內對值的任何改變在函式外部都會被忽略 (2)按引用傳遞:函式範圍內對值的任何改變在函式外部也能反映出這些修改 (3)優缺點: A:按值傳遞時,php必須複製值。特別是對於大型的字串和物件來說,這將會是一個代價很大的操作。 B.按引用傳遞則

C++中引數引用引數指標怎樣區別?

C++中傳值引數和引用引數怎樣區別呢? 看以下例子:#include<iostream>using namespace std;void swap(int a,int b){int temp;temp=a;a=b;b=temp;}main(){int a=3,b=

引用調用引用的區別

生成 ima chang img 代碼 src oid inf clas 只需要記住一句話: 傳值引用一般就是生成一個臨時對象,而引用調用是調用參數本身。 參照下面C語言代碼理解: 在 test.h文件裏實現兩個方法  #include <stdio.

Core MvcRequestHttpContext

ati quest bsp 需要 tco 修改 onf ice 註入 1.傳值方法   使用Request的方法(1-3):   1)Query:獲取鏈接?後面的值     如:http://localhost:55842/Home/About?name=kxy publ

Ajax以及接受,@ResPonseBody @RequestBody

Ajax對於Java程式設計人員開說可是很重要的,可以說是必會的。 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title&

Python 函式中,引數是,還是引用

在 C/C++ 中,傳值和傳引用是函式引數傳遞的兩種方式,在Python中引數是如何傳遞的?回答這個問題前,不如先來看兩段程式碼。 程式碼段1: def foo(arg): arg = 2 print(arg) a = 1 foo(a) # 輸出:2 print(a) #

淺析scala名呼叫呼叫,: => 與() : =>

函式呼叫一般傳值呼叫,但是,在某些情況下,我們不希望函式的值首先被計算,而是等到呼叫的時候再來進行計算,為了適應這種情景,scala提供了傳名呼叫。 先來看兩個例子: package test /** * Created by layne on

Java中方法呼叫引數傳遞的方式是,儘管的是引用而不是物件的。(Does Java pass by reference or pass by value?)

原文地址:http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html 在Java中,所有的物件變數都是引用,Java通過引用來管理物件。然而在給方法傳參時,Java並沒有使用傳引用的方式,而是

HTTP方式請求方式/angular的請求方式

一、Http傳值方式有2種:get、post 二、標準Http協議支援六種請求方法,即:get 、head、put、delete、post、options(通常情況下我們只用到了get和post的請求方式) 三、Http使用get請求介面的時候,一般直接ge

指針指針引用的區別/指針引用的區別(本質)

返回 這樣的 尋址 討論 public 初始 max() 局部變量 構造函數 轉自:http://blog.sina.com.cn/s/blog_673ef8130100imsp.html 指針傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,

徹底反斜杠“”正斜杠"/"的區別

影響 使用 web應用 圖片 命令 mic ont http () 正斜杠,符號是"/";反斜杠,符號是"\"。 在知乎中看到一個答案如下: 知乎用戶:“在絕大多數地方,用的都是/(slash),包括Mac/Linux,也包括URL。你唯一需要記住的是,Microsoft這

真正http協議01—背景故事

客戶端 http1.0 開車 溝通 生涯 net erl 慢慢 維基   去年讀了《圖解HTTP》、《圖解TCP/IP》以及《圖解網絡硬件》但是讀了之後並沒有什麽深刻的印象,只是有了一層模糊的脈絡,剛好最近又接觸了一些有關http的相關內容。所以,就打算把它寫成一個系列,一

這一次,真正信用評分模型(上篇)

工程師 集中 重要 sklearn app 目的 概率 單變量 是我 python風控評分卡建模和風控常識 https://study.163.com/course/introduction.htm?courseId=1005214003&utm_campaign

元件之父子、子

父元件傳值給子元件 父元件 <template> <div id="app"> <h1>props使用方式</h1> <hello txt='元件txt' v-bind:ddd="btn