Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP2 (1).doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
350.21 Кб
Скачать

19) Сегмент данных для хранения констант

Директива #define служит для замены часто использующихся констант,

ключевых слов, операторов или выражений некоторыми идентификаторами.

Идентификаторы, заменяющие текстовые или числовые константы, называют

именованными константами. Идентификаторы, заменяющие фрагменты

программ, называют макроопределениями, причем макроопределения могут

иметь аргументы.

Директива #define имеет две синтаксические формы:

#define идентификатор текст

#define идентификатор (список параметров) текст

Препроцессор С/С++ — программный инструмент, изменяющий код программы для

последующей компиляции и сборки, используемый в языках программирования Си и его

потомка - C++. Этот препроцессор обеспечивает использование стандартного набора

возможностей:

 Замена триграфов ??=, ??(, ??) (и других) символами #, [, ]

 Замена комментариев пустыми строками

 Включение файла — #include

 Макроподстановки — #define

 Условная компиляция — #if, #ifdef, #elif, #else, #endif

define — задаёт макроопределение (макрос) или символическую константу

Замена лексических единиц. Командная строка вида #define name text вызывает в оставшейся части

программы замену всех вхождений идентификатора name на строку text. Например, определение #define p1

3.14159265 Позволяет использовать в программе имя p1 вместо константы 3.14159265. Обратите внимание, что

это определение не завершается точкой с запятой. Замещающий текст обычно представляет собою остаток

строки. Длинное определение можно продолжить, если в конце продолжаемой строки поставить \. Внутри

строк, заключенных в кавычки, подстановка не производится, так что, например, для определенного выше

имени P1 в printf("P1"); подстановки не будет. Имена могут переопределяться и новые определения могут

использовать ранее введенные определения.

Применение моделей памяти позволяет контролировать ее сегментное распределение и делать его более эффективным или

адекватным решаемой задаче. По умолчанию при компиляции и редактировании связей генерируется код для работы в малой (small)

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

 размер кода программы превышает 64 Кб;

 размер статических данных программы превышает 64 Кб.

Имеется два варианта выбора модели памяти для программы:

 назначить нужную модель в опциях компилятора;

 использовать в объявлении объектов программы модификаторы near, far и huge.

Можно комбинировать эти способы.

Архитектура процессоров, основанных на базе 8086/8088, предусматривает разбиение оперативной памяти на физические сегменты,

способные содержать информацию объемом до 64 Кб. Минимальное количество сегментов, выделяемое программе, равно двум: сегмент

кода и сегмент статических данных. К статическим данным при этом относятся все объекты, объявленные с классом памяти extern или

static. Формальные параметры функций и локальные переменные не являются статическими. Они хранятся не в сегменте данных, а в

стеке (однако при этом стек может быть совмещен со стандартным сегментом данных физически).

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

может выделяться как в отдельном сегменте (дальняя динамическая память), так и в стандартном сегменте данных между концом занятой

данными области и стеком (ближняя динамическая память).

Адрес оперативной памяти состоит из двух частей:

 базовый адрес сегмента — 16-битовое число;

 смещение относительно начала сегмента — также 16-битовое число.

Для доступа к коду или данным, находящимся в единственном стандартном сегменте, достаточно использовать только смещение. В

этом случае применяются указатели, объявленные с модификатором near (ближний). Поскольку для доступа к объекту используется только одно двухбайтовое число, применение ближних указателей дает компактный по занимаемой памяти код с хорошим

быстродействием.

Если код или данные находятся в другом сегменте по отношению к адресующему, для доступа к ячейке памяти должны и адрес

сегмента, и смещение. В этом случае указатели объявляются с модификатором far (дальний). Доступ к объектам по дальним указателям

позволяет программе адресовать всю оперативную память, а не только в пределах сегмента размером 64 Кб.

Наконец, указатели с модификатором huge (максимальный) также включают адрес сегмента, и смещение, но имеют иную адресную

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

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

объекта объемом 64 Кб. Для указателей huge арифметические действия выполняются над всеми 32 битами адреса.

Тип адреса huge определен только для данных, таких как массивы и указатели на них. Таким образом, никакой из исходных файлов,

составляющих программу, не должен генерировать более 64 Кб кода.

Кратко опишем шесть имеющихся в Си моделей памяти.

