Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ПИ_ИКТ Программирование по С++ (1 семестр) _Хотов.docx
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
5.83 Mб
Скачать
  • если таких студентов нет, вывести соответствующее сообщение.

    Задание 2

    1.Описать класс с именем STUDENT, содержащий следующие поля:

    • фамилия и инициалы;

    • номер группы;

    • успеваемость (массив из пяти элементов).

    2.Написать программу, выполняющую следующие действия.

    • ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены но возрастанию среднего балла;

    • вывод па дисплей фамилий и номеров групп для всех студентов, имеющих оценки 4 и 5;

    • если таких студентов нет, вывести соответствующее сообщение.

    Задание 3

    1.Описать класс с именем STUDENT, содержащий следующие поля:

    • фамилия и инициалы;

    • номер группы;

    • успеваемость (массив из пяти элементов).

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

    • ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены по алфавиту;

    • вывод на дисплей фамилий и номеров групп для всех студентов, имеющих хотя бы одну оценку 2;

    • если таких студентов нет, вывести соответствующее сообщение.

    Задание 4

    1.Описать класс с именем AEROFLOT, содержащий следующие поля:

    • название пункта назначения рейса;

    • номер рейса;

    • тип самолета.

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

    • ввод с клавиатуры данных в массив, состоящий из семи элементов типа AEROFLOT; записи должны быть упорядочены по возрастанию номера рейса;

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

    • если таких рейсов нет, выдать на дисплей соответствующее сообщение.

    Задание 5

    1.Описать класс с именем AEROFLOT, содержащий следующие поля:

    • название пункта назначения рейса;

    • номер рейса;

    • тип самолета.

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

    • ввод с клавиатуры данных в массив, состоящий из семи элементов типа AEROFLOT; записи должны быть размещены в алфавитном порядке но названиям пунктов назначения;

    • вывод на экран пунктов назначения и номеров рейсов, обслуживаемых самолетом, тип которого введен с клавиатуры;

    • если таких рейсов нет, выдать на дисплей соответствующее сообщение.

    Задание 6

    1.Описать класс с именем WORKER, содержащий следующие поля:

      1. фамилия и инициалы работника;

      2. название занимаемой должности;

      3. год поступления на работу.

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

    • ввод с клавиатуры данных в массив, состоящий из десяти структур типа WORKER; записи должны быть размещены по алфавиту.

    • вывод на дисплей фамилий работников, чей стаж работы в организации превышает значение, введенное с клавиатуры;

    • если таких работников нет, вывести на дисплей соответствующее сообщение.

    Задание 7

    1.Описать класс с именем TRAIN, содержащий следующие поля:

    • название пункта назначения;

    • номер поезда;

    • время отправления.

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

      1. ввод с клавиатуры данных в массив, состоящий из восьми элементов типа TRAIN; записи должны быть размещены в алфавитном порядке по названиям пунктов назначения;

      2. вывод на экран информации о поездах, отправляющихся после введенного с клавиатуры времени;

      3. если таких поездов нет, выдать на дисплей соответствующее сообщение.

    Задание 8

    1.Описать класс с именем TRAIN, содержащий следующие поля:

      1. название пункта назначения;

      2. номер поезда;

      3. время отправления.

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

    • ввод с клавиатуры данных в массив, состоящий из шести элементов типа TRAIN; записи должны быть упорядочены по времени отправления поезда;

    • вывод на экран информации о поездах, направляющихся в пункт, название которого введено с клавиатуры;

    • если таких поездов нет, выдать на дисплей соответствующее сообщение.

    Задание 9

    1.Описать класс с именем TRAIN, содержащий следующие поля:

    • название пункта назначения;

    • номер поезда;

    • время отправления.

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа TRAIN; записи должны быть упорядочены по номерам поездов;

    • вывод на экран информации о поезде, номер которого введен с клавиатуры;

    • если таких поездов нет, выдать на дисплей соответствующее сообщение.

    Задание 10

    1.Описать класс с именем MARSH, содержащий следующие поля:

    • название начального пункта маршрута;

    • название конечного пункта маршрута;

    • номер маршрута.

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа MARSH; записи должны быть упорядочены по номерам маршрутов;

    • вывод на экран информации о маршруте, номер которого введен с клавиатуры;

    • если таких маршрутов нет, выдать на дисплей соответствующее сообщение

    Задание 11

    1.Описать класс с именем MARSH, содержащий следующие поля:

    • название начального пункта маршрута;

    • название конечного пункта маршрута;

    • номер маршрута.

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа MARSH; записи должны быть упорядочены по номерам маршрутов;

    • вывод на экран информации о маршрутах, которые начинаются или кончаются в пункте, название которого введено с клавиатуры;

    • если таких маршрутов нет, выдать на дисплей соответствующее сообщение.

    Задание 12

    1.Описать класс с именем NOTE, содержащий следующие поля:

    • фамилия, имя;

    • номер телефона;

    • день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа NOTE; записи должны быть упорядочены по датам дней рождения;

    • вывод на экран информации о человеке, номер телефона которого введен с клавиатуры;

    • если такого нет, выдать на дисплей соответствующее сообщение.

    Задание 13

    1.Описать класс с именем NOTE, содержащий следующие поля:

      1. фамилия, имя;

      2. номер телефона;

      3. день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа NOTE; записи должны быть размещены но алфавиту;

    • вывод на экран информации о людях, чьи дни рождения приходятся на месяц, значение которого введено с клавиатуры;

    • если таких нет, выдать на дисплей соответствующее сообщение.

    Задание 14

    1.Описать класс с именем NOTE, содержащий следующие поля:

    • фамилия, имя; номер телефона;

    • день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа NOTE; записи должны быть упорядочены по трем первым цифрам номера телефона;

    • вывод на экран информации о человеке, чья фамилия введена с клавиатуры;

    • если такого нет, выдать на дисплей соответствующее сообщение.

    Задание 15

    1.Описать класс с именем ZNAK, содержащий следующие поля:

    • фамилия, имя;

    • знак Зодиака;

    • день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа ZNAK; записи должны быть упорядочены по датам дней рождения;

    • вывод на экран информации о человеке, чья фамилия введена с клавиатуры;

    • если такого нет, выдать па дисплей соответствующее сообщение.

    Задание 16

    1.Описать класс с именем ZNAK, содержащий следующие поля:

    • фамилия, имя;

    • знак Зодиака;

    • день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов типа ZNAK; записи должны быть упорядочены по датам дней рождения;

    • вывод на экран информации о людях, родившихся под знаком, наименование которого введено с клавиатуры;

    • если таких нет, выдать на дисплей соответствующее сообщение.

    Задание 17

    1.Описать класс с именем ZNAK, содержащий следующие поля:

    • фамилия, имя;

    • знак Зодиака;

    • день рождения (массив из трех чисел).

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов ZNAK; записи должны быть упорядочены по знакам Зодиака;

    • вывод иа экран информации о людях, родившихся в месян, значение которого введено с клавиатуры;

    • если таких нет, выдать на дисплей соответствующее сообщение.

    Задание 18

    1. Описать класс с именем PRICE, содержащий следующие поля:

    • название товара;

    • название магазина, в котором продается товар;

    • стоимость товара в руб.

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

    • ввод с клавиатуры данных в массив, состоящий из восьми элементов PRICE; записи должны быть размещены в алфавитном порядке по названиям товаров;

    • вывод на экран информации о товаре, название которого введено с клавиатуры;

    • если таких товаров нет, выдать на дисплей соответствующее сообщение

      1. Перегрузка операций и дружественные функции. Указатели на функции, методы и члены данных Перегрузка операций и дружественные функции.

    Дружественная функция — это функция, которая не является членом класса, но имеет доступ к членам класса, объявленным в полях private или protected.

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

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

    #include <iostream>

    using namespace std;

    #include <cstring>

    class usMoney;

    class ruMoney {

    private:

    long allcop;

    public:

    friend int compare(ruMoney rus, usMoney amr, double kurs);

    ruMoney(long rub, int cop);

    ruMoney() {allcop=0;}

    ruMoney(double money);

    long getrub() {return static_cast<long>(allcop*0.01);}

    int getcop() {return allcop-100*static_cast<long>(0.01*allcop);}

    };

    ruMoney::ruMoney(long rub, int cop) {

    allcop=100*rub+cop;

    }

    ruMoney::ruMoney(double money) {

    allcop=static_cast<long>(100*money);

    }

    class usMoney {

    private:

    long allcent;

    public:

    friend int compare(ruMoney rus, usMoney amr, double kurs);

    usMoney(long dol, int cent);

    usMoney() {allcent=0;}

    usMoney(double money);

    long getdol() {return static_cast<long>(allcent*0.01);}

    int getcent() {return allcent-100*static_cast<long>(0.01*allcent);}

    };

    usMoney:: usMoney (long dol, int cent) {

    allcent=100*dol+cent;

    }

    usMoney:: usMoney (double money) {

    allcent=static_cast<long>(100*money);

    }

    int compare(ruMoney rus, usMoney amr, double kurs) {

    if (rus.allcop<amr.allcent*kurs)

    return -1;

    else if(rus.allcop*kurs==amr.allcent)

    return 0;

    else

    return 1;

    }

    void main() {

    ruMoney a(32,95);

    double kurs=29.73;

    usMoney b=1.11;

    cout<<a.getrub()<<" rub "<<a.getcop()<<" cop\n";

    cout<<b.getdol()<<" dol "<<b.getcent()<<" cent\n";

    cout<<compare(a,b,kurs)<<endl;

    }

    Вывод этой программы:

    32 rub 95 cop

    1 dol 11 cent

    -1

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

    Другая причина использования дружественных функций – перегрузка операторов. Язык С++ позволяет перегружать почти все операторы. Это сделано для того, чтобы пользователь класса мог использовать стандартный вид операций, определенный для стандартных типов данных. Логично, например, определить операции +, *, - и т.д. для класса денег. Можно перегружать любые операции (в том числе и операции сравнения, индексирования и т.д.), существующие в С++, за исключением: # ## ?: sizeof :: .* .

    В программе выше использовался стандартный вид вывода для классов ruMoney и usMoney. Логично было бы переопределить функцию вывода так, чтобы она могла выводить объект нашего класса. Это делается с помощью дружественной функции. Наша программа примет вид:

    #include <iostream>

    using namespace std;

    #include <cstring>

    class usMoney;

    class ruMoney {

    private:

    long allcop;

    public:

    friend int compare(ruMoney rus, usMoney amr, double kurs);

    friend ostream& operator<<(ostream& out, ruMoney rus);

    ruMoney(long rub, int cop);

    ruMoney() {allcop=0;}

    ruMoney(double money);

    long getrub() {return static_cast<long>(allcop*0.01);}

    int getcop() {return allcop-100*static_cast<long>(0.01*allcop);}

    };

    ruMoney::ruMoney(long rub, int cop) {

    allcop=100*rub+cop;

    }

    ruMoney::ruMoney(double money) {

    allcop=static_cast<long>(100*money);

    }

    ostream& operator<<(ostream& out, ruMoney rus) {

    return (out<<rus.getrub()<<" rub "<<rus.getcop()<<" cop");

    }

    class usMoney {

    private:

    long allcent;

    public:

    friend int compare(ruMoney rus, usMoney amr, double kurs);

    friend ostream& operator<<(ostream& out, usMoney amr);

    usMoney (long dol, int cent);

    usMoney () {allcent=0;}

    usMoney (double money);

    long getdol() {return static_cast<long>(allcent*0.01);}

    int getcent() {return allcent-100*static_cast<long>(0.01*allcent);}

    };

    usMoney:: usMoney (long dol, int cent) {

    allcent=100*dol+cent;

    }

    usMoney:: usMoney (double money) {

    allcent=static_cast<long>(100*money);

    }

    ostream& operator<<(ostream& out, usMoney amr) {

    return (out<<amr.getdol()<<" dol "<<amr.getcent()<<" cent");

    }

    int compare(ruMoney rus, usMoney amr, double kurs) {

    if (rus.allcop<amr.allcent*kurs)

    return -1;

    else if(rus.allcop*kurs==amr.allcent)

    return 0;

    else

    return 1;

    }

    void main() {

    ruMoney a(32,95);

    double kurs=29.73;

    usMoney b=1.11;

    cout<<a<<endl<<b<<endl;

    cout<<compare(a,b,kurs)<<endl;

    }

    Вывод ее останется неизменным.

    Пример:

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

    #include "stdafx.h"

    // DataSet записывает соответствующую информацию массиве объектов

    #include <stdio.h>

    #include <conio.h>

    #include <iostream>

    using namespace std;

    #include <string.h>

    const int LEN=80;

    struct sString {

    char s [LEN] ;

    int len;

    };

    sString operator + (sString S1, sString S2){

    sString TmpS;

    if((TmpS.len=S1.len+S2.len)>=LEN){

    TmpS.s[0]='\0'; TmpS . len=0;

    }

    else {

    strcpy(TmpS.s, S1.s);

    strcat(TmpS.s, S2.s);

    }

    return TmpS;

    }

    void main(){

    setlocale(LC_ALL, "Russian");

    sString S1, S2, S3;

    strcpy(S1.s, "Пepeгpузкa oпepаций -") ;

    S1.len=strlen(S1.s);

    strcpy(S2.s, "классная вещь!") ;

    S2.len=strlen(S2.s);

    printf("Были строки:\n %s\n %s\n с длинами %d и %d\n",S1.s, S2.s, S1.len, S2.len);

    S3=S1+S2;//гeнepирyeтся код, вызову: operator+(S1, S2);

    printf("Пoлучилaсь cтpокa:\n\t%s длиной %d\n", S3.s,S3.len);

    _getch();

    }

    Результат:

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

    #include <iostream>

    using namespace std;

    bool smaller(int a, int b) { return a < b; }

    bool greater(int a, int b) { return a > b; }

    void sort(int *p, int n, bool(*cmp)(int, int))

    {

    for (int i= 0; i<n-1; i++)

    for (int j = i+1; j < n; j++)

    if (cmp(p[i], p[j]))

    swap(p[i], p[j]);

    }

    int main() {

    system("color f0");

    const int len = 10;

    int m[len];

    for (size_t i = 0; i < len; i++)

    {

    m[i] = rand() % 100;

    }

    sort(m, len, &smaller);

    sort(m, len, &greater);

    system("pause");

    }

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

    Цель работы:

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

    Изучить:

    • организацию данных типа класс;

    • приемы реализации задач с использованием классов;

    • перегрузка опрераторов;

    • использование дружественных функций.

    Контрольные вопросы

    1. Равносильны ли определения класса служебными словами class, struct И union?

    2. Как реализуется инкапсуляция в языке С++?

    3. Может ли один экземпляр класса обратиться к закрытому члену другого экземпляра того же класса?

    4. Может ли в классе быть несколько конструкторов?

    5. Может ли в классе быть несколько деструкторов?

    6. Что такое "друзья" класса?

    Задание на самостоятельное выполнение

    1. Вектор в n-м евклидовом пространстве задается своими координатами. Реализовать:

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

    б) скалярное произведение векторов;

    в) векторное произведение векторов (операция %);

    г) умножение вектора на скаляр.

    2. Строка символов. Реализовать операции:

    сравнения строк (операция ==);

    удаления из строки указанного символа (операция -);

    переворота строки (операция ~),

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

    3. В британском формате дата задается как число/месяц/год. Реализовать с учётом високосных годов:

    а) сложение даты и определенного пользователем количества дней;

    б) вычитание из даты определенного пользователем количества дней,

    в) вычислeниe числа дней, прошедших между двумя датами (оп. %).

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

    а) сложение (вычитание) двух матриц (операции +, -);

    б) умножение двух матриц (операция *).

    5. Комплексное число задается своим модулем и углом (например, число 10* (соs( / 6) + i * sin( / 6)) зада­ется парой (10,  / 6).

    Реализовать:

    а) сложение (вычитание) чисел;

    б) произведение двух чисел;

    в) деление чисел;

    г) возведение в целочисленную степень (оператор ^);

    д) извлечение квадратного корня (функция sqrt()).

    6. Комплексное число задается своей веществен­ной и мнимой частями (например, 5+3i).

    Реализовать:

    а) сложение (вычитание) чисел (операции +, - );

    б) произведение двух чисел (операция * );

    в) деление чисел (операция /);

    г) возведение в целочисленную степень (оператор ^);

    д) извлечение квадратного корня (функция sqrt())

    7. Время задается в формате час.минута.секунда, Реализовать следующие операции (учесть переход через 24 ч):

    а) сложение времени и определенного пользователем количества секунд;

    б) вычитание из времени указанного пользователем ко­личества секунд;

    в) сложение двух моментов времени;

    г) вычитание из одного момента времени другого;

    д) подсчет числа секунд между двумя моментами време­ни, лежащими в пределах одних суток (оператор %).

    8. Ввести класс для работы с объектом "полином". Реализовать следующие операции:

    а) сложение (вычитание) двух полиномов;

    б) умножение (деление) двух полиномов;

    в) умножение полинома на число (операция &);

    г) вычисление значения полинома в заданной точке Х (операция () );

    д) дифференцирование полинома (операция ~ );

    е) интегрирование полинома (операция ! ).

    9. Ввести класс для работы с объектом "мно­жество целых чисел". Реализовать следующие операции:

    а) объединение двух множеств (операция +);

    б) пересечение двух множеств (операция &);

    в) разность двух множеств (операция -);

    г) добавление элемента во множество;

    д) удаление элемента из множества.

    10. Ввести класс для работы с объектом "рацио­нальная дробь" (вида т/п). Реализовать:

    а) сложение/вычитание двух дробей;

    б) умножение/деление двух дробей;

    в) приведение дроби к несократимому виду (операция !);

    г) вывод дроби в виде т/п (операция << );

    д) сравнения двух дробей (операция || ).

    В заданиях 11 – 20 перегрузить операции ввода и вывода исходных данных.

    1. Описать класс с именем STUDENT, содержащий следующие поля: фамилия и инициалы;номер группы; успеваемость (массив из пяти элементов). Написать программу, выполняющую следующие действия: а) ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены по возрастанию номера группы; б) вывод на дисплей фамилий и номеров групп для всех студентов, включенных в массив, если средний балл студента больше 4,0;

    2. Описать класс с именем STUDENT, содержащий следующие поля: фамилия и инициалы; номер группы; и успеваемость (массив из пяти элементов). Написать программу, выполняющую следующие действия. а) ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены но возрастанию среднего балла; б) вывод па дисплей фамилий и номеров групп для всех студентов, имеющих оценки 4 и 5.

    3. Описать класс с именем STUDENT, содержащий следующие поля: фамилия и инициалы; номер группы; успеваемость (массив из пяти элементов). Написать программу, выполняющую следующие действия: а) ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены по алфавиту; б) вывод на дисплей фамилий и номеров групп для всех студентов, имеющих хотя бы одну оценку 2;

    4. Описать класс с именем AEROFLOT, содержащий следующие поля: название пункта назначения рейса; номер рейса; тип самолета. Написать программу, выполняющую следующие действия: а)ввод с клавиатуры данных в массив, состоящий из семи элементов типа AEROFLOT; записи должны быть упорядочены по возрастанию номера рейса; б) вывод на экран номеров рейсов и типов самолетов, вылетающих в пункт назначения, название которого совпало с названием, введенным с клавиатуры;

    5. Описать класс с именем AEROFLOT, содержащий следующие поля: название пункта назначения рейса; номер рейса; тип самолета. Написать программу, выполняющую следующие действия: а) ввод с клавиатуры данных в массив, состоящий из семи элементов типа AEROFLOT; записи должны быть размещены в алфавитном порядке но названиям пунктов назначения; б) вывод на экран пунктов назначения и номеров рейсов, обслуживаемых самолетом, тип которого введен с клавиатуры;

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

    7. Описать класс с именем TRAIN, содержащий следующие поля: название пункта назначения; номер поезда; время отправления. Написать программу, выполняющую следующие действия: а) ввод с клавиатуры данных в массив, состоящий из восьми элементов типа TRAIN; записи должны быть размещены в алфавитном порядке по названиям пунктов назначения; б) вывод на экран информации о поездах, отправляющихся после введенного с клавиатуры времени;

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

    9. Описать класс с именем TRAIN, содержащий следующие поля: название пункта назначения; номер поезда; время отправления. Написать программу, выполняющую следующие действия: а)ввод с клавиатуры данных в массив, состоящий из восьми элементов типа TRAIN; записи должны быть упорядочены по номерам поездов; б) вывод на экран информации о поезде, номер которого введен с клавиатуры;

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

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Схема алгоритма.

    6. Листинг программы.

    7. Результаты выполнения программы.

      1. Наследование. Открытое и закрытое наследование.

    Краткая теория

    Наследование

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

    Буч определяет наследование следующим образом:

    Наследование - это такое отношение между классами, когда один класс повторяет структуру и поведение другого класса (одиночное наследование) или других (множественное наследование) классов.

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

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

    class имя_класса : (public | protected | private) имя_базового_класса {

    объявления членов класса

    };

    Ключевые слова public, protected, private используются для указания того, насколько члены базового класса будут доступны из производного. Использование в заголовке производного класса public означает, защищенные и открытые члены базового класса должны наследоваться как защищенные и открытые члены производного класса. Это означает, что и защищенные (protected), и открытые (public) члены базового класса доступны в производном классе и также являются защищенными и закрытыми. Закрытые (private) члены базового класса недоступны для производного класса. Заметим, что закрытые и защищенные члены базового класса недоступны из классов, не являющихся производными базового. Открытое наследование, называемое также интерфейсным наследованием, означает, что производный тип является подтипом базового (отношение ISA). Каждый объект производного класса является объектом базового типа.

    Таблица. Статусы доступа при наследовании

    Доступ в базовом классе

    Спецификатор доступа перед базовым классом

    Доступ в производном классе

    struct

    class

    public

    отсутствует

    public

    private

    protected

    отсутствует

    public

    private

    private

    отсутствует

    недоступны

    недоступны

    public

    public

    public

    public

    protected

    public

    protected

    protected

    private

    public

    недоступны

    недоступны

    public

    protected

    protected

    protected

    protected

    protected

    protected

    protected

    private

    protected

    недоступны

    недоступны

    public

    private

    private

    private

    protected

    private

    private

    private

    Private

    private

    недоступны

    недоступны

    Например, класс млекопитающих является базовым для класса собак, класс собак – базовым для класса болонок. Т.е. каждая болонка – собака, каждая собака – млекопитающее, но не наоборот.

    Пример открытого одиночного наследования:

    #include <iostream>

    using namespace std;

    class rectangle {

    protected:

    float x,y,height,width;

    public:

    rectangle(float xbase, float ybase, float h, float w):

    x(xbase), y(ybase), height(h), width(w) { }

    float area() { return height*width; }

    void printarea() {

    cout<<"rectangle area="<<area()<<endl;

    }

    };

    class colorsquare : public rectangle {

    private:

    long color;

    public:

    colorsquare(float xbase, float ybase, float h, long c):

    rectangle(xbase,ybase,h,h), color(c) { }

    long getcolor() {return color;}

    void printarea() {

    cout<<"square area="<<area()<<endl;

    }

    };

    void main() {

    rectangle a(0,0,10,20),*refa;

    refa=&a;

    refa->printarea();

    colorsquare b(0,0,10,0xffffffff), *refb;

    refb=&b;

    refb->printarea();

    refa=refb;

    refa->printarea();

    //refa->getcolor();

    }

    Результат работы программы:

    rectangle area=200

    square area=100

    rectangle area=100

    В этом примере мы создаем базовый класс rectangle, имеющий защищенные члены класса, определяющие параметры прямоугольника. Заметим, что мы объявили их защищенными, а не закрытыми именно с той целью, чтобы они были доступны для производного класса и не доступны извне. Класс colorsquare (цветной квадрат) является подтипом базового класса. Это означает, что производный класс наследует все доступные (открытые и защищенные) свойства и методы базового класса. В дополнение производный класс добавлено свойство color и метод, позволяющий с этим свойством работать. В примере видно, что конструктор базового класса используется конструктором производного класса как часть списка инициализации. Это понятно, ведь объект класса colorsquare является одновременно объектом класса rectangle.

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

    При открытом наследовании указатель на базовый класс может быть также указателем на производный класс. В программе мы создаем два указателя – один на базовый класс rectangle и другой – на производный класс colorsquare. Правило преобразования указателей состоит в том, что указатель на открытый производный класс может быть неявно преобразован к указателю на его базовый класс. Присваивая указатель на производный класс указателю на базовый класс, не возникает ошибки. Компилятор связывает вызов функции с тем ее вариантом, который отвечает классу, указанному при объявлении указателя, а не тому, на объект которого в данный момент направлен указатель. Фактически нам становится доступен объект базового класса, образованный в процессе создания объекта производного класса. Заметим, что методы и свойства производного класса с помощью такого указателя недоступны.

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

    Язык C++ позволяет создавать классы одновременно на основе нескольких базовых классов (множественное наследование). Заметим, что это позволяют не все объектно-ориентированные языки программирования. Например, Java не позволяет использовать множественное наследование.

    В случае множественного наследования класс определяется следующим образом:

    class имя_класса : (public | protected | private) имя_базового_класса1, (public | protected | private) имя_базового_класса2, … {

    объявления членов класса

    };

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

    Предположим теперь, что в классах Class11 и Class12 переопределяется некоторый метод method1 базового класса Class1. Возникает вопрос, какой же экземпляр метода будет использоваться в классе Class2? Решить эту проблему можно, добавляя в качестве префиксов имя класса-источника: Class12::method1.

    Закрытое наследование не носит характера отношения подтипов. При закрытом наследовании мы повторно используем код базового класса, но не предполагаем рассматривать объекты производного класса как объекты базового. Закрытое наследование называется отношением LIKEA, или наследованием реализации. Закрытое и защищенное наследование не создает иерархии типов. Поэтому, если класс colorsquare будет закрытым наследником класса rectangle, преобразования указателей невозможны. В основном такое наследование используется для повторного использования кода.

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

    Виртуальные функции. Абстрактные классы.

    Как было продемонстрировано ранее, перегруженная функция-член вызывается с учетом соответствия типов. Компилятор связывает вызов функции с тем ее вариантом, который соответствует классу, указанному при объявлении указателя, а не тому, на объект которого направлен указатель. В языке С++ имеется возможность динамически (в процессе выполнения) выбирать перегруженную функцию-член среди функций базового и производного классов. Рассмотрим несколько измененный вариант предыдущей программы.

    #include <iostream>

    using namespace std;

    class rectangle {

    protected:

    float x,y,height,width;

    public:

    rectangle(float xbase, float ybase, float h, float w):

    x(xbase), y(ybase), height(h), width(w) { }

    float area() { return height*width; }

    virtual void printarea() {

    cout<<"rectangle area="<<area()<<endl;

    }

    };

    class colorsquare : public rectangle {

    private:

    int color;

    public:

    colorsquare(float xbase, float ybase, float h, int c):

    rectangle(xbase,ybase,h,h), color(c) { }

    int getcolor() {return color;}

    void printarea() {

    cout<<"square area="<<rectangle::area()<<endl;

    }

    };

    void main() {

    rectangle a(0,0,10,20),*refa;

    refa=&a;

    refa->printarea();

    colorsquare b(0,0,10,0xffffffff), *refb;

    refb=&b;

    refb->printarea();

    refa=refb;

    refa->printarea();

    }

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

    rectangle area=200

    square area=100

    square area=100

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

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

    При отсутствии функции-члена производного типа по умолчанию используется виртуальная функция базового класса. В нашем примере при отсутствии определения функции printarea в классе colorsquare всегда будет вызываться эта же функция базового класса.

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

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

    Дополним базовый класс rectangle деструктором:

    virtual ~rectangle() {cout<<"in rectangle\n";}

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

    ~colorsquare() {cout<<"in colorsquare\n";}

    Определим главную функцию таким образом, чтобы продемонстрировать динамическое создание и уничтожение объектов с помощью операторов new и delete:

    void main() {

    rectangle *refa;

    refa=new rectangle(0,0,10,20);

    colorsquare *refb;

    refb=new colorsquare(0,0,10,0xffffffff);

    delete refa;

    refa=refb;

    delete refa;

    }

    Результат работы программы:

    in rectangle

    in colorsquare

    in rectangle

    Видно, что при уничтожении объекта rectangle вызывается его деструктор, а при уничтожении объекта colorsquare – его деструктор, который в свою очередь вызывает деструктор базового класса. В ситуации, когда деструктор не объявлен виртуальным, результат работы программы изменится:

    in rectangle

    in rectangle

    Здесь при уничтожении объекта производного класса вызывается деструктор только базового класса.

    Иерархия типов обычно имеет корневой класс, содержащий некоторое число виртуальных функций. При этом они в большинстве случаев являются фиктивными функциями. Они имеют пустое тело в корневом классе, но в производных классах этим функциям придают смысл (в терминологии ООП это называется отложенным методом). Для таких функций в С++ введено понятие «чисто виртуальные функции» – это виртуальные функции, тело которых не определено. Объявляются они следующим образом:

    virtual прототип_функции=0;

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

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

    class shape {

    protected:

    float dim1,dim2;

    public:

    virtual float area()=0;

    shape(float d1,float d2) : dim1(d1), dim2(d2) { }

    };

    class rectangle : public shape{

    protected:

    float x,y;

    public:

    rectangle(float xbase, float ybase, float h, float w):

    shape(w,h), x(xbase), y(ybase) { }

    float area() { return dim1*dim2; }

    virtual void printarea() {

    cout<<"rectangle area="<<area()<<endl;

    }

    virtual ~rectangle() {cout<<"in rectangle\n";}

    };

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

    • добавили абстрактный класс shape, имеющий виртуальную функцию area и конструктор

    • изменили класс rectangle, сделав его производным от shape. При этом изменился конструктор класса и по-новому определена функция area.

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

    Пространства имен

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

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

    В этих случаях язык С++ рекомендует ввести пространство имен (namespace). Все имена, которые надо включить в одно пространство, записываются внутри именованного блока

    namespace MyNames{

    // Классы, отдельные функции, глобальные переменные.

    const int MAXLEN = 9999;

    void func();

    class A;

    }

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

    Блок namespace с одним и тем же именем можно записать несколько раз каждый блок добавляет в пространство имен новые имена. Можно написатьдалее:

    namespace MyNames{

    doublе f(double);

    }

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

    Уточнение имени

    Вне своего пространства имена уточняются с помощью операции разрешения видимости:

    if (k < MyNames::MAXLEN) fl(a[k]);

    MyNames::func();

    MyNames::A a = new MyNames::A();

    Пространство имен можно сравнить с городом. Все улицы города должны носить разные имена. Но в разных городах названия улиц могут совпадать. В каждом городе есть Садовая улица, Шоссейная улица, Центральная ули­ца. Поэтому, говоря о разных городах, мы уточняем название улицы, добав­ляя город, в котором она расположена. Если же разговор идет об одном го­роде, в таком уточнении нет нужды, ясно, о какой улице идет речь.

    Директива using namespace

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

    using namespace MyNames;

    if (k < MAXLEN) fl(a[k]);

    func();

    A a = new AO;

    Увидев директиву using namespace, компилятор будет во всех следующих строках отыскивать встреченные имена в текущем пространстве и в про­странстве имен MyNames. Разумеется, имена в этих пространствах должны быть различны. Совпадающие имена придется уточнять именем пространст­ва имен.

    Вложение пространств имен

    Директиву using namespace можно записать и внутри блока, определяюще­го пространство имен:

    namespace MyNewNames{

    using namespace MyNames; void f2();

    }

    Тем самым пространство имен MyNames вкладывается в пространство имен MyNewNames. Имена из пространства имен MyNames теперь лежат в пространстве имен MyNewNames и можно написать:

    if (k < MyNewNames::MAXLEN) fl(a[k]);

    MyNewNames::func();

    MyNewNames::A a = new MyNewNames::A();

    Такие имена будут сначала отыскиваться в пространстве имен MyNewNames, затем в пространствах имен, указанных в директивах using namespace.

    Объявление using

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

    using MyNames::MAXLEN;

    using MyNames::A;

    if (k < MAXLEN) fl(a[k]);

    MyNames::func();

    A a = new AO;

    Для имен из стандартной библиотеки классов языка С++ выделено пространство имен, названное std. В этой книге мы часто будем использовать данные имена и применять для сокращения записи директиву using namespace std.

    Неименованное пространство имен

    Можно определить и неименованное пространство имен:

    namespace{

    double fmod(double, int); const double FM = 2.4523;

    }

    На самом деле компилятор даст какое-то уникальное имя этому простран­ству. Поскольку к именам из этого пространства как-то надо обращаться, компилятор тут же вставит директиву using namespace. В результате мы получим что-то вроде:

    namespace XXX{

    double fmod(double, int);

    const double FM = 2.4523;

    }

    using namespace XXX;

    Вследствие этого именами fmod и fm из неименованного пространства имен можно будет пользоваться в пределах файла, в котором все это напи­сано. Итак, неименованное пространство имен ограничивает видимость имени файлом.

    Псевдонимы пространства имен

    Для уже введенного имени пространства имен можно создать псевдоним:

    namespace mnn = MyNewNames;

    Псевдоним можно использовать для сокращения записи — вместо MyNewNames :: MAXLEN писать mnn:: MAXLEN. Кроме того, псевдоним можно использовать так же, как мы обычно используем константы — при необходимости сменить истинное имя пространства имен, например, при обновлении библиотеки классов, нам достаточно сменить это имя только в одном месте.

    Лабораторная работа № 17. Наследование. Открытое и закрытое наследование. Цель работы:

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

    Изучить:

    • организацию данных типа класс;

    • приемы реализации задач с использованием классов;

    • описание производных классов;

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

    Контрольные вопросы

    1. Что такое наследование класса?

    2. Может ли метод класса-наследника обратиться к закрытым полям и ме­тодам своего предка?

    3. Что такое полиморфизм?

    4. Как в языке С++ реализуется полиморфизм?

    5. Что такое виртуальный метод класса?

    6. Сохраняется ли виртуальность при переопределениях метода в классах- наследниках?

    7. Какие способы защиты параметра метода от его случайного изменения внутри метода вы можете предложить?

    8. Может ли метод класса-наследника обратиться к закрытым полям и ме­тодам своего предка?

    9. Можно ли открытый метод класса-предка переопределить закрытым ме­тодом в классе-наследнике?

    10. Можно ли закрытый метод класса-предка переопределить открытым ме­тодом в классе-наследнике?

    11. Какой класс называется абстрактным?

    12. Какие опасности несет множественное наследование? Как их избежать?

    13. Что такое виртуальный базовый класс?

    Задания на самостоятельное выполнение

    1. Множественное наследование. Базовые классы: СЛУЖАЩИЙ (атрибуты: номер социальной страховки и ФИО, функция ввода данных ) и МЕНЕДЖЕР_СТАЖЕР (атрибуты: род стажера, тип программы стажера, функция ввода данных) , производный класс, наследующий все атрибуты двух классов: ОПЛАЧИВАЕМЫЙ_МЕНЕДЖЕР_СТАЖЕР (собственный атрибут – оклад, вызов функций ввода двух базовых классов внутри функции ввода оклада).

    2. Определить базовый виртуальный класс ЧЕЛОВЕК (имя, адрес, дата рождения, пол). Производные классы СТУДЕНТ (дополнительные подходящие сведения) и РАБОЧИЙ (дополнительные сведения). РАБОТАЮЩИЙ СТУДЕНТ - производный класс от классов СТУДЕНТ и РАБОЧИЙ. Написать программу, которая читала бы информацию из файла и создавала бы список людей. Обработать этот список для создания отсортированных по фамилиям списков всех людей, студентов, рабочих и списка рабочих студентов. Отсортировать список всех студентов, не являющихся работниками.

    3. Множественное наследование с виртуальным базовым классом. Определить основной базовый класс, который содержит переменную целого типа и массив из 10 элементов типа длинное целое. Первая непосредственная база является виртуальной (в качестве базового класса выступает основная база) и содержит одну переменную типа double, вторая непосредственная база тоже виртуальная (в качестве базового класса выступает основная база) и содержит переменную типа float. Производный класс имеет символьную переменную. В главной программе вычислить размеры в байтах всех классов.

    4. Задание.

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

    2. Создать производные классы: Rectangle (прямоугольник), Circle (круг), Triangle (треугольник) со своими методами вычисления площади и периметра.

    3. Создать массив n фигур и вывести полную информацию о фигурах на экран.

    1. Задание

    1. Создать класс Function с методом вычисления значения функции y=f(x) в заданной точке.

    2. Создать производные классы: Line (y=ax+b), Kub (y=ax2+bx+c), Hyperbola ( ) со своими методами вычисления значения в заданной точке.

    3. Создать массив n функций и вывести полную информацию о значении данных функций в точке х.

    1. Задание

    1. Создать класс Издание с методами позволяющим вывести на экран информацию об издании, а также определить является ли данное издание искомым.

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

    3. Создать каталог (массив) из n изданий, вывести полную информацию из каталога, а также организовать поиск изданий по фамилии автора.

    1. Задание

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

    2. Создать производные классы: Легковая_машина (марка, номер, скорость, грузоподъемность), Мотоцикл (марка, номер, скорость, грузоподъемность, наличие коляски, при этом если коляска отсутствует, то грузоподъемность равна 0), Грузовик (марка, номер, скорость, грузоподъемность, наличие прицепа, при этом если есть прицеп, то грузоподъемность увеличивается в два раза) со своими методами вывода информации на экран, и определения грузоподъемности.

    3. Создать базу (массив) из n машин, вывести полную информацию из базы на экран, а также организовать поиск машин, удовлетворяющих требованиям грузоподъемности.

    1. Задание

    1. Создать абстрактный класс Persona с методами, позволяющим вывести на экран информацию о персоне, а также определить ее возраст (на момент текущей даты).

    2. Создать производные классы: Абитуриент (фамилия, дата рождения, факультет), Студент (фамилия, дата рождения, факультет, курс), Преподавать (фамилия, дата рождения, факультет, должность, стаж), со своими методами вывода информации на экран, и определения возраста.

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

    1. Задание

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

    2) Создать производные классы: Продукт (название, цена, дата производства, срок годности), Партия (название, цена, количество шт, дата производства, срок годности), Комплект (названия, цена, перечень продуктов) со своими методами вывода информации на экран, и определения соответствия сроку годности.

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

    1. Задание

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

    2. Создать производные классы: Игрушка (название, цена, производитель, материал, возраст, на который рассчитана), Книга (название, автор, цена, издательство, возраст, на который рассчитана), Спорт-инвентарь (название, цена, производитель, возраст, на который рассчитана), со своими методами вывода информации на экран, и определения соответствия искомому типу.

    3. Создать базу (массив) из n товаров, вывести полную информацию из базы на экран, а также организовать поиск товаров определенного типа.

    1. Задание

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

    2. Создать производные классы: Персона (фамилия, адрес, номер телефона), Организация (название, адрес, телефон, факс, контактное лицо), Друг (фамилия, адрес, номер телефона, дата рождения) со своими методами вывода информации на экран, и определения соответствия искомому типу.

    3. Создать базу (массив) из n товаров, вывести полную информацию из базы на экран, а также организовать поиск в базе по фамилии.

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

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

    3. Реализовать следующую систему: таймер — некоторое устройство. По сигналу таймера устройство включается, по другому сигналу — выключается. Таймер, соответственно, можно программировать на подачу этих сигналов в заданное время.

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

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

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Схема алгоритма.

    6. Листинг программы.

    7. Результаты выполнения программы.

      1. Шаблоны (На самостоятельное изучение)

    Возникают случаи, когда мы хотим использовать класс, позволяющий удобно работать (например Shared Pointer, Array) сразу c несколькими типами (например BigNumber, LongString). Но тогда придётся писать отдельные реализации этих классов для каждого типа. Они будут похожи: отличие будет только в имени типа. Очевидно, что это неудобно и плохо: происходит дублирование кода; в случае, когда необходимо несколько реализаций, придётся включать несколько заголовочных файлов; может возникнуть путаница.

    Шаблон класса

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

    template <class T>

    struct Array{

    T& operator [] (size_t i){

    return data_[i];

    }

    private:

    T* data_;

    size_t size_;

    };

    Примечание: вместо class можно писать typename

    Компилятор подставит вместо T формальный параметр.

    Array<int> m;

    Array<double> d;

    Без этих строк код шаблона не будет скомпилирован и не попадёт в объектный файл. Шаблон - это декларация, в нём нет выполняемого кода.

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

    Если ниже создать другой экземпляр с тем же типом:

    Array<int> q;

    то не возникнет повторения, это будет экземпляр того же самого класса Array<int>, т.о. шаблон - не просто подстановка.

    Полное описание шаблона должно быть известно до его использования. Следовательно, нельзя разбить объявление и реализацию на .cpp и .h файлы, вся реализация должна быть известна и находиться в .h файле. Это объясняется тем, что .cpp файлы компилируются отдельно и независимо друг от друга. Это громоздко, однако в пределах одного .h файла можно сначала написать объявление, вынеся описание в конец файла:

    //объявление

    template <class T>

    struct Array{

    T& operator [] (size_t i);

    private:

    T* data_;

    size_t size_;

    };

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

    template<class T>

    T& Array<T>::operator [] (size_t i){

    return data_[i];

    }

    Из эстетических соображений можно вынести определение в отдельный заголовочный файл (например “array_impl.h”) и подключить его после объявления.

    Итог:

    + Шаблоны - конструкции языка, компилятор понимает, что это.

    - Это довольно громоздко, реализация должна быть известна.

    Шаблонные функции.

    template<class T>

    void swap(T& a, T& b){

    T t(a);

    a = b;

    b = t;

    }

    int i=10, j=20;

    swap<int>(i, j);

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

    Компилятор достаточно умён чтобы самостоятельно определять тип для шаблона функции когда это возможно. Это называется deducing - вывод параметров шаблонов на основе параметров функции.

    Написав просто:

    swap(i, j)

    компилятор попытается угадать какой тип имеют i и j и сам подставит int.

    Однако:

    int i = 10;

    long j = 20;

    swap(i, j);

    вызовет ошибку, т.е. long это не и тоже самое что int. Компилятор в этом случае не может выбрать между swap<long> и swap<int>.

    Если же написать:

    swap<long>(i, j);

    то привести int к long возможно, и код будет работать.

    Шаблонные методы

    template <class T>

    struct Array{

    template<class V>

    Array<T>& operator = (Array<V> const & m);

    };

    template<class T>

    template<class V>

    Array<T>& Array<T>::operator = (Array const & m) { … }

    //использование

    Array<int> m;

    Array<double> d;

    d = m;

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

    Ещё пример умного определения типа компилятором:

    template <class T>

    void sort(Array<T>& m) { … }

    sort(d);

    Компилятор поймёт, что в sort передан массив из double.

    Пример применения двух типов для шаблона:

    template <class F, class S>

    struct pair{

    F first;

    S second;

    };

    1. Стандартная библиотека шаблонов

    Использование STL в C++

    Библиотека стандартных шаблонов (STL) (англ. Standard Template Library) — набор согласованных обобщённых алгоритмов, контейнеров, средств доступа к их содержимому и различных вспомогательных функций в C++.

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

    • vector - коллекция элементов Т, сохраненных в массиве, увеличиваемом по мере необходимости. Для того, чтобы начать использование данной коллекции, включите #include <vector>.

    • list - коллекция элементов Т, сохраненных, как двунаправленный связанный список. Для того, чтобы начать использование данной коллекции, включите #include <list>.

    • map - это коллекция, сохраняющая пары значений pair<const Key, T>. Эта коллекция предназначена для быстрого поиска значения T по ключу const Key. В качестве ключа может быть использовано все, что угодно, например, строка или int но при этом необходимо помнить, что главной особенностью ключа является возможность применить к нему операцию сравнения. Быстрый поиск значения по ключу осуществляется благодаря тому, что пары хранятся в отсортированном виде. Эта коллекция имеет соответственно и недостаток - скорость вставки новой пары обратно пропорциональна количеству элементов, сохраненных в коллекции, поскольку просто добавить новое значение в конец коллекции не получится. Еще одна важная вещь, которую необходимо помнить при использовании данной коллекции - ключ должен быть уникальным. Для того, чтобы начать использование данной коллекции, включите #include <map>. Если вы хотите использовать данную коллекцию, чтобы избежать дубликатов, то вы избежите их только по ключу.

    • set - это коллекция уникальных значений const Key - каждое из которых является также и ключом - то есть, проще говоря, это отсортированная коллекция, предназначенная для быстрого поиска необходимого значения. К ключу предъявляются те же требования, что и в случае ключа для map. Естественно, использовать ее для этой цели нет смысла, если вы хотите сохранить в ней простые типы данных, по меньшей мере вам необходимо определить свой класс, хранящий пару ключ - значение и определяющий операцию сравнения по ключу. Очень удобно использовать данную коллекцию, если вы хотите избежать повторного сохранения одного и того же значения. Для того, чтобы начать использование данной коллекции, включите #include <set>.

    • multimap - это модифицированный map, в котором отсутствует требования уникальности ключа - то есть, если вы произведете поиск по ключу, то вам вернется не одно значение, а набор значений, сохраненных с данным ключом. Для того, чтобы начать использование данной коллекции включите #include <map>.

    • multiset - то же самое относится и к этой коллекции, требования уникальности ключа в ней не существует, что приводит к возможности хранения дубликатов значений. Тем не менее, существует возможность быстрого нахождения значений по ключу в случае, если вы определили свой класс. Поскольку все значения в map и set хранятся в отсортированном виде, то получается, что в этих коллекциях мы можем очень быстро отыскать необходимое нам значение по ключу, но при этом операция вставки нового элемента T будет стоить нам несколько дороже, чем например в vector. Для того, чтобы начать использование данной коллекции, включите #include <set>.

    Stl Строки

    Не существует серьезной библиотеки, которая бы не включала в себя свой класс для представления строк или даже несколько подобных классов. STL - строки поддерживают как формат ASCII, так и формат Unicode.

    • string - представляет из себя коллекцию, хранящую символы char в формате ASCII. Для того, чтобы использовать данную коллекцию, вам необходимо включить #include <string>.

    • wstring - это коллекция для хранения двухбайтных символов wchar_t, которые используются для представления всего набора символов в формате Unicode. Для того, чтобы использовать данную коллекцию, вам необходимо включить #include <xstring>.

    Строковые потоки

    Используются для организации сохранения простых типов данных в STL строки в стиле C++. Практическое знакомство с STL мы начнем именно с этого класса. Ниже приведена простая программа, демонстрирующая возможности использования строковых потоков:

    //stl.cpp: Defines the entry point for the console application

    #include <iostream>

    #include <strstream>

    #include <string>

    using namespace std;

    int main()

    {

    strstream xstr;

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

    {

    xstr << "Demo " << i << endl;

    }

    cout << xstr.str();

    string str;

    str.assign(xstr.str(), xstr.pcount());

    cout << str.c_str();

    return 0;

    }

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

    Далее мы производим копирование содержимого буфера в строку и печатаем строку второй раз. На этот раз она печатается без мусора.

    Основные методы, которые присутствуют почти во всех STL коллекциях, приведены ниже.

    • empty - определяет, является ли коллекция пустой.

    • size - определяет размер коллекции.

    • begin - возвращает прямой итератор, указывающий на начало коллекции.

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

    • rbegin - возвращает обратный итератор, указывающий на начало коллекции.

    • rend - возвращает обратный итератор, указывающий на конец коллекции. При этом надо учесть, что реально он не указывает на ее последний элемент, а указывает на воображаемый несуществующий элемент, следующий за последним.

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

    • erase - удаляет элемент или несколько элементов из коллекции.

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

    #include <iostream>

    #include <vector>

    using namespace std;

    int main()

    {

    vector<int> vec;

    cout << "Real size of array in vector: " << vec.capacity()

    << endl;

    for (int j = 0; j < 10; j++)

    {

    vec.push_back(10);

    }

    cout << "Real size of array in vector: " << vec.capacity()

    << endl;

    return 0;

    }

    При использовании микрософтовской реализации STL библиотеки (Visual C++ 7.0) у автора получилось 0 и 13 соответственно до и после заполнения вектора.

    vector

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

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

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

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

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

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

    #include <iostream>

    #include <strstream>

    #include <string>

    #include <vector>

    #include <algorithm>

    using namespace std;

    class MyMessage

    {

    private:

    string from;

    string to;

    string message;

    int id;

    public:

    MyMessage(string from, string to, string message)

    {

    this ->from = from;

    this ->to = to;

    this ->message = message;

    }

    int GetId()

    {

    return this ->id;

    }

    void SetId(int id)

    {

    this ->id = id;

    }

    string GetMessage()

    {

    return this ->message;

    }

    string GetFrom()

    {

    return this ->from;

    }

    string GetTo()

    {

    return this ->to;

    }

    };

    int main()

    {

    vector<MyMessage> common;

    // create pool of messages for 3 users:

    for (int user = 0; user < 3; user++)

    {

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

    {

    strstream messagex;

    messagex << "Message " << i;

    string smessage;

    smessage.assign(messagex.str(), messagex.pcount());

    strstream userx;

    userx << "User " << user;

    string suser;

    suser.assign(userx.str(), userx.pcount());

    MyMessage message("Administrator", suser, smessage);

    message.SetId(user * 10 + i);

    common.push_back(message);

    }

    }

    // create vector for each user:

    vector<MyMessage> user0;

    vector<MyMessage> user1;

    vector<MyMessage> user2;

    for (int x = 0; x < (int)common.size(); x++)

    {

    MyMessage message = common[x];

    if (message.GetTo() == "User 0")

    {

    user0.push_back(message);

    }

    else

    if (message.GetTo() == "User 1")

    {

    user1.push_back(message);

    }

    else

    if (message.GetTo() == "User 2")

    {

    user2.push_back(message);

    }

    }

    cout << "Messages for user 2: " << endl;

    for (int i = 0; i < (int)user2.size(); i++)

    {

    MyMessage message = user2[i];

    cout << message.GetTo() << endl;

    }

    cout << "Messages for user 1: " << endl;

    for (int i = 0; i < (int)user1.size(); i++)

    {

    MyMessage message = user1[i];

    cout << message.GetTo() << endl;

    }

    cout << "Messages for user 0: " << endl;

    for (int i = 0; i < (int)user0.size(); i++)

    {

    MyMessage message = user0[i];

    cout << message.GetTo() << endl;

    }

    cout << "Size of common vector: " << (int)common.size()

    << endl;

    return 0;

    }

    Теперь у вас есть некоторое представление о том, каким образом писать бизнес - логику приложений с использованием STL. Из этого приложения видно, что кроме перечисленных выше методов, у вектора есть оператор operator [], который позволяет нам пользоваться вектором так же, как обычным массивом. Этот оператор используется также в map, deque, string и wstring.

    Итераторы

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

    class Iterator

    {

    T* pointer;

    public:

    T* GetPointer ()

    {

    return this - >pointer;

    }

    void SetPointer (T* pointer)

    {

    this - >pointer = pointer;

    }

    :

    };

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

    Итераторы обеспечивают доступ к элементам в коллекции

    Итераторы для конкретного класса коллекции определяются внутри класса этой коллекции. В STL существует три типа итераторов: iterator, reverse_iterator, и random access iterator. Для обхода коллекции от меньшего индекса к большему, используются обычные или forward итераторы. Для обхода коллекции в обратном направлении используются reverse итераторы. Random access iterator являются итераторами, которые могут обходить коллекцию как вперед, так и назад. Ниже приведен пример использования итераторов для удаления половины элементов вектора:

    #include <iostream>

    #include <vector>

    #include <algorithm>

    using namespace std;

    void printInt(int number);

    int main()

    {

    vector<int> myVec;

    vector<int>::iterator first, last;

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

    {

    myVec.push_back(i);

    }

    first = myVec.begin();

    last = myVec.begin() + 5;

    if (last >= myVec.end())

    {

    return -1;

    }

    myVec.erase(first, last);

    for_each(myVec.begin(), myVec.end(), printInt);

    return 0;

    }

    void printInt(int number)

    {

    cout << number << endl;

    }

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

    Алгоритмы

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

    STL - алгоритмы представляют набор готовых функций, которые могут быть применены к STL коллекциям и могут быть подразделены на три основных группы:

    Функции для перебора всех членов коллекции и выполнения определенных действий над каждым из них:

    count, count_if, find, find_if, adjacent_find, for_each, mismatch, equal, search copy, copy_backward, swap, iter_swap, swap_ranges, fill, fill_n, generate, generate_n, replace, replace_if, transform, remove, remove_if, remove_copy, remove_copy_if, unique, unique_copy, reverse, reverse_copy, rotate, rotate_copy, random_shuffle, partition, stable_partition

    Функции для сортировки членов коллекции:

    Sort, stable_sort, partial_sort, partial_sort_copy, nth_element, binary_search, lower_bound, upper_bound, equal_range, merge, inplace_merge, includes, set_union, set_intersection, set_difference, set_symmetric_difference, make_heap, push_heap, pop_heap, sort_heap, min, max, min_element, max_element, lexographical_compare, next_permutation, prev_permutation

    Функции для выполнения определенных арифметических действий над членами коллекции:

    Accumulate, inner_product, partial_sum, adjacent_difference

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

    #include <iostream>

    #include <strstream>

    #include <string>

    #include <vector>

    #include <algorithm>

    using namespace std;

    class MyFunctor

    {

    string comment;

    public:

    MyFunctor()

    {

    comment = "My comment";

    };

    MyFunctor(string comment)

    {

    this - >comment = comment;

    }

    void operator ()(int test)

    {

    cout << test << comment << endl;

    };

    };

    int main()

    {

    vector<int> test;

    // fill vector:

    for (int i = 0; i < 5; i++)

    {

    test.push_back(i);

    }

    // now use our functor:

    MyFunctor functor(" Test comment");

    for_each(test.begin(), test.end(), functor);

    return 0;

    }

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

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

    #include <iostream>

    #include <strstream>

    #include <string>

    #include <vector>

    #include <algorithm>

    using namespace std;

    void printMan(string user);

    int main()

    {

    vector<string> maleRoom;

    vector<string> fimaleRoom;

    maleRoom.push_back("Vasya");

    maleRoom.push_back("Petya");

    maleRoom.push_back("Sasha");

    fimaleRoom.push_back("Nastya");

    fimaleRoom.push_back("Alena");

    fimaleRoom.push_back("Sveta");

    for_each(maleRoom.begin(), maleRoom.end(), printMan);

    reverse(maleRoom.begin(), maleRoom.end());

    cout << "Males in reverse order " << endl;

    for_each(maleRoom.begin(), maleRoom.end(), printMan);

    maleRoom.swap(fimaleRoom);

    cout << "Now in male room are fimales: " << endl;

    for_each(maleRoom.begin(), maleRoom.end(), printMan);

    return 0;

    }

    void printMan(string man)

    {

    cout << man << endl;

    }

    Предикаты

    Для многих алгоритмов STL необходимо задать условие, посредством которого алгоритм определяет, что ему необходимо делать с тем или иным членом коллекции. По определению, предикат - это функция, принимающая один или более параметров и возвращающая значения истина или ложь. Предикат может быть функцией или функтором. Существует также набор стандартных предикатов. Рассмотрим некоторые способы использования предикатов в библиотеке стандартных шаблонов на примере алгоритмов find_if и sort:

    #include <iostream>

    #include <strstream>

    #include <string>

    #include <vector>

    #include <algorithm>

    #include <functional>

    using namespace std;

    class Man;

    ostream& operator << (ostream& os, Man& man);

    class Man

    {

    string sex;

    string name;

    int age;

    public:

    Man()

    {}

    Man(string name, string sex, int age)

    {

    this ->name = name;

    this ->sex = sex;

    this ->age = age;

    }

    int GetAge()

    {

    return this ->age;

    }

    void SetAge(int age)

    {

    this ->age = age;

    }

    string GetName()

    {

    return this ->name;

    }

    void SetName(string name)

    {

    this ->name = name;

    }

    string GetSex()

    {

    return this ->sex;

    }

    void SetSex(string sex)

    {

    this ->sex = sex;

    }

    void PrintInfo()

    {

    cout << (*this);

    }

    };

    ostream& operator << (ostream& os, Man& man)

    {

    os << " - - - - - Info: - - - - - " << endl;

    os << "My name is: " << man.GetName() << endl;

    os << "I am " << man.GetAge() << " years old " << endl;

    os << "I am " << man.GetSex() << endl;

    os << " - - - - - End of Info - - - - - " << endl;

    return os;

    };

    class ManLess

    {

    public:

    bool operator ()(Man& man1, Man& man2)

    {

    if (man1.GetAge() < man2.GetAge())

    {

    return false;

    }

    else

    {

    return true;

    }

    };

    };

    bool ManOlderThan23(Man& man)

    {

    if (man.GetAge() > 23)

    {

    return true;

    }

    else

    {

    return false;

    }

    };

    class ManOlderThan

    {

    int m_age;

    public:

    ManOlderThan(int age)

    {

    m_age = age;

    };

    bool operator ()(Man& man)

    {

    if (man.GetAge() > m_age)

    {

    return true;

    }

    else

    {

    return false;

    }

    };

    };

    int main()

    {

    // create 3 men

    Man man1("Dima", "male", 23);

    Man man2("Sasha", "male", 30);

    Man man3("Sergey", "male", 32);

    vector<Man> programmers;

    programmers.push_back(man1);

    programmers.push_back(man2);

    programmers.push_back(man3);

    // find and print all programmers older than 23

    cout << "Find all programmers older than 23 " << endl;

    vector<Man>::iterator p =

    find_if(programmers.begin(), programmers.end(),

    ManOlderThan23);

    while (p != programmers.end())

    {

    cout << (*p);

    p++;

    }

    // here is the same in more flexible way:

    cout << "Find all programmers older than 23 " << endl;

    p = find_if(programmers.begin(), programmers.end(), ManOlderThan(23));

    for_each(p, programmers.end(), mem_fun_ref(&Man::PrintInfo));

    cout << "Sorted list of programmers: " << endl;

    sort(programmers.begin(), programmers.end(), ManLess());

    for_each(programmers.begin(), programmers.end(), mem_fun_ref(&Man::PrintInfo));

    return 0;

    }

    На первый взгляд, этот пример выглядит довольно запутанно, но на самом деле все очень просто. Первое, что мы делаем, это включаем упреждающее объявление класса Man, оно необходимо нам для того, чтобы, в свою очередь, использовать его в упреждающем объявлении перегруженного оператора << для нашего класса Man. Теперь мы можем использовать его внутри метода класса Man. Сам класс Man не представляет из себя ничего необычного - это обычный бизнес - класс, описывающий человека.

    Далее описывается предикат - функтор LessMan, необходимый для сортировки членов нашего вектора. Он принимает два параметра типа Man. Он будет использован для сортировки в порядке убывания по возрасту программистов. ManOlderThan23 - это предикат - функция, которая отбирает всех программистов старше 23 лет. После этого мы определяем точно такой же предикат - функтор ManOlder с возможностью устанавливать минимальный возраст человека в момент его создания. Такой подход гораздо гибче предыдущего.

    После входа в функцию main () мы создаем вектор programmers и заполняем его программистами: Дима, Саша и Сергей. Далее мы находим и распечатываем всех программистов старше 23 лет двумя способами, после этого сортируем и распечатываем весь список наших программистов в порядке убывания по возрасту.

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

    Еще одной особенностью этого кода является то, что мы получаем указатель на функцию класса с помощью mem_fun_ref. Как видим, иногда это бывает очень полезно. Для того, чтобы воспользоваться этой возможностью, необходимо включить #include <functional>.

    Заключение

    Достоинство STL - это то, что библиотека действительно является кроссплатформенной. И это не пустая декларация, как происходит со многими другими технологиями. Я думаю, что существует больше платформ, не поддерживающих Java, чем компиляторов C++ для этих же платформ, не поддерживающих STL. Конечно, не существует абсолютной гарантии, что она встроена абсолютно во все компиляторы C++. Например, некоторые компиляторы для мобильных устройств и микроконтроллеров не включают эту библиотеку. Это обусловлено тем, что она является относительно неэффективной в плане использования памяти, поскольку оптимизирована для обеспечения максимальной скорости. В мобильных устройствах, как известно, самый дорогой ресурс - это память, в то время как на вашем PC сегодня его в избытке. Поэтому иногда вам придется писать шаблонные классы, похожие на классы STL, самостоятельно для того, чтобы например перенести приложение, работающее под Windows или Linux на мобильное устройство.

    Лабораторная работа № 18. Стандартная библиотека шаблонов Цель.

    Освоить технологию обобщенного программирования с ис­пользованием библиотеки стандартных шаблонов (STL) языка C++.

    Основное содержание работы.

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

    Порядок выполнения работы.

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

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

    Третья программа демонстрирует использование алгоритмов STL. В программе № 1 выполнить следующее:

      1. Создать объект-контейнер в соответствии с вариантом задания и заполнить его данными, тип которых определяется вариантом задания.

      2. Просмотреть контейнер.

      3. Изменить контейнер, удалив из него одни элементы и заменив другие.

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

      5. Создать второй контейнер этого же класса и заполнить его данны­ми того же типа, что и первый контейнер.

      6. Изменить первый контейнер, удалив из него n элементов после за­данного и добавив затем в него все элементы из второго контейнера.

      7. Просмотреть первый и второй контейнеры.

    В программе № 2 выполнить то же самое, но для данных пользова­тельского типа.

    В программе № 3 выполнить следующее:

        1. Создать контейнер, содержащий объекты пользовательского типа. Тип контейнера выбирается в соответствии с вариантом задания.

        2. Отсортировать его по убыванию элементов.

        3. Просмотреть контейнер.

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

        5. Переместить элементы, удовлетворяющие заданному условию в другой (предварительно пустой) контейнер. Тип второго контейнера опре­деляется вариантом задания.

        6. Просмотреть второй контейнер.

        7. Отсортировать первый и второй контейнеры по возрастанию эле­ментов.

        8. Просмотреть их.

        9. Получить третий контейнер путем слияния первых двух.

        10. Просмотреть третий контейнер.

    11 .Подсчитать, сколько элементов, удовлетворяющих заданному ус­ловию, содержит третий контейнер.

    12. Определить, есть ли в третьем контейнере элемент, удовлетво­ряющий заданному условию.

    Методические указания.

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

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

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

          3. Для вставки и удаления элементов контейнера в программе № 2 использовать соответствующие операции, определенные в классе контей­нера.

          4. Для создания второго контейнера в программе № 3 можно исполь­зовать либо алгоритм remove_copy_if, либо определить свой алгоритм copy_if, которого нет в STL.

          5. Для поиска элемента в коллекции можно использовать алгоритм find_if, либо for_each, либо binary_search, если контейнер отсортирован.

          6. Для сравнения элементов при сортировке по возрастанию исполь­зуется операция <, которая должна быть перегружена в пользовательском классе. Для сортировки по убыванию следует написать функцию comp и использовать вторую версию алгоритма sort.

          7. Условия поиска и замены элементов выбираются самостоятельно и для них пишется функция-предикат.

          8. Для ввода-вывода объектов пользовательского класса следует пе­регрузить операции ">>" и "<<".

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

    10.При перемещении элементов ассоциативного контейнера в неас­социативный перемещаются только данные (ключи не перемещаются). И наоборот, при перемещении элементов неассоциативного контейнера в ас­социативный должен быть сформирован ключ.

    Содержание отчета.

            1. Титульный лист.

            2. Постановка задач.

            3. Определение пользовательского класса.

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

            5. Объяснение этих функций.

            6. Объяснение используемых в программах алгоритмов STL.

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

    Варианты заданий.

    № п/п

    Первый контейнер

    Второй контейнер

    Встроенный тип данных

    1

    vector

    list

    int

    2

    list

    deque

    long

    3

    deque

    stack

    float

    4

    stack

    queue

    double

    5

    queue

    vector

    char

    6

    vector

    stack

    string

    7

    map

    list

    long

    8

    multimap

    deque

    float

    9

    set

    stack

    int

    10

    multiset

    queue

    char

    11

    vector

    map

    double

    12

    list

    set

    int

    13

    deque

    multiset

    long

    14

    stack

    vector

    float

    15

    queue

    map

    int

    16

    priority queue

    stack

    char

    17

    map

    queue

    char

    18

    multimap

    list

    int

    19

    set

    map

    char

    20

    multiset

    vector

    int

    1. Обработка исключительных ситуаций. Теория

    Источник: http://www.codenet.ru/progr/cpp/Try-Catch-Throw.php

    Введение

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

    Try-catch-throw

    Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:

    • try (пытаться) - начало блока исключений;

    • catch (поймать) - начало блока, "ловящего" исключение;

    • throw (бросить) - ключевое слово, "создающее" ("возбуждающее") исключение.

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

    void func()

    {

    try

    {

    throw 1;

    }

    catch (int a)

    {

    cout << "Caught exception number: " << a << endl;

    return;

    }

    cout << "No exception detected!" << endl;

    return;

    }

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

    Caught exception number: 1

    Теперь закоментируйте строку throw 1; и функция выдаст такой результат:

    No exception detected!

    Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может "ловить" любой тип данных, так же как и throw может "кинуть" данные любого типа. Т.е. throw AnyClass(); будет правильно работать, так же как и catch (AnyClass &d) {};.

    Как уже было сказано, catch может "ловить" данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого:

    catch(dumbclass) { }

    так же, как и

    catch(dumbclass&) { }

    Так же можно "поймать" и все исключения:

    catch(...) { }

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

    try {

    throw 1;

    // throw 'a';

    }

    catch (long b) {

    cout << "пойман тип long: " << b << endl;

    }

    catch (char b) {

    cout << "пойман тип char: " << b << endl;

    }

    "Создание" исключений

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

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

    Перегрузка глобальных операторов new/delete

    А сейчас хотелось бы отправить вас к статье "Как обнаружить утечку памяти". В ней рассказывается, как обнаружить неправильное управление распределением памяти в вашей программе. Вы можете спросить, при чем тут перегрузка операторов? Если перегрузить стандартные new и delete, то открываются широкие возможности по отслеживанию ошибок (причем ошибок часто критических) с помощью исключений. Например:

    char *a;

    try

    {

    a = new char[10];

    }

    catch (...) {

    // a не создан - обработать ошибку распределения памяти,

    // выйти из программы и т.п.

    }

    // a успешно создан, продолжаем выполнение

    Это, на первый взгляд, кажется длиннее, чем стандартная проверка в С "а равен NULL?", однако если в программе выделяется десяток динамических переменных, то такой метод оправдывает себя.

    Операторы throw без параметров

    Итак, мы увидели, как новый метод обработки ошибок удобен и прост. Блок try-catch может содержать вложенные блоки try-catch и если не будет определено соответствующего оператора catch на текущем уровен вложения, исключение будет поймано на более высоком уровне. Единственная вещь, о которой вы должны помнить, - это то, что операторы, следующие за throw, никогда не выполнятся.

    try

    {

    throw;

    // ни один оператор, следующий далее (до закрывающей скобки)

    // выполнен не будет

    }

    catch (...)

    {

    cout << "Исключение!" << endl;

    }

    Такой метод может применяться в случаях, когда не нужно передавать никаких данных в блок catch.

    Заключение

    Метод обработки исключений, приведенный в статье, является удобным и мощным средством, однако только вам решать, использовать его или нет. Одно можно сказать точно - приведенный метод облегчит вам жизнь. Если хотите узнать об исключениях чуть больше, посмотрите публикацию Deep C++ на сервере MSDN.

    Лабораторная работа № 19. Использование исключительных ситуаций C++ для обработки ошибок.

    Цель работы:

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

    Контрольные вопросы

    1. Что такое исключительная ситуация?

    2. Можно ли предусмотреть заранее все исключительные ситуации, возни­кающие в программе?

    3. Какой способ обработки исключительных ситуаций предлагает объектно-ориентированное программирование?

    4. Какие конструкции предлагает язык С++ для обработки исключитель­ных ситуаций?

    5. Имеет ли значение порядок записи блоков catch() {}?

    6. Может ли исключительная ситуация возникнуть в блоке catch() {}?

    7. Можно ли в блок catch () {} вложить блок try {} catch () {} для обработ­ки возникшей там исключительной ситуации?

    8. Можно ли вкладывать блоки try{} catch () {} один в другой?

    9. Что должен содержать класс-исключение?

    10. Где создаются объекты классов-исключений?

    11. Что делает исполняющая система С++, если исключительная ситуация не обрабатывается в программе?

    12. Можно ли изменить эти стандартные действия исполняющей системы С++?

    13. Что делает исполняющая система С++, если метод не обрабатывает ис­ключительную ситуацию и не декларирует это в своем заголовке?

    14. Можно ли обработать одну и ту же исключительную ситуацию несколь­ко раз?

    Задание

    Постройте таблицу значений функции y=f(x) для [a, b] с шагом h. Если в некоторой точке x функция не определена, то выведите на экран сообщение об этом.

    Замечание. При решении данной задачи использовать вспомогательный метод f(x), реализующий заданную функцию, а также проводить обработку возможных исключений.

    1.

    Пример:

    #include <math.h>

    #include <iostream>

    #include <iomanip>

    using namespace std;

    double f(double x){

    try

    {

    //если х не попадает в область определения, то генерируется исключение

    if (x == -1) throw "х не попадает в область определения";

    else return 1 /powf(1 + x, 2);

    }

    catch( char * str )

    {

    /*Исключение, перехваченное одной catch-инструкцией, можно сгенерировать

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

    catch-инструкцией. Это позволяет нескольким обработчикам получить доступ

    к исключению.

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

    повторно перехватываться той же catch-инструкцией, а передается

    следующей (внешней) catch-инструкции.*/

    throw; //повторная генерация исключения

    }

    }

    void main()

    {

    setlocale(LC_ALL, "russian");

    double a,b,h;

    cout<<"a=";

    cin>>a;

    cout<<"b=";

    cin>>b;

    cout<<"h=";

    cin>>h;

    for (double i = a; i <= b; i += h)

    {

    try

    {

    cout<<"y("<<i<<") = "<<setprecision(6)<<f(i)<<endl;

    }

    catch(char *ex)

    {

    cout<<"y("<<i<<") = error "<<ex<<endl;

    }

    }

    }

    1. .

    2. ;

    3. ;

    4. ;

    5. ;

    6. ;

    7. ;

    8. ;

    9. ;

    10. ;

    11. ;

    12. ;

    13. ;

    14. ;

    15. ;

    16. ;

    17. ;

    18. ;

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Схема алгоритма.

    6. Листинг программы.

    7. Результаты выполнения программы.

    1. Стандарт С++ 11 (самостоятельное изучение)

    Краткая теория

    http://habrahabr.ru/post/182920/

    #1 — auto

    До С++11, ключевое слово auto использовалось как спецификатор хранения переменной (как, например, register, static, extern). В С++11 auto позволяет не указывать тип переменной явно, говоря компилятору, чтобы он сам определил фактический тип переменной, на основе типа инициализируемого значения.

    auto i = 42; // i - int

    auto l = 42LL; // l - long long

    auto p = new foo(); // p - foo*

    Использование auto позволяет сократить код (если, конечно, тип не int, который на одну букву меньше).

    Сравним С++03 и С++11

    // C++03

    for (std::vector<std::map<int, std::string>>::const_iterator it = container.begin(); it != container.end(); ++it)

    {

    // do smth

    }

    // C++11

    for (auto it = container.begin(); it != container.end(); ++it)

    {

    // do smth

    }

    Стоить отметить, что возвращаемое значение не может быть auto. Однако, вы можете использовать auto вместо типа возвращаемого значения функции. В таком случае, auto не говорит компилятору, что он должен определить тип, он только дает ему команду искать возвращаемый тип в конце функции. В примере ниже, возвращаемый тип функции compose— это возвращаемый тип оператора +, который суммирует значения типа T и E.

    template <typename T, typename E>

    auto compose(T a, E b) -> decltype(a+b) // decltype - позволяет определить тип на основе входного параметра

    {

    return a+b;

    }

    auto c = compose(2, 3.14); // c - double

    #2 — nullptr

    Раньше, для обнуления указателей использовался макрос NULL, являющийся нулем — целым типом, что, естественно, вызывало проблемы (например, при перегрузке функций). Ключевое слово nullptr имеет свой собственный тип std::nullptr_t, что избавляет нас от бывших проблем. Существуют неявные преобразования nullptr к нулевому указателю любого типа и к bool (как false), но преобразования к целочисленным типам нет.

    void foo(int* p) {}

    void bar(std::shared_ptr<int> p) {}

    int* p1 = NULL;

    int* p2 = nullptr;

    if (p1 == p2)

    {

    }

    foo(nullptr);

    bar(nullptr);

    bool f = nullptr;

    int i = nullptr; // ошибка: для преобразования в int надо использовать reinterpret_cast

    #3 — range-based циклы

    Range-Based for — это цикл по контейнеру. Он аналогичен циклу for each в Java или C#. Синтаксически он повторяет for each из Java. Назван он Range-Based в первую очередь потому, чтобы избежать путаницы, ибо в STL уже давно есть алгоритм, именуемыйstd::for_each.

    std::vector<int> foo;

    // заполняем вектор

    for (int x : foo)

    std::cout << x << std::endl;

    Модель ссылок работает также, как и везде:

    for (int& x : foo)

    x *= 2;

    for (const int& x : foo)

    std::cout << x << std::endl;

    Красиво и удобно, правда? Рассмотренный выше auto усиливает данную конструкцию:

    std::vector<std::pair<int, std::string>> container;

    // ...

    for (const auto& i : container)

    std::cout << i.second << std::endl;

    Range-Based for, к слову, работает и на обычных статических массивах:

    int foo[] = {1, 4, 6, 7, 8};

    for (int x : foo)

    std::cout << x << std::endl;

    #4 — override и final

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

    class B

    {

    public:

    virtual void f(short) {std::cout << "B::f" << std::endl;}

    };

    class D : public B

    {

    public:

    virtual void f(int) override {std::cout << "D::f" << std::endl;}

    };

    Теперь это вызовет ошибку при компиляции (точно так же, если бы вы использовалиoverride во втором примере):

    D::f: method with override specifier 'override' did not override any base class methods

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

    class B

    {

    public:

    virtual void f(int) {std::cout << "B::f" << std::endl;}

    };

    class D : public B

    {

    public:

    virtual void f(int) override final {std::cout << "D::f" << std::endl;}

    };

    class F : public D

    {

    public:

    virtual void f(int) override {std::cout << "F::f" << std::endl;}

    };

    Функция, объявленная как final, не может быть переопределена функцией F::f() — в этом случае, она переопределяет метод базового класса (В) для класса D.

    #5 — строго-типизированный enum

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

    Эти проблемы устранены в С++11 с введением новой категории перечислений, названных strongly-typed enums. Они определяются ключевым словом enum class. Они больше не экспортируют свои перечисляемые значения в окружающую область видимости, больше не преобразуются неявно в целый тип и могут иметь определенный пользователем тип (эта опция так же добавлена и для «традиционных» перечислений").

    enum class Options {None, One, All};

    Options o = Options::All;

    #6 — интеллектуальные указатели

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

    unique_ptr: должен использоваться, когда ресурс памяти не должен был разделяемым (у него нет конструктора копирования), но он может быть передан другому unique_ptr

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

    weak_ptr: содержит ссылку на объект, которым управляет shared_ptr, но не осуществляет подсчет ссылок; позволяет избавиться от циклической зависимости

    Приведенный ниже пример демонстрирует unique_ptr. Для передачи владения объектом другому unique_ptr, используйте std::move (эта функция будет обсуждаться в последнем пункте). После передачи владения, интеллектуальный указатель, который передал владение, становится нулевым и get() вернет nullptr.

    void foo(int* p)

    {

    std::cout << *p << std::endl;

    }

    std::unique_ptr<int> p1(new int(42));

    std::unique_ptr<int> p2 = std::move(p1); // transfer ownership

    if (p1)

    foo(p1.get());

    (*p2)++;

    if (p2)

    foo(p2.get());

    Второй пример демонстрирует shared_ptr. Использование похоже, хотя семантика отличается, поскольку теперь владение совместно используемое.

    void foo(int* p)

    {

    }

    void bar(std::shared_ptr<int> p)

    {

    ++(*p);

    }

    std::shared_ptr<int> p1(new int(42));

    std::shared_ptr<int> p2 = p1;

    bar(p1);

    foo(p2.get());

    И, наконец, пример с weak_ptr. Заметьте, что вы должны получить shared_ptr для объекта, вызывая lock(), чтобы получить доступ к объекту.

    auto p = std::make_shared<int>(42);

    std::weak_ptr<int> wp = p;

    {

    auto sp = wp.lock();

    std::cout << *sp << std::endl;

    }

    p.reset();

    if (wp.expired())

    std::cout << "expired" << std::endl;

    #7 — лямбды

    В новом стандарте наконец-то была добавлена поддержка лямбда-выражений. Мы можете использовать лямбды везде, где ожидается функтор или std::function. Лямбда, вообще говоря, представляет собой более короткую запись функтора, что-то вроде анонимного функтора. Подробнее можно почитать, например, на MSDN.

    std::vector<int> v;

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

    std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;});

    auto is_odd = [](int n) {return n%2==1;};

    auto pos = std::find_if(std::begin(v), std::end(v), is_odd);

    if(pos != std::end(v))

    std::cout << *pos << std::endl;

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

    auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};

    error C3533: 'auto &': a parameter cannot have a type that contains 'auto'

    error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer

    error C3536: 'fib': cannot be used before it is initialized

    error C2064: term does not evaluate to a function taking 1 arguments

    Здесь имеет место циклическая зависимость. Чтобы избавиться от нее, необходимо явно определить тип функции, используя std::function.

    std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};

    1. Многопоточное программирование, thread-safety

      1. ВВЕДЕНИЕ В МНОГОПОТОЧНОСТЬ. НОВЫЕ ВОЗМОЖНОСТИ СТАНДАРТА C++11

    http://ru.cppreference.com/w/cpp/thread

    Многопоточность в C++11 - YouTube (http://www.youtube.com/watch?v=TA7M9Ojje-A)

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

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

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

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

    Инициализация потока

    В новом стандарте C++11 многопоточность осуществлен в классе thread, который определен в файле thread.h. Для того чтобы создать новый поток нужно создать объект класса thread и инициализировать передав в конструктор имя функции которая должна выполнятся в потоке. Давайте посмотрим на маленький пример многопоточной программы, чтобы все стало понятнее.

    #include<iostream>

    #include<thread> //Файл в котором определен класс thread

    using namespace std;

    void anyFunc() {

    cout<<"thread function";

    }

    int main() {

    thread func_thread(anyFunc);

    return 0;

    }

    В этой программе создается новый объект класса thread и в объявлении конструктору передается имя функции anyFunc, который печатает на экране текст "thread function". Но если скомпилировать данное приложение и запустить, то как бы странно не было оно закончится аварийна с сообщением об ошибке. Все дело в том что главная функция программы main создает объект func_thread, с параметром конструктора anyFunc и продолжает свое выполнение не дожидаясь чтобы процесс закончился, что и вызывает ошибку времени выполнения. Чтобы ошибки не было надо чтобы до того как закончится функция main все потоки были закончены. Это осуществляется путем синхронизации потоков вызывая метод join. Метод join возвращает выполнение программе когда поток заканчивается, после чего объект класса thread можно безопасно уничтожить.

    #include<iostream>

    #include<thread> //Файл в котором определен класс thread

    using namespace std;

    void anyFunc() {

    cout << "thread function";

    }

    int main() {

    thread func_thread(anyFunc);

    func_thread.join();

    // Выполнение возвращается функции main когда поток заканчивается

    return 0;

    }

    Позвольте добавить что перед вызовом функции join надо проверить является ли объект joinable то есть представляет он реальный поток или нет, к примеру объект может быть объявлен но не инициализирован или уже закончен вызовом функции join. Проверка делается функцией joinable, который возвращает true в случае если объект представляет исполняемый поток и false в противном случаи. Надо отметить что может быть ситуация когда нам ненужно ждать чтобы поток закончился, для этого у класса thread есть другой метод по имени detach. Обе метода ничего не принимают и не возвращают и после их вызова объект становится not joinable и можно безопасно уничтожить.

    #include<iostream>

    #include<thread> //Файл в котором определен класс thread

    using namespace std;

    void anyFunc() {

    cout << "thread function";

    }

    int main() {

    thread func_thread(anyFunc);

    if (func_thread.joinable())

    func_thread.join();

    // Выполнение возвращается функции main когда поток заканчивается

    // func_thread.detach(); В этом случае поток заканчивается принудительно

    return 0;

    }

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

    #include<iostream>

    #include<thread> //Файл в котором определен класс thread

    using namespace std;

    void printStr(char * str) {

    cout << str << '\n';

    }

    void printArray(int a[],const int len) {

    for (int i = 0; i < len; i++) {

    cout << a[i] << ' ';

    }

    }

    int main() {

    char* str = "thread function with parametrs";

    const int len = 8;

    int arr[len] = {12, 45, -34, 57, 678, 89, 0, 1};

    // Передаем параметр функции во время инициализации

    thread func_thread(printStr, str);

    // Параметров может быть много

    thread func_thread2(printArray, arr, len);

    if (func_thread.joinable()) func_thread.join();

    if (func_thread2.joinable()) func_thread2.join();

    return 0;

    }

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

    #include<iostream>

    #include<cstdlib>

    #include<thread>

    using namespace std;

    void addElements(int a[], int &len) {

    for (int i = len; i < len + 5; i++) {

    a[i] = rand();

    }

    len += 5;

    }

    int main() {

    const int LENGTH = 20;

    int arr[LENGTH] = {1, 2, 3, 4, 5}, current_length = 5;

    cout << "Output the array before thread\n";

    for (int i = 0; i < current_length; i++) {

    cout << arr[i] << ' ';

    }

    thread arr_thread(addElements, arr, ref(current_length));

    if (arr_thread.joinable()) arr_thread.join();

    cout << "\nOutput th array after thread\n";

    for (int i = 0; i < current_length; i++) {

    cout << arr[i] << ' ';

    }

    return 0;

    }

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

    #include<iostream>

    #include<thread>

    using namespace std;

    class arrayModifier {

    public:

    void operator()(int a[], int len) {

    for (int i = 0; i < len; i++) {

    a[i] *= 2;

    }

    }

    void invers(int a[], int len) {

    for (int i = 0; i < len; i++) {

    a[i] *= -1;

    }

    }

    };

    int main() {

    const int length = 5;

    int arr[length] = {1, 2, 3, 4, 5};

    arrayModifier obj;

    cout << "Output the array before threads\n";

    for (int i = 0; i < length; i++) {

    cout << arr[i] << ' ';

    }

    // Инициализируется объект функцией

    thread arr_thread(obj, arr, length);

    // Инициализируется обычным открытым методом

    thread arr_thread2(&arrayModifier::invers, &obj, arr, length);

    if (arr_thread.joinable()) arr_thread.join();

    if (arr_thread2.joinable()) arr_thread2.join();

    cout << "\nOutput th array after threads\n";

    for (int i = 0; i < length; i++) {

    cout << arr[i] << ' ';

    }

    return 0;

    }

    В вышеприведенном примере у класса arrayModifier есть два метода первый это operator() который элементы массива умножает на 2, а второй умножает на -1. Во втором объекте мы передаем адрес объекта связи с тем что метод класса в качестве скриптого параметра принимает адрес объекта для которого был вызван этот метод.

    Id потока

    У каждого потока есть свой уникальный номер который отличается от других потоков этой программы. Для этого в классе thread есть закрытый член id и открытый метод get_id вызов которого возвращает значение этого члена.

    #include<iostream>

    #include<thread>

    using namespace std;

    class printNumber {

    public:

    void operator()(int number,int arr[],int idx) {

    int sum = 0;

    for(int i = 0; i < number; i++) {

    if(1%15 == 0) continue;

    if(i%3 == 0) sum += 3*i;

    if(i%5 == 0) sum += 5*i;

    }

    arr[idx] = sum;

    }

    };

    int main() {

    const int length = 10;

    thread::id id;

    thread thread_array[length];

    int res_arr[length] = {0};

    for (int i = 0; i < length; i++) {

    thread_array[i] = thread(printNumber(), rand(),res_arr,i);

    }

    for (int i = 0; i < length; i++) {

    if (thread_array[i].joinable()) {

    id = thread_array[i].get_id();

    thread_array[i].join();

    cout << "Thread with id " << id << " finished. With result "<<res_arr[i]<<"\n";

    }

    }

    return 0;

    }

    Пространство имен this_thread

    В заголовочном файле thread.h определено пространство имен this_thread который содержит в себе функции для работы с конкретным потоком. Три из этих функций для того чтобы на некоторое время остановить выполнение потока: sleep_until - передается переменная класса chrono:time_point и блокируется выполнение потока пока системные часы не дойдут до этого времени; sleep_for - передается переменная класса chrono::duration и выполнение потока блокируется пока не прошло столько времени сколько было преданно; yield - останавливает выполнение потока на некоторое время предоставляя возможность выполнится другим потокам. А четвертая функция это get_id и как метод класса thread возвращает id потока.

    #include<iostream>

    #include<sstream>

    #include<chrono>

    #include<thread>

    using namespace std;

    class printNumber {

    public:

    void operator()() {

    ostringstream out;

    thread::id id = this_thread::get_id();

    out << "Thread with id " << id << " started\n";

    cout << out.str();

    // Останавливает выполнение на одну секунду

    this_thread::sleep_for(chrono::seconds(1));

    out.str("");

    out << "Thread with id " << id << " finished\n";

    cout << out.str();

    }

    };

    int main() {

    const int length = 10;

    thread thread_array[length];

    for (int i = 0; i < length; i++) {

    thread_array[i] = thread(printNumber());

    }

    for (int i = 0; i < length; i++) {

    if (thread_array[i].joinable()) {

    thread_array[i].join();

    }

    }

    return 0;

    }

    Одновременный доступ к ресурсам

    В вышеприведенном примере для печати на экран использовался sstream, предварительно превращая в строку то что хочу печатать а потом передаю в поток вывода. А что если сразу передать в поток вывода, сперва первую строку, потом переменную типа thread::id и наконец вторую строку. Но в этом случае потоки не по очереди будут передавать в поток вывода и в итоге получается совсем не то что мы хотели. Такая ситуация бывает когда несколько потоков работают с одним и тем же объектом. Для того чтобы предотвратить это воспользуемся классом mutex который определен в файле mutex. Переменная типа mutex можно блокировать и разблокировать. Когда вызывается метод lock класса mutex, метод проверяет объект и если он разблокирован то блокирует его и возвращает выполнение, в противном случае оно ждет пока объект разблокируется и после чего делает то же самое. А метод unlock разблокирует объект класса mutex этим позволяя другим процессам его блокировать.

    #include<iostream>

    #include<cstdlib>

    #include<vector>

    #include<mutex>

    #include<thread>

    using namespace std;

    const int elementsCount = 10;

    void push(vector<int> &arr, mutex& m_arr, mutex& m_out) {

    int num;

    for (int i = 0; i < elementsCount; i++) {

    m_arr.lock();

    num = rand();

    arr.push_back(num);

    m_arr.unlock();

    m_out.lock();

    cout << "Push " << num << "\n";

    m_out.unlock();

    }

    }

    void pop(vector<int> &arr, mutex& m_arr, mutex& m_out) {

    int i = 0, num;

    while (i < elementsCount) {

    m_arr.lock();

    if (arr.size() > 0) {

    num = arr.back();

    arr.pop_back();

    m_out.lock();

    cout << "Pop " << num << "\n";

    m_out.unlock();

    i++;

    }

    m_arr.unlock();

    }

    }

    int main() {

    mutex m_arr, m_out;

    vector<int> vec;

    thread push_thread(push, ref(vec), ref(m_arr), ref(m_out));

    thread pop_thread(pop, ref(vec), ref(m_arr), ref(m_out));

    if (push_thread.joinable()) push_thread.join();

    if (pop_thread.joinable()) pop_thread.join();

    return 0;

    }

    В вышеприведенной программе мы создаем два объекта класса thread первый инициализируем функцией push, который добавляет в вектор 10 элементов блокируя и разблокируя объекты m_arr и m_out, а второй функцией pop, который удаляет 10 элементов из вектора, конечно опять блокируя и разблокируя объекты m_arr и m_out.

    Источник http://www.cplusplus.com/reference/multithreading/.

    Лабораторная работа № 20. Многопоточное программирование.

    Цель работы:

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

    Контрольные вопросы

    1. Поток.

    2. Процессы

    3. ID потока

    4. Пространство имен this_thread

    Варианты заданий

    1. Даны последовательности символов А = {а0…аn–1} и С = {с0…ск–1}. В общем случае n ≠ k. Создать многопоточное приложение, определяющее, совпадают ли посимвольно строки А и С. Количество потоков является входным параметром программы, количество символов в строках может быть не кратно количеству потоков.

    2. Дана последовательность символов С = {с0…сn–1}. Дан набор из N пар кодирующих символов (ai,bi). Создать многопоточное приложение, кодирующее строку С следующим образом: поток 0 заменяет в строке C все символы a0 на символы b0, поток 1 заменяет в строке C все символы a1 на символы b1, и т.д. Потоки должны осуществлять кодирование последовательно.

    3. Дана последовательность символов С = {с0…сn–1}. Дан набор из N пар кодирующих символов (ai,bi), т.е. все символы строки ai заменяются на bi. Создать многопоточное приложение, кодирующее строку С следующим образом: строка разделяется на подстроки и каждый поток осуществляет кодирование своей подстроки. Количество символов с последовательности, количество кодирующих пар и потоков являются входными параметрами программы, количество символов в строке может быть не кратно количеству потоков.

    4. Дана последовательность символов С = {с0…сn–1} и символ b. Создать многопоточное приложение для определения количество вхождений символа b в строку C. Количество потоков является входным параметром программы, количество символов в строке может быть не кратно количеству потоков.

    5. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска суммы ∑ai. Количество потоков является входным параметром программы, потоки проводят вычисления независимо друг от друга, количество символов в строке может быть не кратно количеству потоков.

    6. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска произведения чисел a0*а1*…*an–1. Количество потоков является входным параметром программы, потоки проводят вычисления независимо друг от друга, количество символов в строке может быть не кратно количеству потоков.

    7. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска максимального ai. Количество потоков является входным параметром программы, потоки проводят вычисления независимо друг от друга, количество символов в строке может быть не кратно количеству потоков.

    8. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска минимального ai. Количество потоков является входным параметром программы, потоки проводят вычисления независимо друг от друга, количество символов в строке может быть не кратно количеству потоков.

    9. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска всех ai, являющихся простыми числами. Количество потоков является входным параметром программы, потоки проводят вычисления независимо друг от друга, количество символов в строке может быть не кратно количеству потоков.

    10. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска всех ai, являющихся квадратами, любого натурального числа.

    11. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для вычисления выражения a0-а1+a2-а3+a4-а5+...

    12. Дана последовательность натуральных чисел {a0…an–1}. Создать многопоточное приложение для поиска суммы ∑ai, где ai – четные числа.

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

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

    15. Командиру N-ской ВЧ полковнику Кузнецову требуется перемножить два секретных числа. Полковник Кузнецов вызывает дежурного по части лейтенанта Смирнова и требует в течение получаса предоставить ему ответ. Лейтенант Смирнов будит старшего по караулу сержанта Петрова и приказывает ему в 15 минут предоставить ответ. Сержант Петров вызывает к себе рядового Иванова, бывшего студента СФУ, и поручает ему ответственное задание по определению произведения. Рядовой Иванов успешно справляется с поставленной задачей и ответ по цепочке передается полковнику Кузнецову. Требуется создать многопоточное приложение, в котором все военнослужащие от полковника до рядового моделируются потоками одного вида.

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Схема алгоритма.

    6. Листинг программы.

    7. Результаты выполнения программы.

      1. Механизмы синхронизации Вводные понятия синхронизации потоков

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

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

    Синхронизация потоков – согласование потоками взаимного порядка доступа к общим ресурсам и очередности обработки событий.

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

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

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

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

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

    Работа с потоками с помощью функций WinApi Несинхронизированные потоки

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

    #include <process.h>

    #include <stdio.h>

    int a[ 5 ];

    void Thread( void* pParams ){

    int i, num = 0;

    while ( 1 )

    {

    for ( i = 0; i < 5; i++ ) a[ i ] = num;

    num++;

    }

    }

    int main( void ){

    _beginthread( Thread, 0, NULL );

    while( 1 )

    printf("%d %d %d %d %d\n",

    a[ 0 ], a[ 1 ], a[ 2 ],

    a[ 3 ], a[ 4 ] );

    return 0;

    }

    Как видно из результата работы процесса, основной поток (сама программа) и поток Thread действительно работают параллельно (красным цветом обозначено состояние, когда основной поток выводит массив во время его заполнения потоком Thread):

    81751652 81751652 81751651 81751651 81751651

    81751652 81751652 81751651 81751651 81751651

    83348630 83348630 83348630 83348629 83348629

    83348630 83348630 83348630 83348629 83348629

    83348630 83348630 83348630 83348629 83348629

    Запустите программу, затем нажмите "Pause" для остановки вывода на дисплей (т.е. приостанавливаются операции ввода/вывода основного потока, но поток Thread продолжает свое выполнение в фоновом режиме) и любую другую клавишу для возобновления выполнения.

    Критические секции

    А что делать, если основной поток должен читать данные из массива после его обработки в параллельном процессе? Одно из решений этой проблемы - использование критических секций.

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

    #include <windows.h>

    #include <process.h>

    #include <stdio.h>

    CRITICAL_SECTION cs;

    int a[5];

    void Thread(void* pParams) {

    int i, num = 0;

    while (TRUE)

    {

    EnterCriticalSection(&cs);

    for (i = 0; i < 5; i++) a[i] = num;

    LeaveCriticalSection(&cs);

    num++;

    }

    }

    int main(void) {

    InitializeCriticalSection(&cs);

    _beginthread(Thread, 0, NULL);

    while (TRUE)

    {

    EnterCriticalSection(&cs);

    printf("%d %d %d %d %d\n",

    a[0], a[1], a[2],

    a[3], a[4]);

    LeaveCriticalSection(&cs);

    }

    return 0;

    }

    Мьютексы (взаимоисключения)

    Мьютекс (взаимоисключение, mutex) - это объект синхронизации, который устанавливается в особое сигнальное состояние, когда не занят каким-либо потоком. Только один поток владеет этим объектом в любой момент времени, отсюда и название таких объектов - одновременный доступ к общему ресурсу исключается. Например, чтобы исключить запись двух потоков в общий участок памяти в одно и то же время, каждый поток ожидает, когда освободится мьютекс, становится его владельцем и только потом пишет что-либо в этот участок памяти. После всех необходимых действий мьютекс освобождается, предоставляя другим потокам доступ к общему ресурсу.

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

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

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

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

    • Процесс может указать имя существующего мьютекса при вызове функций OpenMutex или CreateMutex .

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

    #include <windows.h>

    #include <process.h>

    #include <stdio.h>

    HANDLE hMutex;

    int a[ 5 ];

    void Thread( void* pParams )

    {

    int i, num = 0;

    while ( TRUE ) {

    WaitForSingleObject( hMutex, INFINITE );

    for ( i = 0; i < 5; i++ ) a[ i ] = num;

    ReleaseMutex( hMutex );

    num++;

    }

    }

    int main( void )

    {

    hMutex = CreateMutex( NULL, FALSE, NULL );

    _beginthread( Thread, 0, NULL );

    while( TRUE )

    {

    WaitForSingleObject( hMutex, INFINITE );

    printf( "%d %d %d %d %d\n",

    a[ 0 ], a[ 1 ], a[ 2 ],

    a[ 3 ], a[ 4 ] );

    ReleaseMutex( hMutex );

    }

    return 0;

    }

    События

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

    Событие - это объект синхронизации, состояние которого может быть установлено в сигнальное путем вызова функций SetEvent или PulseEvent . Существует два типа событий:

    Тип объекта

    Описание

    Событие с ручным сбросом

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

    Событие с автоматическим сбросом

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

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

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

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

    #include <windows.h>

    #include <process.h>

    #include <stdio.h>

    HANDLE hEvent1, hEvent2;

    int a[ 5 ];

    void Thread( void* pParams )

    {

    int i, num = 0;

    while ( TRUE )

    {

    WaitForSingleObject( hEvent2, INFINITE );

    for ( i = 0; i < 5; i++ ) a[ i ] = num;

    SetEvent( hEvent1 );

    num++;

    }

    }

    int main( void )

    {

    hEvent1 = CreateEvent( NULL, FALSE, TRUE, NULL );

    hEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL );

    _beginthread( Thread, 0, NULL );

    while( TRUE )

    {

    WaitForSingleObject( hEvent1, INFINITE );

    printf( "%d %d %d %d %d\n",

    a[ 0 ], a[ 1 ], a[ 2 ],

    a[ 3 ], a[ 4 ] );

    SetEvent( hEvent2 );

    }

    return 0;

    }

    Потокобезапасность

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

    Пример реализации потокобезопасной коллекции для WIN32 с использованием критической секции приведен ниже:

    #include <windows.h>

    #include <iostream>

    #include <strstream>

    #include <string>

    #include <vector>

    #include <algorithm>

    using namespace std;

    void printInt(int number);

    class MyCriticalSection

    {

    private:

    CRITICAL_SECTION CriticalSection;

    bool success;

    public:

    MyCriticalSection()

    {

    success = true;

    // Initialize the critical section.

    InitializeCriticalSection(&CriticalSection);

    };

    bool Lock()

    {

    // Request ownership of the critical section.

    __try

    {

    EnterCriticalSection(&CriticalSection);

    return true;

    }

    __except (EXCEPTION_EXECUTE_HANDLER)

    {

    // Release ownership of the critical section.

    LeaveCriticalSection(&CriticalSection);

    // Release resources used by the critical section object.

    DeleteCriticalSection(&CriticalSection);

    success = false;

    return false;

    }

    };

    void Unlock()

    {

    if (success)

    {

    // Release ownership of the critical section.

    LeaveCriticalSection(&CriticalSection);

    }

    };

    ~MyCriticalSection()

    {

    // Release resources used by the critical section object.

    if (success)

    {

    DeleteCriticalSection(&CriticalSection);

    }

    };

    };

    // define thread safe vector of integers

    class VectorInt : public vector<int>, MyCriticalSection

    {

    public:

    VectorInt() : vector<int>(), MyCriticalSection()

    {}

    void safe_push_back(int arg)

    {

    Lock();

    push_back(arg);

    Unlock();

    }

    };

    int main()

    {

    VectorInt vx;

    for (int i = 0; i < 5; i++)

    {

    vx.safe_push_back(i);

    }

    for_each(vx.begin(), vx.end(), printInt);

    return 0;

    }

    void printInt(int number)

    {

    cout << number << endl;

    }

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

    Типовые задачи синхронизации

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

    К типовым задачам синхронизации можно отнести:

    • взаимное исключение;

    • обедающие философы;

    • поставщики - потребители;

    • читатели - писатели.

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

    Задача "обедающие философы": на круглом столе находятся k тарелок с едой, между которыми лежит столько же вилок, k>=2. В комнате имеется k философов, чередующих философские размышления с принятием пищи. За каждым философом закреплена своя тарелка; для еды философу нужны две вилки, причем он может использовать только вилки, примыкающие к его тарелке. Требуется так синхронизировать философов, чтобы каждый из них мог получить за ограниченное время доступ к своей тарелке. Предполагается, что длительность еды и размышлений философа конечна, но заранее недетерминирована.

    Задача "поставщики-потребители": имеется ограниченный буфер на m мест (m порций информации). Он является критическим ресурсом для процессов двух типов: процессы-поставщики, получая доступ к ресурсу, помещают на свободное место порцию информации; процессы-потребители, получая доступ к ресурсу, считывают из него порцию информации. Требуется исключить одновременный доступ процессов к ресурсу. При полном опустошении буфера задерживаются процессы-потребители, при полном заполнении буфера задерживаются процессы-поставщики.

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

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

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

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

    Механизм семафоров

    Пусть S - семафор - переменная специального типа с целочисленными значениями, над которой определены две операции: Р (закрытие) и V (открытие). Определим эти операции.

    P(S): если S≥1, то процесс продолжает выполняться, а S уменьшается на единицу; если S = 0, то процесс задерживается, а имя его передается в очередь процессов, ожидающих доступа к данному ресурсу (обычно семафоры связывают с некоторыми ресурсами).

    V(S): если в очереди к семафору S есть процессы, то один из них выбирается и активизируется (переводится в состояние готовности: помещается в очередь процессов, претендующих на процессорное время); если в очереди нет процессов, то выполняется операция S = S+1 (при условии непревышения результатом максимально допустимого значения семафора; для двоичного семафора S ∈{0,1}).

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

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

    S=1;

    Процесс_i:

    P(S);

    Критическая секция

    V(S);

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

    S1=1 S2=1 P(S1) P(S2) P(S2) P(S1)

    V(S2) V(S1) V(S1) V(S2)

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

    Семафорное решение задачи о философах

    О бозначим через Р1, Р2, Р3, Р4 - процессы-философы; b1, b2, b3, b4 - вилки. Для еды философу необходимо использовать две соседние вилки одновременно (рис. 3.12).

    Алгоритм работы каждого философа предполагает использование пяти семафоров (Si, i =1,4 ; S). Семафор S предназначен для взаимного исключения процессов при получении доступа к массиву b, отвечающему за вилки. Каждый семафор Si предназначен для подвешивания i-го философа в том случае, если в результате проверки вилок он обнаружит, что нужных ему вилок в наличии нет. Приведем алгоритм работы первого философа, остальные работают аналогично.

    P1: P(S[1]); P(S); if (b1>0)&(b2>0) then begin b1:=0; b2:=0; V(S);

    {питание} P(S);

    b1:=1; b2:=1; V(S);

    for i:=1 to 4 do V(S[i]); {философствование} end else

    V(S); goto P1;

    Лабораторная работа № 21. Семафоры: защита критических секций, условная синхронизация Цели и задачи:

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

    Время: 4 часа

    http://www.e-reading.club/chapter.php/141823/364/Hart_-_Sistemnoe_programmirovanie_v_srede_Windows.html

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

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

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

    Порядок выполнения лабораторной работы

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

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

    Пример

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

    Обсуждение.

    Пусть для определенности буфер – это целочисленный массив из 100 элементов. Задача обладает двумя критическими секциями.

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

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

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

    Рисунок 0‑1. Схема потоков

    Решение задачи о кольцевом буфере с помощью функций WinAPI.

    #include <stdio.h>

    #include <stdlib.h>

    #include <conio.h>

    #include <windows.h>

    const int n = 100, // длина буфера

    m = 7, // количество производителей

    k = 3; // количество потребителей

    int buf[n], front = 0, rear = 0;

    HANDLE hSemFull, hSemEmpty, // семафоры для условной синхронизации

    hMutexD, hMutexF; // мьютексы для исключительного доступа

    // процесс, пополняющий буфер

    DWORD WINAPI Producer(PVOID pvParam)

    {

    int num;

    long prev;

    num = *((int *)pvParam);

    printf("thread %d (producer): start!\n", num);

    while (true)

    {

    WaitForSingleObject(hSemEmpty, INFINITE);

    WaitForSingleObject(hMutexD, INFINITE);

    buf[rear] = rand() % n;

    printf("\nproducer %d: data = %d to %d", num, buf[rear], rear);

    rear = (rear + 1) % n;

    Sleep(1000);

    ReleaseMutex(hMutexD);

    ReleaseSemaphore(hSemFull, 1, &prev);

    } return 0;

    }

    // процесс, берущий данные из буфера

    DWORD WINAPI Consumer(PVOID pvParam)

    {

    int num, data;

    long prev;

    num = *((int *)pvParam);

    printf("thread %d (consumer): start!\n", num);

    while (true)

    {

    WaitForSingleObject(hSemFull, INFINITE);

    WaitForSingleObject(hMutexF, INFINITE);

    data = buf[front];

    printf("\nconsumer %d: data = %d from %d", num, data, front);

    front = (front + 1) % n;

    Sleep(1000);

    ReleaseMutex(hMutexF);

    ReleaseSemaphore(hSemEmpty, 1, &prev);

    }

    return 0;

    }

    int main(int argc, char** argv)

    {

    int i, x[k + m];

    DWORD dwThreadId[k + m], dw;

    HANDLE hThread[k + m];

    hSemEmpty = CreateSemaphore(NULL, n, n, L"Empty");

    hSemFull = CreateSemaphore(NULL, 0, n, L"Full");

    hMutexD = CreateMutex(NULL, FALSE, L"MutexD");

    hMutexF = CreateMutex(NULL, FALSE, L"MutexF");

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

    {

    x[i] = i;

    hThread[i] = CreateThread(NULL, 0, Producer, (PVOID)&x[i], 0, &dwThreadId[i]);

    if (!hThread) printf("main process: thread %d not execute!", i);

    }

    for (i = k; i<k + m; i++)

    {

    x[i] = i;

    hThread[i] = CreateThread(NULL, 0, Consumer, (PVOID)&x[i], 0, &dwThreadId[i]);

    if (!hThread) printf("main process: thread %d not execute!", i);

    }

    WaitForMultipleObjects(k + m, hThread, TRUE, INFINITE);

    // закрытие описателей событий

    CloseHandle(hSemFull);

    CloseHandle(hSemEmpty);

    CloseHandle(hMutexF);

    CloseHandle(hMutexD);

    return 0;

    }

    Варианты заданий

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

    2. Задача о Винни-Пухе или правильные пчелы. В одном лесу живут n пчел и один медведь, которые используют один горшок меда, вместимостью Н глотков. Сначала горшок пустой. Пока горшок не наполнится, медведь спит. Как только горшок заполняется, медведь просыпается и съедает весь мед, после чего снова засыпает. Каждая пчела многократно собирает по одному глотку меда и кладет его в горшок. Пчела, которая приносит последнюю порцию меда, будит медведя. Создать многопоточное приложение, моделирующее поведение пчел и медведя.

    3. Задача о читателях и писателях. Базу данных разделяют два типа процессов – читатели и писатели. Читатели выполняют транзакции, которые просматривают записи базы данных, транзакции писателей и просматривают и изменяют записи. Предполагается, что в начале БД находится в непротиворечивом состоянии (т.е. отношения между данными имеют смысл). Каждая отдельная транзакция переводит БД из одного непротиворечивого состояния в другое. Для предотвращения взаимного влияния транзакций процесс-писатель должен иметь исключительный доступ к БД. Если к БД не обращается ни один из процессов-писателей, то выполнять транзакции могут одновременно сколько угодно читателей. Создать многопоточное приложение с потоками-писателями и потоками-читателями. Реализовать решение, используя семафоры.

    4. Задача об обедающих философах. Пять философов сидят возле круглого стола. Они проводят жизнь, чередуя приемы пищи и размышления. В центре стола находится большое блюдо спагетти. Спагетти длинные и запутанные, философам тяжело управляться с ними, поэтому каждый из них, что бы съесть порцию, должен пользоваться двумя вилками. К несчастью, философам дали только пять вилок. Между каждой парой философов лежит одна вилка, поэтому эти высококультурные и предельно вежливые люди договорились, что каждый будет пользоваться только теми вилками, которые лежат рядом с ним (слева и справа). Написать многопоточную программу, моделирующую поведение философов с помощью семафоров. Программа должна избегать фатальной ситуации, в которой все философы голодны, но ни один из них не может взять обе вилки (например, каждый из философов держит по одной вилки и не хочет отдавать ее). Решение должно быть симметричным, то есть все потоки-философы должны выполнять один и тот же код.

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

    6. Задача о курильщиках. Есть три процесса-курильщика и один процесс-посредник. Курильщик непрерывно скручивает сигареты и курит их. Чтобы скрутить сигарету, нужны табак, бумага и спички. У одного процесса-курильщика есть табак, у второго – бумага, а у третьего – спички. Посредник кладет на стол по два разных случайных компонента. Тот процесс-курильщик, у которого есть третий компонент, забирает компоненты со стола, скручивает сигарету и курит. Посредник дожидается, пока курильщик закончит, затем процесс повторяется. Создать многопоточное приложение, моделирующее поведение курильщиков и посредника. При решении задачи использовать семафоры.

    7. Военная задача. Анчуария и Тарантерия – два крохотных латиноамериканских государства, затерянных в южных Андах. Диктатор Анчуарии, дон Федерико, объявил войну диктатору Тарантерии, дону Эрнандо. У обоих диктаторов очень мало солдат, но очень много снарядов для минометов, привезенных с последней американской гуманитарной помощью. Поэтому армии обеих сторон просто обстреливают наугад территорию противника, надеясь поразить что-нибудь ценное. Стрельба ведется по очереди до тех пор, пока либо не будут уничтожены все цели, либо стоимость потраченных снарядов не превысит суммарную стоимость всего того, что ими можно уничтожить. Создать многопоточное приложение, моделирующее военные действия.

    8. Задача о читателях и писателях-2грязное чтение»). Базу данных разделяют два типа потоков – читатели и писатели. Читатели выполняют транзакции, которые просматривают записи базы данных, транзакции писателей и просматривают и изменяют записи. Предполагается, что в начале БД находится в непротиворечивом состоянии (т.е. отношения между данными имеют смысл). Транзакции выполняются в режиме «грязного чтения», то есть процесс-писатель не может получить доступ к БД только в том случае, если ее занял другой процесс-писатель, а процессы-читатели ему не мешают. Создать многопоточное приложение с потоками-писателями и потоками-читателями. Реализовать решение, используя семафоры, и не используя блокировки чтения-записи.

    9. Задача о читателях и писателях-3 («подтвержденное чтение»). Базу данных разделяют два типа процессов – читатели и писатели. Читатели выполняют транзакции, которые просматривают записи базы данных, транзакции писателей и просматривают и изменяют записи. Предполагается, что в начале БД находится в непротиворечивом состоянии (т.е. отношения между данными имеют смысл). Каждая отдельная транзакция переводит БД из одного непротиворечивого состояния в другое. Транзакции выполняются в режиме «подтвержденного чтения», то есть процесс-писатель не может получить доступ к БД в том случае, если ее занял другой процесс-писатель или процесс-читатель. К БД может обратиться одновременно сколько угодно процессов-читателей. Процесс читатель получает доступ к БД, даже если ее занял процесс-писатель. Создать многопоточное приложение с потоками-писателями и потоками-читателями. Реализовать решение, используя семафоры, и не используя блокировки чтения-записи.

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

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

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

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

    14. Задача о гостинице-2 (умные клиенты). В гостинице 10 номеров с ценой 200 рублей, 10 номеров с ценой 400 рублей и 5 номеров с ценой 600 руб. Клиент, зашедший в гостиницу, обладает некоторой суммой и получает номер по своим финансовым возможностям, если тот свободен. Если среди доступных клиенту номеров нет свободных, клиент уходит искать ночлег в другое место. Создать многопоточное приложение, моделирующее работу гостиницы.

    15. Задача о гостинице - 3 (дамы и джентльмены). В гостинице 10 номеров рассчитаны на одного человека и 15 номеров рассчитаны на двух человек. В гостиницу приходят клиенты дамы и клиенты джентльмены, и конечно они могут провести ночь в номере только с представителем своего пола. Если для клиента не находится подходящего номера, он уходит искать ночлег в другое место. Создать многопоточное приложение, моделирующее работу гостиницы.

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

    17. Задача о нелюдимых садовниках. Имеется пустой участок земли (двумерный массив) и план сада, который необходимо реализовать. Эту задачу выполняют два садовника, которые не хотят встречаться друг с другом. Первый садовник начинает работу с верхнего левого угла сада и перемещается слева направо, сделав ряд, он спускается вниз. Второй садовник начинает работу с нижнего правого угла сада и перемещается снизу вверх, сделав ряд, он перемещается влево. Если садовник видит, что участок сада уже выполнен другим садовником, он идет дальше. Садовники должны работать параллельно. Создать многопоточное приложение, моделирующее работу садовников. При решении задачи использовать мутексы. 18. Задача о картинной галерее. Вахтер следит за тем, чтобы в картинной галерее было не более 50 посетителей. Для обозрения представлены 5 картин. Посетитель ходит от картины к картине, и если на картину любуются более чем десять посетителей, он стоит в стороне и ждет, пока число желающих увидеть картину не станет меньше. Посетитель может покинуть галерею. Создать многопоточное приложение, моделирующее работу картинной галереи.

    19. Задача о Винни-Пухе - 3 или неправильные пчелы - 2. N пчел живет в улье, каждая пчела может собирать мед и сторожить улей (N>3). Ни одна пчела не покинет улей, если кроме нее в нем нет других пчел. Каждая пчела приносит за раз одну порцию меда. Всего в улей может войти тридцать порций меда. Вини-Пух спит пока меда в улье меньше половины, но как только его становится достаточно, он просыпается и пытается достать весь мед из улья. Если в улье находится менее чем три пчелы, Вини-Пух забирает мед, убегает, съедает мед и снова засыпает. Если в улье пчел больше, они кусают Вини-Пуха, он убегает, лечит укус, и снова бежит за медом. Создать многопоточное приложение, моделирующее поведение пчел и медведя.

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

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

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Схема алгоритма.

    6. Листинг программы.

    7. Результаты выполнения программы.

    1. Создание и использование DLL (Microsoft Visual C++).

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

    Таким образом, возникает задача разделения большой программы на отдельные независимые модули, каждый из которых содержит определенный набор процедур и функций. Процедуры и функции такого модуля может вызывать любая другая программа. Подобные модули получили название динамически подключаемых библиотек (DLL – dynamically linked library, Рис. 20 .1). Слово "динамический" указывает на то, что подключение библиотеки происходит не на этапе компиляции, а уже после запуска программы.

    Рис. 20.1 DLL – библиотеки.

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

    Создание повторно используемого кода (C++)

    С помощью Visual C++ можно создавать библиотеки трех следующих типов:

    • библиотеки динамической компоновки (DLL);

    • статические библиотеки;

    • управляемые сборки.

    Как правило, если необходимо создать библиотеку, которая могла бы использоваться машинным кодом C++, то следует предпочесть библиотеку динамической компоновки или статическую библиотеку. Если необходимо создать библиотеку, которая могла бы использоваться кодом на языках C++/CLI или на любом ином языке .NET, например C# или Visual Basic, то следует предпочесть управляемую сборку.

    Создание и использование библиотеки DLL (C++)

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

    В этом пошаговом руководстве рассматриваются следующие действия:

    • создание проекта библиотеки динамической компоновки (DLL);

    • добавление класса в библиотеку динамической компоновки;

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

    • использование функциональных возможностей библиотеки классов в консольном приложении;

    • запуск приложения.

    Обязательные компоненты

    Создание проекта библиотеки динамической компоновки (dll)

    1. В меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите Win32.

    3. В области Шаблоны выберите Консольное приложение Win32.

    4. Выберите имя проекта, например MathFuncsDll, и введите его в поле Имя. Выберите имя решения, например DynamicLibrary, и введите его в поле Имя решения.

    5. Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

    6. На странице Параметры приложения диалогового окна Мастер приложений Win32, в поле Тип приложения, выберите пункт DLL, если он доступен, либо пункт Консольное приложение в противном случае. В некоторых версиях Visual Studio создание проектов DLL с помощью мастеров не поддерживается. Необходимые изменения можно внести позднее для компиляции проекта в библиотеку DLL.

    7. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры выберите пункт Пустой проект.

    8. Чтобы создать проект, нажмите кнопку Готово.

    Добавление класса в библиотеку динамической компоновки

    1. Чтобы создать файл заголовка для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Заголовочный файл (.h). Выберите имя заголовочного файла, например MathFuncsDll.h, и нажмите кнопку Добавить. Отобразится пустой файл.

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

    // MathFuncsDll.h

    namespace MathFuncs

    {

    class MyMathFuncs

    {

    public:

    // Returns a + b

    static __declspec(dllexport) double Add(double a, double b);

    // Returns a - b

    static __declspec(dllexport) double Subtract(double a, double b);

    // Returns a * b

    static __declspec(dllexport) double Multiply(double a, double b);

    // Returns a / b

    // Throws DivideByZeroException if b is 0

    static __declspec(dllexport) double Divide(double a, double b);

    };

    }

    1. Обратите внимание на модификатор __declspec(dllexport) в объявлениях методов в этом коде. Этот модификатор разрешает экспорт метода библиотекой DLL для использования его другими приложениями. Дополнительные сведения см. в разделе dllexport, dllimport.

    2. Чтобы создать исходный файл для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Файл C++ (.cpp). Выберите имя исходного файла, например MathFuncsDll.cpp, и нажмите кнопку Добавить. Отобразится пустой файл.

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

    // MathFuncsDll.cpp

    // compile with: /EHsc /LD

    #include "MathFuncsDll.h"

    #include <stdexcept>

    using namespace std;

    namespace MathFuncs

    {

    double MyMathFuncs::Add(double a, double b)

    {

    return a + b;

    }

    double MyMathFuncs::Subtract(double a, double b)

    {

    return a - b;

    }

    double MyMathFuncs::Multiply(double a, double b)

    {

    return a * b;

    }

    double MyMathFuncs::Divide(double a, double b)

    {

    if (b == 0)

    {

    throw new invalid_argument("b cannot be zero!");

    }

    return a / b;

    }

    }

    1. Чтобы построить библиотеку DLL проекта, в меню Проект выберите СвойстваMathFuncsDll. В левой области в поле Свойства конфигурации выберите Общие. В правой области в поле Тип конфигурации выберите Динамическая библиотека (.dll). Нажмите кнопку ОК для сохранения изменений.

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

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

    1. Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее библиотеку динамической компоновки, в меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите Win32.

    3. В области Шаблоны выберите Консольное приложение Win32.

    4. Выберите имя проекта, например MyExecRefsDll, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и библиотека динамической компоновки.

    5. Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

    6. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Консольное приложение.

    7. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.

    8. Чтобы создать проект, нажмите кнопку Готово.

    Использование функциональных возможностей библиотеки классов в консольном приложении

    1. По завершении создания консольного приложения будет создана пустая программа. Имя исходного файла будет совпадать с именем, выбранным ранее для проекта. В этом примере он имеет имя MyExecRefsDll.cpp.

    2. Для использования математических процедур из библиотеки динамической компоновки необходимо сослаться на эту библиотеку. Для этого в меню Проект(Project) выберите пункт Ссылки....(Reference…) В диалоговом окне Окна свойств(Properties) разверните узел Общие свойства (Common Properties), выберите пункт Ссылки, а затем нажмите кнопку Добавить новую ссылку.... (Add New Rewference…).

    3. Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsDll. Затем нажмите кнопку ОК.

    4. Для создания ссылки на заголовочные файлы библиотеки динамической компоновки необходимо изменить путь к каталогам включения. Для этого в диалоговом окне Окна свойств (Properties) последовательно разверните узлы Свойства конфигурации (Configuration Properties), C/C++, а затем выберите Общие (General). Рядом с полем Дополнительные каталоги включения (Additional Include Directories) введите путь к месту размещения заголовочного файла MathFuncsDll.h.

    5. Исполняемый файл не загружает библиотеки динамической компоновки во время выполнения. Необходимо указать системе место для поиска библиотеки MathFuncsDll.dll. Это можно сделать с помощью переменной среды PATH. Для этого в диалоговом окне Окна свойств (Properties) разверните узел Свойства конфигурации (Configuration Properties), а затем выберите Отладка(Debugging). В поле Среда(Environment) введите следующую строку: PATH=<путь к файлу MathFuncsDll.dll>, где вместо <путь к файлу MathFuncsDll.dll> необходимо подставить фактическое местоположение библиотеки MathFuncsDll.dll. Нажмите кнопку ОК для сохранения всех изменений.

    6. Теперь класс MyMathFuncs можно использовать в приложении. Замените код в файле MyExecRefsDll.cpp следующим кодом:

    // MyExecRefsDll.cpp

    // compile with: /EHsc /link MathFuncsDll.lib

    #include <iostream>

    #include "MathFuncsDll.h"

    using namespace std;

    int main()

    {

    double a = 7.4;

    int b = 99;

    cout << "a + b = " <<

    MathFuncs::MyMathFuncs::Add(a, b) << endl;

    cout << "a - b = " <<

    MathFuncs::MyMathFuncs::Subtract(a, b) << endl;

    cout << "a * b = " <<

    MathFuncs::MyMathFuncs::Multiply(a, b) << endl;

    cout << "a / b = " <<

    MathFuncs::MyMathFuncs::Divide(a, b) << endl;

    return 0;

    }

    1. Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.

    Запуск приложения

    1. Убедитесь, что проект MyExecRefsDll выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsDll и затем в меню Проект выберите команду Назначить запускаемым проектом.

    2. Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат выполнения должен выглядеть примерно следующим образом:

    a + b = 106.4

    a - b = -91.6

    a * b = 732.6

    a / b = 0.0747475

    Создание и использование статической библиотеки (C++)

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

    В этом пошаговом руководстве рассматриваются следующие действия:

    • создание проекта статической библиотеки;

    • добавление класса в статическую библиотеку;

    • создание приложения, ссылающегося на статическую библиотеку;

    • использование функциональных возможностей статической библиотеки в консольном приложении;

    • запуск приложения.

    Создание проекта статической библиотеки

    1. В меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите Win32.

    3. В области Шаблоны выберите Консольное приложение Win32.

    4. Выберите имя проекта, например MathFuncsLib, и введите его в поле Имя. Выберите имя решения, например StaticLibrary, и введите его в поле Имя решения.

    5. Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

    6. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Статическая библиотека.

    7. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.

    8. Чтобы создать проект, нажмите кнопку Готово.

    Добавление класса в статическую библиотеку

    1. Чтобы создать файл заголовка для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Заголовочный файл (.h). Выберите имя заголовочного файла, например MathFuncsLib.h, и нажмите Добавить. Отобразится пустой файл.

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

    // MathFuncsLib.h

    namespace MathFuncs

    {

    class MyMathFuncs

    {

    public:

    // Returns a + b

    static double Add(double a, double b);

    // Returns a - b

    static double Subtract(double a, double b);

    // Returns a * b

    static double Multiply(double a, double b);

    // Returns a / b

    // Throws DivideByZeroException if b is 0

    static double Divide(double a, double b);

    };

    }

    1. Чтобы создать исходный файл для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Файл C++ (.cpp). Выберите имя исходного файла, например MathFuncsLib.cpp, и нажмите Добавить. Отобразится пустой файл.

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

    // MathFuncsLib.cpp

    // compile with: /c /EHsc

    // post-build command: lib MathFuncsLib.obj

    #include "MathFuncsLib.h"

    #include <stdexcept>

    using namespace std;

    namespace MathFuncs

    {

    double MyMathFuncs::Add(double a, double b)

    {

    return a + b;

    }

    double MyMathFuncs::Subtract(double a, double b)

    {

    return a - b;

    }

    double MyMathFuncs::Multiply(double a, double b)

    {

    return a * b;

    }

    double MyMathFuncs::Divide(double a, double b)

    {

    if (b == 0)

    {

    throw new invalid_argument("b cannot be zero!");

    }

    return a / b;

    }

    }

    1. Чтобы построить статическую библиотеку проекта, в меню Проект выберите СвойстваMathFuncsLib. В левой области в поле Свойства конфигурации выберите Общие. В правой области в поле Тип конфигурации выберите Статическая библиотека (.lib). Нажмите кнопку ОК для сохранения изменений.

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

    Создание приложения, ссылающегося на статическую библиотеку

    1. Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее статическую библиотеку, в меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите Win32.

    3. В области Шаблоны выберите Консольное приложение Win32.

    4. Выберите имя проекта, например MyExecRefsLib, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и статическая библиотека.

    5. Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

    6. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Консольное приложение.

    7. На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.

    8. Чтобы создать проект, нажмите кнопку Готово.

    Использование функциональных возможностей статической библиотеки в консольном приложении

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

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

    3. Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsLib. Затем нажмите кнопку ОК.

    4. Для создания ссылки на заголовочные файлы статической библиотеки необходимо изменить путь к каталогам включения. Для этого в диалоговом окне Окна свойств последовательно разверните узлы Свойства конфигурации, C/C++, а затем выберите Общие. Рядом с полем Дополнительные каталоги включения введите путь к месту размещения заголовочного файла MathFuncsLib.h.

    5. Теперь класс MyMathFuncs можно использовать в приложении. Замените код в файле MyExecRefsLib.cpp следующим кодом:

    // MyExecRefsLib.cpp

    // compile with: /EHsc /link MathFuncsLib.lib

    #include <iostream>

    #include "MathFuncsLib.h"

    using namespace std;

    int main()

    {

    double a = 7.4;

    int b = 99;

    cout << "a + b = " <<

    MathFuncs::MyMathFuncs::Add(a, b) << endl;

    cout << "a - b = " <<

    MathFuncs::MyMathFuncs::Subtract(a, b) << endl;

    cout << "a * b = " <<

    MathFuncs::MyMathFuncs::Multiply(a, b) << endl;

    cout << "a / b = " <<

    MathFuncs::MyMathFuncs::Divide(a, b) << endl;

    return 0;

    }

    1. Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.

    Запуск приложения

    1. Убедитесь, что проект MyExecRefsLib выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsLib и затем в меню Проект выберите команду Назначить запускаемым проектом.

    2. Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат выполнения должен выглядеть примерно следующим образом:

    a + b = 106.4

    a - b = -91.6

    a * b = 732.6

    a / b = 0.0747475

    Создание и использование управляемой сборки (C++)

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

    В этом пошаговом руководстве рассматриваются следующие действия:

    • создание нового проекта библиотеки классов;

    • добавление класса в библиотеку классов;

    • создание приложения, ссылающегося на библиотеку классов;

    • использование функциональных возможностей библиотеки классов в консольном приложении;

    • запуск приложения.

    Создание нового проекта библиотеки классов

    1. В меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите CLR. При этом будет создан проект, предназначенный для среды CLR.

    3. В области Шаблоны выберите пункт Библиотека классов.

    4. Выберите имя проекта, например MathFuncsAssembly, и введите его в поле Имя. Выберите имя решения, например ManagedAssemblies, и введите его в поле Имя решения.

    5. Нажмите ОК, чтобы создать проект.

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

    Добавление класса в библиотеку классов

    1. По завершении создания библиотеки классов CLR мастер создаст простейший класс. Имена файла заголовка и файла исходного кода будет совпадать с именем, выбранным ранее для проекта. В этом примере они имеют имена MathFuncsAssembly.h и MathFuncsAssembly.cpp.

    2. Замените код в файле MathFuncsAssembly.h простейшим классом MyMathFuncsAssembly, осуществляющим обычные арифметические операции, такие как сложение, вычитание, умножение и деление. Код должен выглядеть примерно следующим образом:

    // MathFuncsAssembly.h

    using namespace System;

    namespace MathFuncs

    {

    public ref class MyMathFuncs

    {

    public:

    // Returns a + b

    static double Add(double a, double b);

    // Returns a - b

    static double Subtract(double a, double b);

    // Returns a * b

    static double Multiply(double a, double b);

    // Returns a / b

    // Throws DivideByZeroException if b is 0

    static double Divide(double a, double b);

    };

    }

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

    // MathFuncsAssembly.cpp

    // compile with: /clr /LD

    #include "MathFuncsAssembly.h"

    namespace MathFuncs

    {

    double MyMathFuncs::Add(double a, double b)

    {

    return a + b;

    }

    double MyMathFuncs::Subtract(double a, double b)

    {

    return a - b;

    }

    double MyMathFuncs::Multiply(double a, double b)

    {

    return a * b;

    }

    double MyMathFuncs::Divide(double a, double b)

    {

    if (b == 0)

    {

    throw gcnew DivideByZeroException("b cannot be zero!");

    }

    return a / b;

    }

    }

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

    Создание приложения, ссылающегося на библиотеку классов

    1. Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее библиотеку классов, в меню Файл выберите пункт Создать и затем пункт Проект....

    2. В узле Visual C++ области Типы проектов выберите CLR. При этом будет создан проект, предназначенный для среды CLR.

    3. В области Шаблоны выберите Консольное приложение CLR.

    4. Выберите имя проекта, например MyExecRefsAssembly, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и библиотека классов.

    5. Нажмите ОК, чтобы создать проект.

    6. По умолчанию создаваемые проекты настраиваются на использование предкомпилированных заголовков. Чтобы отключить предкомпилированные заголовки, в меню Проект выберите Свойства. Последовательно разверните узлы Свойства конфигурации, C/C++, а затем выберите пункт Предварительно скомпилированные заголовки. В раскрывающемся списке рядом с полем Создавать или использовать предварительно скомпилированный заголовочный файл выберите пункт Не использовать предкомпилированный заголовок. Нажмите кнопку ОК для сохранения этих изменений. Дополнительные сведения о предкомпилированных заголовках см. в разделе Создание файлов предкомпилированных заголовков.

    Использование функциональных возможностей библиотеки классов в консольном приложении

    1. По завершении создания консольного приложения CLR будет создана программа, выводящая на консоль приветствие "Hello World". Имя исходного файла будет совпадать с именем, выбранным ранее для проекта. В этом примере он имеет имя MyExecRefsAssembly.cpp.

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

    3. Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке .NET перечислены библиотеки, включенные в .NET Framework. На вкладке COM перечислены все COM-компоненты, установленные на компьютере. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsAssembly, после чего нажмите кнопку ОК.

    4. Теперь класс MyMathFuncs можно использовать в приложении. Замените содержимое функции в файле MyExecRefsAssembly.cpp следующим кодом:

    // MyExecRefsAssembly.cpp

    // compile with: /clr /FUMathFuncsAssembly.dll

    using namespace System;

    int main(array<System::String ^> ^args)

    {

    double a = 7.4;

    int b = 99;

    Console::WriteLine("a + b = {0}",

    MathFuncs::MyMathFuncs::Add(a, b));

    Console::WriteLine("a - b = {0}",

    MathFuncs::MyMathFuncs::Subtract(a, b));

    Console::WriteLine("a * b = {0}",

    MathFuncs::MyMathFuncs::Multiply(a, b));

    Console::WriteLine("a / b = {0}",

    MathFuncs::MyMathFuncs::Divide(a, b));

    return 0;

    }

    1. Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.

    Запуск приложения

    1. Убедитесь, что проект MyExecRefsAssembly выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsAssembly и затем в меню Проект выберите команду Назначить запускаемым проектом.

    2. Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат должен выглядеть следующим образом:

    a + b = 106.4

    a - b = -91.6

    a * b = 732.6

    a / b = 0.0747474747474748

    Создание и использование DLL (Microsoft Visual C++).

    Создание dll

    Ничего особенного здесь нет. Как обычно, вы просто пишите функции, как в обычной программе. Если вы используете MSVC, создайте новый проект и укажите, что вы создаете Win32 Dynamic-Link Library, в опциях указать Пустой проект(Empty project). После компиляции вы получите DLL, библиотеку импорта (.lib) и библиотеку экспорта (.exp). Далее показан примерный код вашей DLL:

    Заголовочный файл (DLLTEST.H)

    #ifndef _DLLTEST_H_

    #define _DLLTEST_H_

    #include <iostream>

    #include <stdio.h>

    #include <windows.h>

    using namespace std;

    extern "C" __declspec(dllexport) void NumberList();

    extern "C" __declspec(dllexport) void LetterList();

    #endif

    Код библиотеки (DLLTEST.CPP)

    #include "dlltest.h"

    #define MAXMODULE 255

    char module[MAXMODULE];

    extern "C" __declspec(dllexport) void NumberList()

    {

    GetModuleFileNameA(NULL, module, MAXMODULE);

    cout << "\n\nThis function was called from \n"

    << module

    << endl << endl;

    cout << "NumberList(): ";

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

    {

    cout << i << " ";

    }

    cout << endl << endl;

    }

    extern "C" __declspec(dllexport) void LetterList()

    {

    GetModuleFileNameA(NULL, module, MAXMODULE);

    cout << "\n\nThis function was called from \n"

    << module

    << endl << endl;

    cout << "LetterList(): ";

    for(int i=0; i<26; i++)

    {

    cout << char(97 + i) << " ";

    }

    cout << endl << endl;

    }

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

    extern "C" __declspec(dllexport)

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

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

    Использование dll без библиотеки импорта. Динамическое подключение.

    Чтобы загрузить DLL в программу на этапе выполнения, нужно пройти несколько шагов.

    Шаг 1:

    Использовать функцию LoadLibrary или LoadLibraryEx, чтобы загрузить DLL.

    Шаг 2:

    Использовать функцию GetProcAddress, чтобы получить указатель на интересующую нас функцию.

    Шаг 3:

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

    В своей программе я пользовался функцией LoadLibrary. Эта функция возвращает дескриптор DLL.

    Код:

    HINSTANCE hDllInstance = LoadLibrary( "DLLTEST.dll");

    Первый параметр - путь к DLL.

    Дальше - нам нужно получить указатель на функцию. Тоесть нужно определить тип указателя. Это делается строкой (в нашем случае)

    Код:

    typedef void (WINAPI *cfunc)( );

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

    Далее - дело за малым.

    Код:

    cfunc NumberList;

    cfunc LetterList;

    NumberList=(cfunc)GetProcAddress((HMODULE)hLib, "NumberList");

    LetterList=(cfunc)GetProcAddress((HMODULE)hLib, "LetterList");

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

    Первый параметр - дескриптор DLL. Второй - имя функции.

    Теперь можно вызывать эти функции

    Код:

    NumberList();

    LetterList();

    Исходный файл приложения, использующего библиотеку DLL - консольное приложение Win32 (DLLRUN02.EXE)

    #include <windows.h>

    #include <iostream>

    #include <stdio.h>

    #include <conio.h>

    using namespace std;

    #define MAXMODULE 255

    typedef void (WINAPI *cfunc)();

    cfunc NumberList;

    cfunc LetterList;

    void main()

    {

    HINSTANCE hLib=LoadLibrary(TEXT("DLLTEST.DLL"));

    if(hLib==NULL)

    {

    cout << "Unable to load library!" << endl;

    getch();

    return;

    }

    char mod[MAXMODULE];

    GetModuleFileNameA((HMODULE)hLib, mod, MAXMODULE);

    cout << "Library loaded: " << mod << endl;

    NumberList=(cfunc)GetProcAddress((HMODULE)hLib, "NumberList");

    LetterList=(cfunc)GetProcAddress((HMODULE)hLib, "LetterList");

    if((NumberList==NULL) || (LetterList==NULL))

    {

    cout << "Unable to load function(s)." << endl;

    FreeLibrary((HMODULE)hLib);

    return;

    }

    NumberList();

    LetterList();

    FreeLibrary((HMODULE)hLib);

    getch();

    }

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

    Результаты работы dllrun02.Exe

    Library loaded: C:\DLLTEST\DLLTEST.DLL

    This function was called from C:\DLLTEST\DLLRUN02.EXE

    NumberList(): 0 1 2 3 4 5 6 7 8 9

    This function was called from C:\DLLTEST\DLLRUN02.EXE

    LetterList(): a b c d e f g h i j k l m n o p q r s t u v w x y z

    Лабораторная работа № 22. Создание и использование DLL (Microsoft Visual C++).

    Цель работы:

    Овладеть навыками создания и использования динамически подключаемых библиотек (DLL)

    Постановка задачи

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

    2. Написать функцию с умалчиваемыми параметрами в соответствии с вариантом, продемонстрировать различные способы вызова функции:

    • с параметрами заданными явно,

    • с опущенными параметрами

    • часть параметров задана явно, а часть опущена.

    3. Написать функцию с переменным числом параметров в соответствии с вариантом, продемонстрировать вызов функции с различным числом параметров.

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

    5. Написать шаблон функций вместо перегруженных функций из задания 3. Написать демонстрационную программу для вызова этих функций. списка параметров

    6. Решить уравнение указанным в варианте методом. Уравнение передать в функцию как параметр с помощью указателя.

    Варианты

    варианта

    Функция с умалчиваемыми параметрами

    Функция с переменным числом параметров

    Перегруженные функции и шаблон функции

    Передача функции как параметра другой функции с помощью указателя

    1

    Печать фамилии, имени и отчества

    Минимальный элемент в списке параметров

    Среднее арифметическое массива

    Метод итераций

    Отрезок, содержащий корень: [2;3]

    Точное значение: 2,2985

    2

    Печать фамилии, имени и возраста

    Максимальный элемент в списке параметров

    Количество отрицательных элементов в массиве

    Метод Ньютона

    Отрезок, содержащий корень: [2;3]

    Точное значение: 2,2985

    3

    Печать фамилии, курса и группы

    Количество четных элементов в списке параметров

    Максимальный элемент в массиве

    Метод половинного деления

    Отрезок, содержащий корень: [2;3]

    Точное значение: 2,2985

    4

    Печать фамилии, имени и рейтинга

    Среднее арифметическое элементов в списке параметров

    Минимальный элемент в массиве

    Метод итераций

    0,25x3 + x - 1,2502 = 0

    Отрезок, содержащий корень: [0;2]

    Точное значение: 1,0001

    5

    Печать фамилии, курса и рейтинга

    Максимальный из элементов в списке параметров, стоящих на четных местах

    Сортировка массива методом простого обмена

    Метод Ньютона

    0,25x3 + x - 1,2502 = 0

    Отрезок, содержащий корень: [0;2]

    Точное значение: 1,0001

    6

    Печать фамилии, адреса и возраста

    Максимальный из элементов в списке параметров, стоящих на нечетных местах

    Сортировка массива методом простого выбора

    Метод половинного деления

    0,25x3 + x - 1,2502 = 0

    Отрезок, содержащий корень: [0;2]

    Точное значение: 1,0001

    7

    Печать названия экзамена, количества сдающих и среднего балла

    Минимальный из элементов в списке параметров, стоящих на четных местах

    Сортировка массива методом простого включения

    Метод итераций

    Отрезок, содержащий корень: [0;0,85]

    Точное значение: 0,2624

    8

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

    Минимальный из элементов в списке параметров, стоящих на нечетных местах

    Поиск заданного элемента в массиве

    Метод Ньютона

    Отрезок, содержащий корень: [0;0,85]

    Точное значение: 0,2624

    9

    Печать координат точки

    Среднее арифметическое из элементов в списке параметров, стоящих на четных местах

    Поиск заданного элемента в отсортированном массиве

    Метод половинного деления

    Отрезок, содержащий корень: [0;0,85]

    Точное значение: 0,2624

    10

    Вычисление и печать расстояния от точки с координатами x1,y1 до центра координат

    Среднее арифметическое из элементов в списке параметров, стоящих на нечетных местах

    Удаление элемента с заданным номером из динамического массива

    Метод итераций

    0,1x2 - x ln x = 0

    Отрезок, содержащий корень: [1;2]

    Точное значение: 1,1183

    11

    Вычисление и печать расстояния от точки с координатами x1,y1 до точки с координатами x2,y2

    Минимальный элемент в списке параметров

    Удаление элемента с заданным ключом из динамического массива

    Метод Ньютона

    0,1x2 - x ln x = 0

    Отрезок, содержащий корень: [1;2]

    Точное значение: 1,1183

    12

    Печать фамилии, имени и отчества

    Максимальный элемент в списке параметров

    Добавление элемента с заданным номером в динамический массив

    Метод половинного деления

    0,1x2 - x ln x = 0

    Отрезок, содержащий корень: [1;2]

    Точное значение: 1,1183

    13

    Печать фамилии, имени и возраста

    Количество четных элементов в списке параметров

    Добавление элемента после элемента с заданным номером в динамический массив

    Метод итераций

    3x - 4lnx - 5 = 0

    Отрезок, содержащий корень: [2;4]

    Точное значение: 3,2300

    14

    Печать фамилии, курса и группы

    Среднее арифметическое элементов в списке параметров

    Номер максимального элемента в массиве

    Метод Ньютона

    3x - 4lnx - 5 = 0

    Отрезок, содержащий корень: [2;4]

    Точное значение: 3,2300

    15

    Печать фамилии, имени и рейтинга

    Максимальный из элементов в списке параметров, стоящих на четных местах

    Среднее арифметическое массива

    Метод половинного деления

    3x - 4lnx - 5 = 0

    Отрезок, содержащий корень: [2;4]

    Точное значение: 3,2300

    16

    Печать фамилии, курса и рейтинга

    Максимальный из элементов в списке параметров, стоящих на нечетных местах

    Количество отрицательных элементов в массиве

    Метод итераций

    Отрезок, содержащий корень: [0;1]

    Точное значение: 0,5629

    17

    Печать фамилии, адреса и возраста

    Минимальный из элементов в списке параметров, стоящих на четных местах

    Добавление элемента с заданным номером в динамический массив

    Метод Ньютона

    Отрезок, содержащий корень: [0;1]

    Точное значение: 0,5629

    18

    Печать названия экзамена, количества сдающих и среднего балла

    Минимальный из элементов в списке параметров, стоящих на нечетных местах

    Сортировка массива методом простого обмена

    Метод половинного деления

    Отрезок, содержащий корень: [0;1]

    Точное значение: 0,5629

    Методические указания

    1. В функции с умалчиваемыми параметрами использовать структурированный тип данных.

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

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

    4. Перегрузить функции для массивов типа char, int, и double.

    5. Инстанцировать шаблон функции для типов char, int, и double.

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

    7. Точность нахождения корня уравнения выбирается не менее 0.001.

    8. Полученный результат вычисления корня сравнить с точным значением, заданным в задании.

    Содержание отчета

    1. Постановка задачи (общая и для конкретного варианта).

    2. Определения функций для реализации поставленных задач.

    3. Определение функции main().

    4. Тесты

    1. Регулярные выражения в С++

    Краткая теория

    Стандартный класс string позволяет выполнять над строками различные операции, в том числе поиск, замену, вставку и удаление подстрок. Тем не менее, есть классы задач по обработке символьной информации, где стандартных возможностей явно не хватает. Чтобы облегчить решение подобных задач, в Net Framework встроен более мощный аппарат работы со строками, основанный на регулярных выражениях.

    Регулярные выражения предназначены для обработки текстовой информации и обеспечивают:

    1. Эффективный поиск в тексте по заданному шаблону;

    2. Редактирование текста;

    3. Формирование итоговых отчетов по результатам работы с текстом.

    Метасимволы в регулярных выражениях

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

    Рассмотрим наиболее употребительные метасимволы:

    Класс символов

    Описание

    Пример

    .

    Любой символ, кроме \n.

    Выражение c.t соответствует фрагментам:

    cat, cut, c#t, c{t и т.д.

    []

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

    Выражение c[aui]t соответствует фрагментам: cat, cut, cit.

    Выражение c[a-c]t соответствует фрагментам: cat, cbt, cct.

    [^]

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

    Выражение c[^aui]t соответствует фрагментам: cbt, cct, c2t и т.д.

    Выражение c[^a-c]t соответствует фрагментам: cdt, cet, c%t и т.д.

    \w

    Любой алфавитно-цифровой символ.

    Выражение c\wt соответствует фрагментам:

    cbt, cct, c2t и т.д., но не соответствует фрагментам c%t, c{t и т.д.

    \W

    Любой не алфавитно-цифровой символ.

    Выражение c\Wt соответствует фрагментам:

    c%t, c{t, c.t и т.д., но не соответствует фрагментам cbt, cct, c2t и т.д.

    \s

    Любой пробельный символ.

    Выражение \s\w\w\w\s соответствует любому слову из трех букв, окруженному пробельными символами.

    \S

    Любой не пробельный символ.

    Выражение \s\S\S\S\s соответствует любым трем непробельным символам, окруженным пробельными.

    \d

    Любая десятичная цифра

    Выражение c\dt соответствует фрагментам: c1t, c2t, c3t и т.д.

    \D

    Любой символ, не являющийся десятичной цифрой

    Выражение c\Dt не соответствует фрагментам: c1t, c2t, c3t и т.д.

    Кроме метасимволов, обозначающие классы символов, могут применяться уточняющие метасимволы:

    Уточняющие символы

    Описание

    ^

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

    $

    Фрагмент, совпадающий с регулярными выражениями, следует искать только в конце строки

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

    \Z

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

    \b

    Фрагмент, совпадающий с регулярными выражениями, начинается или заканчивается на границе слова, т.е. между символами, соответствующими метасимволам \w и \W

    \B

    Фрагмент, совпадающий с регулярными выражениями, не должен встречаться на границе слов

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

    Повторители

    Описание

    Пример

    *

    Ноль или более повторений предыдущего элемента

    Выражение ca*t соответствует фрагментам:

    ct, cat, caat, caaat и т.д.

    +

    Одно или более повторений предыдущего элемента

    Выражение ca+t соответствует фрагментам:

    cat, caat, caaat и т.д.

    ?

    Не более одного повторения предыдущего элемента

    Выражение ca?t соответствует фрагментам:

    ct, cat.

    {n}

    Ровно n повторений предыдущего элемента

    Выражение ca{3}t соответствует фрагменту:

    cаааt.

    Выражение (cat){2} соответствует фрагменту:

    cаtcat.

    {n,}

    По крайней мере n повторений предыдущего элемента

    Выражение ca{3,}t соответствует фрагментам: cаааt, caaaat, caaaaaaat и т.д.

    Выражение (cat){2,} соответствует фрагментам: cаtcat, catcatcat и т.д.

    {n, m}

    От n до m повторений предыдущего элемента

    Выражение ca{2, 4}t соответствует фрагментам: cааt, caaat, caaaat.

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

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

    Примеры регулярных выражений:

    1. слово rus – @"rus" или "rus"

    2. номер телефона в формате xxx-xx-xx – @"\d\d\d-\d\d-\d\d" или @"\d{3}(-\d\d){2}"

    3. номер автомобиля - @"[A-Z]\d{3}[A-Z]{2}\d{2,3}RUS"

    Задания. Запишите регулярное выражение, соответствующее:

    1. дате в формате дд.мм.гг или дд.мм.гггг

    2. времени в формате чч.мм или чч:мм

    3. целому числу (со знаком и без)

    4. вещественному числу (со знаком и без, с дробной частью и без, с целой частью и без)

    Поиск в тексте по шаблону

    Пространство имен библиотеки базовых классов System.Text.RegularExpressions содержит все объекты платформы .NET Framework, имеющие отношение к регулярным выражениям. Важнейшим классом, поддерживающим регулярные выражения, является класс Regex, который представляет неизменяемые откомпилированные регулярные выражения. Для описания регулярного выражения в классе определено несколько перегруженных конструкторов:

    1. Regex() – создает пустое выражение;

    2. Regex(String) – создает заданное выражение;

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

    Поиск фрагментов строки, соответствующих заданному выражению, выполняется с помощью методов IsMach, Mach, Matches класса Regex.

    Метод IsMach возвращает true, если фрагмент, соответствующий выражению, в заданной строке найден, и false в противном случае. Например, попытаемся определить, встречается ли в заданном тексте слово собака:

    #include "stdafx.h"

    // regex_replace.cpp

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    int main()

    {

    Regex^ r = gcnew Regex("собака",RegexOptions::IgnoreCase);

    String^ text1 = "Кот в доме, собака в конуре.";

    String^ text2 = "Котик в доме, собачка в конуре.";

    Console::WriteLine("{0}",r->IsMatch(text1));

    Console::WriteLine(r->IsMatch(text2));

    system("Pause");

    return 0;

    }

    Замечание. RegexOptions::IgnoreCase – означает, что регулярное выражение применяется без учеба регистра символов

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

    #include "stdafx.h"

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    int main()

    {

    Regex^ r = gcnew Regex("собака|кот",RegexOptions::IgnoreCase);

    String^ text1 = "Кот в доме, собака в конуре.";

    String^ text2 = "Котик в доме, собачка в конуре.";

    Console::WriteLine(r->IsMatch(text1));

    Console::WriteLine(r->IsMatch(text2));

    system("Pause");

    return 0;

    }

    Попытаемся определить, есть ли в заданных строках номера телефона в формате xx-xx-xx или xxx-xx-xx:

    #include "stdafx.h"

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    int main()

    {

    Regex^ r = gcnew Regex("\\d{2,3}(-\\d\\d){2}");

    String^ text1 = "tel:123-45-67";

    String^ text2 = "tel:no";

    String^ text3 = "tel:12-34-56";

    Console::WriteLine(r->IsMatch(text1));

    Console::WriteLine(r->IsMatch(text2));

    Console::WriteLine(r->IsMatch(text3));

    system("Pause");

    return 0;

    }

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

    Метод Match класса Regex не просто определяет, содержится ли текст, соответствующий шаблону, а возвращает объект класса Match – последовательность фрагментов текста, совпавших с шаблоном. Следующий пример позволяет найти все номера телефонов в указанном фрагменте текста:

    #include "stdafx.h"

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    int main()

    {

    Regex^ r = gcnew Regex("\\d{2,3}(-\\d\\d){2}");

    String^ text = "Контакты в Москве tel:123-45-67, 123-34-56; fax:123-56-45 " +

    "Контакты в Саратове tel:12-34-56; fax:12-56-45";

    Match^ tel = r->Match(text);

    while (tel->Success) {

    Console::WriteLine(tel);

    tel = tel->NextMatch();

    }

    system("Pause");

    return 0;

    }

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

    #include "stdafx.h"

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    int main()

    {

    Regex^ r = gcnew Regex("[-+]?\\d+");

    String^ text = "5*10=50 -80/40=-2";

    Match^ teg = r->Match(text);

    int sum = 0;

    while (teg->Success) {

    Console::WriteLine(teg);

    sum += int::Parse(teg->ToString());

    teg = teg->NextMatch();

    }

    Console::WriteLine("sum=" + sum);

    system("Pause");

    return 0;

    }

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

    Метод Matches класса Regex возвращает объект класса MatchCollection – коллекцию всех фрагментов заданной строки, совпавших с шаблоном. При этом метод Matches многократно запускает метод Match, каждый раз начиная поиск с того места, на котором закончился предыдущий поиск.

    #include "stdafx.h"

    // compile with: /clr

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    void main()

    {

    String^ text = "5*10=50 -80/40=-2";

    Regex^ theReg = gcnew Regex("[-+]?\\d+");

    MatchCollection^ theMatches = theReg->Matches(text);

    for each (Match^ theMatch in theMatches)

    {

    Console::Write("{0} ", theMatch->ToString());

    Console::WriteLine();

    }

    system("Pause");

    }

    Редактирование текста

    Регулярные выражения могут эффективно использоваться для редактирования текста. Например, метод Replace класса Regex позволяет выполнять замену одного фрагмента текста другим или удаление фрагментов текста:

    Пример 1. Изменение номеров телефонов:

    // compile with: /clr

    #include "stdafx.h"

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    void main()

    {

    String^ text = "Контакты в Москве tel:123-45-67, 123-34-56; fax:123-56-45. " +

    "Контакты в Саратове tel:12-34-56; fax:11-56-45";

    Console::WriteLine("Старые данные\n"+text);

    String^ newText=Regex::Replace(text, "123-", "890-");

    Console::WriteLine("Новые данные\n" + newText);

    system("Pause");

    }

    Задание. Измените программу так, чтобы шестизначные номера заменялись на семизначные добавлением 0 после первых двух цифр. Например, номер 12-34-56 заменился бы на 120-34-56.

    Пример 2. Удаление всех номеров телефонов из текста:

    // compile with: /clr

    #include "stdafx.h"

    #using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    void main()

    {

    String^ text = "Контакты в Москве tel:123-45-67, 123-34-56; fax:123-56-45. " +

    "Контакты в Саратове tel:12-34-56; fax:12-56-45";

    Console::WriteLine("Старые данные\n"+text);

    String^ newText=Regex::Replace(text, "\\d{2,3}(-\\d\\d){2}", "");

    Console::WriteLine("Новые данные\n" + newText);

    system("Pause");

    }

    Задание. Измените программу так, чтобы из текста удалялись слова tel и fax (если после данных слов стоят двоеточия, то их тоже следует удалить).

    Пример 3. Разбиение исходного текста на фрагменты:

    // compile with: /clr

    #include "stdafx.h"

    //#using <System.dll>

    using namespace System::Text::RegularExpressions;

    using namespace System;

    #include <iostream>

    void main()

    {

    String^ text = "Контакты в Москве tel:123-45-67, 123-34-56; fax:123-56-45. " +

    "Контакты в Саратове tel:12-34-56; fax:12-56-45";

    Regex::Split(text,"[ ,.:;]+");

    array<String^>^ newText = Regex::Split(text,"[ ,.:;]+");

    for each( String^ a in newText)

    Console::WriteLine(a);

    system("Pause");

    }

    Задание. Разместите текст на одной строке и посмотрите, как изменится вывод данных. Объясните результаты.

    Лабораторная работа № 23. Регулярные выражения в С++ в платформе .NET Framework

    Цель работы:

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

    Задание

    Дана строка, в которой содержится осмысленное текстовое сообщение. Слова сообщения разделяются пробелами и знаками препинания.

    1. Определите, содержится ли в сообщении заданное слово.

    2. Выведите все слова заданной длины.

    3. Выведите на экран все слова сообщения, записанные с заглавной буквы.

    4. Удалите из сообщения все однобуквенные слова.

    5. Удалите из сообщения все знаки препинания.

    6. Удалите из сообщения все русские слова.

    7. Удалите из сообщения только те русские слова, которые начинаются на гласную букву.

    8. Заменить все английские слова на многоточие.

    9. Найти максимальное целое число, встречающееся в сообщении.

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

    11. В сообщении могут встречаться номера телефонов, записанные в формате xx-xx-xx, xxx-xxx или xxx-xx-xx. Вывести все номера телефонов, которые содержатся в сообщении.

    12. В сообщении может содержаться дата в формате дд.мм.гггг. В заданном формате дд – целое число из диапазона от 1 до 31, мм – целое число из диапазона от 1 до 12, а гггг – целое число из диапазона от 1900 до 2010 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Выведите на экран все даты, которые относятся к текущему году.

    13. В сообщении могут содержаться IP-адреса компьютеров в формате d.d.d.d, где d – целое число из диапазона от 0 до 255. Вывести все IP-адреса содержащиеся в тексте.

    14. В сообщении могут содержаться IP-адреса компьютеров в формате d.d.d.d, где d – целое число из диапазона от 0 до 255. Удалить из сообщения IP-адреса, в которых последнее число d начинается с заданной цифры (данная цифра вводится с клавиатуры).

    15. Выведите на экран все адреса web-сайтов, содержащиеся в сообщении.

    16. В сообщении может содержаться дата в формате дд.мм.гггг. В заданном формате дд – целое число из диапазона от 1 до 31, мм – целое число из диапазона от 1 до 12, а гггг – целое число из диапазона от 1900 до 2010 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Замените каждую дату сообщения на дату следующего дня.

    17. В сообщении может содержаться дата в формате дд.мм.гггг. В заданном формате дд – целое число из диапазона от 1 до 31, мм – целое число из диапазона от 1 до 12, а гггг – целое число из диапазона от 1900 до 2010 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Замените каждую дату в сообщении на дату предыдущего дня.

    18. В сообщении может содержаться время в формате чч:мм:сс. В заданном формате чч – целое число из диапазона от 00 до 24, мм и сс – целые числа из диапазона от 00 до 60 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Преобразуйте каждое время к формату чч:мм, применив правило округления до целого числа минут.

    19. В сообщении может содержаться время в формате чч:мм. В заданном формате чч – целое число из диапазона от 00 до 24, мм – целое число из диапазона от 00 до 60 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Увеличите время на n минут.

    20. В сообщении может содержаться время в формате чч:мм. В заданном формате чч – целое число из диапазона от 00 до 24, мм – целое число из диапазона от 00 до 60 (если какая-то часть формата нарушена, то данная подстрока в качестве даты не рассматривается). Уменьшите время на n часов.

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Листинг программы.

    6. Результаты выполнения программы.

    1. Создание Windows-приложений на платформе Microsoft .Net C++

    Краткая теория Философия .Net Framework

    CLR (Common Language Runtime) – общеязыковая среда выполнения.

    CLR — это место, в .NET Framework, где выполняется, сгенерированный компилятором, CIL код. Задачи CLR: управление памятью, загрузка сборок, безопасность, обработка исключений, синхронизация и т.д.

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

    Когда мы пишем на С++, компилятор генерирует специальный CIL код, который понимает и выполняет CLR. Так как CLR не полиглот, то для каждого языка, поддерживаемого в .NET, есть свой компилятор, который занимается проверкой написанного и генерированием CIL кода. Именно эта особенность и дает нам возможность писать на разных языках под одну и ту же платформу.

    Что такое CIL код? CIL (Common Intermediate Language) – промежуточный язык, для платформы .NET. Но между чем и чем он промежуточный?

    IL является промежуточным между языком высокого уровня (C++) и машинными командами. CLR во время выполнения IL кода генерирует машинные команды, которые выполняются процессором.

    Но почему мы не можем выбрать один язык и разрабатывать только на нем?

    С одной стороны, у нас есть, «неуправляемый код» C++, который дает доступ к системе на низком уровне (распоряжение памятью, создание программных потоков и т.д.) С другой стороны, Visual Basic позволяет очень быстро строить пользовательские интерфейсы и легко управлять COM-объектами и базами данных.

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

    Выводы:

    • .NET Framework предоставляет возможность выбора разных языков;

    • CLR – ключевой механизм, в платформе .NET;

    • CIL – язык, который генерируют все компиляторы всех языков .NET для CLR;

    • Нет универсального инструмента для решения любой задачи.

    Язык С++. Основные конструкции

    В платформу .Net входят много языков, которые без особого труда интегрируются друг с другом. Мы будем рассматривать язык C++.

    Библиотека Windows Forms

    Все рассмотренное выше является основой всех приложений .Net и удобно для создания консольных приложений, однако для создания серьезных приложений лучше подходит Microsoft Visual Studio 2005 и выше.

    WindowsForms – подмножество типов библиотеки FCL для создания пользовательского интерфейса приложений. Все типы WindowsForms находятся в пространстве имен System::Windows::Forms.

    Внешний вид Windows-приложения на стадии разработки в Microsoft Visual Studio 2008 представлен на рисунке.

    Принцип визуального проектирования приложения в Visual Studio 2008 тот же, что и в Delphi. Visual Studio 2008 позволяет создавать как MDI, так и SDI-приложения.

    Класс Form, MessageBox и компоненты Класс Form

    Основной класс – это класс Form. В .Net 2 появилась возможность разбивать класс по нескольким файлам. VS2008 разбивает описание класса формы хранит в файле и называется <имя формы>.h. В нем располагается пользовательский код по обработке разных событий формы, а также код формы, сгенерированной самой Visual Studio. Механизм разбиения классов можно применять и вручную.

    class PartCl

    {

    int a;

    int b;

    public:

    PartCl(int p1, int p2)

    {

    a=p1;

    b=p2;

    }

    };

    Пример 4. Разбиение классов

    Каждую из частей можно расположить в отдельном файле.

    Свойства форм и обработчики событий формы отображаются в окне Properties.

    Для создания в проекте новой формы необходимо открыть окно Solution Explorer (по умолчанию оно видно), в котором отображается структура всего проекта, и в нем в контекстном меню проекта выбрать пункты Add->New Item…->Visual C++ ->Windows Form. После это надо выбрать нужный тип формы, ввести ее имя и нажать OK. Если окно Solution Explorer закрыто, его можно вызвать при помощи пунктов главного меню View->Solution Explorer.

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

    • Load;

    • GotFocus;

    • Activated;

    • Closing;

    • Closed;

    • Deactivate;

    • LostFocus;

    • Dispose.

    Отображение главной формы происходит при запуске программы. Остальные формы можно вызвать при помощи двух методов класса Form: Show и ShowModal. Метод Show отображает обычную форму, ShowModal отображает модальную форму.

    Диалог MessageBox

    Для вывода каких-либо сообщений можно использовать метод Show класса MessageBox из пространства имен System.Windows.Forms.

    MessageBox.Show("This is a test", "Title", MessageBoxButtons::OK);

    Используя этот класс, можно организовать простую интерактивность с пользователем.

    if(MessageBox::Show("Press Yes or No?", "Title",

    MessageBoxButtons::YesNo) == Windows::Forms::DialogResult::Yes) {};

    Компоненты и панель ToolBox

    Компоненты панели управления для Windows-приложений представлены ниже на рисунке. Имея опыт программирования в визуальных средах проектирования, можно из названий догадаться о предназначении того или иного компонента.

    Изменение содержимого панели ToolBox осуществляется через диалоговое окно Choose ToolBox Items. Данное окно можно вызвать через пункт контекстного меню Choose Itemsпанели ToolBox.

    Работа с элементами управления

    При добавлении элемента управления из Toolbox на форму в класс формы добавляются соответствующие поля данных.

    private: System::Windows::Forms::Button^ button1;

    protected:

    private:

    /// <summary>

    /// Required designer variable.

    /// </summary>

    System::ComponentModel::Container ^components;

    Если же мы какие-либо элементы впишем в код собственноручно, то они будут отображены на форме в дизайнере. Свойства полей данных формы могут быть считаны или изменены в процессе выполнения программы. Все свойства или установки обработчиков событий, сделанные программистом в дизайнере формы или в окне Properties, записываются в код метода private void InitializeComponent().

    Все обработчики событий формы являются ее методами. Любому обработчику событий передаются два параметра: объект, вызвавший событие и параметры этого события.

    private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)

    Параметры события являются экземпляром класса EventArgs или его потомком.

    Для удобного размещения элементов управления на форме можно воспользоваться пунктами главного меню Format.

    Для динамического изменения размеров двух соседних элементов управления имеется элемент Splitter.

    Меню

    Для добавления меню необходимо перетащит на форму элемент управления MenuStrip. Далее в меню в дизайнере формы добавляются различные пункты. Если необходимо добавить горизонтальную полосу разделения в качестве текста пункта меню необходимо указать символ «-».

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

    private void InitializeComponent().

    private System.Windows.Forms.MenuStrip menuStrip1;

    private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1;

    private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem2;

    private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;

    private System.Windows.Forms.ToolStripComboBox toolStripComboBox1;

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

    private void toolStripMenuItem2_Click(object sender, EventArgs e)

    Лабораторная работа № 24. Создание Windows-приложений на платформе Microsoft .Net C++

    Цель работы:

    Научиться основам создания оконных приложений.

    Задания

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

    Вариант 1

    Создать меню с командами Input, Calc и Exit. При выборе команды Input открывается диалоговое окно, содержащее: три поля типа TextBox для ввода длин трех сторон треугольника; группу из двух флажков (Периметр и Площадь) типа CheckBox; кнопку типа Button. Обеспечить возможность: ввода длин трех сторон треугольника; выбора режима с помощью флажков: подсчет периметра и/или площади треугольника. При выборе команды Calc открывается диалоговое окно с результатами. При выборе команды Exit приложение завершается.

    Вариант 2

    Создать меню с командами Size, Color, Paint, Quit. Команда Paint недоступна. При выборе команды Quit приложение завершается. При выборе команды Size открывается диалоговое окно, содержащее:

    • два поля типа TextBox для ввода длин сторон прямоугольника;

    • группу из трех флажков (Red, Green, Blue) типа CheckBox;

    • кнопку типа Button.

    Обеспечить возможность:

    • ввода длин сторон прямоугольника в пикселах в поля ввода;

    • выбора его цвета с помощью флажков.

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

    Вариант 3

    Создать меню с командами Input, Work, Exit.

    При выборе команды Exit приложение завершает работу. При выборе команды Input открывается диалоговое окно, содержащее:

    • три поля ввода типа TextBox с метками Radius, Height, Density;

    • группу из двух флажков (Volume, Mass) типа CheckBox;

    • кнопку типа Button.

    Обеспечить возможность:

    • ввода радиуса, высоты и плотности конуса;

    • выбора режима с помощью флажков: подсчет объема и/или массы конуса.

    При выборе команды Work открывается окно сообщений с результатами.

    Вариант 4

    Создать меню с командами Input, Calc, Draw, Exit.

    При выборе команды Exit приложение завершает работу. При выборе команды Input открывается диалоговое окно, содержащее:

    • поле ввода типа TextBox с меткой Radius;

    • группу из двух флажков (Square, Length) типа CheckBox;

    • кнопку типа Button.

    Обеспечить возможность:

    • ввода радиуса окружности;

    • выбора режима с помощью флажков: подсчет площади круга (Square) и/или длины окружности (Length).

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

    Вариант 5

    Создать меню с командами input, Calc, About.

    При выборе команды About открывается окно с информацией о разработчике. При выборе команды Input открывается диалоговое окно, содержащее:

    • три поля ввода типа TextBox с метками Number 1, Number 2, Number 3;

    • группу из двух флажков (Summ, Least multiple) типа CheckBox;

    • кнопку типа Button.

    Обеспечить возможность ввода трех чисел и выбора режима вычислений с помо­щью флажков: подсчет суммы трех чисел (Summ) и/или наименьшего общего кратного двух первых чисел (Least multiple). При выборе команды Calc открыва­ется диалоговое окно с результатами.

    Вариант 6

    Создать меню с командами Input, Calc, Quit.

    Команда Calc недоступна. При выборе команды Quit приложение завершается. При выборе команды Input открывается диалоговое окно, содержащее:

    • два поля ввода типа TextBox с метками Number 1, Number 2;

    • группу из трех флажков (Summa, Max divisor, Multiply) типа CheckBox;

    • кнопку типа Button.

    Обеспечить возможность:

    • ввода двух чисел;

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

    При выборе команды Calc открывается окно сообщений с результатами.

    Вариант 7

    Создать меню с командами Begin, Help, About.

    При выборе команды About открывается окно с информацией о разработчике. При выборе команды Begin открывается диалоговое окно, содержащее:

    • поле ввода типа TextBox с меткой input;

    • метку типа Label для вывода результата;

    • группу из трех переключателей (2, 8, 16) типа RadioButton;

    • две кнопки типа Button — Do и ОК.

    Обеспечить возможность:

    • ввода числа в десятичной системе в поле input;

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

    При щелчке на кнопке Do должен появляться результат перевода.

    Вариант 8

    Создать меню с командами Input color, Change, Exit, Help.

    При выборе команды Exit приложение завершает работу. При выборе команды Input color открывается диалоговое окно, содержащее:

    • три поля ввода типа TextBox с метками Red, Green, Blue;

    • группу из двух флажков (Left, Right) типа CheckBox;

    • кнопку типа Button.

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

    Вариант 9

    Создать меню с командами Input size, Choose, Change, Exit.

    При выборе команды Exit приложение завершает работу. Команда Change недос­тупна. При выборе команды Input size открывается диалоговое окно, содержащее:

    • два поля ввода типа TextBox с метками Size х, Size у;

    • кнопку типа Button.

    При выборе команды Choose открывается диалоговое окно, содержащее:

    • группу из двух переключателей (Increase, Decrease) типа RadloButton;

    • кнопку типа Button.

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

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

    Вариант 10

    Создать меню с командами Begin, Work, About.

    При выборе команды About открывается окно с информацией о разработчике. При выборе команды Begin открывается диалоговое окно, содержащее:

    • поле ввода типа TextBox с меткой Input word;

    • группу из двух переключателей (Upper case, Lower case) типа RadioButton;

    • кнопку типа Button.

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

    Вариант 11

    Создать меню с командами Input color, Change, Clear.

    При выборе команды Input color открывается диалоговое окно, содержащее:

    • группу из двух флажков (Up, Down) типа CheckBox;

    • группу из трех переключателей (Red, Green, Blue) типа RadioButton;

    • кнопку типа Button.

    Обеспечить возможность:

    • выбора цвета с помощью переключателей;

    • ввода режима, определяющего, какая область закрашивается: все окно, его верхняя или нижняя половина.

    При выборе команды Change цвет главного окна изменяется на заданный (верх­няя, нижняя или обе половины в зависимости от введенного режима). При вы­боре команды Clear восстанавливается первоначальный цвет окна.

    Вариант 12

    Создать меню с командами Translate, Help, About, Exit.

    При выборе команды Exit приложение завершает работу. При выборе команды Translate открывается диалоговое окно, содержащее:

    • поле ввода типа TextBox с меткой Binary number;

    • поле ввода типа TextBox для вывода результата (read-only);

    • группу из трех переключателей (8, 10, 16) типа RadioButton;

    • кнопку Do типа Button.

    Обеспечить возможность:

    • ввода числа в двоичной системе в поле Binary number;

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

    При щелчке на кнопке Do должен появляться результат перевода.

    Вариант 13

    Создать меню с командами Reverse, About, Exit.

    При выборе команды About открывается окно с информацией о разработчике. При выборе команды Reverse открывается диалоговое окно, содержащее:

    • поле ввода типа TextBox с меткой Input;

    • группу из двух переключателей (Upper case, Reverse) типа CheckBox;

    • кнопку ОК типа Button.

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

    Вариант 14

    Создать меню с командами Input, Show и Exit.

    При выборе команды Exit приложение завершает работу. При выборе команды Input открывается диалоговое окно вида:

    Обеспечивается возможность ввода координат двух точек и выбора режима с по­мощью флажков length и koef: подсчет длины отрезка, соединяющего эти точки, и/или углового коэффициента.

    При выборе команды Show открывается окно сообщений с результатами под­счета.

    Вариант 15

    Создать меню с командами Input, About и Exit.

    При выборе команды Exit приложение завершает работу. При выборе команды About открывается окно с информацией о разработчике. При выборе команды Input открывается диалоговое окно вида:

    Обеспечивается возможность ввода суммы в рублях и перевода ее в евро и долла­ры по обычному или льготному курсу. Поля Euro и $ доступны только для чтения.

    Вариант 16

    Создать меню с командами Begin, Work, About.

    При выборе команды About открывается окно с информацией о разработчике. При выборе команды Begin открывается диалоговое окно, содержащее:

    • два поля ввода типа TextBox;

    • группу из двух переключателей (First letter, All letters) типа RadioButton;

    • кнопку типа Button.

    Обеспечить возможность ввода предложения и выбора режима его преобразова­ния: либо начинать с прописной буквы каждое слово (First letter), либо перевести все буквы в верхний регистр (All letters). При выборе команды Work открывается диалоговое окно с результатом преобразования.

    Вариант 17

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

    Создать следующую систему меню:

    Файл

    • Загрузить текст

    • Выход

    Анализ

    • Количество слов

    • Повторяемость буквы

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

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

    Вариант 18

    Создать редактор текстовых файлов с возможностью сохранения текста в фор­мате HTML. Создать следующую систему меню:

    Файл

    • Загрузить текст

    • Сохранить как текст

    • Сохранить как HTML

    Выход

    При выборе файла для загрузки использовать объект OpenFi leDialog. При выборе файла для сохранения использовать объект SaveFileDlalog. Для редактирования текста использовать объект Memo.

    При сохранении текста в формате HTML текст записывать в файл с заменой:

    • всех пробелов на символы  ;

    • всех символов перевода строки на символы <BR>;

    • всех символов < на символы <;

    • всех символов > На символы &gt:;

    • всех символов & на символы &атр;;

    • всех символов " (двойные кавычки) на символы Squot;.

    Вариант 19

    Создать меню с командами Input, Draw, Clear.

    При выборе команды Input открывается диалоговое окно, содержащее:

    • четыре поля для ввода координат двух точек;

    • группу из трех переключателей (Red, Green, Blue) типа RadioButton;

    • кнопку типа Button.

    При выборе команды Draw в главное окно выводится отрезок прямой выбранно­го цвета с координатами концов отрезка, заданными в диалоговом окне. При вы­боре команды Clear отрезок стирается.

    Вариант 20

    Создать меню с командами Input, Change , Exit.

    При выборе команды Exit приложение завершает работу. Команда Change недос­тупна. В центре главного окна выведен квадрат размером 10 0 х 10 0 пикселов. При выборе команды Input открывается диалоговое окно, содержащее:

    • два поля ввода типа TextBox с метками Size х, Size у;

    • группу из двух переключателей (Increase, Decrease) типа RadioButton;

    • кнопку типа Button.

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

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

    Вариант 21

    Написать Windows-приложение, которое по заданным в файле исходным дан­ным выводит информацию о компьютерах.

    Создать меню с командами Choose, Show, Quit.

    Команда Show недоступна. Команда Quit завершает работу приложения.

    При запуске приложения из файла читаются исходные данные. Файл необходи­мо сформировать самостоятельно. Каждая строка файла содержит тип компью­тера, цену (price) и емкость жесткого диска (hard drive) .

    При выборе команды Choose открывается диалоговое окно, содержащее:

    • поле типа TextBox для ввода минимальной емкости диска;

    • поле типа TextBox для ввода максимальной приемлемой цены;

    • группу из двух переключателей (Hard drive, Price) типа RadioButton;

    • OK , Cancel — кнопки типа Button.

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

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

    После чтения данных становится доступной команда Animate.

    При выборе команды Animate в главном окне приложения отображается процесс сортировки в виде столбиковой диаграммы. Каждый элемент представляется столбиком соответствующего размера. На каждом шаге алгоритма два элемента меняются местами. Окно должно содержать заголовок. Изображение должно за­нимать все окно.

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

    После чтения данных становится доступной команда Animate.

    При выборе команды Animate в главном окне приложения отображается процесс сортировки в виде столбиковой диаграммы. Каждый элемент представляется столбиком соответствующего размера. На каждом шаге алгоритма два элемента меняются местами. Окно должно содержать заголовок. Изображение должно за­нимать все окно.

    Содержание отчета

    1. Титульный лист.

    2. Наименование и цель работы.

    3. Краткое теоретическое описание.

    4. Задание на лабораторную работу.

    5. Листинг программы.

    6. Результаты выполнения программы.

    Приложение. Примеры создания Windows-приложений на платформе Microsoft .Net C++

    Пример 1.

    Задание: Вычислить и вывести на экран в виде таблицы значения функции F на интервале от Хнач. до Хкон. с шагом dX.

    где a, b, с — действительные числа.

    Функция F должна принимать действительное значение, если выражение Ац И (Вц ИЛИ Сц) не равно нулю, и целое значение в противном случае. Через Ац, Вц и Сц обозначены целые части значений a, b, с, операции И и ИЛИ — поразрядные. Значения a, b, с, Хнач., Хкон., dX ввести с клавиатуры.

    Отличием программирования с использованием Windows Form, является то, что оно облегчает труд программиста в разработке дружественного интерфейса программы с пользователем, так как весь дизайн формы можно почти полностью реализовать в самой среде разработки, таким образом программисту не придется писать код для интерфейса программы, это все за него сделает среда разработки, ему необходимо только расставить необходимые для работы программы, компоненты (кнопки, поля ввода(TextBox), CheckBox, и т.д.) на форме, так как он сам посчитает, как должно выглядеть его программа.

    1. Создаем проект программы, для этого запускаем среду Visual C++ 2008 и в меню выбираем File | New | Project

    1. Выбираем тип проекта Visual C++ | CLR | Windows Form Application

    1. И в поле Name вводим имя проекта, например «Labи нажимаем ОК.

    Таким образом мы получаем заготовку проекта программы.

    1. Разместим на форме 6 полей ввода(TextBox) для того, чтобы ввести начальные значения a, b, с, Хнач., Хкон., dX и одно поле для вывода результата (TextBox), а также 7 полей надписей (Label) для подписи предназначения полей ввода и вывода результата и одна кнопка (Button) для запуска выполнения расчета и вывода результата в поле ввода. Для этого в меню View выбираем Toolbox, потом в нем нажимаем на для того, чтобы закрепить его на форме, чтобы он не убирался с экрана, после отвода курсора мыши с Toolbox’а, далее раскрываем список Common controls и нажимаем на TextBox и перетаскиваем его на форму, где мы хотим поставить этот элемент, и повторяем это 7 раз.

    Потом нажимаем Label и перетаскиваем его на форму рядом с TextBox слева и повторяем это 7 раз.

    Далее подписываем эти Label следующими текстами: a, b, с, Хнач., Хкон., dX, Результат. Для щелкаем на Label1 и нажимает Alt+Enter и в свойстве Text пишем «a» и нажимаем Enter, и так для всех Label’ов подписываем соответствующими значениями (b, с, Хнач., Хкон., dX, Результат).

    Для TextBox7, где результат устанавливаем также следующие свойства:

    ScrollBars : Vertical

    MultiLine: True

    и изменяем его размер как показано на рисунке

    Далее ставим на форму кнопку (Button) и в его свойствах (Alt+Enter) в поле Text подписываем «Выполнить расчет».

    Далее двойным щелчком мыши на кнопке «Выполнить расчет» создаем обработчик собития на щелчок на этой кнопке и прописываем код программы для решения задачи в этом обработчике, т.е. внутри блока {}:

    private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

    }

    Записываем вне блоков функцию для вычисления факториала F()

    float F(float a, float b, float c, float x){

    if ((b<0) && (c != 0))return a*x*x + c*x;

    else if ((b>0) && (c == 0))return (b - a) / (x - c);

    else return a*(x + c + b);

    }

    и далее заполняем обработчик нажатия кнопки следующим образом:

    private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

    float xn, xk, dx, a, b, c, y;

    int Ac, Bc, Cc;

    a = a.Parse(textBox1->Text);

    b = b.Parse(textBox2->Text);

    c = c.Parse(textBox3->Text);

    xn = xn.Parse(textBox4->Text);

    xk = xk.Parse(textBox5->Text);

    dx = dx.Parse(textBox6->Text);

    double t = 0.0;

    modf(a, &t); //функция modf(double, double*) возвращает в t целую часть a

    Ac = int(t); //приводим t к типу int и заносим в Ac

    modf(b, &t); Bc = int(t);

    modf(c, &t); Cc = int(t);

    textBox7->AppendText("| X | Y |" + Environment::NewLine);

    for (float x = xn; x <= xk; x += dx){

    y = F(a, b, c, x);

    if ((Ac&(Bc | Cc)) == 0)

    {

    modf(y, &t);

    y = float(t);

    }

    textBox7->AppendText("| " + x.ToString() + " | " + y.ToString() + Environment::NewLine);

    }

    }

    };

    Пример 2.

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

    Решение

    1. Создаем проект программы, для этого запускаем среду Visual C++ 2008 и в меню выбираем File | New | Project

    2. Выбираем тип проекта Visual C++ | CLR | Windows Form Application

    3. И в поле Name вводим имя проекта, например «Lab2_2_win» и нажимаем ОК.

    Таким образом мы получаем заготовку проекта программы.

    Создаем форму представленную на рис. 2.2.1.

    Создаем обработчик на нажатие кнопки:

    (правой кнопкой мыши щелкаем на кнопке и выбираем Properties;

    в открывшемся окне нажимаем на следующую кнопку ;

    в открывшемся списке, напротив события Click дважды щелкаем мышкой ) и записываем внутрь обработчика следующий код обработки:

    private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

    int x, y, s = 0, n = 0, pos = 0, pos1 = 0;

    textBox1->Text = textBox1->Text->Trim(); /*Удаляет все начальные и конечные знаки пробела из

    текущего объекта textBox1->Text */

    /*заменяем все двойные пробелы в тексте одинарным пробелом */

    while (textBox1->Text != textBox1->Text->Replace(" ", " "))

    textBox1->Text = textBox1->Text->Replace(" ", " ");

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

    textBox1->Text += " ";

    /*Возвращает индекс первого вхождения указанного знака Юникода в данной строке. Поиск начинается с указанной позиции знака. */

    pos1 = textBox1->Text->IndexOf(" ", pos);

    /*Преобразуем строковое представление числа в эквивалентное ему 32-битовое целое число со знаком. */

    x = x.Parse(textBox1->Text->Substring(pos, pos1 - pos));

    pos = pos1;

    // Пока следующее число не равно нулю

    while (x != 0){

    pos1 = textBox1->Text->IndexOf(" ", pos + 1);

    s += x; //подсчет суммы чисел

    n++; //подсчет количества чисел

    if (pos1 != -1)

    x = x.Parse(textBox1->Text->Substring(pos, pos1 - pos));

    else x = 0;

    pos = pos1;

    }

    s *= n;

    textBox2->Text = s.ToString() + ", Кол-во чисел: " + n.ToString();

    }

    Программа готова, можно запустить ее и проверить.

    1. Работа с базой данных

    Краткая теория

    Обработка баз данных с использованием технологии ADO.NET

    Создание базы данных в среде MS Access

    Вначале создадим базу данных (БД) vic.mdb средствами Access пакета MS Office. Поясню сразу, что реальный положительный эффект при решении какой-либо за­дачи информатизации с использованием баз данных можно ощутить, когда коли­чество записей (то есть количество строк в таблице) превышает 100 тысяч. В этом случае очень важным (решающим) фактором оказывается скорость выборки. Однако для примера работы мы будем оперировать совсем маленькой БД.

    Чтобы вы смогли повторить мои действия, создадим базу данных телефонов ваших контактов. Структура (поля) таблицы будет следующей: Номер п/п, ФИО и Номер телефона. Для этого запускаем на компьютере офисное приложение Microsoft Access, далее в меню Создать выбираем команду Новая база данных (или нажимаем комбинацию клавиш Ctrl+N), задаем папку для размещения БД и имя файла — vic.mdb. Затем в появившемся окне vic: база данных выбираем команду Создание табли­цы в режиме конструктора. Далее задаем три поля (то есть три колонки в будущей таблице): имя первого поля — Номер п/п, тип данных — Счетчик; следующее имя поля — ФИО, тип данных — Текстовый; третье имя поля — Номер телефона, тип дан­ных — Текстовый.

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

    Далее с помощью двойного щелчка в пределах созданной таблицы приступаем к ее заполнению. В нашем примере в таблице всего семь записей (рис. 4).

    Теперь закроем БД Access и откроем созданную нами таблицу БД vic.mdb в среде Visual Studio 2010.

    Редактирование таблицы базы данных ms Access в среде Visual Studio без написания программного кода

    Запускаем Visual Studio 2010, однако заказывать новый проект мы не будем. Сей­час наша цель — открыть созданную нами базу данных в среде Visual Studio. Для этого выбираем пункт меню View ► Server Explorer (также можно воспользоваться комбинацией клавиш Ctrl+Alt+S). Затем, щелкнув правой кнопкой мыши на значке Data Connections, выбираем пункт Add Connection и указываем в качестве источника данных (Data source) Microsoft Access Database File (OLE DB), нажав кнопку Change. Далее с помощью кнопки Browse задаем путь и имя БД, например C:\vic.mdb. Теперь проверяем подключение — кнопка Test Connection. Успешное подключение выдаст сообщение, представленное на рис. 5.

    Проверка подключения выполнена, щелкаем на кнопке OK. Теперь в окне Server Explorer указателем мыши раскрываем узлы, символизирующие базу данных, та­блицы, поля в таблице.

    Рис. 5.

    Тестирование

    подключения

    Далее щелкнем правой кнопкой мыши на узле БД телефо­нов и выбираем команду Retrieve Data. В результате в правом окне получим содержимое этой таблицы, как показано на рис. 6. Здесь данную таблицу мы можем редактировать, то есть изменять содержимое любой записи (Update), добав­лять новые записи (Insert), то есть новые строки в таблицу, удалять записи (Delete). Кроме того, щелкая правой кнопкой мыши в пределах таблицы и выбирая в контекстном меню пункты Pane ► SQL, можно осуществлять SQL-запросы к базе данных, в том числе и наиболее часто используемый запрос Select.

    Рис. 6. Редактирование таблицы базы данных в среде Visual Studio

    Чтение всех записей из таблицы бд ms Access на консоль с помощью объектов классов Command и DataReader

    Напишем программу, которая при минимальном количестве строк программного кода выводит на экран все записи (то есть все строки) таблицы базы данных. При этом воспользуемся наиболее современной технологией ADO.NET. Нам понадо­бятся четыре объекта. Объект Connection обеспечивает соединение с базой данных. Объект Command обеспечивает привязку SQL-выражения к соединению с базой данных. А с помощью объектов DataSet и DataReaders можно просмотреть резуль­таты запроса.

    Мы рассмотрим четыре основных действия над базой данных: Select (выборка записей из таблицы БД), Insert (вставка записей), Update (модификация записей в таблице БД), Delete (удаление некоторых записей из таблицы).

    Запустим Visual Studio 2010 и в окне New Project выберем в среде CLR узла Visual C++ приложение шаблона Console Application CLR. Зададим имя данного про­екта БДDataReader. Нам нужно вывести на экран самым простым способом таблицу. Если мы будем выводить ее при помощи функции MessageBox::Show, то ровных колонок в окне MessageBox::Show мы не получим, поскольку буквы, используемые в этой функции, имеют разную ширину. Обычно в таком случае для вывода таблиц используют шрифт Courier New или Consolas, но объект MessageBox не содержит возможностей смены шрифта. Поэтому мы пойдем по самому короткому пути и выведем таблицу из базы данных на консоль, то есть на черный экран DOS. В этом случае у нас реализуется моноширнный шрифт, в котором все символы имеют одинаковую ширину. Например, буква «Ш» и символ «.» (точка) имеют одинаковую ширину, следовательно, колонки в построенных таблицах будут ров­ными.

    Теперь на вкладке программного кода напишем текст из листинга 2.

    Листинг 2. Чтение всех записей из таблицы БД MS Access и вывод их на консоль

    // БДDataReader1.cpp: главный файл проекта.

    // Программа читает все записи из таблицы БД MS Access и выводит их

    // на консоль с помощью объектов Command и DataReader

    #include "stdafx.h"

    using namespace System;

    // Добавляем эту директиву для краткости выражений:

    using namespace System::Data::OleDb;

    int main(array<System::String ^> ^args)

    {

    // Задаем цвет текста на консоли для большей выразительности:

    Console::ForegroundColor = ConsoleColor::White;

    // Создаем объект класса Connection

    auto Подключение = gcnew OleDbConnection();

    // Передаем ему строку подключения:

    Подключение->ConnectionString = "Data Source=\"C:\\vic.mdb\";User " +

    "ID=Admin;Provider=\"Microsoft.Jet.OLEDB.4.0\";";

    Подключение->Open();

    // Создаем объект класса Command:

    auto Команда = gcnew OleDbCommand();

    Команда->Connection = Подключение;

    // Передаем ему SQL-команду:

    Команда->CommandText = "Select * From [БД телефонов]";

    // Выбрать все записи и сортировать их по колонке "ФИО":

    // Команда->CommandText = "Select * From [БД телефонов] order by ФИО";

    // Аналогично по колонке "Номер п/п":

    // Команда->CommandText =

    // "Select * From [БД телефонов] ORDER BY 'Номер п/п'";

    // Выполняем SQL-команду:

    OleDbDataReader ^ Читатель = Команда->

    ExecuteReader(System::Data::CommandBehavior::CloseConnection);

    Console::WriteLine("Таблица БД:\n");

    while (Читатель->Read() == true)

    // Цикл, пока не будут прочитаны все записи.

    // Читатель->FieldCount - количество полей в строке.

    // Здесь три поля: 0, 1 и 2.

    // Минус прижимает строку влево:

    Console::WriteLine("{0,-3} {1,-15} {2,-15}", Читатель->GetValue(0),

    Читатель->GetValue(1), Читатель->GetValue(2));

    Читатель->Close(); Подключение->Close();

    // Приостановить выполнение программы до нажатия какой-нибудь клавиши:

    Console::ReadKey();

    return 0;

    }

    Как видно из программного кода, вначале мы создаем объект Подключение клас­са Connection и передаем ему строку подключения. В строке подключения полный доступ к mdb-файлу заключен в двойные кавычки. Это сделано для того, чтобы корректно читались длинные имена папок и файлов, содержащие пробелы.

    Далее создаем объект класса Command и передаем ему простейшую SQL- команду:

    Select * From [БД телефонов]

    то есть выбрать все записи из таблицы [БД телефонов]. Название таблицы в SQL- запросе заключено в квадратные скобки из-за пробела в имени таблицы. Заметьте, что в комментарии указаны возможные варианты SQL-запроса: сортировать за­писи по колонке ФИО (ORDER BY ФИО) и по колонке Номер п/п (ORDER BY 'Номер п/п').

    Затем, используя объект класса DataReader, выполняем SQL-команду. Далее в цикле построчно читаем таблицу базы данных. При работе DataReader в памяти хранится только одна строка (запись) данных. Объект класса DataReader имеет Бу­леву функцию Read, которая возвращает true, если существует следующая строка данных, и false, если такие строки (записи) уже исчерпались. Причем с помощью DataReader невозможно заранее узнать количество записей в таблице.

    Результат работы программы показан на рис. 7.

    Рис. 7. Отображение таблицы базы данных на консоли

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

    Убедиться в работоспособности программы можно, открыв соответствующее решение в папке БДDataReader.

    Создание базы данных ms Access в программном коде

    Создадим программу, которая во время своей работы создает базу данных Access, то есть файл new_BD.mdb. Эта база данных будет пустой, то есть она не будет содер­жать ни одной таблицы. Наполнять базу данных таблицами можно впоследствии как из программного кода Visual C++ 2010, так и используя MS Access. Заметим, что в этом примере технология ADO.NET не использована.

    Запустим Visual Studio 2010 и в окне New Project выберем в среде CLR узла Visual C++ приложение шаблона Console Application CLR. Для добавления в наш про­ект DLL-библиотеки ADOX выполним следующее: в пункте меню Project выберем команду Properties ► Add Reference, затем на вкладке COM дважды щелкнем по ссылке Microsoft ADO Ext. 2.8 for DDL and Security, добавив тем самым эту библиотеку в те­кущий проект. Убедиться в том, что теперь существует ссылка на эту библиотеку, можно в окне Properties. Здесь, щелкнув на узле References, увидим ветвь ADOX. Теперь мы можем ссылаться на это имя в программном коде. Далее вводим про­граммный код, приведенный в листинге 10.3.

    Листинг 3. Создание БД во время работы программы

    // БДСоздание.cpp: главный файл проекта.

    // Программа создает базу данных MS Access, то есть файл new_BD.mdb.

    // Эта база данных будет пустой, то есть не будет содержать ни одной таблицы.

    // Наполнять базу данных таблицами можно будет впоследствии

    // как из программного кода C++ 2010, так и используя MS Access.

    // В этом примере технология ADO.NET не использована

    #include "stdafx.h"

    // Добавим в наш проект библиотеку ADOX: Project⴬Add Reference, и на

    // вкладке COM выбираем Microsoft ADO Ext. 2.8 for DDL and Security

    using namespace System;

    // Для вызова MessageBox выберем следующие пункты меню:

    // Project⴬Add Reference и на вкладке .NET дважды щелкнем по ссылке

    // System.Windows.Forms.dll, а в тексте программы добавим директиву:

    using namespace System::Windows::Forms;

    int main(array<System::String ^> ^args)

    {

    ADOX::Catalog ^Каталог = gcnew ADOX::CatalogClass();

    try

    {

    Каталог->Create("Data Source=\"D:\\new_BD.mdb\";User " +

    "ID=Admin;Provider=\"Microsoft.Jet.OLEDB.4.0\";");

    MessageBox::Show("База данных D:\\new_BD.mdb успешно создана");

    }

    catch (System::Runtime::InteropServices::COMException ^ Ситуация)

    {

    MessageBox::Show(Ситуация->Message);

    }

    finally

    { Каталог = nullptr; }

    return 0;

    }

    Чтобы был доступен объект MessageBox для вывода сообщений, добавим в про­ект еще одну dll-библиотеку. Для этого, как и в предыдущем случае, укажем пункты меню Project ► Properties ► Add Reference и на вкладке .NET дважды щелкнем по ссылке System.Windows.Forms.dll, а в тексте программы добавим директиву:

    Using namespace System::Windows::Forms;

    Ключевое слово using используется для импортирования пространства имен, которое содержит класс MessageBox.

    Программа работает следующим образом: создаем экземпляр класса ADOX::cata- log, одна из его функций Create способна создавать базу данных, если на ее вход подать строку подключения. Заметим, что в строку подключения входит также и полный путь к создаваемой БД. Функция Create заключена в блоки try...catch, ко­торые обрабатывают исключительные ситуации. После запуска этого приложения получим сообщение о создании базы данных (рис. 8).

    Если запустить наше приложение еще раз, то мы получим сообщение о том, что такая база данных уже существует (рис. 9), поскольку БД new_BD.mdb только что создана.

    Данное сообщение генерировалось обработчиком исключительной ситуации. Программный код этой программы можно посмотреть, открыв решение БДСоздание.sln в папке БДСоздание.

    Запись структуры таблицы в пустую базу данных MS Access. Программная реализация подключения к БД

    Теперь здесь и далее мы будем использовать только самую современную техноло­гию ADO.NET. Создадим программу, которая записывает структуру таблицы, то есть «шапку» таблицы, в существующую БД. В этой БД может не быть ни одной таблицы, то есть БД может быть пустой. Либо в БД могут уже быть таблицы, но название новой таблицы должно быть уникальным.

    Создадим базу данных new_BD.mdb в корневом каталоге логического диска C:, используя MS Access или программным путем, как это было показано в преды­дущем разделе. Никакие таблицы в базе данных создавать не будем, то есть наша БД будет пустой. Теперь запустим Visual Studio 2010 и в окне New Project выберем в среде CLR узла Visual C++ приложение шаблона Console Application CLR. Затем на­пишем программный код, представленный в листинге 4.

    Листинг 4. Создание таблицы в БД MS Access

    // БдСоздТаблицы.cpp: главный файл проекта.

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

    // Программная реализация подключения к БД. В этой БД может еще не быть

    // ни одной таблицы, то есть БД может быть пустой. Либо в БД могут уже быть

    // таблицы, но название новой таблицы должно быть уникальным

    #include "stdafx.h"

    using namespace System;

    // Для вызова MessageBox выберем следующие пункты меню:

    // Project⴬Add Reference и на вкладке .NET дважды щелкнем по ссылке

    // System.Windows.Forms.dll, а в тексте программы добавим директиву:

    using namespace System::Windows::Forms;

    // Добавляем эту директиву для более краткого обращения к классам

    // обработки данных:

    using namespace System::Data::OleDb;

    int main(array<System::String ^> ^args)

    { // ЗАПИСЬ СТРУКТУРЫ ТАБЛИЦЫ В ПУСТУЮ БД:

    // Создание экземпляра объекта Connection с указанием строки

    // подключения:

    auto Подключение = gcnew OleDbConnection(

    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\new_BD.mdb");

    // Открытие подключения:

    Подключение->Open();

    // Создание экземпляра объекта класса Command

    // с заданием SQL-запроса:

    auto Команда = gcnew OleDbCommand("CREATE TABLE [" +

    "БД телефонов] ([Номер п/п] counter, [ФИО] ch" +

    "ar(20), [Номер телефона] char(20))", Подключение);

    try // Выполнение команды SQL:

    {

    Команда->ExecuteNonQuery();

    MessageBox::Show(

    "Структура таблицы 'БД телефонов' записана в пустую БД");

    }

    catch (Exception ^ Ситуация)

    {

    MessageBox::Show(Ситуация->Message);

    }

    Подключение->Close();

    return 0;

    }

    Для работы функции MessageBox::Show следует в текущий проект добавить ссыл­ку на DLL-библиотеку. Для этого в пункте меню Project ► Properties выберем команду Add Reference и на вкладке .NET дважды щелкнем на ссылке System.Windows.Forms.dll.

    Как видно из текста программы, вначале мы создаем экземпляр класса Connection с указанием строки подключения, это позволит нам управлять этой строкой про­граммно. Далее создаем экземпляр класса Command с заданием SQL-запроса. В этом запросе создаем (CREATE) новую таблицу с именем БД телефонов с тремя полями: Номер п/п типа счетчик (counter), ФИО и Номер телефона. Имя таблицы и имена по­лей заключены в квадратные скобки, поскольку они содержат пробелы.

    Рис. 10. Сообщение о существовании таблицы

    Чтобы выполнить эту SQL-команду, вызываем метод ExecuteNonQuery, который заключим в блоки try...catch для обработки исключительных ситуаций. Если SQL- запрос благополучно выполнился, то получаем сообщение: «Структура таблицы 'БД телефонов' записана в пустую БД». А если, например, таблица с таким именем уже имеется в базе данных, то управление передается блоку catch (перехват исключительной ситуации), и мы получаем сообщение о том, что такая таблица базы дан­ных уже существует (рис. 10).

    Таким образом, в данной программе сначала органи­зовано подключение Connection к БД через строку под­ключения и открытие подключения Open. Затем задание SQL-запроса в объекте Command и выполнение запроса функцией ExecuteNonQuery. Если связывание данных ор­ганизовать программно, то мы получим большую гибкость для тех случаев, когда, например, на стадии разработки неизвестно заранее, где (на каком диске, в какой папке) будет находиться БД.

    Убедиться в работоспособности программы можно, открыв решение БдСоздТаблицы.sln в папке БдСоздТаблицы.

    Добавление записей в таблицу базы данных ms Access

    Совсем маленькую программу из предыдущего раздела можно использовать для выполнения любого запроса, обращенного к базе данных. Например, модифицируем всего лишь одну строчку программного кода программы из предыдущего примера для добавления новой записи в таблицу БД. Для этого при создании экземпляра объ­екта Command зададим SQL-запрос на вставку (INSERT) новой записи в таблицу БД.

    Заметим, что в SQL-запросе мы сознательно обратились к таблице по имени [бд телефонов], то есть со строчной буквы, хотя следовало бы с прописной. Дело в том, что в именах таблиц следует точно указывать регистр символа, поскольку их поиск ведется с учетом регистра (case-sensitive search). Однако это не обязательно при наличии только одной таблицы с таким именем, поскольку при этом используется поиск без учета регистра (case-insensitive search).

    Свойству Connection объекта класса Command следует дать ссылку на объект класса Connection:

    Команда-Connection = Подключение;

    Причем для добавления записи в таблицу БД такая ссылка обязательна в отли­чие от предыдущего примера, где мы создавали новую таблицу в существующей БД. Программный код будет выглядеть так, как представлено в листинге 5.

    Листинг 5. Добавление записей в таблицу базы данных MS Access

    // БдДобавлЗаписи.cpp: главный файл проекта.

    // Программа добавляет запись в таблицу базы данных MS Access. Для этого

    // при создании экземпляра объекта Command задаем SQL-запрос

    // на вставку (Insert) новой записи в таблицу базы данных

    #include "stdafx.h"

    using namespace System;

    // Для вызова MessageBox выберем следующие пункты меню:

    // Project⴬Add Reference и на вкладке .NET дважды щелкнем по ссылке

    // System.Windows.Forms.dll, а в тексте программы добавим директиву:

    using namespace System::Windows::Forms;

    // Добавляем эту директиву для более краткого обращения к классам

    // обработки данных:

    using namespace System::Data::OleDb;

    // ДОБАВЛЕНИЕ ЗАПИСИ В ТАБЛИЦУ БД:

    int main(array<System::String ^> ^args)

    {

    // Создание экземпляра объекта Connection

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

    auto Подключение = gcnew OleDbConnection(

    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\new_BD.mdb");

    // Открытие подключения:

    Подключение->Open();

    // Создание экземпляра объекта Command с заданием SQL-запроса:

    auto Команда = gcnew OleDbCommand(

    "INSERT INTO [бд телефонов] (" +

    "Фио, [номер телефона]) VALUES (‘Света-X’, ‘521-61-41’)");

    // Для добавления записи в таблицу БД эта команда обязательна:

    Команда->Connection = Подключение;

    // Выполнение команды SQL:

    Команда->ExecuteNonQuery();

    MessageBox::Show("В таблицу 'БД телефонов' добавлена запись");

    Подключение->Close();

    return 0;

    }

    Зачастую, отлаживая программный код на Visual Studio C++, при работе с БД появляется необходимость проверки работы программы, например требуется уз­нать, создалась ли таблица в БД, добавилась ли запись в таблице БД, правильно ли сформирован SQL-запрос. Не обязательно запускать MS Access, чтобы вы­полнить SQL-запрос или проверить правильность его синтаксиса. Это можно сде­лать в среде Visual Studio. Для этого в пункте меню View выбираем команду Other Windows ► Server Explorer (комбинация клавиш Ctrl+Alt+S), далее в списке подклю­чений указываем полный путь к нужной БД. Затем, щелкая правой кнопкой мыши на значке нужной таблицы, в контекстном меню выбираем пункт Retrieve Data. При этом в панели инструментов (Toolbar) появляется значок SQL, после щелчка по это­му значку (или нажатия комбинации клавиш Ctrl+3) получим окно SQL-запроса. В этом окне мы можем задавать SQL-запрос, а затем, например, щелкая правой кнопкой мыши, либо проверять его синтаксис, либо выполнять.

    Убедиться в работоспособности программы можно, открыв решение БдДобавл-Записи.sln в папке БдДобавлЗаписи.