【发布时间】:2010-10-20 00:14:00
【问题描述】:
什么时候在任何语言中使用指针都需要使用多个指针,比如说三重指针。何时使用三重指针而不是仅使用常规指针有意义?
例如:
char * * *ptr;
而不是
char *ptr;
【问题讨论】:
-
这是一个非常间接的问题。
-
你可以享受cdecl
什么时候在任何语言中使用指针都需要使用多个指针,比如说三重指针。何时使用三重指针而不是仅使用常规指针有意义?
例如:
char * * *ptr;
而不是
char *ptr;
【问题讨论】:
每个星都应该读作“指针指向的”所以
char *foo;
是“指针 foo 指向的字符”。不过
char *** foo;
是“由指针指向的指针指向的字符,该指针指向指向指针 foo 的指针”。因此 foo 是一个指针。在那个地址是第二个指针。在那个指向的地址是第三个指针。取消引用第三个指针会产生一个字符。如果这就是它的全部内容,那么很难为此做很多事情。
不过,仍然可以完成一些有用的工作。想象一下,我们正在编写 bash 或其他一些过程控制程序的替代品。我们希望以面向对象的方式管理进程的调用...
struct invocation {
char* command; // command to invoke the subprocess
char* path; // path to executable
char** env; // environment variables passed to the subprocess
...
}
但我们想做一些花哨的事情。我们希望有一种方法可以浏览每个子进程看到的所有不同的环境变量集。为此,我们将调用实例中的每组 env 成员收集到一个数组 env_list 中,并将其传递给处理它的函数:
void browse_env(size_t envc, char*** env_list);
【讨论】:
如果你在 C 中使用“对象”,你可能有这个:
struct customer {
char *name;
char *address;
int id;
} typedef Customer;
如果你想创建一个对象,你可以这样做:
Customer *customer = malloc(sizeof Customer);
// Initialise state.
我们在这里使用指向 struct 的指针,因为 struct 参数是按值传递的,我们需要使用 one 对象。 (另外:Objective-C,一种面向对象的 C 包装语言,在内部使用但可见的指向 structs 的指针。)
如果我需要存储多个对象,我使用数组:
Customer **customers = malloc(sizeof(Customer *) * 10);
int customerCount = 0;
由于 C 中的数组变量指向第一项,所以我再次使用指针……。现在我有了双指针。
但现在想象一下,我有一个过滤数组并返回一个新数组的函数。但是想象一下它不能通过返回机制,因为它必须返回一个错误代码——我的函数访问一个数据库。我需要通过引用参数来做到这一点。这是我函数的签名:
int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount);
该函数接受一个客户数组并返回对一个客户数组的引用(它们是指向struct 的指针)。它还获取客户数量并返回过滤后的客户数量(同样,通过引用参数)。
我可以这样称呼它:
Customer **result, int n = 0;
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n);
我可以继续想象更多的情况……这个没有typedef:
int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns);
显然,如果我离开它,我将是一个可怕和/或虐待狂的开发者。所以,使用:
typedef Customer *CustomerArray;
typedef CustomerArray *CustomerMatrix;
我可以这样做:
int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns);
如果您的应用在酒店中使用,每层使用矩阵,您可能需要一个矩阵数组:
int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels);
或者只是这样:
typedef CustomerMatrix *Hotel;
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels);
别让我开始介绍一系列酒店:
int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels);
...排列成矩阵(某种大型酒店公司?):
int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns);
我想说的是,您可以想象多个间接的疯狂应用程序。如果多指针是个好主意并且您决定使用它们,请确保使用 typedef。
(这篇帖子算作SevenStarDeveloper的申请吗?)
【讨论】:
指针只是一个保存内存地址的变量。
因此,当您想要保存指针变量的地址时,您可以使用指向指针的指针。
如果你想返回一个指针,并且你已经在使用返回变量做某事,你将传入一个指针的地址。然后该函数取消引用这个指针,以便它可以设置指针值。 IE。该函数的参数将是一个指向指针的指针。
多级间接也用于多维数组。如果要返回二维数组,可以使用三重指针。在将它们用于多维数组时,请注意在每个间接级别进行时正确转换。
下面是一个通过参数返回指针值的例子:
//Not a very useful example, but shows what I mean...
bool getOffsetBy3Pointer(const char *pInput, char **pOutput)
{
*pOutput = pInput + 3;
return true;
}
你这样调用这个函数:
const char *p = "hi you";
char *pYou;
bool bSuccess = getOffsetBy3Pointer(p, &pYou);
assert(!stricmp(pYou, "you"));
【讨论】:
ImageMagicks 的 Wand 有一个函数声明为
WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *)
【讨论】:
NULL 指针,构造所有参数,(void) 返回值。我认为在纯 C 中没有任何理由的所有事情。
char *f(), *g(), *h();.
N 维动态分配的数组,其中 N > 3,在 C 中需要三个或更多级别的间接寻址。
【讨论】:
双指针的标准用法,例如:myStruct** ptrptr,是作为指向指针的指针。例如,作为函数参数,这允许您更改调用者指向的实际结构,而不仅仅是能够更改该结构中的值。
【讨论】:
Char *** foo 可以解释为指向二维字符串数组的指针。
【讨论】:
您在必要时使用额外级别的间接(或指向),而不是因为它会很有趣。您很少看到三重指针;我想我从来没有见过四倍指针(如果我看到了,我的脑子会很困惑)。
状态表可以由适当数据类型的二维数组表示(例如,指向结构的指针)。当我编写一些几乎通用的代码来处理状态表时,我记得有一个函数采用三重指针 - 它表示指向结构的指针的二维数组。哎哟!
【讨论】:
int main( int argc, char** argv );
【讨论】:
封装资源创建的函数通常使用双指针。也就是说,您传入指向资源的指针的地址。然后该函数可以创建有问题的资源,并将指针设置为指向它。这只有在它具有相关指针的地址时才有可能,因此它必须是双指针。
【讨论】:
如果你必须修改函数内部的指针,你必须传递一个对它的引用。
【讨论】:
每当指针实际指向指针时,使用指向指针的指针是有意义的(此链是无限的,因此“三重指针”等是可能的)。
创建此类代码的原因是因为您希望编译器/解释器能够正确检查您正在使用的类型(防止出现神秘错误)。
您不必使用此类类型 - 只要您需要实际取消引用指针并访问指针指向的数据,您总是可以简单地使用简单的“void *”和类型转换。但这通常是不好的做法并且容易出错 - 当然在某些情况下使用 void * 实际上是好的并且使代码更加优雅。把它想象成你最后的手段。
=> 它主要是为了帮助编译器确保以应有的方式使用事物。
【讨论】:
指向指针的指针在 C++ 中很少使用。它们主要有两种用途。
第一个用途是传递一个数组。例如,char** 是指向 char 的指针,通常用于传递字符串数组。指向数组的指针不起作用是有充分理由的,但这是另一个主题(如果您想了解更多信息,请参阅comp.lang.c FAQ)。在极少数情况下,您可能会看到第三个* 用于数组数组,但通常将所有内容存储在一个连续数组中并手动索引它更有效(例如array[x+y*width] 而不是array[x][y])。然而,在 C++ 中,由于容器类的存在,这种情况要少得多。
第二种用途是通过引用传递。 int* 参数允许函数修改调用函数指向的整数,通常用于提供多个返回值。这种通过引用传递参数以允许多次返回的模式仍然存在于 C++ 中,但与传递引用的其他用途一样,通常被实际引用的引入所取代。通过引用传递的另一个原因 - 避免复制复杂的结构 - 也可以使用 C++ 引用。
C++ 有第三个因素可以减少多指针的使用:它有string。对字符串的引用在 C 中可能采用 char** 类型,因此该函数可以更改其传递的字符串变量的地址,但在 C++ 中,我们通常会看到 string&。
【讨论】:
当您使用嵌套的动态分配(或指针链接)数据结构时。这些东西都是用指针链接的。
【讨论】:
特别是在不积极使用基于类型的别名分析的 C 单线程方言中,编写可以容纳可重定位对象的内存管理器有时会很有用。应用程序接收指向句柄描述符表的指针,而不是给应用程序直接指向内存块的指针,每个句柄描述符包含一个指向实际内存块的指针以及一个指示其大小的字。如果需要为struct woozle 分配空间,可以说:
struct woozle **my_woozle = newHandle(sizeof struct woozle);
然后访问(在 C 语法中有点笨拙——语法在 帕斯卡): (*my_woozle)->someField=23;重要的是应用程序不 在对函数的调用中保持指向任何句柄目标的直接指针 分配内存,但如果每个块只存在一个指针 由句柄标识,内存管理器将能够移动事物 以防碎片化成为问题。
这种方法在 C 方言中效果不佳
追求基于类型的别名,因为NewHandle 返回的指针不
识别struct woozle* 类型的指针,而是识别指针
void* 类型的,甚至在那些指针类型有的平台上
标准不要求实现的相同表示
将指针转换解释为它应该期望别名
可能会发生。
【讨论】:
双重间接简化了许多树平衡算法,通常人们希望能够有效地将子树与其父树“取消链接”。例如,AVL 树实现可能使用:
void rotateLeft(struct tree **tree) {
struct tree *t = *tree,
*r = t->right,
*rl = r->left;
*tree = r;
r->left = t;
t->right = rl;
}
如果没有“双指针”,我们将不得不做一些更复杂的事情,比如显式跟踪节点的父节点以及它是左分支还是右分支。
【讨论】: