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

dar*_*rda 8 c pointers coding-style

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

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

char *a;
a = malloc...
Run Code Online (Sandbox Code Playgroud)

要么

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

char a;
foo(&a);
Run Code Online (Sandbox Code Playgroud)

双指针可以是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);
Run Code Online (Sandbox Code Playgroud)

对我来说,这有助于描述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);
Run Code Online (Sandbox Code Playgroud)

也会工作.

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

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

那么有哪些约定呢?这真的只是一个风格/可读性的问题,而且许多人很难将自己的脑袋缠绕在指针上吗?

mil*_*bug 16

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

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

void fun(int***);
Run Code Online (Sandbox Code Playgroud)

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

让我们来比较一下:

void fun(IntMatrix*);
Run Code Online (Sandbox Code Playgroud)

当然你可以使用int指针来对矩阵进行操作.但那并不是他们的本性.它们在这里作为三重指针实现的事实与用户无关.

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

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

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

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

typedef struct Matrix_
{
    double** data;
} Matrix;

double get_element(Matrix* m, int i, int j)
{
    return m->data[i][j];
}
Run Code Online (Sandbox Code Playgroud)

只是改变为

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];
}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
Run Code Online (Sandbox Code Playgroud)

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

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 */
Run Code Online (Sandbox Code Playgroud)

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


Lun*_*din 11

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

对 3 级间接的需求通常源于对如何正确动态分配多维数组的困惑。即使在编程书籍中,这也经常被错误地教导,部分原因是在 C99 标准之前正确地执行它是一种负担。我的问答帖子正确分配多维数组解决了这个问题,并说明了多级间接如何使代码越来越难以阅读和维护。

尽管正如那篇文章所解释的那样,在某些情况下 atype**可能有意义。具有可变长度的字符串的可变表就是这样一个例子。当type**出现这种需求时,您可能很快就会尝试使用type***,因为您需要type**通过函数参数返回您的。

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

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


但正如所有良好编程实践的规则一样,总有例外。完全有可能出现以下情况:

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

可以将上述内容实现为 ADT,但也可能有正当理由让事情变得简单并仅使用char* [n]. 然后,您有两个选项可以动态分配:

char* (*arr_ptr)[n] = malloc( sizeof(char*[n]) );
Run Code Online (Sandbox Code Playgroud)

或者

char** ptr_ptr = malloc( sizeof(char*[n]) );
Run Code Online (Sandbox Code Playgroud)

前者在形式上更正确,但也很麻烦。因为它必须用作(*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;
}
Run Code Online (Sandbox Code Playgroud)

或者

err_t alloc_ptr_ptr (size_t n, char*** arr)
{
  *arr = malloc( sizeof(char*[n]) );

  return *arr == NULL ? ERR_ALLOC : OK;
}
Run Code Online (Sandbox Code Playgroud)

很难争辩说前者更具可读性,而且它还伴随着调用者所需的繁琐访问。在这种非常特殊的情况下,三星级替代方案实际上更优雅。

因此,教条地驳回 3 个间接级别对我们没有好处。但是选择使用它们必须是知情的,并意识到它们可能会创建丑陋的代码并且还有其他替代方案。

  • @DavidBowling 一般来说,是的。但是为了决定何时使用某些东西,尽管它被广泛认为是不好的做法,你需要相当多的知识。所以这绝对不是初学者的决定;他们应该只遵循最佳实践,时期。 (2认同)

hac*_*cks -1

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

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

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

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

又错了。数组不是指针,反之亦然。指向指针的指针不是二维数组。
我建议您阅读c-faq 第 6 节:数组和指针