简体   繁体   English

在ANSI C中键入检查任意长度数组

[英]Type checking arbitrary length array in ANSI C

Hi I am confined to stdio.h, stdlib.h and string.h and I need to ask a user for input - the input can be any number of characters between 1 and 6, however the first two characters MUST be an uppercase alphabetical letter, and the remaining four characters MUST be a number between 0 and 9. 嗨我被限制在stdio.h,stdlib.h和string.h中,我需要询问用户输入 - 输入可以是1到6之间的任意数量的字符,但前两个字符必须是一个大写的字母,其余四个字符必须是0到9之间的数字。

Examples of valid input: 有效输入的示例:

  • AB1 AB1
  • AB1234 AB1234
  • AB AB
  • A 一个

Examples of Invalid Input: 输入无效的示例:

  • AB12345 (too many characters) AB12345(字符太多)
  • 123 (first two characters are not uppercase letters) 123(前两个字符不是大写字母)
  • ABA (a character after the second one is not a numeric value) ABA(第二个之后的字符不是数值)

Here is my attempt so far (just bear in mind I have almost no experience with C, the likelihood that this solution is "idiomatic" is next to none, and the reason I am asking this is so that I can learn): 这是我到目前为止的尝试(请记住,我几乎没有C的经验,这个解决方案“惯用”的可能性几乎没有,我问这个的原因是我可以学习):

Flightcode is a char array defined as flightcode[7] it lives inside another struct called flight . Flightcode是一个定义为flightcode[7]的char数组,它位于另一个名为flight结构中。 I am fgets ing it into a temp_array[7] first and then strcpy ing it into the flight->flightcode such that the null terminator is appended and I don't know a better way of doing that. fgets将其投入一个temp_array[7]strcpy它荷兰国际集团到机票- > flightcode使得空终止追加,我不知道这样做的更好的方法。

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

#define MAX_FLIGHTCODE_LEN 6
#define MAX_CITYCODE_LEN 3
#define MAX_NUM_FLIGHTS 50
#define DB_NAME "database"

typedef struct {
  int month;
  int day;
  int hour;
  int minute;
} date_time_t;

typedef struct {
  char flightcode[MAX_FLIGHTCODE_LEN + 1];
  date_time_t departure_dt;
  char arrival_city[MAX_CITYCODE_LEN + 1];
  date_time_t arrival_dt;
} flight_t;

date_time_t departure_dt;
date_time_t arrival_dt;

char * scanline(char *dest, int dest_len);



int main(){

char temp_string[100];
flight_t flight[MAX_NUM_FLIGHTS + 1];
int correct_code = 0;

printf("Enter flight code>\n");

scanline(temp_string, sizeof(flight->flightcode));
strcpy(flight->flightcode, temp_string);

while(correct_code == 0)
{
  for(int i = 0; flight->flightcode[i] != '\0' && correct_code == 0; i++)
  {
    while((i < 2 && (flight->flightcode[i] <= 64 || flight->flightcode[i] >= 91)) || (i > 1 && (flight->flightcode[i] < 48 || flight->flightcode[i] >= 58)))
    {
      printf("Invalid input.\n");

      scanline(temp_string, sizeof(flight->flightcode));
      strcpy(flight->flightcode, temp_string);
    }
    if((i < 2 && (flight->flightcode[i] > 64 || flight->flightcode[i] < 91)) || (i > 1 && (flight->flightcode[i] >= 48 || flight->flightcode[i] < 58)))
    {
      correct_code = 1;
    }
  }
}

}

char * scanline(char *dest, int dest_len){
  int i, ch;
  i = 0;
  for (ch = getchar();
       ch != '\n' && ch != EOF && i < dest_len -1; ch = getchar())
      dest[i++] = ch;
  dest[i] = '\0';

  while (ch != '\n' && ch != EOF)
    ch = getchar();

  return (dest);
}

