如何在C语言中遍历结构体数组?

4
给定以下结构体:
typedef struct {    
    int a;    
    int b;    
    int c; 
} POST, *PPOST;

任务是要创建一个函数int compare_post( PPOST pp, POST p);,用于检查在以空指针结尾的"数组"pp中是否存在与p完全相同的副本。

正确的答案如下:

int compare_post( PPOST pp, POST p) {
       while( pp ){
            if((pp->a == p.a )&&(pp->b == p.b )&&(pp->c == p.c ))
               return 1;            
            pp++;     
       }
      return 0; 
}

我的问题是为什么使用while(pp)而不是while(*pp)? 我们难道不需要解引用pp以获取实际值以检测它是否为空吗?

3
你说得对,“正确”的答案并不正确。编辑:无论如何都不合乎逻辑,正如Asadefa和Sunburst275所指出的那样。如果不是指针数组,那么数组不能以空指针结尾,而PPOST不是指针数组。 - Ry-
1
pp 的输入参数不是 PPOST *pp,而是 PPOST pp,它不是指针。您没有使用指针。至少在我看来是这样的。我不知道是否应该使用 PPOST *pp 而不是 PPOST pp - Vandrey
3
或许可以向提供这段代码的人提供反馈:这个答案不仅是错误的,而且教别人使用typedef指针可能不是一个好的做法。这样的typedef会掩盖真实的类型,使得它更难理解,并增加了错误的风险。 - kaylum
5
请见是否应该使用typedef指针?。简而言之,一般的答案是“不”,但对于函数指针可能有例外情况。 - Jonathan Leffler
忽略 PPOST 并将其视为未给出的 typedef。为了迭代直到达到 哨兵 NULLpp 必须是指向指针的 POST 而不是指向 POST 的指针。您的参数必须是类型 POST **。(抵制进一步破坏 typedef 指针的冲动)然后解引用 pp 来检查 NULL 或与 p 的成员相等是有意义的。 - David C. Rankin
显示剩余4条评论
3个回答

0

这个任务没有意义,因为在任务中提到的函数原型

int compare_post( PPOST pp, POST p);

没有意义。函数参数pp不能是指向以NULL指针结尾的数组的第一个元素的指针。如果要用NULL指针终止数组,则该数组必须具有指针数据类型,即它必须是指针数组。但是,如果数组具有指针数据类型,则指向该数组的第一个元素的指针必须是指向指针的指针。换句话说,必须有两个级别的pointer indirection。因此,函数的第一个参数必须更改为以下内容:

int compare_post( PPOST *pp, POST p );

等同于:

int compare_post( POST **pp, POST p );

为了清晰起见,因为有些人认为为指针创建typedef不合适, 我将使用第二种语法。

有了这个新的函数原型,正确的答案应该是以下内容:

int compare_post( POST **pp, POST p ) {
    while ( *pp ) {
        if ( ( (*pp)->a == p.a ) && ( (*pp)->b == p.b ) && ( (*pp)->c == p.c ) )
            return 1;
        pp++;
    }
    return 0;
}

很多代码在几十年间都使用了这种做法 - typedef struct tagName {...} Name, *PName; 现在争论已经太晚了。程序员应该熟悉这个习惯用法。它允许改变对数据结构的理解。是的,它会引起错误,就像我们在这篇文章中看到的那样。但错误可能是由于问题陈述中的虚假陈述造成的:作者声称函数是正确的,而且每个人都依赖它。 - user6101270
@Myshkin:针对您的评论,我已经修改了我的回答,将其表述为“有些人认为为指针创建typedef不合适”,而不是声称它“通常被认为不合适”。 - Andreas Wenzel
@Andreas Wenzel 谢谢!更确切地说,我想强调的是,尽管您的答案与 Kaponir 的答案相似 - 您的回答时间仅晚了几分钟 - 因此两个答案都是独立的。这增加了我们对它们正确性的信心。 - user6101270

-1

为了澄清p是一个struct,而pp是一个指针,本文没有使用typedefs

struct Post { int a, b, c; };

int compare_post(struct Post *pp, struct Post p) {
    while(pp) {
        if((pp->a == p.a) && (pp->b == p.b) && (pp->c == p.c))
            return 1;            
        pp++;     
    }
    return 0;
}

