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

时间:2014-01-31 19:35:36

标签: c pointers coding-style

我觉得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);

也可以。

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

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

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

5 个答案:

答案 0 :(得分:14)

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

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

void fun(int***);

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

让我们将其与:

进行比较
void fun(IntMatrix*);

当然你可以使用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];
}

只需更改为

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以及所有处理结构指针的函数:

// 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);

在源文件中声明实际的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 */

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

答案 1 :(得分:2)

  

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

多个间接不是坏的风格,也不是黑魔法,如果你正在处理高维数据,那么你将处理高水平的间接;如果你真的在处理指向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 );

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

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

<小时/> 1。或指向int指针的指针,或指向指向struct grdlphmp指针的指针的指针,或其他任何指针。

答案 2 :(得分:2)

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

对3级间接访问的需求通常源于对如何动态正确分配多维数组的困惑。即使在编程书籍中,这也经常被错误地教导,部分原因是正确地进行操作在C99标准之前是繁重的。我的问答环节Correctly allocating multi-dimensional arrays解决了这个问题,还说明了多级间接访问将如何使代码越来越难以阅读和维护。

尽管如该帖子所述,在某些情况下,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个间接层对我们没有好处。但是,必须明智地选择使用它们,并意识到它们可能会创建难看的代码,并且还有其他选择。

答案 3 :(得分:0)

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

  

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

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

  

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

又错了。数组不是指针,反之亦然。指向指针的指针不是2D数组 我建议你阅读c-faq section 6. Arrays and Pointers

答案 4 :(得分:0)

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

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

  

人们可以清楚地看到如何需要三指针(以及真正的超级指针)。

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