三指针B. C: 这是一个风格吗?

我觉得是三重指针 C 当作是 "bad". 从时刻使用它们是有道理的。

从基础开始

一个指针

有两个目标:创建一个数组并允许函数更改其内容 /联系链接/:


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 /或数组数组,因为每个数组 "column" 或者 "row" 它不一定是相同的长度/. 就个人而言,当我需要传递数组时,我喜欢使用它 1D:


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


对我来说,它有助于描述福所做的。 但是,这不是必需的:


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

char *a;
a = malloc...
foo/a/;


它也会有效。

根据第一个答案
https://coderoad.ru/1106957/
, 如果一个
foo

更改阵列的大小,将需要双指针。

可以清楚地看到它是如何服用的

三指针

/而且,真的/. 在我的情况下,如果我传递了一系列指针 /或数组数组/, 我会用它。 显然,如果转到更改多维数组大小的函数,则需要它。 当然,数组数组不是太常见,但还有其他案例。

那么这个公约是什么? 这只是一个风格的问题/可读性与许多人难以在指针周围包裹的事实相结合?
已邀请:

董宝中

赞同来自:

使用标志 triple+ 损害可读性和可维护性。

让我们假设您在此处有一个小广告功能:


void fun/int***/;


嗯。 是具有三维齿轮阵列的参数,或者指向二维齿轮阵列的指针,或指向数组指针的指针 /例如,该函数分配数组并分配指针 int 在功能内/

让我们将其与:


void fun/IntMatrix*/;


当然,您可以使用三重指针 int 使用矩阵。

但这不是他们的

. 在这里实施的事实是三重指针没有

关系

给用户。

复杂的数据结构应该是

封装

. 这是面向对象编程的主要思想之一。 即使在 C 您可以在某种程度上应用此原则。 将数据结构包装到结构中 /或者,非常常见 C, 使用 "handles", 这是不完整类型的迹象 - 此成语将在后面解释回应/.

假设您以齿轮阵列的形式实现了矩阵
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 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 */


rest 世界不知道它包含了什么
struct Matrix_

, 并且不知道它的大小。 这意味着用户无法直接声明值,但仅使用指针
Matrix

和功能
create_matrix

. 但是,用户不知道尺寸的事实意味着用户不依赖于它 - 这意味着我们可以删除或添加元素
struct Matrix_

在您的要求。

知食

赞同来自:

在大多数情况下,使用3级别不可或缺的是在该计划其他部分采用的不良设计解决方案的症状。 因此,它被认为是不良的做法,有笑话 "three star programmers", 与餐馆的评级相比,更多的星星意味着最糟糕的质量。

由于如何正确动态地分配多维阵列,因此由于困惑而导致对3个级别的不可或缺性的需求经常出现。 即使在编程书籍中,也经常被错误地学习,部分原因是因为它对标准是正确的繁重 C99. 我的帖子 Q&A
https://coderoad.ru/42094465/
, 转到这个问题,也说明了几个级别的依赖性将使代码越来越难以阅读和维护。

虽然这篇文章如何解释,但有一些情况
type**

可能有意义。 此示例是可变长度字符串的变量表。 当这种需要时
type**

出现,你很快就可以测试使用的诱惑
type***

, 因为你需要归还你的
type**

通过功能参数。

当您设计一些复杂时,大多数情况都需要在某种情况下出现 ADT. 例如,假设我们编码表格 hash, 其中每个索引是链接列表“链接”,以及关联列表数组中的每个节点。 然后,正确的解决方案重新投影程序以使用结构而不是几个级别的不可缺少。 桌子 hash, 关联的列表和阵列必须是不同的类型,自主类型,没有任何互相感知。

因此,使用正确的设计,我们自动避免多个恒星。

但是,如在任何良好的编程实践的规则中,总有例外。 有可能有这样的情况:

有必要实现一系列行。

行数为变量,可以在执行期间更改。

长度线是一个变量。



你可以

实施前述 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 APIs. 那么这两个替代方案都会如下所示:


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 高山水平。 但他们使用的选择应该是很好的意识,实现他们可以创建一个丑陋的代码,并且还有其他替代方案。

涵秋

赞同来自:

那么这个公约是什么? 这只是一个风格的问题/可读性与许多人难以在指针周围包裹的事实相结合?

多次间接不是糟糕的风格,而不是黑魔法 , 如果您处理高维数据,您将处理高水平的不可或缺; 如果您真的处理指向指向指针的指针
T

, 然后不要害怕写
T ***p;

. 不要隐藏指针 typedefs,

如果一个

只有使用这种类型的人不应该担心他的 "pointer-ness". 例如,如果您提供类似类型的类型 "handle", 它传递到B. 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

, 或者那里。

董宝中

赞同来自:

不幸的是,你误解了指针和阵列的概念 C. 记住,那个

阵列不是指针

.

从基础开始,一个指针有两个目标:创建一个数组并允许函数来改变其内容 /联系链接/:

声明指针时,您需要在使用程序之前初始化它。 这可以通过传输变量的地址或通过动态内存分配来完成。

在后一种情况下,指针可以用作索引数组 /但这不是一个数组/.

双指针可以是数组 2D /或数组数组,因为每个数组 "column" 或者 "row" 它不一定是相同的长度/. 就个人而言,当我需要传递数组时,我喜欢使用它 1D:

再次,我错了。 阵列不是指针,反之亦然。 指向路标的指针 - 这不是一个数组 2D.

我会告诉你阅读
http://www.c-faq.com/aryptr/index.html
.

莫问

赞同来自:

经过两级不可或缺的,理解变得困难。 而且,如果你传达这三倍的原因 /或者更多/ 指向他们的方法是他们可以重新分配并重新安装一些指示的内存,它来自方法的概念 "functions", 它只是返回值并且不会影响状态。 它也会对某些时刻的理解和可维护性产生负面影响。

但更根本的根本性地,您在这里遇到了三重指针的主要风格反对意见之一:

很明显,看看需要三重指针。 /而且,真的/.

这里的问题是B. "and beyond": 一旦你得到三个层面,你会在哪里停下来? 当然,

能够

有一定数量的噪音水平。 但是,在某个地方仍然有一个熟悉的极限,更好的是,但灵活性是足够的。 两个好数字。 "三星级编程", 它有时候是如何调用的,并且充其量有争议; 对于需要稍后支持代码的人来说,这是出色的或头痛。

要回复问题请先登录注册