【问题标题】:Using strcmp() in structures passed as pointers在作为指针传递的结构中使用 strcmp()
【发布时间】:2019-03-06 08:31:28
【问题描述】:

我在结构中有一堆练习题,所有都涉及作为指向函数参数的指针传递的结构。现在,我手头有一个特定的问题,它要求我将一些姓名和电话号码存储在电话簿结构中。内容如下:

编写一个实现以下两个函数的 C 程序。函数readin() 读取多个 人的姓名及其相应的电话号码,将数据传递给呼叫者 通过参数p,并返回已输入的名称数量。使用字符# 指示用户输入的结束。函数search() 查找输入姓名的电话号码 目标,然后在屏幕上打印姓名和电话号码。如果输入的名称不能 找到,然后它将打印适当的错误消息。这两个函数的原型如下:

 int readin(PhoneBk *p);
 void search(PhoneBk *p, int size, char *target);

PhoneBk 的结构定义如下:

typedef struct {
    char name[20];
    char telno[20];
} PhoneBk;

节目模板如下:

#include <stdio.h>
#include <string.h>

#define MAX 100

typedef struct {
    char name[20];
    char telno[20];
} PhoneBk;

int readin(PhoneBk *p);
void search(PhoneBk *p, int size, char *target); 

int main() {
    PhoneBk s[MAX];
    char t[20];
    int size;

    size = readin(s);
    printf("Enter search name: \n");

    gets(t);
    search(s, size, t);

    return 0;
}

int readin(PhoneBk *p) {
    /* Write your program code here */
}

void search(PhoneBk *p, int size, char *target) {
    /* Write your program code here */ 
}

我必须在我的程序中比较字符串两次:一,检查是否输入了 # 作为名称(在这种情况下,我希望程序直接跳转到搜索功能而不询问电话号码);第二,如果目标名称与其中一个电话簿结构中的名称匹配。

现在我对声明为数组但作为指针传递的结构的概念完全陌生,所以我认为获取下一条记录的用户输入的方法是递增指针变量 (这里p)。对于 get-the-records-until-#-is-entered 函数,我写了这个(使用strcmp):

int readin(PhoneBk *p) {
    strcpy(p->name, "Test");    //To guarantee a character that is not #.
    while (strcmp(p->name, "#") != 0) {
        printf("Enter person name, # to stop.\n");
        scanf("%s", p->name);
        printf("Enter the telephone number.\n");
        scanf("%s", p->telno);
        p++;
    }
    return p;
}

我的意图是将p-&gt;name 转换为字符串,以便strcmp() 可以处理它。但是反复显示警告:

warning: cast from pointer to integer of different size
warning: passing argument 1 of strcmp() makes pointer from integer without a cast

这个错误已经在论坛上讨论过很多次(我搜索过),但我所看到的对于这种结构和strcmp() 的组合没有任何帮助。

我完全放弃了strcmp(),转而使用第二个函数,即在找到目标名称时显示人员记录的函数,因此我使用循环直接比较字符串。

void search(PhoneBk *p, int size, char *target) {
    int i, flag = 1, strCompare;
    char c1 = *(p->name), c2 = *target, p1 = p->name, p2 = target;
    while (c1 != '\0' && c2 != '\0') {  //Comparing strings.
        strCompare = (int)(c1 - c2);
        if (strCompare != 0)
            break;
        else {
            p1++;
            p2++;
        }
    }
    for (i = 0; i < size; i++, p++) {
        if (strCompare == 0) {
            flag = 0;
            printf("Name = %s, Tel. = %s\n", p->name, p->telno);
        }
        if (flag == 1)
            printf("Name not found.\n");
    }
}

但我又收到了一些警告。

warning: cast from pointer to integer of different size
warning: initialization makes pointer from integer without a cast

我真的认为如果结构都作为数组传递,我的生活会更轻松,在这种情况下,我可以简单地使用 For 循环遍历结构数组并完成它。这是我在整个高中期间一直在做的事情,但现在我在大学里,我不得不处理作为指针传递的结构。

在这种情况下我该如何处理strcmp()?这如何应用于任何将结构作为指针传递的程序?

【问题讨论】:

  • 关于简单语法错误的长问题。将 strcmp((char)p-&gt;name, "#") 更改为 strcmp(p-&gt;name, "#")。单个 char 不是字符串,因此是警告。
  • 永远不要使用getsa dangerous function 甚至已从 C 标准中删除。使用例如fgets 代替,但要注意它与 gets 的区别。
  • 其实这里的问题似乎是你期望单个char 是C 的字符串类。C 中没有 没有字符串类。一切都是原始的,手动数组,char[]。所以你的问题实际上是“如何在 C 中进行字符串处理”,这是一个太大的话题,无法在这里回答。答案是阅读 C 书籍中关于字符串的章节。
  • 至于程序的其余部分,如果编译器还没有对您发出警告,则启用更多警告并将它们视为错误。例如,您真的readin 函数返回什么?
  • 而您的search 函数似乎只进行了一些字符串比较(以非常复杂的方式)。您实际上并没有搜索 PhoneBk 元素的 array 来找到相应的元素。

标签: c pointers strcmp


【解决方案1】:

程序模板结构不好,使用过时且危险的函数gets()给出了一个非常糟糕的例子。

最好将readin() 传递给p 参数指向的数组中的结构数。从代码模板中,我们必须假设MAX 结构可以通过p 访问。

readin 函数应从标准输入读取最多 MAX 条目,并将其存储到目标数组的相应元素中,使用索引或递增 p

这是一个带有索引的解决方案:

