1. 程式人生 > >指標作為形參進行傳遞注意事項

指標作為形參進行傳遞注意事項

一個例子

#include<iostream>
using namespace std;
int m_value = 1;
void func(int *p)
{
    p = &m_value;
}

int main(int argc, char *argv[])
{
    int n = 2;
    int *pn = &n;
    cout << *pn << endl;
    func(pn);
    cout << *pn << endl;
    return 0;
}

執行結果:
2
2

修改func函式如下

void func(int *p)
{
    /*p = &m_value;*/
    *p = 3;
}

執行結果:
2
3

修改func函式如下:
void func(int *p)
{
/p = &m_value;/
//*p = 3;
int a = 3;
*p =a;
}
依然是2,3

修改func函式如下:

void func(int *p)
{
    /*p = &m_value;*/
    //*p = 3;
    int a = 3;
    p =&a;
}

結果是2,2

解釋:
編譯器總是要為函式的每個引數製作臨時副本,指標引數p的副本是 _p,編譯器使 _p = p
如果函式體內的程式修改了_p所指向的內容,就導致引數p的內容作相應的修改。把_p所指的記憶體地址改變了,那麼 _p指向的記憶體塊,和p指向的記憶體塊就不一樣了。所以對p毫無影響。只要程式裡不改變 _p的指向, _p和p就是一樣的,操作 _p內容,就是操作p的內容。

下面常用的交換程式:

#include <stdio.h>
int *swap(int *px, int *py){ 
   int temp; 
   temp = *px; 
   *px
= *py; *py = temp; return px; } int main(void){ int i = 10, j = 20; int *p = swap(&i, &j); printf("now i=%d j=%d *p=%d\n", i, j, *p); return 0; }

結果:

now i=20 j=10 *p=20

進一步思考

函式裡面分配記憶體

void myMalloc(char *s) //我想在函式中分配記憶體,再返回  
{
    s = (char *)malloc(100);
}
void main()
{
    char *p = NULL;
    myMalloc(p); //這裡的p實際還是NULL,p的值沒有改變,為什麼?  
    if (p)
    {
        cout << "p不為空" << endl;
        free(p);
    }
    else
        cout << "p為空" << endl;
}

結果

p為空

看另一個例子:

void myMalloc(char **s) //我想在函式中分配記憶體,再返回  
{
    *s = (char *)malloc(100);
}
void main()
{
    char *p = NULL;
    myMalloc(&p); //傳遞的是p的地址,這裡的p可以得到正確的值了  
    if (p)
    {
        cout << "p不為空" << endl;
        memcpy(p, "Hello",6);//如果拷貝5個的話,最後一個'\0'沒有拷貝過去,輸出p會出現Hello,燙燙燙...
        strcpy(p, "hello");
        cout << p << endl;
        free(p);
    }
    else
        cout << "p為空" << endl;
}

結果:

p不為空
hello

解釋:
1.被分配記憶體的是形參s,p沒有分配記憶體
2.被分配記憶體的是形參s指向的指標p,所以分配了記憶體

第三個小例子:

void GetMemory(char *p, int num)  
{  
     p = (char *)malloc(sizeof(char) * num);  
}  
void Test(void)  
{  
     char *str = NULL;  
     GetMemory(str, 100);      // str 仍然為 NULL       
     strcpy(str, "hello");      // 執行錯誤  
}  

道理一樣。

所以要想借助函式進行分配記憶體,需要傳遞的是“指標的指標”,就是第一個例子。

void GetMemory2(char **p, int num)  
{  
     *p = (char *)malloc(sizeof(char) * num);  
}

另外還可以:用函式返回值來傳遞動態記憶體

char *GetMemory3(int num)  
{  
     char *p = (char *)malloc(sizeof(char) * num);  
     return p;  
}  
void Test3(void)  
{  
     char *str = NULL;  
     str = GetMemory3(100);       
     strcpy(str, "hello");  
     cout<< str << endl;  
     free(str);       
} 

輸出結果:hello
注意這裡函式返回值傳遞的動態記憶體,必須是在“堆上”申請的記憶體!!!

不要用return語句返回指向“棧記憶體”的指標,因為該記憶體在函式結束時自動消亡

char *GetString(void)  
{  
     char p[] = "hello world";  
     return p;      // 編譯器將提出警告  
}  
void Test4(void)  
{  
char *str = NULL;  
str = GetString();      // str 的內容是垃圾  
cout<< str << endl;  
}  

但是下面的又會讓人疑惑:

char *GetString2(void)  
{  
     char *p = "hello world";  
     return p;  
}  
void Test5(void)  
{  
     char *str = NULL;  
     str = GetString2();  
     cout<< str << endl;  
}  

這個能執行,輸出:hello world
函式Test5執行雖然不會出錯,但是函式GetString2的設計概念卻是錯誤的。因為GetString2內的“hello world”是常量字串,位於靜態儲存區,它在程式生命期內恆定不變。無論什麼時候呼叫GetString2,它返回的始終是同一個“只讀”的記憶體塊。

    char *pp = "wocao!";
    pp[0] = '1';//這樣賦值是錯誤的。

所以上述測試時:
多加一句是會報錯的

void main(void)
{
    char *str = NULL;
    str = GetString2();
    cout << str << endl;
    str[0] = '1';//報錯
} 

<高質量C C++程式設計指南>這本書上說: 指標p 指向常量字串(位於常量儲存區),常量字串的內容是不可以被修改的,企圖修改常量字串的內容而導致執行錯誤。所以這個問題出現的原因是char*p=”abcdefghi”,賦值的是字串常量,儲存在常量儲存區,而常量儲存區的內容是無法修改的。
如果使用陣列來代替的話,資料就儲存在堆疊空間,堆疊空間的內容是可以修改的,就不會出現執行時錯誤。

另外再加一個小總結:

程式1void myMalloc(char *s) //我想在函式中分配記憶體,再返回
{
  s=(char *) malloc(100); // s是值參, 函式返回後就回復傳遞前的數值,無法帶回分配的結果
}
這個和呼叫 void func (int i) {i=1;}; 一樣,退出函式體,i指復原的
程式2void myMalloc(char **s)
{
  *s=(char *) malloc(100); // 這個是可以的
}
等價於
void int func(int * pI) {*pI=1;} pI指標不變,指標指向的資料內容是變化的
值參本身不變,但是值參指向的記憶體的內容發生了變化。
程式3void fun(int *p)
{
  int b=100;
  p=&b;       // 等同於第一個問題, b的地址並沒有被返回
}
程式4void fun(int *p)
{
  *p=100; // okay
}

結論:
1.函式的返回值是指標型別的,檢查是靜態記憶體指標還是堆記憶體指標還是棧記憶體指標,棧記憶體指標是絕對要不得滴!
2.函式需要使用指標引數進行傳入傳出的,在函式中只能對指標的指向的值(*p)進行修改,而不能修改指標指向,也就是指標地址!(函式中不得修改指標引數的地址,否則請使用指標的指標!)
一般在函式中定義一個物件有兩種方法:   
  1、在棧上建立區域性變數。注意,在棧上時!棧用於函式是為了返回時找得到呼叫點(在呼叫時壓入棧的)
,那麼,返回時要POP才能得到。函式體中建立的任何東西都消失了(返回值除外),你返回的指標指向的內
容現在不知被用作什麼用途了,如果你還要修改的話,那麼後果不能確定。   
  2、在堆中分配。返回時不會摧毀,因為堆是全域性存在的。但函式的呼叫者要記得delete回來的指標。

指標的引用

void func(int *&p)
{
    p = &m_value;

    // 也可以根據你的需求分配記憶體
    /*p = new int;
    *p = 5;*/
}

int main(int argc, char *argv[])
{
    int n = 2;
    int *pn = &n;
    cout << *pn << endl;
    func(pn);
    cout << *pn << endl;
    return 0;
}

結果是:2,1
此時傳遞過去就是指標p,因為引用!

看一下func(int *&p)方法
p: 是指標的引用,main()方法裡的 *pn
*p:是main()方法裡的pn指向的內容。