Типы, определяемые пользователем
Глава 3. Расширенное представление данных
Массивы
Представление данных в виде отдельных переменных не всегда достаточно при программировании реальных задач. Например, для представления поведения сигнала во времени или хранения информации об изображении удобно использовать специальный тип данных – массивы. Одномерные массивы можно ассоциировать с компонентами вектора, а двумерные – с матрицами. В общем случае массив – это набор элементов данных одного типа, для объявления которого используется следующий синтаксис:
<тип данных> <имя массива>[число элементов];
Например,
int array_int[100]; //одномерный массив 100 целочисленных элементов
double array_d[25]; //одномерный массив 25 вещественных элементов
Как видно из примеров, объявление массивов отличается от объявления обычных переменных наличием квадратных скобок []. Также имена массивов выбираются по тем же правилам, что и имена переменных. Обращение к отдельному элементу массива осуществляется по номеру его индекса. Следующий фрагмент программы демонстрирует запись в массив значений линейной функции f(x)=kx+b и вывода значений на экран:
double k=0.5, b = 10.0;
double f[100];
for(int x=0;i < 100;i++)
{
f[i] = k*x+b;
printf(“%.2f ”,f[i]);
}
В языке С предусмотрена возможность инициализации массива в момент его объявления, например, таким образом
int powers[4] = {1, 2, 4, 6};
В этом случае элементу powers[0] присваивается значение 1, powers[1] – 2, и т.д. Особенностью инициализации массивов является то, что их размер можно задавать только константами, а не переменными. Например, следующая программа приведет к ошибке при компиляции:
int N=100;
float array_f[N]; //ошибка, так нельзя
Поэтому при объявлении массивов обычно используют такой подход:
#include
#define N 100
int main()
{
float array_f[N];
return 0;
}
Следует отметить, что при инициализации массивов число их элементов должно совпадать с его размерностью. Рассмотрим вариант, когда число элементов при инициализации будет меньше размерности массива.
#define SIZE 4
int data[SIZE]={512, 1024};
for(int i = 0;i < SIZE;i++)
printf(“%d, \n”,data[i]);
Результат работы программы будет следующим:
512, 1024, 0, 0
Из полученного результата видно, что неинициализированные элементы массива принимаются равными нулю. В случаях, когда число элементов при инициализации превышает размерность массива, то при компиляции произойдет ошибка. Поэтому, когда наперед неизвестно число элементов, целесообразно использовать такую конструкцию языка С:
int data[] = {2, 16, 32, 64, 128, 256};
В результате инициализируется одномерный массив размерностью 6 элементов. Здесь остается последний вопрос: что будет, если значение индекса при обращении к элементу массива превысит его размерность? В этом случае ни программа, ни компилятор не выдадут значение об ошибке, но при этом в программе могут возникать непредвиденные ошибки. Поэтому программисту следует обращать особое внимание на то, чтобы индексы при обращении к элементам массива не выходили за его пределы. Также следует отметить, что первый элемент массива всегда имеет индекс 0, второй – 1, и т.д.
Для хранения некоторых видов информации, например, изображений удобно пользоваться двумерными массивами. Объявление двумерных массивов осуществляется следующим образом:
int array2D[100][20]; //двумерный массив 100х20 элементов
Нумерация элементов также начинается с нуля, т.е. array2D[0][0] соответствует первому элементу, array2D[0][1] – элементу первой строки, второго столбца и т.д. Для начальной инициализации двумерного массива может использоваться следующая конструкция:
long array2D[3][2] = {{1, 2}, {3, 4}, {5, 6}};
или
long array2D[][] = {{1, 2}, {3, 4}, {5, 6}};
В общем случае можно задать массив любой размерности и правила работы с ними аналогичны правилам работы с одномерными и двумерными массивами.
Работа со строками
В языке С нет специального типа данных для строковых переменных. Для этих целей используются массивы символов (тип char). Следующий пример демонстрирует использование строк в программе:
char str_1[100] = {‘П’,’р’,’и’,’в’,’е’,’т’,’\0’};
char str_2[100] = “Привет”;
char str_3[] = “Привет”;
printf(“%s\n%s\n%s\n”,str_1,str_2,str_3);
В приведенном примере показаны три способа инициализации строковых переменных. Первый способ является классическим объявлением массива, второй и третий используются специально для строк. Причем в последнем случае, компилятор сам определяет нужную длину массива для записи строки. Анализируя первый и второй способы инициализации массива символов возникает вопрос: каким образом язык С «знает» где заканчивается строка? Действительно, массив str_2 содержит 100 элементов, а массив str_3 меньше 100, тем не менее длина строки и в первом и во втором случаях одна и та же. Такой эффект достигается за счет использования специальных управляющих кодов, которые говорят где заканчивается строка или где используется перенос внутри одной строки и т.п. В частности символ ‘\0’ означает в языке С конец строки и все символы после него игнорируются как символы строки. Следующий пример показывает особенность использования данного специального символа.
char str1[10] = {‘Л’,’е’,’к’,’ц’,’и’,’я’,’\0’};
char str2[10] = {‘Л’,’е’,’к’,’ц’, ’\0’,’и’,’я’ };
char str3[10] = {‘Л’,’е’, ’\0’,’к’,’ц’,’и’,’я’ };
printf(“%s\n%s\n%s\n”,str1,str2,str3);
Результатом работы данного кода будет вывод следующих трех строк:
Лекция
Лекц
Ле
Из этого примера видно как символ конца строки ‘\0’ влияет на длину строк. Таким образом, чтобы подсчитать длину строки (число символов) необходимо считать символы до тех пор, пока не встретится символ ‘\0’ или не будет достигнут конец массива.
Учитывая, что тип char также представляет собой один байт, то данная функция даст размер массива. После этого инициализируется счетчик символов length и выполняется цикл while с очевидными условиями. В результате переменная length будет содержать число символов в строке, либо размер массива. Подобная функция вычисления размера строк уже реализована в стандартной библиотеке языка С string.h со следующим синтаксисом:
int strlen(char* str);
где char* str – указатель на строку (об указателях речь пойдет ниже). Следующая программа показывает правило использования функции strlen().
Теперь рассмотрим правила присваивания одной строковой переменной другой. Допустим объявлены две строки
char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
и необходимо выполнить оператор присваивания
str1 = str2;
При такой записи оператора присваивания компилятор выдаст сообщение об ошибке. Для того чтобы выполнить копирование необходимо перебирать по порядку элементы одного массива и присваивать их другому массиву. Это демонстрируется на следующем примере:
char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
int size_array = sizeof(str1);
int i=0;
while(i < size_array && str1[i] != ‘\0’) {
str2[i] = str1[i];
i++;
}
str2[i] = ‘\0’;
В приведенном фрагменте программы выполняется перебор элементов массива str1 с помощью цикла while и значение i-го элемента записывается в массив str2. Данная операция выполняется до тех пор, пока либо не будет достигнут конец массива, либо не встретится символ конца строки ‘\0’. Затем, после выполнения цикла, в конец массива str2 записывается символ ‘\0’. Таким образом, выполняется копирование одной строки в другую. Подобная функция также реализована в библиотеке языка С string.h и имеет следующее определение:
char* strcpy(char* dest, char* src);
Она выполняет копирование строки src в строку dest и возвращает строку dest.
Кроме операций вычисления длины строки и копирования строк важной является операция сравнения двух строк между собой. В языке С две строки считаются одинаковыми, если равны их длины и элементы одной строки равны соответствующим элементам другой. Следующий алгоритм выполняет такое сравнение:
char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
int length = strlen(str1);
int length2 = strlen(str2);
if(length != length2) {
printf(“Срока %s не равна строке %s\n”,str1,str2);
return 0;
}
for(int i=0;i < length;i++)
if(str1[i] != str2[i]) {
printf(“Срока %s не равна строке %s\n”,str1,str2);
break;
}
}
if(i == length)
printf(“Срока %s равна строке %s\n”,str1,str2);
Приведенный пример показывает возможность досрочного завершения программы путем использования оператора return. Данный оператор вызывается, если длины строк не совпадают. Также реализуется цикл, в котором сравниваются элементы массивов str1 и str2. Если хотя бы один элемент не будет совпадать, то на экран выведется сообщение «Строка … не равна строке…» и цикл завершится с помощью оператора break. В противном случае (равенства всех элементов) переменная i будет равна переменной length и тогда на экран выводится сообщение о совпадении строк. Подобный алгоритм сравнения двух строк реализован в функции
int strcmp(char* str1, char* str2);
библиотеки . Данная функция возвращает нуль, если строки str1 и str2 равны и не нуль в противном случае. Приведем пример использования данной функции.
char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
if(strcmp(str1,str2) == 0) printf(“Срока %s равна строке %s\n”,str1,str2);
else printf(“Срока %s не равна строке %s\n”,str1,str2);
В языке С имеется несколько функций, позволяющих вводить строки с клавиатуры. Самой распространенной из них является ранее рассмотренная функция scanf(), которой в качестве параметра передается ссылка на массив символов:
char str[100];
scanf(“%s”,str);
В результате выполнения этого кода, переменная str будет содержать введенную пользователем последовательность символов. Кроме функции scanf() также часто используют функцию gets() библиотеки stdio.h, которая в качестве аргумента принимает ссылку на массив символов:
gest(str);
Данная функция считывает символы до тех пор, пока пользователь не нажмет клавишу Enter, т.е. введет символ перевода строки ‘\n’. Затем она записывает вместо символа ‘\n’ символ ‘\0’ и передает строку вызывающей программе.
Для вывода строк на экран помимо функции printf() можно использовать также функцию puts() библиотеки stdio.h, которая более проста в использовании. Следующий пример демонстрирует применение данной функции.
#define DEF “Заданная строка”
char str[] = “Это первая строка”;
puts(str);
puts(DEF);
puts(&str[4]);
Результат работы следующий:
Это первая строка
Заданная строка
первая строка
Как видно из полученных результатов, функция puts() автоматически переводит курсор на следующую строку и последующие строки выводятся с новой строки. Кроме того, строки можно задавать с помощью директивы #define и выводить их с помощью функции puts. Также можно менять начало вывода строки путем изменения адреса ее начала.
Еще одной удобной функцией работы со строками является функция sprintf() библиотеки stdio.h. Ее действие аналогично рассмотренной ранее функции printf() с той лишь разницей, что результат вывода заносится в строковую переменную, а не на экран:
int age;
char name[100], str[100];
printf(“Введите Ваше имя: ”);
scanf(“%s”,name);
printf(“Введите Ваш возраст: ”);
scanf(“%d”,&age);
sprintf(str,”Здраствуйте %s. Ваш возраст %d лет”,name,age);
puts(str);
В результате массив str будет содержать строку «Здраствуйте … Ваш возраст…».
Анализ последнего примера показывает, что с помощью функции sprintf() можно преобразовывать числовые переменные в строковые, объединять несколько строк в одну и т.п. Вместе с тем библиотека содержит специальные функции по преобразованию строк в цифры. Дело в том, что строка «100» и цифра 100 в памяти компьютера представляются по-разному. Строка «100» - это последовательность трех символов ‘1’,’0’,’0’, а число 100 – это значение, которое может быть представлено в виде одного байта или храниться в переменной типа int. При программировании часто бывает необходимо выполнять преобразование строк в числа. Это осуществляется с помощью функций atoi(), atol(), atof().
Обработка элементов массива
В практике программирования существуют устоявшиеся подходы к удалению, добавлению и сортировке элементов массива. Рассмотрим сначала алгоритм добавления элемента в массив. Допустим, что имеется строка
char str[100] = “Я не пропустил ни одной лекции по информатике.”;
в которой необходимо добавить 17-й символ пробел. При добавлении нового элемента в массив уже имеющиеся элементы всегда сдвигаются вправо относительно добавляемого элемента. Это связано с тем, что при сдвиге вправо, номер первого элемента остается неизменным и равен 0. Если бы сдвиг осуществлялся влево, то номер первого элемента массива становился бы отрицательным, что недопустимо для языка С. Таким образом, для добавления 17-го элемента нужно выполнить сдвиг вправо элементов, находящихся после 17-го символа и на 17 позицию записать символ пробела. Для реализации данного алгоритма можно воспользоваться циклом for, в котором счетчик цикла меняется, начиная с последнего символа строки до номера вставляемого элемента, в данном случае 17-го. В результате получаем следующий алгоритм:
int size = strlen(str);
for(int i = size;i >= 17;i--)
str[i+1] = str[i];
str[17] = ‘ ‘;
Здесь функция strlen() возвращает номер символа конца строки ‘\0’ поэтому при сдвиге символов будет сдвинут и этот специальный символ, который необходим для корректного представления строк. Цикл for инициализирует переменную i величиной size, которая будет указывать на символ ‘\0’ в строке str. Данный цикл выполняется до тех пор, пока величина i не будет меньше 17 и уменьшается на единицу на каждом шаге его работы. Непосредственно сдвиг элементов массива выполняется внутри цикла for, где i+1 элементу присваивается значение i-го элемента. Наконец, после цикла 17-му элементу строки присваивается символ пробела. Подобный алгоритм добавления нового элемента остается справедливым для любых типов одномерных массивов.
При удалении элемента из массива используется операция сдвига элементов влево относительно удаляемого. Воспользуемся тем же примером строки и поставим задачу удаления 4 пробела (нумерация начинается с нуля). Для этого достаточно все элементы, имеющие индексы больше 4, сместить на один символ влево. Это также можно реализовать с помощью цикла for, в котором счетчик цикла будет меняться от 5 и до символа конца строки. В результате получим следующий алгоритм:
int size = strlen(str);
for(int i = 5;i <= size;i++)
str[i-1] = str[i];
Непосредственное смещение элементов выполняется в теле цикла for, в котором i-1 элементу присваивается значение i-го элемента и после этого значение i увеличивается на единицу. Цикл выполняется до тех пор, пока все символы строки, начиная с 5-го, не будут смещены на единицу влево, включая и символ конца строки.
Наконец, рассмотрим алгоритм упорядочивания элементов массива по возрастанию и убыванию. Существует много разных методов упорядочения элементов. Рассмотрим наиболее простой и распространенный известный под названием «метод всплывающего пузырька». Идея метода заключается в переборе всех элементов массива, начиная с первого (нулевого) и поиска наибольшего (или наименьшего) значения. Найденное значение ставится на место первого элемента, а первый элемент на место найденного. Данная процедура повторяется, начиная уже со второго элемента массива где также находится наибольшее (или наименьшее) значение элемента из оставшихся. Затем, выполняется замена: найденный элемент перемещается на место второго, а второй на место найденного. После такой перестановки всех элементов, исключая последний, массив будет упорядочен по убыванию (или возрастанию). Описанный алгоритм сортироваки упорядочивания по убыванию значений элементов приведен ниже.
int array[10] = {2,5,3,7,9,10,4,6,1,8};
for(int i = 0;i < 9;i++)
{
int max = array[i];
int pos_max = i;
for(int j = i+1;j < 10;j++)
if(max < array[j]) {
max = array[j];
pos_max = j;
}
int temp = array[i];
array[i] = array[pos_max];
array[pos_max] = temp;
}
В данном алгоритме задается цикл, в котором последовательно просматриваются элементы массива array. Внутри цикла инициализируется переменная max, в которую записывается максимальное найденное значение, и переменная pos_max, в которой хранится значение индекса найденного максимального элемента. Затем, реализуется вложенный цикл for, в котором выполняется перебор элементов массива, начиная с i+1, и заканчивая последним 10. В теле вложенного цикла выполняется проверка для поиска максимального элемента, и если текущий элемент считается таковым, то значения переменных max и pos_max меняются. После вложенного цикла осуществляется перестановка элементов массива и процесс повторяется.
Аналогичным образом реализуется алгоритм упорядочения по возрастанию, с той лишь разницей, что на каждом шаге работы алгоритма выполняется поиск не максимального, а минимального значения элемента:
for(int i = 0;i < 9;i++)
{
int min = array[i];
int pos_min = i;
for(int j = i+1;j < 10;j++)
if(min > array[j]) {
min = array[j];
pos_min = j;
}
int temp = array[i];
array[i] = array[pos_min];
array[pos_min] = temp;
}
Структуры
При разработке программ важным является выбор эффективного способа представления данных. Во многих случаях недостаточно объявить простую переменную или массив, а нужна более гибкая форма представления данных. Таким элементом может быть структура, которая позволяет включать в себя разные типы данных, а также другие структуры. Приведем пример, в котором использование структуры позволяет эффективно представить данные. Таким примером будет инвентарный перечень книг, в котором для каждой книги необходимо указывать ее наименование, автора и год издания. Причем количество книг может быть разным, но будем полгать, что не более 100. Для хранения информации об одной книге целесообразно использовать структуру, которая задается в языке С с помощью ключевого слова struct, за которым следует ее имя. Само определение структуры, т.е. то, что она будет содержать, записывается в фигурных скобках {}. В данном случае структура будет иметь следующий вид:
struct book {
char title[100]; //наименование книги
char author[100]; //автор
int year; //год издания
};
Такая конструкция задает своего рода шаблон представления данных, но не сам объект, которым можно было бы оперировать подобно переменной или массиву. Для того чтобы объявить переменную для структуры с именем book используется такая запись:
struct book lib; //объявляется переменная типа book
После объявления переменной lib имеется возможность работать со структурой как с единым объектом данных, который имеет три поля: title, author и year. Обращение к тому или иному полю структуры осуществляется через точку: lib.title, lib.author и lib.year. Таким образом, для записи в структуру информации можно использовать следующий фрагмент программы:
printf(“Введите наименование книги: “);
scanf(“%s”,lib.title);
printf(“Введите автора книги: “);
scanf(“%s”,lib.author);
printf(“Введите год издания книги: “);
scanf(“%d”,&lib.year);
После этого в соответствующие поля будет записана введенная с клавиатуры информация и хранится в единой переменной lib. Однако по условиям задачи необходимо осуществлять запись не по одной, а по 100 книгам. В этом случае целесообразно использовать массив структур типа book, который можно задать следующим образом:
struct book lib[100];
Структуры можно автоматически инициализировать при их объявлении подобно массивам, используя следующий синтаксис:
struct bool lib = {
“Евгений Онегин”,
“Пушкин А.С.”,
1995
};
При выполнении данного фрагмента программы в переменные структуры title, author и year будет записана соответственно информация: “Евгений Онегин”, “Пушкин А.С.”, 1995. Здесь следует обратить внимание, что последовательность данных при инициализации должна соответствовать последовательности полей в структуре. Это накладывает определенные ограничения, т.к. при инициализации необходимо помнить последовательность полей в структуре. Стандарт C99 допускает более гибкий механизм инициализации полей структуры:
struct book lib = {.year = 1995,
.author = “Пушкин А.С.”,
.title = “Евгений Онегин” };
или
struct book lib = { .year = 1995,
.title = “Евгений Онегин” };
или
struct book lib = {.author = “Пушкин А.С.”,
.title = “Евгений Онегин”,
1995 };
В первом и во втором примерах при инициализации указываются наименования полей через точку. При этом их порядок и число не имеет значения. В третьем примере первые два поля указаны через имена, а последнее инициализируется по порядковому номеру – третьему, который соответствует полю year.
В некоторых случаях имеет смысл создавать структуры, которые содержат в себе другие (вложенные) структуры. Например, при создании простого банка данных о сотрудниках предприятия целесообразно ввести, по крайней мере, две структуры. Одна из них будет содержать информацию о фамилии, имени и отчестве сотрудника, а вторая будет включать в себя первую с добавлением полей о профессии и возрасте:
struct tag_fio {
char last[100];
char first[100];
char otch[100];
};
struct tag_people {
struct tag_fio fio; //вложенная структура
char job[100];
int old;
};
Структуры, как и обычные типы данных, можно передавать функции в качестве аргумента.
Функциям в качестве аргумента можно также передавать массивы структур. Для этого используется следующее определение:
void show_struct(struct people mans[], int size);
Здесь size – число элементов массива, которое необходимо для корректного считывания информации массива mans.
Битовые поля
Стандарт C99, который часто является основой языка С, позволяет описывать данные на уровне битов. Это достигается путем использования битовых полей, представляющие собой переменные типов signed или unsigned int, у которых используются лишь несколько бит для хранения данных. Такие переменные обычно записываются в структуру и единую последовательность бит. Рассмотрим пример, в котором задается структура flags, внутри которой задано 8 битовых полей:
struct {
unsigned int first : 1;
unsigned int second : 1;
unsigned int third : 1;
unsigned int forth : 1;
unsigned int fifth : 1;
unsigned int sixth : 1;
unsigned int sevnth : 1;
unsigned int eighth : 1;
} flags;
Теперь, для определения того или иного бита переменной flags достаточно воспользоваться операцией
flags.first = 1;
flags.third = 1;
В этом случае будут установлены первый и третий биты, а остальные равны нулю, что соответствует числу 5. Данное значение можно отобразить, воспользовавшись функцией printf():
printf(“flags = %d.\n”,flags);
но переменной flags нельзя присваивать значения как обычной переменной, т.е. следующий программный код будет неверным:
flags = 5; //неверно, так нельзя
Также нельзя присваивать значение flags переменным, например, следующая запись приведет к сообщению об ошибке:
int var = flags; //ошибка, структуру нельзя присваивать переменной
Так как поля first,…, eighth могут содержать только один бит информации, то они принимают значения 0 или 1 для типа unsigned int и 0 и -1 - для типа signed int. Если полю присваивается значение за пределами этого диапазона, то оно выбирает первый бит из присваиваемого числа.
В общем случае можно задавать любое число бит для описания полей, например
struct {
unsigned int code1 : 2;
unsigned int code2 : 2;
unsigned int code3 : 8;
} prcode;
Здесь создается два двух битовых поля и одно восьмибитовое. В результате возможны следующие операции присваивания:
prcode.code1 = 0;
prcode.code2 = 3;
prcode.code3 = 128;
Структуры flags и prcode удобно использовать в условных операторах if и switch. Рассмотрим пример использования структуры битовых полей для описания свойств окна пользовательского интерфейса.
struct tag_window {
unsigned int show : 1 //показать или скрыть
unsigned int style : 3 //WS_BORDER, WS_CAPTION, WS_DLGFRAME
unsigned int color : 3 //RED, GREEN, BLUE
} window;
Определим следующие константы:
#define WS_BORDER 1 //001
#define WS_CAPTION 2 //010
#define WS_DLGFRAME 4 //100
#define RED 1
#define GREEN 2
#define BLUE 4
#define SW_SHOW 0
#define SW_HIDE 1
Пример инициализации структуры битовых полей
window.show = SW_SHOW;
window.style = WS_BORDER | WS_CAPTION;
window.color = RED | GREEN | BLUE; //белый цвет
Объединения
Еще одним важным типом представления данных являются объединения. Это тип данных, который позволяет хранить различные типы данных в одной и той же области памяти (начиная с одного и того же адреса в памяти). Объединение задается с помощью ключевого слова union подобно структуре. Покажем особенность применения объединений на примере хранения данных, которые могут быть и вещественными и целыми или представлять собой символ. Так как наперед неизвестно какой тип данных требуется сохранять, то в объединении необходимо задать три поля:
union tag_value {
int var_i;
double var_f;
char var_ch;
};
Данное объединение будет занимать в памяти 8 байт, ровно столько, сколько занимает переменная самого большого объема, в данном случае var_f типа double. Все остальные переменные var_i и var_ch будут находиться в той же области памяти, что и переменная var_f. Благодаря такому подходу происходит экономия памяти, но платой за это является возможность хранения значения только одной переменной из трех в определенный момент времени. Как видно из постановки задачи такое ограничение не будет влиять на работоспособность программы. Если бы вместо объединения использовалась структура tag_value, то можно было бы сохранять значения всех трех переменных одновременно, но при этом требовалось бы больше памяти.
Для того чтобы программа «знала» какой тип переменной содержит объединение tag_value, необходимо ввести переменную, значение которой будет указывать номер используемой переменной: 0 – var_i, 1 – var_f и 2 – var_ch. Причем эту переменную удобно представить с объединением в виде структуры следующим образом:
struct tag_var {
union tag_value value;
short type_var;
};
Таким образом, получаем следующий алгоритм записи разнородной информации в объединение tag_value.
Перечисляемые типы
В предыдущем примере была использована переменная type_var, которая указывала номер используемой переменной, что не особенно удобно при написании сложных программ, где запомнить номер той или иной переменной структуры довольно сложно. Проще если переменную type_var инициализировать специальными словами, например,
VT_NONE – тип переменной не определен;
VT_INT – целочисленный тип;
VT_FLOAT – вещественный тип;
VT_CHAR – символьный тип.
Этого можно достичь, если type_var определить как перечисляемый тип, который задается с помощью ключевого слова enum следующим образом:
enum tag_type {VT_NONE, VT_INT, VT_FLOAT, VT_CHAR};
При этом объявление перечисляемого типа выполняется подобно объявлению структуры или объединения:
enum tag_type type_var;
В результате для введенной переменной перечисляемого типа допустимо использование следующих операторов:
type_var = VT_INT; //переменная type_var принимает значение VT_INT
if(type_var == VT_NONE) // проверка
for(type_var = VT_NONE; type_var <= VT_CHAR; type_var++) //цикл
Анализ последнего оператора for показывает, что значения перечисляемого типа VT_NONE,…,VT_CHAR являются числами, которые имеют свои имена. Причем, VT_NONE = 0, VT_INT = 1, …, VT_CHAR = 3. В некоторых случаях использование имен удобнее использования цифр, т.к. их запоминать и ориентироваться в них проще, чем в числах.
Здесь следует отметить, что если объявлены два перечисления
enum color {red, green, blue} clr;
enum color_type {clr_red, clr_green, clr_blue} type;
то числовые значения red, green, blue будут совпадать с соответствующими числовыми значениями vt_int, vt_float, vt_char. Это значит, что программный код if(clr == red) будет работать также как и if(clr == clr_red). Часто это не имеет принципиального значения, но, например, следующий оператор выдаст сообщение об ошибке:
switch(clr)
{
case red:printf(“Красный цвет\n”);break;
case green:printf(“Зеленый цвет\n”);break;
case clr_red:printf(“Красный оттенок\n”);break;
};
Ошибка возникнет из-за того, что значение red и значение clr_red равны одному числу – 0, а оператор switch не допускает такой ситуации. Для того чтобы избежать такой ситуации, при задании перечисляемого типа допускаются следующие варианты:
enum color_type {clr_red = 10, clr_green, clr_blue};
enum color_type {clr_red = 10, clr_green = 20, clr_blue = 30};
enum color_type {clr_red, clr_green = 20, clr_blue};
В первом случае значения clr_red = 10, clr_green = 11, clr_blue = 12. Во втором - clr_red = 10, clr_green = 20, clr_blue = 30. В третьем - clr_red = 0, clr_green = 20, clr_blue = 21. Как видно из полученных значений, величины могут инициализироваться при задании перечисления, а если они не объявлены, то принимают значение на единицу больше предыдущего значения.
Типы, определяемые пользователем
Язык С допускает создание собственных типов данных на основе базовых, таких как int, float, struct, union, enum и др. Для этого используется ключевое слово typedef, за которым следует описание типа и его имя.
Рассмотрим действие оператора typedef на примере создания пользовательского типа с именем BYTE для объявления байтовых переменных, т.е. переменных, значения которых меняются в диапазоне от 0 до 255, и которые занимают один байт в памяти ЭВМ:
typedef unsigned char BYTE;
Здесь unsigned char – пользовательский тип; BYTE – имя введенного типа. После такого объявления слово BYTE можно использовать для определения переменных в программе:
BYTE var_byte;
Создание имени для существующего типа может показаться нецелесообразным, но иногда это имеет смысл. Так, применение оператора typedef повышает степень переносимости программного кода с одной платформы на другую. Например, тип, возвращаемый оператором sizeof, определен как size_t. Это связано с тем, что в разных реализациях языка С size_t определен или как unsigned int или как unsigned long для лучшей адаптации к той или иной операционной системы. Таким образом, составленный текст программы достаточно откомпилировать на соответствующей платформе и оператор sizeof автоматически «подстроится» под нее без переделки самой программы.
Кроме объявлений простых пользовательских типов оператор typedef можно использовать и при объявлении новых типов на основе структур. Например, удобно ввести тип COMPLEX для объявления переменных комплексных чисел. Для этого можно воспользоваться следующим кодом:
typedef struct complex {
float real;
float imag;
} COMPLEX;
и работать с комплексными числами
COMPLEX var_cmp1, var_cmp2, var_cmp3;
var_cmp1.real = 10;
var_cmp1.imag = 5.5;
var_cmp2.real = 6.3;
var_cmp2.imag = 2.5;
var_cmp3.real = var_cmp1.real + var_cmp2.real;
var_cmp3.imag= var_cmp1.imag + var_cmp2.imag;
Ключевое слово typedef можно использовать с любыми стандартными типами данных и типами объявленными ранее.
Контрольные вопросы и задания
1. Каким образом задаются массивы в языке С?
2. Запишите массив целых чисел с начальными значениями 1, 2 и 3.
3. Сформулируйте идею алгоритма упорядочивания элементов массива по возрастанию (убыванию).
4. Как задаются строки в программе на С?
5. Для чего предназначена функция strcpy() и в какой библиотеке она определена?
6. Запишите возможные способы начальной инициализации строки.
7. Какой управляющий символ соответствует концу строки?
8. Что выполняет функция strcmp()?
9. Какую роль играют структуры в программировании?
10. Что возвращает функция strlen()?
11. Запишите структуру для хранения имени, возраста и места работы сотрудника.
12. Как задаются переменные на структуры?
13. Чем объединения отличаются от структур?
14. Задайте объединение для хранения целых, вещественных чисел и символов.
15. Как задаются перечисления в языке С?
16. Для чего предназначена функция sprintf()?
17. Создайте свой тип данных для представления беззнаковых целых (unsigned int) чисел.
18. Задайте структуру с битовыми полями для хранения шести свойств окна OS Windows.
19. Напишите программу для преобразования малых букв в строке в большие.
20. Опишите перечисления для оперирования константами TOP, BOTTOM, LEFT и RIGTH.
Дата добавления: 2022-01-22; просмотров: 23; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!
