Хорошая практика кодирования со структурами C? - PullRequest
3 голосов
/ 30 ноября 2011

В C вы можете определить структуры для хранения ассортимента переменных;

typedef struct {
    float       sp;
    float       K;                 // interactive form - for display only
    float       Ti;                //  values are based in seconds
    float       Td;
} pid_data_t;

Но допустим, что K, Ti и Td никогда не должны устанавливаться публично, и должныиспользоваться только для хранения значений после того, как ими манипулируют.Итак, я хочу, чтобы эти значения не обновлялись;

pid_data_t = pid_data;
pid_data.K = 10;         // no good! changing K should be done via a function

Я хочу, чтобы они устанавливались с помощью функции;

int8_t pid_set_pid_params(float new_K_dash, float new_Ti_dash, 
    float new_Td_dash)
{
    …                             // perform lots of things
    pid_data->K  = new_K_dash;
    pid_data->Ti = new_Ti_dash;
    pid_data->Td = new_Td_dash;
}

Есть какие-нибудь мысли по этому поводу?Я знаю, что C ++ использует как свойство get / set, но мне было интересно, что люди могут делать на C.

Ответы [ 4 ]

5 голосов
/ 30 ноября 2011

Ваш общедоступный интерфейс должен предлагать только непрозрачный указатель (может быть DATA* или data_handle), а также функции-обработчики create_data(), set_data_value(), read_data_value(), free_data() и т. Д., Которые работаютна непрозрачном указателе.

Очень похоже на FILE*.

Только не давайте своим клиентам файлы внутреннего заголовка: -)

// library.h

typedef struct data_t * data_handle;

data_handle create_data();
void        free_data(data_handle);

Частная реализация (донне отправляется):

#include "library.h"

struct data_t
{
  /* ... */
};

data_handle create_data() { return malloc(sizeof(struct data_t)); }
void        free_data(data_handle h) { free(h); }
/* etc. etc. */
2 голосов
/ 30 ноября 2011

Канонический способ сделать это - использовать комбинацию непрозрачных указателей и открытых структур, а также распределителей, геттеров и сеттеров для приватных элементов. Примерно по этим направлениям:

foo.h

typedef struct Foo {
    /* public elements */
} Foo;

Foo *new_Foo(void);
void Foo_something_opaque(Foo* foo);

foo.c

#include "foo.h"

typedef struct Private_Foo_ {
    struct Foo foo;
    /* private elements */
} Private_Foo_;

Foo *new_Foo(void)
{
    Private_Foo_ *foo = malloc(sizeof(Private_Foo_));
    /* initialize private and public elements */
    return (Foo*) foo;
}

void Foo_something_opaque(Foo *foo)
{
    Private_Foo_ *priv_foo = (Private_Foo_*) foo;
    /* do something */
}

Это работает, поскольку C гарантирует, что адрес переменной структуры всегда равен адресу самого первого элемента структуры. Мы можем использовать это, чтобы иметь структуру Private_Foo_, содержащую публичный Foo в начале, выдавая указатели на все это, при этом модули компиляции не имеют доступа к определению структуры Private_Foo_, просто видя некоторую память без какого-либо контекста.

Следует отметить, что C ++ работает очень похоже за кулисами.

Обновление

Как указал KereekSB, он будет прерываться при использовании в массиве.

Я говорю: тогда не делайте Foo f[], каким бы заманчивым он ни был, но сделайте массивы указателей на Foo: Foo *f[].

Если вы действительно настаиваете на использовании его в массивах, сделайте следующее:

foo_private.h

typedef struct Private_Foo_ {
    /* private elements */
} Private_Foo_;

static size_t Private_Foo_sizeof(void) { return sizeof(Private_Foo_); }

foo_private.h написан так, что его можно скомпилировать в объектный файл. Используйте некоторую вспомогательную программу, чтобы связать ее, и используйте результат Private_Foo_sizeof(), чтобы сгенерировать фактический, зависящий от платформы foo.h из некоторого файла foo.h.in.

