Лекция 21. Проектирование программ. Указатели на структуры

Проектирование программ

Проектирование программ является крайне обширной темой, на данный момент мы рассмотрим только те концепции, которые уже можно применять в небольших программах.

  1. Разделение реализации и интерфейса (2 набора функций: для взаимодействия с пользователем и для решения самой задачи).

  2. Абстрактные типы данных (АТД). Абстракцией называется отделение того, что делается, от того, как конкретно это делается. Абстрактный тип данных – это такой созданный программистом тип данных, вся работа с которым идёт через набор функций, специально созданный для этого автором типа.

    В АТД внешний интерфейс функций является абстракцией над тем, какие именно данные хранятся внутри АТД и какие именно алгоритмы с ними работают. Такой подход имеет ряд достоинств:

    • Пользователь не обязан знать о том, какие именно данные хранятся внутри АТД, как обустроено их хранение, какие алгоритмы применяются.
    • Если меняется реализация внутренних структур данных и алгоритмов АТД, то очень часто оказывается возможным сохранить внешний интерфейс функций АТД без изменений.

    Пример Мы с вами уже встречали один абстрактный тип данных. Это тип FILE *. FILE – некоторая структура, однако, внутрь структуры мы никогда не залезаем и всегда используем для работы с ней некоторый набор функций: fopen, fprintf, fscanf и так далее. Такой подход, помимо упрощения работы с файлами, даёт нам возможность использовать различную реализацию этого набора функций на различных операционных системах. То есть, мы пишем программу, а потом просто компилируем её для разных ОС, не задумываясь, что функция fopen на разных OC может быть реализована по-разному.

    Примечание Идеи, лежащие в основе АТД в дальнейшем получили сильное развитие в объектно-ориентированном программировании.

    Пример АТД:

    struct vec
    {
        double x;
        double y;
        double z;
    };
    
    typedef struct vec Vec;
    
    Vec sum3d(Vec v1, Vec v2)
    {
        struct vec3d result;
        result.x = v1.x + v2.x;
        result.y = v1.y + v2.y;
        result.z = v1.z + v2.z;
        return result;
    }
    

Указатели на структуры

При передаче структур в функцию они копируются в аргументы, и при попытке изменить аргумент будет изменена только копия. Таким образом, точно так же, как и в случае с числами, для изменения структур надо использовать указатели на те значения, которые нужно изменить.

struct s {
    int a;
    double b;
}

typedef struct s S;

void set_a(S *ptr, int new_a)
{
    (*ptr).a = new_a;
}

Следует обратить внимание на скобки в выражении (*ptr).a . Если о них забыть, то компилятор попытается применить оператор . до *, что приведёт к ошибке, так как оператор . может извлекать поля только из значений структурных типов, а не из указателей на значения.

Так как указателей на структуры и обращений к полям структур по указателям, а следовательно, конструкций вида (*variable).field при написании системных программ на C было достаточно много, Керниган и Ритчи ввели конструкцию variable->field. Оператор -> извлекает значение из указателя на прямую.

Подытожим. Оператор . обеспечивает доступ к полю структуры в значении структуры.

Оператор -> обеспечивает доступ к полю структуры по указателю на значение структуры.

Для доступа к одиночной структуре можно применять два способа записи:

  • (*s).a
  • s->a (предпочтительный способ)

Для доступа к одной из структур массива можно применять три способа записи:

  • s[i].a (самый простой и предпочтительный способ)
  • (*(s + i)).a
  • (s + i)->a

Пример этих операций в простой программе:

struct tag_worker
{
    char name[20];
    int salary;
};

typedef struct tag_worker Worker;

int main(void)
{
    Worker w1;
    strcpy(w1.name, "John");
    w1.salary = 100;

    Worker *w = (Worker *) malloc(sizeof(Worker));
    strcpy(w->name, "John");
    w->salary = 100;

    // Выделяем память под массив из 8-ми структур.
    Worker *w_arr = (Worker *) malloc(8 * sizeof(Worker));
    strcpy(w_arr[0].name, "Mary");
    w_arr[0].a = 105;

    // ...

    return 0;
}

TODO: пример сложного АТД