【问题标题】:Scanf skips second line of input, alwaysScanf 总是跳过第二行输入
【发布时间】:2016-01-02 17:21:12
【问题描述】:

我正在尝试读取文本数据行,并将它们存储在链接列表中。 输入数据如下所示:

David Copperfield
Charles Dickens
4250
24.95
32.95
10
6
END_DATA

我读取数据的方式是先读取第一行,看是不是END_DATA。如果不是,那么我将第一行传递给一个函数,该函数创建一个链接列表节点,其中包含书籍数据。出于某种原因,在我将第一行传递给函数后,scanf 不会读取第二行。

当我在节点读取数据后尝试打印节点时,我的输出如下所示。

David Copperfield 

0 
0.000000 
0.000000 
0 
0
Charles Dickens 

4250 
24.950001 
32.950001 
10 
6
Charles Dickens 

0 
0.000000 
0.000000 
0 
0

源码如下

#include "lab3p1.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


struct NodeRec {
    Book data;
    struct NodeRec *next;
};

typedef struct NodeRec Node;

float findRevenue(Node *head);
float totalWholesaleCost(Node *head);
float totalProfit(Node *head);
float totalBooksSold(Node *head);
float averageProfitPerSale(Node *head);
void printNodes(Node *head);

void insertNode(Node* head, Node* newNode);

Node* createNewNode(char titleS[40]) {
    Node* newNode;
    newNode = malloc(sizeof(Node));
    if (newNode == NULL){
        printf("ERROR ALLOCATING SPACE");
        exit(-1);
    }
    else {
        strcpy(newNode->data.title, titleS);
        scanf("%40[^\n]*c", (char *)&newNode->data.author);

        scanf ("%i %f %f %i %i", &newNode->data.number, &newNode->data.wholesaleprice, &newNode->data.retailprice, &newNode->data.wholesalequantity, &newNode->data.retailquantity);

        printf ("%s \n%s \n%i \n%f \n%f \n%i \n%i\n", newNode->data.title, newNode->data.author,newNode->data.number, newNode->data.wholesaleprice, newNode->data.retailprice, newNode->data.wholesalequantity, newNode->data.retailquantity);

    }

    return newNode;
}


void readBooks(Node* head) {

    char firstline[40];
    char enddataString[40] = "END_DATA";
    scanf("%40[^\n]*c", (char *)&firstline);


    while (strcmp(firstline, enddataString)) {

        Node* newNode = createNewNode(firstline);
        insertNode(head, newNode);

        scanf("%40[^\n]*c", (char *)&firstline);
    }
}



void insertNode(Node* head, Node* newNode) {

    if (head == NULL) {
        head = newNode;
    }
    else {
        Node *preNode, *currentNode;

        int n = newNode->data.number;
        currentNode = head;

        while (currentNode != NULL && (currentNode->data.number) > n ) {
            preNode = currentNode;
            currentNode = currentNode->next;

        }
        newNode->next = currentNode;
        preNode->next = newNode;
    }
}

【问题讨论】:

  • 1) 检查来自scanf() 的结果 2) 添加空格scanf(" %39[^\n]*c" ... 3) (40 --> 39)。 GTG Some 可以详细说明这一点。
  • 我非常爱你。为什么这些空间有效??

标签: c struct linked-list scanf


【解决方案1】:

问题在于您的 scanf 格式字符串。下面这行有几个问题:

scanf("%40[^\n]*c", (char *)&firstline);

首先,您希望阅读直到行尾的所有内容(40 个字符或更少)。 %40[^\n] 会这样做。但是不需要c .. 括号中的表达式代替了它.. 所以,scanf 可能只是在输入中寻找一个普通字符'c'.. 不是你想要的,它会导致你的问题。星号在那个位置上也没有我所知道的含义。

第二个问题是,由于您告诉 scanf 只使用换行符之前的字符,因此换行符保留在 stdin 缓冲区中等待使用。由于下一个 scanf 也拒绝使用换行符,因此它根本不会消耗任何内容。当您使用 scanf 时,您必须将格式字符串设计为分配或丢弃输入中的每个字符,否则您的解析将失败。

将其替换为:

scanf("%40[^\n] ", (char *)&firstline);

..你的程序可以运行。这也应该在您的作者 scanf 调用中使用。这里的格式字符串基本上是说“读取所有不是换行符的内容......直到你看到换行符或得到 40 个字符。然后跳过你看到的任何空格”。这将消耗标准输入直到下一行的第一个字符,这就是你想要的。

但基本上 scanf 是一个熊并且很难正确使用,这就是为什么它很少用于这种事情了。我从不使用它。例如,如果作者或标题包含超过 40 个字符,您是否考虑过程序中会发生什么? scanf 的手册页说

当达到此最大值或 当找到不匹配的字符时,以先发生者为准。

这意味着未读字符将保留在缓冲区中并破坏所有剩余行的解析。

当然可以设计一个可以处理它的 scanf 格式..您必须将其设置为消耗除换行符以外的所有内容,然后将其丢弃..然后留下最后一个空间来消耗换行符。我现在不能让那部分工作..但你应该:)

您遇到的另一个主要问题是您的空终止符。首先,每个字符串都必须以 null 结尾,并且 scanf c 格式说明符的手册页说

..next指针必须是指向char的指针,而且必须有足够的 所有字符的空间(不添加终止空字节)

因此,如果您告诉 scanf 读取 40 个字符,则指向存储的指针最好至少指向 41!例如:

char author[41];

空终止是你的问题!你没有做什么......这将导致崩溃。你现在真的很幸运。

快速摆脱空终止符问题的一种方法是使用 calloc() 来分配节点,而不是 malloc()。使用 calloc() 您的内存承诺从一开始就被所有空字节填充......

无论如何,我认为你现在已经足够了。如果我是你,我会探索使用 fgets() 从输入中单独读取每一行,然后使用各种解析函数来获取你的值。例如 atoi 解析整数。它需要更多的工作,但最终更可靠且易于理解。如果您坚持使用 scanf,您必须真正阅读该手册页并尝试想象输入中的每个字符将如何被消耗或不被消耗。此外,请确保您存储的每个字符串都正确地以空值结尾。在c中这些事情不是为你做的,你必须考虑它们。

【讨论】:

    【解决方案2】:

    主要问题是先前输入"%i %f %f %i %i" 中的剩余'\n' 留在stdin 中。当scanf("%40[^\n]... 被执行时,没有任何东西被保存并且'\n' 保留在stdin 中。

    故事的寓意:

    1. 始终检查 I/O 函数的结果,例如 fopen, fseek, fscanf, scanf, fgets, fgetc, ...

    2. 切勿使用scanf()

    使用fgets()getline() 获取一行数据。然后解析它,边走边检查意外结果。更多代码,但SO 的时间更少。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-30
      • 1970-01-01
      • 2021-12-22
      • 2021-11-24
      • 1970-01-01
      相关资源
      最近更新 更多