Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шпоры резать.doc
Скачиваний:
5
Добавлен:
21.12.2018
Размер:
1.24 Mб
Скачать

1.Си

Сотрудник фирмы Bell Labs Денис Ритчи создал язык Си в 1972 году во время совместной работы с Кеном Томпсоном, как инструментальное средство для реализации операционной системы Unix, однако популярность этого языка быстро переросла рамки конкретной операционной системы и конкретных задач системного программирования. В настоящее время любая инструментальная и операционная система не может считаться полной если в ее состав не входит компилятор языка Си. Ритчи не выдумывал Си просто из головы – прообразом служил язык Би разработанный Томпсоном. Язык программирования Си был разработан как инструмент для программистов-практиков. В соответствии с этим главной целью его автора было создание удобного и полезного во всех отношениях языка. Си является орудием системного программиста и позволяет глубоко влезать в самые тонкие механизмы обработки информации на ЭВМ. Хотя язык требует от программиста высокой дисциплины, он не строг в формальных претензиях и допускает краткие формулировки. Си – современный язык. Он включает в себя те управляющие конструкции, которые рекомендованы теорией и практикой программирования. Его структура побуждает программиста использовать в своей работе нисходящее проектирование, структурное программирование и пошаговую разработку модулей. Си – эффективный язык. Его структура позволяет наилучшим образом использовать возможности современных ПЭВМ. Программирование на этом языке отличается компактностью и быстротой исполнения. Си – переносимый или мобильный язык. Это означает, что программа, написанные на этом языке для одной вычислительной системы, может быть перенесена с минимальными изменениями на другую. Си – мощный и гибкий язык. Большая часть операционной системы Unix, компиляторы и интерпретаторы языков Фортран, Паскаль, Лисп, и Бейсик написаны именно с его помощью. Си – удобный язык. Он достаточно структурирован, чтобы поддерживать хороший стиль программирования и вместе с тем не связан жесткими ограничениями.

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

2.

Язык С не обеспечивает никаких встроенных средств для выполнения таких общих операций как ввод-вывод, управление памятью, обработка строк, и т.п.. Вместо этого, такие средства определены в стандартной библиотеке, которую Вы подключаете к вашим программам. Библиотека GNU C, описанная в этом документе, содержит описание всех библиотечных функций, которые определены в соответствии c ANSI C стандартом, учитывая дополнительные особенности, специфические для POSIX-стандарта операционной системы UNIX, и расширений, специфических для GNU-разработок.

Цель этого руководства состоит в том, чтобы сообщить Вам, как использовать средства GNU библиотеки. Мы упомянули особенности ее стандартов, чтобы помочь Вам разобраться с вещами, которые являются потенциально непереносимыми на другие системы. Но переносимость не является основным вопросом данного руководства.

Если мы в этом руководстве описываем что-нибудь как функцию, то она может иметь и макроопределение. Обычно это не имеет никакого значение, потому что ваше макроопределение делает ту же самую вещь, что и функция. В частности, макро-эквиваленты для библиотечных функций вычисляют параметры ровно один раз, так же образом, как и при обращение к функции. Основная причина для этих макроопределений состоит в том, что иногда они могутреализовывать встроенное расширение, которое является значительно быстрее, чем фактическое обращение к функции. Взятие адреса библиотечной функции работает даже, если она определена как макрокоманда, потому что, в этом контексте, имя функции не сопровождается левой круглой скобкой, которая является синтаксически необходимой, чтобы распознать макрообращение. Вы можете иногда не использовать макроопределение функции, возможно, чтобы сделать вашу программу более простой или для отладки. Имеются два способа, сделать это:

Вы можете избегать макроопределения, включая имя функции в круглых скобках. Это работает, потому что имя функции не появляется в синтаксическом контексте, где оно распознаваемо как макрообращение. Вы можете подавить любое макроопределение для целого исходного файла, используя директиву препроцессора "#undef ", если в явно описании средства не указано обратное.Например, предположим, что заголовочный файл "stdlib.h" объявляет функцию abs как

extern int abs ( int);

и также обеспечивает макроопределение для abs. Тогда при выполнении:

#include < stdlib.h >

int f ( int * i) {return (abs (++ * i));}

cсылка на abs может относиться или к макрокоманде или функции. С другой стороны, в каждом из следующих примеров производится ссылка на функцию, а не на макрос.

#include < stdlib.h >

int g ( int * i) {return ((abs) (++ * i));}

#undef abs

int h ( int *i) { return (abs (++*i)); }

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

Зарезервированные имена

Имена всех библиотечных типов, макрокоманд, переменных и функций, которые исходят из ANSI C стандарта, зарезервированы безоговорочно; ваша программа не имеет права переопределять эти имена. Все другие библиотечные имена зарезервированы, если ваша программа явно включает заголовочный файл, который определяет или объявляет их. Для этих ограничений имеются несколько причин: Другие люди, читая ваш код могут запутаться, если Вы использовали функцию exit, чтобы делать что - нибудь полностью отличное от того, что делает стандартная функция выхода. Предотвращение этой ситуации помогает делать ваши программы более простыми, для понимания и способствует модульности. Это лишает пользователя возможности случайно переопределить библиотечную функцию, которая вызывается в соответствии c другими библиотечными функциями. Если переопределение позволялось, те другие функции не будут работать.

Имена, которые начинаются с "SIG", сопровождаемые символом верхнего регистра, зарезервированы для дополнительных имен сигнала. См. раздел 21.2 [Стандартные сигналы]. Имена, которые начинаются с "SIG_" сопровождаемые символом верхнего регистра, зарезервированы для дополнительных действий сигнала. См. раздел 21.3.1 [Обработка видеосигнала]. Имена, начинающиеся со "str", "mem", или "wcs", сопровождаемые символом нижнего регистра, зарезервированы для дополнительной строки и функций массива. См. Главу 5 [Уилиты для работы со строками и массивами].

Имена, которые заканчиваются на "_t" зарезервированы для дополнительных имен типа.

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

Заголовочный файл "dirent.h" резервирует имена с предстоящим "d_".

Заголовочный файл "fcntl.h" резервирует имена с предстоящими "l_", "F_", "O_", и "S_".

Заголовочный файл "grp.h" резервирует имена с предстоящим "gr_".

Заголовочный файл "limits.h" резервирует имена, с приписанным "_MAX".

Заголовочный файл "pwd.h" резервирует имена с предстоящим "pw_".

Заголовочный файл "signal.h" резервирует имена с предстоящим "sa_" и "SA_".

Заголовочный файл "sys/stat.h" резервирует имена с предстоящим "st_" и "S_".

Заголовочный файл "sys/times.h" резервирует имена с предстоящим "tms_".

Заголовочный файл "termios.h" резервирует имена с предстоящим "c_", "V", "I", "O", и "TC", а также имена с предстоящим "B", сопровождаемым цифрой.

Макрокоманды управления особенностями. Управляя макрокомандами, Вы определяете точный набор особенностей, доступный, когда при компиляции исходного файла. Если Вы компилируете ваши программы, используя "gcc -ansi", Вы получаете только ANSI C библиотечные особенности, если Вы явно не запрашиваете дополнительные особенности, определяя одну или большее количество макрокоманд особенностей. См. раздел "Опции Команд GNU CC" в GNU CC Руководстве, для уточнения информации относительно опций GCC. Вы должны определить эти макрокоманды, используя директивы препроцессора "#define" в начале ваших файлов. Эти директивы должны стоять перед любым #include заголовочного файла системы. Лучше всего указывать их самыми первыми в файле, только после комментариев. Вы могли бы также использовать опцию `-D' для GCC, но лучше, если Вы создаете исходные файлы с указанием их собственного

Если Вы определяете эту макрокоманду, функциональные возможности из SVID, включены также как ANSI C, POSIX.1, и POSIX.2.

_GNU_SOURCE (макрос)

Если Вы определяете эту макрокоманду, включены все: ANSI C, POSIX.1, POSIX.2, BSD, SVID, и расширения GNU. В случаях, конфликтов POSIX.1 с BSD, POSIX определения берут верх. Если Вы хотите получить полный эффект _GNU_SOURCE, но установить BSD определениям больший приоритет необходимо примеенить следующую последовательность определений:

#define _GNU_SOURCE

#define _BSD_SOURCE

#define _SVID_SOURCE

Обратите внимание, что, если Вы делаете это, Вы должны компоновать вашу программу с BSD библиотекой совместимости, указывая опцию ` lbsd-compat' транслятору или компоновщику. Обратите внимание: если Вы забудете сделать это, Вы можете получить очень странные ошибки во время выполнения. Мы рекомендуем, чтобы Вы использовали _GNU_SOURCE в новых программах. Если Вы не определяете опцию `-ansi' для GCC и не определяете никакую из этих макрокоманд явнно, то _GNU_SOURCE дает тот же эффект. Когда Вы определяете макрокоманду, чтобы запросить больший класс особенностей, безобидно определить кроме того макрокоманду для подмножества этих особенностей. Например, если Вы определяете _POSIX_ C_SOURCE, то определение _POSIX_SOURCE не имеет никакого эффекта. Аналогично, если Вы определяете _GNU_SOURCE, определяя затем либо _POSIX_SOURCE либо _POSIX_C_SOURCE либо _SVID_SOURCE, также не будет никакого эффекта. Обратите внимание, что особенности _BSD_SOURCE не подмножество любой другой из обеспечиваемых макрокоманд особенностей. Потому что эта макрокоманда определяет особенности BSD, которые берут верх над особенностями POSIX, которые запрашиваются другими макрокомандами. По этой причине, определение _BSD_SOURCE в дополнение к другим макрокомандам особенностей заставляет особенности BSD брать верх при конфликтое с особенностями 2.POSIX . auto double int struct break else long switch register tupedef char extern return void case float unsigned default for signed union do if sizeof volatile continue enum short while

3.

Структура программы.

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

main ( )

{

...

return 0;

}

Если предположить, что main- первая функция, определенная в программе на языке С++, то поскольку ни одна функция не может содержать определения другой функции, следом в тексте будут располагаться определения вспомогатель-ных функций, «неглавных» функций. Их может быть различное количество.

main ( )

{

...

return 0;

}

function 1 ( )

{

}

function 2 ( )

{

}

...

function n ( )

{

}

Функции могут быть описаны в произвольном порядке. Удобно располо-жить их по алфавиту или сгруппировать по определенному признаку.

Единица трансляции, он же исходный модуль, -- один файл с расширением .c или

..cpp (или другими), подлежащий трансляции с помощью компилятора.

Важно понимать, что КАЖДЫЙ исходный модуль (единица трансляции), ОБРАБАТЫВАЕТСЯ

КОМПИЛЯТОРОМ ОТДЕЛЬНО ОТ ОСТАЛЬНЫХ.

Транслятор — программа или техническое средство, выполняющее трансляцию программы.

Транслятор обычно выполняет также диагностику ошибок, формирует словари идентификаторов, выдаёт для печати тексты программы и т. д.

Трансляция программы — преобразование программы, представленной на одном из языков программирования, в программу на другом языке и, в определённом смысле, равносильную первой.

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

4.

Общий вид функции

В общем виде функция выглядит следующим образом:

возвр-тип имя-функции(список параметров)

{

тело функции

}

возвр-тип определяет тип данного, возвращаемого функцией[1]. Функция может возвращать любой тип данных, за исключением массивов список параметров — это список, элементы которого отделяются друг от друга запятыми. Каждый такой элемент состоит из имени переменной и ее типа данных. При вызове функции параметры принимают значения аргументов. Функция может быть и без параметров, тогда их список будет пустым. Такой пустой список можно указать в явном виде, поместив для этого внутри скобок ключевое слово void.

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

f(тип имя_переменной1, тип имя_переменной2,..., тип имя_переменнойN)

Вот, например, два объявления параметров функций, первое из которых правильное, а второе — нет:

f(int i, int k, int j) /* правильное */

f(int i, k, float j) /* неправильное, у переменной k должен быть собственный спецификатор типа */

В языке правила работы с областями действия — это правила, которые определяют, известен ли фрагменту кода другой фрагмент кода или данных, или имеет ли он доступ к этому другому фрагменту. Об областях действия, определяемых в языке С, говорилось в главе 2. Здесь же мы более подробно рассмотрим одну специальную область действия — ту, которая определяется функцией. Каждая функция представляет собой конечный блок кода. Таким образом, она определяет область действия этого блока. Это значит, что код функции является закрытым и недоступным ни для какого выражения из любой другой функции, если только не выполняется вызов содержащей его функции. (Например, нельзя перейти в середину другой функции с помощью goto.) Код, который составляет тело функции, скрыт от остальной части программы, и если он не использует глобальных переменных, то не может воздействовать на другие части программы или, наоборот, подвергаться воздействию с их стороны. Иначе говоря, код и данные, определенные внутри одной функции, без глобальных переменных не могут воздействовать на код и данные внутри другой функции, так как у любых двух разных функций разные области действия.

5.

Аргументы функции

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

/* Возвращает 1, если символ c входит в строку s;

и 0 в противном случае. */

int is_in(char *s, char c)

{

while(*s)

if(*s==c) return 1;

else s++;

return 0;

}

Функция is_in() имеет два параметра: s и d. Если символ c входит в строку s, то эта функция возвращает 1, в противном случае она возвращает 0.

Хотя параметры выполняют специальную задачу, — принимают значения аргументов, передаваемых функции, — они все равно ведут себя так, как и другие локальные переменные. Формальным параметрам функции, например, можно присваивать какие-либо значения или использовать эти параметры в каких-либо выражениях.

Вызовы по значению и по ссылке

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

Вторым способом передачи аргументов подпрограмме является вызов по ссылке. При его применении в параметр копируется адрес аргумента. Это значит, что, в отличие от вызова по значению, изменения значения параметра приводят к точно таким же изменениям значения аргумента.

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

Проанализируйте следующую программу:

#include <stdio.h>

int sqr(int x);

int main(void)

{

int t=10;

printf("%d %d", sqr(t), t);

return 0;

}

int sqr(int x)

{

x = x*x;

return(x);

}

В этом примере в параметр х копируется 10 — значение аргумента для sqr(). Когда выполняется присваивание х=х*х, модифицируется только локальная переменная х. А значение переменной t, использованной в качестве аргумента при вызове sqr(), по-прежнему остается равным 10. Поэтому выведено будет следующее: 100.10.

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

Вызов по ссылке

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

Указатель передается функции так, как и любой другой аргумент. Конечно, в таком случае параметр следует декларировать как один из типов указателей. Это можно увидеть на примере функции swap(), которая меняет местами значения двух целых переменных, на которые указывают аргументы этой функции:

void swap(int *x, int *y)

{

int temp;

temp = *x; /* сохранить значение по адресу x */

*x = *y; /* поместить y в x */

*y = temp; /* поместить x в y */

}

Функция swap() может выполнять обмен значениями двух переменных, на которые указывают х и y, потому что передаются их адреса, а не значения. Внутри функции, используя стандартные операции с указателями, можно получить доступ к содержимому переменных и провести обмен их значений[2].

Помните, что swap() (или любую другую функцию, в которой используются параметры в виде указателей) необходимо вызывать вместе с адресами аргументов[3]. Следующая программа показывает, как надо правильно вызывать swap():

#include <stdio.h>

void swap(int *x, int *y);

int main(void)

{

int i, j;

i = 10;

j = 20;

printf("i и j перед обменом значениями: %d %d\n", i, j);

swap(&i, &j); /* передать адреса переменных i и j */

printf("i и j после обмена значениями: %d %d\n", i, j);

return 0;

}

void swap(int *x, int *y)

{

int temp;

temp = *x; /* сохранить значение по адресу x */

*x = *y; /* поместить y в x */

*y = temp; /* поместить x в y */

}

И вот что вывела эта программа:

i и j перед обменом значениями: 10 20

i и j после обмена значениями: 20 10

В программе переменной i присваивается значение 10, а переменной j — значение 20. Затем вызывается функция swap() с адресами этих переменных. (Для получения адреса каждой из переменных используется унарный оператор &.) Поэтому в swap() передаются адреса переменных i и j, а не их значения.На заметку Язык C++ при помощи параметров-ссылок дает возможность полностью автоматизировать вызов по ссылке. А в языке С параметры-ссылки не поддерживается

Вызов функций с помощью массивов

Подробно о массивах рассказывалось в главе 4. В настоящем же разделе рассказывается о передаче массивов функциям в качестве аргументов. Этот вопрос рассматривается потому, что эта операция является исключением по отношению к обычной передаче параметров, выполняемой путем вызова по значению[4].

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

#include <stdio.h>

#include <ctype.h>

void print_upper(char *string);

int main(void)

{

char s[80];

printf("Введите строку символов: ");

gets(s);

print_upper(s);

printf("\ns теперь на верхнем регистре: %s", s);

return 0;

}

/* Печатать строку на верхнем регистре. */

void print_upper(char *string)

{

register int t;

for(t=0; string[t]; ++t) {

string[t] = toupper(string[t]);

putchar(string[t]);

}

}

Вот что будет выведено в случае фразы "This is a test." (это тест):

Введите строку символов: This is a test.

THIS IS A TEST.

s теперь в верхнем регистре: THIS IS A TEST.

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

После вызова print_upper() содержимое массива s в main() переводится в символы верхнего регистра. Если вам это не нужно, программу можно написать следующим образом:

#include <stdio.h>

#include <ctype.h>

void print_upper(char *string);

int main(void)

{

char s[80];

printf("Введите строку символов: ");

gets(s);

print_upper(s);

printf("\ns не изменялась: %s", s);

return 0;

}

void print_upper(char *string)

{

register int t;

for(t=0; string[t]; ++t)

putchar(toupper(string[t]));

}

Вот какой на этот раз получится фраза "This is a test.":

Введите строку символов: This is a test.

THIS IS A TEST.

s не изменилась: This is a test.

На этот раз содержимое массива не изменилось, потому что внутри print_upper() не изменялись его значения.

Классическим примером передачи массивов в функции является стандартная библиотечная функция gets(). Хотя gets(), которая находится в вашей стандартной библиотеке, и более сложная, чем предлагаемая вам версия xgets(), но с помощью функции xgets() вы сможете получить представление о том, как работает gets().

/* Упрощенная версия стандартной библиотечной функции gets(). */

char *xgets(char *s)

{

char ch, *p;

int t;

p = s; /* xgets() возвращает указатель s */

for(t=0; t<80; ++t){

ch = getchar();

switch(ch) {

case '\n':

s[t] = '\0'; /* завершает строку */

return p;

case '\b':

if(t>0) t--;

break;

default:

s[t] = ch;

}

}

s[79] = '\0';

return p;

}

Функцию xgets() следует вызывать с указателем char *. Им, конечно же, может быть имя символьного массива, которое по определению является указателем char *. В самом начале программы xgets() выполняется цикл for от 0 до 80. Это не даст вводить с клавиатуры строки, содержащие более 80 символов. При попытке ввода большего количества символов происходит возврат из функции. (В настоящей функции gets() такого ограничения нет.) Так как в языке С нет встроенной проверки границ, программист должен сам позаботиться, чтобы в любом массиве, используемом при вызове xgets(), помещалось не менее 80 символов. Когда символы вводятся с клавиатуры, они сразу записываются в строку. Если пользователь нажимает клавишу <Backspase>, то счетчик t уменьшается на 1, а из массива удаляется последний символ, введенный перед нажатием этой клавиши. Когда пользователь нажмет <ENTER>, в конец строки запишется нуль, т.е. признак конца строки. Так как массив, использованный для вызова xgets(), модифицируется, то при возврате из функции в нем будут находиться введенные пользователем символы.

Аргументы функции main(): argv и argc

Иногда при запуске программы бывает полезно передать ей какую-либо информацию. Обычно такая информация передается функции main() с помощью аргументов командной строки. Аргумент командной строки — это информация, которая вводится в командной строке операционной системы вслед за именем программы. Например, чтобы запустить компиляцию программы, необходимо в командной строке после подсказки набрать примерно следующее:

cc имя_программы

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

Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента: argc и argv. Параметр argc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.

Вот простой пример использования аргумента командной строки. На экран выводятся слово Привет и ваше имя, которое надо указать в виде аргумента командной строки.

6.

согласование по колличеству и по типу наверное. ну по ссылки и по значению.

Ссылка. int aaa(int & b,int * c) { if(b==1) return -1;{ else b++; *c=123123} }

7. ну ф-ции которые размещенны в подключаемых библиотеках, подключаешь и юзаешь, гетс и путс в stdio.h/getch/putch/

7.

Создание библиотеки

Библиотека у нас будет иметь два исходных файла: complex.h и complex.c

/*

complex.h

*/

typedef struct

{

double a,b;

} complex_t;

complex_t mul(complex_t x, complex_t y);

/*

complex.c

*/

#include "complex.h"

complex_t mul(complex_t x, complex_t y)

{

complex_t t;

t.a = x.a * y.a - x.b * y.b;

t.b = x.a * y.b + x.b * y.a;

return t;

}

Пример использования библиотеки

Теперь несложно использовать эту библиотеку.

Для этого нужно:

добавить в заголовок c/cpp-файла строчку #include <complex.h> ;

сделать так, чтобы компилятор находил complex.h ;

сделать так, чтобы сборщик (linker) находил файл библиотеки complex .

/*

test_complex.c

*/

#include <stdio.h>

#include "complex.h"

int main()

{

complex_t x = {1,2};

complex_t y = {3,4};

complex_t z = mul(x,y);

printf ("z = (%lf, %lf)\n", z.a, z.b );

return 0;

}

8.

эта ф-ция читает строку и стандартного потока ввода stdin. читает до символа перевода каретки. и заменяет этот символ, символом конца строки

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

char *fgets(char *line, int maxline, FILE *fp)

Функция fgets читает следующую строку ввода (включая и символ новой строки) из файла fp в массив символов line, причем она может прочитать не более MAXLINE-1 символов. Переписанная строка дополняется символом '\0'. Обычно fgets возвращает line, а по исчерпании файла или в случае ошибки - NULL. (Наша getline возвращала длину строки, которой мы потом пользовались, и нуль в случае конца файла.)

Функция вывода fputs пишет строку (которая может и не заканчиваться символом новой строки) в файл.

int fputs(char *line, FILE *fp)

Эта функция возвращает EOF, если возникла ошибка, и неотрицательное значение в противном случае. Библиотечные функции gets и puts подобны функциям fgets и fputs. Отличаются они тем, что оперируют только стандартными файлами stdin и stdout, и кроме того, gets выбрасывает последний символ '\n', a puts его добавляет.

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

/* fgets: получает не более n символов из iop */

char *fgets(char *s, int n, FILE *iop)

{

register int c;

register char *cs;

cs = s;

while (--n > 0 && (с = getc(iop)) != EOF)

if ((*cs++ = c) == '\n')

break;

*cs= '\0';

return (c == EOF && cs == s) ? NULL : s;

}

/* fputs: посылает строку s в файл iop */

int fputs(char *s, FILE *iop)

{

int c;

while (c = *s++)

putc(c, iop);

return ferror(iop) ? EOF : 0;

}

Стандарт определяет, что функция ferror возвращает в случае ошибки ненулевое значение; fputs в случае ошибки возвращает EOF, в противном случае - неотрицательное значение.

С помощью fgets легко реализовать нашу функцию getline:

/* getline: читает строку, возвращает ее длину */

int getline(char *line, int max)

{

if (fgets(line, max, stdin) == NULL)

return 0;

else

return strlen(line);

}

9.

В стандарте языка для ввода используются две библиотечные функции scanf() и gets(). Чаще используется функция gets().

Функция gets().

Данная функция получает строку от стандартного устройства ввода. Функция читает символы до тех пор, пока ей не встретится символ новой строки (\n), который создается при нажатии на . Функция берет все символы до символа \n, присоединяет к ним \0 и передает строку вызывающей программе. Пример:

#include<stdio.h>

void main (void)

{

char name[81];

printf(«Привет!!! Как Вас зовут? \n»);

gets(name);

printf(«Хорошее имя, %s .\n», name);

}

В данном примере gets() примет любое имя (включая пробелы) длиной до 80 символов (один символ для \0). Если ввод строки с помощью gets() прошел нормально, то функция gets() возвращает считанную строку. Если произошла ошибка, или gets() встретила EOF, то она возвращает NULL или нулевой адрес. Таким образом gets() можно использовать при проверке:

while (gets(name)!=NULL)

Отличие gets() и scanf() заключается в том, как они определяют, что достигли конца строки. Для gets() признаком конца строки является \n.

При выводе строк в основном используются две функции: puts() и printf().

Функция puts().

У этой функции только один аргумент, являющийся указателем строки. Рассмотрим пример:

#include<stdio.h>

#define DEF “Я есть строка”

void main (void)

{

static char str1[] = “Массив инициализирован. ”;

static char *str1= “Указатель инициализирован. ”;

puts(“Аргумент функции puts().”);

puts(DEF);

puts(str1);

puts(str2);

puts(&str1[4]);

puts(str2+4);

}

Результат работы программы следующий:

Аргумент функции puts().

Я есть строка.

Массив инициализирован.

Указатель инициализирован.

ив инициализирован.

атель инициализирован.

Этот пример напоминает, что фразы в кавычках и имена строк символьных массивов являются указателем. Обратите внимание на два последних оператора. Указатель &str1[4] ссылается на пятый элемент массива str1. Этот элемент содержит символ “и”. Функция puts() использует его в качестве начальной точки. Аналогично и str2+4 ссылается на ячейку памяти, содержащую “а”, и с нее начинается вывод строки.

Функция puts() прекращает свою работу, если встречается символ \0. (лучше, если он есть). Нельзя делать следующим образом:

static char dount[]={‘H’,’E’,’E’};

puts(dount);

Т.к. \0 отсутствует, puts() не знает, когда ей остановиться. Будет перебирать ячейки памяти, следующие за dount, бо тех пор, пока не найдет где-нибудь\0.

Замечание: любая строка, выводимая puts(), начинается с новой строки, то есть

если puts() находит \0, она заменяет его символом \n и затем выводит

строку.

10.

Начнем с функции вывода printf(), которая выполняет форматируемый вывод в стандартный поток stdout. Это означает, что значения переменных, которые хранятся в памяти в двоичном виде, при выводе в поток (на экран) переводятся в символьный вид, причем вид преобразования задается спецификатором формата. Спецификаторы формата задаются как составная часть обязательного первого аргумента — форматной строки. После форматной строки задается список выражений, значения которых должны выдаваться на экран. В качестве выражений допускается задавать и переменные. Функция возвращает количество выведенных символов. Если произошел сбой, то возвращается отрицательное значение.

Спецификатор формата начинаются символом % (процент), вслед за которым прописывается код формата. Количество аргументов-переменных должно в точности соответствовать количеству спецификаторов формата. При отсутствии списка переменных функция просто выводит в поток (на экран) форматную строку.

Спецификация формата имеет следующий вид:

%[флаг][ширина][.точность][h|l|L]тип

Здесь квадратные скобки означают, что соответствующий элемент спецификации может отсутствовать. В простейшем виде спецификатор формата записывается как

%тип

Тип задается одной буквой (табл. 8.2), и определяет, в каком виде предстанет значение переменной в потоке (на экране). Так как знак процента % используется как управляющий, то для вывода его в поток надо прописать его в форматной строке дважды %%.

Тип Вид в потоке

c Символ

s Строка символов

d, i Целое десятичное со знаком

o Целое восьмеричное

u Целое десятичное без знака

x, X Целое шестнадцатеричное

f Дробное число в фиксированном формате

e, E Дробное число в научном формате

g, G Дробное число в научном или фиксированном формате

p Указатель (в шестнадцатеричном виде)