Минимальная модель (tiny). Код вместе с данными не превышает по объему 64 Кб. Применялась для исполняемых файлов,

преобразуемых к формату *.com.

Малая модель (small). Программа занимает 2 стандартных сегмента: сегмент кода и сегмент данных, в котором размещается также

стек. Как код, так и данные программы не могут превышать 64 Кб. Модель подходит для большинства несложных программ и назначается

компилятором по умолчанию. Для доступа к объектам кода или данных по умолчанию используются указатели типа near.

Средняя модель (medium). Для данных и стека выделяется один сегмент, для кода — произвольное число сегментов. Каждому

исходному модулю программы выделяется собственный сегмент кода. Модель применяется для программ с большим количеством кода

(более 64 Кб) и небольшими объемами данных (менее 64 Кб). Для доступа к функциям по умолчанию используются указатели far, а для

доступа к данным — указатели near. Модель предлагает компромисс между скоростью выполнения и компактностью кода, поскольку

многие программы чаще обращаются к данным, чем к функциям.

Компактная модель (compact). В этой модели выделяется один сегмент для кода, и произвольное число сегментов для данных.

Модель применяется для небольших программ, работающих со значительными объемами данных. Доступ к функциям производится по

указателям near, а к данным — по указателям far.

Большая модель (large) использует по несколько сегментов и для кода, и для данных. Модель подходит для больших программ со

значительным объемом данных. В этой модели доступ к элементам кода и данных производится по указателям типа far. Как и во всех

предшествующих моделях умолчания можно обойти, явно используя модификаторы near, far и huge там, где они требуются при

объявлении данных и функций.

Максимальная модель (huge) аналогична большой за исключением того, что в ней снимается ограничение на размер массивов в 64 Кб.

Однако для больших массивов не допускается пересечения элементами границ сегмента, из чего следует, что элемент массива не может

превышать по размеру 64 Кб. Кроме того, для обеспечения эффективной адресации размер в байтах элемента большого массива должен

быть степенью двойки.

20) string.h

char *strcopy(char *dsk, char *src); \\ из src копирует в dsk

int strcomp(char *s1, char *s2); \\ сравнение строк, то что по алф раньше-меньше

char *strcut (char *s1, char *s2); \\ s2 дописывается к s1

сhar *strstr(char *str,char *substr); \\ первое вхождение substr в строке str

сhar *strchr(char *str,int key); \\ первое вхождение символа key в строкe str

int strlen(char *s);

char *strtok(char * str, char * delim); \\ разбивает фразу на части

21) Функция strtok

#include <string.h>

char *strtok(char *str1, const char *str2);

Функция strtok() возвращает указатель на следующую лексему в строке, адресуемой параметром str1. Символы, образующие строку, адресуемую параметром str2, представляют собой разделители, которые определяют лексему. При отсутствии лексемы, подлежащей возврату, возвращается нулевой указатель.

В версии С99 к параметрам str1 и str2 применен квалификатор restrict.

Чтобы разделить некоторую строку на лексемы, при первом вызове функции strtok() параметр str1 должен указывать на начало этой строки. При последующих вызовах функции в качестве параметра str1 нужно использовать нулевой указатель. Этим способом вся строка разбивается на лексемы.

При каждом обращении к функции strtok() можно использовать различные наборы разделителей.

Пример

Эта программа разбивает строку "Травка зеленеет, солнышко блестит" на лексемы, разделителями которых служат пробелы и запятые. В результате получится

Травка|зеленеет|солнышко|блестит

#include <stdio.h>

#include <string.h>

int main(void)

{

char *p;

p = strtok("Травка зеленеет, солнышко блестит", " ");

printf(p);

do {

p = strtok('\0', ", ");

if(p) printf("|%s", p);

} while(p);

return 0;

}

ПР. Разбить текст на лексемы

#include <stdio.h>

#include<string.h>

int main (){

char *s=”Hello_World!”;

str[]=”Hello_World!”;

char *word;

char *dilim=” !”;

word=strtok (str,dilim);

while(word !=Null){

printf(“% s\n”,word);

word=strtok(Null,delim);}

return 0; }

22) Указатели на функции.