Scansets and the %n specifier could be used to parse the input. Scanset和%n说明符可用于解析输入。
The format string "%n%2[AZ]%n%4[0-9]%n" uses the %n specifier in three places to capture the number of characters processed. 格式字符串"%n%2[AZ]%n%4[0-9]%n"使用三个位置的%n说明符来捕获处理的字符数。 The scanset %2[AZ] will scan up to two characters if the characters are in the set of upper case letters. 如果字符在大写字母集中,则扫描集%2[AZ]将扫描最多两个字符。 %4[0-9] will scan up to four characters if the characters are digits. 如果字符是数字, %4[0-9]将扫描最多四个字符。
If two values are scanned by sscanf , the number of characters processed are subtracted to make sure there are two leading upper case characters and six or fewer total character and the trailing character is the terminating zero. 如果sscanf扫描了两个值,则减去处理的字符数,以确保有两个前导大写字符和六个或更少的总字符,尾随字符是终止零。

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

#define MAX_FLIGHTCODE_LEN 6
#define MAX_CITYCODE_LEN 3
#define MAX_NUM_FLIGHTS 50
#define DB_NAME "database"

typedef struct {
    int month;
    int day;
    int hour;
    int minute;
} date_time_t;

typedef struct {
    char flightcode[MAX_FLIGHTCODE_LEN + 1];
    date_time_t departure_dt;
    char arrival_city[MAX_CITYCODE_LEN + 1];
    date_time_t arrival_dt;
} flight_t;

date_time_t departure_dt;
date_time_t arrival_dt;

char * scanline(char *dest, int dest_len);

int main(){
    int head = 0, leading = 0, tail = 0;
    int correct_code = 0;
    int result = 0;
    char temp_string[100];
    char upper[3] = "";
    char digits[5] = "";
    flight_t flight[MAX_NUM_FLIGHTS + 1];
    do {
        printf("Enter flight code>\n");

        scanline(temp_string, sizeof(temp_string));
        if ( 0 < ( result = sscanf ( temp_string, "%n%2[A-Z]%n%4[0-9]%n", &head, upper, &leading, digits, &tail))) {
            if ( 1 == result && 0 == temp_string[leading]) {
                correct_code = 1;
                break;
            }
            if ( 2 == result && 2 == leading - head && 7 > tail - head && 0 == temp_string[tail]) {
                correct_code = 1;
            }
            else {
                printf ( "invalid input\n");
            }
        }
        else {
            printf ( "invalid input\n");
        }
    } while(correct_code == 0);
    printf ( "Input is: %s\n", temp_string);
    strcpy(flight->flightcode, temp_string);
    return 0;
}

char * scanline(char *dest, int dest_len){
    int i, ch;
    i = 0;
    for (ch = getchar(); ch != '\n' && ch != EOF && i < dest_len -1; ch = getchar()) {
        dest[i++] = ch;
    }
    dest[i] = '\0';

    while (ch != '\n' && ch != EOF) {
        ch = getchar();
    }

    return dest;
}

First thing, realize that your question text is missing a question. 首先,要意识到您的问题文本缺少一个问题。 Moreover, your question title makes no sense. 而且,你的问题标题毫无意义。

Anyway, here it is a possible, purposely very ugly, solution. 无论如何,这是一个可能的,故意非常丑陋的解决方案。 Approach: you want to do X, so you write the code to do X. Let's start with scanline() : 方法:你想做X,所以你写代码来做X.让我们从scanline()开始:

int scanline(char *dest, int dest_len)
{
    int i = 0;
    int ch;
    while (1) {
        // Read
        ch = fgetc(stdin);
        // Check
        if (ch == EOF)
            break;
        if (ch == '\n')
            break;
        if (i >= dest_len - 1)
            break;
        // Use
        dest[i] = ch;
        ++i;
    }
    dest[i] = 0;

    // Is the string finished? Ok!
    if (ch == '\n' || ch == EOF)
        return 1;

    // Otherwise discard the rest of the line. Not ok!
    while (ch != '\n' && ch != EOF)
        ch = fgetc(stdin);
    return 0;
}