int readin(PhoneBk *p) {
    int i, n = MAX;
    for (i = 0; i < n; i++) {
        printf("Enter person name, # to stop.\n");
        if (scanf("%19s", p[i].name) != 1 || p[i].name[0] == '#')
            return i;
        printf("Enter the telephone number.\n");
        if (scanf("%19s", p[i].telno) != 1)
            return i;
    }
    return i;
}

同样,搜索函数可以遍历数组从0size被排除:

void search(PhoneBk *p, int size, char *target) {
    int i, found = 0;
    for (i = 0; i < size; i++) {
        if (!strcmp(p[i].name, target) {
            printf("Name = %s, Tel. = %s\n", p[i].name, p[i].telno);
            found++;
        }
    }
    if (found == 0)
        printf("Name not found.\n");
}

关于警告,您在strcmp((char)p-&gt;name, "#") 上有一个错字,强制转换是不必要的,在这种情况下将指针转换为单个char,导致 2 个警告:一个用于将指针转换为不同大小的整数另一个用于将整数传递给strcmp() 而不是指向char 的指针。

【讨论】:

  • 我不知道你是如何在 if 语句中使用 scanf() 的,你能解释一下它的作用吗?如果scanf == 1,会发生什么? (虽然我也不熟悉 %19s,但我能理解它的意思:你不能输入超过 19 个字符。)
  • @Celcomtel scanf 返回读取元素的数量,在当前情况下有1个元素要读取(一个字符串),这就是他与1比较的原因。 scanf("%19s", ..) 允许读取最多 19 个字符,在最多写入 20 个字符的情况下添加最终的空字符,允许不超出 name i> 和 telno。执行 man scanf 或在互联网上查找更多信息。你也可以看看我的回答,我用不同的方式来做。
【解决方案2】:

readin 中,您不会测试最后输入的名称,而是下一个条目的名称(未初始化),并且您也不会检查是否到达电话簿的末尾,也不会检查 EOF案子。请注意,您还假设名称只是一个单词并且不能包含空格,但是由于 main 使用了(丑陋的)gets,因此可能允许组合名称。

为了不被多余的空格打扰,似乎需要删除它们。

例如:

#include <ctype.h>

void normalize(char * s)
{
  /* remove unexpected 'isspace' */
  int p1 = 0, p2 = 0;

  while (s[p2] != 0) {
    if (!isspace((unsigned char) s[p2]))
      s[p1++] = s[p2];
    else if ((p1 != 0) && (s[p1 - 1] != ' '))
      s[p1++] = ' ';
    p2 += 1;
  }

  s[((p1 != 0) && (s[p1 - 1] == ' ')) ? p1 - 1 : p1] = 0;
}

int readin(PhoneBk *p)
{
  int n;

  for (n = 0; n != MAX; ++n) {
    puts("Enter person name, max length 18, # to stop.");
    if (fgets(p->name, sizeof(p->name), stdin) == NULL) {
      /* EOF */
      return n;
    }
    normalize(p->name);
    if (p->name[0] == '#')
      break;

    printf("Enter the telephone number, max length 18\n");
    if (fgets(p->telno, sizeof(p->telno), stdin) == NULL) {
      /* EOF, forget partial entry */
      return n;
    }
    normalize(p->telno);

    p += 1;
  }

  return n;
}

你的搜索太复杂了,我宁愿不去理解它,对不起,定义可以很简单:

void search(PhoneBk *p, int size, char *target)
{
  normalize(target);

  while (size--) {
    if (!strcmp(p->name, target)) {
      printf("%s found, phone number is %s\n", target, p->telno);
      return;
    }
    p += 1;
  }

  printf("unknown name %s\n", target);
}

注意,我还将阅读名称标准化,以与书的阅读兼容。

如果我尝试不更改给定的 main 及其丑陋的 gets,编译和执行:

/tmp % gcc -g -pedantic -Wextra c.c
/tmp/ccdXFM1o.o: In function `main':
/tmp/c.c:17: warning: the `gets' function is dangerous and should not be used.
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
john  do
john do found, phone number is 12 34 56
/tmp % 
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
just        me
just me found, phone number is 1 2 3 4 5
/tmp % 
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
me  
unknown name me

【讨论】:

  • 你好@布鲁诺!您的方法存在一个小问题:fgets() 会将换行符读入目标数组,因此输入字段的最大长度将减少 1 个字节。如果用户在换行符之前输入 19 个字符,则不会读取换行符,下一次调用 fgets() 将读取挂起的换行符并立即返回。 scanf("%19s",...) 有同样的问题,但名称长度超过 19 个字符。
  • 还有另一个问题:isspace() 的参数不应该具有char 类型,因为在默认签名char 的架构上,负字符将导致未定义的行为。你应该写isspace((unsigned char)s[p2])
  • 你好@chqrlie(像往常一样)你是对的。关于 isspace 我想在我的脑海中它得到一个 char 但它得到一个 int,所以是的,符号很重要,这是一个 lib 的选择非常奇怪(与 EOF 没有比较)。我再次编辑我的答案,谢谢你(我希望你前一周过得愉快,几个小时前我对你的答案进行了紫外线处理^^)
  • 是的,它是一个奇怪的 API:isspace() 和朋友可以传递从getc() 返回的任何int 值。 EOF 可以,返回值为 0。真正的问题不是这个 API,而是许多编译器默认签名 char 以与旧代码兼容,但以牺牲一致性为代价。我总是指示编译器默认使char 无符号(-funsigned-char 用于 gcc 和 clang),但遗憾的是这不是默认行为。我相信通过提供精心挑选的 8 位字符的输入可以破坏许多程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-02
  • 1970-01-01
  • 2020-12-14
  • 1970-01-01
  • 2012-11-08
  • 1970-01-01
  • 2018-02-24
相关资源
最近更新 更多