Читайте также:
|
|
Как было сказано в лекции 2, любые данные можно представить в виде последовательности чисел. Например, чтобы хранить звук в цифровом виде, нужно через равные промежутки времени определить уровень электрического сигнала, поступающего от микрофона, и, округлив до какого-то знака, записать в двоичном виде. Такой массив чисел называется выборкой. В случае, если нужно хранить стереозвук, приходится чередовать числа, соответствующие левому и правому каналу. Но это значит, что во всех программах, осуществляющих обработку звука, нужно ввести соглашение о том, в каком порядке идут данные, и постоянно следить за тем, чтобы правильный порядок чтения не нарушался. А сбой в чтении всего одного значения менял бы каналы местами.
Разумеется, такая организация работы с данными была бы неудобна. Для работы с данными, имеющими чёткую организацию, языки высокого уровня предоставляют специальные средства. В языке Си это структуры. Структура представляет собой тип данных в виде упорядоченного набора разнородных переменных. Переменные, из которых составлена структура, называются её полями или элементами. Элементом структуры может быть что угодно, кроме этой же структуры (но не указателя на неё). В общем случае объявление структуры выглядит так: struct <имя структуры>[30] {<поля структуры>} [<объявление переменных>];
Структуры могут объявляться как вне функций, так и внутри них[31].
Данные в структуре, как и в массиве, расположены последовательно, причём поля, объявленные раньше, записываются в младшие байты, а объявленные старше – в старшие. Иными словами, расположение полей в памяти осуществляется в порядке возрастания с появлением новых полей. Но поскольку каждое поле имеет свой размер и свой тип, обращение к элементам по индексу невозможно. Поэтому каждому полю структуры присваивается своё индивидуальное имя. Следует помнить, что каждая переменная, имеющая структурный тип (как и всякая другая переменная) имеет собственное уникальное имя, но имена соответственных полей у них одинаковые. Для обращения к элементу структуры существует оператор. (точка). Слева от точки пишется имя переменной, а справа – поля структуры.
Например:
struct sample
{
short int l;
short int r;
};
struct sample subtraction(struct sample);
int main()
{
struct sample sound[1024];
/*some code*/
return 0;
}
struct sample subtraction(struct sample source)
{
struct sample temp;
temp.r=source.l-source.r;
temp.l=temp.r;
return temp;
}
В начале программы объявляется новый структурный тип данных sample, соответствующий одному сэмплу 16-битной стерео выборки. Функция subtraction имеет тип struct sample, она вычитает из правого канала левый[32]. Из переменных структурного типа может быть составлен массив – sound в функции main.
Неудобно каждый раз писать длинную конструкцию struct sample. К тому же, если в программе есть несколько структурных типов, то при беглом взгляде на код придётся каждый раз замедляться около слова struct – то есть, читаемость кода от этого ухудшается. Можно было бы использовать define для того, чтобы избавиться от «балласта» в виде struct, но есть способ лучше. Оператор typedef <тип> <синоним> синонимы типов данных. То есть, после строки
typedef char bool;
обозначения char и bool будут абсолютно равноправны. В отношении структур это выглядит так:
typedef struct SAMPLE
{
short int l;
short int r;
} sample;
Теперь вместо struct SAMPLE можно везде писать просто sample. А поскольку имя структуры является необязательным параметром, можно написать и так:
typedef struct
{
short int l;
short int r;
} sample;
Как уже было сказано, элементом структуры не может быть она сама, но моет быть указатель на неё. То есть, конструкция
typedef struct NODE
{
char* data;
struct NODE next;
} node;
является ошибочной, но правильной (и часто применяющейся) будет такая:
typedef struct NODE
{
char* data;
struct NODE* next;
} node;
Представим теперь, что нужно обратиться к символу, лежащему по адресу, указанному в какой-то переменной типа node, на которую ссылается переменная a этого же типа. Подобное обращение выглядит следующим образом:
*((*(a.next)).data);
Это, конечно, неудобно. Поэтому существует оператор -> (знак минуса и закрывающей треугольной скобки), одновременно выполняющий операцию разадресации и обращения к элементу структуры. С его использованием это же обращение выглядит так:
a->next->data;
Гораздо короче и понятнее. А обращение
a->next->next->next->data;
Без использования -> выглядело бы так:
*((*((*((*(a.next)).next)).next)).data);
Данный пример делает очевидными преимущества применения оператора ->.
В языке Си существует ещё один структурный типа данных – объединение. Объединение отличается от структуры тем, что все его элементы находятся по одному адресу. При этом тип они могут иметь разный. То есть, объединение позволяет обращаться к одному и тому же набору байт памяти, интерпретируя его различным образом.
Синтаксис объединений такой же, как и структур, но вместо «struct» пишется «union». Например:
typedef union
{
int eax;
struct
{
char l;
char h;
} a;
short int ax;
} cpu_reg;
Это объединение имитирует структуру 32-разрядного регистра центрального процессора. Обращение к его полям выглядит так:
a.eax=0x0000000;
a.a.l=0xff;
a.a.h=0xff;
a.eax=0xffff0000;
a.ax=0xffff;
Из этого примера следует первое применение объединений: тип данных для хранения значений, полученных из регистров процессора. Но это не единственное применение.
Объединения можно использовать для осуществления присваивания между переменными разных типов в обход системы преобразования типов языка Си. Допустим, нам надо вывести на экран двоичное представление числа с плавающей точкой. Из примера, приведённого в лекции 7 очевидно, как это сделать с целым числом: с помощью сдвига и целочисленного деления. Однако попытка присвоить целому числу значения с плавающей точкой приведёт к преобразованию типа и до неузнаваемости изменит двоичное представление числа. Значит, нужен способ передавать значения между типами с сохранением двоичного представления. Для этого можно создать объединение:
typedef union
{
float f;
unsigned int i;
} transformer;
Теперь мы можем написать конструкцию вида:
transformer buffer;
unsigned int number;
buffer.f=source;
number=buffer.i;
Считая, что нам уже дана переменная source типа float, в переменной number будет находится число, в двоичном представлении эквивалентное source. После этого написать функцию вывода на экран не составляет труда.
Дата добавления: 2015-11-13; просмотров: 41 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Лекция 7: операторы выбора, безусловный переход, циклы. | | | Лекция 9: связные списки. |