В основном я пытаюсь удалить дубликаты в моем коде, используя enum и новую функцию, чтобы помочь.

Скажем, у меня есть структура со следующим:

typedef struct user {
    bool empty;
    int lineNumber;
    char *errMessage;
    char *username;
    char *password;
    char *uid;
    char *gid;
    char *gecos;
    char *dir;
    char *shell;
} user;

И я делаю перечисление как следующее:

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

Я пытаюсь отправить параметр userValue enumParam в функцию и тем самым выбрать, к какому члену в структуре я хочу получить доступ. Пример вспомогательной функции:

void parseHelper(userValue enumParam) {
    user *newStruct;
    newStruct -> enumParam = "hello";
}

Пример использования:

parseHelper(password)

Expected: newStruct->password should point to "hello"

Но это не помогает, потому что я получаю следующую ошибку:

'user {aka struct user}' не имеет члена с именем 'val'

1
Cows42 2 Сен 2017 в 19:57

5 ответов

Лучший ответ

Не мой друг.

Перечисления - это просто целые числа, они не являются строковыми подстановками :)

3
P__J__ 2 Сен 2017 в 16:59

Вы не можете делать то, что пытаетесь сделать так же прямо, как вы показываете, потому что константы перечисления являются именами для целых чисел, а не именами членов структуры. Вы не можете напрямую написать ссылку на элемент структуры, используя любую переменную для идентификации имени элемента - вы должны явно назвать элемент (так что это встроенное смещение в структуре).

Однако при условии, что в вашем компиляторе есть поддержка C11 для анонимных членов объединения и структуры, и при условии, что вы можете переопределить тип структуры (он не является полностью фиксированным и неподвижным, предопределенным некоторой внешней силой, такой как наставник или стандарт), вы можете получить довольно близко к тому, что вы хотите с кодом, подобным этому:

#include <stdio.h>
#include <stdbool.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    union
    {
        struct
        {
            char *username;
            char *password;
            char *uid;
            char *gid;
            char *gecos;
            char *dir;
            char *shell;
        };
        char *field[7];
    };
} user;

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

static void parseHelper(user *u, userValue enumParam, char *value)
{
    u->field[enumParam] = value;
}

int main(void)
{
    user u;
    u.empty = false;
    u.lineNumber = 1;
    u.errMessage = "no error";
    parseHelper(&u, password, "secret");
    parseHelper(&u, username, "me");
    parseHelper(&u, uid, "0");
    parseHelper(&u, gid, "1");
    parseHelper(&u, gecos, "Yours Truly");
    parseHelper(&u, dir, "/home/me");
    parseHelper(&u, shell, "/bin/sea");

    printf("%s:%s:%s:%s:%s:%s:%s\n", u.username,
           u.password, u.uid, u.gid, u.gecos, u.dir, u.shell);
    return 0;
}

При запуске он выдает:

me:secret:0:1:Yours Truly:/home/me:/bin/sea

Есть несколько факторов, которые делают эту работу.

  1. Имена элементов структуры находятся в отдельных пространствах имен, поэтому имена элементов перечисления не влияют на имена элементов структуры.
  2. Все поля, которые вы пытаетесь обработать, имеют одинаковый тип (char *).
  3. Когда у вас есть анонимный союз в структуре, вы можете получить доступ к элементам объединения по имени.
  4. Если у вас есть анонимная структура в объединении, вы можете получить доступ к элементам в структуре по имени.

Если вы застряли с C90 или C99, вы можете добиться аналогичного эффекта, но он более многословен:

#include <stdio.h>
#include <stdbool.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    union
    {
        struct
        {
            char *username;
            char *password;
            char *uid;
            char *gid;
            char *gecos;
            char *dir;
            char *shell;
        } f;
        char *field[7];
    } u;
} user;

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

static void parseHelper(user *u, userValue enumParam, char *value)
{
    u->u.field[enumParam] = value;
}

int main(void)
{
    user u;
    u.empty = false;
    u.lineNumber = 1;
    u.errMessage = "no error";
    parseHelper(&u, password, "secret");
    parseHelper(&u, username, "me");
    parseHelper(&u, uid, "0");
    parseHelper(&u, gid, "1");
    parseHelper(&u, gecos, "Yours Truly");
    parseHelper(&u, dir, "/home/me");
    parseHelper(&u, shell, "/bin/sea");

    printf("%s:%s:%s:%s:%s:%s:%s\n", u.u.f.username, u.u.f.password,
           u.u.f.uid, u.u.f.gid, u.u.f.gecos, u.u.f.dir, u.u.f.shell);
    return 0;
}

