C中的三重指针:这是风格问题吗?

我觉得C中的三分指针被视为“坏”。 对我来说,有时使用它们是有道理的。

从基础开始, 单指针有两个目的:创建数组,并允许函数更改其内容(通过引用传递):

char *a; a = malloc... 

要么

 void foo (char *c); //means I'm going to modify the parameter in foo. { *c = 'f'; } char a; foo(&a); 

双指针可以是2D数组(或数组数组,因为每个“列”或“行”不需要是相同的长度)。 我个人喜欢在需要传递一维数组时使用它:

 void foo (char **c); //means I'm going to modify the elements of an array in foo. { (*c)[0] = 'f'; } char *a; a = malloc... foo(&a); 

对我来说,这有助于描述foo正在做什么。 但是,没有必要:

 void foo (char *c); //am I modifying a char or just passing a char array? { c[0] = 'f'; } char *a; a = malloc... foo(a); 

也会工作。

根据这个问题的第一个答案,如果foo要修改数组的大小,则需要双指针。

人们可以清楚地看到如何需要三指针 (以及更多,真的)。 在我的情况下,如果我传递一个指针数组(或数组数组),我会使用它。 显然,如果你传入一个改变多维数组大小的函数,那将是必需的。 当然,数组arrays数组并不常见,但其他情况则是如此。

那么有哪些约定呢? 这真的只是一个风格/可读性的问题,而且许多人都很难在指针周围徘徊?

使用triple +指针会损害可读性和可维护性。

我们假设你在这里有一个小函数声明:

 void fun(int***); 

嗯。 参数是三维锯齿状数组,或指向二维锯齿状数组的指针,或指向数组指针的指针(如,函数分配数组并在函数内指定指向int的指针)

让我们来比较一下:

 void fun(IntMatrix*); 

当然你可以使用int的三指针来操作矩阵。 但那不是他们的意思 。 它们在这里作为三重指针实现的事实与用户无关

应该封装复杂的数据结构。 这是面向对象编程的一个明显的想法。 即使在C语言中,您也可以在某种程度上应用此原则。 将数据结构包装在结构中(或者,在C中非常常见,使用“句柄”,即指向不完整类型的指针 – 这个成语将在后面的答案中解释)。

让我们假设您将矩阵实现为double锯齿状的数组。 与连续的2D数组相比,它们在迭代时更糟(因为它们不属于单个连续内存块)但允许使用数组表示法进行访问,并且每行可以具有不同的大小。

所以现在问题是你现在无法改变表示forms,因为指针的使用是硬连接在用户代码上的,现在你已经陷入了劣质的实现。

如果将它封装在结构中,这甚至不会成为问题。

 typedef struct Matrix_ { double** data; } Matrix; double get_element(Matrix* m, int i, int j) { return m->data[i][j]; } 