Указатели на функции[1] — очень мощное средство языка С. Хотя нельзя не отметить, что это весьма трудный для понимания термин. Функция располагается в памяти по определенному адресу, который можно присвоить указателю в качестве его значения. Адресом функции является ее точка входа. Именно этот адрес используется при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.

В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на адрес массива, который равен имени массива без индексов). Рассмотрим следующую программу, в которой сравниваются две строки, введенные пользователем. Обратите внимание на объявление функции check() и указатель p внутри main(). Указатель p, как вы увидите, является указателем на функцию.

#include <stdio.h>

#include <string.h>

void check(char *a, char *b,

int (*cmp)(const char *, const char *));

int main(void)

{

char s1[80], s2[80];

int (*p)(const char *, const char *);

/* указатель на функцию */

p = strcmp;

/* присваивает адрес функции strcmp указателю p */

printf("Введите две строки.\n");

gets(s1);

gets(s2);

check(s1, s2, p); /* Передает адрес функции strcmp

посредством указателя p */

return 0;

}

void check(char *a, char *b,

int (*cmp)(const char *, const char *))

{

printf("Проверка на совпадение.\n");

if(!(*cmp)(a, b)) printf("Равны");

else printf("Не равны");

}

Проанализируем эту программу подробно. В первую очередь рассмотрим объявление указателя p в main():

int (*p)(const char *, const char *);

Это объявление сообщает компилятору, что p — это указатель на функцию, имеющую два параметра типа const char * и возвращающую значение типа int. Скобки вокруг p необходимы для правильной интерпретации объявления компилятором. Подобная форма объявления используется также для указателей на любые другие функции, нужно лишь внести изменения в зависимости от возвращаемого типа и параметров функции.

Теперь рассмотрим функцию check(). В ней объявлены три параметра: два указателя на символьный тип (a и b) и указатель на функцию cmp. Обратите внимание на то, что указатель функции cmp объявлен в том же формате, что и p. Поэтому в cmp можно хранить значение указателя на функцию, имеющую два параметра типа const char * и возвращающую значение int. Как и в объявлении p, круглые скобки вокруг *cmp необходимы для правильной интерпретации этого объявления компилятором.

Вначале в программе указателю p присваивается адрес стандартной библиотечной функции strcmp(), которая сравнивает строки. Потом программа просит пользователя ввести две строки и передает указатели на них функции check(), которая их сравнивает. Внутри check() выражение

(*cmp)(a, b)

вызывает функцию strcmp(), на которую указывает cmp, с аргументами a и b. Скобки вокруг *cmp обязательны. Существует и другой, более простой, способ вызова функции с помощью указателя:

cmp(a, b);

Однако первый способ используется чаще (и мы рекомендуем использовать именно его), потому что при втором способе вызова указатель cmp очень похож на имя функции, что может сбить с толку читающего программу. В то же время у первого способа записи есть свои преимущества, например, хорошо видно, что функция вызывается с помощью указателя на функцию, а не имени функции. Следует отметить, что первоначально в С был определен именно первый способ вызова.

Вызов функции check() можно записать, используя непосредственно имя strcmp():

check(s1, s2, strcmp);

В этом случае вводить в программу дополнительный указатель p нет необходимости.

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

Альтернативный подход — использование оператора switch с длинным списком меток case — делает программу более громоздкой и подверженной ошибкам.

В следующем примере рассматривается расширенная версия предыдущей программы. В этой версии функция check() устроена так, что может выполнять разные операции над строками s1 и s2 (например, сравнивать каждый символ с соответствующим символом другой строки или сравнивать числа, записанные в строках) в зависимости от того, какая функция указана в списке аргументов. Например, строки "0123" и "123" отличаются, однако представляют одно и то же числовое значение.

#include <stdio.h>

#include <ctype.h>

#include <stdlib.h>

#include <string.h>

void check(char *a, char *b,

int (*cmp)(const char *, const char *));

int compvalues(const char *a, const char *b);

int main(void)

{

char s1[80], s2[80];

printf("Введите два значения или две строки.\n");

gets(s1);

gets(s2);

if(isdigit(*s1)) {

printf("Проверка значений на равенство.\n");

check(s1, s2, compvalues);

}

else {

printf("Проверка строк на равенство.\n");

check(s1, s2, strcmp);

}

return 0;

}

void check(char *a, char *b,

int (*cmp)(const char *, const char *))

