Главная страница

Шпаргалка по языку СИ. Конспект Лекций «программирование На Языке Высокого Уровня Си» П. Конспект лекций для студентов высших учебных заведений, обучающихся по специальности 230102. 65 Автоматизированные системы обработки информации и управления


Скачать 1.25 Mb.
НазваниеКонспект лекций для студентов высших учебных заведений, обучающихся по специальности 230102. 65 Автоматизированные системы обработки информации и управления
АнкорШпаргалка по языку СИ
Дата26.05.2022
Размер1.25 Mb.
Формат файлаpdf
Имя файлаКонспект Лекций «программирование На Языке Высокого Уровня Си» П.pdf
ТипКонспект лекций
#550544
страница4 из 15
1   2   3   4   5   6   7   8   9   ...   15
Тема 5. Стандартные типы данных
Данные – это объекты, которые обрабатываются в программе.
В языке Си есть два вида данных: константы и переменные.
Константа – данное, которое не может меняться в процессе работы программы. Тип и значение константы однозначно определяются её изображением в программе. Константы в программе записываются: в виде целых или вещественных чисел; в виде символьных констант; в виде строковых констант; в виде стандартных имен констант; в виде определённых в программе имён констант.
Переменная – данное, которое может меняться в процессе выполнения программы, например путём присваивания ей значения выражения. В тексте программы переменные обозначаются с помощью имён.
64

В программе на языке Си для каждого данного должен быть определён его тип. Тип данного определяет возможные значения данного и допустимые операции, которые можно применять к этим значениям.
В языке Си различают понятия "тип данных" и "модификатор типа". Тип данных - это, например, целый, а модификатор - со знаком или без знака. Целое со знаком будет иметь как положительные, так и отрицательные значения, а целое без знака - только положительные значения. В языке Си можно выделить пять базовых типов, которые задаются следующими ключевыми словами:

char - символьный;

int - целый;

float - вещественный;

double - вещественный двойной точности;

void - не имеющий значения.
Дадим им краткую характеристику:
1.
Переменная типа char имеет размер 1 байт, ее значениями являются различные символы из кодовой таблицы, например: 'ф', ':', 'j' (при записи в программе они заключаются в одинарные кавычки).
2.
Размер переменной типа int в стандарте языка Си не определен. В большинстве систем программирования размер переменной типа int соответствует размеру целого машинного слова. Например, в компиляторах для
16-разрядных процессоров переменная типа int имеет размер 2 байта. В этом случае знаковые значения этой переменной могут лежать в диапазоне от -32768 до 32767.
3.
Ключевое слово float позволяет определить переменные вещественного типа. Их значения имеют дробную часть, отделяемую точкой, например: -5.6, 31.28 и т.п. Вещественные числа могут быть записаны также в форме с плавающей точкой, например: -1.09e+4. Число перед символом "е" называется мантиссой, а после "е" - порядком. Переменная типа float занимает в памяти 32 бита. Она может принимать значения в диапазоне от 3.4е-38 до
3.4e+38.
65

4.
Ключевое слово double позволяет определить вещественную переменную двойной точности. Она занимает в памяти в два раза больше места, чем переменная типа float (т.е. ее размер 64 бита). Переменная типа double может принимать значения в диапазоне от 1.7e-308 до 1.7e+308.
5.
Ключевое слово void (не имеющий значения) используется для нейтрализации значения объекта, например, для объявления функции, не возвращающей никаких значений.
Объект некоторого базового типа может быть модифицирован. С этой целью используются специальные ключевые слова, называемые модификаторами. В стандарте ANSI языка Си имеются следующие модификаторы типа:

unsigned

signed

short

long
Модификаторы записываются перед спецификаторами типа, например: unsigned char. Если после модификатора опущен спецификатор, то компилятор предполагает, что этим спецификатором является int. Таким образом, следующие строки:
long а;
long int а;
являются идентичными и определяют объект а как длинный целый. Табл. 7 иллюстрирует возможные сочетания модификаторов (unsigned, signed, short, long) со спецификаторами (char, int, float и double), а также показывает размер и диапазон значений объекта (для 16-разрядных компиляторов).
Таблица 7.
Типы данных языка Си
Тип
Размер в
байтах
(битах)
Диапазон значений
Знаковый
(может ли хранить
отрицательные числа)
char
1 (8)
от -128 до 127
Да
unsigned char
1 (8)
От 0 до 255
Нет
signed char
1 (8)
от -128 до 127
Да
int
2 (16)
от -32768 до 32767
Да
unsigned int
2 (16)
от 0 до 65535
Нет
66

