4.1.5. Сложные типы данных

(Руководство разработчика по микроконтроллерам семейства HCS08)

Кроме основных типов данных, показанных выше, в языке C могут использоваться сложные конструкции данных, которые рассматриваются далее.

4.1.5.1. Перечисления

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

enum<имяперечисления>
{
списокперечисления
}<имяпеременной>

Элементы в списке разделяются запятой. По умолчанию, первый элемент перечисления равен 0, второй — 1 и т.д. Также можно принудительно указывать значение каждого элемента перечисления.

Но константы перечиления имеют следующую важую особенность: если значение константы не указано, то оно на 1 больше предыдущей. То есть изменив значение одного элемента, все остальные в перечислении тоже изменятся.

Например:

enumweek{mon,tue,wed,thu,fri,sat,sun}days;
//mon=l,tue=2,wed=3,thu=4,fri=5,sat=6,sun=7
//Чтобысоздатьперечислениеслогическимизначениями,следуетиспользоватьтип
//переменнойboolean:
enumboolean{true=l,false=0};
enumbooleanmy_boolean_variable;

Перечисления чрезвычайно удобны при создании управляющих программных автоматов! С помощью перечисления каждому состоянию автомата может быть поставлено в соответствие некоторое численное значение.

4.1.5.2. Указатели

Указатели — это переменные, в которых хранится адрес данных. Использование указателей существенно упрощает программирование на C режимов работы периферийных модулей МК, а также добавляет много новых возможностей в процессе манипуляции ланными.

На Рис.4.1 показан пример действия указателей. Два указателя p1 and p2 хранятся по адресам 0x0100 и 0x0102 соответственно.

Содержимое первого указателя равно 0x0104. Это не данные, а адрес. Если посмотреть в ячейку с адресом 0x0104, то мы обнаружим данные, на которые указывает указатель p1 (0x32). То есть первый указатель ссылается на переменную data1.

Аналогично, содержимое переменной p2 равно 0x0106. Но на самом деле указатель p2 ссылается на переменную aux, которая равна 0x10.

АдресИмя переменнойЗначение переменной
0x0100p10x0104──┐
0x0102p20x0106──│─┐
0x0104data10x32<─┘
0x0105data20xF1
0x0106aux0x10<───┘

Рис.4.1. Принцип действия указателей.

Язык C использует два оператора для работы с указателями: & и *. Оператор & возвращает адрес операнда, а оператор * возвращает значение, записанное по адресу операнда.

Пример 4.2. Работа с указателями

Unsignedchardata1,data2,aux
Unsignedchar*p1,*p2
main
{
p1=&data1;
p2=&aux;
Data1=0x32;
Aux0x10;
Data2=*p1;
};

Пример 4.2 иллюстрирует применение операторов & и *. Показано, как объявлять переменные типа указатели. Для этого необходимо поставить символ * перед именем переменной:

unsignedchar*pl,*p2;

Это объявление двух указателей на переменные типа char. Для МК HCS08 каждый указатель занимает 2 байта памяти, поскольку эти МК обладают 16-битной магистралью адреса. Именно поэтому переменная p2 на Рис.4.1 размещена по адресу 0x102, т.е. со смещением на две ячейки памяти относительно переменной p1

При использовании указателей для программирования МК HCS08 более активно начинают использоваться регистр H:X и индексный способ адресации. Так, простое присвоение: p1=&data1; сводится к следующему коду ассемблера:

LDHX#0x0104;ЗагрузитьвH:Xкодадресаdata1
STHX0x0100;Переслатьадресвячейкуp1

Еще одно свойство указателей — это их возможность участвовать в арифметических операциях. Однако для того, чтобы можно быть выполнять арифметические операции над объектами, на которые ссылаются указатели, необходимо учитывать тип объекта. Указатель типа char ссылается на однобайтную переменную, int — на двухбайтную переменную типа int и т.д.

Пример 4.3. Использование указателей в арифметических операциях

char*pl;//Указательнабайтпамяти
int*p2;//Указательна2-байтноеслово
long*p3;//Указательна4байтапамяти
//Представим,чтоначальныезначенияуказателейследующие:
//p1=0x0100,p2=0x0110иp3=0x0120
//Проведемарифметическиеоперации:
pl++;//p1=0x0101
p2++;//p2=0x0112(0x0110+2)
p3--;//p3=0x011C(0x0120-4)
//Можнопереприсваиватьзначенияуказателей:
p1=p2;//p1=p2=0x0112
//ещеодининтересныйпример:
p2=p2+3;//p2=0x0118(0x0112+(3*2))

Язык программирования C также поддерживает указатели на функции. Для этого указателю необходимо присвоить имя функции:

void*p4;
p4=main;//Указательp4ссылаетсянафункциюmain

Учтите, что указатели типа void не могут участвовать в операциях!

Указатели также могут ссылаться на другие указатели. Но, хотя язык С и поддерживает подобные действия, использовать эту возможность не стоит, поскольку такие конструкции программного кода существенно затрудняют поиск ошибок.

4.1.5.3. Массивы

Массив — это группа элементов одинакового типа. Язык C поддерживает как одномерные массивы (списки), так и многомерные массивы (матрицы) любого типа данных, включая указатели и иные комплексные типы данных.

Объявление массива имеет следующий формат:

<типданных><имямассива>[maxl][max2][maxN];

где:

  • maxl —- максимальное количество элементов в первом измерении;
  • max2 и maxN — количество элементов остальных измерений.

Примеры объявлений массивов:

unsignedcharexample1[10];//Объявлениемассивасименемexample
//Массиводномерный,состоитиз10элементов
//Массивзанимает10байтпамяти
longarray_A[10][10];//Объявлениемассиваarray_A
//Массивсостоитиз100элементов(10x10)
//Массивзанимает400байтпамяти
inttest[3][4][5];//Объявлениетрехмерногомассиваtest,
//состоящегоиз60элементов(3*4*5)
//изанимающего120байтпамяти

Массивы можно инициализировать в момент объявления:

//Одномерныймассив,состоящийиз5элементов:10,20,31,44,59
chartemp[5]={10,20,31,44,59};
//Одномерныймассивиз5символов:F,a,b,i,o
charname[5]={'F','a','b','i','o'};

Аналогично могут быть проинициализированы многомерные массивы:

intaux[2][3]=
{
1,2,3,
4,5,6
}

Для обращения к отдельному элементу массива необходимо ввести его номер:

x=temp[0];//x=10
y=temp[2];//y=31
z=temp[4];//z=59
xl=aux[0][2];//xl=3(1-ястрока,3-яколонка)
x2=aux[1][1];//x2=5(2-ястрока,2-яколонка)
x3=aux[1][0];//x3=4(2-ястрока,2-яколонка)

Нумерация элементов массива в языке C всегда начинается с 0. То есть номер последнего элемента массива на единицу меньше количества элементов!

Еще одна интересная особенность массивов — это адресация к элементам через указатель. На самом деле, использование имени массива без указания номера элемента и есть обращение к первому элементу массива. Это означает, что:

x=temp[0];//x=10;

то же самое, что и

x=*(temp);//x=10;

Так, можно обращаться к любому элементу массива, используя арифметические операции над указателем:

x=*(temp+2);//x=temp[2]=31

Наконец, существует особый тип массивов, который называется «строка». Строки — это массивы, предназначенные для хранения текстовой информации. Окончание текста отмечается нулевым элементом (символ 0x0). Строки объявляются точно так же, как и массивы с типом элементов char. Но при этом не забывайте оставлять лишний элемент для символа конца строки:

charbuffer[15]={"Thisisatest"};

Учтите, что конечный элемент автоматически добавляется компилятором при инициализации строки. Содержимое строки пишется в двойных кавычках.

4.1.5.4. Структуры

Возможности языка С позволяют объединить под одним именем переменные с разным форматом представления данных. Для этого используется понятие структуры.

Структуры — это составной объект, в который входят элементы любых типов. Элемент структуры называется полем структуры. Синтаксис объявления структуры:

struct<имяструктуры>
{
<тип><имяполя1>;
<тип><имяполя2>;
...
}<имяпеременной>;

Имя структуры можно не задавать, если сразу будут объявлены переменные, использующие эту структуру. Если переменные сразу названы не будут, то необходимо дать структуре имя. В качестве примера рассмотрим объявление структуры, состоящей из трех целочисленных полей: hours, minutes и seconds.

structstime
{
charhours;
charminutes;
charseconds;
}time;
...
...
//Теперьдавайтеобъявимпеременнуюalarm,использующуюструктуруstime:
structstimealarm;

Обращение к полям структуры происходит по имени переменной и имени поля, которые отделяются точкой. То есть, чтобы проинициализировать переменную time значением 8:45:30, нужно записать:

time.hours=8;
time.minutes=45;
time.seconds=30;