{

if(!(*cmp)(a, b)) printf("Равны");

else printf("Не равны");

}

int compvalues(const char *a, const char *b)

{

if(atoi(a)==atoi(b)) return 0;

else return 1;

}

Если в этом примере ввести первый символ первой строки как цифру, то check() использует compvalues(), в противном случае — strcmp(). Функция check() вызывает ту функцию, имя которой указано в списке аргументов при вызове check(), поэтому она в разных ситуациях может вызывать разные функции. Ниже приведены результаты работы этой программы в двух случаях:

Введите два значения или две строки.

тест

тест

Проверка строк на равенство.

Равны

Введите два значения или две строки.

0123

123

Проверка значений на равенство.

Равны

Сравнение строк 0123[2] и 123 показывает равенство их значений.

[1]Иногда их называют просто указателями функций. Но следует помнить, что в языках программирования под этим термином подразумевается также средство обращения к подпрограмме-функции или встроенной функции, имеющее конструкцию <имя-функции> (<список-аргументов>).

[2]Обратите внимание, что в языке С нулем начинаются восьмеричные константы. Если бы эта запись была в выражении, то 0123 не было бы равно 123. Однако здесь функция atoi() обрабатывает это число как десятичное.

23) Описания в кот. встреч. [],(),* считаются сложными

Пример int *(*f[3])(int, double) 1- имя f. 2-массив в 3х эл. (Если справа () – то функция, если [] то массив)

3- массив из 3х указателей на функцию с 2мя арг. int, double.(Ecли справа от имени есть(), а слева *, то приоритет имеют скобки) 4-тип возвращ. значения вначале int *,т.е. функции возвращ. указатели на int.

24) Примеры использования массивов указателей.

определение:

int *array[6]; вводит массив указателей на объекты типа int. Имя массива array, он состоит из

шести элементов, тип каждого int *. Определение:

int (*ptr)[6];

вводит указатель ptr на массив из шести элементов, каждый из которых имеет

тип int. Возможность создания массивов указателей позволяет экономить память при

использовании многомерных массивов.

По определению массива, его элементы должны быть однотипные и одинаковые по

длине. Пусть необходимо определить массив для представления списка фамилий. Если

определять его как двумерный массив типа char, то в определении элементов массива

необходимо задать предельные размеры каждого из двух индексов, например: char

spisok[25][20];. Таким образом, количество фамилий в списке не более 25 и что длина

каждой фамилии не превышает 19 символов (букв). При определении массива одну из его

предельных размерностей (значение самого левого индекса) можно не указывать. В этом

случае количество элементов массива определяется, например, инициализацией (рис.1):

char spisok[][20] = {"ИВАНОВ", "ПЕТРОВ", "СИДОРОВ"}; .

Рис.1. Схема размещения в памяти элементов массива spisok

Теперь в массиве spisok только 3 элемента, каждый из них длиной 20 элементов

типа char. В противоположность этому при определении и инициализации этими же

символьными строками одномерного массива указателей типа char *память

распределяется гораздо рациональнее (рис.2):

Рис.2. Схема размещения в памяти элементов с помощью массива указателей

Для указателей массива pointer, в котором 3 элемента и каждый является указателем-

переменной типа char *, выделяется всего 3*sizeof(char *) байтов. Кроме того,

компилятор размещает в памяти три строковые константы "ИВАНОВ" (7 байт), ПЕТРОВ"

(7 байт), "СИДОРОВ" (8 байт), а их адреса становятся значениями элементовpointer[0],

pointer[l], pointer[2] (рис.2).

Применение указателей и их массивов позволяет рационально решать, в частности,

задачи сортировки сложных объектов с неодинаковыми размерами. Например,

рассмотрим задачу сортировки строк матрицы. Матрица с элементами

типа double представлена двумерным массивом double array [n][m], где n и m -

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

порядке возрастания сумм их элементов. Чтобы не переставлять сами строки исходного массива введен вспомогательный одномерный массив указателей double * par[n].

Инииализируем его элементы адресами массивов строк. В качестве значений элементов

массива используем номера строк. Матрицу напечатаем три раза: до и после сортировки с

помощью вспомогательного массива указателей и (после сортировки) с использованием

основного имени массива.

Приведем текст этой программы.

#include <iostream.h>

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]