Проверка типа массива произвольной длины в ANSI C - PullRequest
4 голосов
/ 03 мая 2019

Привет! Я ограничен stdio.h, stdlib.h и string.h и мне нужно запросить ввод у пользователя - ввод может быть любым количеством символов от 1 до 6, однако первые два символа ДОЛЖНЫ быть заглавная буква алфавита, а оставшиеся четыре символа ДОЛЖНЫ быть числами от 0 до 9.

Примеры правильного ввода:

  • AB1
  • AB1234
  • AB
  • A

Примеры неверного ввода:

  • AB12345 (слишком много символов)
  • 123 (первые два символа не являются заглавными буквами)
  • ABA (символ после второго не является числовым значением)

Вот моя попытка на данный момент (просто имейте в виду, что у меня почти нет опыта работы с C, вероятность того, что это решение является "идиоматическим", практически отсутствует, и причина, по которой я спрашиваю об этом, заключается в том, чтобы я мог учиться) :

Flightcode - это массив символов, определенный как flightcode[7], он живет внутри другой структуры, называемой flight. Я fgets сначала ввожу temp_array[7], а затем strcpy в код полета-> код полета, так что добавляется нулевой терминатор, и я не знаю лучшего способа сделать это.

#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 ]

2 голосов
/ 03 мая 2019

Scansets и спецификатор %n могут использоваться для разбора ввода.
Строка формата "%n%2[A-Z]%n%4[0-9]%n" использует спецификатор %n в трех местах для захвата количества обработанных символов.Scanset %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 голос
/ 03 мая 2019

Ваша функция 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;

}
1 голос
/ 03 мая 2019

У вас есть требование, которое не вписывается в то, что легко может сделать 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
    ...
}
...
1 голос
/ 03 мая 2019

Во-первых, осознайте, что в тексте вашего вопроса отсутствует вопрос.Более того, название вашего вопроса не имеет смысла.

В любом случае, здесь это возможное, нарочито очень некрасивое, решение.Подход: вы хотите сделать 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;
}

Я знаю, что это некрасиво, но я считаю, что полезно уточнить триэтапы ввода файла: чтение, проверка, использование.Обратите внимание, что он возвращает 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>, что делает намерение программиста более ясным.
...