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

时间:2019-05-03 06:35:04

标签: c arrays string ansi-c

嗨,我仅限于stdio.h,stdlib.h和string.h,我需要询问用户输入-输入可以是1到6之间的任意数量的字符,但是前两个字符必须是大写字母,其余四个字符必须为0到9之间的数字。

有效输入示例:

  • AB1
  • AB1234
  • AB
  • A

无效输入示例:

  • AB12345(字符太多)
  • 123(前两个字符不是大写字母)
  • ABA(第二个字符之后的字符不是数字值)

到目前为止,这是我的尝试(请记住,我几乎没有C的经验,这种解决方案“惯用”的可能性几乎没有,我问这个的原因是我可以学习) :

Flightcode是一个定义为flightcode[7]的char数组,它位于另一个称为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] != '\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);
}

4 个答案:

答案 0 :(得分:2)

扫描集和%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] = '\0';

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

    return dest;
}

答案 1 :(得分:1)

第一件事,意识到您的问题文本缺少一个问题。此外,您的问题标题没有意义。

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

我知道这很丑陋,但是我认为弄清楚文件输入中涉及的三个步骤会很有帮助:读取,检查,使用。请注意,如果该行达到要求的字符数(比终止符所能容纳的缓冲区大小小1个字符),它将返回true

然后您要检查是否:

  1. scanline()成功
  2. 至少有一个字符。
  3. 字符0在“ A”和“ Z”之间
  4. 字符1在“ A”和“ Z”之间
  5. 字符2在'0'和'1'之间
  6. 第3个字符在“ 0”和“ 1”之间
  7. 字符4在'0'和'1'之间
  8. 字符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>,这使程序员的意图更加清晰。

答案 2 :(得分:1)

您的功能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] = '\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;

}

答案 3 :(得分:1)

您有一个要求与scanf可以轻松完成的工作不完全匹配,因此我会远离它,而将fgets用作主要读取实用程序。

但是,由于可接受的大写字母和数字字符的数量不受固定的限制,因此我将使用基于状态机的自定义解析器。这可能不是最优雅也不有效的方法,但是它简单,健壮且易于维护。

仅出于演示目的,我在第一个大写字母之前允许使用空白字符,在最后一位数字之后使用空格。因此,以下代码接受此正则表达式模式[ \t]*[A-Z]{1,maxupper}[0-9]{0,maxdigit}\s*之后的任意长行,条件是该代码接收大小至少为maxupper+maxupper+1的缓冲区。它返回一个指向缓冲区成功的指针,否则返回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 != '\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;
            }
        }
    }
}

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

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