只是改变为

 typedef struct Matrix_ { int width; double data[]; //C99 flexible array member } Matrix; double get_element(Matrix* m, int i, int j) { return m->data[i*m->width+j]; } 

句柄技术的工作原理如下:在头文件中,您声明了一个不完整的结构体以及所有对结构指针起作用的函数:

 // struct declaration with no body. struct Matrix_; // optional: allow people to declare the matrix with Matrix* instead of struct Matrix* typedef struct Matrix_ Matrix; Matrix* create_matrix(int w, int h); void destroy_matrix(Matrix* m); double get_element(Matrix* m, int i, int j); double set_element(Matrix* m, double value, int i, int j); 

在源文件中,您声明实际的结构并定义所有函数:

 typedef struct Matrix_ { int width; double data[]; //C99 flexible array member } Matrix; double get_element(Matrix* m, int i, int j) { return m->data[i*m->width+j]; } /* definition of the rest of the functions */ 

世界其他地方不知道struct Matrix_包含什么,它不知道它的大小。 这意味着用户不能直接声明值,而只能使用指向Matrixcreate_matrix函数的指针。 但是,用户不知道大小的事实意味着用户不依赖它 – 这意味着我们可以随意删除或添加成员struct Matrix_

那么有哪些约定呢? 这真的只是一个风格/可读性的问题,而且很多人都很难在指针周围徘徊?

多个间接不是坏的风格,也不是黑魔法,如果你正在处理高维数据,那么你将处理高水平的间接; 如果你真的处理指向指向T的指针的指针,那么不要害怕写T ***p; 。 不要隐藏typedef后面的指针, 除非使用该类型的人不必担心它的“指针”。 例如,如果您将类型提供为在API中传递的“句柄”,例如:

 typedef ... *Handle; Handle h = NewHandle(); DoSomethingWith( h, some_data ); DoSomethingElseWith( h, more_data ); ReleaseHandle( h ); 

那么肯定, typedef 。 但是,如果h有意被解除引用,例如

 printf( "Handle value is %d\n", *h ); 

然后不要输入它 。 如果您的用户必须知道 h是指向int 1的指针才能正确使用它,那么该信息不应隐藏在typedef后面。

我会说,根据我的经验,我不必处理更高层次的间接; 三重间接是最高的,我不必多次使用它。 如果你经常发现自己处理>三维数据,那么你会看到高级别的间接,但如果你理解指针表达式和间接工作如何工作它不应该是一个问题。


1.或指向int指针,或指向指向struct grdlphmp指针的指针的指针,或其他。

大多数情况下,使用3级间接是由程序中其他地方做出的糟糕设计决策的症状。 因此它被认为是不好的做法,并且有关于“三星级程序员”的笑话,与餐馆的评级不同,更多的星星意味着更差的质量。

对3级间接的需求通常源于对如何动态地正确分配多维数组的困惑。 即使在编程书籍中,这通常也会被错误地教授,部分原因是因为在C99标准之前正确地做这件事很麻烦。 我的Q&Apost正确地分配多维数组解决了这个问题,并说明了多个间接层将如何使代码越来越难以阅读和维护。

虽然正如该帖所解释的那样,但在某些情况下type**可能有意义。 具有可变长度的字符串的变量表就是这样的示例。 当出现对type**需求时,您可能很快就会想要使用type*** ,因为您需要通过函数参数返回type**

大多数情况下,这种需求出现在您正在设计某种复杂ADT的情况下。 例如,假设我们正在编写一个哈希表,其中每个索引都是一个“链式”链表,链表中的每个节点都是一个数组。 然后,正确的解决方案是重新设计程序以使用结构而不是多个间接级别。 哈希表,链表和数组应该是不同类型,自治类型,彼此之间没有任何意识。

因此,通过使用适当的设计,我们将自动避免多颗星。


但是,正如良好的编程实践的每一条规则一样,总有例外。 完全可能有这样的情况:

  • 必须实现一个字符串数组。
  • 字符串的数量是可变的,可能会在运行时更改。
  • 字符串的长度是可变的。

可以将上面的内容实现为ADT,但它也可能是保持简单并只使用char* [n]正当理由。 然后,您有两个动态分配的选项:

 char* (*arr_ptr)[n] = malloc( sizeof(char*[n]) ); 

要么

 char** ptr_ptr = malloc( sizeof(char*[n]) ); 

前者更正式,但也很麻烦。 因为它必须用作(*arr_ptr)[i] = "string"; ,而替代方案可以用作ptr_ptr[i] = "string";

现在假设我们必须将malloc调用放在一个函数中并且返回类型是为错误代码保留的,就像使用C API定制一样。 这两种选择将如下所示:

 err_t alloc_arr_ptr (size_t n, char* (**arr)[n]) { *arr = malloc( sizeof(char*[n]) ); return *arr == NULL ? ERR_ALLOC : OK; } 

要么

 err_t alloc_ptr_ptr (size_t n, char*** arr) { *arr = malloc( sizeof(char*[n]) ); return *arr == NULL ? ERR_ALLOC : OK; } 

很难说并且说前者更具可读性,并且它还伴随着调用者所需的繁琐访问。 在这个非常具体的案例中,三星级的替代品实际上更优雅。

因此,以教条的方式解雇3个级别的间接对我们没有好处。 但是使用它们的选择必须是充分了解的,并且意识到它们可能会创建丑陋的代码并且还有其他选择。

不幸的是,你误解了C中指针和数组的概念。请记住, 数组不是指针

从基础开始,单指针有两个目的:创建数组,并允许函数更改其内容(通过引用传递):

声明指针时,需要在程序中使用它之前对其进行初始化。 它可以通过将变量的地址传递给它或通过动态内存分配来完成。
在后者中,指针可以用作索引数组(但它不是数组)。

双指针可以是2D数组(或数组数组,因为每个“列”或“行”不需要是相同的长度)。 我个人喜欢在需要传递一维数组时使用它:

再次错了。 数组不是指针,反之亦然。 指向指针的指针不是2D数组。
我建议你阅读c-faq第6节。数组和指针 。

在两个层次的间接之后,理解变得困难。 此外,如果您将这些三(或更多)指针传递给您的方法的原因是它们可以重新分配并重新设置一些指向内存,那么它就会脱离方法的概念,因为它只是“函数”返回值并且不影响状态。 这也会在某种程度上对理解和可维护性产生负面影响。

但更重要的是,你在这里遇到了对三重指针的一个主要风格反对意见:

人们可以清楚地看到如何需要三指针(以及更多,真的)。

这是问题的“超越”:一旦达到三个级别,你会在哪里停下来? 当然, 可以有一个任意数量的间接层。 但是,在可理解性仍然良好但灵活性足够的地方,有一个习惯性限制更好。 两个人的数字不错。 “三星级节目”,有时被称为“充满争议”; 对于那些需要稍后维护代码的人来说,它要么很棒,要么令人头痛。