foo.h

#include

#define FOO_SIZEOF_PRIVATE_ELEMENTS <generated by preconfigure step>

typedef struct Foo_ {
    /* public elements */
    char reserved[FOO_SIZEOF_PRIVATE_ELEMENTS];
} Foo;

Foo *new_Foo(void);
void Foo_something_opaque(Foo* foo);

foo.c

#include "foo.h"
#include "foo_private.h"

Foo *new_Foo(void)
{
    Foo *foo = malloc(sizeof(Foo));
    /* initialize private and public elements */
    return (Foo*) foo;
}

void Foo_something_opaque(Foo *foo)
{
    Private_Foo_ *priv_foo = (Private_Foo_*) foo.reserved;
    /* do something */
}

ИМХО, это действительно грязно. Теперь я фанат умных контейнеров (к сожалению, нет стандартной библиотеки контейнеров для C). В любом случае: в таком контейнере создается с помощью функции, подобной

Array *array_alloc(size_t sizeofElement, unsigned int elements);
void *array_at(Array *array, unsigned int index);
/* and all the other functions expected of arrays */

См. Libowfaw для примера такой реализации. Теперь для типа Foo было тривиально предоставить функцию

Array *Foo_array(unsigned int count);
1 голос
/ 30 ноября 2011

в С, по соглашению ....

для OO C вот так ...

Я бы получил pid_data_create(&data) // initializes your struct

и pid_data_set_proportional_gain(&data, 0.1);

и т.д ...

так что в основном достигается класс ish C ++ ... ставьте перед всеми функциями префикс с именем "class" / "struct" и всегда передайте struct * в качестве первого параметра.

также, он должен хранить указатели функций для polymorphisim, и вы не должны вызывать эти указатели функций напрямую, опять же, иметь функцию, которая принимает вашу структуру в качестве параметра, а затем может сделать вызов указателя функции null, фальшивое наследование / виртуальные функции и прочее)

0 голосов
/ 30 ноября 2011

Ориентация на объект - это способ мышления и моделирования, инкапсуляция данных, в которой данные структуры не должны быть изменены пользователем напрямую, может быть реализована следующим образом:

my_library.h

#ifndef __MY_LIBRARY__
#define __MY_LIBRARY__
typedef void MiObject;

MyObject* newMyObject();

void destroyMyObject(MyObject*)

int setMyObjectProperty1(MyObject*,someDataType1*);

/*Return a pointer to the data/object,  classic pass by value */
someDataType1* getMyObjectProperty2Style1(MyObject*);

int setMyObjectProperty2(MyObject*,someDataType2*);

/* The data/object is passed through reference */
int getMyObjectProperty2Style2(MyObject*,someDataType2**);

    /* Some more functions here */
#endif

my_library.c

struct _MyHiddenDataType{
    int a;
    char* b;
    ..
    ..
};

MyObject* newMyObject(){
struct _MyHiddenData* newData = (struct _MyHiddenData*)malloc(sizeof(struct _MyHiddenData);
//check null, etc
//initialize data, etc
return (MyObject*)newData;
}

int setMyObjectProperty1(MyObject* object,someDataType1* somedata){
    struct _MyHiddenData* data = (struct _MyHiddenData*)object;
    //check for nulls, and process somedata
    data->somePropery=somedata;
}

someDataType1* getMyObjectProperty2Style1(MyObject*){
    struct _MyHiddenData* data = (struct _MyHiddenData*)object;
    //check for nulls, and process somedata
    return data->someProperty;
}
/* Similar code for the rest */

И таким образом вы инкапсулировали свойства структуры, как если бы они были частными. Точно так же статические функции внутри my_libray.c будут вести себя как частные функции. Внимательно посмотрите на C, и вы увидите, что ваше воображение - это предел того, что вы можете сделать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...