signed int
2 (16)
от -32768 до 32767
Да
short int
2 (16)
от -32768 до 32767
Да
unsigned short int
2 (16)
от 0 до 65535
Нет
signed short int
2 (16)
от -32768 до 32767
Да
long int
4 (32)
от -2147483648 до 2147483647
Да
unsigned long int
4 (32)
от 0 до 4294967295
Нет
signed long int
4 (32)
от -2147483648 до 2147483647
Да
float
4 (32)
от 3.4е-38 до 3.4е+38
Нет
double
8 (64)
от 1.7-308 до 1.7+308
Нет
long double
10 (80)
от 3.4Е-4932 до 3.4Е+4932
Нет
Константы вещественного типа записываются в двух формах: в виде вещественных дробных чисел без десятичного порядка; в виде вещественных чисел с десятичным порядком. Вещественные дробные константы без десятичного порядка представляются синтаксической диаграммой (рис.25).
Где «Цифра1» – цифра (0,1,…, 9) целой части числа;
«Цифра2» – цифра дробной части числа.
Пример 12. Вещественные константы без десятичного порядка на языке
Паскаль: –2.45, +33.668, 0.04, 0.0453.
Вещественное число с десятичным порядком представляется диаграммой
(рис.26):
Цифра1
Цифра2
.

+
Цифра1
Цифра2
.

+
E
Цифра3

+
67
Рис. 25. СД представления вещественного числа без десятичного порядка
Рис. 26. СД представления вещественного числа с десятичным порядком

Здесь «Цифра1» – цифры целой части мантиссы;
«Цифра2» – цифры дробной части мантиссы;
Е – признак десятичного порядка;
«Цифра3» – цифры десятичного порядка.
Приведем примеры вещественных чисел с десятичным порядком:
1. Вещественное число 45,3·10
–3
может представляться на языке Си в виде вещественной константы с десятичным порядком следующим образом: +45.3E–
3 или 4.53E–2 или 0.453E–1.
2. Вещественное число –45·10 4 может быть представлено на языке Си в виде вещественной константы с десятичным порядком следующим образом:
–45E4 или –4.5E5 или –0.45E+6.
Количество значащих цифр в вещественной константе и величина порядка зависят от разновидности вещественного числа и определяются в соответствии с приведенной ранее таблицей.
Тема 6. Составные типы данных
Данные регулярного типа (массивы)
Массивом называется структура данных, позволяющая хранить под одним именем совокупность (последовательность) данных (переменных) любого, но только одного какого–то типа. Массив характеризуется размерностью, своим именем, базовым типом хранимых элементов, размером и способом нумерации элементов.
Имя массива – общее имя переменных, входящих в массив.
Базовый тип массива – тип переменных, входящих в массив.
Элементы массива – переменные, входящие в массив.
В тексте программы элементы массива изображаются в виде переменных с индексами. Индексы записываются в квадратных скобках сразу после имени массива и разделяются между собой запятыми. Имя массива совпадает с именем переменной с индексами, т.е. с именем элемента массива. Конкретные значения
68
индексов определяют положение элемента в массиве и однозначно идентифицируют его.
Размерность массива – количество индексов, которое необходимо для однозначной идентификации любого элемента массива.
Размер массива – общее количество элементов в массиве.
На практике довольно часто используют одномерные и двухмерные массивы. Одномерный массив иногда называют вектором, двумерный – матрицей. Одномерный массив удобно представлять в виде группы последовательных ячеек памяти, а матрицу – в виде квадратной таблицы.
Статические массивы можно объявлять с инициализацией, перечисляя значения их элементов в {} через запятую. Если задано меньше элементов, чем длина массива остальные элементы считаются нулями:
int a10[10] = { 1, 2, 3, 4 }; /* и 6 нулей */
Если при описании массива с инициализацией не указать его размер, он будет подсчитан компилятором:
int a3[] = { 1, 2, 3 }; /* как бы a3[3] */
Массивы определяются с помощью синтаксической конструкции, показанной на рис.27.
Тип элемента может быть любым допустимым типом языка Си (в том числе и регулярным типом (массивом) тоже), а в качестве типа индекса может использоваться любое выражение, выдающее значение целого типа: char, short,
int, long. При описании переменной регулярного типа строго фиксируются границы изменения каждого индекса. В дальнейшем эти границы изменять нельзя. Как правило, индексы массива представляют ограниченный тип на базе целого, а верхняя граница диапазона описывается как константа:
int N = 10;
[
]
Имя массива
Рис. 27. СД описания массивов в языке Си
Тип данных
Количество элементов
69

