Главная Рефераты по сексологии Рефераты по информатике программированию Рефераты по биологии Рефераты по экономике Рефераты по москвоведению Рефераты по экологии Краткое содержание произведений Рефераты по физкультуре и спорту Топики по английскому языку Рефераты по математике Рефераты по музыке Остальные рефераты Рефераты по авиации и космонавтике Рефераты по административному праву Рефераты по безопасности жизнедеятельности Рефераты по арбитражному процессу Рефераты по архитектуре Рефераты по астрономии Рефераты по банковскому делу Рефераты по биржевому делу Рефераты по ботанике и сельскому хозяйству Рефераты по бухгалтерскому учету и аудиту Рефераты по валютным отношениям Рефераты по ветеринарии Рефераты для военной кафедры Рефераты по географии Рефераты по геодезии Рефераты по геологии Рефераты по геополитике Рефераты по государству и праву Рефераты по гражданскому праву и процессу Рефераты по делопроизводству Рефераты по кредитованию Рефераты по естествознанию Рефераты по истории техники Рефераты по журналистике Рефераты по зоологии Рефераты по инвестициям Рефераты по информатике Исторические личности Рефераты по кибернетике Рефераты по коммуникации и связи |
Учебное пособие: Основы CУчебное пособие: Основы CКафедра: Автоматика и Информационные ТехнологииОСНОВЫ СОГЛАВЛЕНИЕ Введение Глава 1. Основы языка Си 1.1. Алфавит 1.2. Основные конструкции Си 1.3. Ввод-вывод. 1.3.1. Вывод 1.3.2. Ввод 1.4. Базовые типы данных языка Си 1.4.1. Идентификаторы. 1.4.2. Типизированные константы 1.4.3. Переменные 1.4.3.1. Целые типы 1.4.3.2. Вещественные типы 1.4.3.3. Символьные типы 1.4.4. Тип данных строка 1.4.5. Структуры 1.4.5.1. Массивы 1.4.5.2. Записи 1.4.6 Область видимости и время жизни переменных 1.5. Основные операторы 1.6. Препроцессор 1.7. Программы. Функции 1.8. Указатели 1.9. Указатели и функции 1.10. Файлы 1.11. Дополнительные функции Си 1.11.1. Функции преобразования Функции преобразования символьных строк: atoi(), atof(). 37с. 1.11.3. Функции, работающие со строками 1.12. Особенности программирования на языке Си 1.12.1. Модули. Многомодульное программирование 1.12.2 . Модели памяти 1.12.3 . Программирование в DOS и Windows 1.12.4 . Использование языка ассемблера в программах на Си Глава 2. Примеры использования языка Си 2.1. Сортировка 2.2. Рекурсивные алгоритмы 2.3. Задача "Ханойские башни" Глава 3. Основы С++ 3.1. Отличия С++ от Си 3.2. Объектно-ориентированное программирование в С++ 3.2.1. Классы 3.2.2. Перегрузка функций 3.2.3. Конструкторы 3.2.4. Деструкторы 3.2.5. Конструкторы с параметрами 3.2.6. Введение в наследование 3.2.7. Виртуальные функции 3.2.8. Указатели на объекты Глава 4. Основы программирования на языке С++Builder 4.1. Характеристика С++Builder 4.2. Компоненты VCL. Свойства. События. Методы 4.2.1. Типы компонент 4.2.2. Иерархия классов VCL 4.3. Структура файлов в С++Builder 4.4. Визуальные компоненты(VCL) 4.5. Программы, управление событиями, исключения 4.6. Стратегия отладки Windows-программ 4.7. Использование компонент VCL в разработке программ 4.8. Графические компоненты. 4.9. Мультимедиа 4.10. Спрайтовая графика 4.11. Объектная технология OLE2 4.12. Библиотеки DLL. 4.13. Разработка визуальных компонент 4.14. Введение в программирование CGI 3.15. Программирование баз данных. 3.16. Пакеты Заключение Литература Приложение N1 Оглавление ВведениеВ 1804 году французский изобретатель Жозеф Мари Жаккар создал "программно-управляемый" ткацкий станок. Для управления станком использовались перфокарты, соединенные друг с другом в виде ленты. Деревянные шпильки "читающего устройства" станка по расположению отверстий в перфокарте определяли, какие нити следует поднять, а какие опустить для получения нужного узора. В 1890 году в США изобретателем Германом Холлеритом разработана электромеханическая счетная машина - табулятор, управляемая перфокартами, была использована для составления таблиц с результатами переписи населения США. Основанная Холлеритом фирма по производству табуляторов впоследствии превратилась в корпорацию International Business Machines (IBM). В 1936 году двадцатипятилетний студент Кембриджского университета англичанин Алан Тьюринг опубликовал статью "О вычислимых числах", в которой рассматривалось гипотетическое устройство ("машина Тьюринга"), пригодное для решения любой разрешимой математической или логической задачи, - прообраз программируемого компьютера. В1941 году немецкий инженер Конрад Цузе построил действующий компьютер Z3, в котором использовалась двоичная система счисления. Программы записывались на перфоленте. В 1945 году в высшем техническом училище Пенсильванского университета (США) физик Джон Мочли и инженер Проспер Экерт построили полностью электронную машину "Эниак". Для задания программы было необходимо вручную установить тысячи переключателей и воткнуть сотни штекеров в гнезда контактной панели. 1 июня 1945 года был разослан отчет американского математика венгерского происхождения Джона фон Неймана "Предварительный отчет о машине Эдвак", содержащий концепцию хранения команд компьютера в его собственной внутренней памяти. 21 июня 1948 года в Манчестерском университете (Великобритания) на машине "Марк-1" выполнена первая в мире хранимая в памяти машины программа - поиск наибольшего сомножителя заданного числа. В 1949 году под руководством Мориса Уилкса создан компьютер "Эдсак". Проектировщики "Эдсака" ввели систему мнемонических обозначений, где каждая машинная команда представлялась одной заглавной буквой, и автоматизировали настройку подпрограмм на определенное место в памяти. Морис Уилкс назвал мнемоническую схему и библиотеку подпрограмм собирающей системой (assembly system) - отсюда слово "ассемблер". В 1949 году в Филадельфии (США) под руководством Джона Мочли создан "Краткий код" - первый примитивный интерпретатор языка программирования. В 1951 году в фирме Remington Rand американская программистка Грейс Хоппер разработала первую транслирующую программу. Хоппер назвала ее компилятором (compiler - компоновщик). В 1957 году на 20-м этаже штаб-квартиры фирмы IBM на Мэдисон-авеню в Нью-Йорке родился язык Фортран (FORmula TRANslation - трансляция формул). Группой разработчиков руководил 30-летний математик Джон Бэкус. Фортран - первый из "настоящих" языков высокого уровня. В 1972 году 31-летний специалист по системному программированию из фирмы Bell Labs Деннис Ритчи разработал язык программирования Си. В 1984 году французский математик и саксофонист Филип Кан основывает фирму Borland International. Далее появился диалект языка Си фирмы Borland. Первоначально Си был разработан как язык для программирования в операционной системе Unix. Вскоре он стал распространяться для программистов-практиков. В конце 70-х были разработаны трансляторы Си для МикроЭВМ для операционной системой СР/M. После появления IBM PC стали появляться и компиляторы Си (для этого компьютера их сейчас более 20). В 1983 г. Американский Институт Стандартов (ANSI) сформировал Технический Комитет X3J11, для создания стандарта языка Си. Появившийся на рынке язык Си++ корпорации Borland подчиняется большинству требований стандарта. По сути своей Си является языком функций. Программирование на Си, осуществляется путем написания функций и обращения к библиотечным функциям. Большинство функций возвращают некоторые значения, которые могут использоваться в других операторах. Среди множества достоинств языка Си нужно отметить основные: - универсальность (используется почти на всех существующих ЭВМ); - компактность и универсальность кода; - быстрота выполнения программ; - гибкость языка; - высокую структурированность. Глава 1. Основы языка Си 1.1. АлфавитАлфавит языка состоит из следующих символов: Заглавные и строчные латинские буквы A-Z, a-z и символ подчеркивания. Цифр от 0 до 9. Специальных символов + - * / = > < . ; , : [ ] { } ( ) # $. Символы пробел, табуляция, перевод строки, возврат каретки называются пробельными. Программа на Cи++ представляет собой последовательность ACSII-символов, представляющих собой ее исходный текст. 1.2. Основные конструкции СиРассмотрим простую программу печати на экране фразы "Привет от ЭВМ" #include <stdio.h> main() // программа { printf("Привет от ЭВМ\n"); } Рассмотрим структуру программы
Команда #include подключает файл stdio.h к нашей программе. В файле содержится сведения о функциях ввода-вывода. Файл с именем stdio.h содержит информацию о вводе-выводе. Символ # указывает, что прежде чем транслировать программу необходимо включить в нее функции из файла stdio.h - это так называемая препроцессорная обработка, которая осуществляет некоторую предварительную обработку текста программы перед началом компиляции. Строка main() содержит название функции, с которой всегда начинается программа. Пустые скобки обозначают, что эта функции, но она без параметров. После символов // стоят комментарии (для одной строки), (комментариями также называются символы, стоящие между /* и */). Фигурные скобки {} отмечают начало и конец тела программы или функции. Они также применяются для объединения нескольких операторов программы в блок. В строке printf ("Привет от ЭВМ\n") содержится стандартная функция печати Си. Строка символов, заключенная в скобки (аргумент), является информацией, передаваемой функции printf() из нашей главной функции main(). Когда выполнение функции будет завершено, управление вернется обратно в исходную программу. Символ \n - обозначает перевод строки на новую строку после печати. Рассмотрим следующий пример - написать программу перевода метров в сантиметры. #include <stdio.h> #include <conio.h> main() { int I; int J; int C; printf("M? \n"); scanf("%d",&I); J=100*I; printf(" В %d М содержится %d cm\n", I,J); C=getch(); } В данной программе определены две целочисленные переменные I и J. Введена функция scanf("%d",&I); которая позволяет вводить десятичное число с клавиатуры и значение присвоить переменной I, далее по команде J=100*I; идет вычисление4. Следующая строка printf(" В %d М содержится %d cm\n", I,J); печатает фразу. На место первого %d (d- целая переменная) ставится значение I, на место второго %d ставится значение J. Функция getch() позволяет задержать изображение на экране и требует любого символа, чтобы закончить работу. Рассмотрим еще пример. Напишем функцию и вызовем ее из нашей программы. #include <stdio.h> #include <conio.h> supervisor() { printf("Вы меня вызывали?\n"); } main() { char C; printf("Я вызываю функцию supervisor.\n"); supervisor(); printf("Да. Посмотрите, кто из студентов спит и разбудите их.\n"); C=getch(); } Вначале описываем функцию supervisor(), а затем обращаемся к ней в основной программе по команде supervisor();. В результате получаем диалог: - Я вызываю функцию supervisor. - Вы меня вызывали? - Да. Посмотрите, кто из студентов спит, и разбудите их. 1.3 Ввод-выводДля решения задачи в какой-либо проблемной области необходимо написать программу, в которой должны быть команды, позволяющие:- выделять место для хранения данных;- вводить исходные данных; - обрабатывать исходные данные по алгоритму; - выводить выходные данные. Си содержит функции, необходимые для выполнения этих действий. Все функции будем рассматривать на примерах и начнем с функций ввода-вывода. 1.3.1. ВыводВывод осуществляется на экран, принтер, винчестер (гибкие диски), порт. Рассмотрим функции вывода на экран. Функция printf предназначена для этой цели. Формат: printf(<формат>[,аргумент1],...). Формат - это строка в двойных кавычках, которая выводится на экран. Перед выводом printf заменяет все объекты в строке в соответствии со спецификациями аргумента. Например, printf(" В %d М содержится %d cm\n", I,J); %d в строке формата - это спецификация аргумента. Спецификации аргумента начинаются с символа процента (%) и одной буквой, обозначающей тип данных. %d, используемое в спецификации, говорит о том, что ожидается некоторое целое число. Вот несколько других общеиспользуемых спецификаций формата: - %d целое число; - %u целое число без знака; - %ld длинное целое число; - %p значение указателя; - %f число с плавающей точкой; - %e число с плавающей точкой в экспоненциальной форме; - %c cимвол; - %s строка; - %x целое в шестнадцатеричном формате. Можно задать ширину поля, например %6d -поле шириной 6. Значение будет напечатано сдвинутым вправо (впереди пробелы), так что общая ширина поля равна 6. Для формата вещественных чисел можно указать дробную часть, например %8.4f - поле шириной 8, десятичная часть 4. В конце форматной строки можно поставить знаки: \n перевод строки;. \f (перевод формата или очистка экрана) \t (табуляция) \b (забой <-) \xhhh (вставка символа c кодом ASCII hhh, где hhh содержит от 1 до 3 16-ричных цифр) Для вывода можно использовать функции: puts и putchar. Функция puts выводит строку на экран. Например: #include <stdio.h> main () { puts("Привет, студент"); } Функция putchar выводит на экран один символ. 1.3.2 ВводВвод в Си в основном осуществляется с клавиатуры, из файла и порта. Функция scanf аналогична printf. Ее формат: scanf(<формата>[,аргумент1],...). В scanf используются те же спецификаторы формата, что и у функции printf. Необходимо отметить, что scanf имеет одну особенность: аргументы, следующие за строкой формата, должны быть адресами, а не значениями (это не распространяется на символьные переменные). Ранее в примерах мы видели, что при вводе целого числа записывалась функция следующим образом: scanf("%d", &a); & -операция адреса, которая передает scanf адреса. При вводе нескольких переменных через запятую можно использовать запятую внутри формата. Пример: scanf ("%d, %d", &a, &b); Теперь можно вводить, например 23,56. Имеются сложности с вводом строки символов из нескольких слов - введутся только данные до первого пробела. Для решения этой проблемы существует функция gets. #include <stdio.h> main () { char name [60]; printf("Как вас зовут: "); gets (name); printf ("Привет, %s\n", name); } Функция gets читает все, что набирается до тех пор, пока не нажат Ввод. В С++ ввод-вывод может осуществляться не только с помощью функций, но и с помощью операций. Операция вывода << операция ввода >>. Формат вывода на экран:cout<< выражение. Формат ввода с клавиатуры:cin<< переменная. При использовании операций ввода-вывода необходимо включить в программу файл iostream.h. Пример: #include <iostream.h> main() { int i; cout << "Целое число?: "; cin >> i; cout << " Квадрат числа: " << i*i << "\n"; return 0; } Возможен ввод-вывод нескольких величин (через пробел). 1.4 Базовые типы данных Языка Си 1.4.1 ИдентификаторыИмена, которые присваиваются константам, типам данных, переменным и функциям называются идентификаторами. В Си следующие правила создания идентификаторов: должны начинаться с буквы (a...z,A...Z) или с подчеркивания (_), остальная часть идентификатора должна состоять из букв, подчеркиваний и/или цифр(0...9). 1.4.2 Типизированные константы В Си используются константы, которые представляют собой имя, присваиваемое какой-либо величине. Идентификатор имени может быть любой длинны, но распознаются первые 32 символа. Идентификатор начинается с латинской буквы или символа подчеркивания, последующие символы могут быть цифры, латинские буквы и символы подчеркивания. Компилятор Си рассматривает буквы верхнего и нижнего регистра как различные символы. Типизированные константы бывают: целые, с плавающей запятой, символьные константы и символьные строки. Константы представляются в виде целого десятичного, восьмиричного или шестнадцатиричного числа. Описание констант начинается с ключевого слова const, далее тип и значение, например const int Nalog=2. 1.4.3 Переменные Переменная - это поименованная величина, значение которой может изменяться во время выполнения программы. Переменная принадлежит к определенному типу. 1.4.3.1 Целые типы Целые числа представляются целыми типами. Целый тип - это обобщенный тип, представление которого зависит от операционной системы и типа процессора. Рассмотрим основные типы целых чисел:
Рассмотрим простой пример. #include <stdio.h> #include <conio.h> main() { const int Nalog= 2; int Stavka; int Symma; int C; Stavka = 300; Symma = Stavka * Nalog; printf("Symma naloga = %d\n",Symma); C=getch(); } В примере объявлены одна константа и две переменные целого типа. 1.4.3.2 Вещественные типы Для хранения чисел с дробной частью используется вещественный тип. В C++ существуют следующие основные типы вещественных чисел:
Рассмотрим следующий пример. #include <stdio.h> main() { const float Nalog= 0.7; int Stavka; float Symma; Stavka = 325; Symma = Stavka * Nalog; printf("Symma naloga = %8.4f\n",Symma); } В данном примере вещественный тип представлен переменной Symma. 1.4.3.3 Символьные типы Символьный тип -Char предназначен для хранения одного символа, поэтому его размер - один байт.
Рассмотрим пример: #include <stdio.h> main() { char A; char B; char C; A = 'D'; B = '!'; printf("A= %c B= %c\n",A,B); C = '*'; printf("C= %c\n",C); } В данном примере переменной A присвоено значение ‘D’, переменной B значение ‘!’, а переменной С присвоено значение символа ‘*’. 1.4.4 Тип данных строка Для представления строки символов в Си используют массивы типа char. Рассмотрим пример. #include <stdio.h> #include <string.h> main() { char A[256]; /* длина может быть до 256 символов */ char B[11]; char C[24]; strcpy(A,"IBM PC Pentium"); strcpy(B,"Windows 95"); strcpy(C,""); /* очистка переменной */ printf("A= %s\n",A); printf("B= %s\n",B); strcpy(C,B); printf("C= %s\n",C); } В данном примере представлены три строки символов A, B, C. По команде, например, strcpy (A,"IBM PC Pentium"); в строку A помещается текст IBM PC Pentium. Кроме рассмотренного примера, для определения строк можно использовать указатель на символы. Пример: #include <stdio.h> #include <string.h> main() { char *msg; msg = "Привет, студент"; puts(msg); } Звездочка перед msg означает, что msg является указателем на символ - т.е. msg может хранить адрес символа. Однако, при этом память для размещения символов не выделяется. Команда msg = "Привет, студент " присваивает начальный адрес этой строки - (адрес символа П) переменной msg. Команда puts(msg) печатает символы до тех пор, пока она не встретит нулевой символ, обозначающий конец строки.. 1.4.5 Структуры Размещенная в памяти совокупность связанных между собой данных представляет структуру. В Си рассматриваются следующие структуры: массивы, записи и их комбинации. 1.4.5.1 Массивы Именованная совокупность однородных данных называется массивом. Каждый элемент массива хранится в отдельной области памяти и имеет собственный номер (начиная с нуля). Рассмотрим пример. #include <stdio.h> main() { int B[3]; B[0] = 0; B[1] = 10; B[2] = 20; B[3] = 30; printf("B[0]= %d\n",B[0]); printf("B[1]= %d\n",B[1]); printf("B[2]= %d\n",B[2]); printf("B[3]= %d\n",B[3]); } В рассмотренном примере определен массив B, состоящий из четырех целочисленных элементов. С элементами массива можно манипулировать как с обыкновенными переменными. Существуют многомерные массивы, например: int A[3][2]; Массив A -двумерный массив ( состоит из четырех строк и трех столбцов): char A[3][3][3][3]; Массив A - четырехмерный массив. Рассмотрим пример работы с двумерным массивом. #include <stdio.h> main() { float B[4][3]; B[0][0] = 0; B[1][1] = 1.1; B[2][1] = 1.2; B[3][1] = 1.3; B[1][2] = 2.1; B[2][2] = 2.2; B[3][2] = 2.3; printf("B[1,1]= %4.2f B[2,1]= %4.2f B[3,1]= %4.2f\n", B[1][1],B[2][1],B[3][1]); printf("B[1,2]= %4.2f B[2,2]= %4.2f B[3,2]= %4.2f\n", B[1][2],B[2][2],B[3][2]); } 1.4.5.2 Записи В отличие от массивов запись позволяет хранить данные различных типов. Запись начинается после ключевого слова struct. Рассмотрим пример - в записи хранится информация о студентах: фамилия, год рождения, номер группы. #include <stdio.h> #include <string.h> typedef struct A { char Fio[31]; int God; int Gruppa; }; main() { struct A B; strcpy(B.Fio,"Ivanow G.I."); B.God = 1977; B.Gruppa = 384; printf("Fio = %s\n",B.Fio); printf("God = %d\n",B.God); printf("Gruppa = %d\n",B.Gruppa); } В рассмотренном примере запись имеет следующую структуру: struct A { /* A имя записи} */ char Fio[31]; /* 1 поле записи */ int God; /* 2 поле записи */ int Gruppa; /* 3 поле записи */ } Далее можем ввести структуру B аналогичную A по команде struct A B; Далее можем в поля помещать конкретные данные, но мы должны уточнить, что поля принадлежат переменной B нотацией с точкой (B.Fio:=”Ivanow G.I.”; и т.д.). С помощью ключа typedef структурам дается имя. Пример можно переписать следующим образом. #include <stdio.h> #include <string.h> typedef struct { char Fio[31]; int God; int Gruppa; } A; main() { A B; strcpy(B.Fio,"Ivanow G.I."); B.God = 1977; B.Gruppa = 384; printf("Fio = %s\n",B.Fio); printf("God = %d\n",B.God); printf("Gruppa = %d\n",B.Gruppa); } В примере мы рассмотрели одну запись, но в реальной жизни в группе не может быть одного студента, поэтому мы можем совместить массив и запись и создать массив записей. Рассмотрим пример. #include <stdio.h> #include <string.h> typedef struct A { char Fio[31]; int God; int Gruppa; }; main() { A WGruppa[12]; strcpy(WGruppa[1].Fio,"Ivanow G.I."); WGruppa[1].God = 1977; WGruppa[1].Gruppa = 384; strcpy(WGruppa[2].Fio,"Petrow R.G."); WGruppa[2].God = 1978; WGruppa[2].Gruppa = 384; printf("Fio1 = %s\n",WGruppa[1].Fio); printf("God1 = %d\n",WGruppa[1].God); printf("Gruppa1 = %d\n",WGruppa[1].Gruppa); printf("Fio2 = %s\n",WGruppa[2].Fio); printf("God2 = %d\n",WGruppa[2].God); printf("Gruppa2 = %d\n",WGruppa[2].Gruppa); } Первоначально мы определили структуру A, а затем использовали ее при объявлении структуры WGruppa, как массива состоящего из 12 записей структуры B. Теперь для адресации мы должны указать номер элемента массива и имя поля. Существуют варианты, когда одна запись содержит другую запись, например, добавляется адрес, к рассмотренной выше записи студент. Пример. #include <stdio.h> #include <string.h> typedef struct Adress { char City[31]; char Street_Nd_Kw[61]; }; typedef struct A { char Fio[31]; int God; int Gruppa; Adress D_addr; }; main() { A WGruppa[12]; strcpy(WGruppa[1].Fio,"Ivanow G.I."); WGruppa[1].God = 1977; WGruppa[1].Gruppa = 384; strcpy(WGruppa[1].D_addr.City,"Shadrinsk"); strcpy(WGruppa[1].D_addr.Street_Nd_Kw,"Lenina 10 kw.1"); strcpy(WGruppa[2].Fio,"Petrow R.G."); WGruppa[2].God = 1978; WGruppa[2].Gruppa = 384; strcpy(WGruppa[2].D_addr.City,"Kataisk"); strcpy(WGruppa[2].D_addr.Street_Nd_Kw,"Akulowa 1 kw.2"); printf("Fio1 = %s\n",WGruppa[1].Fio); printf("God1 = %d\n",WGruppa[1].God); printf("Gruppa1 = %d\n",WGruppa[1].Gruppa); printf("City= %s\n",WGruppa[1].D_addr.City); printf("Street= %s\n",WGruppa[1].D_addr.Street_Nd_Kw); printf("Fio2 = %s\n",WGruppa[2].Fio); printf("God2 = %d\n",WGruppa[2].God); printf("Gruppa2 = %d\n",WGruppa[2].Gruppa); printf("City= %s\n",WGruppa[2].D_addr.City); printf("Street= %s\n",WGruppa[2].D_addr.Street_Nd_Kw); } 1.4.6 Область видимости и время жизни переменных По области видимости переменные в Си можно разделить на три группы: 1.Переменная, определенная во всех модулях (файлах) программы. Такая переменная определяется при помощи ключевого слова extern. Эта переменная будет видна во всех точках программы. Такая переменная является глобальной для всей программы. 2.Переменная, определенная в одном из модулей (файле) перед текстами всех функций. Такая переменная будет глобальной для данного модуля, т.е. может быть использована во всех точках данного модуля. 3.Переменная определенная в данной функции. Эта переменная может быть использована только в пределах данной функции. Такую переменную мы будем называть локальной. По времени жизни все переменные можно разделить на две группы: 1.Переменные живущие в течении работы программы. 2.Переменные уничтожающиеся после выхода из функции. Глобальные переменные относятся к первому типу по времени жизни. Локальные переменные уничтожаются по выходу из функции. В том случае, когда мы хотим сделать локальную переменную долгоживущей используется слово static. Локальные переменные имеющие такой тип живут начиная с момента первого вызова функции и до конца работы программы. Однако в смысле видимости эти переменные остаются локальными. Запись static int i=0; Означает, что переменная инициализируется нулем при первом входе в функцию, но при последующих входах в функцию ее значение сохраняется в зависимости от тех действий, которые над ней были произведены. Современные компиляторы Си транслируют программу так, чтобы максимально оптимизировать ее работу. Одним из средств оптимизации это хранение переменных, когда это возможно в регистрах, вместо ячеек памяти. В тех случаях когда вы хотите запретить, чтобы данная переменная хранилась в регистрах используется модификатор volatile. Такая необходимость может возникнуть если предполагается возможность изменение переменной в результате внешнего воздействия (например прерывания). И последнее замечание. Динамически выделенная память, где бы вы ее не выделяли живет до тех пор пока вы ее не освободили. 1.5 Основные операторы Операция присваивания. Самой общей операцией является присваивание, например, с= a/b. В Си присваивание обозначается знаком равенства=, при этом значение справа от знака равенства присваивается переменной слева. Возможно, применять также последовательные присваивания, например: с = a = b. Арифметические операторы. В Си выполняются следующие группы арифметических операций: 1.Бинарные: сложение(+), вычитание(-), умножение(*), деление(/), целочисленное деление(%) (для типа int получение остатка). 2.Унарные: унарный плюс (+), унарный минус (-), адресация (&), косвенная адресация (*), определение размера памяти типа (sizeof). 3.Логические: и (&&), или (!!), не (!=). 4.Отношения: a)равно (==), не равно(!>); б) меньше чем (<), больше чем (>), меньше или равно (<=), больше или равно (>=); 5.Приращения (++) и уменьшения (--). Например, i++ обозначает, что i=i+1, а i-- обозначает i=i-1. 6.Побитовые операции - позволяют производить операции над битами. 7.Комбинированные операции. В Турбо-Си существуют сокращения при написании выражений, содержащих многочисленные операции: a = a + b; сокращается до a += b; a = a - b; сокращается до a -= b; a = a * b; сокращается до a *= b; a = a / b; сокращается до a /= b; a = a % b; сокращается до a %= b; 8.Адресные операции: 1. Операция определения адреса (&) 2. Операция обращения по адресу (*). Операция & возвращает адрес данной переменной; если Х является переменной типа int, то &Х является адресом (расположения в памяти) этой переменной. С другой стороны, если msg является указателем на тип char, то *msg является символом, на который указывает msg. Рассмотрим пример: #include <stdio.h> main() { int X; char *msg; X = 6 + 1; msg = "Привет\n"; printf(" X = %d &X = %p \n",X,&X); printf("*msg = %c msg = %p \n", *msg, msg); } При печати в первой функции печатается два значения: значение X 7 и адрес X (назначаемый компилятором). Во второй функции также печатается два значения: символ, на который указывает msg (П), и значение msg, которое является адресом этого символа (также назначен компилятором). Старшинство операций в Си соответствует старшинству операций в математике. Оператор запятая. Для организации множественных выражений, расположенных внутри круглых скобок используется оператор запятая. Выражение внутри скобок вычисляется слева направо, и все выражение принимает значение, которое было вычислено последним. Например: char X,Y; (X = Y, Y = getch()) присваивает переменной X значение Y, затем считывает символ, вводимый с клавиатуры, и запоминает его в Y. Результатом всего выражения, в итоге, будет значение введенного с клавиатуры символа. Операторы управления. Оператор If... дает возможность в зависимости от условия выполнять ту или иную ветвь программы. Синтаксис оператора следующий: If условие выражение1 else выражение2; Условие должно давать результат в виде логического значения истинности или ложности. Выражение1 будет выполняться если условие истинно. Выражение2 будет выполняться если условие ложно. Существует сокращенный вариант оператора: If условие выражение1 Пример. Определить, является ли введенное число днем недели, т.е. входит ли число в диапазон от 1 до 7. #include <stdio.h> int A; main() (A > 7)) Выражение условия (A<1) || (A>7) будет давать TRUE, если выполняется A<1 или A>7 - в этом случае выполняется ветка printf('Error ',A);, иначе ветка printf('OK ',A);. Существует другой вариант записи оператора If ... Пример: #include <stdio.h> main() { int y,t; printf("? "); scanf("%d",&t); y=(t>0)? t*10: t-10; /* if t>0 y=t*10 else y=t-10;*/ printf("OK %d\n",y); } В данном варианте вид оператора показан в комментариях. Оператор switch... case используется в случае, когда необходимо анализировать переменную и в зависимости от ее значения производить те или иные действия. Рассмотрим пример. С клавиатуры вводятся буквы латинского алфавиты. В зависимости от буквы произвести те или иные действия. #include <stdio.h> char A; main() { printf("? "); scanf("%c",&A); switch (A) { case 'c': printf(" smoll %c\n",A); break; /* выход из блока */ case 'F': case 'G': printf(" big %c\n",A); break; default: printf("Error %c\n",A); } } В данном примере если введен символ с, то выполняется printf(" smoll %c\n",A);, если вводится заглавные буквы F или G, то выполняется printf(" big %c\n",A);, если не один из рассмотренных символов не вводится, то выполняется printf("Error %c\n",A);. Для повторения некоторого множества команд несколько раз можно использовать оператор do... while. Рассмотрим пример. #include <stdio.h> main() { int A; do { printf("Zifra? "); scanf("%d",&A); printf("Error %d\n",A); } while (!(A == 9)); printf("OK %d\n",A); } С клавиатуры вводится число. Выполняется оператор printf("Error %d\n",A);. Далее идет анализ - равно число 9 или нет, если не равно, снова выполняется тело цикла: printf("Zifra? "); scanf("%d",&A); printf("Error %d\n",A). Если число равно 9, то выполняется оператор printf("OK %d\n",A); и работа цикла заканчивается. Главной особенностью оператора do... while является тот факт, что тело цикла, заключенное между операторами do и while выполняется хотя бы один раз, т.е. вначале выполняется тело цикла, а затем идет анализ условия. Таким образом, смысл рассматриваемого оператора заключается в следующем: "Выполняй тело цикла до тех пор, пока истинно условие". Оператор while... в отличие от do... while вначале анализирует условие, а затем выполняет тело цикла. Пример. #include <stdio.h> main() { int A; A = 0; while (A != 9) { printf("Zifra? "); scanf("%d",&A); printf("Error %d\n",A); } printf("OK %d\n",A); } В данном примере инициализирована переменная A:=0;. Это сделано, потому что вначале идет анализ равна она 9 или нет. Если не равна, то выполняется тело цикла. Смысл рассматриваемого оператора заключается в следующем: «Пока истинно условие выполняй тело цикла». Оператор for... используется, когда известно сколько раз необходимо выполнить тело цикла, но этот оператор горазда гибче по сравнению с Паскалем. Рассмотрим пример. #include <stdio.h> int A; main() { for (A = 1; A <= 5; A++) /* A++ означает A=A-1 */ printf("Zifra %d\n",A); } В этом примере A хранит состояние счетчика цикла. Первоначально A:=1. Выполняется оператор printf("Zifra %d\n",A). Далее значение A увеличивается на единицу. Идет анализ A<=5 или нет. Если A больше 5, то цикл заканчивает работу. Если нет, то снова выполняется оператор printf("Zifra %d\n",A). В следующем примере рассмотрим вариант оператора for..., когда начальное значение переменной больше конечного, а переменная во время работы цикла уменьшается на единицу. #include <stdio.h> int A; main() { for (A = 5; A >= 1; A--) /* A-- означает A=A-1 */ printf("Zifra %d\n",A); } Существует множество модификаций оператора for..., например: - пустой оператор - для временной задержки: for (n=1;n <=10000;n++) ; /* пустой оператор */ - использование различного шага: for (n=1;n <=230;n=n+10) - изменение переменных: for (x=2;n*n <=476;n=5*x++) Рассмотрим пример, в котором инициализируются две переменные и каждая из которых, изменяется после итерации цикла: #include <stdio.h> #define f 30 #define n 19 main() { int y,t; for (y = 1,t=f; y<=16; y++,t+=n) /*t+=n означает t=t+n*/ printf(" %3d %7d\n",y,t); } Далее рассмотрим, операторы ветвления (операторы перехода из одной части программы в другую). Оператор goto позволяет передавать управление на любую строку программы. Для этой цели используется метка. Пример. #include <iostream.h> #include <stdio.h> main() { char A; label_1:/* метка */ printf("? \n"); cin>>A; if (A != 'y') goto label_1; } Для прерывания цикла по некоторому условию можно использовать оператор break. Пример. #include <stdio.h> #include <iostream.h> char A; int I; main() { for (I = 1; I <= 10; I++) { printf("? \n"); cin >>A; i (A == 'y') break; } } Для прерывания итерации цикла и перехода к следующей итерации используется оператор Continue. Пример. #include <stdio.h> #include <iostream.h> char A; int I; main() { for (I = 1; I <= 10; I++) { printf("? \n"); cin >>A; if (A == 'y') continue; printf("Работает %c\n",A); } } Для прерывания программы также используются операторы return() и exit(). 1.6. Препроцессор Препроцессор языка Си позволяет перед началом трансляции включать в программу фрагменты программ, написанных отдельно от основной. Директива #define. Директива #define может появляться в любом месте программы, а даваемое ею определение имеет силу от места до конца программы. #include <iostream.h> #include <stdio.h> #define TRI 3 #define OTWET TRI*TRI #define OT printf("ОТВЕТ равен %d.\n",OTWET) #define jd cin >>C; main( ) { int C; OT; jd; } После выполнения программы получится: ОТВЕТ равен 9 В первой строке программы TRI - это макроопределение и оно равно 3, где 3 - строка замещения. Во второй строке макроопределение OTWET имеет строку замещения TRI*TRI и т.д. Каждая строка состоит из трех частей. Первой стоит директива #define, далее идет макроопределение. Макроопределение не должно содержать внутри себя пробелы. И, наконец, идет строка (называемая "строкой замещения"), которую представляет макроопределение. Когда препроцессор находит в программе одно из макроопределений, он заменяет его строкой замещения. Этот процесс прохождения от макроопределения до заключительной строки замещения называется "макрорасширением". Директива #include. Когда препроцессор "распознает" директиву #include, он ищет следующее за ней имя файла и включает его в текущую программу. Директива бывает в двух видах: #include<stdio.h> имя файла в угловых скобках #include "my.h" имя файла в двойных кавычках Угловые скобки сообщают препроцессору, что файл следует искать в одном или нескольких стандартных системных каталогов. Кавычки говорят ему, что сначала нужно смотреть в рабочем каталоге, а затем искать в "стандартных" местах. Директивы: #undef, #ifdef, #else, #endif Эти директивы позволяют приостановить действие более ранних определений. Директива #undef отменяет самое последнее определение поименованного макроопределения. #define TRI 3 #define F 5 #undef TRI /* TRI теперь не определен */ #define F 10 /* F переопределен как 10 */ #undef F /* F снова равен 5 */ #undef F /* F теперь не определен */ Рассмотрим еще пример. #ifdef OTW #include "otw.h" /* выполнится, если OTW определен */ #define ST 10 #else #include "w.h" /* выполнится, если OTW не определен */ #define ST 20 #endif Директива ifdef сообщает, что если последующий идентификатор OTW определяется препроцессором, то выполняются все последующие директивы вплоть до первого появления #else или #endif. Когда в программе есть #else, то программа от #else до #endif будет выполняться, если идентификатор не определен. 1.7 Программы. Функции Как мы рассматривали раньше, программа на Си имеет корневой сегмент, начинающийся с директив препроцессора и ключевого слова main. Далее идет собственно программа, начинающаяся с открывающейся фигурной скобки { и заканчивающаяся закрывающейся фигурной скобкой }. Часто используемые участки программы выделяются в функции. Каждая функция также начинается с директив препроцессора и имени и скобок { }. Рассмотрим пример программы рисования лестницы. #include <stdio.h> main() А теперь напишем эту программу с использованием функции Lestniza. #include <stdio.h> Lestniza(void) main() { Lestniza(); Lestniza(); Lestniza(); } Как видно из программы, обращение к функции осуществляется три раза. Для преодоления этого недостатка переработаем программу и введем формальные и фактические аргументы: #include <stdio.h> int C; Lestniza(int B)/* B - формальный аргумент */ main() { Lestniza(3); /* 3 -фактический аргумент */ } В данной функции B является формальным аргументом (конечная величина оператора for to). Для присвоение ей конкретного значения используется фактический аргумент, который передается функции при ее вызове в основной программе. Если в функцию передается несколько параметров, то они должны передаваться в том порядке, в каком записаны в функции. Рассмотрим функции, возвращающее свое значение на примере возведения числа в квадрат. #include <stdio.h> float Kwadrat(float A) { return A * A; } float B; main() { printf("? \n"); scanf("%f",&B); printf("Kwadrat = %8.2f\n",Kwadrat(B)); } Как видно из примера - имя функции Kwadrat - она вычисляет квадрат числа. В строке printf("Kwadrat = %8.2f\n",Kwadrat(B)); эта функция вызывается - на вход подается значение (введенное число), а в результате получаем результат - квадрат числа, который возвращается в программу по команде return. Рассмотрим еще один вариант работы с функцией, возвращающей значение без команды return. #include <stdio.h> Kwadrat(float A, float *B) { *B = A * A; } float C, D; main() { printf("? \n"); scanf("%f",&C); Kwadrat(C,&D); printf("Kwadrat = %8.2f\n",D); } 1.8. УказателиУказатель - это переменная, содержащая адрес данных, а не их значение. Указатель используется: 1.Для связи независимых структур друг с другом. 2.Для динамического распределения памяти. 3.Для доступа к различным элементам структуры. Рассмотрим следующую программу: #include <stdio.h> main() { int Z,*Y; Y =&Z; Z = 100; printf("Прямое значение Z: %d\n", Z); printf("Значение Z, полученное через указатель: %d\n",*Y); printf(" Адрес Z через получение адреса: %p\n",&Z); printf("Адрес Z через указатель: %p\n", Y); } В данном примере Y указатель на целую переменную и содержит ее адрес. В свою очередь & позволяет получить адрес по которому размещено значение переменной Z. В этой программе: - адрес переменной Z присваивается Y; - целое значение 100 присваивается Z; - оператор &, позволяет получить адрес, по которому размещено значение Z. Результат работы программы: Прямое значение Z: 100 Значение Z, полученное через указатель: 100 Адрес Z через получение адреса: 85B3:0FDC Адрес Z через указатель: 85B3:0FDC Указатели также используются для оптимального распределения памяти. Рассмотрим пример указателя на число типа char. #include <stdio.h> #include <alloc.h> #include <string.h> #include <stdlib.h> #include <process.h> int main(void) { char *str; /* указатель на символьную переменную */ str = (char *)malloc(10); strcpy(str, "Hello"); printf("String is %s\n", str); free(str); return(0); } Вначале по команде char *str; создан тип str, который является указателем на переменную типа char(* обозначает "указатель"). По команде str = (char *)malloc(10); выделяем 10 байт памяти под переменную str(типа строка). По команде strcpy(str, "Hello"); осуществляется - "записать в область памяти, на которую указывает str, строку символов "Hello". По команде printf("String is %s\n", str); осуществляется "напечатать на экране то, на что указывает str. Команда free(str); освобождает память, на которую указывает str. Рассмотрим более сложный пример получения доступа к записи, используя указатель. #include <stdio.h> #include <string.h> #include <alloc.h> #include <process.h> struct Student { /* запись Student */ char Fio[30]; /* поле записи Fio */ int Gruppa; /* поле записи Gruppa */ }; /* конец записи */ struct Student *A; main() { if ((A =(Student *) malloc(sizeof(Student))) == NULL) { printf("Нет памяти\n"); exit(1); } strcpy(A[1].Fio, "Ivanov"); A[1].Gruppa=385; printf("Fio1 %s\n Gruppa %d\n", A[1].Fio, A[1].Gruppa); strcpy(A[2].Fio, "Petrow"); A[2].Gruppa=386; printf("Fio2 %s\n Gruppa %d\n", A[2].Fio, A[2].Gruppa); free(A); } Указатель также может использоваться для получения косвенного указателя на структуру. Пусть poit является указателем на структуру и что elem элемент, определенный структурным шаблоном. Тогда point->elem определяет элемент, на который выполняется ссылка. Рассмотрим предыдущий пример. struct Student { /* запись Student */ char Fio[30]; /* поле записи Fio */ int Gruppa; /* поле записи Gruppa */ }; /* конец записи */ Student *poin; Сейчас к полям структуры мы можем обращаться несколькими способами. Эквивалентные обращения: Student.Gruppa=236; poin->Gruppa=236; Отметим одну важную особенность указателей в Си. Транслятор автоматически учитывает тип указателя в арифметических действиях над ним. Например если i есть указатель на целую (т.е. двухбайтную) переменную, то действие типа i++ означает, что указатель получает приращение не один, а два байта, т.е. будет указывать на следующую переменную или элемент массива. По этой причине указатель можно использовать вместо индексов массива. Например если A - указатель на массив целого типа, то вместо A[i] можно писать *(A+i). Более того, использование указателей вместо индексов позволяет компилятору создавать более компактный и быстрый код. 1.9 Указатели и функции Указатели также можно использовать в качестве формальных параметров функции. Рассмотрим пример. Функция swap (обмен) объявляет два формальных параметра x и y, как указатели на данные типа int. Это означает, что функция swap работает с адресами целых переменных (а не с их значениями). Поэтому будут обработаны данные, адреса которых переданы функции во время обращения к ней. Функция main(), вызывает swap. #include <stdio.h> swap(int *x, int *y) { int wr; wr = *x; *x = *y; *y =wr; } main() { int i,j; i = 100; j = 200; printf("Было: i=%d, j=%d\n",i,j); swap(&i,&j); printf("Стало: i =%d, j=%d\n",i,j); } После выполнения программы значения i и j поменяются местами. Необходимо отметить, что хотя указатели и экономят память, но они используют время процессора горазда больше. Таким образом, рассмотрены основные элементы языка Си. 1.10 ФайлыФайл - это совокупность данных, записанных на каком-либо носителе. Файл можно создать, записать в него данные, стереть данный, обновить данные, дополнить данными. Ввод-вывод в файл осуществляется с помощью методов прямого или последовательного доступа. Расмотрим сперва режим последовательного доступа. Ввод-вывода буферизован. Это означает, что программа пишет и читает в буфер; обмен данными между буфером и файлом происходит в случае, если буфер полон, или произошло закрытие файла, или перед выходом из программы. Рассмотрим пример: считать данные из одного файла и переписать в другой. #include <stdio.h> main() { FILE *in, *out; char n1[8], n2[8]; printf("Исходный файл\n"); gets(n1); printf("Выходной файл\n"); gets(n2); if ((in = fopen(n1, "rt"))== NULL) { printf("Не могу открыть исходный файл\n"); return 1; } if ((out = fopen(n2, "wt"))== NULL) { printf("Не могу открыть выходной файл\n"); return 1; } while (!feof(in)) fputc(fgetc(in), out); fclose(in); fclose(out); } Строка FILE *in, *out; определяет указатель на два файла. Имя файла м.б. любым- в нашем случае in - исходный файл, out - выходной. В следующей строке char n1[8], n2[8]; определяем две переменные n1 и n2 для хранения имен файлов. Следующие четыре строки позволяют ввести имена входного и выходного файла и присвоить эти имена переменным n1 и n2. Прежде чем начать работать с файлом он должен быть открыт. Для этого существует функция fopen() в которой первый параметр содержит имя файла, а второй - вид работы, например "rt"– читать файл. Команда in = fopen(n1, "rt" вызовет открытие файла, запомненного в переменной n1 на чтение, и в программе будет возвращен указатель in на этот файл, которым (указателем) мы и будем пользоваться при чтении символов из файла. Если файл не существует, то значение fp будет NULL, произойдет выполнение fprintf(stderr, "Не могу открыть выходной файл\n"); return 1; и программа завершит работу. Аналогично работает функция out = fopen(n2, "wt", только теперь out - выходной файл, а вид работы "wt" -запись в файл). По этой команде создается файл под именем, записанным в переменной n2. Чтение из файла осуществляется вызовом fgetc(in). Читается один символ из файла, связанного с указателем in. По команде fputc(fgetc(in), out); считанный символ записывается в файл out. Для чтения информации из всего файла используется конструкция while (!feof(in)) fputc(fgetc(in), out);. Функция feof(in) возвращает величину, отличную от нуля, если находится в конечной позиции, и ноль - в противном случае. Пока не встретится ноль, данные из исходного файла читаются и записываются в выходной. Закрытие файла происходит при вызове функции fclose(). Если при этом файл был открыт на запись, происходит вывод содержимого буфера, связанного с этим файлом. Связь между указателем и файлом разрывается. Аналогично функция fgetc(string,n,fp) читает из файла, связанного с fp, строку и помещает ее в string. Символы читаются, пока не будет получен символ '\n', или пока не исчерпается файл, или пока не будет прочитано (n-1) символов. Режим прямого доступа более гибок, т.к. позволяет обращаться напрямую к любой записи файла. Минимальной записью для файла прямого доступа является байт. Ниже будет рассмотрен случай файла прямого доступа с записью равной байту. Такие файлы называются двоичными. Файлы прямого доступа незаменимы при написании программ, которые должны работать с большими объемами информации, хранящимися на внешних устройствах. В основе обработке СУБД лежат файлы прямого доступа. Кратко изложим основные положения работы с файлами прямого доступа. 1). Каждая запись в файле прямого доступа имеет свой номер. Записи нумерются от 0 до N-1, где N - количество записей в файле. Для двоичного файла N совпадает с длиной файла в байтах. Для открытого файла одна из записей является текущей - говорят, что указатель установлен на данную запись. Перемещать указатель можно при помощи функции lseek. 2). При открытии (или создания файла) указатель автоматически помещается на начало (запись 0). При выполнении операций чтения или записи указатель автоматически перемещается за последнюю считанную (записанную запись) запись. 3). Изменить размер файла (увеличить или урезать) можно при помощи функции chsize. При увеличении размера файла к нему добавляются записи, заполненные кодами 0. Ниже представлена программа, демонстрирующая работу с файлами. #include <stdio.h> #include <io.h> #include <string.h> #include <fcntl.h> #include <SYS\STAT.H> void main() { int h; /*дескриптор создаваемого файла*/ char * s="Эта строка будет помещена в файл"; char buf[7]; /*буфер для чтения из файла*/ _fmode=O_BINARY; /*работаем с двоичными файлами*/ if((h=creat("proba.txt",S_IREAD |S_IWRITE))==-1) /*создать файл*/ { printf("Не удалось открыть файл!\n"); exit(1); } write(h,s,strlen(s)); /*записать в файл строку s*/ lseek(h,4,SEEK_SET); /*четвертый байт от начала файла*/ read(h,buf,6); /*читать слово 'строка' (6 байт) из файла*/ buf[6]=0; /*отмечаем конец строки*/ close(h); /*закрыть файл*/ printf("%s\n",buf); /*печать считанной строки*/ } Наша программа достаточно полно прокоментирована, поэтому мы приводим достаточно краткие пояснения. Программа создает файл прямого доступа и записывает туда последовательность байт (строку). Далее происходит прямое обращение к подстроке этой строки непосредственно в файле. При разборе текста программы обратим читателей на некоторые моменты: 1.Обработка ошибки создания файла. Действительно, открытие файла может быть не удачным и в хорошая программа должна обрабатывать такие ситуации. 2. При удачном открытии файла ему отводится дескриптор (уникальное число), по которому затем можно к файлу обращаться. 3. Наконец не забывайте, что строка только тогда станет строкой, когда в конце стоит код \0. В заключении нашего рассмотрения файлов отметим, что наше изложение касалось работы с файлами в среде MS DOS. Работа с файлами в ОС Windows несколько отличается от рассмотренного и основывается на использовании специальных функций API (о программировании в Windows см. ниже). 1.11 Дополнительные функции Си1.11.1 Функции преобразования Имеется переменная С: ФУНКЦИЯ ПРОВЕРЯЕТ, ЯВЛЯЕТСЯ ЛИ C isalpha(c) буквой isdigit(c) цифрой islower(c) строчной буквой isspace(c) пустым символом (пробел, табуляция или новая строка) isupper(c) прописной буквой isalnum(c) алфавитноцифровым (буква или цифра) isascii(c) кодом ASCII (0-127) iscntrl(c) управляющим символом ispunct(c) знаком пунктуации toupper(c) преобразует c в прописную букву tolower(c) преобразует c в строчную букву Пример: преобразует букву A из прописной в строчную a. #include <stdio.h> #include <ctype.h> main() { char Z,Y='A'; printf("Было %c\n ",Y); Z=tolower(Y); printf("Стало %c\n ",Z); } 1.11.2 Преобразования символьных строк: atoi(), atof() При вводе информации иногда целесообразнее ввести строку символов, а затем преобразовать в соответствующее число. Для этого используются функции atoi() и atof(). Первая преобразует строку в целое, вторая - в число с плавающей точкой. Пример: #include <stdlib.h> #include <stdio.h> main() { char ch[10]; int integ; puts("Введите, пожалуйста, целое"); gets(ch); integ=atoi(ch); printf("Число было %d.\n", integ); } 1.11.3 Функции, работающие со строками strlen()- находит длину строки; strcat()- объединяет две строки; strcmp()- сравнивает содержимое строк; strcpy()- копирует строки. Рассмотрим пример. #include <string.h>#include <stdio.h>main(){char k[60]="Есть в дожде откровенье";char l[20]="Потаенная нежность";printf("Длина 1 строки= %d\n ",strlen(k));strcat(k,l);puts(k);}В результате выполнения программы получится: Длина 1 строки= 24. Есть в дожде откровенье, Потаенная нежность.В данном примере функция strlen(k) определяет длину строки k. Функция strcat(k,l) присоединяет к концу строки k строку l. 1.12 Особенности программирования на языке Си 1.12.1 Модули. Многомодульное программирование Можулем будем называть часть программы помещенная в отдельный файл и транслируемая независимо от других частей программы. Т.о. часть программы подключаемая при помощи препроцессорной команды include, согласно нашему определению, модулем являтся не будет. Напомним читателю некоторые стандартные положения трансляции программ: 1. Стандартный процесс трансляции состоит из двух этапов: собственно трансляции и редактирования связей. 2. На первой стадии трансляции текстовый файл, содержащий фрагмент программы преобразуется к объектному виду. 3. На второй стадии трансляции все объектные модули (в том числе модули, содержащиеся в стандартных и других библиотеках) объединяются в один исполняемый модуль. На этой стадии происходит согласование используемых внешних переменных и внешних функций. На первый взгляд кажется, что двухэтапный процесс трансляции усложняет и замедляет разработку программного обеспечения. Однако, разбивая программу на модули мы можем перевести их в объектный формат и далее подсоединять их только на втором этапе трансляции, что, в конечном итоге, ускоряет процесс трансляции. Кстати, все стандартные функции, которыми Вы пользуетесь в своих программах содержаться, в стандартных библиотеках объектных модулей. Описание же библиотечных функций содержится в H-файлах. Заметим, что при любом количестве модулей один и только один будет содержать функцию main. Этот модуль мы будем называть главным. Современные средства трансляции позволяют легко разрабатывать многомодульные программы. Работая с Borland С вы можете либо включить все модули в проект (если Вы работаете с интегрированной средой) либо указать их в командном файле (если Вы работаете ) со строковым компилятором. При этом на первом месте должен стоять главный модуль. При этом, если Вы указали модуль с расширением .obj, то он будет учавствовать только во второй стадии трансляции, в противном случае он каждый раз будет перекомпилироваться. Стандартные библиотеки в проекте указывать не надо - система автоматически подключает их на втором этапе трансляции. При разработке программного обеспечения разбиение на модули осуществляется тематически. Например, в один модуль помещаются все функции работающие с файлами, во второй - функции, осуществляющие математические расчеты и т.д. Заканчивая общетеоретическое рассмотрение модульного программирования заметим, что в нем имеется еще один положительный момент. Дело в том, что двух-этапный процесс трансляции и структура объектных файлов являются стандартом для многих трансляторов с языков высокого уровня, а также ассемблеров. Т.о. появляется возможность собирать программы, модули которых были написаны на разных языках. Ниже нами будет приведен пример использования модулей, написанных на языке ассемблера. Приведем пример двухмодульной программы. /*Модуль 1, главный*/ #include <stdio.h> extern int min(int, int, int); /*находит минимальное значение из 3*/ extern int max(int, int, int); /*находит максималное значение из 3*/ void main () { int a,b,c; a=2; b=10; c=14; /*напечатать произведение минимального и максимального числа*/ printf("%d\n", max(a,b,c)*min(a,b,c)); /*ответ 28*/ } /*Модуль 2*/ #include <stdio.h> extern int min(int, int, int); //находит минимальное значение из 3 extern int max(int, int, int); //находит максималное значение из 3 int max(int a1, int b1, int c1) { if(a1>b1) { if(c1>a1)return c1; else return a1; } else { if(b1>c1)return b1; else return c1; } } int min(int a1, int b1, int c1) { if(a1<b1) { if(c1<a1)return c1; else return a1; } else { if(b1<c1)return b1; else return c1; } } Кратко прокоментируем приведенную выше программу. Программа состоит из двух модулей. В главном модуле содержаться вызовы функций, которые содержаться во втором модуле. Как видно из текста программы при описании функций мы используем ключевое слово extern. Пусть первая программа называется modul1, а вторая modul1. Тогда после первого этапа трансляции на диске появятся объектные модули modul1.obj и modul2.obj. На втором этапе трансляции происходит объединение этих модулей и на диске появляется исполняемый модуль modul1.exe. 1.12.2 Модели памяти Обратимся теперь к одной весьма важной проблеме, с которой рано или поздно сталкивается любой программист программирующий на Си в операционной системе MS DOS. Эта проблема называется - выбор модели памяти. Выбор модели памяти можно осуществить отметив соответствующие опции в интегрированной среде обработки или или указав соответствующие параметры для строкового компилятора. Вопрос заключается в том: что означает выбранная модель памяти. Чтобы разобраться в указанной проблеме прежде всего следует обратиться к тому как осуществляется адресация памяти на компьютерах IBM. Прежде всего заметим, что микропроцессор Intel может работать в двух режимах реальном и защищенном, отличающихся в том числе и системой адресации. Операционная система MS DOS работает в реальном режиме (или в имитированном реальном режиме). Начнем, поэтому, с адресации в реальном режиме. Поскольку первые микропроцессоры Intel были 16-битные то для того, чтобы расширить объем адресуемой памяти, адрес ячейки памяти формируется из двух компонент. Обозначим первую компоненту как seg и назовем ее сегментным адресом. Вторую компоненту обозначим ofs и назовем ее смещением. Тогда физический адрес ячейки может быть найден по формуле: seg*16+ofs. Поскольку размер регистров составляет 16 бит, то имеем, что максимальный возможный адрес составляет (2^16-1)*16+(2^16-1). Т.е. объем охватываемой памяти оказывается равным приблизительно 1 Мб. Заметим, что при фиксированном seg смещение позволяет адресовать 64 Кб памяти. В результате вся память разбивается на сегменты. Максимальный размер сегмента составляет 64 Кб. Адрес начала сегмента всегда кратен 16. Такую сегментную структуру приходится учитывать и при написании программы. Традиционно в программе можно выделить три компонента: код, данные, стек. Для каждой из этих компонент должно быть выделено определенное количество сегментов. Для сегментации сегментов в микропроцессоре Intel существует 4 регистра называемых сегментными: cs - регистр сегмента кода, ds - регистр сегмента данных, ss - регистр сегмента стека, es - дополнительный сегментный регистр. Ранее было введено понятие указателя. До сих пор мы пользовались этим понятием не задумываясь о их типе. Этот тип устанавливается по умолчанию согласно модели памяти (см. ниже). В Си существует 3 типа указателя: NEAR, FAR и HUGE. Указатель NEAR - соответствует смещению в текущем сегменте. Длина его 16 бит. Указатель FAR - 32-битный указатель, точнее пара SEG,OFS. Легко видеть, что один и тот же физический адрес может быть представлен несколькими парами: seg,ofs. Кроме того сравнение и действия над указателями касается только смещения. Т.о. два указателя относящиеся к одной и той же ячейки памяти оказываются не равными друг другу. Указатель HUGE - 32-битный указатель. Отличается от FAR тем, что он нормализован - из всех пар seg,ofs выбрана пара с минимальным ofs. Такой вид указателя называется нормализованным. Перейдем теперь непосредственно к рассмотрению моделей памяти, коих существует ровно 6. Модель tiny (крохотная). Все сегментные регистры (cs,ds,ss,es) указывают на один адрес. Т.о. у Вас для всего (кода, данных, стека) всего 64 Кб. памяти. Используются только указатели типа near. Программы написанные в этой модели могут быть преобразованы к COM-виду. Модель small (малая). Программный сегмент и сегмент данных начинаются с разных адресов. Т.е. для кода и для стека отводится по 64 Кб. Сегмент стека начинается с того же адреса, что сегмент данных. Используются только указатели near. Модель medium (средняя). Может иметь несколько сегментов кода, но один сегмент данных. Другими словами указатели типа far определены только для сегментов кода. Сегмент стека начинается с адреса сегмента данных. Модель compact (компактная). Может иметь несколько сегментов данных (один для статических данных), но один сегмент кода. Другими словами указатели типа far определены для сегментов данных (и стека). Стек имеет свой собственный сегмент. Модель large (большая). Может иметь несколько сегментов кода и данных. Для стека, как и в предыдущем случае, имеется свой сегмент. Используется указатель типа far. Для статических данных отводится один сегмент. Модель huge (огромная). Совпадает с предыдущей, но снимается ограничение на количество сегментов для статических данных. Предыдущий материал показывает какие указатели используются по умолчанию в той или иной модели памяти. Однако используя модификаторы near, far и huge можно изменить тип указателя задаваемого по умолчани. 1.12.3 Программирование в DOS и Windows До сих пор мы не акцентировали Ваше внимание на то в какой операционной системе мы работаем. Для начала программирования на Си это не имеет большого значения. Однако теперь мы можем сказать, что настоящее программирование в Windows начнется только с Главы 4. В данном параграфе мы поговорим о принципиальных особенностях программирования в среде MS DOS и Windows. Значительная часть времени в программировании уходит на про-граммирование внешних устройств. Причем под внешними устройствами понимается и работа с памятью, файловой системой, дисплеем, клавиатурой, мышью и т.д. Основным отличием операционной системы Windows от MS DOS является то, все управление всеми внешними устройствами Windows берет на себе. Ниже на рисунке представлена схема взаимодействия приложения с внешними устройствами в системах MS DOS и Windows Беря на себя взаимодействие с внешними устройствами Windows позволяет создавать более надежное и совместимое программное обеспечение. Вторым преимуществом операционной системы Windows является ее многозадачность. Все задачи, запускаемые в ОС оказываются совершенно равноправными по отношению к рессурсам микропроцессора. Замечательно и то, что многозадачность возможна и в рамках одной задачи, когда две функции могут выполняться параллельно и независимо друг от друга. Еще одной особенностью програмирования в среде Windows является присутствие только одной модели памяти. в Windows используется так называемая линейная или плоская модели памяти. Суть этой модели заключается в том, что содержание всех сегментных регистров фиксируется, а адресация осуществляется с помощью 32-битных регистров. Такая модель основывается на так называемой страничной адресации в защищенном режиме. Для программирования это дает значительные преимущества, заключающиеся в том, что поскольку сегментом теперь является вся память, то снимаются все ограничения на размер кода, данных, стека и объема отводимого под локальные переменные. К особенностям программирования в Windows мы еще вернемся в Главе 4, а пока познакомимся с тем, как можно непосредственно использовать системные вызовы в программах для операционной системы MS DOS. Ниже приведена программа, которая для печати строки использует системный вызов (так называемое 21-е прерывание). #include <stdio.h> #include <dos.h> void main() { char *s="Печать с помощью системного вызова "; struct REGPACK r; r.r_ax=0x0200; /*функция 2, 21-его прерывания*/ r.r_ds=FP_SEG(s); r.r_bx=FP_OFF(s); while (*(char *)MK_FP(r.r_ds,r.r_bx)!=0) { r.r_dx=*(char *)MK_FP(r.r_ds,r.r_bx); intr(0x21,&r); r.r_bx++; } } Прокоментируем программу. В программе используется функция INTR, позволяющая осуществлять системные вызовы операционной системы MS DOS. Эти системные вызовы называются прерываниями и имеют свой номер. Наиболее часто используемое прерывание имеет номер 21 (шестнадцатиричный). В нашей программе мы используем функцию 2 этого прерывания, которая позволяет печатать один символ, код которого помещен в регистр DX. Для работы с регистрами используется предопределенная структура REGPACK. Особо обращаем внимание на функции FP_SEG, FP_OFF, MK_FP. FP_SEG и FP_OFF позволяют получать по указателю сегментный адрес и смещение. Функция MK_FP наоборот формирует указатель исходя из сегментного адреса и смещения. Напоминаем, что на конце строки стоит символ с кодом 0, на чем и основывается выход из цикла. 1.12.4 Использование языка ассемблера в программах на Си Для оптимизации программ часто используют язык ассемблера (далее просто ассемблер). Поскольку этот язык практически в чистом являет собой язык микропроцессора, то получаемый с помощью него код весьма компактен и выполняется гораздо быстрее кода, получаемого из фрагмента на языке высокого уровня. При работе с языком Си можно использовать как встроенный ассемблер, так и язык ассемблера во внешних модулях. Рассмотрим в начале встроенный ассемблер. Достоинством его является возможность писать ассемблерные фрагменты прямо среди фрагментов на языке Си. Основным недостатком является то, что часто встроенный ассемблер обладает меньшими возможностями реального ассемблера (отсутсвие некоторых команд, директив). Основой встроенного ассемблера является ключевое слово asm, после которого может идти или команда на языке ассемблера или блок команд, заключенных в фигурные скобки. Ниже приводится простой пример. #include <stdio.h> void main() { char * s="Печать из ассемблерного блока"; /*далее идут команды на языке ассемблера*/ asm lds bx,s asm mov ah,2 l1: asm mov dl,[bx] asm cmp dl,0 asm jz l2 asm int 21h asm inc bx asm jmp short l1 l2: } Мы намеренно взяли программу из предыдущего параграфа и переписали ее на языке ассемблера. Прокоментируем ее не вдаваясь в особенности выполнения ассемблерных команд. Для вывода символа на экран его помещают в регистр dl и вызывается функция 2 21-его прерывания. На очередной символ строки указывает регистр bx. Процесс вывода символов заканчивается когда в регистр dl попадает код 0 (конец строки). Перейдем теперь к случаю, когда к программе на языке Си подключается модуль, написанный на языке ассемблера. Подключение осуществляется на втором этапе трансляции (см. параграф 1.12.1). Ниже приведены модуль на языке ассемблера и модуль на языке Си. Причем первый содержит процедуру, вызываемую из второго. ;модуль на языке ассемблера CODE SEGMENT ASSUME CS:CODE PUBLIC _PRI_STR ;процедура будет вызываться из другого модуля _PRI_STR PROC FAR PUSH BP MOV BP,SP ;получаем адрес начала строки LDS BX,[BP+6] ;номер вызываемой функции MOV AH,2 CONT: ;очередной символ поместить в регистр dl MOV DL,DS:[BX] ;проверяем - не конец ли строки CMP DL,0 JZ _en ;вызов 21-его прерывания INT 21H ;переходим к следующему символу inc bx ;на начало цикла JMP SHORT CONT _en: POP BP ;возвращаемся в вызывающий модуль RET _PRI_STR ENDP CODE ENDS END /*Модуль на языке Си*/ #include <stdio.h> extern void far PRI_STR(char *); void main() { char * st="Печать из ассемблерного модуля."; PRI_STR(st); } Коментарий. Прежде всего, отметим, что модули должны быть согласованы по модели памяти (см. 1.12.2). Мы предполагаем, что модуль на языке Си компилируется в модели Large. В модуле на языке ассемблера согласование по модели заключается в том, что вызываемая из другого модуля процедура имеет тип Far. Оба модуля можно просто включить в проект (модуль на языке Си должен быть первым, а модуль на языке ассемблера должен иметь расширение asm) при этом для ассемблерного модуля при трансляции автоматически будет вызываться транслятор tasm.exe. Ассемблерный модуль может быть отранслирован и отдельно, тогда в проекте он должен иметь расширение obj. Второй тип согласования - согласование имен. Мы должны учесть: 1. Трансляторы Си различают заглавные и прописные буквы, поэтому вызываемая процедура должна быть написана одинаково в обоих модулях. 2. При трансляции к именам Си впереди добавляется символ подчеркивания, что следует учесть в ассемблерном модуле. Наша программа выполняет те же действия, что и предыдущая программа этого параграфа, т.е. печатает строку. Печать осуществляется процедурой PRI_STR, которой передается как параметр указатель на эту строку. Обращаем ваше внимание на то, что вызываемая процедура в ассмблерном модуле объявлена как PUBLIC, т.е. ее имя будет помещено в объектный модуль. В свою очередь в модуле на языке Си эта процедура объявлена как extern. На этом мы заканчиваем рассмотрение аспектов связанных с зыком ассемблера. Подробны об языке ассемблера и его использовании в языках высокого уровня можно найти в книге [], написанной одним из авторов этих. Глава 2. Примеры использования языка Си 2.1 Сортировка Практически каждый алгоритм сортировки можно разбить на три части: - сравнение, определяющее упорядоченность пары элементов; - перестановку, меняющую местами пару элементов; - собственно сортирующий алгоритм, который осуществляет сравнение и перестановку элементов до тех пор, пока все элементы множества не будут упорядочены. М е т о д п у з ы р ь к а ( обменная сортировкой с выбором). Идея этого метода отражена в его названии. Самые легкие элементы массива "всплывают" наверх, самые "тяжелые" - тонут. Алгоритмически это можно. Реализуется так - будем просматривать весь массив "снизу вверх" и менять стоящие рядом элементы в том случае, если "нижний" элемент меньше, чем "верхний". Таким образом, мы вытолкнем наверх самый "легкий" элемент всего массива. Теперь повторим всю операцию для оставшихся неотсортированными N-1 элементов (т.е. для тех, которые лежат "ниже" первого). #include <stdio.h> #define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; } main() { int a[10], dim=10; int i, j; for (i=0;i<dim;i++) { printf("Элемент\n"); scanf("%d",&a[i]); } printf("Было\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); /* Проход массива "вниз", начиная с нулевого элемента */ for (i = 0; i < dim; i++) /* Проход массива "вверх", начиная с последнего до i-го элемента */ for (j = dim-1; j > i; j--) /* Сравнение двух соседних элементов и их обмен */ if(a[j-1] > a[j]) swap(a[j-1], a[j]); printf("Стало\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); } С о р т и р о в к а в ы б о р о м. На этот раз при просмотре массива мы будем искать наименьший элемент, сравнивая его с первым. Если такой элемент найден, поменяем его местами с первым. Затем повторим эту операцию, но начнем не с первого элемента, а со второго. И будем продолжать подобным образом, пока не рассортируем весь массив. #include <stdio.h> #define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; } main() { int a[10], dim=10; int i, j, k; for (i=0;i<dim;i++) { printf("Элемент\n"); scanf("%d",&a[i]); } printf("Было\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); /* Проход массива, начиная с 0-го до предпоследнего элемента */ for (i = 0; i < dim-1; i++) { /* Проход массива, начиная с (i+1)-го до последнего элемента */ for (k = i, j = i+1; j < dim; j++) if(a[j] < a[k]) k = j; /* Поиск наименьшего k-го эл-та */ swap(a[k], a[i]); /* Перемещение наименьшего "вверх" */ } printf("Стало\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); } М е т о д Ш е л л а. Этот метод предложил Donald Lewis Shell в 1959 г. Основная идея алгоритма заключается в том, чтобы вначале устранить массовый беспорядок в массиве, сравнивая далеко стоящие друг от друга элементы. Как видно, интервал между сравниваемыми элементами (gap) постепенно уменьшается до единицы. Это означает, что на поздних стадиях сортировка сводится просто к перестановкам соседних элементов (если, конечно, такие перестановки являются необходимыми). #include<stdio.h> #define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; } main() { int a[10], dim=10; int i, j, gap; for (i=0;i<dim;i++) { printf("Элемент\n"); scanf("%d",&a[i]); } printf("Было\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); for (gap = dim/2; gap > 0; gap/=2) /* Выбор интервала */ for (i = gap;i < dim; i++) /* Проход массива */ /* Сравнение пар, отстоящих на gap друг от друга */ for (j = i-gap; j >= 0 && a[j] > a[j+gap]; j -= gap) swap(a[j], a[j+gap]); printf("Стало\n"); for (i=0;i<dim; i++) printf("%d\n",a[i]); } 2.2 Рекурсивные алгоритмыФункция называется рекурсивной, если ее значение для данного аргумента определяется через значения той же функции для предшествующих аргументов. В программировании функция называется рекурсивной, если последовательность операторов, составляющих тело функции, включает в себя один или несколько вызовов самой этой функции. Рассмотрим более подробно организацию и работу рекурсивных подпрограмм. Рекурсию можно использовать для вычисления факториала n!. Чтобы найти n!, нужно определить (n-1)!. А для этого необходим (n-2)! и так далее. #include <conio.h> #include <stdio.h> int z; int Fact(int n) { if (n == 1) return 1; else return Fact(n - 1) * n; } main() { int n; printf("Число? \n"); scanf("%d",&n); z = Fact(n); printf("%d",z); } 2.3 Задача Ханойские башни Легенда говорит ,– в одном из храмов Юго-Восточной Азии находятся три вертикальных стержня, на которые нанизаны 64 золотых кольца разного диаметра. Некогда бог Вишну поместил все 64 кольца на первый стержень так, что диаметр колец снизу вверх уменьшался, и повелел жрецам переместить башню из колец с первого стержня на третий, соблюдая следующее правило: на каждом шаге можно перенести самое верхнее кольцо с одного из стержней наверх другого стержня при условии, что на каждом из стержней кольца будут сохранять форму башни (т.е. их диаметр снизу вверх уменьшается). С тех пор много тысяч лет жрецы днем и ночью перекладывают кольца. Легенда гласит, что когда все кольца окажутся на третьем стержне, наступит конец света. Программа: #include <stdio.h> #include <dos.h> /* sleep() */ #define MAX_NRINGS 64 /* Максимальное число колец */ int st[4][MAX_NRINGS]; /* 1,2,3 - стержни */ int nr[4]; /* Число колец на стержнях */ int nmoves; /* Число перемещений */ /* ---------------------------------------------- */ /* Печать текущего расположения колец на стержнях */ /* ---------------------------------------------- */ void print_st(void) { int i, j; for(i = 1; i <= 3; i++) printf("\n"); } /* ------------------------------------ */ /* Установка начального положения колец */ /* ------------------------------------ */ void init(int nrings) { for(nr[1] = 0; nrings > 0; nr[1]++,nrings--) st[1][nr[1]] = nrings; * Нанизали кольца на 1-й стержень */ nr[2] = nr[3] = 0; /* Два других стержня пусты */ nmoves = 0; print_st(); } /* ----------------------------- */ /* Функция переносит одно кольцо */ /* со стержня n1 на стержень n2 */ /* ----------------------------- */ void move1(int n1, int n2) { st[n2][nr[n2]++] = st[n1][--nr[n1]]; sleep(1); /* Пауза в 1 секунду */ print_st(); /* Печать текущей позиции */ nmoves++; } /* ------------------------------------------------- */ /* Функция hanoi перемещает верхние nrings колец */ /* со стержня i1 на стержень i3, используя стержень */ /* i2 в качестве промежуточного. 1 <= i1,i2,i3 <= 3. */ /* ------------------------------------------------- */ void hanoi(int nrings, int i1, int i2, int i3) { if(nrings == 1) move1(i1, i3); else { hanoi(nrings-1, i1, i3, i2); move1(i1, i3); hanoi(nrings-1, i2, i1, i3); } } main() { int nrings; printf("Число колец: "); scanf("%d", &nrings); init(nrings); hanoi(nrings, 1, 2, 3); printf("Перенос колец завершен.\n"); printf("Число перемещений - %d.\n", nmoves); return(0); } В прложении N1 показано решение некоторых задачи на языке С. Глава 3. Основы С++ 3.1 Отличия С++ от С 1. В С++ ключевое слово void не обязательно (эквивалентно int m(); и int m(void)). 2. В С++ все функции должны иметь прототипы. 3.Если в С++ функция возвращает тип, отличный от void, то оператор return должен содержать значение типа. 4.В С++ можно выбирать место для объявления локальных переменных не только в начале блока. 5.В С++ ввод-вывод может осуществляться не только с помощью функций, но и с помощью операций. 3.2 Объектно-ориентированное программирование в С++ Развитие средств вычислительной техники требовало новых методик программирования: - программирование небольших программ на базе переключателей; - программирование на ассемблере; - программирование на языках высокого уровня (Фортран); - программирование на языках структурного программирования (Паскаль, Си); - объектно-ориентированное программирование (ООП). ООР позволяет разбить задачу на ряд самостоятельных связанных между собой подзадач, содержащих модели объектов реального мира. Каждая подзадача содержит коды и данные, относящиеся к объекту, что упрощает решение задачи в целом и позволяет решать задачи большего объема. Понятие объекта тесно связано с понятием класса. Класс – это дальнейшее развитие понятия структуры. Он позволяет создавать новые типы и определять функции, манипулирующие с этими типами. Объект - это представитель определенного класса. ООП использует механизмы инкапсуляции, полиморфизма и наследования. Инкапсуляция позволяет создавать объекты - данные, процедуры и функции, манипулирующие с этими данными. Данные, доступные для использования внутри объекта - private, данные доступные извне - public. В общем, виде объект можно рассматривать как переменную, определенную программистом. Полиморфизм позволяет одно имя функции использовать для решения разных задач (общих для класса действий). В зависимости от данных выполняются те или иные действия. Наследование позволяет одному объекту наследовать свойства другого объекта, т.е. порожденный класс наследует свойства родительского класса и добавляет собственные свойства. 3.2.1 Классы Класс используется для создания объектов. Основная форма имеет вид: class имя класса { закрытые функции и переменные public: открытые функции, функции-члены и переменные } список объектов;//не является обязательным Закрытые функции и переменные - члены(members) доступны только для других членов этого класса. Открытые функции и переменные доступны для любой части программы, в которой находится класс. Функции, объявленные внутри описания класса называются функциями членами (member functions). Для определения функций-членов используется форма: тип имя класса:: имя функции-члена (параметры) { тело функции } Два двоеточия после имени класса называются операцией расширения области видимости (scope resolution operator). Определение класса только определяет тип объектов, а сами объекты не задает), мять не выделяется). Для создания объектов имя класса используется как спецификатор типа данных. После создания объекта к открытым членам класса можно обращаться, используя операцию точка. Пример. #include <iostream.h> class class1 {//объвлен сласс class1 int a; //доступна для функций членов class1 public: int kwadrat(int b);//функция член класса class1 }; int class1::kwadrat(int b) //определение функции kwadrat() { a=b*b; return a; } main() { class1 c; //создается объект с типа class1 cout<<"\n"<<c.kwadrat(3)<<"\n";//вычисление и вывод квадрата трех return 0; } 3.2.2 Перегрузка функций Две или более функции, имеющие одно и тоже имя называются перегружеными. Обычно функции отличаются количеством и типом аргументов. Транслятор автоматически на основании количества или типов аргументов выберет правильный вариант. Пример. #include <iostream.h> void k(int a);//прототип первой функции void k(int a, float b); //прототип второй функции void k(int a) //описание первой функции { cout << a <<"\n"; } void k(int a, float b) //описание второй функции { cout <<a<<"\n"<< b <<"\n"; } main() { k(4);//вызов первой функции k(5, 10.2);//вызов второй функции return 0; } 3.2.3 Конструкторы Для автоматической инициализации создаваемых объектов в С++ используется функция - конструктор (constructor function), которая включается в описание класса. Функция конструктор имеет тоже имя, что и класс и не возвращает ни какого значения. Пример: #include <iostream.h> // Объявление класса class1 class class1 { int a; public: class1(); // Конструктор void kwadrat(); }; // Инициализация а конструктором при создании объекта pr class1::class1() { a=100; } //Функция возведения в квадрат и печати void class1::kwadrat() { cout << a*a; } main() { class1 pr;//Создание объекта pr pr.kwadrat(); //Вызов функции kwadrat return 0; } Как видно из примера конструктор вызывается при создании объекта pr. 3.2.4 Деструкторы Функция деструктор (destructor)вызывается при удалении объекта для освобождения ресурсов (памяти и т.д.). Она также включается в объявление класса. Перед описанием деструктора ставится значок ~. Пример. #include <iostream.h> // Объявление класса class1 class class1 { int a; public: class1(); // Конструктор ~class1(); //Деструктор void kwadrat(); }; // Инициализация а конструктором при создании объекта pr class1::class1() { a=100; } //Освобождение ресурсов деструктором class1::~class1() { cout<<"Освобождение\n"; } //Функция возведения в квадрат и печати void class1::kwadrat() { cout << a*a<<"\n"; } ain() class1 pr;//Создание объекта pr pr.kwadrat(); //Вызов функции kwadrat return 0; } Деструктор вызывается при удалении объекта или выхода из программы. 3.2.5 Конструкторы с параметрами Конструктору можно передать параметры. Для этого нужно добавить необходимые параметры в объявление и определение конструктора. Затем при объявлении объекта параметры задаются в качестве аргумента. Пример: #include <iostream.h> class myclass { int a; public: myclass(int x); // конструктор void show(); }; myclass::myclass(int x) { cout << "В конструкторе\n"; a = x; } void myclass::show() { cout << a << "\n"; } main() { myclass ob(4); ob.show(); return 0; } Конструктор myclass имеет один параметр. Значение, передаваемое в myclass() используется для инициализации а. Аргумент 4 передается в ob(4) в качестве аргумента. Деструктор в отличие от конструктора параметров не имеет. В данном примере конструктору мы передавали константы, но так же можно передавать переменные: Пример: include <iostream.h> class myclass { int i, j; public: myclass(int a, int b); void show(); }; myclass::myclass(int a, int b) { i = a; j = b; } void myclass::show() { cout << i << ' ' << j << "\n"; } main() { int x, y; cout << "Введите два целых: "; cin >> x >> y; // использование переменных для создания ob myclass ob(x, y); ob.show(); return 0; } В программе рассмотрено важное свойство объектов. Они могут создаваться по мере необходимости. 3.2.6 Введение в наследование Наследование - это механизм посредством которого один класс (производный) может наследовать свойства другого класса (базового). Базовый класс определяет все качества, которые являются общими для всех прпоизводных классов. Пример: //Базовый класс class B { int i; public: void set_i(int n); int get_i(); }; //Производный класс D class D : public B { int j; public: void set_j(int n); int mul(); }; После имени класса D стоит двоеточие, за которым стоит ключевое слово public и имя класса B. Это означает, что класс D будет наследовать все компоненты класса B. Само ключевое слово public информирует компилятор о том, что т.к. B будет наследоваться, то все открытые элементы базового класса будут открытыми элементами производного класса. Однако все закрытые элементы базового класса остаются закрытыми. Пример: // Простой пример наследования. #include <iostream.h> // Задание базового класса class base { int i; public: void set_i(int n); int get_i(); }; // Задание производного класса class derived : public base { int j; public: void set_j(int n); int mul(); }; // Установка значения i в базовом классе void base::set_i(int n) { i = n; } // Возврат значения i в базовом классе int base::get_i() { return i; } // Установка значения j в производном классе void derived::set_j(int n) { j = n; } // Возврат значения i из base и, одновременно, j из derived int derived::mul() { // производный класс может вызывать функции-члены базового класса return j * get_i(); } main() { derived ob; ob.set_i(10); // загрузка i в base ob.set_j(4); // загрузка j в derived cout << ob.mul(); // вывод числа 40 return 0; } Важно! При определении mul() вызывается функция get_i()- базового класса B, а не производного D, что указывает на то, что открытые члены базового класса становятся открытыми членами производного. Но в функции mul() вместо прямого доступа к i, необходимо вызывать get_i(), потому что закрытые члены базового класса(i) остаются закрытыми для производных классов. 3.2.7 Виртуальные функции Кратко проблема может быть сформулирована следующим образом: как будет вызываться функция производного класса, имеющая такое же название, что функция базового класса. Рассмотрим следующий пример. #include <stdio.h> class base { public: int i; base(int x); //конструктор void func() { printf("Базовая функция %d",i); return; }; }; //текст конструктора base::base(int x) { i=x; return; }; class der1: public base { public: der1(int x) :base(x) {}; //конструктор void func() { printf("Функция из производного класса %d", i*i); return; } }; main() { base * pc; //указатель на базовый класс base ob(2); //создать экземпляр объекта базового класса der1 ob1(2); //создать экземпляр объекта производного класса pc=&ob; //указатель на объект базового класса pc->func(); //вызов функции базового класса pc=&ob1; //указатель на объект производного класса pc->func(); //попытка вызова функции производного класса return 0; } На первый взгляд, кажется, что в перврм случае будет вызываться функция базового класса, а во втором функция производного. Однако при проверке Вы легко убедитесь, что и в том и в другом случае будет вызвана функция функция базового класса. В чем тут дело? Дело в том, что компилятору трудно понять, какую реально функцию мы имеем в виду и он на стадии компилирования подставляет во всех тех случаях, где встречается имя func() адрес функции базового класса. Такой процесс установки адресов называется "ранним связыванием". Иногда употребляется термин "статическое связывание". Если же мы хотим, чтобы во втором случае, т.е. когда указатель pc указывал на производный класс вызывалась функция этого класса, ее еще в базовом классе следует указать как виртуальную. В нашем случае вместо строки void func() следует написать virtual void func(). После этого наш пример будет работать как надо. Как видите, ситуация несколко напоминает проблему перегрузки. Однако перегружаемые функции отличаются друг от друга типом или аргументами, здесь же функции должны быть идентичны. В случае использования виртуальных функций адрес вызываемой функции будет определяься в процессе выполнения кода программы. Такой процесс называется "поздним связыванием", употребляется также термин "динамическое связывание". Для дальнейшего уяснения свойств виртуальных функций рассмотрим еще один пример, являющийся развитием первого. #include <stdio.h> class base { public: int i; base(int x); //конструктор virtual void func() { printf("Базовая функция %d\n",i); return; }; }; //текст конструктора base::base(int x) { i=x; return; }; class der1: public base { public: der1(int x) :base(x) {}; //конструктор void func() { printf("Функция из производного класса %d\n", i*i); return; } }; class der2: public base { public: der2(int x) :base(x) {}; //конструктор }; main() { base * pc; //указатель на базовый класс base ob(2); //создать экземпляр объекта базового класса der1 ob1(2); //создать экземпляр объекта производного класса 1 der2 ob2(2); //создать экземпляр объекта производного класса 2 pc=&ob; //указатель на объект базового класса pc->func(); //вызов функции базового класса pc=&ob1; //указатель на объект производного класса 1 pc->func(); //попытка вызова функции производного класса pc=&ob2; //указатель на объект производного класса 2 pc->func(); //попытка вызова функции производного класса return 0; } Как видите, мы ввели еще один производный класс. В нем функция func() не определена. В этом случае будет вызываться функция класса родителя. Т.е. появится строка: Базовая функция 2. Как видите принцип очень прост: если Вы хотите, чтобы вызывалась функция родительского класса, не определяйте ее в производном. Еще один вопрос может возникнуть в связи с данным примером: как быть, если мы хотим, чтобы для класса объектов der2 вызывалась функция класса der1. Решение очень просто - сделайте класс der2 наследником не класса base, а класса der1. И последнее. Как мы видели, в производных классах функция, определенная в базовом классе как виртуальная может определяться, а может и нет. Если Вы хотите, чтобы во всех производных классах обязательно была определена виртуальная функция, то в базовом классе ее надо определить следующим образом: virtual void func() = 0; В этом случае базовый класс называется агрегатным и от него нельзя будет создавать экземпляры объектов, зато во всех производных классах компилятор обяжет Вас определить данную виртуальную функцию и, тем самым, уменьшить вероятность ошибок. 3.2.8 Указатели на объекты Доступ к члену объекта возможен не только через точку (.). Возможен доступ и через указатель на этот объект. В этом случае применяют стрелку (->). Пример: #include <iostream.h> class myclass { int a; public: myclass(int x); // конструктор int get(); }; myclass::myclass(int x) { a = x; } int myclass::get() { return a; } main() { myclass ob(120); // создание объекта myclass *p; // создание указателя на объект p = &ob; // передача адреса ob в p cout << "Значение, получаемое при использовании объекта:" << ob.get(); cout << "\n"; cout << "Значение, получаемое при использовании указателя:" << p->get(); return 0; } В программе объявление myclass *p создает указатель на объект myclass(а не создает объект!). Для передачи адреса ob в p используется выражение p=&ob. Для получения доступа к объекту через указатель используется выражение p->get();. Глава 4. Основы программирование на языке C++Builder 4.1 Характеристика С++Builder С++ Builder – это дальнейшее развитие языка Си, основанное на системе быстрой разработки приложений RAD (Rapid Application Development). В C++ Builder интегрирована Палитра компонент, разделенная картотечными вкладами на несколько функциональных страниц. Функциональные возможности компонент из страниц можно достаточно просто модифицировать, а также разрабатывать собственные компоненты. Система содержит библиотеку из более 100 визуальных компонент, которые перетаскиваются мышью на форму и сразу становятся элементами управления прототипа программы. После размещения компонент на форме, Инспектор объектов поможет устанавливать их свойства и предписывать событиям коды обработки. Проект будет строиться постепенно, на фоне производимых изменений в свойствах, событиях и функциях используемых элементов. C++ Builder поддерживает основные принципы объектно-ориентированного программирования - инкапсуляцию, полиморфизм и множественное наследование, а также нововведенные спецификации и ключевые слова в стандарте языка С++. C++ Builder поддерживает связь с базами данных: dBASE, Paradox, Sybase, Oracle, InterBase , Informix, Exel, Access, FoxPro. Механизм BDE (Borland Database Engine) придает обслуживанию связей с базами данных простоту и прозрачность. Проводник Database Explorer позволяет изображать связи и объекты баз данных графически. Интегрированная среда разработки объединяет Редактор форм, Инспектор объектов, Палитру компонент, Администратор проекта, интегрированные Редактор кода и Отладчик - инструменты быстрой разработки программных приложений, обеспечивающие полный контроль над кодом и ресурсами. Профессиональные средства языка С++ интегрированы в визуальную среду разработки. C++ Builder предоставляет быстродействующий компилятор с языка Borland С++, инкрементальный загрузчик и средства отладки. Конструирование по способу «drag-and-drop» позволяет создавать приложение простым перетаскиванием захваченных мышью визуальных компонент из палитры на форму приложения. Инспектор объектов предоставляет возможность оперировать со свойствами и событиями компонент, автоматически создавая заготовки функций обработки событий, которые наполняются кодом и редактируются в процессе разработки. Механизмы двунаправленной разработки (two-way-tools) обеспечивает контроль за кодом посредством гибкого, интегрированного и синхронизированного взаимодействия между инструментами визуального программирования и Редактором кода. Свойства, методы и события - это элементы языка, обеспечивающие быструю разработку приложений в рамках объектно-ориентированного программирования. Свойства позволяют устанавливать разнообразные характеристики объектов. Методы производят операции над объектом. События связывают воздействия пользователя на объекты с кодами реакции на эти воздействия. Между программными продуктами С++ Builder и Borland C++ существует полная и взаимная функциональная совместимость. Все компоненты, формы и модули данных, языка Delphi, могут быть повторно использованы в приложениях C++Builder без каких бы то ни было изменений. C++ и Delphi равноправны - почти все, что написано в Delphi, можно использовать в C++Builder, и наоборот. C++Builder не делает различия между тем, какие программные модули вы добавляете к проекту своего приложения - написаны они на C++ (файлы с расширением CPP) или на Delphi (файлы с расширением PAS). Компилятор свободно принимает следующие кодовые конструкции моделей Delphi 3.0: компоненты, формы, объекты, константы, простые методы и функции - все перечисленные коды можно прямо подставлять в свои проектные файлы. Технология визуального наследования форм дает возможность модифицировать формы Delphi в среде C++Builder без каких бы то ни было проблем. 4.2 Компоненты VCL. Свойства, события, методы Компоненты VCL- это строительные кирпичи, из которых разрабатывается интерфейс программы с пользователем. VCL это объект, который можно «перетащить» из вкладок Палитры компонент на форму приложения. Поместив компоненту на форму, можно манипулировать ее свойствами (Редактором форм) и кодом (Редактором кода). Свойства. Каждая из компонент обладает свойствами, которые позволяют изменять атрибуты, определяющие размер, форму, видимость компоненты. Для доступа к этим свойствам используется инспектор объектов или эти свойства меняются программным кодом. В инспекторе объектов для каждой компоненты имеются две закладки Properties(свойства) и Events(события). Многие свойства являются общими для различных компонент, но некоторые свойства имеются только у отдельных компонент. Некоторые свойства компонент устанавливаются числовыми значениями, например Height(высота), некоторые снабжены окном, позволяющим выбрать вариант, например Cursor(курсор). У некоторых компонент есть свойства, снабженные всплывающими меню и редакторами, предназначенными для внесения изменений. Также некоторые свойства зависят от установки других свойств, например Hint (подсказка), появляется, если свойство ShowHint имеет значение True. События. Компоненты C++Builder связаны с набором событий или ассоциированных с ними обработчиков событий. Для создания обработчика событий нужно дважды щелкнуть мышью правее события и ввести требуемый код. Методы. Методы представляют еще один путь управления компонентами. Каждая компонента – объект, в состав которого входят процедуры и функции. Метод – это описанная в объекте общая процедура или функция, которую можно вызвать из программы. Чтоб просмотреть методы, доступные для компонента, необходимо щелкнуть на этой компоненте и нажать клавишу F1, далее щелкнуть на Methods и получить список доступных методов. Например, для компонента Button метод Hide() делает кнопку невидимой: Button1->Hide(); В отличие от свойств методы не видны в окне инспектора объектов.4.2.1 Типы компонент Каждая из компонент представляет собой законченную конструкцию, содержащую свойства, методы и события. Стандартные компоненты VCL инкапсулируют поведение типовых элементов управления операционной системы Windows, как TButton, TListbox и TEdit. В основном они расположены на вкладках Standard и Win95 Палитры компонент. В отличие от стандартных компонент, оригинальные компоненты представляют собой элементы управления, у которых нет ни метода для собственного отображения, ни заранее определенного поведения (Tpanel, TstringGrid и др.). Графические компоненты представляют собой видимые элементы управления, которые не могут принять фокус ввода, т.к. не являются оконными (TImage, Tshape и др.). Невидимые компоненты не появляются на форме в виде каких-либо элементов управления. Поведение невидимых компонент определяется на этапе проектирования, путем инкапсуляции нужных свойств объекта (TOpenDialog, Ttimer и др.). 4.2.2 Иерархия классов VCL Расположенная ниже схема показывает ключевые классы в иерархической структуре, от которых произведены все компоненты VCL. TObject -> -> TComponent -> TGraphicControl -> … | | -> TWinControl -> … TObject является базовым классом для всех порождаемых классов, а TComponent является базовым классом для всех порождаемых компонент. Невидимые компоненты произведены от класса TComponent. Основные графические компоненты - от класса TGraphlcControl. Компоненты стандартного управления произведены непосредственно от класса TWinControl, а оригинальные компоненты - косвенно от класса TCustornControl, восходящего к TWinControl. Ниже приведена схема, продолжающая иерархическую структурную схему компонентных классов VCL. TGraphicControl TWinControl *Tbevel *TButtonControl *TCustomLabel o Tbutton · TDBText +TBitBtn · Tlabel o TCustomCheckBox *Timage +TCheckBox *TPaintBox +TDBCheckBox *TShape o TRadioButton *TSpeedButton *TCustomComboBox · TComboButton o TComboBox · TNavButton o TDBComboBox · TDriveComboBox · TFilterComboBox *TCustomControl · TCustomGrid + TCunstomDBGrid + TDBGrid + TDBLookupList + TpopupGrid + TCustomOutline +TOutline + TDrawGrid + TStringGrid · TCunstomGroupBox + TCunstomRadioGroup +TDBRadioGroup +TRadioGroup +TGroupBox · TCunstomPanel +TDBNavigator +TPanel · TDBImage · TDBLookupControl +TDBLookupComboBox + TDBLookupListBox + TPopupDataList · THeader · THintWindow · TMediaPlayer · TNotebook · TOleContainer · TPage · Tscroller · TTabSet * TCustomEdit · TCustomMaskEdit + TDBEdit + TInplaceEdit + TMaskEdit · TCustomMemo + TCustomRichEdit + TRichEdit + TDBMemo + TMemo · TDBLookupCombo · TEdit * TCustomHotKey · THotKey * TCustomListBox · TDBListBox · TDirectoryListBox · TFileListBox · TListBox * TCustomListView o TListView *TCustomTabControl · TPageControl · TTabbedNotebook · TTabControl *TCustomTreeView · TTreeView * TCustomUpDown · TUpDown * TDBCtrlGrid * TDBCtrlPanel * THeaderControl * TOleControl * TProgressBar * TScrollBar * TScrollingWinControl · TForm + TDesignWindow + TInputRedDialog + TLoginDialog + TPasswordDialog · TScrollBox * TStatusBar * TTabPage * TTabSheet * TTrackBar 4.3 Структуры файлов в C++ Builder Для каждого приложения C++ Builder создается один файл проекта, один make-файл один файл модуля и один файл ресурсов. Первоначально файлу проекта присваивается по умолчанию имя Project1.cpp Если в процессе разработки приложения добавляются формы и модули C++ Builder обновляет файл проекта. Make-файл(по умолчанию Project1.mak) - это текстовый файл, содержащий макросы, директивы и инструкции по компиляции и сборке проекта. Модули являются основой создания библиотек и приложений в C++ Builder. Модуль содержит исходный текст на языке C++ и первоначально представляет собой файл с расширением Unit1.cpp. Файл с расширением h генерируется при создании нового модуля. В нем содержится информация о данных и функциях, которые можно использовать в модуле, т.е.- в h-файле содержится интерфейс, а в самом модуле - реализация. Форма является одним из важнейших элементов приложения C++ Builder. Процесс редактирования формы происходит при добавлении к форме компонентов, изменении их свойств, создании обработчиков событий (по умолчанию файл формы или файл ресурсов имеет имя Unit1.dfm). Когда к проекту добавляется новая форма, создаются 3 отдельных файла: файл модуля (.срр) - содержит исходный код, связанный с формой; • h-файл(.h) - содержит описание класса формы, т. е. описание содержащихся на форме компонентов и обработчиков событий; файл формы (.dfm) - двоичный файл, содержащий сведения о доступных в инспекторе объектов свойствах компонентов, содержащихся в форме. В процессе трансляции создаются дополнительные файлы с расширениями ilc, ild, ilf, ils, obj, которые можно удалить. 4.4 Визуальные компоненты (VCL) VCL в C++Builder находятся на нескольких страницах. Страница Standard состоит из следующих визуальных компонент.
1.Компонент MainMenu позволяет создавать линейку меню проектируемой формы и выпадающее меню (компонент невизуальный). 2.Компонент PopupMenu позволяет создавать всплывающее меню при нажатии правой кнопки мыши (компонент невизуальный). 3.Компонент Label используется для создания надписей. 4.Компонент Edit используется для ввода или отображения однострочных текстов. 5.Компонент Memo используется для ввода или отображения многострочных текстов. 6.Компонент Button используется для создания кнопок, котрыми можно выбирать те или иные команды. 7.Компонент Checkbox позволяет выбирать или выключать те или иные опции. 8.Компонент RadioButton предлагает набор альтернатив из которых выбирается одна. 9.Компонент ListBox представляет стандартное окно списка, позволяющее выбирать те или иные пункты из списка. 10.Компонент ComboBox объединяет функции ListBox и Edit.Он позволяет ввести текст или выбрать из списка. 11.Компонент ScrollBar -это стандартная линейка прокрутки. 12.Компонент GroupBox объединяет группу связанных органов управления (контрольные индикаторы, радиокнопки). 13.Компонент RadioGroup -комбинация GroupBox с набором RadioButton. Он позволяет создавать группы радикнопок. 14.Компонент Panel испорльзуется для группировки органов управления. Страница Win95 включает следующие компоненты: - TabControl - используется для создания закладок, которые можно выбирать; - PageControl - создает страницы, управляемые закладками; - TreeView - позволяет просматривать иерархические структуры данных; - ListView - отображает списки в колонках; - ImageList - позволяет работать со списками изображений (невизуальный компонент); - Header - создает составные перемещаемые заголовки; - Richedit - создает окно редактирования (позволяет выбирать цвет, шрифт и т.д.); - StatusBar - отображает информацию о состоянии программы; - TrackBar - создает ползунок; - ProgressBar - используется для отображения хода процесса; - UpDown - создает кнопку-счетчик; - HotKey - создает поддержку "горячих клавиш"; - AnimatedSymbol – создает простейшую аннимацию. Страница Additional состоит из следующих компонент: - BitBtn -используется для создания кнопок, на которых расположен рисунок; - SpeedButton - применяется для создания линеек инструментов вместе с компонентом Panel; - MaskEdit - используется для ввода символов в соответствии с шаблоном; - StringGrid - применяется для отображения текстовой информации в строках и столбцах; - DrawGrid - применяется для отображения нетекстовой информации в строках и столбцах; - Image - используется для отображения графики; - Shape - используется для рисования геометрических фигур; - Bevel - используется для рисования объемного прямоугольника; - ScrollBar - применяется для создания зон отображения с прокруткой; - Scroller - применяется для создания зон отображения с горизонтальной прокруткой; Компоненты страницы Data Acces используются для связи и передачи информации между приложениями и базами данных. В работе не рассматриваются. Компоненты страницы Data Controls используются для организации доступа к данным. В работе не рассматриваются. Страница Win3.1 содержит компоненты, ориентированные на Windows 3.1. В работе не рассматриваются. Страница Internet содержит компоненты, ориентированные на работу с Internet. - TFTP – компонент, осуществляющий перенос данных между локальным и удаленным компьютером с рлмощью протокола FTP; - THTML – предназначен для отображения документов формата HTML; - THTTP – позволяет получать документы с помощью протокола HTTP; - TNNTP – позволяет осуществлять соединение в режиме on-line с серверами новостей по протоколу NNTP; - TPOP – позволяет приложению обмениваться данными с почтовыми серверами с использованием протокола POP3; - TSMTP – поддерживает обмен сообщениями с почтовыми серверами на основе протокола SMTP; - TTCP – осуществляет сетевое соединение и обмен данными по протоколу TCP; - TUDP – осуществляет соединение и обмен данными по протоколу UDP. Компоненты страницы Dialogs используются для создания различных окон диалога(невизуальные компоненты); - OpenDialog - для создания окон диалога "открыть файл"; - SaveDialog - для создания окон диалога "сохранить файл"; - FontDialog - для создания окон диалога "шрифты"; - ColorDialog - для создания окон диалога " цвет"; - PrintDialog - для создания окон диалога "печать"; - PrinterSetupDialog - для создания окон диалога "установки принтера"; - FindDialog - для создания окон диалога "найти"; - ReplaceDialog - для создания окон диалога "заменить". Страница System содержит компоненты, позволяющие включить в программу системные средства: - Timer - используется для запуска процедур, функций и т.д. в заданное время (невизуальный компонент); - PaintBox - создает область, в которой можно рисовать; - FileListBox - создает окно, в котором отображается список файлов из выбранного каталога; - DirectoryListBox - отображает каталоги выбранного диска; - DriveComboBox - позволяет отображать и выбирать диски; - FilterComboBox - позволяет создавать окно для отображения фильтров файлов; - MediaPlayer - позволяет проигрывать звуковые и видеофайлы; - OLEContainer -создает область для объекта OLE; - DDEClientConv - используется для диалога с DDE сервером (невизуальный компонент); - DDEClientItem - используется для спецификации данных, передаваемых DDE серверу (невизуальный компонент); - DDEServerConv - используется для DDE сервером для диалога с DDE с клиентом (компонент невизуальный); - DDEServerItem - используется для спецификации данных сервера (компонент невизуальный). QReport -ВК используются для генерации отчетов. В работе не рассматривается. Activeve X- OCX-компоненты, неявляющиеся частью C++Builder. - ChartFX - используется для построения диаграмм; - VCFirstImpression -реализует трехмерную графику; - VCFormulaOne - используется для работы с электронными таблицами; - VCSpeller - средство проверки орфографии; - GraphicsServer - средство для работы с графикой; На этой странице могут быть расположены свободно распространяемые компоненты для разработки тех или иных приложений (например, просмотрщик файлов виртуальной реальности VRML и др.). Страница Samples содержит компоненты, разработанные пользователями - примеры VCL. - Gauge - создает индикатор хода процесса; - SpinButton - позволяет создавать кнопки- счетчики; - SpinEdit - создает окно редактирования с кнопкой-счетчиком; - MCxSpriteBgr - панель, на которой можно создавать спрайты; - MCxStrite - позволяет создавать спрайты. Компоненты VCL можно разрабатывать самостоятельно, поэтому существует большое количество страниц и компонентов. Вот некоторые из страниц. JG - дополнительные компоненты - Banner - позволет создавать в окне движущийся текст или изображение; - SizeTask - позволяет устанавливать максимальный размер формы MyStuff - дополнительные компоненты - Animated - проигрывает bmp-файлы; - AutoLed - кнопка меняет цвет; - MMLabel - метка меняет цвет; 4.5 Программы, управление событиями, исключения Рассмотрим простейшую программу - вывод строки по нажатию кнопки. Создадим новый проект или воспользуемся пустым проектом, который предоставляется С++ Builder в момент его запуска. На пустую форму положим два компонента: строку редактирования Edit и кнопку Button. Для этого щелкнем мышью на пиктограмме требуемого элемента в палитре, а затем поставим его в нужное место формы. Далее щелкнем на свойстве Text компоненты Edit и удалим текст, присвоенный строке системой. Изменим свойство Caption кнопки Button, введя текст "Нажми". Форма готова. Осталось написать исходный текст для обработки нажатия кнопки. Для этого надо выделить кнопку Button, переключить окно инспектора объекта в режим создания обработчиков событий и дважды щелкнуть на событии OnClick. Откроется окно редактирования кода, в котором появился пустой обработчик события. Добавим в него строку исходного текста: Edit1-> Text = "Привет от Borland C++!"; После этого командой Run-Run начать компиляцию и выполнение программы. Нажмем кнопку "Нажми", и программа поприветствует нас. В отличии от DOS, в которой программа получает управление при запуске, и выполняется от начала до конца, в Windows 95 программа управляется событиями. При управлении событиями программа не делает ничего до тех пор, пока не происходит событие - нажатие клавиши мыши, нажатие клавиши клавиатуры и т.д. Существуют различные виды событий и их обработка. На примерах рассмотрим некоторые из них. 1.Обработчик, откликающийся на событие. Рассмотрим пример - обработка нажатия клавиши мыши и вывод координат курсора мыши. 1.Запускаем C++ Builder. 2.В инспекторе объектов выбираем страницу Events. 3.Справа от метки события OnMouseDown (нажатие клавиши мыши) дважды щелкаем мышью. 4.Появляется программный код процедуры - обработчика событий. 5.Вводим код. Form1->Canvas->TextOut(X,Y,"Координата "+IntToStr(X)+ ","+ IntToStr(Y)); 6. Запускаем программу на выполнение. При нажатии клавиши мыши активизируется обработчик событий OnMouseDown,которому передаются параметры - координаты X и Y. Таким образом в данном случаее мы рассмотрели вариант, когда обработчику событий передана некоторая информация - параметры X и Y. 2.Обрабочик событий, передающий информацию, вызываемой процедуре. Возможен вариант, когда обработчик событий передает информацию функции, которая его вызвала. Рассмотрим пример, когда в окне редактирования все буквы преобразуются в заглавные. 1.Запускаем C++ Builder. 2.Выводим на форму из палитры компонент элемент Edit. 3.В разделе инспектора объектов Events вправо от OnKeyPress два раза щелкаем мышью, получаем фрагмент кода. 4.Набираем код. Key= AnsiUpperCase(Key)[1]; 5.Запускаем на выполнение. Любая введеная буква печатается как заглавная. В данном примере переменная Key, содержащая значение нажатой клавиши указана как указатель на адрес символа - в этом случае любые изменения, сделанные в переменной передаются обратно в вызывающую функцию. Функция AnsiUpperCase(Key) преобразует символ в заглавный. Кроме этого существуют обработчики событий, ориентированный на обработку вспомогательных операций в сложной программе. Исключения. Для обработки исключений(ошибок) в C++ Builder имеются специальные средства. В частности, при возникновении ошибки возможна передача управления блоку, который ориентирован на обработку этой ошибки. Для этой цели используется блок try ... except() следующего формата: try { исполняемый код } except (проверяемая пеpеменная) { код, исполняемый при ошибке } 4.6 Стратегия отладки Windows-программ Ошибки в программе могут быть самыми разнообразными. Но для Windows-приложений характерны следующие : - вызов неверной функции, когда вместо одной функции по ошибке вызывается другая; - вызовам Windows API передаются неправильные аргументы; - задание неверных параметров цикла; - ошибка в условии операторов if, switch, while и т.д., приводящая к неправильному ветвлению; - возникновение не предусмотренного программистом варианта реакции системы. Для отладки программ обычно используется несколько стандартных операций: * установка точек прерывания (breakpoints); * просмотр содержимого переменных и участков памяти; * подстановка ложных данных для моделирования ситуации "что - если"; * ложные вызовы. DOS-программу можно выполнять пошагово, перебираясь от одной строки исходного текста к другой В Windows же это нереально. Нужно поставить точку прерывания на интересующем вас участке программы и запустить ее на выполнение командой Run - Run. Достигнув точки прерывания, программа приостановит свое выполнение, давая возможность программисту приступить к отладочным действиям. В программах на C++ Builder, точки прерывания удобнее всего ставить внутри обработчиков событий. Простейший способ установить точку прерывания - нажать на клавишу <F5>. При этом выбранная для остановки строка выделяется красной полосой, на левом краю строчки появляется маленький значок "Stop". Повторное нажатие на <F5> отменяет уже имеющуюся точку прерывания. Другой способ установить точку прерывания - щелкнуть мышью на левом краю окна редактирования. Когда ошибочный обработчик найден, нужно приступать к уточнению места ошибки. Для этого Можно выполнять программу шаг за шагом командой Trace Into ( клавиша <F7>) или Step Over (клавиша <F8>), проверяя, как изменяются данные программы. После того как программа отлажена, необходимо перетранслировать ее, установив в главном меню в разделе Options->C++ опцию Release. 4.7 Использование компонент VCL в разработке программ Использование меню. Страница Standard содержит наиболее часто используемые компоненты – к ним относятся компоненты MainMenu и PopupMenu. Каждому пользователю Windows, раскрывающееся меню представляет собой подробный список таких команд, как Open или Exit. В большинстве приложений в главном меню перечислены имена одного или нескольких раскрывающихся подменю. Главное меню (компонент MainMenu). Используется для создания главного меню приложения, которое всегда отображается под заголовком окна. Всплывающее меню (компонент PopupMenu). Используется для создания всплывающих меню, которые появляются при при щелчке правой кнопкой мыши, когда указатель, мыши находится внутри клиентной области окна. Для того чтобы построить главное меню программы, необходимо выполнить следующее: 1. На форму главного окна поместите объект MainMenu. По умолчанию в C++ Builder. ему присваивается имя MainMenu1. 2. Установите свойство формы Menu равным имени объекта MainMenu. 3. Дважды щелкните на объекте MainMenu1 и используйте Menu Designer для ввода команд меню. Для того чтобы создать всплывающее меню, которое появляется при щелчке правой кнопкой мыши, выполните следующее: 1. Поместите на форму объект РорupМеnu. По умолчанию в C++ Builder ему присваивается имя PopupMenu1. 2. Присвойте PopupMenu1 свойству формы PopupMenu. 3. Дважды щелкните на объекте PopupMenu1 и используйте Menu Designer для ввода команд меню. Рассмотрим пример – разработаем простейший редактор текста. Создайте файл с именем 1.txt в каталоге, в котором будет храниться пример и запишите в него какой-либо текст. Запустите C++ Builder. 1.Поместите на форму компонент Memo. Согласно рисунка измените размеры. В инспекторе объектов установите ScrollBars в позицию ssVertical (вертикальная полоса прокрутки). На поле Memo вы видите текст Memo1. Для того чтобы он исчезал при запуске программы щелкните мышью по форме и напишите между {и}; команду очистки окна Memo1: Memo1->Clear(); 2. Поместите на форму кнопку Button. Свойство Caption в инспекторе объектов измените на Close. Щелкните на кнопке. и напишите код: Close(); - по этой команде программа прекращает работу. 3. Поместите на форму компоненты MainMenu и PopupMenu. Щелкните по форме и напишите в свойствах MainMenu- MainMenu1 и PopupMenu - PopupMenu1 соответственно. 4.Щелкните два раза на компоненте MainMenu. Появляется конструктор меню. В инспекторе объектов свойство Сaption измените на &File(&- быстрая клавиша ALT+F). Нажмите клавишу Enter. Далее в конструкторе меню щелкните мышью под командой File и опять свойство Caption измените на &Open, ниже опять появляется синий квадратик напишите свойство Caption &Save, а затем на следующем пункте меню измените Caption на E&xit. 5.Щелкните мыщью рядом с &File (правее) и запишите в Caption &Help. Таким образом все пункты меню мы записали и они должны появитсяна форме. 6.Начнем ставить в соответствие событиям (пунктам меню) программный код. Щелкните мышью на команде File, поставте курсор на Open и щелкните мышью. между командами { и } запишите код: Memo1->Lines->LoadFromFile("1.txt"); По этой команде текст из файла 1.txt загрузится в окно Memo. Свойство Lines объекта Memo это объект типа строка символов, которая отображается в окне. Метод LoadFromFile позволяет загрузить файл в Memo. Перед нами редактор текста - текст можно модифицировать, а затем записать. 7.Щелкните мышью на команде File, поставте курсор на Save и щелкните мыщью. между командами begin и end; запишите код: Memo1->Lines->SaveToFile("1.txt"); По этой команде текст запишится в файл 1.txt. 8. Наиболее интерсные действия с командой Exit. Стандартно можно было-бы щелкнуть мышью на команде Exit и записать код Close();, но этот код у нас есть по нажатию клавиши Button программа прекращает работу, поэтому мы делаем так: 8.1.В инспекторе объектов в выпадающем меню (в шапке) выбираем опцию Exit1. 8.2 Переходим в инспекторе объектов на страницу Events и щелкаем мышью в поле события OnClick. 8.3.Выбираем Button1Click. То есть мы совмещаем события: - нажатие клавиши(закрытие программы); - выбор опции меню Exit. 9. В дизайнере меню заходи в раздел Help -щелкаем мышью и пишем код: Canvas->TextOut(10,10,"Помощи пока нет!"); 10. Добавляем всплывающее меню. Щелкаем дважды на компоненте PopupMenu. 11.Появляется дизайнер меню. Первую опцию меню указываем в Caption - &Open, следующую &Save и последнюю E&xit(также как в MainMenu). 12.Щелкаем на опции Open.В выпадающем меню инспектора объектов выбираем опцию Open2. Переходим в инспекторе объектов на страницу на страницу Event и щелкаем мышью в поле события OnClick. Находим Open1Click и щелкаем мышью (теперь мы совместили событие открытие файла 1.txt в меню MainMenu и PopupMenu); Соответственно выбираем опцию &Save и совмещаем с событием Save1Click. Далее совмещаем опцию Exit с событием Button1Click. Далее программу запоминаем и транслируем. Программа будет выглядеть так: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { Memo1->Clear(); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Close(); } //--------------------------------------------------------------------------- void __fastcall TForm1::Open1Click(TObject *Sender) { Memo1->Lines->LoadFromFile("1.txt"); } //--------------------------------------------------------------------------- void __fastcall TForm1::Save1Click(TObject *Sender) { Memo1->Lines->SaveToFile("1.txt"); } //--------------------------------------------------------------------------- void __fastcall TForm1::Help1Click(TObject *Sender) { Canvas->TextOut(10,10,"Помощи пока нет!"); } //--------------------------------------------------------------------------- Кнопки переключатели и выключатели. Кнопки, переключатели и выключатели - это основа графического пользовательского интерфейса, поэтому C++ Builder предоставляет целый "набор" всевозможных кнопок, переключателей и выключателей. Кроме того, имеется возможность объединять группы кнопок и использовать организующие компоненты - фаски и панели. Кнопка (компонент Button). Стандартный управляющий элемент Windows. Кнопка с растровым изображением (компонент BitBtn). Компонент BitBtn работает также, как и компоненты Button, но отличаются от последних наличием цветной пиктограммы, визуально представляющей действие кнопки. Кнопка панели инструментов (компонент SpeedButton). Обычно эти компоненты используются для создания панелей инструментов. На кнопках BitBtn или SpeedButton отображается пиктограмма (glyph), которая представляет собой растровое изображение Windows. Несмотря на то, что размеры пиктограмм не ограничены, обычно применяется стандартный размер - 16х16 пикселей при 16 цветах. Свойство glyph компонента является объектом, имеющим тип TBitmap. Пиктограммы содержат от одного до четырех отдельных блоков, хранящихся в одном файле. эти блоки представляют различные состояния кнопки. 1. Состояние кнопки = ненажатое (обычное состояние). 2. Состояние кнопки = недоступное (отображение серым цветом; Enabled = false). 3. Состояние кнопки = нажатое (отображение смещенное и, возможно, затемненное). 4. Состояние кнопки = фиксированное нажатое (только для кнопок панели инструментов). Пиктограммы можно назначать как при создании формы, так и во время работы программы(например, для получения динамического визуального эффекта либо для изменения вида кнопки в зависимости от внешних условий). Для того чтобы пиктограмма загружалась во время работы, разместите объект BitBtn на форме, щелкните на нем дважды и в обработчик события OnClick вставьте следующие операторы: BitBtn1->Glyph->LoadFromFile (“alarm.bmp"); Выключатель (компонент CheckBox). Это также стандартный управляющий элемент Windows, состоящий из метки и небольшого квадратика. Щелкнув на нем, пользователь может включить или выключить параметр или режим, описанный меткой. Группа (компонент GroupBox). Данный компонент предназначен для логического объединения множества переключателей и других объектов. Фокус ввода пользователи могут перемещать между группами с помощью клавиши <Tab>. Затем, используя клавиши управления курсором, можно выбрать один из управляющих элементов, объединенных в группу. Переключатель (компонент RadioButton). На группу или панель можно поместить несколько переключателей, но в большинстве случаев для создания наборов переключателей гораздо удобнее использовать компонент RadioGroup. Кроме того, наборы переключателей можно распологать непосредственно на форме. Группа переключателей (компонент RadioGroup). Этот компонент имеет много общего с группой, но он намного удобнее в использовании при создании наборов переключателей. Для организации переключателей необходимо только указать текст переключаемых параметров в свойстве Items группы переключателей. Строка ввода с настройкой (компонент SpinEdit) - компонент, представляющий собой кнопку настройки, объединенную со строкой ввода. Щелчки на кнопке приводят к увеличению или уменьшению числа, находящегося в строке ввода. Кроме того, пользователи могут вводить число непосредственно в самой строке ввода, которая к ттому же распознает команды работы с буфером обмена Windows. Панель (компонент Panel). Данный компонент позволяет упорядочить загроможденные информацией окна, а также является основой создания панелей инструментов и строк состояния. Фаска (компонент Bevel). Этот чисто визуальный компонент выглядит в окне как прямоугольный выступ или углубление. Фаска также используется для создания горизонтальных и вертикальных линий. Рассмотри пример использования рассмотренных выше компонент. Начнем разработку нового проекта. Согласно рисунка поместим на верхнюю часть формы компонент Panel. Свойству Caption присвоим “Демонстрация”. Далее на форму добавим компонент Label и установим свойство Caption Имя”. Под метку добавим компонент Edit. Используя инспектор объектов удалим символы из свойства Text объекта Edit. Далее добавим компонент Memo. В инспекторе объектов установим ScrollBars в позицию ssVertical (вертикальная полоса прокрутки). Далее в инспекторе объектов двойным щелчком откроем свойство Lines, удалим текст Memo1 и четыре раза нажмем клавишу Enter(будем работать с четыремя строками). Установим на форме компоненты RadioGroup, Scrollbar и ComboBox. Дважды щелкнем на правее свойства Items и введем четыре строчки: серый, голубой, желтый, красный. Дважды щелкнем по компоненте RadioGrouo и введем код: if(RadioGroup1->ItemIndex==0){Form1->Color=clSilver;} if(RadioGroup1->ItemIndex==1){Form1->Color=clBlue;} if(RadioGroup1->ItemIndex==2){Form1->Color=clYellow;} if(RadioGroup1->ItemIndex==3){Form1->Color=clRed;} Этот фрагмент кода позволяет изменять цвет формы в зависимости от отмеченной кнопки. Далее дважды щелкнем по компоненте Scrollbar и добавим код: RadioGroup1->ItemIndex=ScrollBar1->Position; Этот фрагмент кода позволяет изменять цвет формы в зависимости от положения ползунка. Поместим на форму копонент GroupBox и добавим две кнопки Button, две кнопки BitBtn , две RadioButton и две Checkbox. Щелкнем по кнопке Button1 и введем код: //Очистить содержимое Memo Memo1->Clear(); //Скопировать в Memo текст, введенный в Edit Memo1->Lines->Add(Edit1->Text); //Скопировать в Memo текст из Combobox Memo1->Lines->Add(ComboBox1->Text); // Если нажата первая радиокнопка, то цвет Memo белый if (RadioButton1->Checked) Memo1->Color=clWhite; // Если нажата первая радиокнопка, то цвет Memo светло-голубой if (RadioButton2->Checked) Memo1->Color=clAqua; Щелкнем по кнопке Button2 и введем код: Close(); //закончить работу Щелкнем по кнопке BitBtn1 и введем код: //если помечена кнопка Checkbox1, то размер шрифта на форме 12 // иначе размер шрифта 8 if (CheckBox1->State==cbChecked) Font->Size=12; else Font->Size=8; //если помечена кнопка Checkbox2, то цвет шрифта на форме фиолетовый // иначе цвет шрифта черный if (CheckBox2->State==cbChecked) Form1->Font->Color=clPurple; else Form1->Font->Color=clBlack; Щелкнем по кнопке BitBtn2 и введем код: Form1->Hide();// свернуть форму с именем Form1 AddTab->Show();// показать форму с именем AddTab Как работать с двумя формами рассмотрим дальше. Вся программа будет выглядеть следующим образом: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" #include "Unit2.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::RadioGroup1Click(TObject *Sender) { if(RadioGroup1->ItemIndex==0){Form1->Color=clSilver;} if(RadioGroup1->ItemIndex==1){Form1->Color=clBlue;} if(RadioGroup1->ItemIndex==2){Form1->Color=clYellow;} if(RadioGroup1->ItemIndex==3){Form1->Color=clRed;} } //--------------------------------------------------------------------------- void __fastcall TForm1::ScrollBar1Change(TObject *Sender) { RadioGroup1->ItemIndex=ScrollBar1->Position; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Memo1->Clear(); Memo1->Lines->Add(Edit1->Text); Memo1->Lines->Add(ComboBox1->Text); if (RadioButton1->Checked) Memo1->Color=clWhite; if (RadioButton2->Checked) Memo1->Color=clAqua; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { Close(); } //--------------------------------------------------------------------------- void __fastcall TForm1::BitBtn1Click(TObject *Sender) { if (CheckBox1->State==cbChecked) Font->Size=12; else Font->Size=8; if (CheckBox2->State==cbChecked) Form1->Font->Color=clPurple; else Form1->Font->Color=clBlack; } //--------------------------------------------------------------------------- void __fastcall TForm1::BitBtn2Click(TObject *Sender) { Form1->Hide(); AddTab->Show(); } //--------------------------------------------------------------------------- Построим вторую страницу приложения. Добавим в разрабатываемый проект вторую форму и дадим ей имя AddTab и сохраним под именем AdditionalTab.pas. Разместим визуальные компоненты на форме согласно рисунка. Поместим на форму в левый нижний угол компонент Bevel. Поместим три кнопки типа BitBtn в нижний левый угол формы на компонент Bevel. Свойство Kind для BitBtn1 и BitBtn2 установим равным bkCustom, а для BitBtn3 – равным bkClose. Установим на кнопках для свойств Caption текст, соответствующий рисунку. Для каждой из кнопок установим гравировку (glyph), используя готовые гравировки, поставляемые в комплекте с языком или создавая свои с помощью редактора Image Editor в меню инструментов Tools. Добавляем код действия для кнопок: BitBtn1: Form1->Show();\\ показать форму Form1 AddTab->Hide();\\свернуть форму AddTab BitBtn2: ShowMessage("Пока НЕТ");\\следующей страницы пока нет BitBtn3: Form1->Close();//закрыть форму Form1 Для правильного закрытия приложения и освобождения ресурсов необходимо добавить для события Close формы Form1 код: Application->Terminate();\\освободить ресурсы В левом верхнем углу формы разместим компонент Shape (укажем в свойстве Shape значение stEllipse), компонент Panel и на нем две кнопки SpeedButton и компонент Image, в который можно загрузить какое-либо изображение. Кнопки SpeedButton могут находиться в состоянии: Up- верхнее, Disabled – отключено, Down – нижнее, Stay Down – прижатое. Для каждого состояния отображается соответствующий элемент гравировки. Для использования этих состояний с помощью встроеннного графического редактора создается матрица 16х64 точки. Изображение будет иметь четыре квадратные зоны. Установим свойство NumGlups для Speedbutton1 равным 4, а свойство GroupIndex равным 1. Дважды по кнопки SpeedButton1 и пишем код: if (SpeedButton1->Down==true) Image1->Visible=false; Shape1->Brush->Color=clRed; Код означает, что если нажата кнопка, то изображение Image1 исчезает, а изображение фигуры Shape1 красного цвета. Дважды щелкаем по кнопке SpeedButton2 и записываем код: if (SpeedButton1->Down=true) {Image1->Visible<<true; Shape1->Brush->Color=clLime; Для SpeedButton1 используем свойство Hint (подсказка). В этом свойстве хранится строка подсказки, которая появляется, когда курсор мыши находится над кнопкой. ShowHint необходимо установить в true. Добавим на форму компонент Label свойство Caption зададим Phone. Под меткой установим компонент MaskEdit. Дважды щелкнем на свойстве EditMask и запусти редактор маски ввода. Далее щелкнем на Phone и нажмем клавишу OK. Компонент MasEdit будет настроен на прием телефонных номеров. Поместим на форму компонент StringGrid и установим свойства: RowCount и ColCount. Свойства FixedCols и FixedRows установим в 0. Подберем размер сетки чтоб были видны девять ячеек. Добавим на форму кнопку со свойством Caption Fill Grid. Дважды щелкнем на ней и наберем код: int x,y; for (x=0; (x<=StringGrid1->ColCount-1);x++) for ( y=0;(y<=StringGrid1->RowCount-1);y++) StringGrid1->Cells[x][y]=("Coord. "+ IntToStr(x)+"-"+IntToStr(y)); Далее поставим на форму панель с прокруткой ScrollBox и разместим на ней компоненты:BitBtn со свойством Kind равным bkHelp. Добавим на ScrollBox Panel. Дважды щелкаем на кнопке BitBtn4 и добавляем код: ShowMessage(" Test "); Компонент ScrollBox c размещенными на нем компонентами позволяет продемострировать способности показвать фрагменты, которые не помещаются в зоне использования. Общий вид программного модуля AddTab: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" //--------------------------------------------------------------------------- #pragma link "Grids" #pragma resource "*.dfm" TAddTab *AddTab; //--------------------------------------------------------------------------- __fastcall TAddTab::TAddTab(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TAddTab::BitBtn1Click(TObject *Sender) { Form1->Show(); AddTab->Hide(); } //--------------------------------------------------------------------------- void __fastcall TAddTab::BitBtn3Click(TObject *Sender) { Form1->Close(); } //--------------------------------------------------------------------------- void __fastcall TAddTab::FormClose(TObject *Sender, TCloseAction &Action) { Application->Terminate(); } //--------------------------------------------------------------------------- void __fastcall TAddTab::SpeedButton1Click(TObject *Sender) { if (SpeedButton1->Down==true) {Image1->Visible=false; Shape1->Brush->Color=clRed; } } //--------------------------------------------------------------------------- void __fastcall TAddTab::SpeedButton2Click(TObject *Sender) { if (SpeedButton1->Down=true) {Image1->Visible<<true; Shape1->Brush->Color=clLime; } } //--------------------------------------------------------------------------- void __fastcall TAddTab::Button1Click(TObject *Sender) { int x,y; for (x=0; (x<=StringGrid1->ColCount-1);x++) for ( y=0;(y<=StringGrid1->RowCount-1);y++) StringGrid1->Cells[x][y]=("Coord. "+ IntToStr(x)+"-"+IntToStr(y)); } //--------------------------------------------------------------------------- void __fastcall TAddTab::BitBtn4Click(TObject *Sender) { ShowMessage(" Test "); } //--------------------------------------------------------------------------- void __fastcall TAddTab::BitBtn2Click(TObject *Sender) { ShowMessage("Пока НЕТ"); } //--------------------------------------------------------------------------- void __fastcall TAddTab::MaskEdit1Change(TObject *Sender) { } //--------------------------------------------------------------------------- Рассмотрим еще один проект. Поместим на форму согласно рисунка компоненты: TabControl (на него помести панель Panel с именем Tab1), ProgressBar, TrackBar, TreeView, Timer, OpenDialog, Edit, Label (с именем TreeView), Panel, Button (с именем Open Dialog). Дважды щелкнем на свойстве Tabs сомпоненты TabControl. В редакторе строк введем названия закладок – Tab1, Tab2, Tab3. Свойство Max компонентов ProgressBar и TrackBar установим равное 10. В событие OnChange компоненты TrackBar введем код: ProgressBar1->Position=TrackBar1->Position; Дважды щелкнем по компоненте Timer и добавим код к событию OnTimer, предварительно указав свойство Interval равным 1000(1 секунда): i++; //интервал счетчика 1 секунда Panel2->Caption = IntToStr(i);// панель показывает в секундах время // работы программы Необходимо внимательно посмотреть на листинг программы и обратить внимание на переменную i. Для компонента TreeView дважды щелкнем на поле справа от свойства Items, а затем добавим к дереву пункт Level1, затем к узлу второго уровня пункт Level2 и далее Level3. К кнопке OpenDialog добавим код: OpenDialog1->FileName="*.*"; if (OpenDialog1->Execute()){ Edit1->Text=OpenDialog1->FileName; В результате программа прнимет вид: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; int i=0; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBar1Change(TObject *Sender) { ProgressBar1->Position=TrackBar1->Position; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { OpenDialog1->FileName="*.*"; if (OpenDialog1->Execute()){ Edit1->Text=OpenDialog1->FileName; } } //--------------------------------------------------------------------------- void __fastcall TForm1::Timer1Timer(TObject *Sender) { i++; Panel2->Caption = IntToStr(i); } //--------------------------------------------------------------------------- 4.8.Графические компоненты. Для работы с графикой в C++Builder имеются графические компоненты: - Image - используется для импоpта на фоpму pастpовых изобpажений, пиктогpамм и метафайлов; - Shape - используется для создания геометpических фигуp;. - PaintBox - используйется для создания рисунков на компонентах, котоpые не имеют свойства Canvas (напpимеp, компонент Panel). В C++Builder гpафические опеpации выполняются с использованием свойства Canvas, котоpое поддеpживается многими компонентами. Напpимеp, свойство Canvas есть у фоpмы, а так же у TBitmap, TComboBox, TListBox, TPaintBox и дpугих компонентов. Canvas пpедставляет собой объект, наделенный собственными полномочиями, для чего класс TCanvas поддеpживает множество свойств, методов и событий. Чаще всего используется свойство Canvas. В пpоцессе pакзpаботки это свойство недоступно в окне Object Inspector. Для того, чтобы использовать Canvas, необходимо написать опеpатоpы, котоpые пpисваивают свойствам Canvas значения и вызывают методы Canvas. В таблице приведены методы, свойства и события, реализуемые TСanvas.
Рассмотрим примеры: Все визуальные компоненты имеют свойства Top (координата X) и Left (координата Y). Значения X и Y измеряются в пикселах (пиксел - наименьшая частица поверхности рисунка, которой можно манипулировать). В зависимости от количества бит, на пиксел различаются различные виды графики от 8- битовой до 24-битовой. В связи с этим возможно отображение до 16 миллионов цветов в стандарте RGB. Рассмотрим пример рисования синусоиды при нажатии кнопки на форме: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #include <math.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { float X,Y; int PX,PY; int H; //определяем половину стороны внизу формы H=Form1->Height / 2; for (PX= 0; PX < Form1->Width; PX++) { //масштаб X приводится к 2 ПИ, чтоб сделать //полную волну X=PX*(2*3.1415/Form1->Width); Y=sin(X); PY=(0.6*Y*H)+H; Canvas->Pixels[PX][PY]= 0;//устанавливаем черный цвет пикселя } } //---------------------------------------------------------------------------
Каждое свойство Canvas имеет воображаемое перо для рисова- ния линий и контуров и команды: - Form1->Canvas->MoveTo(X,Y); -передвигает перо в точку с координатами(X,Y) без рисования; - Form1->Canvas->Point(X,Y);- рисует точку; - Form1->Canvas->LineTo(X,Y); -чертит линию от текущей по- зиции до точки с координатами X,Y; Form1->Canvas->Pen->Color:=цвет; - устанавливает цвет пе- ра(clBlue - голубой или RGB(0,0,255)); - Form1->Canvas->Pen->Width:=толщина; - устанавливает толщи- ну пера в пикселях; - Form1->Canvas->Pen->Style:=стиль; - устанавливает стиль пе- ра(например,пунктирная линия - psDot); - Form1->Canvas->Pen->Mode:=режим; - устанавливает режим ри- сования(pmNot- цвет пера инверсный цвету фона, pmCopy - перо текущего цвета); - Form1->Canvas->Brush->Color:=цвет; - устанавливает цвет закрашенного графического объекта; - Form1->Canvas->Brush->Style:=стиль; - стиль закраски(напри- мер,bsSolid - закраска одним цветом, bsCross -решеткой). Анимация в C++ Builder. Анимация - это движущаяся графика. Рассмотрим программу, которая создает анимационную последовательность изображений 10 эллипсов, которые движутся от середины компонента PaintBox к его границам в случайных направлениях при нажатии кнопки Button. Процедура рисует эллипс, а затем стирает его изображение. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include <math.h> // <<--включить библиотеку математических функций #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { double RadReal; int Angle,X,Y; int Count,Count2; for(Count2= 1; Count2< 10; Count2++) { Angle = random(368); RadReal = (Angle * M_PI)/180; for(Count= 1; Count< 100; Count++) { X = (double)/*<<-------*/(Count*sin(RadReal))+100; Y = (double)/*<<-------*/(Count*cos(RadReal))+100; // рисуем эллипс голубым цветом PaintBox1->Canvas->Brush->Color =clBlue; PaintBox1->Canvas->Ellipse (X-6,Y-6,X+6,Y+6); // стираем эллипс белым цветом PaintBox1->Canvas->Brush->Color = clWhite; PaintBox1->Canvas->Rectangle(0,0,200,200); } } } //--------------------------------------------------------------------------- Если запустить программу на выполнение, то изображение эллипсов будет мерцать, хотя они будут двигаться очень быстро. Для преодоления этого недостатка в аннимации используют метод двойной буферизации. В этом случае используют две плоскости - одна отображается на экране, а вторая используется для рисования.Далее плоскости меняются местами. Одним из способов организации двойной буферизации - создание в памяти битовой матрицы в качестве промежуточного буфера. Изображение рисуется в битовой матрице, а по завершению кадра копируется в отображаемый буфер. Рассмотрим предыдущий пример с использованием битовой матрицы. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include <math.h> // <<--включить библиотеку математических функций #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { double RadReal; int Angle,X,Y; int Count,Count2; // создание битовой матрици Graphics::TBitmap *TheBitmap = new Graphics::TBitmap(); // определяем ее размеры TheBitmap->Height = 200; TheBitmap->Width = 200; for(Count2= 1; Count2< 10; Count2++) { Angle = random(368); // занести случайное число RadReal = (Angle * M_PI)/180; for(Count= 1; Count< 100; Count++) { X = (double)(Count*sin(RadReal))+100; Y = (double)(Count*cos(RadReal))+100; // очистка экрана TheBitmap->Canvas->Brush->Color = clWhite; TheBitmap->Canvas->Rectangle(0,0,200,200); // рисование эллипса TheBitmap->Canvas->Brush->Color = clBlue; TheBitmap->Canvas->Ellipse(X-6,Y-6,X+6,Y+6); // копирование битовой матрици на экран PaintBox1->Canvas->CopyRect(Rect(0,0,200,200), TheBitmap->Canvas, Rect(0,0,200,200)); } } // освобождаем память выделенную под битовую матрицу delete TheBitmap; } //--------------------------------------------------------------------------- При запуске примера видно, что эллипсы движутся плавно, но процедура выполняется медленнеее 4.9 Мультимедиа Мультимедиа - это использования звука и видео в программах. Для работы со звуком (файлами с расширением wav и mid) нужна звуковая карта - Soundblaster. В файлах с расширением wav записано цифровое представление информации о волновой форме электрического сигнала, соответствующего звуку. Файлы с расширением mid используются для хранения музыкальных фрагментов. Звук в этих файлах хранится в виде данных о том на каком инструменте исполняются те или иные ноты и как они звучат (цифрой эквивалент дирижерской партитуры). Файл с расширением avi представляет оцифрованное изображение в виде кадров, которые сменяются с частотой 24 (могут быть отличия в различных системах) кадра в секунду. Обычно видеофайлы сжаты, т.к. занимают достаточно много места в памяти. Для сжатия применяется в основном следующий метод: определяется неподвижная часть изображения, по сравнению к начальному кадру и эта информация сжимается, т.е. сохраняются только отличия одного кадра от другого. Для проигрывания wav файлов в C++ Builder используется процедура PlaySound. Рассмотрим пример – по нажатию клавиши Button – проигрывается звуковой файл bomb.wav. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" #include "mmsystem.hpp" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { PlaySound("bomb.wav",0,SND_SYNC); } //---------------------------------------------------------------------------
Как видно из программы дополнительно введен модуль mmsystem (в котором хранятся константы для функции PlaySound) и вызвана API функция PlaySound, которая имеет три параметра: Первый параметр - "bomb.wav" типа AnsiString - содержит имя проигрываемого файла. Второй параметр (в нашем случае 0) применяется, если файл содержится в файле ресурсов. Третий параметр специфицирует опции, задающие способ воспроизведения звука. Для проигрывания всех типов мультимедийных файлов можно использовать компонент MediaPlaer. Рассмотрим пример, проигрывания avi, wav и mid файлов. На форме расположим компоненты: MainMenu, OpenDialog, MediaPlayer, добавим к меню команду File|Open и в событие меню Open введем код: void __fastcall TForm1::Open1Click(TObject *Sender) { if(OpenDialog1->Execute()) { MediaPlayer1->FileName=OpenDialog1->FileName; MediaPlayer1->Open(); } } Общий вид программы следующий: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::N2Click(TObject *Sender) { if(OpenDialog1->Execute()) { MediaPlayer1->FileName=OpenDialog1->FileName; MediaPlayer1->Open(); } } //--------------------------------------------------------------------------- Загружая через меню тот или иной мультимедийный файл мы сможем его проигрывать. В случае, если панель проигрывателя показывать не надо, а проигрывать файл необходимо, то свойство Visible компоненты MediaPlayer нужно установить равным false. Рассмотрим пример - информационной системы - в зависимости от выбранной радиокнопки проигрывается тот или иной avi файл. Используются компоненты: MediaPlayr, RadioGroup, RadioButton, Panel, Button. Основная логика программы реализуется в обработке события OnClick кнопки Button. Когда пользователь нажимает кнопку, программа проверяет, какая из радиокнопок выбрана. В зависимости от этого проигрывается один из трех avi-файлов. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { MediaPlayer1->Display=Panel1; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if(RadioButton1->Checked) MediaPlayer1->FileName="1.AVI"; if(RadioButton2->Checked) MediaPlayer1->FileName="2.AVI"; if(RadioButton3->Checked) MediaPlayer1->FileName="3.AVI"; MediaPlayer1->Open(); MediaPlayer1->DisplayRect=Rect(0,0, Panel1->Width,Panel1->Height); MediaPlayer1->Play(); } //--------------------------------------------------------------------------- 4.10 Спрайтовая графика Спрайты - движущиеся картинки небльшого размера, являются одной из главных частей любого игрового или обучающего пакета программ. Рассмотрим несколько примеров использование спрайтов MChSprite из линейки Samples Компонента MchSpriteBgr. Данная компонента представляет собой фон на котором происходит движение спрайтов. Одним из основных свойств MchSpriteBgr является свойство Picture загрузка bmp или gif изображения. Компонента MchSprite. Компонента представляет собой собственно спрайт- небольшое bmp -изображение. Для загрузки изображения используется свойство SprSpriteBitmap. Основные свойства MchSprite: SprHideAfter – виден или нет спрайт после окончания движения (MChSprite1->SprHideAfter=True - не виден); SprSetScaleX масштаб спрайта по оси Х; SprSetScaleY масштаб спрайта по оси Y; Пример изменение масштаба спрайта при приближении: Form1->MChSprite2->SprSetScaleX(1+1.0*sin(alpha)); (где alpha - переменная типа double); SprPosFunc – функция пользователя закон движения спрайта (возвращает координаты движения спрайта) – пример: //функция пользователя для движения спрайта N 2 TPoint __fastcall Sprite2PosFunc(TDateTime AtTime) //AtTime время от начала движения спрайта в секундах { doublex,y,alpha; alpha=2*3.14*(AtTime-Form1->MChSprite2->SprTimeStarted)* 24.0*60.0100.0/(180.0*3.14); x= Form1->MChSpriteBgr1->ClientWidth /2.0 + (Form1->MChSpriteBgr1->ClientWidth /3.0)*cos(alpha); y= Form1->MChSpriteBgr1->ClientHeight /2.0 + (Form1->MChSpriteBgr1->ClientWidth /3.0)*sin(alpha); Form1->MChSprite2->SprSetScale(1+1.0*sin(alpha)); return(Point(int(x),int(y))); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { MChSprite2->SprPosFunc=Sprite2PosFunc;//вызов функции движения MChSprite2->SprCruise(-1); } //--------------------------------------------------------------------------- SprSpriteBitmap – изображение спрайта; SprTrColor – цвет спрайта; SprDragable – если истина, то спрайт можно премещать мышью; SprColliding – если истина, то вызывается функция пользовыателя для обработки столкновения спрайтов; SprRefX, SprRefY соответственно контрольная точка на растре спрайта по оси X и Y; SprRadiusX ,SprRadiusY радиус столкновения спрайта по оси X и оси Y соответственно; Основные методы MchSprite. Метод SprRun (Point (X1, Y1), Point (X2,Y2), t) –делает спрайт видимым при движении из одной позиции (X1, Y1) в другую (X2, Y2) в течении того или иного времени (t). Пример: (MChSprite1->SprRun( Point (MChSpriteBgr1->ClientWidth, 0), Point (0, MChSpriteBgr1->ClientHeight), 3); Метод SprShowOn (); делает спрайт видимым в текущей позиции. Метод SprCruise (-1); делает спрайт видимым и его движение задается функцией пользователя SprPosFunc; Метод SprMoveTo (Point (X, Y); делает спрайт видимым в позиции X, Y. Пример: MChSprite6->SprMoveTo (Point (int ((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); Метод SprShowAt(Point(X,Y); - делает спрайт видимым в точке X,Y. Метод SprHide – делает спрайт невидимым; Метод SprStop – останавливает спрайт; Метод SprGoTo (Point(X,Y),t); - делает спрайт видимым и перемещает в течении t секунд из текущей позиции в позицию X, Y. Метод SprGo (Point (X1, Y1), Point (X2, Y2), t); - делает спрайт видимым при движении из одной позиции (X1, Y1) в другую (X2, Y2) в течении того или иного времени (t). Рассмотрим пример использования спрайтов. На форме расположены 6 спрайтов и 4 кнопки. В зависимости от нажатия той или иной кнопки происходят события – летит ракета, сверхновая звезда сталкивается с Солнцем и происходит взрыв и т.д. Листинг программы: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #include <math.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma link "MChSpBg" #pragma link "MChSprt" #pragma resource "*.dfm" TForm1 *Form1; bool CollideFlip,Exploding; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------------------------- void __fastcall NoCollide(TDateTime AtTime) { if (CollideFlip) MessageBeep(MB_OK); CollideFlip=False; } //---------------------------------------------------------------------- void __fastcall OnCollide(TMChSprite *SprCollided ,TDateTime AtTime) { if (SprCollided==Form1->MChSprite2) { // MessageBeep(MB_ICONASTERISK); if (! CollideFlip) { MessageBeep(MB_ICONASTERISK); CollideFlip=True; } if (Form1->MChSprite4->SprNextPos.x<int((Form1->MChSpriteBgr1->ClientWidth)/2)) { if (Form1->MChSprite4->SprIndex<Form1->MChSprite3->SprIndex) Form1->MChSpriteBgr1->BgrSprIndexExchangeZ(Form1->MChSprite4->SprIndex, Form1->MChSprite3->SprIndex); } else { if (Form1->MChSprite4->SprIndex>Form1->MChSprite3->SprIndex) Form1->MChSpriteBgr1->BgrSprIndexExchangeZ(Form1->MChSprite4->SprIndex, Form1->MChSprite3->SprIndex); } } } //-------------------------------------------------------------------- void __fastcall OnExplosion(TMChSprite *SprCollided ,TDateTime AtTime) { if (Exploding) { // SprCollided->SprStop; // SprCollided->SprHide; } if ((! Exploding) && (SprCollided==Form1->MChSprite1)) { Exploding=True; Form1->MChSprite2->SprColliding=true; Form1->MChSprite5->SprColliding=true; Form1->MChSprite6->SprCruise(20); } } //--------------------------------------------------------------------- TPoint __fastcall Sprite6PosFunc(TDateTime AtTime) { Form1->MChSprite6->SprSetScale(1.25*(AtTime-Form1->MChSprite6->SprTimeStarted) *24.0*60.0*60.0*5.0); // if ((AtTime-Form1->MChSprite6->SprTimeStarted)*24.0*60.0*60.0>17.0) // Form1->MChSprite6->SprHide; return(Point(int((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); } //----------------------------------------------------------------------- TPoint __fastcall Sprite4PosFunc(TDateTime AtTime) { double x,y,alpha; alpha =2*3.14*(AtTime-Form1->MChSprite3->SprTimeStarted)*24.0*60.0+(90.0+0.0)/180.0*3.14; x= Form1->MChSpriteBgr1->ClientWidth /2.0 + (Form1->MChSpriteBgr1->ClientWidth /3.0)*cos(alpha); y= Form1->MChSpriteBgr1->ClientHeight /2.0 + - (Form1->MChSpriteBgr1->ClientWidth /3.0)*sin(alpha); Form1->MChSprite4->SprSetScale(1-0.9*sin(alpha)); return(Point(int(x),int(y))); } //----------------------------------------------------------------------- TPoint __fastcall Sprite3PosFunc(TDateTime AtTime) { double x,y,alpha; alpha=-2*3.14*(AtTime-Form1->MChSprite3->SprTimeStarted)*24.0*60.0+(90.0-180.0)/180*3.14; x= Form1->MChSpriteBgr1->ClientWidth /2.0 + (Form1->MChSpriteBgr1->ClientWidth /3.0)*cos(alpha); y= Form1->MChSpriteBgr1->ClientHeight *0.5 + -(Form1->MChSpriteBgr1->ClientWidth *0.55)*sin(alpha); if (x>=(Form1->MChSpriteBgr1->ClientWidth)/2) Form1->MChSprite3->SprSetScaleX( (1-1.0*sin(alpha))); else Form1->MChSprite3->SprSetScaleX(-(1-1.0*sin(alpha))); if (y<(Form1->MChSpriteBgr1->ClientHeight)/2) Form1->MChSprite3->SprSetScaleY( (1-1.0*sin(alpha))); else Form1->MChSprite2->SprSetScaleY(-(1-1.0*sin(alpha))); return(Point(int(x),int(y))); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { Exploding=false; MChSprite2->SprShowAt(Point(int((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); MChSprite3->SprPosFunc=Sprite3PosFunc; MChSprite4->SprPosFunc=Sprite4PosFunc; MChSprite6->SprPosFunc=Sprite6PosFunc; MChSprite6->SprHideAfter=true; MChSprite6->SprOnCollide=OnExplosion; MChSprite4->SprOnCollide=OnCollide; MChSprite4->SprNoCollide=NoCollide; MChSprite3->SprCruise(-1); MChSprite4->SprCruise(-1); MChSprite6->SprMoveTo(Point(int((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Exploding=false; MChSprite6->SprSetScale(0.2); MChSprite6->SprMoveTo(Point(int((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); Form1->MChSprite1->SprColliding=true; Form1->MChSprite6->SprColliding=true; Form1->MChSprite1->SprGo( Point(Form1->MChSpriteBgr1->ClientWidth,0), Point(int(Form1->MChSpriteBgr1->ClientWidth/2), int(Form1->MChSpriteBgr1->ClientHeight/2)), 5 ); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { MChSprite5->SprColliding=False; MChSprite5->SprHideAfter=True; MChSprite5->SprGo(Point(Form1->MChSpriteBgr1->ClientWidth, Form1->MChSpriteBgr1->ClientHeight), Point(0,0), 20); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { Exploding=false; MChSprite1->SprColliding=false; MChSprite2->SprColliding=false; MChSprite5->SprColliding=false; MChSprite6->SprColliding=false; MChSprite6->SprHide(); MChSprite5->SprHideAfter=true; MChSprite2->SprShowAt(Point(int((Form1->MChSpriteBgr1->ClientWidth)/2), int((Form1->MChSpriteBgr1->ClientHeight)/2))); MChSprite3->SprCruise(-1); MChSprite4->SprCruise(-1); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { Close(); } //--------------------------------------------------------------------------- Используя рассмотренный пример, можно изучить практически все свойства спрайтов. 4.11 Объектная технология OLE2 Фирма Microsoft включила в систему Windows 95 подсистему внедрения и связывания объектов. В общем виде OLE2 позволяет пользователю получить доступ к объектам разработанных систем в приложении, с которым он работает. Например, работая в Word, можно вызвать таблицу Excel, обработать определенным образом данные, а результат запомнить в Word. Кроме этого автоматизация OLE2 позволяет программисту включать в свои программы разработанные компоненты, например Word. Основа OLE2 - Com -компонентная модель объекта (Component Object Model), которая предусматримает модульность и совместитмость во взаимодействии между компонентами, написанными на разных языках. Соm определяет двоичный интерфейс, независимый от языка программирования, который может вступать в коммуникацию с другими компонентами, ничего не зная о них. Объекты, ориентированные на внешнее использование и доступные только из программы называются автоматными объектами OLE. Рассмотрим пример программы, которая вызывает Word, создает новый файл и записывает в Word содержимое компонента Edit. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include <OleAuto.hpp> //подключение средств автоматизации OLE #include "Unit1.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Variant V; try { V = CreateOleObject("Word.Basic"); } catch(...){ ShowMessage("Не могу запусть Word"); throw; Close(); } V.OleProcedure("AppShow"); V.OleProcedure("FileNew"); V.OleProcedure("Insert", Edit1->Text); } //--------------------------------------------------------------------------- Рассмотрим как работает програмы. Прежде всего необходимо указать в программе команду подключения средств, отвечающей за автоматизацию OLE #include <OleAuto.hpp>. Далее по команде V = CreateOleObject ("Word.Basic"); вызываем Word, делаем приложение видимым по команде V.OleProcedure ("File New"); создаем новый документ по команде V.OleProcedure ("File New"). Команда V.OleProcedure ("Insert", Edit1->Text); текст из редактора Edit и записывает в Word). Как видно, в программе используются средства обработки исключений try { V = CreateOleObject("Word.Basic"); } catch(...){ ShowMessage("Не могу запусть Word"); throw; Close(); которые в случае ошибки закрывают программу. Кроме рассмотренного примера для автоматизации OLE существуют стандартные объекты OCX, которые похожи на визуальные компоненты C++Builder, но на самом деле являются самостоятельными объектами. Объекты OCX могут быть включены в программу не только на C++Builder, но и Delphi. Но в отличии от компонент C++Builder - они не включаются в код программы, поэтому при распространении программного обеспечения, использующего компоненты OCX необходимо распространять и сами компоненты OCX. 4.12 Библиотеки DLL DLL (Dinamic Link Library) - динамически присоединяемая библиотека, используется для хранения функций и ресурсов отдельно от исполняемого файла. Обычно в программах статистической сборки функции, ресурсы, процедуры и т.д. компонуются в исполняемом файле, использование же DLL позволяет наиболее часто используемые процедуры, функции или ресурсы хранить в DLL и загружать их из DLL по мере необходимости - это динамическое присоединение. Обычно DLL не загружается в память пока это не необходимо, но будучи загружена, она делает доступными функции, процедуры или ресурсы для любой программы и для любых языков программирования, которые могут работать с DLL. Рассмотрим пример создания DLL, коорая кодирует строку. Кодировка происходит путем сдвигов кода символа на еденицу вправо. Для создания библиотеки SUM.DLL необходимо выполнить шаги: 1.Запустить C++ Builder. и выполнить File|New и выбрать пиктограмму DLL. 2.Набрать код: //Генерирует C++Builder #include <vcl\vcl.h> #pragma hdrstop //--------------------------------------------------------------------------- // Important note about DLL memory management: // // If your DLL exports any functions that pass String objects (or structs/ // classes containing nested Strings) as parameter or function results, // you will need to add the library BCBMM.LIB to both the DLL project and any // EXE projects that use the DLL. This will change the DLL and its calling // EXE's to use the BCBMM.DLL as their memory manager. In these cases, // the file BCBMM.DLL should be deployed along with your DLL. // // To avoid using BCBMM.DLL, pass string information using "char *" or // ShortString parameters. //--------------------------------------------------------------------------- USERES("sum.res"); //--------------------------------------------------------------------------- void ViewForm(); int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) { return 1; } //Набираемый код #include "sum.h" //--------------------------------------------------------------------------- char FileName[256]; void SayHello() { GetModuleFileName( NULL,FileName, sizeof (FileName) ); MessageBox ( NULL, FileName, "Hello from:", MB_OK ); } int Sum(int a,int b) { return a + b; } char* Codec(char *eptr) { int x,l; char ch; l = strlen(eptr); if(eptr[l-1] != '`') { for(x=0; x<l;x++) { ch = eptr[x]; ch = (char)((int)ch + 1); eptr[x] = ch; } eptr[l] = '`'; eptr[l+1] = '\0'; } else { for(x=0; x<l-1;x++) { ch = eptr[x]; ch = (char)((int)ch - 1); eptr[x] = ch; } eptr[l-1] = '\0'; } return eptr; } void InvokeYesNoDialog() { ViewForm(); } //--------------------------------------------------------------------- void ViewForm() { TForm *FpForm = new TForm(Application); FpForm->Left = 10; FpForm->Top = 10; FpForm->Width = 200; FpForm->Height = 200; FpForm->Visible = true; TPanel *FpPanel = new TPanel(FpForm); FpPanel->Parent = FpForm; FpPanel->Left = 2; FpPanel->Top = 2; FpPanel->Width = 198; FpPanel->Height = 198; FpPanel->Visible = true; MessageBox ( NULL,"If YES clicked < OK >", "You finished ?", MB_OK ); if(FpPanel) { delete FpPanel; if(FpForm) delete FpForm; } } 3.Сохранить проект в каталоге под именем sum.mak 4.Выбрать Options|Project...|Directories/Conditionals 5.В поле Conditionals набрать: __BUILDING_THE_DLL 6.В каталоге где находится проект создать файл sum.h со следующим кодом: #ifdef __BUILDING_THE_DLL #define __EXPORT_TYPE __export #else #define __EXPORT_TYPE __import #endif void __EXPORT_TYPE SayHello(); int __EXPORT_TYPE Sum(int a,int b); char* __EXPORT_TYPE Codec(char *eptr); //<------------------ void __EXPORT_TYPE InvokeYesNoDialog(); 7.Откомпилировать командой Project|BuildAll. В результате компиляции получим sum.DLL. 8.Скопировать sum.DLL в каталог ..\BIN C++ Builder-а 9.Набрать в командной строке implib.exe -w sum sum.dll 10.В результате получим файл sum.lib который будет подключен к выполняемому проекту 11.Скопировать sum.lib в каталог с проектом. Рассмотрим пример вызова DLL из программы. В программе имеются визуальные компоненты:Edit (для ввода строки), Button1 (шифровка-дешифровка строки), Button2 - выход из программы. Программа работает следующим образом - после запуска вводится строка и нажимается кнопка Button1 - в результате строка шифруется, далее снова нажимается кнопка Button1 - строка дешифруется. //--------------------------------------------------------------------------- #ifndef projcodH #define projcodH //--------------------------------------------------------------------------- #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TEdit *Edit1; TButton *Button1; TButton *Button2; void __fastcall Button1Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern TForm1 *Form1; //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "projcod.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; #include "sum.h" //<<--------------ВНИМАНИЕ----- //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char* szMyStr = new char [StrLen(Edit1->Text.c_str())+2]; *szMyStr = 0; StrLCat(szMyStr, Edit1->Text.c_str(), StrLen(Edit1->Text.c_str())); Edit1->Text = Codec(szMyStr); //<---------------- delete [] szMyStr; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { Application->Terminate(); } //---------------------------------------------------------------------------
К исполняемому файлу (код которого приведен выше) подключить sum.lib выполнив команду: Project\Add to Project\.....sum.lib Запустить проект на компиляцию и выполнение. Возможен вызов DLL из DLL, или вызов DLL из другого языка программирования. 4.13 Разработка визуальных компонент C++ Builder позволяет расширять библиотеку VCL собственными компонентами. Рассмотрим на примере разработку визуальной компоненты, которая позволяет перемножать два числа, если их значение не превышает 100. Прежде всего отметим, что разрабатываемая компонента TMul потомок Tcomponent. Расположим ее на странице Samples библиотеки VCL. Разрабатывемый компонент содержит свойства методы и события. Свойства используются для задания определенных атрибутов (полей данных) и инициализации некоторых действий. Методы - это процедуры и функции, которые делают класс общедоступным. События используются для расширения компонентов в случае, когда возникают события (например, щелчок мыши). Разрабатываемый компонент имеет два свойства типа int (Val1 - первый сомножитель, Val2- второй сомножитель) один метод DoMult (перемножает сомножители) и событие TooBig (вызывет обработчик событий, когда один из сомножителей больше 100). При вызове метода, два значения в свойствах перемножаются и результат становится значением третьего свойства (Res). Событие TooBig реализуется, если хотя бы одно из чисел больше 100. Рассмотрим этапы построения TMul. 1.Создадим модуль, используя раздел Component/ New Главного меню. В закладке Component Wizard указываем Calss Name: – TMul, Ancestor type: - Tcomponent, Pallette Page: Samples. В результате будет сгенерирован код, который включает стандартную оболочку объявления класса TMul, и поцедуру регистрации класса. Далее модуль сохраняется под именем Mul.cpp. В результате будет созданы файлы: Mul.cpp и Mul.h. 2.Добавление свойств к компоненту. Открываем файл Mul.h и записываем код. В разделе private определим свойства FVal1, FVal2 - доступные в процессе проектирования и в процессе выполнения и свойство Res -доступное в процессе выполнения. Также описываем событие TooBig, для определения которого используется тип TnitifyEvent – обобщенный указатель на функцию, которая передает один параметр типа Tcomponent – this.
class TMul : public TComponent { private: int FVal1; int FVal2; int FRes ; TNotifyEvent FTooBig; Далее определим свойства (__property): Res в разделе public, т.к. оно доступно во время выполнения и свойства Val1 и Val2 в разделе __published, т.к. они доступны во время проектирования. Определяем прямой доступ для чтения и записи (read,write) значений переменных в разделе __published Для переменной FRes прямой доступ не нужен,т.к. свойство Res предназначено только для чтения. class TMul : public TComponent { private: int FVal1; int FVal2; int FRes ; TNotifyEvent FTooBig; protected: public: __fastcall TMul(TComponent* Owner); void __fastcall DoMult(void); //свойство для получения результата __property int Res = { read=FRes, write=FRes }; __published: //первый операнд __property int Val1 = { read=FVal1, write=FVal1, default = 1 }; //второй операнд __property int Val2 = { read=FVal2, write=FVal2, default = 1 }; //событие __property TNotifyEvent TooBig = { read=FTooBig, write=FTooBig}; }; Далее загружаем файл Mul.cpp и добавляем код. 3.Добавление конструктора. Для создания представителя класса вызывается конструктор. Конструктор также: - устанавливает значения по умолчанию для переменных класса; - отвечает за динамическое распределение памяти и получение ресурсов, необходимых классу; - вызывается при добавлении компонента к форме во время проектирования или во время выполнения. __fastcall TMul::TMul(TComponent* Owner) : TComponent(Owner) { FVal1 = 1; FVal2 = 1; } 4.Добавление метода. Для добавления метода необходимо написать код: void __fastcall TMul::DoMult(void) { if((Val1 < 100) && (Val2 < 100)) Res = FVal1 * FVal2; else if (FTooBig) TooBig(this); } 5.Добавление событие. Событие предоставляет программисту задать специальный код, исполняемый когда что-то происходит. В нашем случае добавляется событие TooBig, которое запускается когда значение Val1 или Val2 больше 100. Обработка события осуществляется в строке: if (FTooBig) TooBig(this); Если нашем случае FtooBig=TRUE то происходит обработка события. Листинг файла Mul.h //--------------------------------------------------------------------------- #ifndef mulH #define mulH //--------------------------------------------------------------------------- #include <vcl\SysUtils.hpp> #include <vcl\Controls.hpp> #include <vcl\Classes.hpp> #include <vcl\Forms.hpp> //--------------------------------------------------------------------------- class TMul : public TComponent { private: int FVal1; int FVal2; int FRes ; TNotifyEvent FTooBig; protected: public: __fastcall TMul(TComponent* Owner); void __fastcall DoMult(void); __property int Res = { read=FRes, write=FRes }; __published: __property int Val1 = { read=FVal1, write=FVal1, default = 1 }; __property int Val2 = { read=FVal2, write=FVal2, default = 1 }; __property TNotifyEvent TooBig = { read=FTooBig, write=FTooBig}; }; //--------------------------------------------------------------------------- #endif Листинг файла Mul.cpp: //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "mul.h" //--------------------------------------------------------------------------- static inline TMul *ValidCtrCheck() { return new TMul(NULL); } //--------------------------------------------------------------------------- __fastcall TMul::TMul(TComponent* Owner) : TComponent(Owner) { FVal1 = 1; FVal2 = 1; } //--------------------------------------------------------------------------- namespace Mul { void __fastcall Register() { TComponentClass classes[1] = {__classid(TMul)}; RegisterComponents("Samples", classes, 0); } } //--------------------------------------------------------------------------- void __fastcall TMul::DoMult(void) { if((Val1 < 100) && (Val2 < 100)) Res = FVal1 * FVal2; else if (FTooBig) TooBig(this); } //--------------------------------------------------------------------------- Далее компонент необходимо оттранслировать и включить в библиотеку компонент. Для включения компонента в библиотеку необходимо: - в главном меню выбрать Component/Install; - в появившемся окне Install Components выбрать опцию Add; - зайти в папку где находится компонента, выбрать файл Mul.cpp и щелкнуть мышью по кнопке OK. Компонента установится на странице Samples. Для тестирования компонента используем программу. На форму поместим три компоненты Edit, три Label, Button и Mul. Щелкнем по кнопке Button и запишем код. Mul1->Val1 = StrToInt(EdVal1->Text); Mul1->Val2 = StrToInt(EdVal2->Text); Mul1->DoMult(); EdRes->Text = IntToStr(Mul1->Res); Далее щелкаем мышью справа от события TooBig сомпонеты Mul и записываем код: Form1->Caption="Переполнение"; Общий вид программы: //--------------------------------------------------------------------------- #pragma link "mul" #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Mul1->Val1 = StrToInt(EdVal1->Text); Mul1->Val2 = StrToInt(EdVal2->Text); Mul1->DoMult(); EdRes->Text = IntToStr(Mul1->Res); } //--------------------------------------------------------------------------- void __fastcall TForm1::Mul1TooBig(TObject *Sender) { Form1->Caption="Переполнение"; } //--------------------------------------------------------------------------- Далее программу можно выполнить. Кроме рассмотренного примера возможна модификация существующих визуальных компонент и получение новых компонент на базе существующих. 4.14 Введение в программирование CGI Важнейшей особенностью web-сервера является возможность получения информации от пользователя и обработка ее с помощью CGI(общий шлюзовый интерфейс). CGI – компонент программного обеспечения, который: - позволяет писать сценарии и создавать на их основе управляемые пользователем интерактивные приложения; - обеспечивает возможность взаимодействовать с другими программами сервера; - имеет средства динамического создания Web-страниц на основе информации пользователя; - может вызвать внешнюю программу и передать в нее пользовательские данные. В конечном итоге пользователь, не будучи ограничен в рамках заранее написанных документов, используя CGI сценарий может создать круг приложений, которые ему необходимы для получения информации. Программе CGI необходимо извлечь информацию, обработать ее и сообщить ответ. Рассмотрим простой пример – создание виртуального документа: Запускаем C++Builder. В главном меню выбираем File/New и в открывшейся таблице New Items выбираем Console App. Удаляем все, что находится в открывшемся окне и записываем код: //Пример CGI #include <vcl\vcl.h> #pragma hdrstop char S[] = "ПРИВЕТ<P>\n"; void main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("\nContent-type: text/html\n\n"); printf("<html>\n"); printf("<body>\n"); printf(S); printf("</body>\n"); printf("</html>\n"); } Запоминаем программу и запускаем на трансляцию. После этого помещаем выполняемый файл (например Prim.exe) на Web-сервер в раздел CGI. Запускаем броузер и вызывем программу из раздела CGI. Во время выполнения программа посылает свою информацию на стандартный вывод, как поток данных, состоящий из двух частей: - заголовок в котором описан формат возвращаемых данных printf("\nContent-type: text/html\n\n"); - текст формата HTML; - тело выходных данных – данные в формате HTML. Сервер возвращает результаты выполнения CGI-программы броузеру. Для Web-сервера неважен язык, на котором написана программа. 4.15 Программирование баз данных Особенностью разработки доступа к базам данных при помощи C++Builder является полностью объектный визуальный подход. Вторая особенность заключается в том, что доступ к разным базам данных (dBase, Paradox, Oracle и другим) происходит совершенно унифицированно. Эта дает определенные преимущества по сравнению с таким мощным средством разработки баз данных как Visual FoxPro. Интерфейсом между приложением и базами данных служит BDE - Borland Database Engine, т.е Механизм База данных Borland. Он позволяет не только осуществлять доступ к локальным базам данных, но и осуществлять доступ к данным по типу клиент-сервер. Одной из особенностей C++Builder является то,что доступ к базам данных осуществляется одинаково как на стадии разработки, так и на стадии исполнения. Для разработки используется трех ступенчатая модель. Каждой ступени соответствует своя компонента (класс). TDBGrid - отображает в окне область редактирования (по типу команды Browse (Grid) в FoxPro) TdataSource - служит интерфейсом между TDBEdit и нижней компонентой. TTable - содержит все записи таблици. На более низком уровне работают драйвера BDE, осуществляющие непосредственный обмен с хранимыми данными. Расмотрим более подробно описанные выше компоненты. Источник данных (TDataSource). Основные свойства. AutoEdit - разрешает или запрещает редактирование записей (False или True). DataSet - определяет имя конкретного набора данных, таблицы или запроса. Например Table1. Можно переключаться с одного набора данных на другой непосредственно во время выполнения программы. Name - определяет название данной компоненты. Например DataSource. С данной компонентой связаны три события OnDataChange, OnStateChange, OnUpdateData. Таблица (TTable). Основные свойства. Active - разрешает или запрещает просмотр данных. DatabaseName - псевдоним базы данных и путь к каталогу, где база данных содержится. Name - псевдоним таблицы. TableName - конкретное имя таблицы (в указанном каталоге). Exclusive - разрешает или запрещает другому приложению обращаться к таблице. IndexFiles - выбор индексного файла для таблицы. IndexName - правило сортировки данных в индексе. Filter - установка фильтра. MasterFields и MasterSource - для образования связей между двумя таблицами. Для данной компоненты определены стандартные методы используемые другими системами управления баз данных: Locate, Lookup, Append, Insert, Next, MoveBye и другие. Отображение данных (TDBGrid). Некоторые свойства. DataSource - совпадает со свойством Name компоненты TDataSource. В результате имеем следующую связку: TDBGrid TDataSource TTable ----------------------------------------------------------------------------------------- DataSource <--> Name-DataSet <--> Name -DatabaseName-TableName Такая связка обеспечит в окне редактирования появления записей таблицы имеющей имя TableName (см.ниже). Другие свойства данной компоненты тривиальны - они определяют положение окна редактирования, цветовую гамму, размера окна, шрифты и т.п. Еще одна компонента часто используется в связи с работой с базами данных. Запросы (Tquery). Данная компонента понадобится вам, если у вас возникнет необходимость пользоваться командами SQL-языка. Основные свойства. Active - разрешает или запрещает просмотр данных. DatabaseName - содержит псевдоним базы данных или полный путь к каталогу, где эта база данных находится. SQL - используется для ввода команды SQL. Синтаксис этих команд является стандартом и с ним можно познакомиться в любом руководстве по СУБД (например FoxPro). RequestLive - разрешает или запрещает BDE возращать набор данных в любом случае. Приступаем теперь к разбору конкретного примера. Пример очень прост. При запуске программы появляется форма. На форме имеется окно редактирования таблицы, кнопка "Выход", кнопка "Добавить", кнопка "Удалить". Таблица содержит данные о людях: ФИО, выданная сумма, дата выдачи. У вас будет имется возможность редактировать таблицу, добавлять и удалять записи. При запуске программа проверяет наличие таблицы в указанном каталоге и при необходимости создает и индексирует эту таблицу. Поместим на форму три кнопки, TDBGrid (для просмотра таблицы), TTable, TDataSource.. Пусть наша таблица будет находится в каталоге c:\baza\ и будет иметь имя 'proba' Заполним свойства трех компонент основных компонент: TTable : DatabaseName=c:\baza\ Name=Table1 TableType=ttDBase TableName=proba IndexName=proba TDataSource: DataSet=Table1 Name=DataSource1 TDBGrid: DataSource=DataSource1 Все остальные свойства можно оставить по умолчанию и менять по ходу работы над программой. Приведем текст модуля Unit1.cpp содержащего основные исполняемые функции. //--------------------------------------------------------------------------- #include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma link "Grids" #pragma resource "*.dfm" TForm1 *Form1; char * baza="proba.dbf"; char * baza1="proba"; //--------------------------------------------------------------------------- //Конструктор __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { HANDLE handle; //пытаемся открыть таблицу как обычный файл handle=CreateFile(baza, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if(handle!=INVALID_HANDLE_VALUE) { //файл есть и закроем CloseHandle(handle); } else { //файла нет //создаем таблицу Table1->FieldDefs->Clear(); Table1->FieldDefs->Add("Name", ftString, 20, true); Table1->FieldDefs->Add("Zarpl", ftFloat, 0, true); Table1->FieldDefs->Add("Data", ftDate, 0, true); //теперь создаем индексный файл Table1->IndexDefs->Clear(); TIndexOptions MyIndexOptions; MyIndexOptions << ixPrimary << ixUnique; Table1->IndexDefs->Add(baza1, "Data", MyIndexOptions); Table1->CreateTable(); } //здесь открыть таблицу Table1->Active = true; //активизировать индекс Table1->OpenIndexFile("proba.MDX"); } //---------------------------------------------- //Деструктор __fastcall TForm1::~TForm1() { //закрыть таблицу Table1->Active = false; } //--------------------------------------------------------------------------- //выход void __fastcall TForm1::Button1Click(TObject *Sender) { //метод Close закрывает форму и вызывает деструктор Close(); } //--------------------------------------------------------------------------- //кнопка "добавить запись" void __fastcall TForm1::Button3Click(TObject *Sender) { Table1->Append(); } //--------------------------------------------------------------------------- //кнопка "удалить запись" void __fastcall TForm1::Button2Click(TObject *Sender) { Table1->Delete(); } Прокоментируем вышеприведенный текст. Отметим одну интересную возможность C++Builder. Он может одновременно работать как с библиотекой объектов с которой мы до сих пор работали так и непосредственно с функциями API. В нашей программе мы демонстрируем эту возможность: в конструкторе пытаемся открыть файл (функция CreateFile). Если открытие не удается, выполняется часть кода создающий таблицу и индексный файл. Ниже представлено окно работы нашей программы. 4.16 ПАКЕТЫ Существенную роль в разработке приложений на языке C++Builder 3 и выше играют пакеты(packages), которые представляют собой библиотеки динамической компоновки, содержащие компоненты C++Builder из библиотеки визуальных компонент(VCL). Применение пакетов позволяет умсеньшить размеры исполняемых (.EXE- файлов, однако в таком случае вместе с программой необходимо распространять эти библиотеки. Существует несколько видов пакетов- выделим из них пакеты времени выполнения(ПВВ). Пакеты данного вида используются: когда приложение выполняется. ПВВ могут связываться статически и динамически. При статическом связывании образуется выполняемый файл, для которого не нужны библиотеки с расширением (.BPL). Связывание в пакет динамически ведет к тому, что библиотеки .BPL должны распространяться с приложением. В C++Builder 3 и выше в меню Project/Options/Packages есть опция Build with runtime packages. Если флажок возле этой опции установлен, то приложение необходимо распространять вместе с библиотеками .BPL, если флажок не установлен, то получается полностью скомпилированный выполняемы файл. Динамическое связывание применяется при конструировании приложений, состоящих из большого количества выполняемых файлов. Заключение В представленном учебном пособии были рассмотрены некоторые вопросы программирования на языке С++ и C++Builder. Для углубленного изучения языка программирования Си необходимо изучить литературу, приведенную ниже. Литература: 1. Уэйт М., Прата С., Мартин Д. Язык Си. Руководство для начинающих. - М.: Мир, 1988.- 512 с. 2. Страуструп Б. Язык программирования СИ++. - М.: Радио и связь,1991.352с. 3. Бруно Бабэ Просто и ясно о BORLAND С++.-М.:БИНОМ,1995.-400с. 4.Бочков С.О., Субботин Д.М. Язык программирования Си для персонального компьютера. -М.: Радио и связь,1990.-384с. 5. Ричард Вайнер, Льюс Пинсон С++ изнутри. - Киев: ДиаСофт,1993.-304с. 6. Лукас П. С++ под рукой. - Киев:ДиаСофт, 1993.-176с. 7. Стефан Дьюхарст, Кэти Старк Программирование на С++.- Киев: ДиаСофт, 1993.-272с. 8. Белецкий Я. Турбо Си++: Новая разработка. - М.: Машиностроение, 1994. -400с. 9. Том Сван. Программирование для WINDOWS в BORLAND C++.-М.:БИНОМ, 1995.-400с 10. Шилд Г. Программирование на С и С++ для WINDOWS 95.-К.:Торгово- издательское бюро BHV, 1996.-400с. 11. Круглински Дэвид. Основы Visual C++.-М.:Русская Редакция ТОО Channel Trading Ltd, 1997.-696с. 12. Шилдт Герберт. MFC: основы программирования. - К.: Издательская группа BHV, 1997.-560с. 13. Д. Кнут. Искусство программирования для ЭВМ. Т. 3. Сортировка и поиск. - М.: Мир, 1978. 14. Б. Керниган, Д. Ритчи. Язык программирования Си. - М.: Финансы и статистика, 1992. 15. Р.Андерсон. Доказательство правильности программ. М.: Мир, 1984. 16. Шилд Герберт. Самоучитель С++.-BHV.-С.-Петербург.,1997.-512с. 17. Шамис В.А. Borland C++ Builder. Программирование на С+ без проблем М.: Нолидж, 1997.-266с. 18. Елманова Н.З., Кошель С.П. Введение в Borland C++ Builder.-М.: Диалог. - МИФИ, 1997.-272с. 19. Рейсдорф К. Освой самостоятельно Borland C++Builder. М.: БИНОМ, 1998.-702с. 20. Рамодин Д. Создание приложений в среде Borland C++Builder, Мир ПК №10,11,12-1997г. 21. Калверт Чарльз C++Builder. Энциклопедия пользователя. 22.Оузьер Д., Гробман С., Батсон С. Delphi2. Освой самостоятельно.- М.:Восточная Книжная Компания,1997.-624с. 23. Пирогов В.Ю. Программирование на языке ассемблера для операционных систем MS DOS и Windows, 1999, -550c. 24. Калверт Чарльз и др. Borland C++Builder 3.Самоучитель.-К:ДиаСофт, 1999.-272с. 25. Московские олимпиады по программированию,М.-1988.-208с. ПРИЛОЖЕНИЕ N1 Задача N1. Определить, какая из точек плоскости A(x1,y1), B(x2,y2), C(x3,y3) расположена ближе к началу координат. #include<stdio.h> #include<conio.h> #include<math.h> int main(void) { char *n[30]; int x1,y1,x2,y2,x3,y3,p1,p2,p3,k; clrscr(); printf("Введите координаты первой точки\n"); printf("x1=",&x1); scanf("%d",&x1); printf("y1=",&y1); scanf("%d",&y1); printf("Введите координаты второй точки\n"); printf("x2=",x2); scanf("%d",&x2); printf("y2=",y2); scanf("%d",&y2); printf("Введите координаты третьей точки\n"); printf("x3=",x3); scanf("%d",&x3); printf("y3=",y3); scanf("%d",&y3); p1=sqrt(x1*x1+y1*y1); p2=sqrt(x2*x2+y2*y2); p3=sqrt(x3*x3+y3*y3); k=p1; *n="первая"; if(k>p2) { k=p2;*n="вторая"; } if(k>p3) { k=p3;*n="третья"; } printf("%s точка ближе всех к началу координат \n",*n); getch(); return(0); } Задача N2. Можно ли раменять 25 рублей 9 купюрами достоинством в 1, 3 и 5 рублей? #include<stdio.h> int main(void) { int x,y,s,z; s=0; for(x=1;x<=10;x++) { for(y=1;y<=10;y++) { for(z=1;z<=10;z++) { if (( x+3*y+5*z==25) & (x+y+z==9)) { printf("Можно %d-1p, %d-3p, %d-5p\n",x,y,z); s=1; } } } } if (s==0) { printf("25 рублей нельзя разменять 9 купюрами в 1р,3p,5p");} return(0); } Задача N3. Определить в одномерном числовом массиве число соседств из двух чисел разного знака. #include<stdio.h> int main(void) { int i,j,s,n,a[10]; printf("Введите эл-ты массива \n"); for (i=1;i<=10;++i) scanf("%d",&a[i]); s=0; for (i=1;i<=9;++i) { if (a[i]* a[i+1]<0) s=s+1; } printf ("Число эл-тов разного знака =%d \n",s); } Задача N4. Определить, является ли одномерный числовой массив упорядоченным по убыванию. #include<stdio.h> #include<dos.h> int main(void) { int i,j=0,n=10,s=0; int a[10]; printf("Введите элементы массива\n"); for (i=1;i<=n;++i) {scanf("%d",&a[i]);} for (i=1;i<=n-1;++i) { if (a[i]<a[i+1]) s=1;} if (s==1) {printf ("Массив не упорядочен по убыванию \n");} else {printf ("Массив упорядочен по убыванию \n");} } Задача N5. Найти сумму чисел, обратных квадратам первых 100 натуральных чисел. #include<stdio.h> #include<dos.h> int main(void) { float i,s=0; printf ("-----------------------------\n"); for (i=1;i<=100;i++) { s=s+1/(i*i);} printf ("Сумма чисел равна %f\n",s); delay(5000); } Задача N6. Дана точка А с координатами x,y. Определить в каком квадранте координатной оси она расположена. #include<stdio.h> #include<conio.h> #include<stdlib.h> int main(void) { char xs[10],ys[10]; int x,y; clrscr(); printf ("Введите координаты т.А \n"); gets(xs); gets(ys); x=atoi(xs); y=atoi(ys); if (x==0) printf ("Точка на оси Х\n"); if (y==0) printf ("Точка на оси Y\n"); if (x>0 && y>0) printf ("Точка А в 1-й четверти \n"); if (x<0 && y>0) printf ("Точка А в 2-й четверти \n"); if (x<0 && y<0) printf ("Точка А в 3-й четверти \n"); if (x>0 && y<0) printf ("Точка А в 4-й четверти \n"); getch(); } Задача N7. Вы забрели в сад, в котором три калитки и решили пройти через них не пропустив ни одной. Набрав некоторое количество яблок, вы отдали половину всех яблок и еще поляблока человеку у первой калитки, половину того что осталось и еще поляблока человеку у второй калитки, а половину всех оставшихся яблок и еще поляблока человеку у третей калитки и при этом не разрезали ни одного яблока. Сколько яблок вы должны набрать в саду чтоб вынести X яблок? #include<stdio.h> int main(void) { int k,n,i,x; printf("\n Задача про яблоки .\n"); printf("Введите количество яблок,которое нужно вынести\n"); scanf("%d",&k); n=k; for (i=1;i<=3;i++) { x=2*k+1; k=x; } printf("%d нужно взять\n",k); return(0); } Задача N8. Написать программу вычисления числа сочетаний из m по n(формула m!\n!*(m-n)! при m>=n). #include<stdio.h> int main(void) { int i,m,n; float f1=1,f2=1,f3=1,c; printf ("Введите m: "); scanf ("%d",&m); for (i=1;i<=m;++i) { f1=f1*i; } printf ("Введите n: "); scanf ("%d",&n); for (i=1;i<=n;++i) { f2=f2*i; } if (m>=n) { for (i=1;i<=m-n;++i) { f3=f3*i; } c=f1/(f2*f3); printf ("c=%f \n",c); } else printf (" m должно быть больше, либо равно n! \n"); } Задача N9. Слова и фразы, переходящие в себя при "аккустическом" отображении называются палиндромами. Написать программу определяющую является слово палиндром или нет. #include<stdio.h> #include<conio.h> #include<string.h> int main(void) { char A[80],B[80]; int i,L; clrscr(); printf ("Введите слово A: "); scanf ("%s",&A); L=strlen(A); for (i=0;i<=L;++i) B[i]=A[L-i-1]; B[L]='\0'; i=strcmpi(A,B); if (i==0) printf ("Слово-полиндром \n"); else printf ("Слово-не полиндром \n"); getch(); } Задача N10. Определить находится ли точка M(x,y) внутри верхней части единичного круга с центром в начале координат. #include<stdio.h> int main(void) { float x,y,z; printf("Введите координаты точки\n"); scanf("%f",&x); scanf("%f",&y); z=x*x+y*y; printf("%f %f\n",z,y); if ((z<=1)&&(y>0)) printf("Точка принадлежит верхней части единичного круга\n"); else printf("Точка не принадлежит верхней части единичного круга\n"); return(0); } Задача N11. Составить программу для решения квадратного уравнения Ax^2+Bx+C=0. #include<stdio.h> #include<math.h> #include<dos.h> int main(void) { int i,d,a,b,c; float x1,x2; printf("\n Решение квадратного уравнения\n"); printf("Ведите коэффициенты уравнения a,b,c\n"); printf("a=");scanf("%d",&a); printf("b=");scanf("%d",&b); printf("c="); scanf("%d",&c); d=b*b-4*a*c; printf("d=%d\n",d); if(d<0) printf("корней нет\n"); else { if(d>0) { x1=(-b+sqrt(d))/2*a; x2=(-b-sqrt(d))/2*a; printf("корни уравнения x1=%f x2=%f\n",x1,x2);} else { x1=(-b)/(2*a); printf("корень уравнения x1=%f \n",x1); } } delay(5000); return(0); } Задача N12. Составить программу, позволяющую узнать войдет ли открытка размерами A,B в конверт размерами X,Y. #include<stdio.h> #include<dos.h> int main() { int t,a,b,x,y; printf("Введите размер a открытки :\n"); scanf("%d",&a); printf("Введите размер b открытки:\n"); scanf("%d",&b); printf("Введите размер x конверта :\n"); scanf("%d",&x); printf("Введите размер y конверта :\n"); scanf("%d",&y); if(b>a) {t=a;a=b;b=t;} if(y>x) {t=x;x=y;y=t;} if ((x>=a) &(y>=b)) printf("Открытка войдет в конверт .\n"); else printf("Открытка не войдет в конверт .\n"); delay(3500); return 0; } Задача N13. Существует ли четырехзначное натуральное число, куб суммы цифр которого равен ему самому? #include <stdio.h> #include <math.h> #include<conio.h> int main(void) { int j1,j2,j3,j4; float p,t,q=3; for(j1=1;j1<=9;j1++) { for(j2=1;j2<=9;j2++) { for(j3=1;j3<=9;j3++) { for(j4=1;j4<=9;j4++) { p=(j1*1000+j2*100+j3*10+j4); t=pow((j1+j2+j3+j4),q); if (p==t) { printf("Четырехзначное число, равное кубу суммы своих цифр\n"); printf("%f\n",p); } } } } } getch(); return(0); } Задача N14. Пара носков стоит 1.05 руб. связка -12 пар стоит 10.25 руб. а коробка - 12 связок стоит 114 руб. Составить программу, которая по числу N пар носков вычисляет числа n1, n2, n3 коробок, связок и пар носков, которые следует купить. Пояснение - вместо 11 пар носков покупать связку - это дешевле. Оптимальная покупка без излишков находится следующим образом: n1=n\144 : m=n-n1*144 n2=m\12 : n3=m-n2*12 Удешевить ее можно лишь двумя способами - либо взять лишнюю связку и не брать пар, либо лишнюю коробку и не брать ни связок, ни пар. Если n3*1.05>10.25, то надо взять лишнюю связку: n2=n2+1 : n3=0 Если для получившейся покупки (старой или изменившейся) бу- дет n2*10.25+n3*1,05>114.00, то надо сделать n1=n1+1 : n2=0 : n3=0 Это решение пригодно для любых цен (при которых коробка дешевле, чем 12 связок, а связка - чем 12 пар). #include <stdio.h> int main(void) { int n1,n2,n3,n,m; scanf("%d",&n); n1=n/144; m=n%144; n2=m/12; n3=m%12; if (n3*105>1025) {n2++;n3=0;} if (n2*1025+n3*105>11400){n1++;n2=n3=0;} printf("Для покупки %d пар надо купить %d кор.,%d св.%d пар \n",n,n1,n2,n3); } Задача N15. Имеется k селений. Если в селении i расположить пункт скорой помощи, то поездка по вызову в селение j займет время a[i,i]+a[i,j], где 1<=i,j<k,i не равно j. Найти номер селения i, поездка от которого в самое отдаленное(по времени) селение занимала бы минимальное время. Массив a[1:k,1:k] задан, в нем все a[i,j]>0;и a[i,j] м.б. не равно a[j,i]. Сформулируем эту задачу следующим образом. В каждой строке i массива A выберем максимальное среди чисел A[i, j] (j не равно i) и сложим его с A[i, i]. Найти i, при котором соответствующая сумма будет минимальна. #include <stdio.h> #define K 20 main() {int i,j,k,i1,s,t,a[K][K]; scanf("%d",&k); for (i=0;i<k;i++) for(j=0;j<k;j++) scanf("%d" ,&a[i][j]); for (i=0;i<k;i++) {for (s=0,j=0;j<k;j++) if(j!=i && s<a[i][j]) s=a[i][j]; s+=a[i][i]; if (i==0 || s<t) {i1=i; t=s;} } printf("%d\n" ,i); } Задача N16. Даны два упорядоченных массива a[1]<=a[2]<=...<=a[m] и b[1]<=b[2]<=...<=b[n]. Образовать из них упорядоченный массив c[1]<=c[2]<=...c[m+n]. Эта задача принципиально должна быть выполнена за m+n действий. Возьмем из A и B по первому элементу. Меньший из них занесем в С и заменим следующим из его же массива. Снова выберем меньший из двух, занесем в С и т.д. После каждого сравнения в С добавляется элемент - значит, сравнений будет меньше, чем m+n. Нужно только позаботиться о том, чтобы программа работала верно и при исчерпании одного из массивов. #include <stdio.h> #define mm 100 #define nn 100 main() { int m,n,i,j,k,a[mm],b[nn],c[mm+nn]; scanf("%d %d",&m,&n); for (i=0;i<m;i++) scanf("%d",&a[i]); for(j=0;j<n;j++) scanf("%d",&b[j]); for(k=i=j=0;k<m+n; k++) {if (i>=m) c[k]=b[j++]; else if (j>=n) c[k]=a[i++]; else if (a[i]>b[j]) c[k]=b[j++]; else c[k]=a[i++]; } for (k=0;k<m+n; k++) printf("%4d%c",c[k],(k+1)%10==0 || k==m+n-1?'\n':' '); } Задача N17. На квадратном клетчатом листе бумаги размерами 100х100 нарисовано несколько прямоугольников: каждый из прямоугольников состоит из некоторого числа целых клеток, причем различные прямоугольники не накладываются и не соприкасаются друг с другом. Написать программу, которая сосчитает и напечатает число прямоугольников. Эта задача решается следующим образом – прямоугольников столько, сколько их северо-западных углов (иначе говоря - верхних левых). Остается только не запутаться в случае, когда угол стоит у границы. #include <stdio.h> #define mm 20 #define nn 20 main() { int m,n,j,i,s,a[mm][nn]; scanf("%d %d",&m,&n); for(i=0; i<=m;i++) for (j=0;j<n; j++) if (a[i][j]=i*j) scanf("%d" ,&a[i][j]); for (s=0,i=1;i<=m; i++) for (j=1;j<=n; j++) if (a[i][j] && !(a[i-1][j]+a[i][j-1])) s++; printf ("%d\n",s); } |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||