Главная страница
Навигация по странице:

  • 1.11.2 Преобразования символьных строк: atoi(), atof()

  • 1.11.3 Функции, работающие со строками

  • 1.12 Особенности программирования на языке Си 1.12.1 Модули. Многомодульное программирование

  • 1.12.3 Программирование в DOS и Windows

  • 1.12.4 Использование языка ассемблера в программах на Си

  • Глава 2. Примеры использования языка Си 2.1 Сортировка

  • 2.3 Задача Ханойские башни

  • Глава 3. Основы С++ 3.1 Отличия С++ от С

  • 3.2 Объектно-ориентированное программирование в С++

  • 3.2.2 Перегрузка функций

  • 3.2.5 Конструкторы с параметрами

  • 3.2.6 Введение в наследование

  • 3.2.7 Виртуальные функции

  • 3.2.8 Указатели на объекты

  • Глава 4. Основы программирование на языке C ++ Builder 4.1 Характеристика С++ Builder

  • 4.2 Компоненты VCL. Свойства, события, методы

  • День до сдачи работы мы справимся, и ты получишь Отлично по своему предмету! Только представь ты занимаешься своим любимым делом, пока твои лохиодногруппники теряют свои нервные клетки Проникнись Это бесценное ощущение Курсовая,


    Скачать 389.95 Kb.
    НазваниеДень до сдачи работы мы справимся, и ты получишь Отлично по своему предмету! Только представь ты занимаешься своим любимым делом, пока твои лохиодногруппники теряют свои нервные клетки Проникнись Это бесценное ощущение Курсовая,
    Дата04.07.2021
    Размер389.95 Kb.
    Формат файлаdocx
    Имя файлаUchebnoe_posobie_Osnovy_C.docx
    ТипРешение
    #223314
    страница6 из 9
    1   2   3   4   5   6   7   8   9

    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

    #include

    main()

    {

    char Z,Y='A';

    printf("Было %c\n ",Y);

    Z=tolower(Y);

    printf("Стало %c\n ",Z);

    }
    1.11.2 Преобразования символьных строк: atoi(), atof()

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

    Для этого используются функции atoi() и atof(). Первая преобразует строку в целое, вторая - в число с плавающей точкой. Пример:

    #include

    #include

    main()

    {

    char ch[10];

    int integ;

    puts("Введите, пожалуйста, целое");

    gets(ch);

    integ=atoi(ch);

    printf("Число было %d.\n", integ);

    }
    1.11.3 Функции, работающие со строками

    strlen()- находит длину строки;

    strcat()- объединяет две строки;

    strcmp()- сравнивает содержимое строк;

    strcpy()- копирует строки.

    Рассмотрим пример.
    #include
    #include
    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

    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

    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
    {

    if(c1
    }

    else

    {

    if(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

    #include

    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

    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

    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

    #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
    {

    printf("Элемент\n");

    scanf("%d",&a[i]);

    }

    printf("Было\n");

    for (i=0;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
    printf("%d\n",a[i]);

    }

    С о р т и р о в к а в ы б о р о м.

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

    #include

    #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
    {

    printf("Элемент\n");

    scanf("%d",&a[i]);

    }

    printf("Было\n");

    for (i=0;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
    printf("%d\n",a[i]);

    }

    М е т о д Ш е л л а.

    Этот метод предложил Donald Lewis Shell в 1959 г. Основная идея алгоритма заключается в том, чтобы вначале устранить массовый беспорядок в массиве, сравнивая далеко стоящие друг от друга элементы. Как видно, интервал между сравниваемыми элементами (gap) постепенно уменьшается до единицы. Это означает, что на поздних стадиях сортировка сводится просто к перестановкам соседних элементов (если, конечно, такие перестановки являются необходимыми).

    #include

    #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
    {

    printf("Элемент\n");

    scanf("%d",&a[i]);

    }

    printf("Было\n");

    for (i=0;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
    printf("%d\n",a[i]);

    }

    2.2 Рекурсивные алгоритмы


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

    Рассмотрим более подробно организацию и работу рекурсивных подпрограмм.

    Рекурсию можно использовать для вычисления факториала n!. Чтобы найти n!, нужно определить (n-1)!. А для этого необходим (n-2)! и так далее.

    #include

    #include

    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

    #include /* 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| ");

    for(j = 0; j < nr[i]; j++) printf("%d ", st[i][j]);

    }

    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

    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"<
    return 0;

    }
    3.2.2 Перегрузка функций

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

    Пример.

    #include

    void k(int a);//прототип первой функции

    void k(int a, float b); //прототип второй функции

    void k(int a) //описание первой функции

    {

    cout << a <<"\n";

    }

    void k(int a, float b) //описание второй функции

    {

    cout <
    }

    main()

    {

    k(4);//вызов первой функции

    k(5, 10.2);//вызов второй функции

    return 0;

    }
    3.2.3 Конструкторы

    Для автоматической инициализации создаваемых объектов в С++ используется функция - конструктор (constructor function), которая включается в описание класса.

    Функция конструктор имеет тоже имя, что и класс и не возвращает ни какого значения.

    Пример:

    #include

    // Объявление класса 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

    // Объявление класса 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

    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

    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

    // Задание базового класса

    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

    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

    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

    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();
    1   2   3   4   5   6   7   8   9


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