int M = 20;
float A [N] [M];
// матрица NxM из 200 элементов
int B [N];
// вектор из 10 целых элементов
char C {'A','F',’6’,’5’}
// вектор из 4 символьных элементов
В разделе операторов для обращения к элементам описанных массивов могут использоваться переменные с индексами, причем в качестве индексов могут выступать любые выражения, выдающие результат такого же типа, что и соответствующий индекс, например:
A[2,3]:=3.14;
A[i,j]:=i*i+j–2;
A[i,2]:=A[i–1,j+2];
B[j]:=3; B[j+i]:=i+j;
Строки
Строка в Си - это последовательность байт (букв, символов, литер,
character), завершающаяся в конце специальным признаком - байтом '\0'. Этот признак добавляется компилятором автоматически, когда мы задаем строку в виде "строка". Длина строки (т.е. число литер, предшествующих '\0') нигде явно не хранится. Длина строки ограничена лишь размером массива, в котором сохранена строка, и может изменяться в процессе работы программы в пределах от 0 до длины массива-1. При передаче строки в качестве аргумента в функцию, функции не требуется знать длину строки, т.к. передается указатель на начало массива, а наличие ограничителя '\0' позволяет обнаружить конец строки при ее просмотре. В табл.8 представлены строковые типы данных.
Таблица 8.
Строковые типы данных в языке Си
Тип
Размер в байтах
(битах)
Диапазон значений
Знаковый
(может ли хранить
отрицательные числа)
Char
1 (8)
от -128 до 127
Да
Unsigned char
1 (8)
от 0 до 255
Нет
Signed char
1 (8)
от -128 до 127
Да
Базовым типом данных является Char. Объект этого базового типа может быть модифицирован с помощью следующих модификаторов: unsigned, signed.
Язык Си позволяет определить строки двумя различными способами. В первом используется массив символов, а во втором - указатель на первый символ массива.
70

Определение char а[10]; указывает компилятору на необходимость резервирования места для максимум 10 символов. Константа а содержит адрес ячейки памяти, в которой помещено значение первого из десяти объектов типа char. Процедуры, связанные с занесением конкретной строки в массив а, копируют ее по одному символу в область памяти, на которую указывает константа а, до тех пор, пока не будет скопирован нулевой символ, оканчивающий строку. Когда выполняется функция типа printf("%s", а), ей передается значение а, т.е. адрес первого символа, на который указывает а. Если первый символ - нулевой, то работа функции printf() заканчивается, а если нет, то она выводит его на экран, прибавляет к адресу единицу и снова начинает проверку на нулевой символ. Такая обработка позволяет снять ограничения на длину строки (конечно, в пределах объявленной размерности): строка может иметь любую длину, но в пределах доступной памяти.
Инициализировать строку при таком способе определения можно следующим образом:
char array[7] = "Строка";
char s[ ] = {'С', 'т', 'р', 'о', 'к', 'а', '\0'};
(при определении массива с одновременной инициализацией пределы изменения индекса можно не указывать).
Второй способ определения строки - это использование указателя на символ. Определение char *b; задает переменную b, которая может содержать адрес некоторого объекта. Однако в данном случае компилятор не резервирует место для хранения символов и не инициализирует переменную b конкретным значением. Когда компилятор встречает оператор вида b ="IBM PC";, он производит следующие действия. Во-первых, как и в предыдущем случае, он создает в каком-либо месте объектного модуля строку "IBM PC", за которой следует нулевой символ ('\0'). Во-вторых, он присваивает значение начального адреса этой строки (адрес символа 'I') переменной b. Функция printf("%s", b)
работает так же, как и в предыдущем случае, осуществляя вывод символов до тех пор, пока не встретится заключительный нуль.
71

Массив указателей можно инициализировать, т.е. назначать его элементам конкретные адреса некоторых заданных строк при определении.
Для ввода и вывода строк символов помимо scanf() и printf() могут использоваться функции gets() и puts() (их прототипы находятся в файле stdio.h).
Если string – массив символов, то ввести строку с клавиатуры можно так:
gets(string);
(ввод оканчивается нажатием клавиши ). Вывести строку на экран можно следующим образом:
puts(string);
Отметим также, что для работы со строками существует специальная библиотека функций, прототипы которых находятся в файле string.h.
Наиболее часто используются функции strcpy(), strcat(), strlen() и strcmp().
srcpy() – используется для копирования строки или ее части в другую строку.
Копирование происходит побайтно, пока не встретится ‘\0’. Возвращает указатель на строку s1.
strcpy(s1, s2); // копирует строку s2 в строку s1
strncpy(s1, s2,n);
//копирует первые n символов из строки s2 в строку s1
Помните, что указываемый массив-приемник s1 должен быть достаточного размера, чтобы содержать строку-источник s2, иначе программа может быть испорчена. Если n больше, чем длина строки в s2, то в s1 символы заполняются нулями до величины n.
Задавая аргумент-источник не ссылкой на начало символьного массива, а адресом любого его элемента, мы можем скопировать либо правую, либо среднюю подстроку:
9 strcpy(s1,&s2[k]);
//копирует правую подстроку из s2 в s1
9 strncpy(s1,&s[2],n);
//копирует среднюю подстроку из s2 в s1
char * strcpy_my (char *s1, char *s2)
//Пример функции копирования
{
char *ptrs1 = s1;
while (( *s1++ = *s2++) != 0);
return ptrs1;
}
72