Это дает тот же результат, конечно.

0
Jonathan Leffler 2 Сен 2017 в 18:13

У меня есть предложение для вас.

Сначала добавьте последний элемент в свой enum список (userValue_max):

typedef enum {username, password, uid, gid, gecos, dir, shell, userValue_max} userValue;  

Теперь определим ваш struct немного другим способом, содержащим массив строк:

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    char *field[userValue_max];
} user;  

Теперь напишите свой код следующим образом:

void parseHelper(userValue enumParam) {
    user *newStruct = malloc(sizeof(user)); // Allocating memory here!!
    newStruct->field[enumParam] = "hello";
}  
2
pablo1977 2 Сен 2017 в 18:13

Я бы хотел использовать указатели на функции. Это не очень чистое решение, но оно должно работать ..

В основном в этом решении перечисления ДОЛЖНЫ быть в том же порядке, в котором указатели на символы определены в структуре. (Я бы предпочел установить их вручную для индекса в структуре следующим образом:

typedef enum {
    username = 0, 
    password = 1,
    uid = 2,
    gid = 3,
    gecos = 4,
    dir = 5,
    shell = 6
} userValue;

Теперь перечисление можно использовать как индекс из первого символа *, представленного перечислением в структуре (имя пользователя). Это можно сделать, получив указатель на символ * и увеличивая его, используя арифметику указателя.

Пример полного кода (используется отладчик для подтверждения правильного выполнения):

#include <string.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    char *username;
    char *password;
    char *uid;
    char *gid;
    char *gecos;
    char *dir;
    char *shell;
} user;

typedef enum { 
    username = 0,
    password = 1,
    uid = 2,
    gid = 3,
    gecos = 4,
    dir = 5,
    shell = 6
} userValue;

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    char** selected = &newStruct.username;
    selected = (char**)(selected + ((int)enumParam));
    *selected = "hello";
}

int main()
{
    parseHelper(username);
    parseHelper(password);
    parseHelper(uid);
    parseHelper(gid);
    parseHelper(gecos);
    parseHelper(dir);
    parseHelper(shell);
    return 0;
}

< Сильный > -Edit Возможно, более читаемый способ достижения того же результата - привести первый char* (в данном случае имя пользователя) к массиву char * и использовать стандартный синтаксис массива для перехода к желаемому смещению в

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    // Other fields is an array of char * which starts at username...
    char** otherFields = &newStruct.username;
    otherFields[(int)enumParam] = "hello";
}

- вариант 2

Если вы можете изменить определение структуры и нигде больше не хотите специально обращаться к строкам по их именам (например, user->username), вы можете объявить массив указателей на символы в вашей структуре следующим образом:

typedef struct user
{
    bool empty;
    int lineNumber;
    char *otherFields[7];
    // otherFields[0] - username
    // otherFields[1] - password
    // otherFields[2] - uid
    // otherFields[3] - gid
    // otherFields[4] - gecos
    // otherFields[5] - dir
    // otherFields[6] - shell
} user;

И затем заполните при необходимости, используя индекс для массива:

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    newStruct.otherFields[enumParam] = "hello";
}

Под капотом это решение идентично первому. Однако это дает дополнительное преимущество, заключающееся в упрощении использования перечисления для доступа к требуемому char*, в то же время теряя преимущество использования имени строки для доступа к конкретной строке в остальной части кода.

1
Brian Zammit 2 Сен 2017 в 22:51

Оператор -> предназначен для доступа к переменной-члену указателя на структуру. Важно то, что имена этих переменных-членов не могут отличаться. Вы должны называть их по имени, которое вы им дали в декларации.

Здесь вы пытаетесь получить доступ к переменной-члену с именем 'val', но такой переменной-члена нет. Отсюда и ошибка.

В этой функции вы должны будете поместить возможные значения enum в условия, чтобы соответствовать возможным именам структуры.

0
Alex Morrison 2 Сен 2017 в 17:11