虽然使用while(pp)这样的指针比较进入循环在语法上是有意义的,只要pp不为空就会进入循环,但这几乎肯定是一个逻辑错误。空指针"保证与任何对象或函数的指针比较都不相等"。因此,如果ppstruct Post的有效对象,则它将重复比较,直到数组元素成对相等,返回1,或者超出数组范围并导致未定义行为。唯一安全调用此函数的方法是将空指针作为pp传递(返回0),或通过在pp数组中工程化任何等于p的值(返回1)。根据函数名称,我期望像这样:

int compare_post(struct Post *x, struct Post *y) {
    return (x->a == y->a) && (x->b == y->b) && (x->c == y->c);
}

这与C字符串的间接级别不同,为了表示可变长度,它们使用一个哨兵NUL。这个字符是全零位,用转义序列'\0'表示,以及显式转换(char)0。这在先前的上下文中不是空指针。


1
这个答案应该更清楚地说明代码是错误的,无论是在问题中显示的代码还是在答案中显示的代码。任务说明应该有一个以空指针结尾的数组,但是在问题中的代码和这个答案中的代码都没有。 - Eric Postpischil
@EricPostpischil,您为什么认为代码是不正确的?问题中明确说明pp是以空指针结尾的数组。 - Adham Zahran
3
pp是一个结构体指针。如果它指向的是一个数组,那么该数组就是由结构体组成的数组,结构体本身不能为null。如果pp是指向指针数组中的某个指针,我们并没有得到该数组或pp的地址,我们只得到了pp的值。所以我们无法遍历该数组以查看其中是否有空指针。我们唯一拥有的地址是pp的值。如果它不是空指针,则pp+1也不是空指针,pp+2pp+3pp+4等都不是空指针,直到可能超出数组范围并违反指针算术规则为止。 - Eric Postpischil
你是对的,它确实声明以空指针结束;我更新了这个解释为什么这是没有意义的。 - Neil

-1
这个问题的更好标题应该是如何将数组传递给函数。 所讨论的函数是不正确的 - 为了使这种方法起作用,我们需要改变签名 - 传递指向PPOST的指针。很可能这段代码的作者依赖于一个想法(而不是标准),即特殊值NULL(哨兵)指定序列的结尾,对于分配给N + 1个指针的数组来说,在N个非空元素(指向结构体的指针)之后跟随N + 1个元素(指针),它将等于零。这个零指针将标记范围的结尾。当然,这是编码人员的责任 - 在调用函数之前通过将最后一个元素分配为NULL来标记指针数组的末尾。在调用函数之前,请执行以下操作:
// allocate array of pointers
PPOST *ptr = (PPOST*) malloc((N + 1)*sizeof(PPOST*));
ptr + N = NULL; // mark the end - sentinel
POST p;
// ---
compare_post( ptr, p)
// ---
int compare_post( PPOST *pp, POST p)
{
   while( *pp ){
        if(((*pp)->a == p.a )&&((*pp)->b == p.b )&&((*pp)->c == p.c ))
           return 1;            
        pp++;     
   }
  return 0; 
}

你应该测试指针 - 解引用的指向指针 (*pp) - 而不是结构体 **pp 的值。也就是说,你将遍历数组直到达到第 N + 1 个元素。

更常见的方法(特别是在 C++ 标准库中广泛使用)是传递两个指针来指定范围的边界:序列的开始和结束位置:[beg, end)。其中,end 不包括在范围内。这样可以避免创建繁琐的指针数组:

// allocate array of struct POST
PPOST ptr = (PPOST) malloc( N * sizeof(POST));
// fill structures
PPOST onePastTheEnd = ptr + N;
POST p;
// ---
compare_post( ptr, onePastTheEnd, p)
// ---
int compare_post( PPOST pp, PPOST end, POST p)
{
   while( pp != end ){
        if((pp->a == p.a )&&(pp->b == p.b )&&(pp->c == p.c ))
           return 1;            
        pp++;     
   }
  return 0; 
}

一旦我们开始更改函数签名,我们也可以传递数组的大小 - 这是另一种方法。

评论不适合进行长时间的讨论;此对话已被移至聊天室 - Bhargav Rao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接