Обратите внимание, что использование нулевого ограничителя упрощает различные операции над строками.
Длина строк в языке си определяется системной функцией strlen().
Единственным ее аргументом ёявляется анализируемая строка. Функция возвращает длину строки в символах без учета нулевого ограничителя.
void main()
/*пример функции*/
{
char str[80];
printf("ввести строку: ");
gets(str);
printf("%d", strlen(s));
}
В Си операция конкатенации (объединения) строк реализуется с помощью функции strcat():
strcat(s1,s2); //добавляет s2 к s1
strncat(s1,s2,n);
//добавляет n первых символов из s2 к s1
Поиск вхождения одной строки в другую дает ответ на вопрос, содержится ли значение одного текста в другом и с какой позиции обнаружено это вхождение. Нулевая позиция в качестве результата такой операции соответствует отрицательному ответу.
char * strcat_my (char *s1, char *s2)
/*Пример собственной функции
конкатенации*/
{
char *p1, *p2;
p1 = s1; p2 = s2;
while ( *p1 != ‘\0’) p1++; //найти конец 1-ой строки.
//или while ( *p1) p1++;
while (( *p1 = *p2) != 0)//копировать строку р2, пока не будет скопирован
{
p1++;
// нулевой ограничитель
p2++;
//Передвинуть указатели к следующему байту
}
//Или while (( *p1++ = *p2++) != 0);/*.
*р1 = ‘\0’;
return s1;
}
Язык Си предлагает довольно разнообразные варианты поиска вхождений:
strstr(s1,s2);//ищет вхождение строки s2 в s1
strchr(s1,c); //ищет вхождение символа ‘с’ с начала строки s1
strrcgr(s1,c);//ищет вхождение символа ‘с’ с конца строки s1
strpbrk(s1,s2); //ищет вхождение любого символа из s2 в s1
strspn(s1,s2);//ищет вхождение любого фрагмента, составленного из
//символов s2 в s1
73

strcmp() – осуществляет сравнение текстовых данных. Операции сравнения отдельных символов или строк основаны на последовательном анализе отношений числовых значений соответствующих кодов. В кодовых страницах символы букв упорядочены в соответствии их расположением в латинском или национальном алфавитах. Поэтому код буквы "A" меньше кода буквы "F", код буквы "Г" меньше кода буквы "Ю" и т.д.
Некоторое неудобство вызывает тот факт, что одноименные большие и малые буквы имеют разные коды – в одной клетке кодировочной таблицы можно разместить только один символ, кроме того большие и малые буквы имеют разный смысл. Это не позволяет напрямую упорядочить слова в соответствии с их лексикографическим расположением в словарях. Поэтому приходится предварительно заменять коды всех малых букв в тексте на коды больших (или наоборот) и только после этого выполнять операцию сравнения. Такая замена для букв латинского алфавита особых проблем не представляет, т.к. смещение между кодами соответствующих больших и малых букв - величина постоянная.
А вот с русскими буквами приходится повозиться – в кодировке ASCII цепочка малых букв между "п" и "р" разорвана символами псевдографики, а буквы "Ё" и "ё" вообще находятся не на своих местах.
Учитывая эту специфику, следует достаточно внимательно использовать языковые средства, связанные с преобразованием или игнорированием разницы в кодировке больших и малых букв. Для русскоязычных текстов их применять нельзя.
Язык Си позволяет преобразовывать содержимое символьных строк к верхнему strupr(s) или к нижнему strlwr(s) регистру. Но коды символов, не принадлежащих множеству букв латинского алфавита, остаются при этом без изменения.
Для сравнения строк Си предлагает довольно много системных функций, но не забывайте, что их действие не всегда допустимо над русскими словами.
Каждая из описываемых ниже функций принимает положительное значение, если ее первый операнд строго "больше" (лексикографически) второго, нулевое
74
значение при "равенстве" операндов, и отрицательное значение, если первый операнд оказался "меньше".
strcmp(s1,s2);//сравнивает строки s1 и s2
strcmpi(s1,s2); //сравнивает s1 и s2 с игнорированием разницы между
//большими и малыми буквами
stricmp(s1,s2);
//эквивалентна функции strcmpi
strncmp(s1,s2,k);
//сравнивает первые k символов в s1 и s2
strncmpi(s1,s2,k);
//сравнивает первые k символов в s1 и s2 с
//игнорированием разницы между большими и
//малыми буквами
strnicmp(s1,s2,k);
//эквивалентна функции strncmpi
Функцию strcmp() можно использовать для проверки вводимого пароля, как показано в следующем примере:
/* вернуть "верно", если пароль угадан */
password()
{
char s[80];
printf("ввести пароль: ");
gets(s);
if(strcmp(s, "пароль"))
{
printf("пароль ошибочен\n");
return 0;
}
return 1;
}
Имейте в виду, что если строки равны, функция strcmp() возвращает "ложь", и если вы хотите использовать это условия для другого действия, необходимо записывать оператор логического отрицания «!» (NOT), как показано в следующем примере:
main()
{
char s[80];
for('')
{
printf(": ");
gets(s);
if(!strсmp("quit", s)) break;
}
}
Эта программа будет продолжать запрашивать ввод до тех пор, пока не будет введено слово quit. Следующий пример иллюстрирует действие функций обработки строк:
main()
{
char s1[80], s2[80];
gets(s1); gets(s2);
printf("Длина: %c %c\n", strlen(s1), strlen(s2));
75

if(!strcmp(s1, s2)) printf("Эти строки равны\n");
strcat(s1,s2);
printf("%s\n", s1);
}
Если вы выполните эту программу и введете строки hello и hello, то в результате получите:
Длина: 5 5
Эти строки равны
Hellohello
До сих пор мы рассматривали присваивание указателю адреса только первого элемента массива. Однако это можно делать и с адресом любого отдельного элемента массива путем добавления & к индексированному имени.
Особенно удобно пользоваться этим правилом при выделении подстроки.
Например, следующая программа выводит на экран остаток введенной строки после первого пробела:
/* вывести на экран остаток строки после первого пробела */
main()
{
char s[80];
char *p;
int i;
printf("ввести строку: ");
gets(s);
/* найти первый пробел или конец строки */
for(i=0; s[i] && s[i]!=` `; i++);
p = &s[i];
printf(p);
}
В этой программе p будет указывать либо на пробел, если он есть, либо на ноль, если в строке нет пробелов. Если p указывает на пробел, то программа выведет на экран его и затем остаток строки. Например, если вы введете фразу язык программирования Си, функция printf() напечатает сначала пробел и затем программирования Си. Если p укажет на ноль, то ничего не выводится на экран.
Имена переменных и констант строкового типа рекомендуется начинать с символов «s» или «str», например «sMyString», «strName».
Данные комбинированного типа (структуры)
Структура – это объединение одного или нескольких объектов
(переменных, массивов, указателей, других структур и т.д.). Как и массив, она представляет собой совокупность данных. Отличием является то, что к ее
76
элементам необходимо обращаться по имени и что различные элементы структуры не обязательно должны принадлежать одному типу.
Объявление структуры осуществляется с помощью ключевого слова struct, за которым идет ее тип и далее список элементов, заключенных в фигурные скобки:
struct тип
{
тип элемента_1 имя элемента_1;
тип элемента_n имя элемента_n;
};
Именем элемента может быть любой идентификатор. Как и выше, в одной строке можно записывать через запятую несколько идентификаторов одного типа.
Рассмотрим пример:
sruct date
{
int day;
int month;
int year;
};
Следом за фигурной скобкой, заканчивающей список элементов, могут записываться переменные данного типа, например:
struct date {...} a, b, c;
(при этом выделяется соответствующая память). Описание без последующего списка не выделяет никакой памяти; оно просто задает форму структуры. Введенное имя типа позже можно использовать для объявления структуры, например:
struct date days;
Теперь переменная days имеет тип date.
При необходимости структуры можно инициализировать, помещая вслед за описанием список начальных значений элементов.
Разрешается вкладывать структуры друг в друга, например:
struct man
{
char name[20], fam[20];
struct date bd;
int age;
};
77

Определенный выше тип data включает три элемента: day, month, year, содержащий целые значения (int). Структура man включает элементы name, fam,
bd и voz. Первые два – name[20] и fam[20] - это символьные массивы из 20 элементов каждый. Переменная bd представлена составным элементом
(вложенной структурой) типа data. Элемент age содержит значения целого типа
(int). Теперь можно определить переменные, значения которых принадлежат введенному типу:
struct man man_[100];
Здесь определен массив man_, состоящий из 100 структур типа man.
Чтобы обратиться к отдельному элементу структуры, необходимо указать его имя, поставить точку и сразу же за ней записать имя нужного элемента, например:
man_[j].age = 19;
man_[j].bd.day = 24;
man_[j].bd.month = 2
man_[j].bd.year = 1987;
При работе со структурами необходимо помнить, что тип элемента определяется соответствующей строкой описания в фигурных скобках.
Например, массив man_ имеет тип man, year является целым числом и т.п.
Поскольку каждый элемент структуры относится к определенному типу, его имя может появиться везде, где разрешено использование значений этого типа.
Допускаются конструкции вида man_[i]=man_[j]; где man_[i] и man_[j] – объекты, соответствующие единому описанию структуры. Другими словами, разрешается присваивать одну структуру другой по их именам.
Унарная операция & позволяет взять адрес структуры. Предположим, что определена переменная day:
struct date {int d, m, у;} day;
Здесь day – это структура типа date, включающая три элемента: d, m, у.
Другое определение
struct date *db;
устанавливает тот факт, что db - это указатель на структуру типа date.
78

