类型检查 ANSI C 中的任意长度数组

Type checking arbitrary length array in ANSI C

您好,我只能使用 stdio.h、stdlib.h 和 string.h,我需要请求用户输入 - 输入可以是 1 到 6 之间的任意数量的字符,但是前两个字符必须是大写字母,其余四个字符必须是 0 到 9 之间的数字。

有效输入示例:

无效输入示例:

这是我到目前为止的尝试(请记住,我几乎没有使用 C 的经验,这个解决方案 "idiomatic" 的可能性与 none 相邻,以及我问的原因这样我就可以学习了):

Flightcode 是一个定义为 flightcode[7] 的字符数组,它位于另一个名为 flight 的结构中。我先 fgets 将其转换为 temp_array[7] 然后 strcpy 将其转换为 flight->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] != '[=10=]' && 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] = '[=10=]';

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

  return (dest);
}

首先,意识到您的问题文本缺少一个问题。此外,您的问题标题没有任何意义。

无论如何,这是一个可能的、故意非常丑陋的解决方案。做法:你想做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;
}

我知道这很难看,但我相信澄清文件输入涉及的三个步骤:读取、检查、使用是有帮助的。请注意,它 returns true 如果该行达到所需的字符数(比缓冲区大小少一个以容纳终止符。

然后你想检查是否:

  1. scanline()成功
  2. 至少有一个字符。
  3. 字符 0 在 'A' 和 'Z'
  4. 之间
  5. 字符 1 在 'A' 和 'Z'
  6. 之间
  7. 字符 2 在“0”和“1”之间
  8. 字符 3 在“0”和“1”之间
  9. 字符 4 在“0”和“1”之间
  10. 字符 5 在“0”和“1”之间

让我们为此编写代码:

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

一些备注:

  1. 在您的代码中,只要第一个字符正确,您就将 correct_code 设置为 1。如果你想遍历字符,你必须检查是否有错误并退出循环。
  2. 当您有可用的特定字符文字时,不要使用 ASCII 代码。
  3. 我建议您采用我的解决方案,并作为练习修复它,使其能够处理任意 MAX_FLIGHTCODE_LEN,并可能处理任意数量的字母和数字。当然MAX_FLIGHTCODE_LEN等于他们的总和!
  4. 放弃不使用<ctype.h>的无用要求,同时使用<stdbool.h>,这让程序员的意图更明确。

您的函数 scanline 并没有比标准函数 fgets 做更多的事情。我建议改用标准函数。删除尾随的换行符 '\n' 很容易。

我已将支票分成 3 部分:

  • 检查长度大于0且不大于MAX_FLIGHTCODE_LEN。
  • 检查前2个字符是否为大写字母A..Z
  • 检查剩余字符为数字 0..9

建议代码:

#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] = '[=10=]';
    }

    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] == '[=10=]';
      }

      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;

}

您的要求不符合 scanf 可以轻松完成的要求,因此我会远离它,并使用 fgets 作为主要读取实用程序。

但是由于可接受的大写和数字字符的数量不是固定的,所以我会使用基于状态机的自定义解析器。它可能不是最优雅也不是最有效的方式,但它简单、健壮且易于维护。

为了演示,我在第一个大写字母前允许有空格,在最后一个数字后有空格。因此,以下代码接受此正则表达式模式 [ \t]*[A-Z]{1,maxupper}[0-9]{0,maxdigit}\s* 之后的任意长行,前提是它接收大小至少为 maxupper+maxupper+1 的缓冲区。 returns 指向缓冲区的指针成功,否则为 NULL。

正如您所说,您不能使用 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 != '[=10=]'; 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 = '[=10=]';           // EOF: do not forget the terminating NULL
            return state >= 1 ? buffer : NULL;   // must have at least 1 char
        }
        for (char *b=buf; *b!='[=10=]'; 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 = '[=10=]';
                        return buffer;
                    }
                    if (! isspace(*b)) state++
                    break;
                case 5:    // on error clean end of line
                    if ('\n' == *b) return NULL;
            }
        }
    }
}

然后在您的代码中,您只需这样调用它:

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

扫描集和 %n 说明符可用于解析输入。
格式字符串 "%n%2[A-Z]%n%4[0-9]%n" 在三个地方使用 %n 说明符来捕获处理的字符数。如果字符在大写字母集中,则扫描集 %2[A-Z] 将最多扫描两个字符。 %4[0-9] 如果字符是数字,将最多扫描四个字符。
如果 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] = '[=10=]';

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

    return dest;
}