Ширина показывает, сколько символов будет выведено в поток. В это число входят все символы: и знак, и десятичная точка для дробных чисел. Если ширина окажется фактически меньше, чем требуется для вывода, то все равно значение выводится полностью. Точность задает количество цифр дробной части и применяется обычно только для дробных чисел.

А теперь про ввод

Парная для printf() функция scanf() выполняет форматируемый ввод из стандартного потока stdin в переменные программы. Так как stdin "привязан" к клавиатуре, то при вводе, очевидно, выполняется преобразование из символьного вида во внутренний двоичный формат. Первым параметром тоже является форматная строка, за которой следует список адресов переменных, куда требуется поместить задаваемые значения. Функция возвращает количество успешно введенных значений. В случае ошибки функция scanf() возвращает системную константу EOF. Обычно эта константа определена как –1.

Символы в форматной строке делятся на три вида: спецификаторы формата, разделители и прочие. К разделителям относятся пробел, табуляция ('\t') и символ конца строки ('\n'). Спецификаторы формата — такие же, как и для функции printf()

риведем еще несколько тонкостей, связанных со вводом строк и символов:

1. Спецификатор %c читает очередной символ, в том числе и символ-разделитель. Чтобы пропустить один символ-разделитель и прочитать следующий основной символ, используйте %1s.

2. Аргументом для спецификатора %Wc (W — ширина поля) должен быть массив символов, состоящий из W элементов (char arg[W]).

3. Множество символов, заключенное в квадратные скобки [], может быть подставлено вместо символа типа s.

11.

Операторы

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

Это простой оператор:

A := B;

Это составной или структурированный оператор:

begin

B := C;

A := B;

end;

Простые операторы

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

MyValue :=

MyValue + (SomeVeryLongStatement / OtherLongStatement);

Составные операторы

Составные операторы всегда заканчиваются точкой с запятой.

begin

MyStatement;

MyNext Statement;

MyLastStatement;

end;

11.

Согласно принятой нами терминологии, любое законченное предложение на языке C++ называется оператором. Рассмотрим множество БНФ, определяющих синтаксис операторов.

Оператор ::= ОператорВыражение

::= Объявление

::= СоставнойОператор

::= ПомеченныйОператор

::= ОператорПерехода

::= ВыбирающийОператор

::= ОператорЦикла

ОператорВыражение ::= [Выражение];

Судя по последней форме Бэкуса-Наура, любое правильно построенное выражение (построенное по правилам грамматики), заканчивающееся точкой с запятой, является оператором C++. Мы уже второй раз сталкиваемся с пустым оператором (достаточно внимательно посмотреть на последнюю форму Бэкуса-Наура). Первый раз мы встретили пустой оператор при анализе объявления. Ничего удивительного. Объявление - это тоже оператор. Пустой оператор имеет особое назначение. Он используется везде, где по правилам синтаксиса обязательно требуются операторы, а по алгоритму раазрабатываемой программы не нужно ни одного. Эти ситуации подробно будут рассмотрены ниже. Оператор объявления мы уже рассмотрели ранее. На очереди - составной оператор.

СоставнойОператор ::= {[СписокОператоров]}

Что такое список операторов - также уже известно. Судя по последней БНФ, составной оператор (даже пустоой) всегда начинается разделителем { и завершается разделителем }. Кроме того, составной оператор может быть абсолютно пустым (между двумя фигурными скобками может вообще ничего не стоять).

Так что конструкция

{};

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

Для исключения путаницы в понятиях "операция" и "оператор", отметим, что оператор - это наименьшая исполняемая единица программы. Различают операторы выражения, действие которых состоит в вычислении заданных выражений (например: a = sin(b)+c; j++;), операторы объявления, составные операторы, пустые операторы, операторы метки, цикла и т.д. Для обозначения конца оператора в языке Си используется точка с запятой. Что касается составного оператора (или блока), представляющего собой набор логически связанных операторов, помещенных между открывающей ({) и закрывающей (}) фигурными скобками ("операторными скобками"), то за ним точка с запятой не ставится. Отметим, что блок отличается от составного оператора наличием определений в теле блока.

12.

Конструкции принятия решений позволяют программисту проверять усло-вия и выбирать направление действий.

Одним из видов конструкции принятия решений являются циклы.

В С ++ применяются:

•одноальтернативный оператор if.

•двухальтернативный оператор if-else.

•многоальтернативный оператор switch

•вложенные конструкции принятия решений

•операторы try, catch и throw

•оператор цикла for

•оператор цикла do-while

•оператор цикла while

•пропуск итерации

•выход из циклов

•вложенные циклы

Одноальтернативный оператор условия if.

if <условие> оператор; - простая форма

Двухальтернативный оператор условия if-else.

Двухальтернативный оператор if-else обеспечивает два альтернативных направления действий в зависимости от значения проверяемого булева условия.

Многоальтернативный оператор условия IF - ELSE.

If (условие1) <оператор1>;|<последов.опер.1>

Else if (условие2) <оператор2>;

...

else if (условие n)<оператор n1>;

else <оператор n2>;

Оператор SWITCH.

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

Правила использования оператора SWITCH сводятся к следующему:

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

2. Значение после каждой метки case должно быть константой.

3. С++ не поддерживает метки case c диапазоном значений. В этом случае каждое значение из диапазона должно появляться с отдельной меткой case.

4. Окончание оператора case обычно отмечается словом break. Это вызывает пе-реход к выполнению первого оператора, который следует после SWITCH. Если break не включен, то выполнение будет продолжаться со следующего опера-тора case.

5. Предложение default - всеохватывающее, но оно не обязательно, если вы хо-тите проверить только отдельный ряд случаев.

6. Ряд операторов в каждой метке case или в групповых метках case можно не за-ключать в фигурные скобки.

13.

Формат оператора:

if (выражение) оператор-1; [else оператор-2;]

Выполнение оператора if начинается с вычисления выражения.

Далее выполнение осуществляется по следующей схеме:

- если выражение истинно (т.е. отлично от 0), то выполняется оператор-1.

- если выражение ложно (т.е. равно 0),то выполняется оператор-2.

- если выражение ложно и отсутствует оператор-2 (в квадратные скобки заключена необязательная конструкция), то выполняется следующий за if оператор.

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

Пример:

if (i < j) i++: else { j = i-3; i++; }

Этот пример иллюстрирует также и тот факт, что на месте оператор-1, так же как и на месте оператор-2 могут находиться сложные конструкции.

Допускается использование вложенных операторов if. Оператор if может быть включен в конструкцию if или в конструкцию else другого оператора if. Чтобы сделать программу более читабельной, рекомендуется группировать операторы и конструкции во вложенных операторах if, используя фигурные скобки. Если же фигурные скобки опущены, то компилятор связывает каждое ключевое слово else с наиболее близким if, для которого нет else.

14.

Оператор switch предназначен для организации выбора из множества различных вариантов. Формат оператора следующий:

switch ( выражение ) { [объявление] : [ case константное-выражение1]: [ список-операторов1] [ case константное-выражение2]: [ список-операторов2] : : [ default: [ список операторов ]] }

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

Значение этого выражения является ключевым для выбора из нескольких вариантов. Тело оператора smitch состоит из нескольких операторов, помеченных ключевым словом case с последующим константным-выражением. Следует отметить, что использование целого константного выражения является существенным недостатком, присущим рассмотренному оператору.

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

Все константные выражения в операторе switch должны быть уникальны. Кроме операторов, помеченных ключевым словом case, может быть, но обязательно один, фрагмент помеченный ключевым словом default.

Список операторов может быть пустым, либо содержать один или более операторов. Причем в операторе switch не требуется заключать последовательность операторов в фигурные скобки.

Отметим также, что в операторе switch можно использовать свои локальные переменные, объявления которых находятся перед первым ключевым словом case, однако в объявлениях не должна использоваться инициализация.

Схема выполнения оператора switch следующая:

- вычисляется выражение в круглых скобках;

- вычисленные значения последовательно сравниваются с константными выражениями, следующими за ключевыми словами case;

- если одно из константных выражений совпадает со значением выражения, то управление передается на оператор, помеченный соответствующим ключевым словом case;

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

Отметим интересную особенность использования оператора switch: конструкция со словом default может быть не последней в теле оператора switch. Ключевые слова case и default в теле оператора switch существенны только при начальной проверке, когда определяется начальная точка выполнения тела оператора switch. Все операторы, между начальным оператором и концом тела, выполняются вне зависимости от ключевых слов, если только какой-то из операторов не передаст управления из тела оператора switch. Таким образом, программист должен сам позаботится о выходе из case, если это необходимо. Чаще всего для этого используется оператор break.

Для того, чтобы выполнить одни и те же действия для различных значений выражения, можно пометить один и тот же оператор несколькими ключевыми словами case.

Пример:

int i=2; switch (i) { case 1: i += 2; case 2: i *= 3; case 0: i /= 2; case 4: i -= 5; default: ; }

15.

Цикл while предназначен для повторного вычисления блока операторов, пока остается истинным задаваемое в нем выражение-условие. Его общий синтаксис имеет две формы:

МЕТКА while (ВЫРАЖЕНИЕ) БЛОК

МЕТКА while (ВЫРАЖЕНИЕ) БЛОК continue БЛОК1

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

Оператор while выполняется по следующей схеме. Вычисляется выражения-условия ВЫРАЖЕНИЕ. Если оно истинно, то выполняются операторы БЛОК. В противном случае оператор цикла завершает свою работу и передает управление следующему после него оператору программы (цикл 1 примера 5.5). Таким образом, оператор цикла while является управляющей конструкцией цикла с предусловием: сначала проверяется условие завершения цикла, а потом только тело цикла, определяемое операторами БЛОК. Поэтому может оказаться, что тело цикла не будет выполнено ни одного раза, если при первом вхождении в цикл условие окажется ложным (цикл 3 примера 5.5).

Вместо ключевого слова while можно использовать ключевое слово until. В этом случае управляющая конструкция называется циклом until, который отличается от разобранного цикла while тем, что его тело выполняется, только если выражение условия ложно. Блок операторов БЛОК!, задаваемый после ключевого слова continue, выполняется всякий раз, когда осуществляется переход на выполнение новой итерации. Это происходит после выполнения последнего оператора тела цикла или при явном переходе на следующую итерацию цикла командой next. Блок continue на практике используется редко, но с его помощью можно строго определить цикл for через оператор цикла while.

16.

Оператор цикла do while называется оператором цикла с постусловием и используется в тех случаях, когда необходимо выполнить тело цикла хотя бы один раз. Формат оператора имеет следующий вид:

do тело while (выражение);

Схема выполнения оператора do while :

1. Выполняется тело цикла (которое может быть составным оператором).

2. Вычисляется выражение.

3. Если выражение ложно, то выполнение оператора do while заканчивается и выполняется следующий по порядку оператор. Если выражение истинно, то выполнение оператора продолжается с пункта 1.

Чтобы прервать выполнение цикла до того, как условие станет ложным, можно использовать оператор break.

Операторы while и do while могут быть вложенными.

Пример:

int i,j,k; ... i=0; j=0; k=0; do { i++; j--; while (a[k] < i) k++; } while (i<30 && j<-30);

17.

Цикл FOR.

Цикл for в C++ отличается большей гибкостью, поскольку с его помощью можно организовать как фиксированные, так и условные итерации.

for (< инициализация переменных управления циклом>;

<проверка продолжения цикла>;

< модификация переменных управления циклом, часто их приращение или уменьшения>)

<оператор>;

Пример:

for (i=0, i<10; i++)

cout << "i=" <Оператор for имеет три компоненты, каждый из которых необязателен. Пер-вый компонент инициализирует переменные управления циклом. Второй - это условие, которое проверяет, будет ли цикл выполнять следующую итерацию, по-следний компонент - приложение, которое изменяет переменные управления циклом; часто это просто операции инкримента и / или декримента.

Открытые циклы, использующие циклы for.

Если оставить все три компонента цикла пустыми, результатом будет от-крытый цикл. (open loop).

C++ позволяют выходить из них следующими способами:

1.Оператор break вызывает переход к выполнению кода, следующего за текущим циклом, во многом подобен тому, как он мог бы быть использован, чтобы про-должить выполнение вне оператора switch. Используйте оператор break, если вы хотите выйти из цикла и продолжить работу, оставшейся части программы.

2.Оператор return осуществляет возврат из текущей функции (включая main). О return и функциях далее.

3.Оператор throw вызывает генерацию исключения. Это используется, если произошла ошибка, и вы можете продолжать выполнение оставшейся части программы без какого-либо обработчика ошибок. Однако, применяйте этот метод с осторожностью; исключения предназначены для использования в не-штатных обстоятельствах - в случае ошибок.

4.В самых чрезвычайных случаях выйти из программы можно с помощью функ-ции exit (STDLIB.H). Эту функцию используют только в крайней необходи-мости, когда не остается никакой надежды на восстановления работоспособ-ности программы. Функция exit прекратит выполнение итерации и приведет к выходу из программы.

18.

При выполнении операций происходят неявные преобразования типов в следующих случаях:

- при выполнении операций осуществляются обычные арифметические преобразования (которые были рассмотрены выше);

- при выполнении операций присваивания, если значение одного типа присваивается переменной другого типа;

- при передаче аргументов функции.

Кроме того, в Си есть возможность явного приведения значения одного типа к другому.

В операциях присваивания тип значения, которое присваивается, преобразуется к типу переменной, получающей это значение. Допускается преобразования целых и плавающих типов, даже если такое преобразование ведет к потере информации.

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

Преобразование целого со знаком к плавающему типу происходит без потери] информации, за исключением случая преобразования значения типа long int или unsigned long int к типу float, когда точность часто может быть потеряна.

Преобразование целых типов без знака. Целое без знака преобразуется к более короткому целому без знака или со знаком путем усечения старших битов. Целое без знака преобразуется к более длинному целому без знака или со знаком путем дополнения нулей слева.

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

Целые значения без знака преобразуются к плавающему типу, путем преобразования целого без знака к значению типа signed long, а затем значение signed long преобразуется в плавающий тип. Преобразования из unsigned long к типу float, double или long double производятся с потерей информации, если преобразуемое значение больше, чем максимальное положительное значение, которое может быть представлено для типа long.

Преобразования плавающих типов. Величины типа float преобразуются к типу double без изменения значения. Величины double и long double преобразуются к float c некоторой потерей точности. Если значение слишком велико для float, то происходит потеря значимости, о чем сообщается во время выполнения.

При преобразовании величины с плавающей точкой к целым типам она сначала преобразуется к типу long (дробная часть плавающей величины при этом отбрасывается), а затем величина типа long преобразуется к требуемому целому типу. Если значение слишком велико для long, то результат преобразования не определен.

Преобразования из float, double или long double к типу unsigned long производится с потерей точности, если преобразуемое значение больше, чем максимально возможное положительное значение, представленное типом long.

Преобразование типов указателя. Указатель на величину одного типа может быть преобразован к указателю на величину другого типа. Однако результат может быть не определен из-за отличий в требованиях к выравниванию и размерах для различных типов.

Указатель на тип void может быть преобразован к указателю на любой тип, и указатель на любой тип может быть преобразован к указателю на тип void без ограничений. Значение указателя может быть преобразовано к целой величине. Метод преобразования зависит от размера указателя и размера целого типа следующим образом:

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

- если указатель больше, чем размер целого типа, то указатель сначала преобразуется к указателю с тем же размером, что и целый тип, и затем преобразуется к целому типу.

Целый тип может быть преобразован к адресному типу по следующим правилам:

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

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

Преобразования при вызове функции. Преобразования, выполняемые над аргументами при вызове функции, зависят от того, был ли задан прототип функции (объявление "вперед") со списком объявлений типов аргументов.

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

Эти преобразования выполняются независимо для каждого аргумента. Величины типа float преобразуются к double, величины типа char и short преобразуются к int, величины типов unsigned char и unsigned short преобразуются к unsigned int. Могут быть также выполнены неявные преобразования переменных типа указатель. Задавая прототипы функций, можно переопределить эти неявные преобразования и позволить компилятору выполнить контроль типов.

Преобразования при приведении типов. Явное преобразование типов может быть осуществлено посредством операции приведения типов, которая имеет формат:

( имя-типа ) операнд.

18,19

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

Если один из операндов в выражении имеет тип long double, то остальные тоже преобразуются к типу long double.

В противном случае, если один из операндов в выражении имеет тип double, то остальные тоже преобразуются к типу double.

В противном случае, если один из операндов в выражении имеет тип float, то остальные тоже преобразуются к типу float.

В противном случае, если один из операндов в выражении имеет тип unsigned long, то остальные тоже преобразуются к типу unsigned long.

В противном случае, если один из операндов в выражении имеет тип long, то остальные тоже преобразуются к типу long.

В противном случае, если один из операндов в выражении имеет тип unsigned, то остальные тоже преобразуются. к типу unsigned.

В противном случае все операнды преобразуются к типу int. При этом тип char преобразуется в int со знаком; тип unsigned char в int, у которого старший байт всегда нулевой; тип signed char в int, у которого в знаковый разряд передается знак из сhar; тип short в int (знаковый или беззнаковый).

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

В языке Си можно явно указать тип любого выражения. Для этого используется операция преобразования ("приведения") типа. Она применяется следующим образом:

(тип) выражение

(здесь можно указать любой допустимый в языке Си тип).

Рассмотрим пример:

int a = 30000;

float b;

........

b = (float) a * 12;

(переменная a целого типа явно преобразована к типу float; если этого не сделать, то результат будет потерян, т.к. a * 12 > 32767).

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

19.

В языке программирования C-51 используются арифметические операции, результат которых зависит от типа операндов:

+ суммирование

- вычитание

* умножение

/ деление

% вычисление остатка от целочисленного деления

Примеры выражений, использующие арифметические операции:

А+В

А+В-С

A*T+F/D

A*(B+C)-(D-E)/F

20.

К логическим операциям относятся операция логического И (&&) и операция логического ИЛИ (||). Операнды логических операций могут быть целого типа, плавающего типа или типа указателя, при этом в каждой операции могут участвовать операнды различных типов.

Операнды логических выражений вычисляются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, то второй операнд не вычисляется.

Логические операции не вызывают стандартных арифметических преобразований. Они оценивают каждый операнд с точки зрения его эквивалентности нулю. Результатом логической операции является 0 или 1, тип результата int.

Операция логического И (&&) вырабатывает значение 1, если оба операнда имеют нулевые значения. Если один из операндов равен 0, то результат также равен 0. Если значение первого операнда равно 0, то второй операнд не вычисляется.

Операция логического ИЛИ (||) выполняет над операндами операцию включающего ИЛИ. Она вырабатывает значение 0, если оба операнда имеют значение 0, если какой-либо из операндов имеет ненулевое значение, то результат операции равен 1. Если первый операнд имеет ненулевое значение, то второй операнд не вычисляется.

В языке программирования C-51 также определено несколько одноместных арифметических операций:

'-' изменение знака операнда на противоположное значение

'+' знак плюс не влияет на значение операнда

++ увеличение значения операнда на единицу

-- уменьшение значения операнда на единицу

Для одноместной операции требуется один операнд, которому она предшествует. Например:

P3 = -5; //Присвоить порту P3 значение числа -5

Бинарными (или двуместными) операциями в математике и программировании называют такие функции, которые принимают два аргумента (операнда), как правило, одного типа, и выдают одно значение в качестве результата.

Примеры:

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

конкатенация (сложение) текстовых строк;

логические операции ("и", "или", "исключающее или", "импликация" и др.);

отношения ("больше", "меньше", "равно" и т.п.), если рассматривать их как операции, дающие истинностные значения.

Бинарные операции, как правило, записываются в инфиксном формате, когда знак операции (оператор) ставится между операндами. Например: A+B или X/Y.

21.

Адресные операции: указатели в адр. Опер-ях

Адресные операции

& Операция получения адреса операнда.

Операндом может быть любое l-выражение. Операция возвращает адрес объекта или функции, на который ссылается операнд. Операция невыполнима по отношению к объектам, определённым со спецификатором register, поскольку существует вероятность того, что они не располагаются в памяти и не имеют определённого адреса.

* Операция обращения по адресу или операция косвенного обращения.

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

22.

Массив – это упорядоченная совокупность переменных, которые имеют общее имя и одинаковый тип. Имя массива – это общее имя переменных, входящих в массив.

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

Положение элемента в массиве однозначно определяется его индексом (для одномерного массива) или индексами (для многомерного массива). Размерность массива – это количество индексов у каждого элемента массива.

Каждый индекс изменяется в некотором диапазоне [a,b]. Диапазон [a,b] называется граничной парой, a - нижней границей, b - верхней границей индекса. При объявлении массива границы задаются выражениями. Если при объявлении массива все границы заданы константными выражениями, то число элементов массива известно в момент его объявления и ему может быть выделена память уже на этапе трансляции. Такие массивы называются статическими.

В языке С/C++ все массивы являются статическими; более того, все массивы являются 0-базируемыми. Это означает, что нижняя граница всех индексов массива фиксирована и равна нулю. Поэтому при описании массива указывается количество возможных значения для каждого индекса (начиная с 0).

Для размещения массива в памяти ЭВМ отводится поле памяти, размер которого определяется типом, длиной и размерностью массива. В языке C/C++ эта информация задается в разделе описаний. Массив описывается так:

int A[10]; // одномерный массив из 10 целых элементов. Индекс изменяется от 0 до 9.

Всякий раз, когда в выражении появляется идентификатор типа массива, он преобразуется в указатель на первый член массива. Из-за преобразований массивы не являются адресами. По определению операция индексирования [] интерпретируется таким образом, что E1[E2] идентично *((E1)+(E2)). В силу правил преобразования, применяемых к +, если E1 массив и E2 целое, то E1[E2] относится к E2-ому члену E1. Поэтому, несмотря на такое проявление асимметрии, индексирование является коммутативной операцией.

Это правило сообразным образом применяется в случае многомерного массива. Если E является n-мерным массивом ранга i*j*...*k, то возникающее в выражении E преобразуется в указатель на (n-1)-мерный массив ранга j*...*k. Если к этому указателю, явно или неявно, как результат индексирования, применяется операция *, ее результатом является (n-1)-мерный массив, на который указывалось, который сам тут же преобразуется в указатель.

23.

Одномерные массивы

Одномерный массив - это фиксированное количество элементов одного и того же типа, объединенных общим именем, где каждый элемент имеет свой номер. Нумерация элементов массива в С# начинается с нуля, то есть, если массив состоит из 10 элементов, то его элементы будут иметь следующие номера: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Одномерный массив в С# реализуется как объект, поэтому его создание представляет собой двухступенчатый процесс. Сначала объявляется ссылочная переменная на массив, затем выделяется память под требуемое количество элементов базового типа, и ссылочной переменной присваивается адрес нулевого элемента в массиве. Базовый тип определяет тип данных каждого элемента массива. Количество элементов, которые будут храниться в массиве, определяется размер массива.

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

Форма записи

базовый_тип [] имя__массива;

Например:

int [] a;

Описана ссылка на одномерный массив, которая в дальнейшем может быть использована:

для адресации на уже существующий массив;

передачи массива в метод в качестве параметра

отсроченного выделения памяти под элементы массива.

базовый_тип [] имя__массива = new базовый_тип [размер];

Например:

int []a=new int [10];

Объявлен одномерный массив заданного типа и выделена память под одномерный массив указанной размерности. Адрес данной области памяти записан в ссылочную переменную. Элементы массива равны нулю.

Замечание. Надо отметить, что в C# элементам массива присваиваются начальные значения по умолчанию в зависимости от базового типа. Для арифметических типов - нули, для ссылочных типов - null, для символов - пробел.

базовый_тип [] имя__массива={список инициализации};

Например:

int []a={0, 1, 2, 3}; Выделена память под одномерный массив, размерность которого соответствует количеству элементов в списке инициализации. Адрес этой области памяти записан в ссылочную переменную. Значение элементов массива соответствует списку инициализации.

Обращения к элементам массива происходи с помощью индекса, для этого нужно указать имя массива и в квадратных скобках его номер. Например, a[0], b[10], c[i].

Многомерные массивы

Многомерные массивы имеют более одного измерения. Чаще всего используются двумерные массивы, которые представляют собой таблицы. Каждый элемент массива имеет два индекса, первый определяет номер строки, второй - номер столбца, на пересечении которых находится элемент. Нумерация строк и столбцов начинается с нуля.

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

тип [,] имя__массива;

тип [,] имя__массива = new тип [размер1, размер2];

тип [,] имя__массива={{элементы 1-ой строки}, … , {элементы n-ой строки}};

тип [,] имя__массива= new тип [,]{{элементы 1-ой строки}, … ,{элементы n-ой строки}};

строки}};

Например:

int [,] a;

int [,] a= new int [3, 4];

int [,] a={{0, 1, 2}, {3, 4, 5}};

int [,] a= new int [,]{{0, 1, 2}, {3, 4, 5}};

24.

Создание указателей

Указатель — это переменная специального типа. Она хранит не какое-то числовое значение, а адрес (номер первого байта в памяти компьютера), по которому хранится какая-то другая переменная. При создании указателя необходимо задать тип переменной, на которую он указывает. Синтаксис объявления указателя такой:

имя_типа * идентификатор;

Пример:

int * pi;

float * pf, f;

double * ps, * pt;

В первой строке этого примера объявлены переменная pi, являющейся указателем на тип int (то есть в ячейке памяти, на которую указывает pi должна хранится переменная типа int). Во второй строке объявлены переменная pf, являющейся указателем на тип float и переменная f типа float. Обратите особое внимание на эту строчку: для того, чтобы объявить несколько указателей в одной строке, необходимо перед идентификатором каждого из них поставить символ *. А еще лучше объявлять в одной строке только одну переменную. В третей строке объявляется два указателя на тип double: ps и pt.

Сразу после объявления значение указателя не определено, то есть он может указывать в произвольную ячейку памяти, поэтому пользоваться им нельзя. Иначе вы в лучшем случае получите ошибку segmentation fault, в худшем — программа будет работать непредсказуемо.

Создание массива при помощи оператора new

Для создания массива в динамической памяти используется оператор new с указанием размера создаваемого массива. Пример:

int * pi; // Объявить указатель pa

pi=new int[n]; // Создать массив из n элементов типа int

Теперь с pi можно работать, как с обычным массивом из n элементов типа int: становятся доступны элементы pi[0], pi[1], ..., pi[n-1].

Существует единственное числовое значение, которое можно присвоить непосредственно указателю: это 0 (то есть присваивание pi=0 разрешено, а присваивание pi=1 — нет). Нулевой адрес — особый, по этому адресу не может хранится ни одна переменная. То есть указатель, имеющий нулевое значение указывает в "никуда", к такому указателю нельзя применить оператор разыменования.

Оператор new использует функцию операционной системы для выделения памяти. Если затребованный размер памяти слишком большой (а также при попытке создать массив из нуля или отрицательного числа элементов), операционная система не будет выделять память и оператор new вернет нулевое значение. Если это нулевое значение будет присвоено указателю, к которому впоследствии будет применен оператор разыменования или оператор обращения к элементу массива, то программа аварийно завершит работу с ошибкой segmentation fault. Чтобы быть уверенным, что оператор new был выполнен удачно, необходимо сразу после его вызова проверить значение, которое он вернул и в случае, если оно равно 0, выполнить какие-либо особенные действия, например, вывести сообщение о невозможности выделения необходимого объема памяти. Например:

int n=1000000000;

int *pi=new int[n];

if(pi==0)

{

cout<<"Невозможно создать массив из "<<n<<" элементов int";

return 1; // Завершаем работу функции main

}

Освобождение памяти

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

delete pi;

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

Связь между указателями и массивами

Итак, с указателем можно работать, как с массивом, в частности, к нему можно применять оператор доступа к элементу []. Верно и обратное — с массивом можно работать, как с указателем. Если мы объявили массив int arr[10], то мы можем использовать идентификатор arr без указания элемента массива, как синоним для указателя на начало массива. С массивами можно выполнять все операции, которые можно делать с указателями, кроме тех, которые меняют значение самого указателя: =, +=, -=, ++. --, то есть единственное отличие массивов от указателей заключается в том, что значение указателя можно изменить в программе, а значение массива (то есть адрес его начала) — нет.

25.

Обращение к элементам массива

Массив позволяет хранить несколько значений в одной и той же переменной. Для обращения к определенным значениям, хранящимся в массиве, используйте значение индекса, которое указывает на требуемый элемент. Например, для обращения к первому элементу массива test_scores вы должны использовать значение индекса 0. Для обращения ко второму элементу используйте индекс 1. Подобно этому, для обращения к третьему элементу используйте индекс 2. Как показано на рис. 16.1, первый элемент массива всегда имеет индекс 0, а значение индекса последнего элемента на единицу меньше размера массива:

Важно помнить, что C++ всегда использует 0 для индекса первого элемента массива, а индекс последнего элемента на единицу меньше размера массива. Следующая программа ARRAY. CPP создает массив с именем values, который вмещает пять целочисленных значений. Далее программа присваивает элементам значения 100, 200, 300, 400 и 500:

#include <iostream.h>

void main(void)

{

int values[5]; // Объявление массива

values[0] = 100;

values[1] = 200;

values[2] = 300;

values[3] = 400;

values [4] = 500;

cout << "Массив содержит следующие значения" << endl;

cout << values [0] << ' ' << values [1] << ' ' << values [2] << ' ' << values [3] << ' ' << values [4] << endl;

}

Как видите, программа присваивает первое значение элементу 0 (va lues[0]). Она также присваивает последнее значение элементу 4 (размер Массива (5) минус 1).

Использование индекса для обращения к элементам массива

Массив позволяет вашим программам хранить несколько значений внутри одной и той же переменной. Для обращения к определенному значению внутри массива программы используют индекс. Говоря кратко, значение индекса указывает требуемый элемент массива. Все массивы C++ начинаются с элемента с индексом 0. Например, следующий оператор присваивает значение 100 первому элементу массива с именем scores:

scores[0] = 100;

Когда ваша программа объявляет массив, она указывает количество элементов, которые массив может хранить. Например, следующий оператор объявляет массив, способный хранить 100 значений типа int.

int scores[100];

В данном случае массив представляет собой элементы от scores[0] до scores[99].

Использование индексной переменной

Если ваши программы используют массив, обычной операцией является использование индексной переменной для обращения к элементам массива. Например, предположим, что переменная / содержит значение 3, следующий оператор присваивает значение 400 элементу values[3J:

values[i] = 400;

Следующая программа SHOWARRA.CPP использует индексную переменную i внутри цикла for для вывода элементов массива. Цикл for инициализирует i нулем, так что программа может обращаться к элементу values[O]. Цикл for завершается, когда i больше 4 (последний элемент массива):

#include <iostream.h>

void main (void)

{

int values[5]; // Объявление массива int i;

values[0] = 100;

values[1] = 200;

values[2] = 300;

values[3] = 400;

values[4] = 500;

cout << "Массив содержит следующие значения" << endl;

for (i = 0; i < 5; i++) cout << values [i] << ' ';

}

Каждый раз, когда цикл for увеличивает переменную i, программа может обратиться к следующему элементу массива. Экспериментируйте с этой программой, изменяя цикл for следующим образом:

for (i = 4; i >= 0; i--) cout << values [i] << ' ';

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

26.

Многомерные массивы

Многомерные массивы - это массивы с более чем одним индексом.

Чаще всего используются двумерные массивы.

При описании многомерного массива необходимо указать C++, что массив имеет более чем одно измерение.

Пример 1.

int t[3][4];

Описывается двумерный массив, из 3 строк и 4 столбцов.

Элементы массива:

t[0][0] t[0][1] t[0][2] t[0][3]

t[1][0] t[1][1] t[1][2] t[1][3]

t[2][0] t[2][1] t[2][2] t[2][3]

При выполнении этой команды под массив резервируется место. Элементы массива располагаются в памяти один за другим.

Пусть это линейка памяти:

Рисунок 1.7.1.

Пример 2.

int temp [3] [15] [10];

резервируется место под 3-х мерный массив.

В памяти многомерные массивы представляются как одномерный массив, каждый из элементов которого, в свою очередь, представляет собой массив.

Рассмотрим на примере двумерного массива.

int a[3][2]={4, l, 5,7,2, 9};

Представляется в памяти:

Таблица 1.7.3.

a[0][0] заносится значение 4

a[0][1] заносится значение 1

a[1][0] заносится значение 5

a[1][1] заносится значение 7

a[2][0] заносится значение 2

a[2][1] заносится значение 9

Второй способ инициализации при описании массива

int а[3][2]={ {4,1}, {5, 7}, {2, 9} };

Обращение к элементу массива производится через индексы.

cout<< а[0][0];

Выдаст значение 4.

cout << a[1][1];

Выдаст знaчение 7.

Программа, которая инициализирует массив и выводит его элементы на экран.

#include <iostream.h>

main ()

{

int a[3] [2]={

{1,2}, {3,4}, {5,6}

};

int i,j;

for (i=0; i<=2; i++)

for(j=0;j<=l;j++)

cout <<"\n a["<< i <<"," << j <<"] ="<< a[i]|j];

return 0;

}

Для того, чтобы убрать из программы явные значения размера и массива, можно воспользоваться директивой define

#include < iostream.h>

#define I 3

#define J 2

main()

{ int a[I][J]={ {l,2}, {3,4}, {5,6} };

int i, j;

for ( i=0 ; i< I; i++)

for( j=0; j< J; j++)

cout <<"\n a["<< i <<"," << j << "] ="<< a[i][j];

return 0;

}

При передаче массива в функцию всегда происходит передача его адреса. Т.о. в C++ все массивы передаются по адресу.

27.

Любой текст состоит из символов. Для хранения одного символа предназначен тип данных char. Переменную типа char можно рассматривать двояко: как целое число, занимающее 1 байт и способное принимать значения от 0 до 255 (тип unsigned char) или от -128 до 127 (тип signed char) и как один текстовый символ. Сам же тип char может оказаться как знаковым, так и беззнаковым, в зависимости от операционной системы и компилятора. Поэтому использовать тип char не рекомендуется, лучше явно указывать будет ли он знаковым (signed) или беззнаковым (unsigned).

Как и целые числа, данные типа char можно складывать, вычитать, умножать, делить, а можно выводить на экран в виде одного символа. Именно это и происходит при выводе символа через объект cout. Если же нужно вывести числовое значение символа (также называемое ASCII-кодом), то значение символа необходимо преобразовать к типу int. Например:

#include<iostream>

using namespace std;

int main()

{ unsigned char c='A'; // Константы char заключаются в одинарные кавычки

cout<<c<<" "<<(int)c<<endl;

c=126; // char можно присвоить и числовое значение

cout<<c<<" "<<(int)c<<endl;

return 0; }

В этом примере переменной с типа char присваивается значение, равное символу 'A' (константы типа char записываются как символы в одинарных кавычках), затем на экран выводится значение c, как символа и его ASCII-код, потом переменной c присваивается значение 126 (то есть символ с ASCII-кодом 126) и снова выводится на экран символ и его ASCII-код.

Организовать последовательное посимвольное считывание всего входного потока можно при помощи цикла while:

#include<iostream>

using namespace std;

int main()

{

unsigned char c;

while(cin>>c) // Цикл пока считывание успешно

{ // Делаем необходимые действия

}

return 0;

}

В этом примере программа будет посимвольно считывать входной поток (по умолчанию — ввод с клавиатуры), пока не встретит признак конца файла. Для того, чтобы сообщить программе о завершении файла при вводе с клавиатуры необходимо нажать клавиши Ctrl-d в системе Linux и Ctrl-z в системе Windows.

Эта программа при считывании данных будет игнорировать символы–разделители: пробелы, символы новой строки и табуляции. Если нужно, чтобы в переменную c считывались все символы, в том числе и разделители, то необходимо для потока ввода cin установить манипулятор noskipws при помощи инструкции cin>>noskipws;.

Строки в языке C++

Текстовую строку можно представить, как массив символов типа char, но в языке C++ для хранения текстовых строк был создан более удобный тип string. По сути, тип данных string и является массивом символов. Например, если мы объявили переменную S как string S, а затем присвоили ей значение "школа" (текстовые строки заключаются в двойные кавычки), то мы можем обращаться к отдельным символам строки S, представляя S, как массив символов, например, S[0]=='ш', S[1]=='к' и т.д. Для того, чтобы узнать длину строки используется метод length(), вызываемый в виде S.length().

Строковые данные можно считывать с клавиатуры, выводить на экран, присвавать переменным типа string. Также строки можно складывать друг с другом: например, при сложении строк "Hello, " и "world!" получится строка "Hello, world!". Такая операция над строками называется конкатенацией.

Основные приемы работы с объектами string проиллюстрированы в программе:

string S, S1, S2; // Объявление трех строк

cout<<"Как вас зовут? ";

cin>>S1; // Считали строку S1

S2="Привет, "; // Присвоили строке значение

S=S2+S1; // Использование конкатенации

cout<<S<<endl; // Вывод строки на экран

cout<<S.length(); // Длина строки S

При считывании строк из входного потока считываются все символы, кроме символов–разделителей (пробелов, табуляций и новых строк), которые являются границами между строками. Например, если при выполнении следующей программы

string S1, S2, S3; // объявили 3 строки

cin>>S1>>S2>>S3;

ввести текст `Мама мыла раму' (с произвольным количеством пробелов между словами), то в массив S1 будет записана строка "Мама", в S2 — "мыла", в S3 — "раму".

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

string s;

while(cin>>s) // Цикл пока считывание успешно

{ // Делаем необходимые действия

}

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

string S;

getline(cin,S);

В данном случае если запустить эту программу и ввести строку "Мама мыла раму", то именно это значение и будет присвоено строке S. Считать же весь входной поток по строкам можно при помощи следующего кода:

string s;

while ( getline(cin,S) ) // Цикл пока считывание успешно

{ // Делаем необходимые действия

}

28.

Массивы указателей; указатели указателей

Так как указатели сами являются переменными, то вы впол-не могли бы ожидать использования массива указателей. Это действительно так. Мы проиллюстрируем это написанием прог-раммы сортировки в алфавитном порядке набора текстовых строк, предельно упрощенного варианта утилиты SORT операци-

онной систем UNIX. В главе 3 мы привели функцию сортировки по шеллу, кото-рая упорядочивала массив целых. Этот же алгоритм будет рабо-тать и здесь, хотя теперь мы будем иметь дело со строчками текста различной длины, которые, в отличие от целых, нельзясравнивать или перемещать с помощью одной операции. Мы нуж-даемся в таком представлении данных, которое бы позволяло удобно и эффективно обрабатывать строки текста переменной длины. Здесь и возникают массивы указателей. Если подлежащие

сортировке сроки хранятся одна за другой в длинном символь-ном массиве (управляемом, например, функцией ALLOC), то к каждой строке можно обратиться с помощью указателя на ее первый символ. Сами указатели можно хранить в массиве. Две строки можно сравнить, передав их указатели функции STRCMP.Если две расположенные в неправильном порядке строки должны быть переставлены, то фактически переставляются указатели в массиве указателей, а не сами тексты строк. Этим исключаются сразу две связанные проблемы: сложного управления памятью ибольших дополнительных затрат на фактическую перестановку строк. Процесс сортировки включает три шага:

чтение всех строк ввода

их сортировка

вывод их в правильном порядке

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

Функция, осуществляющая ввод, должна извлечь символы каждой строки, запомнить их и построить массив указателей строк. Она должна также подсчитать число строк во вводе, так как эта информация необходима при сортировке и выводе. так как функция ввода в состоянии справиться только с конечным чис-

лом вводимых строк, в случае слишком большого их числа она может возвращать некоторое число, отличное от возможного числа строк, например -1. Функция осуществляющая вывод, дол-жна печатать строки в том порядке, в каком они появляются в массиве указателей.

29

ясность - способы многооценивания проги

краткость - оценка стиля записи выражений

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

язык

стиль

размер

имя1_имя2 Имя1 Имя2

имя программы

автор

среда разработки

дата создания

дата посл. модификации

автор модиф.

краткое описение проги

спецификация ф-ций (обычно перед ф-цией)

запись проги: лесерка, правило отступа(требов. структ. прогр.)

комментарии разбиение на файлы

с помощью линковщика или include

30.

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

действий, которые надлежит выполнить, и

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

Рассмотрим «алгоритм активного пробуждения», которому следует некий клерк для того, чтобы встать с постели п отпршштьси пи работу:

Встать с постели.

Снять пижаму.

Принять душ.

Одеться.

Позавтракать.

Отправиться на работу.

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

Встать с постели.

Снять пижаму

Одеться.

Принять душ.

Позавтракать.

Отправиться на работу

В этом случае наш служащий появится на работе мокрым до нитки. Задание порядка, в к-ом должны выполняться лператоры,называется программным управлением.

Псевдокод

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

Программы на псевдокоде на самом деле не выполняются на компьютерах. Скорее они просто помогают программисту «продумывать» программу перед попыт­ки написать ее на языке программирования, таком, например, как С.

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

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

int i ;

просто сообщает компилятору о типе переменной i и даёт ему указание зарезервировать место в памяти для этой переменной. Но это определение не вызывает какого-либо действия при выполнении программы,как,например,ввод,вывод или вычисление. Некоторые программисты предпочитают в начале программы на псевдокоде перечислять все переменные с кратким упоминанием об их назначении. Повторяем,псевдокод является неформальным вспомогательным средством разработки программ.

31. способы употребления конструкций языка программирования

32.

33.

Критерии для анализа стиля программирования. Экспертные оценки.

Язык программирования — это язык. И в нем, также как в обычном языке, определены алфавит, лексика, синтаксис и т.д., то есть, определены некоторые правила построения фраз (операторов), используя которые, человек может общаться на этом языке с другими людьми (и даже с ЭВМ!). Констатируется, что каждый человек, знающий некоторый язык (умеющий читать на этом языке и писать на этом языке), также имеет некоторый стиль, который является характерным для данного человека и который проявляется, когда человек пишет на этом языке, например, программу.

Основная и самая важная причина возникновения стилевых конфликтов — обучение программистов, поставленное "из рук вон". Предоставленные самим себе, познавая программирование, они "варятся в собственном соку" и каждый для себя вырабатывает собственные критерии "хорошести" и "плохости" программы, собственный стиль, переделать который впоследствии очень сложно.

В настоящее время наличие развитых методов программирования, достаточный объём информации о программировании, позволяет организовать процесс обучения программистов с "насаждением" определенного стиля программирования (как это делается при изучении "родного" языка — прививаются навыки "грамотного" письма). Это наиболее простой, легкий и проверенный путь избавления от стилевых конфликтов, позволяющий получить программистов, умеющих писать красивые программы и облегчить им в будущем тернистый путь превращения в профессионалов.

Крайне желательно для насаждения красивого стиля программирования использовать в качестве примеров программы, разработанные "гуру" программирования. Образцы программ, как правило, поставляются в виде example в составе систем программирования. Для языков Си, С++ очень хорошими примерами являются системные программы среды UNIX, в частности, LINUX.

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

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

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

В максимальной степени значение качества стиля программирования проявляется в процессе эксплуатации программного обеспечения на этапе сопровождения, когда возникает необходимость доработок и исправления ошибок. Легкий, красивый стиль программирования способствует увеличению жизненного цикла программ. Это особенно важно становится в последние годы в связи с принижением значения серии ГОСТов ЕСПД, регламентирующих программную документацию. Молодое поколение программистов часто учится программировать само по себе (из любопытства) и нередко даже не знает о существовании регламентирующих стандартов и технических условий.

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

Везде далее, где написано "процедура", следует читать "процедура (функция)".

1.Оценка модульности программы

В языках программирования (в частности, в языках, используемых в учебном процессе — Паскаль, Бейсик, Фортран, Си и т.д.) широко используется аппарат процедур и функций. Причем, процедуры и функции могут быть как внешними (относительно главного модуля программы или относительно какой-либо процедуры), так и внутренними (вложенными).

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

Введем "норму". Положим, что "нормальная" длина процедуры составляет 50 исполняемых операторов, т.е. не учитываем строки комментария, операторы объявлений констант, типов и переменных. "Норма" зависит от языка программирования.

Оценим среднюю длину процедуры как отношение суммы длин всех процедур, включая блок main, к количеству всех процедур, включая блок main.

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

2.Оценка структурности кодирования

Будем учитывать требования структурного кодирования суммированием штрафных баллов по каждому случаю нарушения:

оператор GOTO используется для выхода из процедуры — 5 баллов;

оператор GOTO используется для перехода назад по программе — 2 балла;

другие случаи использования оператора GOTO (выход из цикла вперед по программе, использование в условном операторе) — 1 балл;

наличие в строке более одного оператора — 0,25 балла;

процедуры не отделены друг от друга строками комментария (минимум две строки комментария на процедуру) — 1 балл;

текст программы не сформатирован, т.е. не используются сдвиги вправо для обозначения блоков программы — 5 баллов;

отсутствуют начальные блоки комментариев в процедурах — 3 балла;

не комментированы циклы, условные операторы, операторы END (операторы ";" для Си), операторы перехода — 0,25 балла;

метки GOTO (а также метки оператора format для Фортрана) определены не в возрастающем порядке — 1 балл;

перекрытие GOTO — диапазонов — 3 балла;

метки оператора format (для Фортрана) расположены в диапазоне GOTO — 1 балл;

глубина вложенности условных операторов и операторов циклов не превышает 10; каждый уровень вложенности сверх указанного — 0,5 балла.

Оценка структурности кодирования определяется как сумма штрафных баллов.

Здесь необходимо предусмотреть веса для параметров для того, чтобы сумма баллов всегда была меньше, либо равна 100.

3.Оценка структурированности

Пусть известны мера модульности программы и мера структурности кодирования программы. Тогда оценку структурированности программы определим как произведение меры модульности программы на разность между 1 и отношением меры структурности программы к 100. Очевидно, что неотрицательная оценка структурированности программы не превосходит 1. Программа имеет оценку равную 1, если к ней нет претензий с точки зрения структурированности.

4.Оценка читабельности

Предположим, что для идеальной во всех отношениях программы оценка читабельности равна 1.

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

)Структура программы выдержана:

Начальный блок комментария.

Объявления (констант, типов, переменных, внешних процедур, внутренних процедур).

Внутренние процедуры.

Блок begin-end.

Штраф — 1 балл.

2)Наличие начального блока комментария, описывающего наименование программы, принадлежность (в состав чего входит), функциональное назначение, метод решения (источник алгоритма), входные и выходные данные, ограничения и условия применения, побочные эффекты, дату разработки, автора, дату последней корректировки, версию. Для процедур и функций дополнительно описывается использование глобальных переменных. За отсутствие каждого перечисленного пункта — штраф 0.5 балла.

3)Константы, типы и переменные описаны в комментариях. За каждый неописанный случай — штраф 0,1 балла.

4)Текст отформатирован: логические блоки выделены сдвигами операторов вправо — 0 баллов.

Нарушение — 0,05 балла, но в сумме не более 3 баллов. Грубые нарушения данного пункта:

все операторы набиты с первой колонки — 3 балла;

"книжный текст": сверхплотное расположение операторов (по несколько операторов на каждой строке) — 3 балла.

5)Процедуры отделены друг от друга двумя строками комментария — 0 баллов, одной строкой — 1 балл, комментарий отсутствует — 2 балла.

6)Более одного оператора на строке; за каждый случай — 0,1 балла.

7)Все логические блоки (операторы if, case, switch, goto, exit, операторы циклов и т.д.) прокомментированы — 0 баллов. Невыполнение требования — 0,25 балла.

8) Прокомментированы операторы end /* чего? */ (для языка Си — ";").

Невыполнение требования — 0,1 балла.

9)Комментарии располагаются сбоку (справа) от исполняемых (комментируемых) операторов, а не расположены с ними вперемежку.

Невыполнение требований — до 0,5 балла.

10)Если переменные используются в программе для нескольких целей — 1 балл. Если случай "перенацеливания" не комментирован — дополнительно — 0,5 балла за каждый случай.

11) Использование булевских переменных (типа BOOLEAN): true — включено, false — выключено; если переключатель моделируется переменной другого типа, то аналогично, 1 — включено, а 0 — выключено. За каждое нарушение — 1 балл.

12)Если алгоритм программы формирует во входной области программы особые точки, то это прокомментировано. Невыполнение требования — 3 балла.

13)Имеются комментарии, позволяющие оценить влияние на данный модуль изменений, вносимых в другие модули (например, прокомментировано каждое использование глобальных переменных).

Невыполнение требования — до 1 балла.

14)Программа удовлетворяет требованию структурированности (см. п.3). Оценка выполнения требования в баллах равна трёхкратной оценке структурированности.

Необходимо предусмотреть веса для параметров для того, чтобы общая сумма баллов была всегда меньше либо равна 100.

33.1.

Оценка модульности программы

В языках программирования (в частности, в языках, используемых в учебном процессе — Паскаль, Бейсик, Фортран, Си и т.д.) широко используется аппарат процедур и функций. Причем, процедуры и функции могут быть как внешними (относительно главного модуля программы или относительно какой-либо процедуры), так и внутренними (вложенными).Модульность программы подразумевает, что программа разбита на процедуры разумной длины, каждая из которых выполняет некоторую функцию, определяемую алгоритмом программы. При определении длины процедуры будем учитывать только операторы самой процедуры, в терминах Паскаля — блок Begin-End. Внутренние (вложенные) процедуры будем считать самостоятельно, как отдельные процедуры.Введем "норму". Положим, что "нормальная" длина процедуры составляет 50 исполняемых операторов, т.е. не учитываем строки комментария, операторы объявлений констант, типов и переменных. "Норма" зависит от языка программирования.Оценим среднюю длину процедуры как отношение суммы длин всех процедур, включая блок main, к количеству всех процедур, включая блок main.Введем меру модульности программы как разность между 1 и суммой двух отношений, где первое есть отношение абсолютной разницы средней длины процедуры и константы "нормальной" длины процедуры к 100, а второе представляет собой отношение длины самой большой процедуры к удвоенному произведению общей длины программы (общему количеству исполняемых операторов в программе).

2.Оценка структурности кодирования

Будем учитывать требования структурного кодирования суммированием штрафных баллов по каждому случаю нарушения:

оператор GOTO используется для выхода из процедуры — 5 баллов;

оператор GOTO используется для перехода назад по программе — 2 балла;

другие случаи использования оператора GOTO (выход из цикла вперед по программе, использование в условном операторе) — 1 балл;

наличие в строке более одного оператора — 0,25 балла;

процедуры не отделены друг от друга строками комментария (минимум две строки комментария на процедуру) — 1 балл;

текст программы не сформатирован, т.е. не используются сдвиги вправо для обозначения блоков программы — 5 баллов;

отсутствуют начальные блоки комментариев в процедурах — 3 балла;

не комментированы циклы, условные операторы, операторы END (операторы ";" для Си), операторы перехода — 0,25 балла;

метки GOTO (а также метки оператора format для Фортрана) определены не в возрастающем порядке — 1 балл;

перекрытие GOTO — диапазонов — 3 балла;

метки оператора format (для Фортрана) расположены в диапазоне GOTO — 1 балл;

глубина вложенности условных операторов и операторов циклов не превышает 10; каждый уровень вложенности сверх указанного — 0,5 балла.

Оценка структурности кодирования определяется как сумма штрафных баллов.

Здесь необходимо предусмотреть веса для параметров для того, чтобы сумма баллов всегда была меньше, либо равна 100.

3.Оценка структурированности

Пусть известны мера модульности программы и мера структурности кодирования программы. Тогда оценку структурированности программы определим как произведение меры модульности программы на разность между 1 и отношением меры структурности программы к 100. Очевидно, что неотрицательная оценка структурированности программы не превосходит 1. Программа имеет оценку равную 1, если к ней нет претензий с точки зрения структурированности.

4.Оценка читабельности

Предположим, что для идеальной во всех отношениях программы оценка читабельности равна 1.

Определим требования, которым должен удовлетворять исходный текст читабельной программы, и буде начислять штрафные баллы за каждый случай нарушения этих требований:1)Структура программы выдержана:

Начальный блок комментария.

Объявления (констант, типов, переменных, внешних процедур, внутренних процедур).

Внутренние процедуры.

Блок begin-end.

Штраф — 1 балл.

2)Наличие начального блока комментария, описывающего наименование программы, принадлежность (в состав чего входит), функциональное назначение, метод решения (источник алгоритма), входные и выходные данные, ограничения и условия применения, побочные эффекты, дату разработки, автора, дату последней корректировки, версию. Для процедур и функций дополнительно описывается использование глобальных переменных. За отсутствие каждого перечисленного пункта — штраф 0.5 балла.3)Константы, типы и переменные описаны в комментариях. За каждый неописанный случай — штраф 0,1 балла.4)Текст отформатирован: логические блоки выделены сдвигами операторов вправо — 0 баллов.

Нарушение — 0,05 балла, но в сумме не более 3 баллов. Грубые нарушения данного пункта:

все операторы набиты с первой колонки — 3 балла;

"книжный текст": сверхплотное расположение операторов (по несколько операторов на каждой строке) — 3 балла.

5)Процедуры отделены друг от друга двумя строками комментария — 0 баллов, одной строкой — 1 балл, комментарий отсутствует — 2 балла.6)Более одного оператора на строке; за каждый случай — 0,1 балла.7)Все логические блоки (операторы if, case, switch, goto, exit, операторы циклов и т.д.) прокомментированы — 0 баллов. Невыполнение требования — 0,25 балла.8) Прокомментированы операторы end /* чего? */ (для языка Си — ";").Невыполнение требования — 0,1 балла.9)Комментарии располагаются сбоку (справа) от исполняемых (комментируемых) операторов, а не расположены с ними вперемежку.Невыполнение требований — до 0,5 балла.10)Если переменные используются в программе для нескольких целей — 1 балл. Если случай "перенацеливания" не комментирован — дополнительно — 0,5 балла за каждый случай.11) Использование булевских переменных (типа BOOLEAN): true — включено, false — выключено; если переключатель моделируется переменной другого типа, то аналогично, 1 — включено, а 0 — выключено. За каждое нарушение — 1 балл.12)Если алгоритм программы формирует во входной области программы особые точки, то это прокомментировано. Невыполнение требования — 3 балла.13)Имеются комментарии, позволяющие оценить влияние на данный модуль изменений, вносимых в другие модули (например, прокомментировано каждое использование глобальных переменных).Невыполнение требования — до 1 балла.14)Программа удовлетворяет требованию структурированности (см. п.3). Оценка выполнения требования в баллах равна трёхкратной оценке структурированности.Необходимо предусмотреть веса для параметров для того, чтобы общая сумма баллов была всегда меньше либо равна 100.Пусть общая сумма набранных баллов равна b. Тогда оценку читабельности программы определим как разность между 1 и отношением b к 100.

34.

Классификация данных по назначению, структуре и формату.

Классификация типов данных[источник?]

Типы данных бывают следующие:

Простые.

Перечислимый тип. Может хранить только те значения, которые прямо указаны в его описании.

Числовые. Хранятся числа. Могут применяться обычные арифметические операции.

Целочисленные: со знаком, то есть могут принимать как положительные, так и отрицательные значения; и без знака, то есть могут принимать только неотрицательные значения.

Вещественные: с запятой (то есть хранятся знак и цифры целой и дробной частей) и с плавающей запятой (то есть число приводится к виду m*be, где m — мантисса, b - основание показательной функции, e — показатель степени (порядок) (в англоязычной литературе экспонента), причём в нормальной форме 0<=m<b, а в нормализованной форме 1<=m<b, e — целое число и хранятся знак и числа m и e).

Числа произвольной точности, обращение с которыми происходит посредством длинной арифметики. Примером языка с встроенной поддержкой таких типов является UBASIC, часто применяемый среди криптографов.

Символьный тип. Хранит один символ. Могут использоваться различные кодировки.

Логический тип. Имеет два значения: истина и ложь. Могут применяться логические операции. Используется в операторах ветвления и циклах. В некоторых языках является подтипом числового типа, при этом ложь=0, истина=1.

Множество. В основном совпадает с обычным математическим понятием множества. Допустимы стандартные операции с множествами и проверка на принадлежность элемента множеству. В некоторых языках рассматривается как составной тип.

Составные (сложные).

Массив. Является индексированным набором элементов одного типа. Одномерный массив — вектор, двумерный массив — матрица.

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

Запись (структура). Набор различных элементов (полей записи), хранимый как единое целое. Возможен доступ к отдельным полям записи. Например, struct в C или record в Pascal.

Файловый тип. Хранит только однотипные значения, доступ к которым осуществляется только последовательно (файл с произвольным доступом, включённый в некоторые системы программирования, фактически является неявным массивом).

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

Указатель. Хранит адрес в памяти компьютера, указывающий на какую-либо информацию, как правило — указатель на переменную.

Ссылка.

35.

Внутреннее представление данных в ЭВМ: символьных, двоичных, десятичных, адресных констант.

Информация в ЭВМ кодируется, как правило, в двоичной или в двоично-десятичной системе счисления.

Современные ЭВМ автоматически выполняют несколько сотен различных команд. Например, стандартный набор современных ПК содержит около 240 машинных команд. Все машинные команды можно разделить на группы по видам выполняемых операций:

операции пересылки информации внутри ЭВМ;

арифметические операции над информацией;

логические операции над информацией;

операции обращения к внешним устройствам ЭВМ;

операции передачи управления;

обслуживающие и вспомогательные операции.

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

Операции безусловной передачи управления требуют выполнения после данной команды не следующей по порядку, а той, адрес которой в явном или неявном виде указан в адресной части.

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

В вопросах организации обработки информации с помощью ПК важное место занимают системы счисления, формы представления данных и специальное кодирование чисел.

Различают позиционные и непозиционные системы счисления. В непозиционных системах счисления каждое число обозначается соответствующей совокупностью символов. Характерным представителем непозиционных систем является римская система счисления со сложным способом записи чисел и громоздкими правилами выполнения арифметических операций.

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

Количество символов, используемых в позиционной системе счисления, называется ее основанием. Его обозначают обычно буквой q. В десятичной системе счисления используется 10 символов (цифр): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, и основанием системы является число десять.

Особое место среди позиционных систем счисления занимают системы со степенными весами разрядов, в которых веса смежных позиций цифр (разрядов) отличаются по величине в постоянное количество раз, равное основанию q системы счисления.

В общем случае в такой ПСС с основанием q любое число х может быть представлено в виде:

X(q)=xn-1qn-1+xn-2qn-2+…+x1q1+x0q0+x-1q-1+…+x-mq-m=(xiqi

Где X(q)-запись числа в системе счисления с основанием q; q-основание системы счисления; xi –целые числа, меньше q; n-число разрядов (позиций) в целой части числа; m-число разрядов в дробной части числа.

Например: 4295,67(10)=4*103+2*102+9*101+5*100+6*10-1+7*10-2

Для обозначения используемой системы счисления ее основание указывается в индексе в круглых скобках. Изображение числа X(q) в виде последовательности коэффициентов xi полинома является его условной сокращенной записью (кодом).

X(q)=Xn-1 Xn-2…X1X0,X-1…X-m

Запятая отделяет целую часть числа от дробной и служит началом отсчета значений веса каждой позиции (разряда).

В информатике применяют позиционные системы счисления с недесятичным основанием: двоичную, восьмеричную, шестнадцатеричную, т.е. системы счисления с основанием q=2k, где k-1, 3, 4.

Наибольшее распространение получила двоичная систем счисления. В этой системе для представления любого числа используются два символа – цифры 0 и 1. Основание системы счисления q=2.

Например: 13,625(10)=1*23+1*22+0*21+1*20+1*2-1+0*2-2+1*2-3=1101,101(2)

В восьмеричной системе счисления алфавит состоит из восьми символов (цифр): 0, 1 … 7. Основание системы счисления q=8.

Например: 28(10)=3*81+4*80=34(8)

В шестнадцатеричной системе счисления алфавит включает в себя 16 символов (цифр и букв): 0, 1 … 9, А, В, С, D, E, F. Основание системы счисления q=16. Например: Например: 75(10)=4*161+В*160=4В(16)

2. Представление чисел с фиксированной точкой.

При представлении числа Х в форме с фиксированной точкой указываются знак числа (sign X) и модуль числа (mod X) в q-ичном коде. Иногда такую форму представления чисел называют естественной формой. Место точки (запятой) постоянно для всех чисел и в процессе решения задач не меняется. Знак положительного числа кодируется цифрой «0», а знак отрицательного числа – цифрой «1».

Код числа в форме с фиксированной точкой, состоящий из кода знака и q- ичного кода его модуля, называется прямым кодом q-ичного числа. Разряд прямого кода числа, в котором располагается код знака, называется знаковым разрядом кода. Разряды прямого кода числа, в которых располагается q-ичный код модуля числа, называются цифровыми разрядами кода. При записи прямого кода знаковый разряд располагается левее старшего цифрового разряда и обычно отделяется от цифровых разрядов точкой.

Максимальное и минимальное значения чисел определяются формулами:

Xmax=+(qn-q-m); Xmin=-(qn-q-m).

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

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

3. Представление чисел с плавающей точкой.

При представлении числа Х в форме числа с плавающей точкой (в нормальной форме) требуется задать знаки мантиссы и порядка, их модули в q- ичном коде, а также основание системы счисления.

Х=mqp

Где m-мантисса числа; q-основание системы счисления; p-порядок.

Для задания числа в нормальной форме требуется задать знаки мантиссы и порядка, их модули в q-ичном коде, а также основание системы счисления. Нормальная форма представления чисел неоднозначна, т.к. взаимное изменение m и p приводит к плаванию точки (запятой). Отсюда произошло название формы представления чисел.

Для однозначности представления чисел в ПК используется нормальная нормализованная форма, в которой положение точки всегда задается перед значащей цифрой мантиссы. Точность вычислений при использовании формата с плавающей точкой определяется числом разрядов мантиссы. Она увеличивается с увеличением числа разрядов.

4. Представление символов. Кодировка ASCII.

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

Во многих ПК для представления алфавитно-цифровых символов используется код ASCII (American Standart Code for Information Interchange – американский код обмена информацией), расширенный путем добавления букв русского алфавита. Для представления каждого символа отводится один байт, с помощью которого можно закодировать 256 символов. В связи с тем, что в коде ASCII нет букв русского языка, для их корректного отображения используются специальные программы-русификаторы.

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

Состояния “установлен” и “сброшен” соотв. 2 цифрам двоичной системы счисления, фундаментальной для ЭВМ. Эти цифры называются битами. Двоичное кодирование – представление данных последовательностью битов. При двоичном кодировании числовой информации степень двойки при каждой двоичной цифре на единицу больше, чем у предшествующей цифры. Пример конструкции двоичного кода: 1012=(1*22)+(0*21)+(1*20)=510.

Таким образом осуществляется представление целых чисел без знака. Отрицательные числа со знаком представляются в дополнительном коде – в форме дополнения до двух. Чтобы найти двоичное представление отрицательного числа, надо взять его полжительную форму, проинвертировать ее и добавить к полученному результату 1.

Числами с плавающей точкой называются числа вида x=M*Pq, где М – мантисса, P – порядок, q – основание системы счисления. Нормализованная форма таких чисел:

M – дробное, |M|

Q – фиксировано.

P – целое число со знаком.

В машине в двоичных кодах хранится M и P. Порядок, как правило, выравнивается, т.е. приводится к большему по модулю порядку, путем сдвига мантиссы вправо с меньшим порядком на кол-во разрядов, равное (Pmax-Pmin)log2Q.

Адресация. Данные в ЭВМ: структура и форматы представления.

Адресация на примере процессора 8086.

Числа, устанавливаемые процессором на адресной шине, являются адресами, т.е. номерами ячеек оперативной памяти, из которых необходимо считывать очередную команду или данные. Размер ячейки оперативной памяти составляет 8 разрядов, т.е. 1 байт. Поскольку процессор использует 16-разрядные адресные регистры, то это обеспечивает ему доступ к 65536 (FFFFh) байт или 64К (1K=1024 байт) основной памяти. Такой блок непосредственно адресуемой памяти называется сегментом. Любой адрес формируется из адреса сегмента (всегда кратен 16) и адреса ячейки внутри сегмента (этот адрес называют смещением). На компьютерах, оснащенных процессором 8086, оперативная память обычно имеет размер, равный 640К. Для того чтобы работать с памятью такого размера, процессор осуществляет пересчет адресов с помощью процедуры, называемой вычислением эффективного адреса (рис.2.3).

Физический 20-разрядный адрес вычисляется сложением сдвинутого влево на 4 разряда 16-разрядного адреса сегмента оперативной памяти со значением 16-разрядного смещения относительно начала этого сегмента. Используя 20-разрядные адреса, можно адресовать 1М оперативной памяти ( 1M=1024K=1048576 байт). В программе на ассемблере полный адрес записывается в виде SSSS:0000, гдe SSSS значение сегмента; 0000 – значение смещения. Участок оперативной памяти размером 16 байт называется параграфом.

Данные в ЭВМ – подразделяются на числовые и нечисловые.

Числовые данные:

Целые типы – для представления целых чисел.

Вещественные типы – для представления рациональных чисел. Бывают:

а) с фиксированной точкой;

б) с плавающей точкой.

Нечисловые данные:

Логические данные – принимающие значение истина или ложь.

Строковые данные.

Множества.

Произвольные данные (текст, звук, графика).

36.

Идентификаторы и зарезервированные слова.

Идентифика́тор - уникальный признак субъекта или объекта доступа

Идентификатором называется последовательность цифр и букв, а также специальных символов, при условии, что первой стоит буква или специальный символ. Для образования идентификаторов могут быть использованы строчные или прописные буквы латинского алфавита. В качестве специального символа может использоваться символ подчеркивание (_). Два идентификатора для образования которых используются совпадающие строчные и прописные буквы, считаются различными. Например: abc, ABC, A128B, a128b .

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

Следует отметить важные особенности при выборе идентификатора.

Во первых, идентификатор не должен совпадать с ключевыми словами, с зарезервированными словами и именами функций библиотеки компилятора языка СИ.

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

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

Зарезерви́рованное сло́во (или ключево́е сло́во) — в языках программирования слово, имеющее специальное значение. Идентификаторы с такими именами запрещены. Зарезервированные слова представляют определённую проблему при переводе текста с языка на язык (а иногда даже с компилятора на компилятор) и при написании ПО, разные части которого написаны на разных языках — может случиться, что на языке А будет создан идентификатор, который является зарезервированным словом языка Б и поэтому он окажется недоступным. Обычно стараются избегать слов, которые в каком-либо из широко известных языков являются зарезервированными.

Зарезервированные слова

Типы данных

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

int enum

long struct

short union

unsigned typedef

char

float

double

В Си все нестандартные (производителе- и платформозависимые) зарезервированные слова начинаются с двух знаков подчёркивания. Например, в C Builder может быть такая запись:

__fastcall int DoSomething(int x)

36.

Идентификатор

Идентификатором называется последовательность цифр и букв, а также специальных символов, при условии, что первой стоит буква или специальный символ. Для образования идентификаторов могут быть использованы строчные или прописные буквы латинского алфавита. В качестве специального символа может использоваться символ подчеркивание (_). Два идентификатора для образования которых используются совпадающие строчные и прописные буквы, считаются различными. Например: abc, ABC, A128B, a128b . Важной особенностью является то, что компилятор допускает любое количество символов в идентификаторе, хотя значимыми являются первые 31 символ. Идентификатор создается на этапе объявления переменной, функции, структуры и т.п. после этого его можно использовать в последующих операторах разрабатываемой программы. Следует отметить важные особенности при выборе идентификатора. Во первых, идентификатор не должен совпадать с ключевыми словами, с зарезервированными словами и именами функций библиотеки компилятора языка СИ. Во вторых, следует обратить особое внимание на использование символа (_) подчеркивание в качестве первого символа идентификатора, поскольку идентификаторы построенные таким образом, что, с одной стороны, могут совпадать с именами системных функций и (или) переменных, а с другой стороны, при использовании таких идентификаторов программы могут оказаться непереносимыми, т.е. их нельзя использовать на компьютерах других типов. В третьих, на идентификаторы используемые для определения внешних переменных, должны быть наложены ограничения, формируемые используемым редактором связей (отметим, что использование различных версий редактора связей, или различных редакторов накладывает различные требования на имена внешних переменных). Ключевые слова - это зарезервированные идентификаторы, которые наделены определенным смыслом. Их можно использовать только в соответствии со значением известным компилятору языка СИ. Приведем список ключевых слов

auto double int struct break else long switch

register tupedef char extern return void case float

unsigned default for signed union do if sizeof

volatile continue enum short while

Кроме того в рассматриваемой версии реализации языка СИ, зарезервированными словами являются :

_asm, fortran, near, far, cdecl, huge, paskal, interrupt .

Ключевые слова far, huge, near позволяют определить размеры указателей на области памяти. Ключевые слова _asm, cdelc, fortran, pascal служат для организации связи с функциями написанными на других языках, а также для использования команд языка ассемблера непосредственно в теле разрабатываемой программы на языке СИ. Ключевые слова не могут быть использованы в качестве идентификаторов.

Константами называются перечисление величин в программе. В языке СИ разделяют четыре типа констант: целые константы, константы с плавающей запятой, символьные константы и строковыми литералы. Целая константа: это десятичное, восьмеричное или шестнадцатеричное число, которое представляет целую величину в одной из следующих форм: десятичной, восьмеричной или шестнадцатеричной. Десятичная константа состоит из одной или нескольких десятичных цифр, причем первая цифра не должна быть нулем (в противном случае число будет воспринято как восьмеричное). Восьмеричная константа состоит из обязательного нуля и одной или нескольких восьмеричных цифр (среди цифр должны отсутствовать восьмерка и девятка, так как эти цифры не входят в восьмеричную систему счисления). Шестнадцатеричная константа начинается с обязательной последовательности 0х или 0Х и содержит одну или несколько шестнадцатеричных цифр (цифры представляющие собой набор цифр шеснадцатеричной системы счисления: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)

Примеры целых констант:

Десятичная Восьмеричная Шестнадцатеричная

константа константа константа

16 020 0x10

127 0117 0x2B

240 0360 0XF0

Если требуется сформировать отрицательную целую константу, то используют знак "-" перед записью константы (который будет называться унарным минусом). Например: -0x2A, -088, -16 . Каждой целой константе присваивается тип, определяющий преобразования, которые должны быть выполнены, если константа используется в выражениях. Тип константы определяется следующим образом:

- десятичные константы рассматриваются как величины со знаком, и им присваивается тип int (целая) или long (длинная целая) в соответствии со значением константы. Если константа меньше 32768, то ей присваивается тип int в противном случае long.

- восьмеричным и шестнадцатеричным константам присваивается тип int, unsigned int (беззнаковая целая), long или unsigned long в зависимости от значения константы согласно табл 5.

Таблица 5

Диапазон шестнадцатеричных констант Диапазон восьмеричных констант Тип

0x0 - 0x7FFF 0 - 077777 int

0X8000 - 0XFFFF 0100000 - 0177777 unsigned int

0X10000 - 0X7FFFFFFF 0200000 - 017777777777 long

0X80000000 - 0XFFFFFFFF 020000000000 - 037777777777 unsigned long

Для того чтобы любую целую константу определить типом long, достаточно в конце константы поставить букву "l" или "L". Пример:

5l, 6l, 128L, 0105L, OX2A11L.

Константа с плавающей точкой - десятичное число, представленное в виде действительной величины с десятичной точкой или экспонентой. Формат имеет вид:

[ цифры ].[ цифры ] [ Е|e [+|-] цифры ] .

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

Примеры: 115.75, 1.5Е-2, -0.025, .075, -0.85Е2

Символьная константа - представляется символом заключенном в апострофы. Управляющая последовательность рассматривается как одиночный символ, допустимо ее использовать в символьных константах. Значением символьной константы является числовой код символа. Примеры:

' '- пробел ,

'Q'- буква Q ,

'\n' - символ новой строки ,

'\\' - обратная дробная черта ,

'\v' - вертикальная табуляция .

Символьные константы имеют тип int и при преобразовании типов дополняются знаком. Строковая константа (литерал) - последовательность символов (включая строковые и прописные буквы русского и латинского а также цифры) заключенные в кавычки (") . Например: "Школа N 35", "город Тамбов", "YZPT КОД". Отметим, что все управляющие символы, кавычка ("), обратная дробная черта (\) и символ новой строки в строковом литерале и в символьной константе представляются соответствующими управляющими последовательностями. Каждая управляющая последовательность представляется как один символ. Например, при печати литерала "Школа \n N 35" его часть "Школа" будет напечатана на одной строке, а вторая часть "N 35" на следующей строке. Символы строкового литерала сохраняются в области оперативной памяти. В конец каждого строкового литерала компилятором добавляется нулевой символ, представляемый управляющей последовательностью \0. Строковый литерал имеет тип char[] . Это означает, что строка рассматривается как массив символов. Отметим важную особенность, число элементов массива равно числу символов в строке плюс 1, так как нулевой символ (символ конца строки) также является элементом массива. Все строковые литералы рассматриваются компилятором как различные объекты. Строковые литералы могут располагаться на нескольких строках. Такие литералы формируются на основе использования обратной дробной черты и клавиши ввод. Обратная черта с символом новой строки игнорируется компилятором, что приводит к тому, что следующая строка является продолжением предыдущей. Например:

"строка неопределенной \n

длины"

полностью идентична литералу

"строка неопределенной длинны" .

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

37

Константы и переменные скалярного типа.

Константами называются перечисление величин в программе. В языке СИ разделяют четыре типа констант: целые константы, константы с плавающей запятой, символьные константы и строковыми литералы.

Целая константа: это десятичное, восьмеричное или шестнадцатеричное число, которое представляет целую величину в одной из следующих форм: десятичной, восьмеричной или шестнадцатеричной.

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

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

Шестнадцатеричная константа начинается с обязательной последовательности 0х или 0Х и содержит одну или несколько шестнадцатеричных цифр (цифры представляющие собой набор цифр шеснадцатеричной системы счисления: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)

Если требуется сформировать отрицательную целую константу, то используют знак "-" перед записью константы (который будет называться унарным минусом). Например: -0x2A, -088, -16 .

Для того чтобы любую целую константу определить типом long, достаточно в конце константы поставить букву "l" или "L". Пример:

5l, 6l, 128L, 0105L, OX2A11L.

Константа с плавающей точкой - десятичное число, представленное в виде действительной величины с десятичной точкой или экспонентой. Формат имеет вид:

[ цифры ].[ цифры ] [ Е|e [+|-] цифры ] .

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

Примеры: 115.75, 1.5Е-2, -0.025, .075, -0.85Е2

Символьная константа - представляется символом заключенном в апострофы. Управляющая последовательность рассматривается как одиночный символ, допустимо ее использовать в символьных константах. Значением символьной константы является числовой код символа. Примеры:

' '- пробел ,

'Q'- буква Q ,

'\n' - символ новой строки ,

'\\' - обратная дробная черта ,

'\v' - вертикальная табуляция .

Символьные константы имеют тип int и при преобразовании типов дополняются знаком.

Строковая константа (литерал) - последовательность символов (включая строковые и прописные буквы русского и латинского а также цифры) заключенные в кавычки (") . Например: "Школа N 35", "город Тамбов", "YZPT КОД".

38.

Объявление и определение данных и указателей.

Внешнее определение данных имеет форму:

определение_данных:

описание

Классом памяти таких данных может быть extern (в частности,

по умолчанию) или static, но не auto или register.

Объявители указателей(по конспекту)

В объявления T D, где D имеет вид

* список-квалификаторов-типанеоб D1

а тип идентификатора объявления T D1 есть "модификатор-типа T", тип идентификатора D есть "модификатор-типа список-квалификаторов-типа указатель на T". Квалификаторы, следующие за *, относятся к самому указателю, а не к объекту, на который он указывает. Рассмотрим, например, объявление

int *ap[];

Здесь ap[] играет роль D1; объявление int ap[] следует расшифровать как "массив из int": список квалификаторов типа здесь пуст, а модификатор типа есть "массив из". Следовательно, на самом деле объявление ap гласит: "массив из указателей на int". Вот еще примеры объявлений:

int i, *pi, *const cpi = &i;

const int ci = 3, *pci;

В них объявляются целое i и указатель на целое pi. Значение указателя cpi неизменно; cpi всегда будет указывать в одно и то же место, даже если значение, на которое он указывает, станет иным. Целое ci есть константа, оно измениться не может (хотя может инициализироваться, как в данном случае). Тип указателя pci произносится как "указатель на const int"; сам указатель можно изменить; при этом он будет указывать на другое место, но значение, на которое он будет указывать, с помощью pci изменить нельзя.

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

спецификатор-типа [ модификатор ] * описатель .

Спецификатор-типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип 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 будет указывать на одну и ту же область памяти. Содержание же

этой области может быть изменено. */

39.

Классы памяти, область действия объявления.

Классы памяти

Каждая переменная имеет тип и принадлежит к некоторому классу памяти. Время жизни и область действия идентификатора определяются ассоциированным с ним классом памяти. Существуют четыре разновидности классов памяти:

auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является сокращением слова automatic.

static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.

extern - внешний - идентификаторы, называемые внешними, external, используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.

register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.

Автоматические переменные в программе можно описать так:

auto A; auto char c1; auto int x= 125;

Если мы этим не пользовались, то только потому что опущенный описатель auto используется по умолчанию. Зона действия автоматической переменной ограничена блоком или функцией, где она описана. Она начинает существовать после обращения к функции и исчезает после выхода из нее. Таким образом автоматические переменные не занимают область в памяти. Значение автоматической переменной не может быть изменено другими функциями и в этих функциях может находится переменные с таким же именем. Проанализируем результаты работы следующей программы.

#include <stdio.h>

main()

{

int t;

{

int t=2;

{ int t=3;

printf("%d\n",t);

}

printf("%d\n",t);

}

printf("%d\n",t);

}

В этой программе перменная t описана в нескольких блоках, в каждом блоке она может принимать разные значения не зависимо от других. С ней могут выполнятся разные операции. В нашей програамме значение переменной t выводится на дисплей. В нашем случае выведятся числа 2, 3, 746. Число 746 - так называемое число "мусор" оно такое так как ей не присваивали значение в первом блоке.

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

#include <stdio.h>

int x=145;/*Описание внешней переменной*/

main()

{

extern int x,y;

printf("x=%d y=%d \n",x,y);

}

int y=541; /*Описание внешней переменной*/

Внешнии переменные могут определятся вне квкой-либо функции; при этом выделяется фактическая память. В любой другой функции, обращающейся к этим переменным, они должны описываться; делается явно с помощью описателя extern. Все внешние переменные размещают в начале исходного модуля (вне всяких функций!), опуская дополнительные описания со словом extern внутри функций. Конечно, если внешняя переменная и функция, которая ее использует, размещены в разных файлах, описывать эту переменную в функции необходиммо. Но самым важным способом является описание каждой внешней перемнной с ключевого слова extern в любой функции, которая ее использует. А еще лучше избегать применения внешних переменных, так как они часто служат источником труднообнаруживаемых ошибок.

int x=3; /*описание внешней переменной */

/* увелечение x */

int plus1()

{

x=x+1;

printf("прибавляем единицу: x=%d\n",x);

}

/*уменьшение x */

int mainus1()

{

x=x-1;

printf("вычитаем единицу: x=%d\n",x);

}

main()

{

printf("начальное значение x=%d\n",x);

plus1();

minus1();

minus1();

printf("конечное значение x=%d \n", x);

}

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

static char c; static int a=1;

переменная объявлена как статическая.

*статические переменные*/

#include <stdio.h>

plus1()

{

static int x=0;

x=x+1;

printf("x=%d\n",x);

}

main()

{

plus1();

plus1();

plus1();

}

Начальное значение равное нулю присваивает переменной x только один раз. Затем в программе main, функция plus1() несколько раз запускается, так как при каждом запуске функции аргумент x не изменяется, а оставляет значение из предыдущей функции. Таким образом повторение функции plus1 обеспечивает увелечение переменной x на 1 при каждом запуске 1, 2, 3 ...

Регистровые переменные объявляются в программе с помощью ключевого слова register и по замыслу автора языка Си должны хранится в сверх быстрой памяти ЭВМ - регистрах. Используются аналогично автоматическим переменным. Целесообразность их применения для увелечения быстродействия программы представляется в большинстве случаев сомнительной.

39.2

Время жизни и область видимости программных объектов

Время жизни переменной (глобальной или локальной) определяется по следующим правилам.

1. Переменная, объявленная глобально (т.е. вне всех блоков), существует на протяжении всего времени выполнения программы.

2. Локальные переменные (т.е. объявленные внутри блока) с классом памяти register или auto, имеют время жизни только на период выполнения того блока, в котором они объявлены. Если локальная переменная объявлена с классом памяти static или extern, то она имеет время жизни на период выполнения всей программы.

Видимость переменных и функций в программе определяется следующими правилами.

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

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

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

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

Метки в функциях видимы на протяжении всей функции.

Имена формальных параметров, объявленные в списке параметров прототипа функции, видимы только от точки объявления параметра до конца объявления функции.

39.2

Область (сфера) действия идентификатора (имени) - это часть программы, в которой идентификатор может быть использован для доступа к связанному с ним объекту. Область действия зависит от того, где и как определены объекты и описаны

идентификаторы. Здесь имеются следующие возможности: блок, функция, прототип функции, файл (модуль) и класс.

Если идентификатор описан (определен) в блоке, то область его действия – от точки описания до конца блока, включая все вложенные блоки, такая переменная называется локальной (как уже говорилось). Когда блок является телом функции, то в нем определены не только описанные в нем объекты, но и указанные в заголовке функции формальные параметры. Таким образом, сфера действия формальных па- раметров в определении функции есть тело функции. Если переменная объявлена вне любого блока, она называется глобальной и областью ее действия считается файл, в котором она определена, от точки описания до конца. Например, областью действия переменной ch является весь файл с програм- мой, а переменной j - только строка печати printf ("%c\n", ch) (пример 7). Имя переменной должно быть уникально в своей области действия. Понятие видимость объекта понадобилось в связи с возможностью повтор- ных определений идентификатора внутри вложенных блоков (или функций). В этом случае разрывается исходная связь имени с объектом, который становится «невиди- мым» из блока, хотя сфера действия имени сохраняется. Достаточно часто сфера (область) действия идентификатора и видимость связанного с ним объекта совпада- ют. Область действия может превышать видимость, но обратное невозможно. К гло- бальной переменной из вложенного блока, имеющего переменную с тем же именем, можно обратиться, используя операцию доступа к области видимости ::.

Пример.

/*часть программы, демонстрирующая область видимости некоторых переменных */

int x; // глобальное x

void f()

{

40.

Модификаторы «[],(),*» в объявлениях. 

Абстрактный-описатель - это описатель без идентификатора, состоящий из одного или более модификаторов указателя, массива или функции. Модификатор указателя (*) всегда задается перед идентификатором в описателе, а модификаторы массива [] и функции () - после него. Таким образом, чтобы правильно интерпретировать абстрактный описатель, нужно начать интерпретацию с подразумеваемого идентификатора.

Основной единицей организации и обработки данных в иерархической и сетевой модели данных служит запись (или группа данных), состоящая из элементов. Элемент данных – наименьшая (обычно поименованная) единица структуры данных, к которой СУБД может адресоваться. Элементы данных группируются в агрегаты данных – поименованные совокупности элементов или более мелких входящих агрегатов. Запись – это агрегат, который не входит в состав никакого другого агрегата.

Различают понятия тип записи и экземпляр записи. Тип записи определяется составом и последовательностью элементов и агрегатов. Экземпляр записи (или просто запись) – совокупность конкретных значений элементов в соответствии со структурой записи.

В записях и агрегатах могут быть повторяющиеся элементы. В этом случае говорят об элементах типа «вектор» или об агрегатах типа «повторяющаяся группа».

41

Базовые типы данных и перечислимый тип enum, объявление перечислимого типа через шаблон, использование перечислимого типа вместо констант.

Переменная, которая может принимать значение из некоторого списка значений, называется переменной перечислимого типа или перечислением.

Объявление перечисления начинается с ключевого слова enum и имеет два формата представления.

Формат 1. enum [имя-тега-перечисления] {список-перечисления} описатель[,описатель...];

Формат 2. enum имя-тега-перечисления описатель [,описатель..];

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

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

Переменная типа enum могут использоваться в индексных выражениях и как операнды в арифметических операциях и в операциях отношения.

В первом формате 1 имена и значения перечисления задаются в списке перечислений. Необязательное имя-тега-перечисления, это идентификатор, который именует тег перечисления, определенный списком перечисления. Описатель именует переменную перечисления. В объявлении может быть задана более чем одна переменная типа перечисления.

Список-перечисления содержит одну или несколько конструкций вида:

идентификатор [= константное выражение]

Каждый идентификатор именует элемент перечисления. Все идентификаторы в списке enum должны быть уникальными. В случае отсутствия константного выражения первому идентификатору соответствует значение 0, следующему идентификатору - значение 1 и т.д. Имя константы перечисления эквивалентно ее значению.

Идентификатор, связанный с константным выражением, принимает значение, задаваемое этим константным выражением. Константное выражение должно иметь тип int и может быть как положительным, так и отрицательным. Следующему идентификатору в списке присваивается значение, равное константному выражению плюс 1, если этот идентификатор не имеет своего константного выражения. Использование элементов перечисления должно подчиняться следующим правилам:

1. Переменная может содержать повторяющиеся значения.

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

3. Имена типов перечислений должны быть отличны от других имен типов перечислений, структур и смесей в этой же области видимости.

4. Значение может следовать за последним элементом списка перечисления.

Пример:

enum week { SUB = 0, /* 0 */

VOS = 0, /* 0 */

POND, /* 1 */

VTOR, /* 2 */

SRED, /* 3 */

HETV, /* 4 */

PJAT /* 5 */

} rab_ned ;

В данном примере объявлен перечислимый тег week, с соответствующим множеством значений, и объявлена переменная rab_ned имеющая тип week.

Во втором формате используется имя тега перечисления для ссылки на тип перечисления, определяемый где-то в другом месте. Имя тега перечисления должно относится к уже определенному тегу перечисления в пределах текущей области видимости. Так как тег перечисления объявлен где-то в другом месте, список перечисления не представлен в объявлении.

Пример:

enum week rab1;

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

41.

Все программы обрабатывают какую-то информацию. В языках C/C++ данные представляются одним из восьми базовых типов: char (текстовые данные), int (целые числа), float (числа с плавающей запятой одинарной точности), double (числа с плавающей запятой двойной точности), void (пустые значения), bool(логические значения), перечисления и указатели. Остановимся на каждом из типов данных.

• Текст (тип данных char) представляет собой последовательность символов, таких как a, Z, ? иЗ,которые могут быть разделены пробелами. Обычно каждый символ занимает 8 бит, или один байт, с диапазоном значений от 0 до 255.

• Целые числа (тип данных int) находятся в диапазоне от -32768 до 32767 и занимают 16 бит, т.е. два байта, или одно слово. В Windows 98 и WindowsNTиспользуются 32- разрядные целые, что позволяет расширить диапазон их значений от -2147483648 до 2147483647. • Числа с плавающей запятой одинарной точности (тип данных float) могут представляться как в фиксированном формате, например число л (3,14159), так и в экспоненциальном (7,56310). Диапазон значений — +/-3,4Е-38—3,4Е+38, размерность — 32 бита, т.е. 4 байта, или 2 слова.

• Числа с плавающей запятой двойной точности (тип данных double) имеют диапазон значений от +/-1,7Е-308 до +/-1,7Е+308 и размерность 64 бита, т.е. 8 байтов, или 4 слова. Ранее существовал тип longdouble с размерностью 80 бит и диапазоном от +/- 1,18Е-4932 до +/-1Д8Е+4932. В новых 32-разрядных версиях компиляторов он эквивалентен типу double и поддерживается из соображений обратной совместимости с написанными ранее приложениями.

• Перечисления представляются конечным набором именованных констант различных типов.

• Тип данных void, как правило, применяется в функциях, не возвращающих никакого значения. Этот тип данных также можно использовать для создания обобщенных указателей, как будет показано в главе "Указатели".

• Указатели, в отличие от переменных других типов, не содержат данных в обычном понимании этого слова. Вместо этого указатели содержат адреса памяти, где хранятся данные.

• Переменные нового типа данных bool в C++ могут содержать только одну из двух констант: true или false.

Переменная, которая может принимать значение из некоторого списка значений, называется переменной перечислимого типа или перечислением.

Объявление перечисления начинается с ключевого слова enum и имеет два формата представления.

Формат 1. enum [имя-тега-перечисления] {список-перечисления} описатель[,описатель...];

Формат 2. enum имя-тега-перечисления описатель [,описатель..];

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

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

Переменная типа enum могут использоваться в индексных выражениях и как операнды в арифметических операциях и в операциях отношения.

В первом формате 1 имена и значения перечисления задаются в списке перечислений. Необязательное имя-тега-перечисления, это идентификатор, который именует тег перечисления, определенный списком перечисления. Описатель именует переменную перечисления. В объявлении может быть задана более чем одна переменная типа перечисления.

Список-перечисления содержит одну или несколько конструкций вида:

идентификатор [= константное выражение]

Каждый идентификатор именует элемент перечисления. Все идентификаторы в списке enum должны быть уникальными. В случае отсутствия константного выражения первому идентификатору соответствует значение 0, следующему идентификатору - значение 1 и т.д. Имя константы перечисления эквивалентно ее значению.

Идентификатор, связанный с константным выражением, принимает значение, задаваемое этим константным выражением. Константное выражение должно иметь тип int и может быть как положительным, так и отрицательным. Следующему идентификатору в списке присваивается значение, равное константному выражению плюс 1, если этот идентификатор не имеет своего константного выражения. Использование элементов перечисления должно подчиняться следующим правилам:

1. Переменная может содержать повторяющиеся значения.

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

3. Имена типов перечислений должны быть отличны от других имен типов перечислений, структур и смесей в этой же области видимости.

4. Значение может следовать за последним элементом списка перечисления.

42

Агрегаты данных, описание агрегатов через шаблоны, вложенные описания

Наряду с массивами в Си/Си++ имеются агрегаты данных типа структур и объединений. Тип структуры представляет собой упорядоченную совокупность данных различных типов, к которой можно обращаться как к единому данному. Описание структурного типа строится по схеме:

struct идентификатор

{ деклараторы членов } деклараторы_инициализаторы;

Такое объявление выполняет две функции, во-первых объявляется структурный тип, во-вторых объявляются переменные этого типа.

Идентификатор после ключевого слова struct является именем структурного типа. Имя типа может отсутствовать, тогда тип будет безымянный и в других частях программы нельзя будет объявлять данные этого типа. Деклараторы_инициализаторы объявляют конкретные переменные структурного типа, т.е. данные описанного типа, указатели на этот тип и массивы данных. Деклараторы_инициализаторы могут отсутствовать, в этом случае объявление описывает только тип структуры.

43.

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

struct { список определений }

В структуре обязательно должен быть указан хотя бы один компонент. Определение структур имеет следующий вид:

тип-данных описатель;

где тип-данных указывает тип структуры для объектов, определяемых в описателях. В простейшей форме описатели представляют собой идентификаторы или массивы.

Пример:

struct { double x,y; } s1, s2, sm[9]; struct { int year; char moth, day; } date1, date2;

Переменные s1, s2 определяются как структуры, каждая из которых состоит из двух компонент х и у. Переменная sm определяется как массив из девяти структур. Каждая из двух переменных date1, date2 состоит из трех компонентов year, moth, day. >p>

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

struct { unsigned идентификатор 1 : длина-поля 1; unsigned идентификатор 2 : длина-поля 2; }

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

Пример:

struct { unsigned a1 : 1; unsigned a2 : 2; unsigned a3 : 5; unsigned a4 : 2; } prim;

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

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

43.

Структуры данных struct, поля битов в структуре

Структура - это набор данных, где данные могут быть разного типа.

Сверху вниз справа налево. Например, структура может содержать несколько переменных типа int и несколько переменных типа char. Переменные, которые содержаться в структуре называются членами или полями структуры. Структуры можно определять с помощью ключевого слова struct.

Пример описания структуры:

struct student {     char name[50];     int kurs;     int age; }; Мы определили структуру в которую входят переменные kurs, age и массив name. В этом описании student является шаблоном структуры, struct student является типом данных. После описания структуры нужно ставить точку с запятой. Чтобы использовать структуру необходимо объявить переменные типа struct student.

Например,struct student s1, s2;

Переменные s1 и s2 являются переменными типа struct student. Компилятор автоматически выделит память под эти переменные. Под каждую из переменных типа структуры выделяется непрерывный участок памяти.

Доступ к полям структуры производится по имени поля (а не по индексу, как у массивов):

имя_структурной_переменной.имя_поля (операция точка) указатель_на_структуру -> имя_поля (операция указатель)

Например,

strcpy(s1.name, "Бардин Павел"); s1.kurs=3; s1.age=20;

В языке С есть возможность объявлять переменные структуры при описании структуры:

struct student {     char name[50];     int kurs;     int age; } s1, s2;

Элементами или полями структуры могут быть переменные, массивы, ранее определенные структуры. Функции не могут быть полями структуры (В языке Си). В языке С++ функции могут быть полями структуры и такие структуры называются классами. Они определяются с помощью ключевого слова class.

Для переменных s1 и s2 возможно присваивание  s1=s2

так как эти переменные созданы на базе одного шаблона. После такого присваивания поля структуры s1 будут содержать ту же информацию, что и поля s2. Если мы опишем две структуры с одними и теми же полями, но первая структура будет иметь шаблон student1, а вторая student2, то присваивание s1=s2 недопустимо.

Элементом структуры может быть битовое поле, обеспечивающее доступ к отдельным битам памяти. Вне структур битовые поля объявлять нельзя. Нельзя также организовывать массивы битовых полей и нельзя применять к полям операцию определения адреса. В общем случае тип структуры с битовым полем задается в следующем виде: struct { unsigned идентификатор 1 : длина-поля 1;

unsigned идентификатор 2 : длина-поля 2; }

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

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

44.

Объединение данных union.

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

Объявление то же самое, как и объявление структуры, только вместо специального слова struct используется union. Вот пример:

union chislo {    int a;    float b; };

Разрешенные операции:

можно присваивать объединения друг другу

адрес брать так же ни кто не запрещал

к элементам можно получить доступ, так же как и в структурах, т.е. через (.) или (->)

Объединение ( union ) можно инициализировать только один значением, причем оно должно соответствовать первому элементу этого объединения. В нашем случае:

union chislo A = {34 }; // пойдет union chislo B = {34.56 }; // нельзя

У нас первым элементом расположено поле int, поэтому при инициализации так же должно быть поле int.

44.

Объединение подобно структуре, однако в каждый момент времени может использоваться (или другими словами быть ответным) только один из элементов объединения. Тип объединения может задаваться в следующем виде:

union { описание элемента 1; ... описание элемента n; };

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

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

Объединение применяется для следующих целей:

- инициализации используемого объекта памяти, если в каждый момент времени только один объект из многих является активным;

- интерпретации основного представления объекта одного типа, как если бы этому объекту был присвоен другой тип.

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

Пример:

union { char fio[30]; char adres[80]; int vozrast; int telefon; } inform; union { int ax; char al[2]; } ua;

При использовании объекта infor типа union можно обрабатывать только тот элемент который получил значение, т.е. после присвоения значения элементу inform.fio, не имеет смысла обращаться к другим элементам. Объединение ua позволяет получить отдельный доступ к младшему ua.al[0] и к старшему ua.al[1] байтам двухбайтного числа ua.ax .

45.

Массивы агрегатов данных.

Создаются из базовых типов. Базовые типы опред либо в язые, либо программистом самост. Typedef старый_тип новый_тип.

Массив представляет собой агрегат из нескольких переменных одного и того же типа. Массив с именем a из LENGTH элементов типа TYPE объявляется так: TYPE a[LENGTH];

Это соответствует тому, что объявляются переменные типа TYPE со специальными именами a[0], a[1], ..., a[LENGTH-1]. Каждый элемент массива имеет свой номер - индекс. Доступ к x-ому элементу массива осуществляется при помощи операции индексации:

int x = ... ; /* целочисленный индекс */

TYPE value = a[x]; /* чтение x-ого элемента */

a[x] = value; /* запись в x-тый элемент */

Статические массивы можно объявлять с инициализацией, перечисляя значения их элементов в {} через запятую. Если задано меньше элементов, чем длина массива остальные элементы считаются нулями:

int a10[10] = { 1, 2, 3, 4 }; /* и 6 нулей */

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

int a3[] = { 1, 2, 3 }; /* как бы a3[3] */

В большинстве современных компьютеров (с фон-Неймановской архитектурой) память представляет собой массив байт. Когда мы описываем некоторую переменную или массив, в памяти выделяется непрерывная область для хранения этой переменной. Все байты памяти компьютера пронумерованы. Номер байта, с которого начинается в памяти наша переменная, называется адресом этой переменной (адрес может иметь и более сложную структуру, чем просто целое число - например состоять из номера сегмента памяти и номера байта в этом сегменте). В Си адрес переменной можно получить с помощью операции взятия адреса &. Пусть у нас есть переменная var, тогда &var - ее адрес. Адрес нельзя присваивать целой переменной; для хранения адресов используются указатели (смотри ниже).

Данное может занимать несколько подряд идущих байт. Размер в байтах участка памяти, требуемого для хранения значения типа TYPE, можно узнать при помощи операции sizeof(TYPE), а размер переменной - при помощи sizeof(var). Всегда выполняется sizeof(char)==1. В некоторых машинах адреса переменных (а также агрегатов данных массивов и структур) кратны sizeof(int) или sizeof(double) - это так называемое "выравнивание (alignment) данных на границу типа int". Это позволяет делать доступ к данным более быстрым (аппаратура работает эффективнее).

Язык Си предоставляет нам средство для работы с адресами данных - указатели (pointer)| -. Указатель физически - это адрес некоторой переменной ("указуемой" переменной). Отличие указателей от машинных адресов состоит в том, что указатель может содержать адреса данных только определенного типа. Указатель ptr, который может указывать на данные типа TYPE, описывается так:

TYPE var; /* переменная */

TYPE *ptr; /* объявление ук-ля */

ptr = & var;

В данном случае мы занесли в указательную переменную ptr адрес переменной var. Будем говорить, что указатель ptr указывает на переменную var (или, что ptr установлен на var). Пусть TYPE равно int, и у нас есть массив и указатели:

int array[LENGTH], value;

int *ptr, *ptr1;

Установим указатель на x-ый элемент массива

ptr = & array[x];

Указателю можно присвоить значение другого указателя на такой же тип. В результате оба указателя будут указывать на одно и то же место в памяти: ptr1 = ptr;

Мы можем изменять указуемую переменную при помощи операции *

*ptr = 128; /* занести 128 в указуемую перем. */

value = *ptr; /* прочесть указуемую переменную */

В данном случае мы заносим и затем читаем значение переменной array[x], на которую поставлен указатель, то есть

*ptr означает сейчас array[x]

Таким образом, операция * (значение по адресу) оказывается обратной к операции & (взятие адреса):

& (*ptr) == ptr и * (&value) == value

Операция * объясняет смысл описания TYPE *ptr; оно означает, что значение выражения *ptr будет иметь тип TYPE. Название же типа самого указателя - это (TYPE *). В частности, TYPE может сам быть указательным типом - можно объявить указатель на указатель, вроде char **ptrptr;

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

массив указатель

____________ _____

array: | array[0] | ptr:| * |

| array[1] | |

| array[2] |<--------- сейчас равен &array[2]

| ... |

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

ptr = array; или ptr = &array[0];

но не

ptr = &array;

Операция & перед одиноким именем массива не нужна и недопустима!

ОСНОВНОЕ ПРАВИЛО: пусть ptr - указатель или имя массива. Тогда операции индексации, взятия значения по адресу, взятия адреса и прибавления целого к указателю связаны соотношениями:

ptr[x] тождественно *(ptr+x)

&ptr[x] тождественно ptr+x

46.

Спецификаторы и модификаторы в объявлениях

Важное отличие языка СИ от других языков (PL1, FORTRAN, и др.) является отсутствие принципа умолчания, что приводит к необходимости объявления всех переменных используемых в программе явно вместе с указанием соответствующих им типов.

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

[спецафикатор-класа-памяти] спецификатор-типа

описатель [=инициатор] [,описатель [= инициатор] ]...

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

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

Инициатор - задает начальное значение или список начальных значений, которые (которое) присваивается переменной при объявлении.

Спецификатор класса памяти - определяется одним из четырех ключевых слов языка СИ: auto, extern, register, static, и указывает,каким образом будет распределяться память под объявляемую переменную, с одной стороны, а с другой, область видимости этой переменной, т.е., из каких частей программы можно к ней обратиться.

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

спецификатор-типа [ модификатор ] * описатель .

Спецификатор-типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов может быть выполнено с помощью операции приведения типов.

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

Для модификации размера указателя можно использовать ключевые слова near, far, huge.

Как уже говорилось выше, все переменные используемые в программах на языке СИ, должны быть объявлены. Тип объявляемой переменной зависит от того, какое ключевое слово используется в качестве спецификатора типа и является ли описатель простым идентификатором или же комбинацией идентификатора с модификатором указателя (звездочка), массива (квадратные скобки) или функции (круглые скобки). При объявлении простой переменной, структуры, смеси или объединения, а также перечисления, описатель - это простой идентификатор. Для объявления указателя, массива или функции идентификатор модифицируется соответствующим образом: звездочкой слева, квадратными или круглыми скобками справа. Отметим важную особенность языка СИ, при объявлении можно использовать одновременно более одного модификатора, что дает возможность создавать множество различных сложных описателей типов. Однако надо помнить, что некоторые комбинации модификаторов недопустимы: - элементами массивов не могут быть функции, - функции не могут возвращать массивы или функции. При инициализации сложных описателей квадратные и круглые скобки (справа от идентификатора) имеют приоритет перед звездочкой (слева от идентификатора). Квадратные или круглые скобки имеют один и тот же приоритет и раскрываются слева направо. Спецификатор типа рассматривается на последнем шаге, когда описатель уже полностью проинтерпретирован. Можно использовать круглые скобки, чтобы поменять порядок интерпретации на необходимый.

46.

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

[спецафикатор-класа-памяти] спецификатор-типа

описатель [=инициатор] [,описатель [= инициатор] ]...

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

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

Инициатор - задает начальное значение или список начальных значений, которые (которое) присваивается переменной при объявлении.

Спецификатор класса памяти - определяется одним из четырех ключевых слов языка СИ: auto, extern, register, static, и указывает,каким образом будет распределяться память под объявляемую переменную, с одной стороны, а с другой, область видимости этой переменной, т.е., из каких частей программы можно к ней обратиться.

47.

Тип функции

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

char MyF1 (int, int, int*, float);

char MyNew (int MyP1, int MyP2, int* MyP3, float MyP3);

имеют один и тот же тип:

char (int, int, int*, float)

Подобную конструкцию мы назовём описанием типа функции.

А вот как выглядит описание типа функции, которая возвращает указатель на объект типа char:

char * (int, int, int*, float)

Описанию этого типа соответствует, например, функция

char *MyFp (int MyP1, int MyP2, int* MyP3, float MyP3);

Комбинируя знак ptr-операции * с именем функции мы получаем новую языковую конструкцию:

char (*MyPt1) (int MyP1, int MyP2, int* MyP3, float MyP3);

Это уже не объявление функции. Это определение указателя на функцию! Это объект со следующими характеристиками:

его имя MyPt1,

это указатель на функцию,

эта функция должна возвращать значения типа char,

список её формальных параметров имеет вид (int,int,int*, float).

Так что это должны быть функции со строго определёнными характеристиками. В нашем случае - это функции типа

char (int, int, int*, float)

Описание типа указателя на функцию, возвращающую указатель на объект типа char с параметрами (int, int, int*, float)

char * (int, int, int*, float)

отличается от описания типа этой функции дополнительным элементом (*):

char * (*) (int, int, int*, float).

Пример определения подобного указателя:

char* (*MyPt2) (int MyP1, int MyP2, int* MyP3, float MyP3);

И опять новый объект:

его имя MyPt2,

это указатель на функцию,

эта функция должна возвращать указатель на объекты типа char,

список её формальных параметров имеет вид (int,int,int*, float).

47.

Функция и указатель на функцию: объявление.

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

Функция - это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Термин функция в языке программирования С эквивалентен понятию подпрограммы.

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

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

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

48

В языке Си есть инструмент для управления памятью. Это классы памяти. Каждая переменная принадлежит к одному из следующих классов памяти (их 4)

1.auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является сокращением слова automatic.

Автоматические переменные в программе можно описать так:

auto A; auto char c1; auto int x= 125;

Пимер 2.3#include <stdio.h>

main()

{

int t;

{

int t=2;

{

int t=3;

printf("%d\n",t);

}

printf("%d\n",t);

}

printf("%d\n",t);

}

2.static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.

Описание статических переменных выглядит так:

static char c; static int a=1;

Рассмотрим пример 2.6, в котором переменная объявлена как статическая.

Пример 2.6*статические переменные*/

#include <stdio.h>

plus1()

{

static int x=0;

x=x+1;

printf("x=%d\n",x);

}

main()

{

plus1();

plus1();

plus1();

}

3.extern - внешний - идентификаторы, называемые внешними, external, используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.

4.register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.

48.

Класс памяти

Класс памяти определяет порядок размещения объекта в памяти. Различают автоматический и статический классы памяти. C++ располагает четырьмя спецификаторами класса памяти:

auto

register

static

extern

по два для обозначения принадлежности к автоматическому и статическому классам памяти.

В свою очередь, статический класс памяти может быть локальным (внутренним) или глобальным (внешним).

Следующая таблица иллюстрирует иерархию классов памяти. Динамический класс памяти Статический класс памяти

Автоматический Регистровый Локальный Глобальный

auto register static Extern

Спецификаторы позволяют определить класс памяти определяемого объекта:

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

register. Ещё один спецификатор автоматического класса памяти. Применяется к объектам, по умолчанию располагаемым в локальной памяти. Представляет из себя "ненавязчивую просьбу" к транслятору (если это возможно) о размещении значений объектов, объявленных со спецификатором register в одном из доступных регистров, а не в локальной памяти. Если по какой-либо причине в момент начала выполнения кода в данном блоке операторов регистры оказываются занятыми, транслятор обеспечивает с этими объектами обращение, как с объектами класса auto. Очевидно, что в этом случае объект располагается в локальной области памяти.

static. Спецификатор внутреннего статического класса памяти. Применяется только(!) к именам объектов и функций. В C++ этот спецификатор имеет два значения. Первое означает, что определяемый объект располагается по фиксированному адресу. Тем самым обеспечивается существование объекта с момента его определения до конца выполнения программы. Второе значение означает локальность. Объявленный со спецификатором static локален в одном программном модуле (то есть, недоступен из других модулей многомодульной программы) или в классе (о классах - позже). Может использоваться в объявлениях вне блоков и функций. Также используется в объявлениях, расположенных в теле функций и в блоках операторов.

extern. Спецификатор внешнего статического класса памяти. Обеспечивает существование объекта с момента его определения до конца выполнения программы. Объект, объявленный со спецификатором extern доступен во всех модулях программы, то есть глобален.

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

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

Локальная:

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

Файл:

Имя, описанное вне любого блока или класса, может использоваться в файле, где оно описано, после места описания.

Класс:

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

Имя может быть скрыто посредством явного описания того же имени в блоке или классе. Имя в блоке или классе может быть скрыто только именем, описанным в охватываемом блоке или классе. Скрытое нелокальное имя также может использоваться, когда его область видимости указана операцией :: . Имя класса, скрытое именем, которое не является именем типа, все равно может использоваться, если перед ним стоит class, struct или union. Имя перечисления enum, скрытое именем, которое не является именем типа, все равно может использоваться, если перед ним стоит enum.

49.

Рекурсивные функции

Ситуацию, когда функция тем или иным образом вызывает саму себя, называют рекурсией. Рекурсия, когда функция обращается сама к себе непосредственно, называется прямой; в противном случае она называется косвенной.

Все функции языка С++ (кроме функции main) могут быть использованы для построения рекурсии.

В рекурсивной функции обязательно должно присутствовать хотя бы одно условие, при выполнении которого последовательность рекурсивных вызовов должна быть прекращена.

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

Если попытаться отследить по тексту программы процесс выполнения рекурсивной функции, то мы придем к такой ситуации: войдя в рекурсивную функцию, мы “движемся” по ее тексту до тех пор, пока не встретим ее вызова, после чего мы опять начнем выполнять ту же самую функцию сначала. При этом следует отметить самое важное свойство рекурсивной функции - ее первый вызов еще не закончился. Чисто внешне создается впечатление, что текст функции воспроизводится (копируется) всякий раз, когда функция сама себя вызывает. На самом деле этот эффект воспроизводится в компьютере. Однако копируется при этом не весь текст функции (не вся функция), а только ее части, связанные с данными (формальные, фактические параметры, локальные переменные и точка возврата). Алгоритм (операторы, выражения) рекурсивной функции не меняется, поэтому он присутствует в памяти компьютера в единственном экземпляре.

Пример1. Вычислить n!

Определение факториала рекурсивно:

0!=1; n!=(n-1)!*n при n=1,2,3, …

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

long fact (int n){

if ( n<1 ) return 1;

else return n*fact(n-1);

}

49

Рекурсивные функции

Рекурсивная функция – это функция, в теле которой имеется вызов самой себя. Использование рекурсивных функций бывает удобным при программировании ряда задач, например вычислении факториала неко- торого числа N

/* Пример 43 */

Int factorial(int n)

{

int a;

if ( n==1) return 1;

a = factorial(n-1)*n;

return a

}

Вызов функции при рекурсии не создает новую копию функции в памяти, а создает новые копии локальных переменных и параметров. Из рекурсивной функции необходимо предусмотреть выход, иначе это вызовет “зависание” вычислительной системы. Необходимо помнить, что при большом числе рекурсивных вызовов будет происходить быстрое заполнение стека, размер которого ограничен. Это может вызвать остановку программы. Поэтому, использование рекурсии как метода программирования, должно быть осторожным.

50.

Автономные C++ объекты. Автономными объектами будем называть такие объекты, которые имеют корректные конструкторы, деструкторы и операторы присваивания.

Используя автономные объекты можно частично решить некоторые проблемы, но возникает новая - проблема совместимости с C библиотеками. Нужен набор функций преобразования из C++ объектов в параметры C функций и обратно.

Функции преобразования

Каждому типу поставим в соответствие набор функций преобразования в параметры и из параметров C функций.

// С тип.

template<class T>

class c_t;

// Тип C [IN] параметра.

template<class T>

class c_in_t;

// Тип C [IN, OUT] параметра.

template<class T>

class c_in_out_t;

// Тип C [OUT] параметра.

template<class T>

class c_out_t;

// Функция преобразующая константый объект в C [IN] параметр.

template<class T>

typename c_in_t<T>::type c_in(const T &);

// Функция преобразующая объект в C [IN, OUT] параметр.

template<class T>

typename c_in_out_t<T>::type c_in_out(T &);

// Функция преобразующая объект в C [OUT] параметр.

template<class T>

typename c_out_t<T>::type c_out(T &);

// Функция возвращающая ссылку на константный объект по C [IN] параметру.

template<class T>

const T & cpp_in(const typename c_in_t<T>::type &X);

// Функция возвращающая ссылку на объект по C [IN, OUT] параметру.

template<class T>

T & cpp_in_out(const typename c_in_out_t<T>::type &X);

// Функция возвращающая ссылку на объект по C [OUT] параметру.

template<class T>

T & cpp_out(const typename c_out_t<T>::type &X);

Примеры использования:

Тогда, вызов такой C функции

HRESULT Function(/*IN*/ IUnknown *P, /*IN,OUT*/IUnknown **PP);

будет выглядеть так:

pointer<IUnknown> P;

pointer<IUnknown> PP;

...

HRESULT R = Function(c_in(P), c_in_out(P));

Реализация C функции на C++:

HRESULT Function(/*IN*/ IUnknown *P_, /*IN,OUT*/IUnknown **PP_)

{

const pointer<IUnknown> &P = cpp_in<pointer<IUnknown> >(P_);

pointer<IUnknown> &PP = cpp_in_out<pointer<IUnknown> >(PP_);

// Дальше, безопасно работаем с P и c PP, вместо небезопасных P_ и PP_.

...

PP = P;

...

return S_OK;

}

Осталось только специализировать.

51

Работа с символами

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

Помните "Таблицу символов"? Давайте сделаем её сами!

Вывод осуществим в TStringGrid. Этот компонент представляет собой таблицу, где в каждой ячейке записано текстовое значение. Компонент расположен на вкладке Additional (по умолчанию следует прямо за Standard). Перво-наперво настроим нашу табличку. Нам нужны всего две колонки: в одной будем отображать код символа, а в другой - сам символ. Количество колонок задаётся в свойстве с логичным названием ColCount. Устанавливаем его равным 2. По умолчанию у StringGrid задан один фиксированный столбец и одна фиксированная строка (они отображаются серым цветом). Столбец нам не нужен, а вот строка очень кстати, поэтому ставим FixedCols = 0, а FixedRows оставляем = 1.

Заполнение осуществим прямо при запуске программы, т.е. не будем ставить никаких кнопок. Итак, создаём обработчик события OnCreate() формы.

Количество символов в кодовой таблице 256, плюс заголовок - итого 257. Зададим число строк программно (хотя можно задать и в Инспекторе Объекта):

procedure TForm1.FormCreate(Sender: TObject);

begin

StringGrid1.RowCount:=257;

end;

Вывод делается крайне просто - с помощью цикла. Просто проходим числа от 0 до 255 и выводим соответствующий символ. Также выводим надписи в заголовок. Доступ к ячейкам StringGrid осуществляется с помощью свойства Cells: Cells[номер_столбца,номер_строки]. В квадратных скобках указываются номера столбца и строки (начинаются с нуля). Значения текстовые.

procedure TForm1.FormCreate(Sender: TObject);

var

i: Integer;

begin

StringGrid1.RowCount:=257;

StringGrid1.Cells[0,0]:='Код';

StringGrid1.Cells[1,0]:='Символ';

for i := 0 to 255 do

begin

StringGrid1.Cells[0,i+1]:=IntToStr(i);

StringGrid1.Cells[1,i+1]:=Chr(i);

end;

end;

Запускаем, смотрим.

Если вы внимательно посмотрите на нашу таблицу, то увидите, что многие символы отображаются в виде квадратиков. Нет, это не значки. Так отображаются символы, не имеющие визуального отображения. Т.е. символ, например, с кодом 13 существует, но он невидим. Эти символы используются в дополнительных целях. К примеру, символ #0 (т.е. символ с кодом 0) часто применяется для указания отсутствия символа. Существуют также строки, называемые null-terminated - это строки, заканчивающиеся символом #0. Такие строки используются в языке Си.

По кодам можно опознавать нажатия клавиш. К примеру, клавиша Enter имеет код 13, Escape - 27, пробел - 32, Tab - 9 и т.д.

Давайте добавим в нашу программу возможность узнать код любой клавиши. Для этого обработаем событие формы OnKeyPress(). Чтобы этот механизм работал, необходимо установить у формы KeyPreview = True.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);

begin

ShowMessage('Код нажатой клавиши: '+IntToStr(Ord(Key)));

end;

Здесь мы выводим окошко с текстом. У события есть переменная Key, в которой хранится символ, соответствующий нажатой клавише. С помощью функции Ord() узнаём код этого символа, а затем функцией IntToStr() преобразуем это число в строку.

51.

Для копирования строк существуют несколько библиотечных функций, наиболее общеупотребительной из которых является функция char* strcpy(char* dest, const char* src)

Функция посимвольно копирует содержимое строки, на которую указывает src в строку, на которую указывает dest и возвращает dest. Так как массив может быть преобразован в указатель, такой вызов функции абсолютно легален:char str1[10], str2[10];

strcpy(str1, "Hello");

strcpy(str2, str1);

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

Снизить риск такого развития событий способна функция char* strncpy(char* dest, const char* src, size_t count)

Последний параметр – максимальное количество копируемых символов. Таким образом, передавая туда размер приемника, вы гарантируете, что функция никогда не выйдет за пределы выделенной памяти. Однако помните, что если исходная строка будет скопирована не полностью, нуль-терминатор не появится в результирующей строке. Его придется записать самостоятельно.

Для лексикографического сравнения строк используются функции strcmp и stricmp. Первая сравнивает строки с учетом регистра, вторая – без. Однако, все это относится только к латинице. Если вы хотите сравнивать без учета регистра кириллические строки, придется разобраться с локалями.

Прототипы этих функций таковы:int stricmp(const char *string1, const char *string2);

int strcmp(const char *string1, const char *string2);

Обе функции возвращают число меньшее 0, если первая строка меньше второй, большее нуля если первая строка больше второй и 0, если строки лексикографически равны.

Полагаю, вам не придет в голову сравнивать строки, используя операции ‘<’ и ‘>’.

Длина строки

Для вычисления длины строки используется функция size_t strlen(const char *string);

Функция возвращает длину строки, не включая нуль-терминатор. Как всегда, следите за тем, чтобы в выделенной под string памяти все же нашелся такой символ. В противном случае функция выйдет за пределы выделенной памяти и все будет плохо. Напомню, что для определения длины строки функции придется последовательно обратиться ко всем ее символам до нуль-терминатора, а значит, потенциально эта операция довольно дорогая. Поэтому, не следует использовать эту функцию в циклах.

Преобразования строк

Для преобразования числа в строку и наоборот можно использовать функции sprintf и sscanf. Например, так:char str[50];

int i=15;

int j;

sprintf(str, "%d", i); // Записать в str строковое представление i

sscanf(str, "%d", &j); // Записать в j число, содержащееся в строке str

sprintf(str, "i=%d and j=%d", i, j);

// содержимое str: "i=15 and j=15"

Эти функции очень похожи на printf и scanf, за исключением того, что они работают не с консолью, а со строковым буфером.

Хотя sprintf и sscanf довольно удобны, у них есть несколько недостатков. Во-первых, они не всегда быстро работают, во-вторых не типобезопасны. Например, если в строке формата вы укажите, что передаете два целых, а вместо этого передадите два double, ошибка обнаружится только при выполнении программы и найти ее причину будет не так-то просто.

В-третьих, доступно целое семейство функций atof, atoi, atol и itoa, ltoa. Все они очень похоже между собой. Функции из первой группы преобразуют строку в число (float, int или long) в зависимости от окончания. Функции из второй группы выполняют обратное преобразование.

Конкатенация (объединение) строк:

Есть две специальные функции:char* strcat(char* dest, const char* source)

char* strncat(char* dest, const char* source, size_t size)

Эти функции добавляют к строке, на которую указывает dest, символы из строки source. Первая версия добавляет все символы до нуль-терминатора, вторая – максимум size символов. Результирующая строка завершается нуль-терминатором.

52.

Возможны только две операции с функциями: вызов и взятие адреса. Указатель, полученный с помощью последней операции, можно впоследствии использовать для вызова функции. Например:

void error(char* p) { /* ... */ }

void (*efct)(char*); // указатель на функцию

void f()

{

efct = &error; // efct настроен на функцию error

(*efct)("error"); // вызов error через указатель efct

}

Для вызова функции с помощью указателя (efct в нашем примере) надо вначале применить операцию косвенности к указателю - *efct. Поскольку приоритет операции вызова () выше, чем приоритет косвенности *, нельзя писать просто *efct("error"). Это будет означать *(efct("error")), что является ошибкой. По той же причине скобки нужны и при описании указателя на функцию. Однако, писать просто efct("error") можно, т.к. транслятор понимает, что efct является указателем на функцию, и создает команды, делающие вызов нужной функции.

Отметим, что формальные параметры в указателях на функцию описываются так же, как и в обычных функциях. При присваивании указателю на функцию требуется точное соответствие типа функции и типа присваиваемого значения. Например:

void (*pf)(char*); // указатель на void(char*)

void f1(char*); // void(char*);

int f2(char*); // int(char*);

void f3(int*); // void(int*);

void f()

{

pf = &f1; // нормально

pf = &f2; // ошибка: не тот тип возвращаемого

// значения

pf = &f3; // ошибка: не тот тип параметра

(*pf)("asdf"); // нормально

(*pf)(1); // ошибка: не тот тип параметра

int i = (*pf)("qwer"); // ошибка: void присваивается int

}

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

Часто бывает удобнее обозначить тип указателя на функцию именем, чем все время использовать достаточно сложную запись. Например:

typedef int (*SIG_TYP)(int); // из <signal.h>

typedef void (SIG_ARG_TYP)(int);

SIG_TYP signal(int, SIG_ARG_TYP);

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

typedef void (*PF)();

PF edit_ops[] = { // команды редактора

&cut, &paste, &snarf, &search

};

PF file_ops[] = { // управление файлом

&open, &reshape, &close, &write

};

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

Выбор происходит нажатием клавиши мыши:

PF* button2 = edit_ops;

PF* button3 = file_ops;

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

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

Если пользователь выбрал позицию меню, которая определяется, например, как позиция 3 для клавиши 2, то соответствующая команда реализуется вызовом:

(*button2[3])();

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

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

typedef int (*CFT)(void*,void*);

void sort(void* base, unsigned n, unsigned int sz, CFT cmp)

/*

Сортировка вектора "base" из n элементов

в возрастающем порядке;

используется функция сравнения, на которую указывает cmp.

Размер элементов равен "sz".

Алгоритм очень неэффективный: сортировка пузырьковым методом

*/

{

for (int i=0; i<n-1; i++)

for (int j=n-1; i<j; j--) {

char* pj = (char*)base+j*sz; // b[j]

char* pj1 = pj - sz; // b[j-1]

if ((*cmp)(pj,pj1) < 0) {

// поменять местами b[j] и b[j-1]

for (int k = 0; k<sz; k++) {

char temp = pj[k];

pj[k] = pj1[k];

pj1[k] = temp;

}

}

}

}

В подпрограмме sort неизвестен тип сортируемых объектов; известно только их число (размер массива), размер каждого элемента и функция, которая может сравнивать объекты. Мы выбрали для функции sort() такой же заголовок, как у qsort() - стандартной функции сортировки из библиотеки С. Эту функцию используют настоящие программы.

Покажем, как с помощью sort() можно отсортировать таблицу с такой структурой:

struct user {

char* name; // имя

char* id; // пароль

int dept; // отдел

};

typedef user* Puser;

user heads[] = {

"Ritchie D.M.", "dmr", 11271,

"Sethi R.", "ravi", 11272,

"SZYmanski T.G.", "tgs", 11273,

"Schryer N.L.", "nls", 11274,

"Schryer N.L.", "nls", 11275

"Kernighan B.W.", "bwk", 11276

};

void print_id(Puser v, int n)

{

for (int i=0; i<n; i++)

cout << v[i].name << '\t'

<< v[i].id << '\t'

<< v[i].dept << '\n';

}

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

int cmp1(const void* p, const void* q)

// сравнение строк, содержащих имена

{

return strcmp(Puser(p)->name, Puser(q)->name);

}

int cmp2(const void* p, const void* q)

// сравнение номеров разделов

{

return Puser(p)->dept - Puser(q)->dept;

}

Следующая программа сортирует и печатает результат:

int main()

{

sort(heads,6,sizeof(user), cmp1);

print_id(heads,6); // в алфавитном порядке

cout << "\n";

sort(heads,6,sizeof(user),cmp2);

print_id(heads,6); // по номерам отделов

}

Допустима операция взятия адреса и для функции-подстановки, и для перегруженной функции ($$R.13.3).

Отметим, что неявное преобразование указателя на что-то в указатель типа void* не выполняется для параметра функции, вызываемой через указатель на нее. Поэтому функцию

int cmp3(const mytype*, const mytype*);

нельзя использовать в качестве параметра для sort(). Поступив иначе, мы нарушаем заданное в описании условие, что cmp3() должна вызываться с параметрами типа mytype*. Если вы специально хотите нарушить это условие, то должны использовать явное преобразование типа.

53

Любая программа на языке Си состоит из одной или более функций, причём ровно одна из них обязательно должна называться "main" ("главная"). Программа всегда начинает выполняться с главной функции main. имеется возможность выбирать имена для всех создаваемых функций, кроме той, с которой начинается выполнение программы.

Отличительным признаком функции служат круглые скобки после её имени. Круглые скобки указывают на то, что main() - имя функции. В круглых скобках в общем случае содержится информация, передаваемая функции. Идущая следом открывающая фигурная скобка { отмечает начало последовательности операторов, образующих тело функции. Закрывающая фигурная скобка } отмечает конец последовательности операторов, образующих тело функции. На этой скобке выполнение функции завершается.

Функция main, с которой начинается выполнение программы на языке программирования С, может быть определена с параметрами, которые передаются из внешнего окружения, например, из командной строки. Во внешнем окружении действуют свои правила представления данных, а точнее, все данные представляются в виде строк символов. Для передачи этих строк в функцию main используются два параметра, первый параметр служит для передачи числа передаваемых строк, второй для передачи самих строк. Общепринятые (но не обязательные) имена этих параметров argc и argv. Параметр argc имеет тип int, его значение формируется из анализа командной строки и равно количеству слов в командной строке, включая и имя вызываемой программы (под словом понимается любой текст не содержащий символа пробел). Параметр argv это массив указателей на строки, каждая из которых содержит одно слово из командной строки. Если слово должно содержать символ пробел, то при записи его в командную строку оно должно быть заключено в кавычки.

Функция main может иметь и третий параметр, который принято называть argp, и который служит для передачи в функцию main параметров операционной системы (среды) в которой выполняется программа на языке программирования С.

Заголовок функции main имеет вид:

int main (int argc, char *argv[], char *argp[])

Если, например, командная строка программы на языке программирования С имеет вид:

A:\>cprog working 'C program' 1

то аргументы argc, argv, argp представляются в памяти как показано в схеме на рис.1.

argc [ 4 ]

argv [ ]--> [ ]--> [A:\cprog.exe\0]

[ ]--> [working\0]

[ ]--> [C program\0]

[ ]--> [1\0]

[NULL]

argp [ ]--> [ ]--> [path=A:\;C:\\0]

[ ]--> [lib=D:\LIB\0]

[ ]--> [include=D:\INCLUDE\0]

[ ]--> [conspec=C:\COMMAND.COM\]

[NULL]

Рис.1. Схема размещения параметров командной строки

54.

Разбор аргументов в главной функции и их использование

Иногда при запуске программы бывает полезно передать ей какую-либо информацию. Обычно такая информация передается функции main() с помощью аргументов командной строки. Аргумент командной строки — это информация, которая вводится в командной строке операционной системы вслед за именем программы. Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента: argc и argv. Параметр argc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.

Вот простой пример использования аргумента командной строки. На экран выводятся слово Привет и ваше имя, которое надо указать в виде аргумента командной строки.

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[]) {

if(argc!=2) {

printf("Вы забыли ввести свое имя.\n");

exit(1); }

printf("Привет %s", argv[1]);

return 0; }

Очень важно правильно объявлять argv. Вот как это делают чаще всего:

char *argv[]; Пустые квадратные скобки указывают на то, что у массива неопределенная длина. Теперь получить доступ к отдельным аргументам можно с помощью индексации массива argv. Например, argv[0] указывает на первую символьную строку, которой всегда является имя программы; argv[1] указывает на первый аргумент и так далее.

Обратите внимание, если аргументы командной строки не будут указаны, то будет выведено сообщение об ошибке. В программах с аргументами командной строки часто делается следующее: в случае, когда пользователь запускает эти программы без ввода нужной информации, выводятся инструкции о том, как правильно указывать аргументы. Обычно argc и argv используют для того, чтобы передать программе начальные команды, которые понадобятся ей при запуске. Например, аргументы командной строки часто указывают такие данные, как имя файла, параметр или альтернативное поведение. Использование аргументов командной строки придает вашей программе "профессиональный внешний вид" и облегчает ее использование в пакетных файлах. Имена argc и argv являются традиционными, но не обязательными. Эти два параметра в функции main() вы можете назвать как угодно. Кроме того, в некоторых компиляторах для main() могут поддерживаться-дополнительные аргументы, поэтому обязательно изучите документацию к вашему компилятору.

Когда для программы не требуются параметры командной строки, то чаще всего явно декларируют функцию main() как не имеющую параметров. В таком случае в списке параметров этой функции используют ключевое слово void.

55.

Функции ввода-вывода: классификация функций, ввод-вывод символов и строк.

56.

Функции ввода-вывода: классификация функций, ввод-вывод символов и строк

Функции форматированного ввода-вывода

printf – производит форматированный вывод в stdout.

int printf(const char *format [,argument,…]);

scanf – выполняет форматированный вывод из потока stdin.

int scanf(const char *format [,adress,…]);

fprintf – посылает форматированный вывод в поток stream.

int fprintf(FILE *stream, const char *format [,argument,…]);

fscanf – выполняет форматированный ввод из потока stream.

int fscanf(FILE *stream, const char *format [,adress,…]);

sprintf – производит форматированный вывод в сстроку buffer.

int sprintf(char *buffer, const char *format [,argument,…]);

sscanf – выполняет форматированный ввод из строки buffer.

Int sscanf(const char *buffer, const char *format [,adress,…]);

Богатый ассортимент средств управления форматом позволяет легко создавать таблицы, графики или отчеты. Функциями, выполняющими этот вывод являются printf() - для стандартного потока вывода stdout, и fprintf() – для любого потока. Функция fprintf() имеет вид: fprintf(поток_вывода, “формат”, перем_1, перем_2,…); Работает она аналогично printf() и выводит данные в указанный поток вывода. Для форматированного ввода используются функции scanf() и fscanf(). Для преобразования текстовой строки можно использовать sscanf(). Она работает аналогично fscanf(), но данные берет из сроки, а не из файла.

Для работы со стандартными потоками в режиме форматного ввода-вывода определены две функции:

printf( ) - форматный вывод;

scanf( ) - форматный ввод.

Прототип функции printf() имеет вид:

int printf(const char *format,...);

При обращении к функции printf() возможны две формы задания первого параметра:

int printf ( *форматная строка, список_аргументов);

int printf (указателъ_на_форматную_строку,. список_аргументов);

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

Возвращаемое значение функции printf() - число напечатанных символов; а в случае ошибки - отрицательное число.

Форматная_строка ограничена двойными кавычками и может включать произвольный текст, управляющие символы и спецификации преобразования данных. Текст и управляющие символы из форматной строки просто копируются в выходной поток. Форматная строка обычно размещается в списке фактических параметров функции, что соответствует первому варианту вызова функции printf(). Второй вариант предполагает, что первый фактический параметр - это указатель типа char *, a сама форматная строка определена в программе как обычная строковая константа или переменная.

В список аргументов функции printf() включают выражения, значения которых должны быть выведены из программы. Частные случаи этих выражений - переменные и константы. Количество аргументов и их типы должны соответствовать последовательности спецификаций преобразования в форматной строке. Для каждого аргумента должна быть указана точно одна спецификация преобразования.

Если аргументов недостаточно для данной форматной строки, то результат зависит от реализации (от операционной системы и от системы программирования). Если аргументов больше, чем указано в форматной строке, "лишние" аргументы игнорируются. Гарантируется, что при любом количестве параметров и любом их типе после выполнения функций printf() дальнейшее выполнение программы будет корректным.

56.

Форматированный ввод и вывод. Богатый ассортимент средств управления форматом позволяет легко создавать таблицы, графики или отчеты. Функциями, выполняющими этот вывод являются printf() - для стандартного потока вывода stdout, и fprintf() – для любого потока. Функция fprintf() имеет вид: fprintf(поток_вывода, “формат”, перем_1, перем_2,…); Работает она аналогично printf() и выводит данные в указанный поток вывода. Для форматированного ввода используются функции scanf() и fscanf(). Для преобразования текстовой строки можно использовать sscanf(). Она работает аналогично fscanf(), но данные берет из сроки, а не из файла. Потоки cin, cout, cerr. В языке С++ имеется другая библиотека ввода/вывода, определяемая заголовочным файлом iostream.h. Ввод/вывод в ней определяется набором специальных классов. Аналогами потоков stdin, stdout и stderr являются cin, cout и cerr. Они открываются автоматически при запуске программы. Операции выделения >> и вставки <<. Для ввода/вывода с помощью указанных потоков используются специальным образом определенные операции “занести в поток” и “получить из потока”, << и <<. Операция >> выделяет данные из входного потока и помещает в указанные переменные, а операция << помещает значения указанных переменных в поток.Приведем пример использования потока stdin и потока cin: scanf(“%d%lf%c”,&ivalue,&dvalue,&cvalue); cin>>ivalue>>dvalue>>cvalue; Аналогично для вывода: printf(“Integer:%d double: %lf”,ivalue,dvalue); cout<<”Integer:”<<ivalue<<” double:”<<dvalue;

57.

Важное отличие языка СИ от других языков (PL1, FORTRAN, и др.) является отсутствие принципа умолчания, что приводит к необходимости объявления всех переменных используемых в программе явно вместе с указанием соответствующих им типов.

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

[спецафикатор-класа-памяти] спецификатор-типа

описатель [=инициатор] [,описатель [= инициатор] ]...

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

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

Инициатор - задает начальное значение или список начальных значений, которые (которое) присваивается переменной при объявлении.

Спецификатор класса памяти - определяется одним из четырех ключевых слов языка СИ: auto, extern, register, static, и указывает,каким образом будет распределяться память под объявляемую переменную, с одной стороны, а с другой, область видимости этой переменной, т.е., из каких частей программы можно к ней обратиться.

1.2.1 Категории типов данных

Ключевые слова для определения основных типов данных

Целые типы : Плавающие типы:

char float

int double

short long double

long

signed

unsigned

Переменная любого типа может быть объявлена как немодифицируемая. Это достигается добавлением ключевого слова const к спецификатору-типа. Объекты с типом const представляют собой данные используемые только для чтения, т.е. этой переменной не может быть присвоено новое значение. Отметим, что если после слова const отсутствует спецификатор-типа, то подразумевается спецификатор типа int. Если ключевое слово const стоит перед объявлением составных типов (массив, структура, смесь, перечисление), то это приводит к тому, что каждый элемент также должен являться немодифицируемым, т.е. значение ему может быть присвоено только один раз.

Примеры:

const double A=2.128E-2;

const B=286; (подразумевается const int B=286)

Целый тип данных

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

Таблица 6

Тип Размер памяти в байтах Диапазон значений

char 1 от -128 до 127

int Для IBM XT,AT,SX,DX 2

short 2 от -32768 до 32767

long 4 от -2 147 483 648 до 2 147 483 647

unsigned shar 1 oт 0 до 255

unsigned int Для IBM XT,AT,SX,DX 2

unsigned short 2 от 0 до 65535

unsigned long 4 от 0 до 4 294 967 295

Отметим, что ключевые слова signed и unsigned необязательны. Они указывают, как интерпретируется нулевой бит объявляемой переменной, т.е., если указано ключевое слово unsigned, то нулевой бит интерпретируется как часть числа, в противном случае нулевой бит интерпретируется как знаковый. В случае отсутствия ключевого слова unsigned целая переменная считается знаковой. В том случае, если спецификатор типа состоит из ключевого типа signed или unsigned и далее следует идентификатор переменной, то она будет рассматриваться как переменная типа int. Например:

unsigned int n;

unsigned int b;

int c; (подразумевается signed int c );

unsigned d; (подразумевается unsigned int d );

signed f; (подразумевается signed int f )

Отметим, что модификатор-типа char используется для представления символа (из массива представление символов) или для объявления строковых литералов. Значением объекта типа char является код (размером 1 байт), соответствующий представляемому символу. Для представления символов русского алфавита, модификатор типа идентификатора данных имеет вид unsigned char, так как коды русских букв превышают величину 127.

Следует сделать следующее замечание: в языке СИ не определено представление в памяти и диапазон значений для идентификаторов с модификаторами-типа int и unsigned int. Размер памяти для переменной с модификатором типа signed int определяется длиной машинного слова, которое имеет различный размер на разных машинах. Так, на 16-ти разрядных машинах размер слова равен 2-м байтам, на 32-х разрядных машинах соответственно 4-м байтам, т.е. тип int эквивалентен типам short int, или long int в зависимости от архитектуры используемой ПЭВМ. Таким образом, одна и та же программа может правильно работать на одном компьютере и неправильно на другом. Для определения длины памяти занимаемой переменной можно использовать операцию sizeof языка СИ, возвращающую значение длины указанного модификатора-типа.

Например:

a = sizeof(int);

b = sizeof(long int);

c = sizeof(unsigned long);

d = sizeof(short);

Отметим также, что восьмеричные и шестнадцатеричные константы также могут иметь модификатор unsigned. Это достигается указанием префикса u или U после константы, константа без этого префикса считается знаковой.

Например:

0xA8C (int signed );

01786l (long signed );

0xF7u (int unsigned );

Данные плавающего типа

Для переменных, представляющих число с плавающей точкой используются следующие модификаторы-типа : float, double, long double (в некоторых реализациях языка long double СИ отсутствует).

Величина с модификатором-типа float занимает 4 байта. Из них 1 байт отводится для знака, 8 бит для избыточной экспоненты и 23 бита для мантиссы. Отметим, что старший бит мантиссы всегда равен 1, поэтому он не заполняется, в связи с этим диапазон значений переменной с плавающей точкой приблизительно равен от 3.14E-38 до 3.14E+38.

Величина типа double занимает 8 байт в памяти. Ее формат аналогичен формату float. Биты памяти распределяются следующим образом: 1 бит для знака, 11 бит для экспоненты и 52 бита для мантиссы. С учетом опущенного старшего бита мантиссы диапазон значений равен от 1.7E-308 до 1.7E+308.

Примеры:

float f, a, b;

double x,y;

Указатели

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

спецификатор-типа [ модификатор ] * описатель .

Спецификатор-типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип 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 будет указывать на одну и ту же область памяти. Содержание же

этой области может быть изменено. */

1.2.5. Переменные перечислимого типа

Переменная, которая может принимать значение из некоторого списка значений, называется переменной перечислимого типа или перечислением.

Объявление перечисления начинается с ключевого слова enum и имеет два формата представления.

Формат 1. enum [имя-тега-перечисления] {список-перечисления} описатель[,описатель...];

Формат 2. enum имя-тега-перечисления описатель [,описатель..];

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

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

Переменная типа enum могут использоваться в индексных выражениях и как операнды в арифметических операциях и в операциях отношения.

В первом формате 1 имена и значения перечисления задаются в списке перечислений. Необязательное имя-тега-перечисления, это идентификатор, который именует тег перечисления, определенный списком перечисления. Описатель именует переменную перечисления. В объявлении может быть задана более чем одна переменная типа перечисления.

Список-перечисления содержит одну или несколько конструкций вида:

идентификатор [= константное выражение]

Каждый идентификатор именует элемент перечисления. Все идентификаторы в списке enum должны быть уникальными. В случае отсутствия константного выражения первому идентификатору соответствует значение 0, следующему идентификатору - значение 1 и т.д. Имя константы перечисления эквивалентно ее значению.

Идентификатор, связанный с константным выражением, принимает значение, задаваемое этим константным выражением. Константное выражение должно иметь тип int и может быть как положительным, так и отрицательным. Следующему идентификатору в списке присваивается значение, равное константному выражению плюс 1, если этот идентификатор не имеет своего константного выражения. Использование элементов перечисления должно подчиняться следующим правилам:

1. Переменная может содержать повторяющиеся значения.

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

3. Имена типов перечислений должны быть отличны от других имен типов перечислений, структур и смесей в этой же области видимости.

4. Значение может следовать за последним элементом списка перечисления.

Пример:

enum week { SUB = 0, /* 0 */

VOS = 0, /* 0 */

POND, /* 1 */

VTOR, /* 2 */

SRED, /* 3 */

HETV, /* 4 */

PJAT /* 5 */

} rab_ned ;

В данном примере объявлен перечислимый тег week, с соответствующим множеством значений, и объявлена переменная rab_ned имеющая тип week.

Во втором формате используется имя тега перечисления для ссылки на тип перечисления, определяемый где-то в другом месте. Имя тега перечисления должно относится к уже определенному тегу перечисления в пределах текущей области видимости. Так как тег перечисления объявлен где-то в другом месте, список перечисления не представлен в объявлении.

Пример:

enum week rab1;

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

Инициализация данных

При объявлении переменной ей можно присвоить начальное значение, присоединяя инициатор к описателю. Инициатор начинается со знака "=" и имеет следующие формы.

Формат 1: = инициатор;

Формат 2: = { список - инициаторов };

Формат 1 используется при инициализации переменных основных типов и указателей, а формат 2 - при инициализации составных объектов.

Примеры:

char tol = 'N';

Переменная tol инициализируется символом 'N'.

const long megabute = (1024 * 1024);

Немодифицируемая переменная megabute инициализируется константным выражением после чего она не может быть изменена.

static int b[2][2] = {1,2,3,4};

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

static int b[2][2] = { { 1,2 }, { 3,4 } };

При инициализации массива можно опустить одну или несколько размерностей

static int b[3[] = { { 1,2 }, { 3,4 } };

Если при инициализации указано меньше значений для строк, то оставшиеся элементы инициализируются 0, т.е. при описании

static int b[2][2] = { { 1,2 }, { 3 } };

элементы первой строки получат значения 1 и 2, а второй 3 и 0.

При инициализации составных объектов, нужно внимательно следить за использованием скобок и списков инициализаторов.

Примеры:

struct complex { double real;

double imag; } comp [2][3] =

{ { {1,1}, {2,3}, {4,5} },

{ {6,7}, {8,9}, {10,11} } };

В данном примере инициализируется массив структур comp из двух строк и трех столбцов, где каждая структура состоит из двух элементов real и imag.

struct complex comp2 [2][3] =

{ {1,1},{2,3},{4,5}, {6,7},{8,9},{10,11} };

В этом примере компилятор интерпретирует рассматриваемые фигурные скобки следующим образом:

- первая левая фигурная скобка - начало составного инициатора для массива comp2;

- вторая левая фигурная скобка - начало инициализации первой строки массива comp2[0]. Значения 1,1 присваиваются двум элементам первой структуры;

- первая правая скобка (после 1) указывает компилятору, что список инициаторов для строки массива окончен, и элементы оставшихся структур в строке comp[0] автоматически инициализируются нулем;

- аналогично список {2,3} инициализирует первую структуру в строке comp[1], а оставшиеся структуры массива обращаются в нули;

- на следующий список инициализаторов {4,5} компилятор будет сообщать о возможной ошибке так как строка 3 в массиве comp2 отсутствует.

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

Пример:

union tab { unsigned char name[10];

int tab1;

} pers = {'A','H','T','O','H'};

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

Инициализацию массива символов можно выполнить путем использования строкового литерала.

char stroka[ ] = "привет";

Инициализируется массив символов из 7 элементов, последним элементом (седьмым) будет символ '\0', которым завершаются все строковые литералы.

В том случае, если задается размер массива, а строковый литерал длиннее, чем размер массива, то лишние символы отбрасываются.

Следующее объявление инициализирует переменную stroka как массив, состоящий из семи элементов.

char stroka[5] = "привет";

В переменную stroka попадают первые пять элементов литерала, а символы 'Т' и '\0' отбрасываются.

Если строка короче, чем размер массива, то оставшиеся элементы массива заполняются нулями.

Отметим, что инициализация переменной типа tab может иметь следующий вид:

union tab pers1 = "Антон";

и, таким образом, в символьный массив попадут символы:

'А','Н','Т','О','Н','\0',

а остальные элементы будут инициализированы нулем.

58.

Переадресация ввода-вывода: понятия фильтра и конвейера

Потоки cin, cout, cerr.В языке С++ имеется другая библиотека ввода/вывода, определяемая заголовочным файлом iostream.h. Ввод/вывод в ней определяется набором специальных классов. Аналогами потоков stdin, stdout и stderr являются cin, cout и cerr. Они открываются автоматически при запуске программы. Операции выделения >> и вставки <<.Для ввода/вывода с помощью указанных потоков используются специальным образом определенные операции “занести в поток” и “получить из потока”, << и <<. Операция >> выделяет данные из входного потока и помещает в указанные переменные, а операция << помещает значения указанных переменных в поток.Приведем пример использования потока stdin и потока cin:

scanf(“%d%lf%c”,&ivalue,&dvalue,&cvalue); cin>>ivalue>>dvalue>>cvalue;

Аналогично для вывода:printf(“Integer:%d double: %lf”,ivalue,dvalue); cout<<”Integer:”<<ivalue<<” double:”<<dvalue;

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

sort < mylist | more.

В этом примере данные из файла mylist сортируются программой sort и постранично выводятся на экран программой more.

В UNIX тоже возможна переадресация потока с одновременным выводом данных на экран, и даже переадресация на два разных устройства. Для переадресации стандартного вывода в файл с одновременной выдачей информации на экран используется команда tee. Например, команда cat в UNIX позволяет просматривать файл. Следующая конструкция:

cat first | tee second копирует файл first в файл second, одновременно показывая его на экране.

59.

Структуры

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

struct { список определений }

В структуре обязательно должен быть указан хотя бы один компонент. Определение структур имеет следующий вид:

тип-данных описатель;

где тип-данных указывает тип структуры для объектов, определяемых в описателях. В простейшей форме описатели представляют собой идентификаторы или массивы.

Пример:

struct { double x,y; } s1, s2, sm[9];

struct { int year;

char moth, day; } date1, date2;

Переменные s1, s2 определяются как структуры, каждая из которых состоит из двух компонент х и у. Переменная sm определяется как массив из девяти структур. Каждая из двух переменных date1, date2 состоит из трех компонентов year, moth, day. >p>Существует и другой способ ассоциирования имени с типом структуры, он основан на использовании тега структуры. Тег структуры аналогичен тегу перечислимого типа. Тег структуры определяется следующим образом:

struct тег { список описаний; };

где тег является идентификатором.

В приведенном ниже примере идентификатор student описывается как тег структуры:

struct student { char name[25];

int id, age;

char prp; };

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

struct тег список-идентификаторов;

Пример:

struct studeut st1,st2;

Использование тегов структуры необходимо для описания рекурсивных структур. Ниже рассматривается использование рекурсивных тегов структуры.

struct node { int data;

struct node * next; } st1_node;

Тег структуры node действительно является рекурсивным, так как он используется в своем собственном описании, т.е. в формализации указателя next. Структуры не могут быть прямо рекурсивными, т.е. структура node не может содержать компоненту, являющуюся структурой node, но любая структура может иметь компоненту, являющуюся указателем на свой тип, как и сделано в приведенном примере.

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

st1.name="Иванов";

st2.id=st1.id;

st1_node.data=st1.age;

60.

61.

Для доступа к файлу применяется тип данных FILE. Это структурный тип, имя которого задано с помощью оператора typedef в стандартном заголовочном файле <stdio.h>. Программисту не нужно знать, как устроена структура типа файл: ее устройство может быть системно зависимым, поэтому в целях переносимости программ обращаться явно к полям структуры FILE запрещено. Тип данных "указатель на структуру FILE” используется в программах как черный ящик: функция открытия файла возвращает этот указатель в случае успеха, и в дальнейшем все файловые функции применяют его для доступа к файлу.

Прототип функции открытия файла выглядит следующим образом:

FILE *fopen(const char *path, const char *mode);

Здесь path - путь к файлу (например, имя файла или абсолютный путь к файлу), mode - режим открытия файла. Строка mode может содержать несколько букв. Буква "r" (read) означает, что файл открывается для чтения (файл должен существовать). Буква "w" (write) означает запись в файл, при этом старое содержимое файла теряется, а в случае отсутствия файла он создается. Буква "a" (от слова append) означает запись в конец существующего файла или создание нового файла, если файл не существует.

В некоторых операционных системах имеются различия в работе с текстовыми и бинарными файлами (к таким системам относятся MS DOS и MS Windows; в системе Unix различий между текстовыми и бинарными файлами нет). В таких системах при открытии бинарного файла к строке mode следует добавлять букву "b" (от слова binary), а при открытии текстового файла -- букву "t" (от слова text). Кроме того, при открытии можно разрешить выполнять как операции чтения, так и записи; для этого используется символ + (плюс). Порядок букв в строке mode следующий: сначала идет одна из букв "r", "w", "a", затем в произвольном порядке могут идти символы "b", "t", "+". Буквы "b" и "t" можно использовать, даже если в операционной системе нет различий между бинарными и текстовыми файлами, в этом случае они просто игнорируются.

Режим - определяет, каким образом файл будет открыт. Ниже в таблице показаны допустимые значения режимов.

Режим

Что обозначает данный режим

r

Открыть текстовый файл для чтения

w

Создать текстовый файл для записи

a

Добавить в конец текстового файла

wb

Создать двоичный файл для записи

rb

Открыть двоичный файл для чтения

ab

Добавить в конец двоичного файла

r+

Открыть текстовый файл для чтения/записи

w+

Создать текстовый файл для чтения/записи

a+

Добавить в конец текстового файла или создать текстовый файл для чтения/записи

r+b

Открыть двоичный файл для чтения/записи

w+b

Создать двоичный файл для чтения/записи

a+b

Добавить в конец двоичного файла или создать двоичный файл для чтения/записи

# include <stdio.h>

# include <stdlib.h>

/**/

Main()

{

FILE *fp;

Clrscr();

if ((fp = fopen("test", "w")==NUL)

{

cprintf("Ошибка при открытии файла.\n\r")"

gench();

}

Crpintf("Открытие файла прошло нормально.\n\r");

gench();

}

62.

при помощи fgets

Функция fgets применяется для чтения строки из потока. Считывание происходит до тех пор пока не будет достигнут конец строки (\n) или длина строки, в которую происходит считывание. Предположим, у нас есть файл some_file.txt с текстом

палиндромы

А в Енисее - синева.

А лама мала.

А лис, он умён - крыса сыр к нему носила. (И. Бабицкий)

#include <stdio.h>

#include <string.h>

int main (int argc, char* argv[]) /* argc хранит количество параметров, а argv[] указатели на эти параметры.

Например если мы запустим выполняемый файл "fgets_example param1 param2" то argc будет равно 2, а argv[] = {"fgets_example", "param1", "param2"}*/

{

FILE *file;

char *fname = "some_file.txt";

char result_sting[20]; //Строка в 20 символов

file = fopen(fname,"r");

if(file == 0)

{

printf("не могу открыть файл '%s'",fname);

return 0;

}

int i=0;

char *real_tail;

while(fgets(result_sting,sizeof(result_sting),file))

{

real_tail="";

printf("Строка %d:Длина строки - %d:",i++,strlen(result_sting));

if(result_sting[strlen(result_sting)-1] == '\n')//проверяем является ли последний элемент в строке символом ее окончания

{

real_tail="\\n";

result_sting[strlen(result_sting)-1]='\0';

};// эта часть кода добавленна лишь для отображения символа конца строки в консоль без перевода на новую строку

printf("%s%s\n",result_sting,real_tail);

}

fclose(file);

return 0;

}

в результате выполнения мы получим

Строка 0:Длина строки - 11:палиндромы\n

Строка 1:Длина строки - 19: А в Енисее - си

Строка 2:Длина строки - 6:нева.\n

Строка 3:Длина строки - 17: А лама мала.\n

Строка 4:Длина строки - 19: А лис, он умён

Строка 5:Длина строки - 19:- крыса сыр к нему

Строка 6:Длина строки - 19:носила. (И. Бабицки

Строка 7:Длина строки - 2:й)

Запись в поток при помощи fputc

Функция fputc применяется для записи символа в поток.

int fputc(int c, FILE *fp);

Параметр c "тихо" конвертируется в unsigned char перед выводом. Если прошло успешно, то fputc возвращает записанный символ. Если ошибка, то fputc возвращает EOF.

Стандартный макрос putc также определен в <stdio.h>, работая в общем случае аналогично fputc, за исключением того момента, что будучи макросом, он может обрабатывать свои аргументы более одного раза.

Стандартная функция putchar, также определенная в <stdio.h>, принимает только первый аргумент, и является эквивалентной putc(c, stdout), где c является упомянутым аргументом.

[править]

Пример использования

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

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

char buffer[5] = {0}; /* инициализируем нулями */

int i, rc;

FILE *fp = fopen("мойфайл", "rb");

if (fp == NULL) {

perror("Ошибка при открытии \"мойфайл\"");

return EXIT_FAILURE;

}

for (i = 0; (rc = getc(fp)) != EOF && i < 5; buffer[i++] = rc)

;

fclose(fp);

if (i == 5) {

puts("Прочитанные байты...");

printf("%x %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]);

} else

fputs("Ошибка чтения файла.\n", stderr);

return EXIT_SUCCESS;

}

63.1

Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си <stdio.h>.

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

Потоковая модель файлового ввода-вывода была популяризирована во многом благодаря операционной системе Unix, написанной на языке Си. Большая функциональность современных операционных систем унаследовала потоки от Unix, а многие языки семейства языков программирования Си унаследовали интерфейс файлового ввода-вывода языка Си с небольшими отличиями (например, PHP). Стандартная библиотека C++ отражает потоковую концепцию в своем синтаксисе (смотри iostream).

поиск в файле, добавление и удаление данных.

Поиск в файле

while(!feof(pFile))

{

fscanf(pFile,"%s", &st);

printf("%s",st);

m=strlen(st);

}

int t=0;

printf("\n");

printf("введите кол-во символов ");

scanf("%i",&t);

if(t>0)

{

ch=fgetc(pFile);

// while((ch=fgetc(pFile))!= EOF)

//fgetc(pFile,"%c", &st);

for(int i=1;i<=t;i++)

{

printf("%c",ch);

}

}

64.

Поиск файла

char *searchpath(char *file);

Вначале поиск осуществляется в текущем каталоге текущего диска. Если файл не найден, функция перебирает пути из переменной окружения PATH.

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

65.

Управление динамической памятью: функции распределения памяти

Для глобальных переменных отводится фиксированное место в памяти на все время работы программы. Локальные переменные хранятся в стеке. Между ними находится область памяти для динамического распределения.

Функции malloc( ) и free( ) используются для динамического распределения свободной памяти. Функция malloc( ) выделяет память, функция free( ) освобождает ее. Прототипы этих функций хранятся в заголовочном файле stdlib.h и имеют вид:

void *malloc(size_t size);

void *free(void *p);

Функция malloc( ) возвращает указатель типа void; для правильного использования значение функции надо преобразовать к указателю на соответствующий тип. При успешном выполнении функция возвращает указатель на первый байт свободной памяти размера size. Если достаточного количества памяти нет, возвращается значение 0. Чтобы определить количество байтов, необходимых для переменной, используют операцию sizeof( ).

Пример использования этих функций:

#include <stdio.h>

#include <stdlib.h>

void main(void)

{

int *p, i;

p = (int *) malloc(100 * sizeof(int)); /* Выделение памяти для 100

целых чисел */

if (!p)

{

printf("Недостаточно памяти\n");

exit(1);

}

for (i = 0; i < 100; ++i) *(p+i) = i; /* Использование памяти */

for (i = 0; i < 100; ++i) printf("%d", *(p++) );

free(p); /* Освобождение памяти */

}

Перед использованием указателя, возвращаемого malloc( ), необходимо убедиться, что памяти достаточно (указатель не нулевой).

65.

Оператор C++ new позволяет вашим программам распределять память во время выполнения. Для использования оператора new вам необходимо указать количество байтов памяти, которое требуется программе. Предположим, например, что вашей программе необходим 50-байтный массив. Используя оператор new, вы можете заказать эту память, как показано ниже: char *buffer = new char[50]; Говоря кратко, если оператор new успешно выделяет память, он возвращает указатель на начало области этой памяти. В данном случае, поскольку программа распределяет память для хранения массива символов, она присваивает возвращаемый указатель переменной, определенной как указатель на тип char. Если оператор new не может выделить запрашиваемый вами объем памяти, он возвратит NULL-указатель, который содержит значение 0. Каждый раз, когда ваши программы динамически распределяют память с использованием оператора new, они должны проверять возвращаемое оператором new значение, чтобы определить, не равно ли оно NULL. Если вашей программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete вы просто указываете этому оператору указатель на данную область памяти, как показано ниже: delete pointer;

66.

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

В языках программирования (Pascal, C, др.) существует и другой способ выделения памяти под данные, который называется динамическим. В этом случае память под величины отводится во время выполнения программы. Такие величины будем называть динамическими. Раздел оперативной памяти, распределяемый статически, называется статической памятью; динамически распределяемый раздел памяти называется динамической памятью (динамически распределяемой памятью).

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

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

Указатель содержит адрес поля в динамической памяти, хранящего величину определенного типа. Сам указатель располагается 1000 в статической памяти.

Адрес величины — это номер первого байта поля памяти, в котором располагается величина. Размер поля однозначно определяется типом.

Далее будем более подробно обсуждать указатели и действия с ними в языке Pascal, примеры будем приводить на Pascal и C.

Величина ссылочного типа (указатель) описывается в разделе описания переменных следующим образом:

Var <идентификатор> : ^<имя типа>;

Вот примеры описания указателей:

Type Mas1 = Array[1..100] Of Integer;

Var P1 : ^Integer;

P2 : ^String;

Pm : ^Mas1;

Здесь P1 — указатель на динамическую величину целого типа; P2 — указатель на динамическую величину строкового типа; Pm — указатель на динамический массив, тип которого задан в разделе Type.

Сами динамические величины не требуют описания в программе, поскольку во время компиляции память под них не выделяется. Во время компиляции память выделяется только под статические величины. Указатели — это статические величины, поэтому они требуют описания.

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

NEW(<указатель>);

Считается, что после выполнения этого оператора создана динамическая величина, имя которой имеет следующий вид:

<имя динамической величины> := <указатель>^

Пусть в программе, в которой имеется приведенное выше описание, присутствуют следующие операторы:

NEW(P1); NEW(P2); NEW(Pm);

После их выполнения в динамической памяти оказывается выделенным место под три величины (две скалярные и один массив), которые имеют идентификаторы:

P1^, P2^, Pm^

Например, обозначение P1^ можно расшифровать так: динамическая переменная, на которую ссылается указатель P1.

Дальнейшая работа с динамическими переменными происходит точно так же, как со статическими переменными соответствующих типов. Им можно присваивать значения, их можно использовать в качестве операндов в выражениях, параметров подпрограмм и пр. Например, если переменной P1^ нужно присвоить число 25, переменной P2^ присвоить значение символа "Write", а массив Pm^ заполнить по порядку целыми числами от 1 до 100, то это делается так:

P1^ := 25;

P2^ := 'Write';

For I := 1 To 100 Do Pm^[I] := I;

Кроме процедуры NEW значение указателя может определяться оператором присваивания:

<указатель> := <ссылочное выражение>;

В качестве ссылочного выражения можно использовать

указатель;

ссылочную функцию (т.е. функцию, значением которой является указатель);

константу Nil.

Nil — это зарезервированная константа, обозначающая пустую ссылку, т.е. ссылку, которая ни на что не указывает. При присваивании базовые типы указателя и ссылочного выражения должны быть одинаковы. Константу Nil можно присваивать указателю с любым базовым типом.

До присваивания значения ссылочной переменной (с помощью оператора присваивания или процедуры NEW) она является неопределенной.

Ввод и вывод указателей не допускается.

Рассмотрим пример. Пусть в программе описаны следующие указатели:

Var D, P : ^Integer;

K : ^Boolean;

Тогда допустимыми являются операторы присваивания

D := P; K := Nil;

поскольку соблюдается принцип соответствия типов. Оператор K := D ошибочен, т.к. базовые типы у правой и левой части разные.

Если динамическая величина теряет свой указатель, то она становится "мусором". В программировании под этим словом понимают информацию, которая занимает память, но уже не нужна.

Представьте себе, что в программе, в которой присутствуют описанные выше указатели, в разделе операторов записано следующее:

NEW(D); NEW(P);

{Выделено место в динамической памяти под две целые переменные. Указатели полу

1000

чили соответствующие значения}

D^ := 3; P^ := 5;

{Динамическим переменным присвоены значения}

P := D;

{Указатели P и D стали ссылаться на одну и ту же величину, равную 3}

WriteLn(P^, D^); {Дважды напечатается число 3}

Таким образом, динамическая величина, равная 5, потеряла свой указатель и стала недоступной. Однако место в памяти она занимает. Это и есть пример возникновения "мусора". На схеме показано, что произошло в результате выполнения оператора P := D.

В Паскале имеется стандартная процедура, позволяющая освобождать память от данных, потребность в которых отпала. Ее формат:

DISPOSE(<указатель>);

Например, если динамическая переменная P^ больше не нужна, то оператор

DISPOSE(P)

удалит ее из памяти. После этого значение указателя P становится неопределенным. Особенно существенным становится эффект экономии памяти при удалении больших массивов.

Если указатель ссылается только на следующее звено списка (как показано на рисунке и в объявленной выше структуре), то такой список называют однонаправленным, если на следующее и предыдущее звенья — двунаправленным списком. Если указатель в последнем звене установлен не в Nil, а ссылается на заглавное звено списка, то такой список называется кольцевым. Кольцевыми могут быть и однонаправленные, и двунаправленные списки.

67.

Шаблоны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).

В C++ возможно создание шаблонов функций и классов.

Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем). Например, нам нужен какой-то класс:

class SomeClass{

int SomeValue;

int SomeArray[20];

...

}

Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray и вещественный тип SomeValue и элементов SomeArray. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template и укажем параметры в угловых скобках. В нашем примере:

template < int ArrayLength, typename SomeValueType > class SomeClass{

SomeValueType SomeValue;

SomeValueType SomeArray[ ArrayLength ];

...

}

Тогда для первой модели пишем:

SomeClass < 20, int > SomeVariable;

для второй:

SomeClass < 30, double > SomeVariable2;

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

Шаблоны функций

[править]

Синтаксис описания шаблона

Шаблон функции начинается с ключевого слова template, за которым в угловых скобках следует список параметров. Затем следует объявление функции:

template< typename T >

void sort( T array[], int size ); // прототип: шаблон sort объявлен, но не определён

template< typename T >

void sort( T array[], int size ) // объявление и определение

{

T t;

for (int i = size - 1; i > 0; i--)

for (int j = i; j > 0; j--)

if (array[j] < array[j-1])

{

t = array[j];

array[j] = array[j-1];

array[j-1] = t;

}

}

template< int BufferSize > // целочисленный параметр

char* read()

{

char *Buffer = new char[ BufferSize ];

/* считывание данных */

return Buffer;

}

Ключевое слово typename появилось сравнительно недавно, поэтому стандарт[1] допускает использование class вместо typename:

template< class T >

Вместо T допустим любой другой идентификатор.

68.

Директива #include

Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:

#include "имя файла"

#include <имя файла>

Имя файла должно соответствовать соглашениям операционной системы и может состоять либо только из имени файла, либо из имени файла с предшествующим ему маршрутом. Если имя файла указано в кавычках, то поиск файла осуществляется в соответствии с заданным маршрутом, а при его отсутствии в текущем каталоге. Если имя файла задано в угловых скобках, то поиск файла производится в стандартных директориях операционной системы, задаваемых командой PATH.

Директива #include может быть вложенной, т.е. во включаемом файле тоже может содержаться директива #include, которая замещается после включения файла, содержащего эту директиву.

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

69.

Директива #define

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

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

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

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

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

Пример:

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.

Во второй синтаксической форме в директиве #define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз.

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

Пример:

#define MAX(x,y) ((x)>(y))?(x):(y)

Эта директива заменит фрагмент

t=MAX(i,s[i]);

на фрагмент

t=((i)>(s[i])?(i):(s[i]);

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

Например, при наличии скобок фрагмент

t=MAX(i&j,s[i]||j);

будет заменен на фрагмент

t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);

а при отсутствии скобок - на фрагмент

t=(i&j>s[i]||j)?i&j:s[i]||j;

в котором условное выражение вычисляется в совершенно другом порядке.

70.

Директивы препроцессора представляют собой инструкции, записанные в тексте программы на СИ, и выполняемые до трансляции программы. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т.п. Все директивы препроцессора начинаются со знака #. После директив препроцессора точка с запятой не ставятся.Директива #define служит для замены часто использующихся констант, ключевых слов, операторов или выражений некоторыми идентификаторами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными константами. Идентификаторы, заменяющие фрагменты программ, называют макроопределениями, причем макроопределения могут иметь аргументы.Директива #define имеет две синтаксические формы:

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

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

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

Пример:

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.Во второй синтаксической форме в директиве #define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз.При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров. Пример:

#define MAX(x,y) ((x)>(y))?(x):(y)

Эта директива заменит фрагмен t=MAX(i,s[i]); на фрагмент t=((i)>(s[i])?(i):(s[i]);

Как и в предыдущем примере, круглые скобки, в которые заключены формальные параметры макроопределения, позволяют избежать ошибок связанных с неправильным порядком выполнения операций, если фактические аргументы являются выражениями. Например, при наличии скобок фрагмент t=MAX(i&j,s[i]||j); будет заменен на фрагмент t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);

а при отсутствии скобок - на фрагмент t=(i&j>s[i]||j)?i&j:s[i]||j;

в котором условное выражение вычисляется в совершенно другом порядке.

71.

Компиляция программы на Си - многопроходная. На ее нулевой фазе для обработки исходного файла используется препроцессор. Компилятор вызывает препроцессор автоматически, однако последний может быть вызван и независимо. Директивы препроцессора - это инструкции, записанные непосредственно в исходном тексте программы.Директивы нужны для того, чтобы облегчить написание и модификацию программ, а также сделать их более независимыми от аппаратных платформ и операционных систем. Директивы препроцессора позволяют заменять лексемы в тексте программы, вставить в файл содержимое других файлов, запрещать компиляцию части файла или делать ее зависимой от некоторых условий и т. д.Можно выделить следующие основные виды директив:

· определение макрокоманд;

· вставка файлов;

· условная компиляция программы.

Директивы препроцессор Си перечислены в табл. 10.1.

Таблица 10.1. Директивы препроцессора Си

#define #else #if #ifndef #line #elif #endif #ifdef #include #undef

При указании любой директивы первым значащим символом в строке должен быть символ "#".Директивы могут быть записаны в любом месте исходного файла. Их действие распространяется от точки программы, в которой они записаны до конца исходного файла. Часть директив могут содержать аргументы. Использование директив #ifdef и #ifndef эквивалентно применению директивы #if совместно с операцией defined(идентификатор). Эти директивы поддерживаются для совместимости с предыдущими версиями компиляторов Си. Синтаксис директив следующий:

#ifdef идентификатор

#ifndef идентификатор

При обработке #ifdef препроцессор проверяет, определен ли в данный момент идентификатор директивой #define. Если это так, условие считается истинным, иначе ложным.Директива #ifndef противоположна по действию: если идентификатор не был определен директивой #define или его определение отменено директивой #undef, то условие считается истинным. В противном случае условие ложно.Аналогично #if, за #ifdef и #ifndef может следовать набор директив #elif и/или директива #else. Набор должен быть завершен директивой #endif.

72.

Шаблоны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например типам данных, размерам буферов, значениям по умолчанию).В C++ возможно создание шаблонов функций и классов.Шаблоны позволяют создавать параметризованные классы и функции. Например, нам нужен какой-то класс:

class SomeClass{

int SomeValue;

int SomeArray[20];

...

}

Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray и вещественный тип SomeValue. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template и укажем параметры в угловых скобках. В нашем примере:

template < int ArrayLength, typename SomeValueType > class SomeClass{

SomeValueType SomeValue;

SomeValueType SomeArray[ ArrayLength ];

...

}

Тогда для первой модели пишем:

SomeClass < 20, int > SomeVariable;

для второй:

SomeClass < 30, double > SomeVariable2;

Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса. struct aa { int b; int c; }; aa тут яваляется шаблоном, тоже самое для enum и unionПростейшим примером служит определение минимума из двух величин.

Если a меньше b то вернуть а, иначе - вернуть b.

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

template< typename T >

T min( T a, T b )

{

return a < b ? a : b;

}

Для вызова этой функции можно просто использовать её имя:

min( 1, 2 );

min( 'a', 'b' );

min( string( "abc" ), string( "cde" ) );

73.

Шаблоны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например типам данных, размерам буферов, значениям по умолчанию).В C++ возможно создание шаблонов функций и классов.Шаблоны позволяют создавать параметризованные классы и функции. Например, нам нужен какой-то класс: class SomeClass{ int SomeValue; int SomeArray[20]; ... } template <int size> struct aa{ char name[size]; };

74.

75.

Абстрактные структуры данных предназначены для удобного хранения и доступа к информации. Они предоставляют удобный интерфейс для типичных операций с хранимыми объектами, скрывая детали реализации от пользователя. Конечно, это весьма удобно и позволяет добиться большей модульности программы. Абстрактные структуры данных иногда делят на две части: интерфейс, набор операций над объектами, который называют АТД(абстрактный тип данных) и реализацию.

Стек - такой последовательный список с переменной длиной, включение и исключение элементов из которого выполняются только с одной стороны списка, называемого вершиной стека. Применяются и другие названия стека - магазин и очередь, функционирующая по принципу LIFO (Last - In - First- Out - "последним пришел - первым исключается"). Примеры стека: винтовочный патронный магазин, тупиковый железнодорожный разъезд для сортировки вагонов.

Основные операции над стеком - включение нового элемента (английское название push - заталкивать) и исключение элемента из стека (англ. pop - выскакивать).

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

определение текущего числа элементов в стеке;

очистка стека;

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

x:=pop(stack); push(stack,x);

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

а). пустого;

б-г). после последовательного включения в него элементов с именами 'A', 'B', 'C';

д, е). после последовательного удаления из стека элементов 'C' и 'B';

ж). после включения в стек элемента 'D'.

76.

Абстрактные структуры данных предназначены для удобного хранения и доступа к информации. Они предоставляют удобный интерфейс для типичных операций с хранимыми объектами, скрывая детали реализации от пользователя. Конечно, это весьма удобно и позволяет добиться большей модульности программы. Абстрактные структуры данных иногда делят на две части: интерфейс, набор операций над объектами, который называют АТД(абстрактный тип данных) и реализацию.

Очередь FIFO

Очередью FIFO (First - In - First- Out - "первым пришел - первым исключается"). называется такой последовательный список с переменной длиной, в котором включение элементов выполняется только с одной стороны списка (эту сторону часто называют концом или хвостом очереди), а исключение - с другой стороны (называемой началом или головой очереди). Те самые очереди к прилавкам и к кассам, которые мы так не любим, являются типичным бытовым примером очереди FIFO.

Основные операции над очередью - те же, что и над стеком - включение, исключение, определение размера, очистка, неразрушающее чтение.

77.

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

Графически связи в списках удобно изображать с помощью стрелок. Если компонента не связана ни с какой другой, то в поле указателя записывают значение, не указывающее ни на какой элемент. Такая ссылка обозначается специальным именем - nil.

Рис. 1: Представление односвязного списка в памяти

На рис. 1 приведена структура односвязного списка. На нем поле INF - информационное поле, данные, NEXT - указатель на следующий элемент списка. Каждый список должен иметь особый элемент, называемый указателем начала списка или головой списка, который обычно по формату отличен от остальных элементов. В поле указателя последнего элемента списка находится специальный признак nil, свидетельствующий о конце списка.

Двусвязный список характеризуется наличием пары указателей в каждом элементе: на предыдущий элемент и на следующий:

Рис. 2: Представление двусвязного списка в памяти

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

Разновидностью рассмотренных видов линейных списков является кольцевой список, который может быть организован на основе как односвязного, так и двухсвязного списков. При этом в односвязном списке указатель последнего элемента должен указывать на первый элемент; в двухсвязном списке в первом и последнем элементах соответствующие указатели переопределяются, как показано на рис.3.

Рис. 3: Структура кольцевого двухсвязного списка

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

78.

Из всех универсальных невстроенных типов самым полезным, по всей видимости, является ассоциативный массив. Его часто называют таблицей (map), а иногда словарем, и он хранит пары значений. Имея одно из значений, называемое ключом, можно получить доступ к другому, называемому просто значением. Ассоциативный массив можно представлять как массив, в котором индекс не обязан быть целым: template<class K, class V> class Map { // ... public: V& operator[](const K&); // найти V, соответствующее K // и вернуть ссылку на него // ... }; Здесь ключ типа K обозначает значение типа V. Предполагается, что ключи можно сравнивать с помощью операций == и <, так что массив можно хранить в упорядоченном виде. Отметим, что класс Map отличается от типа assoc из $$7.8 тем, что для него нужна операция "меньше чем", а не функция хэширования.

79.

Проект — это базовое понятие. Под проектом подразумевается совокупность исходных файлов, распределенных каким-то образом по подкаталогам проекта. Проект может содержать один или более проектный файл для построения из исходных текстов чего-либо, что может считаться целью проекта. Например, для C++ это может быть статическая библиотека, динамическая библиотека, исполнимый файл. Проект может вообще не содержать проектных файлов, если целью проекта является предоставление исходных файлов другим проектам. Для С++ примерами таких проектов являются cpp_util_2, auto_ptr_3, большая часть библиотек boost и т.д., в которых код сосредоточен в inline-функциях и не требует отдельной компиляции.

Проект может нуждаться в подпроектах. Подпроект — это исходные тексты (и не только) другого проекта, импортированные в структуру каталогов проекта. Например, проект so_4 нуждается в исходных текстах проектов auto_ptr_3, cpp_util_2, memcheck_2, oess_1, threads_1 и др. Эти проекты объявляются подпроектами проекта so_4. При помощи специальной команды их исходные тексты копируются в каталог проекта so_4 и используются для компиляции so_4. Проект состоит из одного или нескольких модулей. Каждый модуль представляет некую самостоятельную сущность, которая может выступать в качестве подпроекта в другом проекте. Например, проект oess_1 состоит из модулей: oess_1/defs, oess_1/io, oess_1/file, oess_1/stdsn, oess_1/db, oess_1/tlv и т.д. Включение всех модулей подпроекта oess_1, например, в проект so_4 может быть невыгодно, если проект so_4 использует функциональность только модуля oess_1/stdsn. В этом случае в качестве подпроекта so_4 использует модуль oess_1/stdsn. 2.2. Версия: поколение, ветвь, релиз Версия — это некоторый идентификатор, который определяет функциональность проекта. Предлагается использовать идентификатор из трех составляющих: поколение (generation), ветвь (branch), релиз (release). Например, запись oess версии 1.1.4 говорит о релизе 4 ветви “2” первого поколения проекта oess. Поколение и ветвь определяют степень несовместимости различных версий проектов. Версии абсолютно не совместимы, если не совпадает значение поколения. Т.е., если проект so использовал oess первого поколения, то для перехода на второе поколение oess потребуется серьезная переделка проекта so. Значение поколения следует изменять, если в проекте происходят фундаментальные изменения. Например, меняется идеология работы с проектом. Поколение проекта является настолько важным показателем, что номер поколения следует включать в имя проекта. Так, название oess_1 сразу говорит о проекте “oess” первого поколения, название so_4 — о проекте “so” четвертого поколения и т.д. Разные версии проекта более-менее совместимы между собой, если в их версиях совпадает номер поколения, но не совпадают имена ветвей. Несовпадающие имена ветвей говорят о том, что переход с одной ветви на другую возможен без значительных изменений в использующем этот проект проекте. Т.е. гарантируется сохранение идеологии работы с проектом, но в незначительной степени меняется синтаксис. Если в качестве имен ветвей использовать числовые значения, например, 4.0.3, 4.1.5, 4.2.7, то увеличение номера ветви говорит о создании новой версии, не гарантирующей 100% совместимости “сверху-вниз”.

80.

81.

Модульное программирование

Со временем при в проектировании программ акцент сместился с организации процедур на организацию структур данных. Помимо всего прочего это вызвано и ростом размеров программ. Модулем обычно называют совокупность связанных процедур и тех данных, которыми они управляют. Парадигма программирования приобрела вид:

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

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

[1] Предоставить пользователю интерфейс для стека (например, функции push () и pop ()).

[2] Гарантировать, что представление стека (например, в виде массива элементов) будет доступно лишь через интерфейс пользователя.

[3] Обеспечивать инициализацию стека перед первым его использованием.

Язык Модула-2 прямо поддерживает эту парадигму, тогда как С только допускает такой стиль. Ниже представлен на С возможный внешний интерфейс модуля, реализующего стек:

// описание интерфейса длямодуля, // реализующего стек символов:

void push ( char );

char pop ();

const int stack_size = 100;

Допустим, что описание интерфейса находится в файле stack.h, тогда реализацию стека можно определить следующим образом:

#include "stack.h" // используем интерфейс стека

static char v [ stack_size ]; // ``static'' означает локальный

// в данном файле/модуле

static char * p = v; // стек вначале пуст

void push ( char c )

{ //проверить на переполнение и поместить в стек }

char pop ()

{ //проверить, не пуст ли стек, и считать из него }

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

#include "stack.h" // используем интерфейс стека

void some_function ()

{

push ( 'c' );

char c = pop ();

if ( c != 'c' ) error ( "невозможно" );

}

Поскольку данные есть единственная вещь, которую хотят скрывать, понятие упрятывания данных тривиально расширяется до понятия упрятывания информации, т. е. имен переменных, констант, функций и типов, которые тоже могут быть локальными в модуле. Хотя С++ и не предназначался специально для поддержки модульного программирования, классы поддерживают концепцию модульности ($$5.4.3 и $$5.4.4). Помимо этого С++, естественно, имеет уже продемонстрированные возможности модульности, которые есть в С, т. е. представление модуля как отдельной единицы трансляции.

82.

Отладка программных комплексов: этапы отладки, методы отладки, организация тестирования программ.

Тестирование проекта (C++)

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

На данном этапе производится наблюдение за значением переменной в процессе выполнения программы и выявление причин отклонения этого значения от ожидаемого.

Запуск программы в режиме отладки

Щелкните вкладку testgames.cpp в области редактирования, если этот файл не отображается.

Щелкните следующую строку в редакторе, чтобы установить ее в качестве текущей: solitaire = new Cardgame(1);

Чтобы установить в этой строке точку останова, в меню Отладка выберите команду Точка останова или нажмите клавишу F9. Кроме того, для установки или удаления точки останова можно щелкнуть в области слева от строки кода.

Слева от строки с установленной точкой останова появляется красный кружок. В меню Отладка выберите команду Начать отладку или нажмите клавишу F5.При достижении программой строки с установленной точкой останова выполнение временно приостанавливается (так как программа находится в режиме приостановки выполнения). Строка, которая должна выполняться следующей, отмечается желтой стрелкой слева от строки. Чтобы узнать текущее значение переменной totalparticipants, наведите на нее курсор. Имя переменной и ее значение, равное 12, отобразится в окне всплывающей подсказки. Чтобы наблюдать за переменной totalparticipants в окне Контрольные значения, щелкните ее правой кнопкой мыши и выберите команду Добавить контрольное значение. Кроме того, можно выделить переменную и перетащить ее в окно Контрольные значения.

В меню Отладка нажмите кнопку Шаг с обходом или нажмите клавишу F10, чтобы перейти к следующей строке кода.

Значение переменной totalparticipants изменится на 13.

Щелкните правой кнопкой мыши последнюю строку в методе main (return 0;) и выберите команду Выполнить до текущей позиции. Желтая стрелка слева от строки кода указывает на следующий оператор, который должен выполняться.

Значение переменной totalparticipants должно уменьшаться при завершении работы приложения Cardgame.На этом этапе значение totalparticipants должно быть равно нулю, так как все указатели приложения Cardgame были удалены, однако в окне Контрольные значения 1 значение переменной totalparticipants указывается равным 18.

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

Чтобы остановить программу, в меню Отладка выберите команду Остановить отладку или нажмите сочетание клавиш SHIFT+F5.

2.3.3. Отладка программы

Отладка начинается с устранения из программы синтаксических ошибок, т.е. таких, которые могут быть определены компилятором. Перед выполнением программы ее надо компилировать (Compile) и компонировать (Link). Если Вы сразу запускаете программу на выполнение (Run), то компиляция и компоновка происходят автоматически. Но обратите внимание на то, что компилятор C кроме сообщений об ошибках может выдавать еще и предупреждения. Предупреждения выдаются к таким конструкциям программы, которые являются формально правильными (с точки зрения синтаксиса), но компилятор "подозревает" в них семантическую ошибку. Довольно часто предупреждения компилятора действительно отражают ошибки программиста. Программа с ошибками не может компоноваться и исполняться. Программа с предупреждениями - может. Если Вы сразу (без исполнения компиляции отдельным шагом) запускаете программу на высполнение, Вы не увидете предупреждений компилятора, т.е рискуете выполнять программу с теми ошибками, которые могли быть выявленны предварительно. Мы рекомендуем всегда выполнять компиляцию отдельным шагом и не переходить к следующему шагу, пока Вы не убедитесь в том, что предупреждений компилятора нет или его "подозрения" безосновательны. Отладку програмного кода можно вести методом "черного ящика" или "белого ящика".

Подход "черного ящика" рассматривает программу как ящик с непрозрачными стенками, и тот, кто ведет отладку, ничего не знает о том, что там внутри. Он только задает программе входные данные и проверяет, правильны ли выходные данные программы. Идеальный тест методом "черного ящика" должен проверить програму на всех теоретически возможных комбинациях входных данных, но это по большей части просто технически невозможно. Значит, для тестирования следует подобрать "критические" комбинации входных данных, на которых больше всего возможны ошибки, - а это уже требует некоторых допущений о внутренней структуре программы. К тому же, когда оошибку обнаружено, подход "черного ящика" не дает нам возможности ее локализовать - обнаружить, в каком точно месте программы она находится. Значит, подход "черного ящика" более пригоден не для самой отладки, а для внешнего тестирования программы.

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

Современные системы програмирования (и Borland C++ в том числе) предоставляют возможность отладки программы в пошаговом режиме - выполнение программы с остановкой после каждого оператора или в заданых точках программы, с возможностью также проверять текущие значения переменных. Но не следует чересчур увлекаться этой возможностью. В ряде случаев "старый добрый" метод вывода промежуточных результатов оказывается и более быстрым, и более информативным.

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

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

83.

Особенности перехода от «С» к С++.

Язык программирования С++ произошёл от Си. Однако в дальнейшем Си и C++ развивались независимо, что привело к росту несовместимостей между ними. Последняя редакция Си, С99, добавила в язык несколько конфликтующих с С++ особенностей. Эти различия затрудняют написание программ и библиотек, которые могли бы нормально компилироваться и работать одинаково в компиляторах Си и C++, что, конечно, запутывает тех, кто программирует на обоих языках.

Бьёрн Строуструп, придумавший C++, неоднократно выступал за максимальное сокращение различий между Си и C++ для создания максимальной совместимости между этими языками. Противники же такой точки зрения считают, что так как Си и C++ являются двумя различными языками, то и совместимость между ними не так важна, хоть и полезна. Согласно этому лагерю, усилия по уменьшению несовместимости между ними не должны препятствовать попыткам улучшения каждого языка в отдельности.

Вот различия между этими языками, существующие на сегодня:

inline — подставляемые функции существуют в глобальном пространстве С++, а в Си — в пространстве файла (статическом пространстве). Другими словами, это значит, что в С++ любое определение подставляемой функции (независимо от переопределения функций) должно соответствовать правилу одного определения, требующего того, чтобы любая подставляемая функция была определена только один раз. В Си же одна и та же подставляемая функция может быть определена по-разному в разных компилируемых файлах одной программы.

В отличие от C++, ключевое слово bool в С99 требует включения соответствующего заголовочного файла stdbool.h. В стандарте C99 определен собственный тип логических данных _Bool. Предыдущий стандарт Си (C89) не определял булевый тип вообще, поэтому для этого часто использовались различные (а значит, несовместимые) методы.

Символьные константы (заключённые в одинарные кавычки) имеют тип int в Си и тип char в C++. Поэтому в Си справедливо равенство sizeof('a') == sizeof(int), а в C++ — равенство sizeof('a') == sizeof(char).[1]

Некоторые новые возможности C99, в первую очередь, restrict, не включены в стандарт Си++.

Си перенял от C++ ряд особенностей:

прототипы объявления функций;

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

более сильную проверку типов, включая добавление типа void, спецификатора const и удаление принятия по умолчанию типа int в качестве возвращаемого значения.

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

Как язык, так и стандартные библиотеки С++ проектировались в расчете на переносимость. Имеющиеся реализации языка будут работать в большинстве систем, поддерживающих С. В программах на С++ можно использовать библиотеки С. Большинство служебных программ, рассчитанных на С, можно использовать и в С++.

Язык С сам развивался в последние несколько лет, что отчасти было связано с разработкой С++ [14]. Стандарт ANSI для С [27] содержит, например, синтаксис описания функций, позаимствованный из языка "С с классами". Происходит взаимное заимствование, например, тип указателя void* был придуман для ANSI С, а впервые реализован в С++. Как было обещано в первом издании этой книги, описание С++ было доработано, чтобы исключить неоправданные расхождения. Теперь С++ более совместим с языком С, чем это было вначале ($$R.18). В идеале С++ должен максимально приближаться к ANSI C, но не более [9]. Стопроцентной совместимости никогда не было и не будет, поскольку это нарушит надежность типов и согласованность использования встроенных и пользовательских типов, а эти свойства всегда были одними из главных для С++.

Для изучения С++ не обязательно знать С. Программирование на С способствует усвоению приемов и даже трюков, которые при программировании на С++ становятся просто ненужными. Например, явное преобразование типа (приведение) , в С++ нужно гораздо реже, чем в С (см. "Замечания для программистов на С" ниже). Тем не менее, хорошие программы на языке С по сути являются программами на С++. Например, все программы из классического описания С [8] являются программами на С++. В процессе изучения С++ будет полезен опыт работы с любым языком со статическими типами.

Замечание для программистов на С

Чем лучше программист знает С, тем труднее будет для него при программировании на С++ отойти от стиля программирования на С. Так он теряет потенциальные преимущества С++.

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

83.

Три слоя Си: ядро, препроцессор и функциональное программирование. В отличие от Паскаля, Модулы или Ады, которые были созданы автором (или авторским коллективом) в практически законченном виде, Си создавался скорее стихийно. Достаточно проанализировать Си в его современном виде, чтобы выделить первоначальное “ядро” языка и как минимум два крупных “слоя”, добавленных позднее. Скорее всего, первый Си включал в себя современный набор операторов языка и простейшие средства описания типов данных и переменных; и даже не имел средств для задания констант. Когда Си стал применяться для решения серьезных задач, к нему добавили так называемые “директивы препроцессора”, такие, как #define и #include. #define был призван решить проблему задания констант, но позднее стал применяться и для других целей (например, inline-процедур). Директива #include стала примитивным (и единственным на сегодняшний день) средством импорта модулей. Наконец, есть третий “слой” Си, на который редко обращают внимание. В момент создания языка в США была очень популярна концепция “функционального программирования”, основным языком которого был (и остается по сей день) Лисп. Несмотря на явную несхожесть этих языков, влияние функционального программирования на Си несомненно. Положение о том, что каждый оператор, каждая операция и любая функция должны выдавать какой-либо результат, правила ассоциативности операций, другие особенности, присущие языкам “функционального программирования”, сделали Си источником различных программистских “трюков” (например, на Си можно написать цикл while, в теле которого не будет ни одного оператора, но он тем не менее будет выполнять некоторую полезную работу).

Плюсы и минусы. “Стихийное” формирование языка послужило источником как положительных, так и отрицательных его качеств. В “актив” Си, безусловно, следует записать набор операторов языка. Компактные, мощные и красиво сделанные конструкции позволяют программисту кодировать алгоритмы любой сложности. Другим приятным качеством Си можно считать механизм работы со строками символов. В то же время описания типов (особенно указателей и структур) нельзя назвать идеальными. Применение директивы #include для импорта идентификаторов приводит к неоправданному расходу времени при компиляции (особенно заметному в современных ОС, содержащих тысячи стандартных функций API). Отсутствие всеобъемлющего контроля типов (строгой типизации) и, особенно, типа boolean, приводит к сложно обнаруживаемым ошибкам, которые в других языках были бы выявлены еще на этапе компиляции.

Си++ и Ява. В языках программирования, созданных на основе Си, некоторые из его недостатков были устранены. Так, в Си++ для замены директивы #define введены конструкции const (для объявления констант) и inline (для описания inline-функций). Добавлен тип boolean и контроль типов в операторе присваивании и при вызове функций. Введен тип перечисления enum и параметры-ссылки (аналоги параметров var в Паскале и Модуле). Создатели Явы пошли еще дальше. Они полностью переделали конструктор типов данных, ввели строгий контроль типов и вместо импорта модулей (#include) стали использовать импорт классов. Можно сказать, что Ява является Си-клоном только внешне. По идеологии это типичный представитель европейской школы программирования.

84.,87.

Наследование, инкапсуляция и полиморфизм, иерархия классов.

Все языки OOP, включая С++, основаны на трёх основополагающих концепциях, называемых инкапсуляцией, полиморфизмом и наследованием. Рассмотрим эти концепции.

1. Инкапсуляция

Инкапсуляция (encapsulation) - это механизм, который объединяет данные и код, манипулирующий зтими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В объектно-ориентированном программировании код и данные могут быть объединены вместе; в этом случае говорят, что создаётся так называемый "чёрный ящик". Когда коды и данные объединяются таким способом, создаётся объект (object). Другими словами, объект - это то, что поддерживает инкапсуляцию.

Внутри объекта коды и данные могут быть закрытыми (private). Закрытые коды или данные доступны только для других частей этого объекта. Таким образом, закрытые коды и данные недоступны для тех частей программы, которые существуют вне объекта. Если коды и данные являются открытыми, то, несмотря на то, что они заданы внутри объекта, они доступны и для других частей программы. Характерной является ситуация, когда открытая часть объекта используется для того, чтобы обеспечить контролируемый интерфейс закрытых элементов объекта.

На самом деле объект является переменной определённого пользователем типа. Может показаться странным, что объект, который объединяет коды и данные, можно рассматривать как переменную. Однако применительно к объектно-ориентированному программированию это именно так. Каждый элемент данных такого типа является составной переменной.

{ private:

int a,b; //скрытые свойства

void DoSomething(); //скрытый метод.

public:

int ReturnSomething(); //открытый интерфейс

};

Класс А инкапсулирует свойства a, b и метод DoSomething, представляя внешний интерфейс ReturnSomething.

2. Полиморфизм

Полиморфизм (polymorphism) (от греческого polymorphos) - это свойство, которое позволяет одно и то же имя использовать для решения двух или более схожих, но технически разных задач. Целью полиморфизма, применительно к объектно-ориентированному программированию, является использование одного имени для задания общих для класса действий. Выполнение каждого конкретного действия будет определяться типом данных. Например для языка Си, в котором полиморфизм поддерживается недостаточно, нахождение абсолютной величины числа требует трёх различных функций: abs(), labs() и fabs(). Эти функции подсчитывают и возвращают абсолютную величину целых, длинных целых и чисел с плавающей точкой соответственно. В С++ каждая из этих функций может быть названа abs(). Тип данных, который используется при вызове функции, определяет, какая конкретная версия функции действительно выполняется. В С++ можно использовать одно имя функции для множества различных действий. Это называется перегрузкой функций (function overloading).

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

Полиморфизм может применяться также и к операторам. Фактически во всех языках программирования ограниченно применяется полиморфизм, например, в арифметических операторах. Так, в Си, символ + используется для складывания целых, длинных целых, символьных переменных и чисел с плавающей точкой. В этом случае компилятор автоматически определяет, какой тип арифметики требуется. В С++ вы можете применить эту концепцию и к другим, заданным вами, типам данных. Такой тип полиморфизма называется перегрузкой операторов (operator overloading). Ключевым в понимании полиморфизма является то, что он позволяет вам манипулировать объектами различной степени сложности путём создания общего для них стандартного интерфейса для реализации похожих действий.