Запишем выражение:
db = &day;
В этом случае для выбора элементов d, m, у структуры необходимо использовать конструкции:
(*db).d; (*db).m; (*db).y;
Действительно, db - это адрес структуры, *db - сама структура. Круглые скобки здесь необходимы, так как точка имеет более высокий, чем звездочка, приоритет. Для аналогичных целей в языке Си предусмотрена специальная операция –>. Эта операция выбирает элемент структуры и позволяет представить рассмотренные выше конструкции в более простом виде:
db -> d; db -> m; db -> у;
Пример 13. Рассмотрим структуру, в которой хранится информация о студенте. Далее в программе мы присваиваем определенным полям нужные значения, в зависимости от значения селектора варианта (Curs).
struct Student
{
char FIO[20];
int YearBirth;
bool sex;
char Group[20];
float Stipendia;
int Curs;
int SchoolNo;
char SchoolCity[7];
int KruzhokNo[5];
char DiplomTema[50];
char MestoPractiki[50];
}
Stud1;
switch(Stud1.Curs)
{
case 1 : Stud1.SchoolNo = 345;
Stud1.SchoolCity = "Москва";
break;
case 2 : Stud1.KruzhokNo[1]:=22;
break;
case 5 : Stud1.DiplomTema="Программная система на Си";
Stud1.MestoPractiki="МГУП";
break;
}
Перечисления
В языке Си перечислением (множеством) называется группа элементов, ассоциированных с единым именем. Перечисляемый тип данных реализован в
79
языке Си с целью использования в программах возможностей математической теории конечных множеств. Множество (или перечисление) описывается с помощью конструкции:
enum <имя множества> (элементы множества);
Например:
enum seasons (spring, summer, autumn, winter);
enum seasons а, b, с;
Здесь введен новый тип данных seasons и определены переменные этого типа. Каждая из них (а, b, c) может принимать одно из четырех значений: spring,
summer, autumn и winter. Эти переменные можно было определить сразу при описании типа:
enum seasons (spring, summer, autumn, winter) a, b, с;
Рассмотрим еще один пример:
enum days {mon, tues, wed, thur, fri, sat, sun} my_week;
Имена, занесенные в days (также как и в seasons в предыдущем примере), представляют собой константы целого типа. Первая из них (mon) автоматически устанавливается в нуль, и каждая следующая имеет значение на единицу больше, чем предыдущая (tues=1, wed=2 и т.д.).
Можно присвоить константам определенные значения целого типа
(именам, не имеющим их, будут, как и раньше, назначены значения предыдущих констант, увеличенные на единицу). Например:
enum days (man=5, tues=8, wed=10, thur, fri, sat, sun} my_week;
После этого mon=5, tues=8,wed=10, thur=11, fri=12, sat=13, sun=14.
Тип enum можно использовать для задания констант true=1 и false=0, например:
enum t_f (false, true) а, b;
Пример 14. Пусть требуется ввести строку, состоящую из латинских букв, цифр, пробелов. Требуется напечатать заданный текст, удалив из него все цифры.
int i;
char z;
/*Текущий вводимый символ*/
printf("\nНапишите предложение с точкой в конце:\n");
for(i=0;z!='.';i++)
80

{
scanf("%c",&z);
if(z=='0'||z=='1'||z=='2'||z=='3'||z=='4')continue;
if(z=='5'||z=='6'||z=='7'||z=='8'||z=='9')continue;
printf("%c",z);
}
Объединения
Обединения – это некоторая переменная, которая может хранить (в разное время) объекты различного типа и размера. В результате появляется возможность работы в одной и той же области памяти с данными различного вида. Для описания объединения используется ключевое слово union, а соответствующий синтаксис аналогичен структурам. Пусть задано определение:
union r
{
int ir;
float fr;
char cr;
} z;
Здесь ir имеет размер 2 байта, fr - 4 байта, cr - 1 байт. Размер переменной z будет равен размеру самого большого из трех приведенных типов (т.е. 4 байтам).
В один и тот же момент времени z может иметь значение только одной из переменных ir, fr или cr.
Указатели
Указатель

это адрес памяти, распределяемой для размещения идентификатора (в качестве идентификатора может выступать имя переменной, массива, структуры, строкового литерала). В том случае, если переменная объявлена как указатель, то она содержит адрес памяти, по которому может находиться скалярная величина любого типа. При объявлении переменной типа указатель, необходимо определить тип объекта данных, адрес которых будет содержать переменная, и имя указателя с предшествующей звездочкой (или группой звездочек). Формат объявления указателя:
<спецификатор типа> [< модификатор> ] * описатель.
Спецификатор типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом
81
отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов может быть выполнено с помощью операции приведения типов.
В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.
Для модификации размера указателя можно использовать ключевые слова near, far, huge.
unsigned int * a; /* переменная а представляет собой указатель
на тип unsigned int (целые числа без знака) */
double * x;
/* переменная х указывает на тип данных с
плавающей точкой удвоенной точности */
char * fuffer;
/* объявляется указатель с именем fuffer
который указывает на переменную типа char */
double nomer;
void *addres;
addres = & nomer;
(double *)addres ++;
/* Переменная addres объявлена как указатель на объект любого типа. Поэтому ей
можно присвоить адрес любого объекта (& - операция вычисления адреса). Однако,
как было отмечено выше, ни одна арифмитическая операция не может быть выполнена
над указателем, пока не будет явно определен тип данных, на которые он
указывает. Это можно сделать, используя операцию приведения типа (double *) для
преобразования addres к указателю на тип double, а затем увеличение адреса. */
const * dr;
/* Переменная dr объявлена как указатель на константное выражение, т.е.
значение указателя может изменяться в процессе выполнения программы, а
величина, на которую он указывает, нет. */
unsigned char * const w = &obj.
/* Переменная w объявлена как константный указатель на данные типа char
unsigned. Это означает, что на протяжение всей программы w будет указывать на
одну и ту же область памяти. Содержание же этой области может быть изменено. */
Методы доступа к массивам
82

В языке Си между указателями и массивами существует тесная связь.
Например, когда объявляется массив в виде int array[25], то этим определяется не только выделение памяти для двадцати пяти элементов массива, но и для указателя с именем array, значение которого равно адресу первого по счету
(нулевого) элемента массива, т.е. сам массив остается безымянным, а доступ к элементам массива осуществляется через указатель с именем array. С точки зрения синтаксиса языка указатель arrey является константой, значение которой можно использовать в выражениях, но изменить это значение нельзя.
Поскольку имя массива является указателем допустимо, например, такое присваивание:
int arrey[25];
int *ptr;
ptr = array;
Здесь указатель ptr устанавливается на адрес первого элемента массива, причем присваивание ptr = arrey можно записать в эквивалентной форме ptr =
&arrey[0].
Для доступа к элементам массива существует два различных способа.
Первый способ связан с использованием обычных индексных выражений в квадратных скобках, например, array[16] = 3 или array[i+2] = 7. При таком способе доступа записываются два выражения, причем второе выражение заключается в квадратные скобки. Одно из этих выражений должно быть указателем, а второе - выражением целого типа. Последовательность записи этих выражений может быть любой, но в квадратных скобках записывается выражение следующее вторым. Поэтому записи array[16] и 16[array] будут эквивалентными и обозначают элемент массива с номером шестнадцать.
Указатель, используемый в индексном выражении не обязательно должен быть константой, указывающей на какой-либо массив, это может быть и переменная.
В частности после выполнения присваивания ptr = array доступ к шестнадцатому элементу массива можно получить с помощью указателя ptr в форме ptr[16] или
16[ptr].
83

Второй способ доступа к элементам массива связан с использованием адресных выражений и операции разадресации в форме *(array+16) = 3 или
*(array+i+2) = 7. При таком способе доступа адресное выражение равное адресу шестнадцатого элемента массива тоже может быть записано разными способами
*(array+16) или *(16+array).
При реализации на компьютере первый способ приводится ко второму, т.е. индексное выражение преобразуется к адресному. Для приведенных примеров array[16] и 16[array] преобразуются в *(array+16).
Для доступа к начальному элементу массива (т.е. к элементу с нулевым индексом) можно использовать просто значение указателя array или ptr. Любое из присваиваний
*array = 2;
array[0] = 2;
*(array+0) = 2;
*ptr = 2;
ptr[0] = 2;
*(ptr+0) = 2;
присваивает начальному элементу массива значение 2, но быстрее всего выполнятся присваивания *array=2 и *ptr=2, так как в них не требуется выполнять операции сложения.
Указатели на многомерные массивы
Указатели на многомерные массивы в языке Си

это массивы массивов, т.е. такие массивы, элементами которых являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов.
Например, при выполнении объявления двумерного массива int arr2[4][3] в памяти выделяется участок для хранения значения переменной arr, которая является указателем на массив из четырех указателей. Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех указателей содержит адрес массива из трех элементов типа int, и, следовательно, в памяти компьютера выделяется четыре участка для хранения четырех массивов чисел типа int, каждый из которых состоит из трех элементов. Распределение памяти для двумерного массива представлено ниже.
arr
84