I know this is ugly, but I believe that it is helpful to clarify the three steps involved in file input: read, check, use. 我知道这很难看,但我相信澄清文件输入中涉及的三个步骤是有帮助的:读取,检查,使用。 Note that it returns true if the line was up to the required number of characters (one less than the buffer size to accomodate for the terminator. 请注意,如果行达到所需的字符数(一个小于缓冲区大小以容纳终结符),则返回true

Then you want to check if: 然后你想检查是否:

  1. scanline() is successful scanline()成功
  2. there is at least one character. 至少有一个角色。
  3. character 0 is between 'A' and 'Z' 字符0介于'A'和'Z'之间
  4. character 1 is between 'A' and 'Z' 字符1介于'A'和'Z'之间
  5. character 2 is between '0' and '1' 字符2介于'0'和'1'之间
  6. character 3 is between '0' and '1' 字符3介于'0'和'1'之间
  7. character 4 is between '0' and '1' 字符4介于'0'和'1'之间
  8. character 5 is between '0' and '1' 字符5介于'0'和'1'之间

Lets write the code for that: 让我们写下代码:

int main(void) 
{
    flight_t flight;

    while (1) {
        printf("Enter flight code>\n");
        if (!scanline(flight.flightcode, sizeof(flight.flightcode))) {
            printf("Too many characters.\n");
            continue;
        }
        int i = 0;
        if (flight.flightcode[i] == 0) {
            printf("Empty input.\n");
            continue;
        }
        if (flight.flightcode[i] < 'A' || flight.flightcode[i] > 'Z') {
            printf("Character %d is not upper case.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
        if (flight.flightcode[i] < 'A' || flight.flightcode[i] > 'Z') {
            printf("Character %d is not upper case.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
        if (flight.flightcode[i] < '0' || flight.flightcode[i] > '9') {
            printf("Character %d is not a digit.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
        if (flight.flightcode[i] < '0' || flight.flightcode[i] > '9') {
            printf("Character %d is not a digit.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
        if (flight.flightcode[i] < '0' || flight.flightcode[i] > '9') {
            printf("Character %d is not a digit.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
        if (flight.flightcode[i] < '0' || flight.flightcode[i] > '9') {
            printf("Character %d is not a digit.\n", i);
            continue;
        }
        i++;
        if (flight.flightcode[i] == 0)
            break;
    }
}

Some remarks: 一些评论:

  1. in your code you set correct_code to 1 as soon as the first character was ok. 在你的代码中,一旦第一个字符correct_code ,你correct_code设置为1。 If you want to loop through the characters you must check if there is an error and exit the loop. 如果要遍历字符,则必须检查是否存在错误并退出循环。
  2. don't use ASCII codes when you have the specific character literals available. 如果您具有特定的字符文字,请不要使用ASCII代码。
  3. I suggest that you take my solution and, as an exercise fix it to be able to work with arbitrary MAX_FLIGHTCODE_LEN , and possibly with arbitrary number of letters and numbers. 我建议你采取我的解决方案,并作为练习修复它能够使用任意MAX_FLIGHTCODE_LEN ,并可能使用任意数量的字母和数字。 Of course MAX_FLIGHTCODE_LEN shall be equal to their sum! 当然MAX_FLIGHTCODE_LEN应该等于它们的总和!
  4. Drop the useless requirement for not using <ctype.h> , and use also <stdbool.h> , which makes the programmer intention clearer. 删除不使用<ctype.h>的无用要求,并使用<stdbool.h> ,这使程序员的意图更加清晰。

Your function scanline does not do much more than the standard function fgets . 您的功能scanline不会比标准功能fgets做更多的事情。 I propose to use the standard function instead. 我建议改用标准功能。 Removing the trailing newline '\\n' is easy. 删除尾随换行符'\\n'很容易。

I have split the checks into 3 parts: 我把支票分为3部分:

  • Check the length to be more than 0 and not more than MAX_FLIGHTCODE_LEN. 检查长度是否大于0且不大于MAX_FLIGHTCODE_LEN。
  • Check the first 2 characters to be uppercase letters A..Z 检查前2个字符是否为大写字母A..Z
  • Check the remaining characters to be digits 0..9 检查剩余字符是否为数字0..9

Proposed code: 拟议代码:

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

#define MAX_FLIGHTCODE_LEN 6
#define MAX_CITYCODE_LEN 3
#define MAX_NUM_FLIGHTS 50
#define DB_NAME "database"

typedef struct {
  int month;
  int day;
  int hour;
  int minute;
} date_time_t;

typedef struct {
  char flightcode[MAX_FLIGHTCODE_LEN + 1];
  date_time_t departure_dt;
  char arrival_city[MAX_CITYCODE_LEN + 1];
  date_time_t arrival_dt;
} flight_t;

date_time_t departure_dt;
date_time_t arrival_dt;


int main(void){

  char temp_string[100];
  flight_t flight[MAX_NUM_FLIGHTS + 1];
  int correct_code;
  size_t len;
  int i;

  do
  {
    /* we first assume the code is correct and set this to 0 on any error */
    correct_code = 1;
    printf("Enter flight code>\n");

    if(fgets(temp_string, sizeof(temp_string), stdin) == NULL)
    {
        if(feof(stdin)) fprintf(stderr, "no input (EOF)\n");
        else perror("fgets");
        correct_code = 0;
        temp_string[0] = '\0';
    }

    if(correct_code)
    {
      len = strlen(temp_string);

      /* cut off newline
       * Use a loop to handle CR and LF just in case Windows might leave more than one character */
      while((len > 0) &&
            ((temp_string[len - 1] == '\n') ||
             (temp_string[len - 1] == '\r')))
      {
        len--;
        temp_string[len] == '\0';
      }

      if(len > MAX_FLIGHTCODE_LEN)
      {
        correct_code = 0;
        fprintf(stderr, "Input must not be longer than %d characters.\n", MAX_FLIGHTCODE_LEN);
      }

      if(len == 0)
      {
        correct_code = 0;
        fprintf(stderr, "Empty input.\n");
      }
    }

    /* check first two letters */
    for(i = 0; (i < 2) && (i < len) && correct_code; i++)
    {
      /* you could use function isupper when you make sure the locale is set to "C" */
      if((temp_string[i] < 'A') || (temp_string[i] > 'Z'))
      {
        correct_code = 0;
        fprintf(stderr, "first two characters must be uppercase letters. Found '%c' at position %d\n", temp_string[i], i);
      }
    }

    /* check digits starting from 3rd character */
    for(i = 2; (i < MAX_FLIGHTCODE_LEN) && (i < len) && correct_code; i++)
    {
      /* you could use function isdigit here */
      if((temp_string[i] < '0') || (temp_string[i] > '9'))
      {
        correct_code = 0;
        fprintf(stderr, "Third to last characters must be digits. Found '%c' at position %d\n", temp_string[i], i);
      }
    }

    if(correct_code)
    {
      /* we already checked that length is not more than MAX_FLIGHTCODE_LEN, so we don't need strncpy to avoid buffer overflow */
      strcpy(flight->flightcode, temp_string);
      printf("Valid code: %s\n", flight->flightcode);
    }
    else
    {
      fprintf(stderr, "Invalid code.\n");
    }
  } while(!correct_code);

  return 0;

}

You have a requirement that does not fit well with what scanf can easily do, so I would stay away from it, and use fgets as a primary read utility. 您的要求与scanf可以轻松完成的要求不scanf ,因此我会远离它,并使用fgets作为主要读取实用程序。

But as the number of acceptable uppercase and digit characters is not fixed by only limited I would use a custom parser based on a state machine. 但是由于可接受的大写和数字字符的数量不仅仅是有限的我将使用基于状态机的自定义解析器。 It is probably not the most elegant nor efficient way but it is simple, robust and easy to maintain. 它可能不是最优雅也不是最有效的方式,但它简单,强大且易于维护。

Just to demonstrate it, I have allowed blank characters before the first uppercase one and spaces after the last digit. 为了演示它,我在第一个大写字母之前允许空白字符,在最后一个数字之后允许空格。 So the following code accept an arbitrary long line following this regex pattern [ \\t]*[AZ]{1,maxupper}[0-9]{0,maxdigit}\\s* provided it receives a buffer of size at least maxupper+maxupper+1 . 所以下面的代码接受这个正则表达式模式之后的任意长行[ \\t]*[AZ]{1,maxupper}[0-9]{0,maxdigit}\\s*前提是它收到一个大小至少为maxupper+maxupper+1的缓冲区maxupper+maxupper+1 It returns a pointer to the buffer is successful or NULL if not. 它返回指向缓冲区成功的指针,否则返回NULL。

As you have said that you could not use the ctype macros, I have defined ASCII (or any charset derived from ASCII) equivalent for the ones I have used. 正如你所说的那样你不能使用ctype宏,我已经定义了ASCII(或任何从ASCII派生的字符集)等同于我使用的那些。

#define TRUE 1
#define FALSE 0

inline int isupper(int c) {
    return c >= 'A' && c <= 'Z';  // only for ASCII and derived
}
inline int isdigit(char c) {
    return c >= '0' && c <= '9';    // guarantee per standard
}
inline int isblank(int c) {
    return c == ' ' || c == '\t';
}
inline int isspace(int c) {
    static const char spaces[] = " \t\r\n\v";
    for(const char *s=spaces; *s != '\0'; s++) {
        if (c == *s) return TRUE;
    }
    return FALSE;
}

char *get_string(char *buffer, int maxupper, int maxdigit, FILE *fd) {
    char buf[16];      // any size >=2 will fit
    char *cur = buffer;
    int state = 0, uppersize=0, digitsize=0;
    for (;;) {         // allow lines longer than buf
        if (NULL == fgets(buf, sizeof(buf), fd)) {
            *cur = '\0';           // EOF: do not forget the terminating NULL
            return state >= 1 ? buffer : NULL;   // must have at least 1 char
        }
        for (char *b=buf; *b!='\0'; b++) {
            switch(state) {
                case 0:   // spaces before first uppercase
                    if (isblank(*b)) break;
                    state++;
                case 1:   // first uppercase
                    if (! isupper(*b)) {
                        state = 5;    // must read up to \n
                        break;
                    }
                    state++;
                case 2:   // process uppercase chars
                    if (! isupper(*b)) {
                        if (uppersize > 0) state++;
                        else  {
                            state = 5;    // must read up to \n
                            break;
                        }
                    }
                    else {
                        if (uppersize >= maxupper)  {
                            state = 5;    // must read up to \n
                            break;
                        }
                        *cur++ = *b;
                        uppersize++;
                        break;
                    }
                case 3:   // process digit chars
                    if (! isdigit(*b)) {
                        state++;
                    }
                    else {
                        if (digitsize >= maxdigit)  {
                            state = 5;    // must read up to \n
                            break;
                        }
                        *cur++ = *b;
                        digitsize++;
                        break;
                    }
                case 4:    // allow spaces after last digit
                    if ('\n' == *b) {
                        *cur = '\0';
                        return buffer;
                    }
                    if (! isspace(*b)) state++
                    break;
                case 5:    // on error clean end of line
                    if ('\n' == *b) return NULL;
            }
        }
    }
}

Then in your code, you simply calls it that way: 然后在你的代码中,你只需这样调用它:

...
printf("Enter flight code>\n");
if (NULL == get_string(flight->flightcode, 2, 4, stdin)) {
    // process the error
    ...
}
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM