C++中传递参数是指针类型以及传入参数是指针的指针(**)详解
C++中传递参数是指针时,在函数内部,其实是会复制一份新的指针,只不过这两个指针指向的是同一块内存地址
首先我们要明白一点,在C++传递参数时,不论是传入指针还是传入值,传入函数后都会在函数内部创建一个副本 => 也就是说,传入前的指针或是值不会变. 但是指针有个点就是,函数内部的这个指针副本,和外面的这个指针,它们指向的是同一块内存地址。这样,我们可以通过在函数中传入指针参数,来修改指针指向的值,但是不能修改指针本身(因为函数内部是复制了一份指针副本)
那么,我们现在来看一个问题,比如现在有一个int类型的变量a, 我现在需要在一个函数modifyA()内部修改这个变量a的值? 有什么方法
main() { int a = 5; } ModifyA() { }
显然,如果我把a作为参数传入到modifyA()方法中去,显然会在方法modifyA内部复制一个整形变量作为a的副本,达不到修改变量a的值的目的。 在C++中,有两种方法可以达到修改a的值的目的
1. 传入a的引用,把a的引用作为ModifyA()函数的参数 => ModifyA(int& x);
2. 传入一个整形的指针int* p, 这个指针p指向a => 这样,在函数ModifyA内部会复制一份指针,但是这个复制的指针同样也会指向a, 它和原来的指针p指向同一个内存地址。这样,我们可以修改a的值
上面我们是想修改int类型的变量a的值,需要这样操作。那么同理,如果我们修改的不是int类型的变量,也就是说,变量类型不是int类型,而是指针类型,比如我们需要在函数内部修改指针类型char*类型的变量,应该怎么办? 同理:
1. 传入该类型的引用 char*& (int类型的引用是int&, char*类型的引用自然就是char*&)
2. 传入指向该类型的指针,指向char*类型的指针是 char** => 也就是本文要讨论的 指向指针的指针
我们来看一个非常经典的用malloc函数分配内存的例子
void AllocateMemory(char* p) //函数的参数是指针类型,那么在调用这个函数时,应该传入的是参数的地址 { p = malloc(sizeof(int)*10); return; }
void TestMethod()
{
char* pointer = null;
AllocateMemory(pointer); //这里,传入的是pointer的一个副本
}
在函数AllocateMemory内部,将会产生一个pointer的副本,这个副本和TestMethod中的pointer指向同一块内存地址A1(为了方便讲解,我们给这个地址取名A1)
然后在函数AllocateMemory内部,通过调用malloc函数,给pointer的副本指向了一个新分配的地址,我们把它叫做内存地址A2, 所以现在变成这样
所以,我们可以看到,这段代码,TestMethod()中的pointer指向没有任何变化,调用完AllocateMemory函数后,它还是指向原来的内存地址,它并不会指向malloc函数新分配的内存地址A2
但是,我们的本意是想用malloc函数重新分配一块内存地址,然后TestMethod方法中的指针pointer能够指向这块新分配的内存,要达到这个目标,需要怎么做呢? 我们这样改写代码
void AllocateMemory2(char* *p) //函数的参数是指向char*类型的指针的指针 { *p = malloc(sizeof(int)*10); return; } void TestMethod2() { char* pointer = null; AllocateMemory(&pointer); //这里,传入的是pointer的一个副本 }
我们来理解char* *p
p是一个指针,它指向的值是*p, 它指向的这个值本身又是一个char*类型的指针 => 这个指针又指向另一块内存地址(存放一个char类型的值)
所以明白了吧,在方法TestMethod2中, pointer是一个char*类型的指针,也就是上面图中中间方框的那个,而最左边方框中其实是中间方框中的地址,所以也就是&pointer,这也就是我们需要传入AllocateMemory中的参数, 所以调用时,传入的是
AllocateMemory(&pointer); => 调用时,同理,会复制一份&pointer的副本
在方法里面,我们把用malloc方法新分配的内存赋给了pointer指向的内容,也就是说
显然,目的达到
了解了上面这些,现在我们来看看在开发中遇到的一个问题,当时大概是这样的ClientDlg.hClientDlg.h
class ClientDlg : public CDialog //CDialog是MFC的afxwin里面的
{
ClientDlg* m_next_ptr;
}
PasswordDlg.h
Class PasswordDlg : ClientDlg
{
}
TestDlg.cpp
TestDlg : ClientDlg
{
void TestDlg::Init()
{
m_next_ptr = NULL;
}
void TestDlg::Method1
{
.......... //some code here
m_next_ptr = new PasswordDlg();
m_next_ptr->create();
m_next_ptr->showWindow(SW_NORMAL);
}
}
现在有一个新的Helper.cpp, 要把上面TestDlg的Method1中的那三行提成一个新的方法,放在Helper.cpp里面,刚开始我是这么写的
Helper.cpp
void Helper::ShowPasswordWindow(ClientDlg* dlgNext)
{
dlgNext = new PasswordDlg();
dlgNext->create();
dlgNext->showWindow(SW_NORMAL):
}
TestDlg.cpp
TestDlg : ClientDlg
{
#include "Helper.h"
void TestDlg::Init()
{
m_next_ptr = NULL;
}
void TestDlg::Method1
{
.......... //some code here
Helper::ShowPasswordWindow(m_next_ptr):
}
}
这个代码是有问题,就是因为函数 ShowPasswordWindow(ClientDlg* dlgNext) 中传入ClientDlg*的指针时,会复制一份指针dlgNext的副本 _dlgNext, 也就是说,我们的目标本来是把TestDlg中的 m_next_ptr, 让它是一个PasswordDlg窗体
m_next_ptr = new PasswordDlg();
但实际上呢,我们调用ShowPasswordWindow(m_next_ptr)后,里面会创建一个m_next_ptr的副本 copy_m_next_ptr, 所以其实是
copy_m_next_ptr = new PasswordDlg(); 而TestDlg.cpp中的 m_next_ptr依然还是NULL => 这显然和初衷不符, 那么,如果我们要达到初衷 =》更改TestDlg.cpp中的 m_next_ptr, 让它成为一个PasswordDlg窗体,我们应该怎么做呢
这个时候我们就要用到上面提到的指向指针的指针,m_next_ptr是一个ClientDlg*的指针,我们需要另一个指针 new_pointer来指向它,也就是说new_pointer的内存中存放的是指针m_next_ptr的地址 &m_next_ptr
这样,在Helper::ShowPasswordWindow()方法中传入的参数是这个指向m_next_ptr的指针new_pointer, 方法里面会复制一份new_pointer的副本copy_new_pointer,但是这个副本copy_new_pointer指向的也是m_next_ptr,这样,就可以保证修改到m_next_ptr
显然, 这里的 new_pointer是一个指针,它指向的是另一个指针m_next_ptr, 所以它是指向指针的指针,这也就是 Helper::ShowPasswordWindow()的参数, 修改后的代码如下
Helper.cpp
void Helper::ShowPasswordWindow(ClientDlg** dlgNext)
{
*dlgNext = new PasswordDlg();
*dlgNext->create();
*dlgNext->showWindow(SW_NORMAL):
}
TestDlg.cpp
TestDlg : ClientDlg
{
#include "Helper.h"
void TestDlg::Init()
{
m_next_ptr = NULL;
}
void TestDlg::Method1
{
.......... //some code here
Helper::ShowPasswordWindow(&m_next_ptr):
}
}