b
arr[0]a arr[0][0] arr[0][1] arr[0][2]
arr[1]a arr[1][0] arr[1][1] arr[1][2]
arr[2]a arr[2][0] arr[2][1] arr[2][2]
arr[3]a arr[3][0] arr[3][1] arr[3][2]
Таким образом, объявление arr2[4][3] порождает в программе три разных объекта: указатель с идентификатором arr, безымянный массив из четырех указателей и безымянный массив из двенадцати чисел типа int. Для доступа к безымянным массивам используются адресные выражения с указателем arr.
Доступ к элементам массива указателей осуществляется с указанием одного индексного выражения в форме arr2[2] или *(arr2+2). Для доступа к элементам двумерного массива чисел типа int должны быть использованы два индексных выражения в форме arr2[1][2] или эквивалентных ей *(*(arr2+1)+2) и (*(arr2+1))
[2]. Следует учитывать, что с точки зрения синтаксиса языка Си указатель arr и указатели arr[0], arr[1], arr[2], arr[3] являются константами и их значения нельзя изменять во время выполнения программы.
Размещение трехмерного массива происходит аналогично и объявление float arr3[3][4][5] порождает в программе кроме самого трехмерного массива из шестидесяти чисел типа float массив из четырех указателей на тип float, массив из трех указателей на массив указателей на float, и указатель на массив массивов указателей на float.
При размещении элементов многомерных массивов они располагаются в памяти подряд по строкам, т.е. быстрее всего изменяется последний индекс, а медленнее

первый. Такой порядок дает возможность обращаться к любому элементу многомерного массива, используя адрес его начального элемента и только одно индексное выражение.
Например, обращение к элементу arr2[1][2] можно осуществить с помощью указателя ptr2, объявленного в форме int *ptr2=arr2[0] как обращение ptr2[1*4+2] (здесь 1 и 2 это индексы используемого элемента, а 4 это число элементов в строке) или как ptr2[6]. Заметим, что внешне похожее обращение arr2[6] выполнить невозможно, так как указателя с индексом 6 не существует.
85

Для обращения к элементу arr3[2][3][4] из трехмерного массива тоже можно использовать указатель, описанный как float *ptr3=arr3[0][0] с одним индексным выражением в форме ptr3[3*2+4*3+4] или ptr3[22].
Далее приведена функция, позволяющая найти минимальный элемент в трехмерном массиве. В функцию передается адрес начального элемента и размеры массива, возвращаемое значение

указатель на структуру, содержащую индексы минимального элемента.
struct index
{
int i,
int j,
int k
} min_index;
struct index * find_min (int *ptr1, int l, int m int n)
{
int min,i,j,k,ind;
min=*ptr1;
min_index.i = min_index.j = min_index.k=0;
for (i=0; i*(ptr1+ind)
{
min=*(ptr1+ind);
min_index.i=i;
min_index.j=j;
min_index.k=k;
}
return &min_index;
}
Операции с указателями
Над указателями можно выполнять унарные операции: инкремент и декремент. При выполнении операций ++ и -- значение указателя увеличивается или уменьшается на длину типа, на который ссылается используемый указатель.
int *ptr, a[10];
ptr = &a[5];
ptr++;
// равно адресу элемента a[6]
ptr--;
// равно адресу элемента a[5]
В бинарных операциях сложения и вычитания могут участвовать указатель и величина типа int. При этом результатом операции будет указатель на исходный тип, а его значение будет на указанное число элементов больше или меньше исходного.
int *ptr1, *ptr2, a[10];
int I = 2;
ptr1 = a+(i+4); // равно адресу элемента a[6]
ptr2 = ptr1-i;
// равно адресу элемента a[4]
86

В операции вычитания могут участвовать два указателя на один и тот же тип. Результат такой операции имеет тип int и равен числу элементов исходного типа между уменьшаемым и вычитаемым, причем если первый адрес младше, то результат имеет отрицательное значение.
int *ptr1, *ptr2, a[10];
int i;
ptr1 = a+4;
ptr2 = a+9;
i = ptr1-ptr2;
// равно 5
i = ptr2-ptr1;
// равно -5
Значения двух указателей на одинаковые типы можно сравнивать в операциях ==, !=, <, <=, >, >= при этом значения указателей рассматриваются просто как целые числа, а результат сравнения равен 0 (ложь) или 1 (истина).
int *ptr1, *ptr2, a[10];
ptr1 = a+5;
ptr2 = a+7;
if (prt1 > ptr2) a[3] = 4;
В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3] = 4 не будет выполнен.
1   2   3   4   5   6   7   8   9   ...   15


написать администратору сайта