К элементам структуры можно также обращаться через указатель на структуру. В таком случае для адресации поля структуры используется символ «→»:

<указательструктуры>→<поле>=<значение>;

Рассмотрим пример обращения к полю ранее объявленной структуры stime с помощью указателя:

structstime*ptime;
...
...
ptime=&time;//ptime-указательнаструктуруtime
ptime→minutes=10;//Переменнойtime.minutesприсваиваетсязначение10

Язык программирования C поддерживает специальный тип структуры, который называется битовым полем. Это структуры, в которых размер полей может быть задан программистом. Битовые поля особенно удобны для доступа к определенному биту регистра или ячейки памяти с помощью переменной.

Объявление битового поля производится точно так же, как и стандартного поля структуры, но после имени поля необходимо поставить знак «:» и ввести количество битов для данного поля. Обращение к битовым полям производится так же, как и к обычным полям структур.

Приведем небольшой пример объявления и использования битовых полей:

structsbitfields{
charbit0:1;//Первоебитовоеполедлиной1бит
charfieldA:3;//Второебитовоеполедлиной3бита
charfieldB:8;//Третьебитовоеполедлиной8бит
intfieldC:12;//Четвертоебитовоеполедлиной12бит
}bfield;
bfield.bit0=1;
bfield.fieldC=5;

Учтите, что битовые поля типа char могут содержать не больше 8 бит, поля типа int — 16 бит, а поля типа long могут максимально содержать 32 бита!

Необходимо иметь в виду, что размер битовых полей ограничен. Если попытаться записать число, разрядность которого больше, чем ограничение битового поля, то записанное число будет усечено. Например:

bfield.fieldA=10;//10вдесятичнойсистеме=1010вдвоичной

В результате в поле fieldA запишется число 2, поскольку размер поля ограничен тремя битами, т.е. запишется число 010 в двоичной системе, что соответствует 2 в десятичной системе.

4.1.5.5. Объединения

Объединения — специальный тип данных, обычно применяемый для совместного использования памяти переменными разного типа.

Компилятор выделяет участок памяти, необходимый самому большому элементу объединения. Все остальные элементы адресуются в ту же область. Компилятор устройств семейства HCS08 использует модель хранения элементов объединения little endian.

При использовании модели little endian многобайтовые переменные хранятся, начиная со старшего байта, поэтому в старшем адресе хранится младший байт. А в модели big endian — наоборот: старший байт переменной хранится в старшем байте памяти.

Тип объединение может задаваться следующим образом:

union<имяобъединения>
{
<тип><имяполяl>;
<тип><имяполя2>;
...
}<имяпеременной>;

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

unionu32
{
unsignedlongintvar32;
unsignedintvarl6[2];
unsignedcharvar8[4];
}test;

Присвоение значения элементам объединения происходит аналогично:

test.var32=0x12345678;

Объединение test хранится в памяти так, как показано на Рис.4.2. Применение объединений бывает полезно для преобразования типов данных, а также для доступа к отдельным байтам переменной.

ПолеАдрес памяти
Начальный адресНачальный адрес + 1Начальный адрес + 2Начальный адрес + 3
Var320x120x340x560x78
Var16[0]0x120x34
Var16[1]0x560x78
Var8[0]0x12
Var8[1]0x34
Var8[2]0x56
Var8[3]0x78

Рис.4.2. Расположение полей объединения в памяти.

Следует иметь в виду, что объединения не могут быть параметрами функций!

4.1.5.6. Определение типов

Еще одна интересная особенность языка C — возможность определять существующие типы данных новыми именами. Это делается с помощью ключевого слова typedef.

Главная особенность указанной возможности — упрощение переноса программного обеспечения с одной платформы на другую. Предположим, нам необходимо перенести программу с платформы А на платформу Б. На платформе А переменные типа int — 16-битные, а на платформе Б — 32-битные.

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

//ПлатформаA(int-16-битныепеременные)
typedefunsignedintul6;//ul6—16-битныепеременные
typedefunsignedlongu32;//u32—32-битныепеременные
//ПлатформаБ(int-32-битныепеременные)
typedefunsignedshortul6;//u16—16-битныепеременные
typedefunsignedintu32;//u32—32-битныепеременные

Далее в программе, все 16- и 32-битные переменные должны быть объявлены только что созданными типами, т.е.

ul6varl6,test,aux;//Определение16-битнойпеременной
u32var32,test32,temp;//Определение32-битнойпеременной

Электронные компоненты Freescale >>>
Подробнее о компании Freescale >>>