• День 1 
  • Контрольные вопросы 
  • Упражнения
  • День 2
  • Контрольные вопросы
  • Упражнения
  • День 3
  • Контрольные вопросы 
  • Упражнения
  • День 4 
  • Контрольные вопросы 
  • Упражнения 
  • День 5
  • Контрольные вопросы 
  • Упражнения 
  • День 6
  • Контрольные вопросы 
  • Упражнения 
  • День 7
  • Контрольные вопросы 
  • Упражнения 
  • День 8
  • Контрольные вопросы 
  •  Упражнения
  • День 9
  • Контрольные вопросы 
  •  Упражнения
  • День 10
  • Контрольные вопросы 
  •  Упражнения
  • День 11
  • Контрольные вопросы 
  •  Упражнения
  • День 12
  • Контрольные вопросы 
  •  Упражнения
  • День 13
  • Контрольные вопросы 
  •  Упражнения
  • День 14
  • Контрольные вопросы 
  •  Упражнения
  • День 15
  •  Контрольные вопросы
  •  Упражнения
  • День 16
  • Контрольные вопросы 
  •  Упражнения
  • День 17
  • Контрольные вопросы 
  •  Упражнения
  • День 18
  •  Контрольные вопросы
  • Упражнения 
  • День 19
  •  Контрольные вопросы
  • Упражнения 
  • День 20
  •  Контрольные вопросы
  • Упражнения 
  • День 21
  •  Контрольные вопросы
  •  Упражнения
  • Приложение Г - Ответы

    День 1 

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

     1. В чем разница между интерпретаторами и компиляторами?

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

    2. Как происходит компиляция исходного кода программы?

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

    3. В чем состоит назначение компоновщика?

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

    4. Какова обычная последовательность действий в цикле разработки?

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

    Упражнения

    1. Инициализирует две целочисленные переменные, а затем выводит их сумму и произведение.

    2. Ознакомьтесь с документацией, которая прилагается к вашему компилятору.

    3. В первой строке перед словом include нужно поместить символ #.

    4. Эта программа выводит на экран слова Hello World, которые завершаются символом разрыва строки.

    День 2

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

     1. В чем разница между компилятором и препроцессором?

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

    2. В чем состоит особенность функции main()?

    Функция main() вызывается автоматически при каждом выполнении программы.

    3. Какие два типа комментариев вы знаете и чем они отличаются друг от друга?

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

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

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

    5. Могут ли комментарии занимать несколько строк?

    Это позволено лишь комментариям в стиле С. Если же вы хотите продолжить на следующей строке комментарии в стиле C++, необходимо поставить в начале второй строки еще одну пару символов (//).

    Упражнения

    1. Напишите программу, которая выводит на экран сообщение I love C++.

       1: #include <iostream.h>

       2:

       3: int main()

       4: {

       5:    cout << "I love C++\n";

       6:    return 0;

       7: }

    2. Напишите самую маленькую программу, которую можно скомпилировать, скомпоновать и выполнить.

    int main() { returna 0; }

    3. Жучки: введите эту программу и скомпилируйте ее. Почему она дает сбой? Как ее можно исправить?

       1: #include <iostream.h>

       2: int main()

       3: {

       4:    cout << Is there a bug here?";

       5:    return 0;

       6: }

    В строке 4 пропущена открывающая кавычка для строкового выражения.

    4. Исправьте ошибку в упражнении 3, после чего перекомпилируйте ее, скомпонуйте и запустите на выполнение.

       1: #include <iostream.h>

       2: int main()

       3: {

       4:    cout << " Is there а bug fiere?";

       5:    return 0;

       6: }

    День 3

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

     1. В чем разница между целочисленной и вещественной (с плавающей точкой) переменными?

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

    2. Каково различие между типами unsigned short int и long int?

    Ключевое слово unsigned означает, что данная целочисленная переменная будет содержать только положительные числа. В большинстве компьютеров для коротких (short) целых чисел отводится 2 байта, а для длинных (long) — 4 байта.

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

    4. Каковы преимущества использования ключевого слова const вместо директивы #define?

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

    5. Как влияет на работу программы "хорошее" или "плохое" имя переменной? Хорошее имя переменной говорит о назначении этой переменной; плохое не несет никакой информации. Например, MyAge (МойВозраст) и PeopleOnTheBus (Пассажиров в автобусе) — это хорошие имена переменных, а в таких именах, как xjk и prndl, вероятно, мало пользы.

    6. Если перечисление (enum) заданно таким образом, то каково значение его члена Blue?

    enum COLOR { WHITE, BLACK = 100, RE0, BLUE, GREEN = 300 }; BLUE = 102

    7. Какие из следующих имен переменных являются хорошими, плохими и вообще недопустимыми?

    а) Age — хорошее имя;

    б) !ex — недопустимое имя;

    в) R79J — допустимое, но неудачное имя;

    г) TotalIncome — хорошее имя;

    д) _Invalid — допустимое, но неудачное имя.

    Упражнения

    1. Какой тип переменной был бы правильным для хранения следующей информации?

    • Ваш возраст.

    Unsigned short integer

    • Площадь вашего заднего двора. Unsigned long integer или unsigned float

    • Количество звезд в галактике. Unsigned double

    • Средний уровень выпадения осадков за январь месяц. Unsigned short integer

    2. Создайте подходящие имена переменных для хранения этой информации.

    а) myAge (МойВозраст)

    б) backYardArea (ПлощадьЗаднегоДвора)

    в) StarsInGalaxy (ЗвездВГалактике)

    г) averageRainFall (СреднемесячныйУровеньОсадков)

    3. Объявите константу для числа pi, равного 3.14159.

    const float pi = 3.14159;

    4. Объявите переменную типа float и инициализируйте ее, используя константу pi.

    float myPi = PI;

    День 4 

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

     1. Что такое выражение?

    Это любой набор операторов, возвращаюший значение.

    2. Является ли запись x = 5 + 7 выражением? Каково его значение?

    Да, является и возвращает значение 12.

    3. Каково значение выражения 201 / 4?

    50

    4. Каково значение выражения 201 % 4?

    1

    5. Если переменные myAge, а и b имеют тип int, то каковы будут их значения после выполнения выражения:

    myAge = 39;

    а = myAge++;

    b = ++myAge;

    myAge: 41, а: 39, b: 41

    6. Каково значение выражения 8+2*3?

    14

    7. Какая разница между выражениями if(x = 3) и if(x == 3)?

    Первое выражение присваивает переменной x значение 3 и возвращает TRUE. Второе выражение проверяет, равно ли значение переменной x числу 3, и возвращает TRUE, если значение переменной x равно 3, и FALSE в противном случае.

    8. Будут ли следующие выражения возвращать true или false?

    а) 0

    FALSE

    б) 1

    TRUE

    в) -1

    TRUE

    г) x = 0

    FALSE

    д) x == 0 // предположим, что x имеет значение 0

    TRUE

    Упражнения 

     1. Напишите один оператор if, который проверяет две целочисленные переменные и присваивает переменной с большим значением меньшее значение, используя только один дополнительный оператор else.

    if (x > у) x = у;

    else // у > x || у == x

    У = x;

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

    1: #include <iostream.h>

    2: int main()

    3: {

    4:    int а, Ь, с;

    5:    cout << "Please enter three numbers\n";

    6:    cout << "а: ";

    7:    cin >> а;

    8:    cout << "\nb: ";

    9:    cin >> b;

    10:  cout << "\nc: ";

    11:  cin >> с;

    12:

    13: if (с = (a-b))

    14: { cout << "а: ";

    15:    cout << а;

    16:    cout << "minus b: ";

    17:    cout << b;

    18:    cout << "equals с: ";

    19:    cout << с << endl; >

    20: else

    21:    cout << "a-b does not equal с: " << endl;

    22:    return 0;

    23: }

    3. Введите программу из упражнения 2; скомпилируйте, скомпонуйте и выполните ее. Введите числа 20, 10 и 50. Вы получали результат, который и ожидали? Почему нет?

    Введите числа 20, 10, 50. А вот результат:

    а: 20 minus

    b: 10 equals

    с: 10

    Неожиданный результат? Дело в том, что в строке 13 выполняется присваивание, а не проверка равенства.

    4. Проанализируйте эту программу и спрогнозируйте результат:

    1: #include<iostream.h>

    2: int main()

    3: {

    4:    int а = 1, b = 1, с;

    5:    if (с = (a-b))

    6:    cout << "The value of c is: " << c;

    7:    return 0;

    8: }

    5. Введите, скомпилируйте, скомпонуйте и выполните программу из упражнения 4. Каков был результат? Почему?

    6. Поскольку в строке 5 переменной с присваивается значение а - b, то значение присваивания выражения а (1) минус b (1) равно 0. Поскольку 0 означает false (ложь), то выходит, что условие проверки не выполняется и поэтому ничего не выводится.

    День 5

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

    1. В чем разница между объявлением прототипа функции и определением функции?

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

    2. Должны ли имена параметров, указанные в прототипе, определении и вызове функции соответствовать друг другу?

    Нет. Все параметры идентифицируются позицией, а не по имени.

    3. Если функция не возвращает значение, как следует объявить такую функцию? Для возврата функции следует установить тип void.

    4. Если не объявить тип возврата, то какой тип будет принят по умолчанию для возвращаемого значения?

    Любая функция, в которой явно не объявляется тип возвращаемого значения, возвращает значение типа int.

    5. Что такое локальная переменная?

    Это переменная, передаваемая или объявленная внутри некоторого блока (обычно функции). Она видима только в пределах этого блока.

    6. Что такое область видимости?

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

    7. Что такое рекурсия?

    В общем случае это способность функции вызывать самое себя.

    8. Когда следует использовать глобальные переменные?

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

    9. Что такое перегрузка функции?

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

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

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

    Упражнения 

     1. Запишите прототип для функции с именем Perimeter, которая возвращает значение типа unsigned long int и принимает два параметра, имеющих тип unsigned short int.

    unsigned long int Perimeter(unsigned short int, unsigned short int);

    2. Запишите определение функции Perimeter согласно объявлению в упражнении 1. Два принимаемых ею параметра представляют длину и ширину прямоугольника, а функция возвращает его периметр (удвоенная длина плюс удвоенная ширина).

    unsigned long int Perimeter(unsigned short int length, unsigned short int width)

    {

       return 2*length + 2*width;

    }

    3. Жучки: что неправильно в этой функции?

    #include <iostream.h>

    void myFunc(unsigned short int x);

    int main()

    {

       unsigned short int x, у;

       у = myFunc(int);

       cout << "x: " << x << " у: " << у << "\n";

    }

    void myFunc(unsigned short int x)

    {

       return (4*x);

    }

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

    4. Жучки: что неправильно в этой функции?

    #include <iostream.h>

    int myFunc(unsigned short int x);

    int main()

    {

       unsigned short int x, у;

       у = myFunc(x);

       cout << "x: " << X << " у: " << у << "\n";

    }

    int myFunc(unsigned short int x);

    {

       return (4*x);

    }

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

    5. Напишите функцию, которая принимает два параметра типа unsigned short int и возвращает результат деления первого параметра на второй. Функция не должна выполнять операцию деления, если второе число равно нулю, но в этом случае она должна возвратить значение -1.

    short int Divider(unsigned short int valOne, unsigned short int valTwo)

    {

       if (valTwo == 0)

          return -1;

       else

          return valOne / valTwo;

    }

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

    #include <iostream.h>

    typedef unsigned short int USHORT;

    typedef unsigned long int ULONG;

    short int Divider(

    unsigned short int valone,

    unsigned short int valtwo);

    int main()

    {

       USHORT one, two;

       short int answer;

       cout << "Enter two numbers.\n Number one: ";

       cin >> one;

       cout << "Number two: ";

       cin >> two;

       answer = Divider(one, two);

       if (answer > -1)

         cout << "Answer: " << answer;

       else

         cout << "Error, can't divide by zero!";

       return 0;

    }

    7. Напишите программу, которая запрашивает число и показатель степени. Напишите рекурсивную функцию, которая возводит число в степень путем многократного умножения числа на самое себя, т.е. если число равно 2, а показатель степени равен 4, то эта функция должна возвратить число 16.

    #include <iostream.h>

    typedef unsigned short USHORT;

    typedef unsigned long ULONG;

    ULONG GetPower(USHORT n, USHORT power);

    int main()

    {

       USHORT number, power;

       ULONG answer;

       cout << "Enter a number: ";

       cin >> number;

       cout << "To what power? ";

       cin >> power;

       answer = GetPower(number,power);

       cout << number << " to the " << power << "th power is " << answer << endl;

       return 0;

    }

    ULONG GetPower(USHORT n, USHORT power)

    {

       if(power == 1)

          return n;

       else

          return (n * GetPower(n,power-1));

    }

    День 6

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

    1. Что представляет собой оператор прямого доступа и для чего он используется?

    Оператор точки прямого доступа представляет собой символ точки (.). Он используется для обращения к членам класса.

    2. Что резервирует память — объявление или определение?

    Память резервируется определениями переменных. Объявления классов не резервируют память.

    3. Объявление класса является его интерфейсом или выполнением?

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

    4. Какова разница между открытыми (public) и закрытыми (private) данными- членами?

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

    5. Могут ли функции-члены быть закрытыми?

    Да. Как функции-члены, так и переменные-члены могут быть закрытыми.

    6. Могут ли переменные-члены быть открытыми?

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

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

    Да. Каждый объект класса имеет свои собственные переменные-члены.

    8. Ну5кно ли объявления класса завершать точкой с запятой? А определения методов класса?

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

    9. Как бы выглядел заголовок функции-члена Meow класса Cat, которая не принимает никаких параметров и возвращает значение типа void?

    Заголовок функции-члена Meow() класса Cat, которая не принимает параметров и возвращает значение типа void, должен иметь следующий вид:

    void Cat::Meow()

    10. Какая функция вызывается для выполнения инициализации класса? Для инициализации класса вызывается конструктор.

    Упражнения 

    1. Напишите программу, которая объявляет класс с именем Employee (Служащие) с такими переменными-членами: age (возраст), yearsOfService (стаж работы) и Salary (зарплата).

    class Employee

    {

       int Age;

       int YearsOfService;

       int Salary;

    };

    2. Перепишите класс Employee, чтобы сделать данные-члены закрытыми и обеспечить открытые методы доступа для чтения и установки всех данных-членов.

    class Employee

    {

       public:

          int GetAge() const;

          void SetAge(int age);

          int GetYearsOfService() const;

          void SetYearsOfService(int years);

          int GetSalary() const;

          void SetSalary(int salary);

       private:

          int Age;

          int YearsOfService;

          int Salary;

    };

    3. Напишите программу с использованием класса Employee, которая создает два объекта класса Employee; устанавливает данные-члены age, YearsOfService и Salary, а затем выводит их значения.

    int main()

    {

       Employee John;

       Employee Sally;

       John.SetAge(30);

       John.SetYearsOfService(5);

       John.SetSalary(50000);

       Sally.SetAge(32);

       Sally.SetYearsOfService(8);

       Sally.SetSalary(40000);

       cout << "At AcmeSexist company, John and Sally have the same job.\n";

       cout << "John is " << John.GetAge() << " years old and he has been with";

       cout << "the firm for " << John.GetYearsOfService << " years.\n";

       cout << "John earns $" << John.GetSalary << " dollars per year.\n\n";

       cout << "Sally, on the other hand is " << Sally.GetAge() << " years old and has";

       cout << "been with the company " << Sally.GetYearsOfService;

       cout << " years. Yet Sally only makes $" << Sally.GetSalary();

       cout << " dollars per year! Something here is unfair.";

       return 0;

    }

    4. На основе программы из упражнения 3 создайте метод класса Employee, который сообщает, сколько тысяч долларов зарабатывает служащий, округляя ответ до 1 000 долларов.

    float Employee:GetRoundedThousands() const

    {

       return (Salary+500) / 1000;

    }

    5. Измените класс Employee так, чтобы можно было инициализировать данные-члены

    age, YearsOfService и Salary в процессе "создания" служащего.

    class Employee

    {

       public:

          Employee(int Age, int yearsOfService, int salary);

          int GetAge() const;

          void SetAge(int Age);

          int GetYearsOfService() const;

          void SetYearsOfService(int years);

          int GetSalary() const;

          void SetSalary(int salary);

       private:

          int Age;

          int YearsOfService;

          int Salary;

    };

    6. Жучки: что неправильно в следующем объявлении?

    class Square

    {

       public:

          int Side;

    }

    Объявления классов должны завершаться точкой с запятой.

    7. Жучки: что весьма полезное отсутствует в следующем объявлении класса?

    class Cat

    {

       int GetAge() const;

       private:

          int itsAge;

    };

    Метод доступа к данным GetAge() является закрытым по умолчанию. Помните: все члены класса считаются закрытыми, если не оговорено иначе.

    8. Жучки: какие три ошибки обнаружит компилятор в этом коде?

    class TV

    {

       public:

          void SetStation(int Station);

          int GetStation() const;

       private:

          int itsStation;

    };

    main()

    {

       TV myTV;

       myTV.itsStation = 9;

       TV.SetStation(10);

       TV myOtherTv(2);

    }

    Нельзя обращаться к переменной itsStation непосредственно. Это закрытая пере- менная-член.

    Нельзя вызывать функцию-член SetStation()npHMO в классе. Метод SetStation() можно вызывать только для объекта.

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

    День 7

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

    1. Можно ли в цикле for инициализировать сразу несколько переменных-счетчиков?

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

    for <х = 0. у = 10; х < 100; х++, у++)

    2. Почему следует избегать использование оператора goto?

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

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

    3. Можно ли с помошью оператора for организовать цикл, тело которого не будет выполняться?

    Да. Если условие после инициализации оказывается ложным (FALSE), то тело цикла

    for никогда не будет выполняться. Вот пример:

    for (lnt х = 100; х < 100; х++)

    4. Можно ли организовать цикл while внутри цикла for?

    Да. Любой цикл может быть вложен внутрь любого другого цикла.

    5. Можно ли организовать цикл, который никогда не завершится? Приведите пример.

    Да. Ниже приведены примеры как для цикла for, так и для цикла wtiile:

    for(;;)

    {

       // Этот цикл никогда не заканчивается!

    }

    whlle(1)

    {

       // Этот цикл никогда не заканчивается!

    }

    6. Что происходит при запуске бесконечного цикла?

    Программа зависнет и придется перезагрузить компьютер.

    Упражнения 

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

    for (int X = 0; X < 100; х++)?

    100

    2. Создайте вложенный цикл for, заполняющий нулями массив размером 10x10.

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

    {

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

          cout << О ;

       cout << "\n";

    }

    3. Организуйте цикл for, счетчик которого изменяется от 100 до 200 с шагом 2.

    for (int х = 100; х<=200; х*=2)

    4. Организуйте цикл while, счетчик которого изменяется от 100 до 200 с шагом 2.

    int х = 100;

    while (х <= 200)

       х+= 2:

    5. Организуйте цикл do...while, счетчик которого изменяется от 100 до 200 с шагом 2.

    int х = 100;

    do

    {

       х+=2:

    } while (х <= 200);

    6. Жучки: найдите ошибку в приведенном фрагменте программы:

    int counter = 0;

    while (counter < 10)

    {

       cout << "counter: " << counter;

    }

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

    7. Жучки: найдите ошибку в приведенном фрагменте программы:

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

       cout << counter << " ";

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

    8. Жучки: найдите ошибку в приведенном фрагменте программы:

    int counter = 100;

    while (counter < 10)

    {

       cout << "counter now: " << counter;

       counter—-;

    }

    Счетчик counter инициализирован числом 100, но проверяемое условие таково, что, если значение переменной counter больше 10, выражение условия возвратит FALSE и тело цикла никогда не будет выполнено. Если первую строку заменить вариантом int counter = 5;, то этот цикл не закончится до тех пор, пока не выполнится обратный отсчет до минимально возможного значения счетчика. Поскольку тип счетчика int по умолчанию определяется как signed, то мы получим бесконечный цикл.

    9. Жучки: найдите ошибку в приведенном фрагменте программы:

    cout << "Enter a number between 0 and 5: ";

    cin >> theNumber;

    switch (theNumber)

    {

       case 0:

          doZero();

       case 1: // идем дальше

       case 2: П идем дальше

       case 3: // идем дальше

       case 4: Ц идем дальше

       case 5:

          doOneToFive();

          break;

       default:

          doDefault();

          break;

    }

    После оператора case 0, видимо, должен быть оператор break. Если это не так, то ситуацию следовало бы разъяснить с помошью комментария.

    День 8

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

     1. Какой оператор используется для получения адреса переменной?

    Для возвращения адреса любой переменной используется оператор получения адреса (&).

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

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

    3. Что такое указатель?

    Это переменная, которая содержит адрес другой переменной.

    4. В чем различие между адресом, который хранится в указателе, и значением, записанным по этому адресу?

    Адрес, сохраненный в указателе, — это адрес другой переменной. Значение, сохраненное по этому адресу, — это любое значение, сохраняемое в переменной, на которую ссылается указатель. Оператор разыменования (*) возвращает значение, сохраненное по адресу, который хранится в указателе.

    5. В чем различие между оператором разыменования и оператором получения адреса?

    Оператор разыменования (*) возвращает значение, хранящееся по адресу, на который ссылается указатель. А оператор получения адреса (&) возвращает адрес переменной в памяти.

    6. В чем различие между следующими объявлениями: const int * ptrOne и int * const ptrTwo?

    Выражение const int * ptrOne объявляет, что переменная ptrOne представляет собой указатель на постоянное целое число. Само это целое число не может быть изменено с помощью данного указателя.

    Выражение int * const pi rTwo объявляет, что переменная ptrTwo является постоянным указателем на некоторое целое число. После такой инициализации этот указатель не может быть переназначен.

     Упражнения

     1. Объясните смысл следующих объявлений переменных.

    • int * рОпе;

    • int vTwo;

    • int * pThree = &vTwo;

    Ответы:

    а) int * pOne: — объявляет указатель на целое значение;

    б) int vTwo — объявляет целочисленную переменную;

    в) int * pThroe = &vTwo; — объявляет указатель на целое значение и инициализирует его адресом переменной.

    2. Допустим, в программе объявлена переменная yourAge типа unsigned short. Как объявить указатель, позволяющий манипулировать этой переменной?

    unsigned short *рАgе = AyourAgo;

    3. С помошью указателя присвойте переменной yourAge значение 50.

    *рАge =50

    4. Напишите небольшую программу и объявите в ней переменную типа int и указатель на этот тип. Сохраните адрес переменной в указателе. Используя указатель, присвойте переменной какое-либо значение.

    int theInteger;

    int *plnteger = &theInteger:

    *plnteger = 5:

    5. Жучки: найдите ошибку в следующем фрагменте программы:

    #include <iostream.h>

    int main()

    {

       int *pInt;

       *pInt = 9;

       cout << " The value at pInt: " << *pInt;

       return 0;

    }

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

    6. Жучки: найдите ошибку в следующем фрагменте программы:

    int main()

    {

       int SomeVariable = 5.

       cout << "SomeVariable: " << SomeVariable << "\n";

       int *pVar = & SomeVariable;

       pVar = 9:

       cout << "SomeVariable: " << *pVar << "\n":

       return 0;

    }

    Возможно, программист хотел присвоить число 9 переменной, на которую указывает указатель pVar. К сожалению, число 9 было присвоено самому указателю pVar, поскольку был опушен оператор косвенного доступа (*)• Если указатель pVar используется для присвоения ему значения, такое программирование неминуемо приведет к тяжелым последствиям.

    День 9

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

     1. В чем разница между ссылкой и указателем?

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

    2. Когда нужно использовать именно указатель, а не ссылку?

    Если в программе нужно назначить указателю новую переменную или если указатель нужно сделать нулевым.

    3. Что возвращает оператор new, если для создания нового объекта недостаточно памяти?

    Нулевой указатель.

    4. Что представляет собой константная ссылка?

    Это сокращенный вариант определения ссылки на константный объект.

    5. В чем разница между передачей объекта как ссылки и передачей ссылки в функцию?

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

     Упражнения

    1. Напишите программу, которая объявляет переменную типа int, ссылку на значение типа int и указатель на значение типа int. Используйте указатель и ссылку для управления значением переменной типа int.

    int main()

    {

      int varOne;

      int& rVar = varOne;

      int* pVar = &varOne;

      rVar = 5:

      *pVar = 7:

      return 0;

    }

    2. Напишите программу, которая объявляет константный указатель на постоянное целое значение. Инициализируйте его, чтобы он указывал на целочисленную переменную varOne. Присвойте переменной varOne значение 6. Используйте указатель, чтобы присвоить переменной varOne значение 7. Создайте вторую целочисленную переменную varTwo. Переназначьте указатель, чтобы он указывал на переменную varTwo. Пока не компилируйте это упражнение.

    int main()

    {

       int varOne;

       const int * const pVar = dvarOne;

       *pVar = 7;

       int varTwo;

       pVar = &varTwo.

       return 0;

    }

    3. Скомпилируйте программу, написанную в упражнении 2. Какие действия компилятор считает ошибочными? Какие строки генерируют предупреждения?

    Нельзя присваивать значение константному объекту и нельзя переназначать константный указатель.

    4. Напишите программу, которая создает блуждающий указатель.

    int main()

    {

       int >> pVar;

       *pVar = 9;

       return 0;

    }

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

    int main()

    {

       int varOne;

       int * pVar = ivarOne;

       *pVar = 9;

       return 0.

    }

    6. Напишите программу, которая приводит к утечке памяти.

    #include <iostream.h>

    int * FuncOne();

    int main()

    {

       int * pInt = FuncOneO;

       cout << "the value of pint in main is: " << *pInt << endl:

       return 0;

    }

    int * FuncOne()

    {

       int * pInt = new int (5):

       cout << "the value of pint in FuncOne is: " << *pInt << endl:

       return pInt;

    }

    7. Исправьте программу из упражнения 6.

    #include <iostream.h>

    int FuncOne();

    int main()

    {

       int theInt = FuncOne();

       cout << "the value of pint in main is: " << theInt << endl;

       return 0:

    }

    int FuncOne()

    {

       int * pInt = new int (5);

       cout << "the value of pint in FuncOne is: " << <<pInt << endl;

       delete pint;

       return temp;

    }

    8. Жучки: что неправильно в этой программе?

    1: #include <iostream.h>

    3: class CAT

    4: {

    5:    public:

    6:       CAT(int age) { itsAge = age; }

    7:       ~CAT(){ }

    8:       int GetAge() const { return itsAge; }

    9:    private:

    10:      int itsAge:

    11: };

    12:

    13: CAT & MakeCat(int age):

    14: int main()

    15: {

    16:    int age = 7;

    17:    CAT Boots = MakeCat(age);

    18:    cout << "Boots is " << Boots.GetAge() << " years old\n";

    19:    return 0:

    20: }

    22: CAT & MakeCat(int age)

    23: {

    24:    CAT * pCat = new CAT(age);

    25:    return *pCat;

    26: }

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

    9. Исправьте программу из упражнения 8.

    1: #include <iostream.h>

    2:

    3: class CAT

    4: {

    5:    public:

    6:       CAT(int age) { itsAge = age; }

    7:       ~CAT(){ }

    8:       int GetAgeO const { return itsAge;}

    9:    private:

    10:      int itsAge;

    11: };

    13: CAT * MakeCat(int age);

    14: int main()

    15: {

    16:    int age = 7;

    17:    CAT * Boots = MakeCat(age);

    18:    cout << "Boots is " << Boots.GetAge() << " years old\n";

    19:    delete Boots;

    20:    return 0;

    21: }

    23: CAT * MakeCat(int age)

    24: {

    25:    return new CAT(age);

    26: }

    День 10

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

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

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

    2. Какая разница между определением и объявлением?

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

    3. Когда вызывается конструктор-копировщик?

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

    4. Когда вызывается деструктор?

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

    5. Чем отличается конструктор-копировщик от оператора присваивания (=)?

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

    6. Что представляет собой указатель this?

    Это скрытый параметр в каждой функции-члене, который указывает на сам объект.

    7. Как различить перегрузку префиксных и постфиксных операторов приращения?

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

    8. Можно ли перегрузить operator+ для переменных типа short int?

    Нет, для встроенных типов нельзя перегружать никаких операторов.

    9. Допускается ли в C++ перегрузка operator++ таким образом, чтобы он выполнял в классе операцию декремента?

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

    10. Как устанавливается тип возврата в объявлениях функций операторов преобразования типов?

    Никак. Подобно конструкторам и деструкторам, они не имеют никаких возвращаемых значений.

     Упражнения

    1. Представьте объявление класса SimpleCircle (простая окружность) с единственной переменой-членом itsRadius (радиус). В классе должны использоваться конструктор и деструктор, заданные по умолчанию, а также метод установки радиуса.

    class SimpleCircle

    {

       public:

          SimpleCircle();

          ~SimpleCircle();

          void SetRadius(int);

          int GetRadiusO;

       private:

          int itsRadius;

    };

    2. Используя класс, созданный в упражнении !, с помошью конструктора, заданного по умолчанию, инициализируйте переменную itsRadius значением 5.

    SimpleCircle::SimpleCircle():

    itsRadius(5);

    { }

    3. Добавьте в класс новый конструктор, который присваивает значение своего пара-

    метра переменной itsRadius.

    SimpleCircle::SimpleCircle(int radius):

    itsRadius(radius)

    { }

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

    шем классе SimpleCircle с переменной itsRadius.

    const SimpleCircle& SimpleCircle::operator++()

    {

       ++(itsRadius);

       return *this;

    }

    // постфиксный оператор Operator ++(int).

    // Выборка, затем инкрементирование

    const SimpleCircle SimpleCircle::operator++ (int)

    // обьявляем локальный обьект класса SimpleCircle и инициализируем его значением

    * this

       SimpleCircle temp(*this);

       ++(itsRadius);

       return temp;

    }

    5. Измените SimpleCircle таким образом, чтобы сохранять itsRadius в динамической области памяти и фиксировать существующие методы.

    class SimpleCircle

    {

       public:

          SimpleCircle();

          SimpleCircle(int);

          ~SimpleCircle();

          void SetRadius(int);

          int GetRadius();

          const SimpleCircle& operator++();

          const SimpleCircle operator++(int);

       private:

          int *itsRadius;

    };

    SimpleCircle::SimpleCircle()

       {itsRadius = new int(5);}

    SimpleCircle::SimpleCircle(int radius)

       {itsRadius = new int(radius);}

    SimpleCircle::~SimpleCircle()

    {

       delete itsRadius;

    }

    const SimpleCircle& SimpleCircle::operator++()

    {

       ++(*itsRadius);

       return <<this;

    }

    // Постфиксный оператор Operator++(int).

    // Выборка, затем инкрементирование

    const SimpleCircle SimpleCircle::operator++ (int)

    {

       // объявляем локальный объект класса SimpleCircle и инициализируем его значением

       *this

          SimpleCircle temp(<<this);

          ++(*itsRadius);

          return temp;

    }

    6. Создайте в классе SimpleCircle конструктор-копировщик.

    SimpleCircle::SimpleCircle(const SimpleCircle & rhs)

    {

       int val = rhs.GetRadius();

       itsRadius = new int(val);

    }

    7. Перегрузите в классе SimpleCircle оператор присваивания.

    SimpleCircle& SimpleCircle::operator=(const SimpleCircle & rhs)

    {

       if (this == &rhs)

          return *tnis;

       delete itsRadius;

       itsRadius = new int;

       *itsRadius = rhs.GetRadius();

       return *this;

    }

    8. Напишите программу, которая создает два объекта класса SimpleCircle. Для создания одного объекта используйте конструктор, заданный по умолчанию, а второму экземпляру при объявлении присвойте значение 9. С каждым из объектов используйте оператор инкремента и выведите полученные значения на печать. Наконец, присвойте значение одного объекта другому объекту и выведите результат на печать.

    #include <iostream.h>

    class SimpleCircle

    {

       public:

          // конструкторы

          SimpleCircle();

          SimpleCircle(int);

          SimpleCircle(const SimpleCircle &);

          ~SimpleCircle() {}

          // методы доступа к данным

          void SetRadius(int);

          int GetRadius() const;

          // операторы

          const SimpleCircle& operator++();

          const SimpleCircle operator++(int);

          SimpleCircle& operator=(const SimpleCircle &):

       private:

          int *itsRadius;

    };

    SimpleCircle::SimpleCircle()

    {itsRadius = new int(5);}

    SimpleCircle::SimpleCircle(int radius)

    {itsRadius = new int(radius);}

    SimpleCircle::SimpleCircle(const SimpleCircle & rh$)

    {

       int val = rhs.GetRadius();

       itsRadius = new int(val);

    }

    SimpleCircle::~SimpleCircle()

    {

       delete itsRadius;

    }

    SimpleCircleS SimpleCircle :operator=(const SimpleCircle & rhs)

    {

       if (this == &rhs)

          return <<this;

       *itsRadius = rhs.GetRadius();

       return *this;

    }

    const SimpleCircle& SimpleCircle::operator++()

    {

       ++(*itsRadius);

       return *this;

    }

    // Постфиксный оператор Operator ++(int).

    // Выборка, затем инкрементирование

    const SimpleCircle SimpleCircle::operator++ (int)

    {

       // объявляем локальный объект класса SimpleCircle и инициализируем его значением

       *this

          SimpleCircle ternp(*this);

          ++(*itsRadius);

          return temp;

    }

    int SimpleCircle::GetRadius() const

    {

       return ~itsRadius;

    }

    int main()

    {

       SimpleCircle CircleOne, CircleTwo(9);

       CircleOne++;

       ++CircleTwo;

       cout << "CircleOne: " << CircleOne.GetRadius() << endl;

       cout << "CircleTwo: " << CircleTwo.GetRadius() << endl;

       CircleOne = CircleTwo;

       cout << "CircleOne: " << CircleOne.GetRadius() << endl:

       cout << "CircleTwo: " << CircleTwo.GetRadius() << endl;

       return 0;

    }

    9. Жучки: что неправильно в следующем примере использования оператора присваивания?

    SQUARE SQUARE::operator=(const SQUARE & rhs)

    {

       itsSide - new int;

       *itsSide = rhs.GetSide();

       return *this;

    }

    Нужно выполнить проверку на равенство объектов rhs и this, в противном случае обращение к оператору а = а приведет к аварийному отказу вашей программы.

    10. Жучки: что неправильно в следующем примере использования оператора суммирования?

    VeryShort VeryShort::operator+ (const VeryShort& rhs)

    {

       itsVai += rhs.GetltsVal();

       return *this;

    }

    Этот оператор operator+ изменяет значение в одном из операндов, а не создает с помощью суммирования новый объект VeryShort. Правильно написать следующее:

    VeryShort VeryShort::operator+ (const VeryShort& rhs)

    {

       return VeryShort(itsVal + rhs.GetltsVal());

    }

    День 11

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

    1. Что такое v-таблица?

    V-таблица, или таблица виртуальных функций, является обычным средством управления виртуальными функциями в C++, используемым компилятором. Эта таблица хранит список адресов всех виртуальных функций и обеспечивает вызов правильной функции в зависимости от указываемого типа объекта во время выполнения программы.

    2. Что представляет собой виртуальный деструктор?

    Деструктор любого класса, который может быть объявлен виртуальным. Во время выполнения программы при применении delete к указателю на определенный тип объекта будет вызван деструктор соответствующего типа.

    3. Можно ли объявить виртуальный конструктор?

    Виртуальных конструкторов не существует.

    4. Как создать виртуальный конструктор-копировщик?

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

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

    Base::FunctionName();

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

    FunctionName():

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

    Да, виртуальность наследуется и не может быть отменена.

    8. С какой целью используется ключевое слово protected?

    Защищенные члены (которые объявлены с использованием ключевого слова protected) доступны для функций-членов производных объектов.

     Упражнения

    1. Объявите виртуальную функцию, которая принимает одно целочисленное значение и возвращает void.

    virtual void SomeFunction(int);

    2. Запишите объявление класса Square (квадрат), произведенного от класса Rectangle (прямоугольник), который, в свою очередь, произведен от класса Shape (форма).

    class Square : public Rectangle

    { };

    3. Предположим, что в предыдущем примере объект класса Shape не использует параметры, объект класса Rectangle принимает два параметра (length и width), а объект класса Square — один параметр (length); запишите конструктор для класса Square.

    Square::Square(int length):

       Rectangle(length, length)}}

    4. Запишите виртуальный конструктор-копировщик для класса Square, взятого из упражнения 3.

    class Square

    {

       public:

          // ...

          virtual Square * clone() const { return new Square(*this); }

          // ...

    };

    5. Жучки: что неправильно в следующем программном коде?

    void SomeFunction (Shape);

    Shape * pRect = new Rectangle;

    SomeFunction(*pRect);

    Возможно, здесь все правильно. Функция SomeFunction ожидает получения объекта класса Shape. Вы передали ей объект класса Rectangle, произведенный от класса Shape. До тех пор пока вам не нужны никакие составные части класса Rectangle, такой подход будет нормально работать. Если же вам понадобятся члены класса Rectangle, придется изменить объявление функции SomeFunction, чтобы она принимала указатель или ссылку на объект класса Rectangle,

    6. Жучки: что неправильно в следующем программном коде?

    class Shape()

    {

       public:

          Shape();

          virtual ~ShapeO;

          virtual Shape(const Shape&);

    };

    Нельзя объявить виртуальным конструктор-копировщик.

    День 12

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

    1. Как обратиться к первому и последнему элементам массива SomeArray[25]?

    SomeArray[0], SomeArray[24]

    2. Как объявить многомерный массив?

    Напишите набор индексов для каждого измерения. Например,

    SomeArray[2][3][2] — это трехмерный массив. Первое измерение содержит два элемента, второе — три, а третье — два.

    3. Выполните инициализацию элементов многомерного массива, созданного при ответе на вопрос 2.

    SomeArray[2][3)[2] = { { {1,2},{3,4},<5,6} } , { {7,8},{9,10},{11,12} } };

    4. Сколько элементов содержит массив SomeArray[i0][5][20]?

    10x5x20=1 ООО

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

    Не существует никакого фиксированного максимума. Это зависит от объема доступной памяти.

    6. Можно ли в связанном списке использовать индексы?

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

    7. Каким является последний символ в строке "Сергей — хороший парень"?

    8. Концевой нулевой символ.

     Упражнения

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

    int GameBoard[3][3];

    2. Запишите программный код, инициализирующий значением 0 все элементы созданного перед этим массива.

    int GameBoard[3][3] = { {0,0,0},{0,0,0},{0.0,0} }

    3. Объявите класс узла Node, поддерживающего целые числа.

    class Node

    {

       public:

          Node ();

          Node (int);

          ~Node():

          void SetNext(Node * node) { itsNext = node; }

          Node * GetNextO const { return itsNext; }

          int GetVal() const { return itsVal; }

          void Insert(Node *);

          void Display();

       private:

          int itsVal;

          Node * itsNext;

    };

    4. Жучки: что неправильно в следующей программе?

    unsigned short SomeArray[5][4];

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

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

          SomeArray[i][j] = i+j;

    Массив SomeArray предназначен для хранения 5x4 элементов, но код инициализирует матрицу 4x5 элементов.

    5. Жучки: что неправильно в следующей программе?

    unsigned short SomeArray[5][4];

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

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

          SomeArray[i][j] = 0;

    Вероятно, программист хотел написать i < 5, но написал вместо этого i <= 5.

    Программа будет работать, когда i == 5 и j == 4, но в массиве SomeArray нет такого

    элемента, как SomeArray[5][4].

    День 13

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

    1. Что такое приведение типа объекта вниз?

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

    2. Что такое v-ptr?

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

    3. Предположим, для создания прямоугольника с закругленными углами используется класс RoundRect, произведенный от двух базовых классов — Rectangle и Circle, которые, в свою очередь, производятся от общего класса Shape. Как много объектов класса Shape создается при создании одного объекта класса RoundRect?

    Если никакой класс не наследует использование ключевого слова virtual, то создаются два объекта класса Shape: один для класса RoundRect и один для класса Circle. Если же ключевое слово virtual используется для обоих классов, то создается только один общий объект класса Shape.

    4. Если классы Horse (Лошадь) и Bird (Птица) виртуально наследуются от класса Animal

    (Животное) как открытые, будут ли конструкторы этих классов инициализировать конструктор класса Animal? Если класс Pegasus (Пегас) наследуется сразу от двух классов, Horse и Bird, как в нем будет инициализироваться конструктор класса Animal?

    Оба класса Horse и Bird инициализируют в своих конструкторах базовый класс Animal. Класс Pegasus делает то же самое, но когда создается объект класса Pegasus, инициализации класса Animal в производных классах Horse и Bird игнорируются.

    5. Объявите класс Vehicle (Машина) как абстрактный тип данных.

    class Vehicle

    {

    virtual void Move() = 0;

    }

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

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

     Упражнения

    1. Опишите класс JetPlane (Реактивный самолет), наследуя его от двух базовых классов — Rocket (Ракета) и Airplane (Самолет).

    class JetPlane : public Rocket, public Airplane

    2. Произведите от класса JetPlane, объявленного в первом упражнении, новый класс 747.

    class 747 : public JetPlane

    3. Напишите программу, производящую классы Саг (Легковой автомобиль) и Bus (Автобус) от класса Vehicle (Машина). Опишите класс Vehicle как абстрактный тип данных с двумя чистыми виртуальными функциями. Классы Саг и Bus не должны быть абстрактными.

    class Vehicle

    {

       virtual void Move() = 0;

       virtual void Haul() = 0;

    };

    class Car : public Vehicle

    {

       virtual void Move();

       virtual void Haul();

    }:

    class Bus : public Vehicle

    {

       virtual void Move();

       virtual void Haul();

    };

    4. Измените программу из предыдущего упражнения таким образом, чтобы класс Саr тоже стал ADT, и произведите от него три новых класса: SportsCar (Спортивный автомобиль), Wagon (Фургон) и Coupe (Двухместный автомобиль-купе). В классе Саr должна замещаться одна из виртуальных функций, объявленных в классе Vehicle, с вызовом функции базового класса.

    class Vehicle

    {

       virtual void Move() = 0;

       virtual void Haul() = 0;

    };

    class Car : public Vehicle

    {

       virtual void Move():

    };

    class Bus : public Vehicle

    {

       virtual void Move();

       virtual void Haul();

    }

    class SportsCar : public Car

    {

       virtual void Haul();

    }

    class Coupe : public Car

    {

       virtual void Haul();

    }

    День 14

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

    1. Могут ли статические переменные-члены быть закрытыми?

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

    2. Объявите статическую переменную-член.

    static int itsStatic;

    3. Объявите статическую функцию.

    static int SomeFunction();

    4. Объявите указатель на функцию, принимающую параметр типа int и возвращающую значение типа long.

    long (*function)(int);

    5. Измените указатель, созданный в задании 4, на указатель на функцию-член класса Саr.

    long ( Car::*function)(int);

    6. Объявите массив из десяти указателей, созданных в задании 5.

    long ( Car::*function)(int) theArray [10];

     Упражнения

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

    1: class myClass

    2: {

    3:    public:

    4:       myClass();

    5:       ~myClass();

    6:    private:

    7:       int itsMember;

    8:       static int itsStatic;

    9: };

    10:

    11: myClass::myClass():

    12: itsMember(1)

    13: {

    14:    itsStatic++;

    15: }

    16:

    17:myClass::~myClass()

    18: {

    19:    itsStatic—-;

    20: }

    21:

    22: int myClass::itsStatic = 0;

    23:

    24: int main()

    25: {}

    2. Используя программный блок из упражнения 1, напишите короткую выполняемую

    программу, которая создает три объекта, а затем выводит значения их перемен-

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

    ляйте объекты и выводите на экран значение статической переменной-члена.

    1: #include <iostream.h>

    2:

    3: class myClass

    4: {

    5:    public:

    6:       myClass();

    7:       ~myClass();

    8:       void ShowMember()

    9:       void ShowStatic();

    10:   private:

    11:      int itsMember;

    12:      static int itsStatic;

    13: };

    14:

    15: myClass::myClass():

    16: itsMember(1)

    17: {

    18:    itsStatic++;

    19: }

    20:

    21: myClass::~myClass()

    22: {

    23:    itsStatic--;

    24:    cout << "In destructor. ItsStatic: " << itsStatic << endl;

    25: }

    26:

    27: void myClass::ShowMember()

    28: {

    29:    cout << "itsMember: " << itsMereber << endl;

    30: }

    31:

    32: void myClass::ShowStatic()

    33: {

    34:    cout << "itsStatic: " << itsStatic << endl;

    35: }

    36: int myClass::itsStatic = 0;

    37:

    38: int main()

    39: {

    40:    myClass obj1;

    41:    obj1.ShowMember();

    42:    obj1.ShowStatic();

    43:

    44:    myClass obj2;

    45:    obj2.ShowMember();

    46:    obj2.ShowStatic();

    47:

    48:    myClass obj3;

    49:    obj3.ShowMember();

    50:    obj3.ShowStatic();

    51:    return 0;

    52: }

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

    1: #include <iostream.h>

    2:

    3: class myClass

    4: {

    5:    public:

    6:       myClass();

    7:       ~myClass();

    8:       void ShowMember();

    9:       static int GetStatic();

    10:   private:

    11:      int itsMember;

    12:      static int itsStatic;

    13: }

    14:

    15: myClass::myClass():

    16: itsMember(l)

    17: {

    18:    itsStatic++;

    19: }

    20:

    21: myClass::~myClass()

    22: {

    23:    itsStatic--;

    24:    cout << "In destructor. ItsStatic: " << itsStatic << endl;

    25: }

    26:

    27: void myClass::ShowMember()

    28: {

    29:    cout << "itsMember: " << itsMember << endl;

    30: }

    31:

    32: int myClass::itsStatic = 0;

    33:

    34: void myClass::GetStatic()

    35: {

    36:    return itsStatic;

    37: }

    38:

    39: int main()

    40: {

    41:    myClass obj1:

    42:    obj1.ShowMeraber();

    43:    cout << "Static: " << myClass::GetStatic() << endl;

    44:

    45:    myClass obj2;

    46:    obj2.ShowMember();

    47:    cout << "Static " << myClass::GetStatic() << endl;

    48:

    49:    myClass obj3;

    50:    obj3.ShowMember();

    51:    cout << "Static: " << myClass::GetStatic() << endl;

    52:    return 0;

    53: }

    4. Создайте в программе из упражнения 3 указатель на функцию-член для доступа к значению нестатической переменной-члена и воспользуйтесь им для вывода этих значений на печать.

    1: #include ciostream.h>

    2:

    3: class myClass

    4: {

    5:    public

    6:       myClass();

    7:       ~myClass();

    8:       void ShowMember();

    9:       static int GetStatic();

    10:   private:

    11:      int itsMember;

    12:      static int itsStatic;

    13: }

    14:

    15: myClass::myClass():

    16: itsMember(1)

    17: {

    18:    itsStatic++;

    19: }

    20:

    21: myClass::~myClassO

    22: {

    23:    itsStatic--;

    24:    cout << "In destructor. ItsStatic: " << itsStatic << endl;

    25: }

    26:

    27: void myClass::ShowMember()

    28: {

    29:    cout << "itsMember: " << itsMember << endl;

    30: }

    31:

    32: int myClass::itsStatic = 0;

    33:

    34: int myClass::GetStatic()

    35: {

    36:    return itsStatic;

    37: }

    38:

    39: int main()

    40: {

    41:    void (myClass::*PMF) ();

    42:

    43:    PMF=myClass: :ShowMember;

    44:

    45:    myClass obj1;

    46:    (obj1.*PMF)();

    47:    cout << "Static: " << myClass::GetStatic() << endl;

    48:

    49:    myClass obj2:

    50:    (obj2.*PMF)();

    51:    cout << "Static: " << myClass::GetStatic() << endl;

    52:

    53:    myClass obj3;

    54:    (obj3.*PMF)();

    55:    cout << "Static: " << myClass::GetStatic() << endl;

    56:    return 0;

    57: }

    5. Добавьте две дополнительные переменные-члена к классу из предыдущих упраж-

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

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

    наковую сигнатуру. Для доступа к этим методам используйте указатель на функ-

    цию-член.

    1: #include<iostream.h>

    2:

    3: class myClass

    4: {

    5:    public:

    6:       myClass();

    7:       ~myClass();

    8:       void ShowMember();

    9:      void ShowSecond();

    10:      void ShowThird();

    11:      static int GetStatic();

    12:   private:

    13:      int itsMember;

    14:      int itsSecond;

    15:      int itsThird;

    16:      static int itsStatic;

    17: }

    18:

    19: myClass::myClass()

    20:    itsMember(1),

    21:    itsSecond(2),

    22:    itsThird(3)

    23: {

    24:    itsStatic++;

    25: }

    27: myClass::^rnyClass()

    28: {

    29:    itsStatic--;

    30:    cout << "In destructor. ItsStatic: " << itsStatic << endl;

    31: }

    32:

    33: void myClass::ShowMember()

    34: {

    35:    cout << "itsMember: " << itsMember << endl;

    36: }

    37:

    38: void myCIass:: ShowSecond()

    39: {

    40:    cout << "itsSecond: " << itsSecond << endl;

    41: }

    42:

    43: void myClass::ShowThird()

    44: {

    45:    cout << "itsThird: " << itsThird << endl;

    46: }

    47: int myClass::itsStatic = 0;

    48:

    49: int myClass::GetStatic()

    50: {

    51:    return itsStatic;

    52: }

    53:

    54: int main()

    55: {

    56:    void (myClass::*PMF) ();

    57:

    58:    myClass obj1;

    59:    PMF=myClass::ShowMember;

    60:    (obj1.*PMF)();

    61:    PMF=myClass::ShowSecond;

    62:    (obj1.*PMF)();

    63:    PMF=myClass::ShowThird;

    64:    (ob]1 "PMF)();

    65:    cout << "Static: " << myClass::GetStatic() << endl;

    66:

    67:    myClass obj2;

    68:    PMF=myClass::ShowMerober;

    69:    (obj2.*PMF)();

    70:    PMF=myClass::ShowSecond;

    71:    (obj2.*PMF)();

    72:    PMF=myClass::ShowThird;

    73:    (obj2. *PMF)();

    74:    cout << "Static: " << myClass::GetStatic() << endl;

    75:

    76:    myClass obj3;

    77:    PMF=myClass::ShowMember;

    78:    (obj3. *PMF)();

    79:    PMF=myClass::ShowSecond;

    80:    (obj3.*PMF)();

    81:    PMF=royClass::ShowThird;

    82:    (obj3.*PMF)();

    83:    cout << "Static: " << myClass::GetStatic() << endl;

    84:    return 0;

    85: }

    День 15

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

    1. Как объявить класс, являющийся частным проявлением другого класса?

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

    2. Как объявить класс, объекты которого должны использоваться в качестве переменных-членов другого класса?

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

    3. В чем состоят различия между вложением и делегированием?

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

    4. В чем состоят различия между делегированием и выполнением класса в пределах другого класса?

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

    5. Что такое функция-друг?

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

    6. Что такое класс-друг?

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

    7. Если класс Dog является другом Boy, то можно ли сказать, что Boy — друг Dog?

    Нет, дружественность классов не взаимна.

    8. Если класс Dog является другом Boy, а Terrier произведен от Dog, является ли Terrier другом Boy?

    Нет, дружественность классов не наследуется.

    9. Если класс Dog является другом Boy, а Boy — другом House, можно ли считать Dog другом House?

    Нет, дружественность классов не ассоциативна.

    10. Где необходимо размешать объявление функции-друга?

    В любом месте внутри объявления класса. Не имеет никакого значения, в каком именно разделе будет помешено это объявление — в public:, protected: или private:.

     Упражнения

     1. Объявите класс Animal (Животное), который содержит переменную-член, являющуюся объектом класса String.

    class Animal:

    {

       private:

          String itsName;

    };

    2. Опишите класс BoundedArray, являющийся массивом.

    class boundedArray : public Array

    {

       // ...

    }

    3. Опишите класс Set, выполняемый в пределах массива BoundedArray.

    class Set : private Array

    {

       // ...

    }

    4. Измените листинг 15.1 таким образом, чтобы класс String включал перегруженный

    оператор вывода (>>).

    1: #include <iostream.h>

    2: #include <string.h>

    3:

    4: class String

    5: {

    6:    public:

    7:       // конструкторы

    8:       String();

    9:       String(const char *const):

    10:      String(const String &);

    11:      ~String();

    12:

    13:      // перегруженные операторы

    14:      char & operator[](int offset);

    15:      char operator[](int offset) const:

    16:      String operator+(const String&);

    17:      void operator+=(const String&);

    18:      String & operator= (const String &);

    19:      friend ostream& operator<<

    20:         ( ostream& _theStream,String& theString);

    21:      friend ist.ream& operator>>

    22:         ( istream& _theStream,String& theString);

    23:      // Общие функции доступа

    24:      int GetLen()const { return itsLen; }

    25:      const char * GetString() const { return itsString; }

    26:      // static int ConstructorCount:

    27:

    28:   private:

    29:      String (int); // закрытый конструктор

    30:      char * itsString;

    31:      unsigned short itslen;

    32:

    33: };

    34:

    35: ostream& operator<<( ostream& theStream,String& theStnng)

    36: {

    37:    theStream << theString.GetString();

    38:    return theStream;

    39: }

    40:

    41: istream& operator>>( istream& theStream,String& theString)

    42: {

    43:    theStream >> theString.GetString();

    44:    return theStream;

    45: }

    46:

    47: int main()

    48: {

    49: StringtheString("npHBeT, мир.");

    50: cout << theString;

    51:     return 0;

    52: }

    5. Жучки: что неправильно в этой программе?

    1: #include <iostrearm.h>

    2:

    3: class Animal;

    4:

    5: void setValue(Animal& , int);

    6:

    7:

    8: class Animal

    9: {

    10:   public:

    11:      int GetWeight()const { return itsWeight; }

    12:      int GetAge() const { return itsAge; }

    13:   private:

    14:      int itsWeight;

    15:      int itsAge;

    16: };

    17:

    18: void setValue(Animal& theAnimal, int theWeight)

    19: {

    20:    friend class Animal;

    21:    theAnimalitsWeight = theWeight;

    22: }

    23:

    24: int main()

    25: {

    26:    Animal peppy;

    27:    setValue(peppy,5):

    28:    return 0;

    29: }

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

    6. Исправьте листинг, приведенный в упражнении 5, и откомпилируйте его.

    1: #include <iostream.h>

    2:

    3: class Animal;

    4:

    5: void setValue(Animal& , int);

    6:

    7:

    8:  class Animal

    9:  {

    10:    public:

    11:       friend void setValue(Animal&, int);

    12:       int GetWeight()const { return itsWeight; }

    13:       int GetAge() const { return itsAge; }

    14:    private:

    15:       int itsWeight;

    16:       int itsAge;

    17: };

    18:

    19: void setValue(Animal& theAnimal, int theWeight)

    20: {

    21:    theAnimal.itsWeight = theWeight;

    22: }

    23:

    24: int main()

    25: {

    26:    Animal peppy;

    27:    setValue(peppy,5);

    28:    return 0;

    29: }

    7. Жучки: что неправильно в этой программе?

    1: #include <iostream.h>

    2:

    3: class Animal;

    4:

    5: void setValue(Animal& , int):

    6: void setValue(Animal& , int.int);

    7:

    8: class Animal

    9: {

    10:   friend void setValue(Animal& ,int):

    11:   private:

    12:      mt itsWeight;

    13:      int itsAge;

    14: };

    15:

    16: void setValue(Animal& theAnimal, int theWeight)

    17: {

    18:    theAnimal.itsWeight = theWeight;

    19: }

    20:

    21:

    22: void setValue(Animal& theAnimal, int theWeight, int theAge)

    23: {

    24:    theAnimal.itsWeight = theWeight:

    25:    theAnimal.itsAge = theAge;

    26: }

    27:

    28: int main()

    29: {

    30:    Animal peppy;

    31:    setValue(peppy,5);

    32:    setValue(peppy,7,9);

    33:    return 0:

    34: }

    Функиия setValue(Animal& ,int) была объявлена дружественной, но перегруженная

    функция setValue(Animal& ,int,int) не была объявленадружественной.

    8. Исправьте листинг, приведенный в упражнении 7, и откомпилируйте его.

    1: #include <iostream.h>

    2:

    3: class Animal;

    4:

    5: void setValue(Animal& , int);

    6: void setValue(Animal& , int.int);

    7:

    8: class Animal

    9: {

    10:   friend void setValue(Animal& ,int);

    11:   friend void setValue(Animal& ,int.int): // изменение!

    12:   private:

    13:      int itsWeight;

    14:      int itsAge;

    15: };

    16:

    17: void setValue(Animal& theAnimal, int theWeight)

    18: {

    19:    theAnimal.itsWeight = theWeight;

    20: }

    21:

    22:

    23: void setValue(Animal& theAnimal, int theWeight, int theAge)

    24: {

    25:    theAnimal.itsWeight = theWeight;

    26:    theAnimal.itsAge = theAge;

    27: }

    28:

    29: int main()

    30: {

    31:    Animal peppy;

    32:    setValue(peppy.5);

    33:    setValue(peppy,7,9);

    34:    return 0;

    35: } 

    День 16

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

    1. Что такое оператор ввода и как он работает?

    Оператор ввода (>>) является членом объекта istream и используется для записи данных в переменные программы.

    2. Что такое оператор вывода и как он работает?

    Оператор вывода (<<)является членом объекта ostream и используется для записи данных в устройство вывода.

    3. Перечислите три варианта перегруженной функции cin.get() и укажите основные их отличия.

    Первый вариант функции-члена get() используется без параметров. Она возвращает значение считанного символа. При достижении кониа файла она возвратит EOF (end of file, т.е. конец файла).

    Второй вариант функции-члена cin.get() принимает в качестве параметра ссылку на символьную переменную. Этой переменной присваивается следующий символ в потоке ввода. Возвращаемым значением этой функции является объект iostream.

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

    4. Чем сin.read()отличается от сin.getline()?

    Функция cin.read() используется для чтения структур двоичных данных.

    Функция cin.getline() предназначена для чтения из буфера istream.

    5. Какая ширина устанавливается по умолчанию для вывода длинных целых чисел с помощью оператора вывода?

    Автоматически устанавливается ширина, достаточная для отображения всего числа.

    6. Какое значение возврашает оператор вывода?

    Ссылку на объект istream.

    7. Какой параметр принимается конструктором объекта ofstream?

    Имя открываемого файла.

    8. Чтоустанавливаетаргумент ios::ate?

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

     Упражнения

     1. Напишите программу, использующую четыре стандартных объекта класса iostream — cin, cout, cerr и clog.

    1: #include <iostream.h>

    2: int main()

    3: {

    4:    int x;

    5:    cout << "Enter а nurober; ";

    6:    cin >> x;

    7:    cout << "You entered: " << x << endl;

    8:    cerr << "Uh oh, this to cerr!" << endl;

    9:    clog << "Ouh oh, this to clog!" << endl;

    10:   return 0;

    11: }

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

    1: #include <iostream.h>

    2: int main()

    3: {

    4:    char name[80];

    5:    cout << "Enter your full name: ";

    6:    cin.getline(name,80);

    7:    cout << "\nYou entered: " << name << endl;

    8:    return 0;

    9: }

    3. Перепишите листинг 16.9, отказавшись от использования методов putback() и ignore().

    1: // Листинг 16.9. Измененный

    2: #include <iostream.h>

    3:

    4: int main()

    5: {

    6:    char ch;

    7:    cout << "enter а phrase: ";

    8:    while ( cin.get(ch) );

    9:    {

    10:      switch (ch)

    11:      {

    12:         case '!':

    13:            cout << '$';

    14:            break:

    15:         case '#':

    16:            break;

    17:         default:

    18:            cout << ch;

    19:            break;

    20:      }

    21:   }

    22:   return 0;

    23: }

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

    1: #include <fstream.h>

    2: enum BOOL { FALSE, TRUE };

    3:

    4: int main(int argc, char**argv) // возвращает 1 в случае ошибки

    5: {

    6:

    7:    if (argc != 2)

    8:    {

    9:       cout << "Usage: argv[0] <infile>\n";

    10:      return(1);

    11:   }

    12:

    13:   // открываем поток ввода

    14:   ifstream fin (argv[1],ios::binary);

    15:   if (!fin)

    16:   {

    17:      cout << "Unable to open " << argv[1] << " for reading.\n";

    18:      return(1);

    19:   }

    20:

    21:   char ch;

    22:   while ( fin.get(ch))

    23:      if ((ch > 32 && ch < 127) || ch == '\n' || ch = '\t')

    24:         cout << ch;

    25:   fin.close();

    26: }

    Напишите профамму, которая выведет заданные аргументы командной строки обратном порядке, отбросив имя программы.

    1: #include <fstream.h>

    2:

    3: int main(int argc, char**argv) // возвращает 1 в случае ошибки

    4: {

    5:    for (int ctr = argc-1; ctr ; ctr--)

    6:       cout << argv[ctr] << " ";

    7:    return 0;

    8: }

    День 17

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

     1. Можно ли использовать идентификаторы, объявленные в пространстве имен, без применения ключевого слова using?

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

    2. Назовите основные отличия между именованными и неименованными пространствами имен.

    Неименованные пространства имен компилятор рассматривает так, как если бы к ним по умолчанию был применен оператор using. Следовательно, имена в них можно использовать без идентификатора пространства имен. Чтобы сделать доступными имена обычных пространств имен, необходимо либо обращаться к ним с помощью идентификатора пространства имен, либо использовать оператор using или ключевое слово using в объявлении пространства имен.

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

    3. Что такое стандартное пространство имен std?

    Данное пространство определено в стандартной библиотеке C++ (C++ Standard Library) и содержит объявления всех классов и функций стандартной библиотеки.

     Упражнения

    1. Жучки: найдите ошибку в следующем коде:

    #include <iostream>

    int main()

    {

       cout << "Hello world!" << endl;

       return 0;

    }

    Стандартный файл заголовка C++ iostream объявляет объекты cout и endl в пространстве имен std. Их нельзя использовать вне стандартного пространства имен std без соответствующего идентификатора.

    2. Перечислите три способа устранения ошибки, найденной в коде упражнения 1.

    • using namespace std;

    • using std::cout;

      using std::endl;

    • std::cout << "Hello world!" << std::endl;

    День 18

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

    1. Какая разница между объектно-ориентированным и процедурным программированием?

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

    2. Каковы этапы объектно-ориентированного анализа и проектирования?

    а) разработка концепции;

    б) анализ;

    в) проектирование;

    r) реализация;

    д) тестирование;

    е) возврашение.

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

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

    Упражнения 

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

    Какие объекты и какие классы потребуются для имитации этой ситуации?

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

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

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

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

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

    Следовательно, в модель перекрестка необходимо включить такие классы:

    class Entity; // клиент перекрестка


    // базовый класс для всех автомобилей, грузовиков, велосипедов и спецмашин

    class Vehicle : Entity ...;

    // базовый класс для пешеходов

    class Pedestrian : Entity ...;

    class C.;r : public Vehicle...;

    class Truck . public Vehicle...;

    class Motorcycle : public Vehicle...;

    class Bicycle : public Vehicle...;

    class Emergency_Vehicle : public Vehicle...;

    // класс списка автомобилей и людей, ожидающих движения

    class Intersection;

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

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

    А кроме того, есть еше велосипедисты, которые ведут себя то как пешеходы, то как водители.

    Как эти соображения изменят модель?

    Вероятно, целесообразно начать с создания производных объектов, которые моделируют разновидности рассмотренных выше объектов:

    class Local_Car : public Car...;

    class Tourist_Car : public Car...;

    class Taxi : public Car...;

    class Local_Pedestrian : public Pedestrian...;

    classTourist_Pedestrian : public Pedestrian...;

    class Local_Bicycle . public Bicycle...;

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

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

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

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

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

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

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

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

    Приведем прототипы перечисленных выше классов.

    class Calendar_Class; // ссылка на класс

    class Meeting, // ссылка на класс

    class Configuration

    {

       public:

          Configuration();

          ~Configuration();

          Meeting Schedule( ListOfPerson&, Oelta Time duration );

          Meeting Schedule( ListOfPerson&, Delta Time duration, Time );

          Meeting Schedule( ListOfPerson&, Delta Time duration, Room );

          ListOfPerson& People(); // открытые методы доступа

          ListOfRoom& Rooms(); // открытые методу доступа

       protected:

          ListOfRoom rooms;

          ListOfPerson people;

    };

    typedef long Room_ID;

    class Room

    {

       public:

          Room( String name, Room_ID id, int capacity. String directions description = "" );

          ~Room();

          Calendar_Class Calendar();

       protected:

          Calendar_Class calendar;

          int capacity;

          Room_ID id;

          String name;

          String directions; // где этот гостиничный номер?

          String description;

    };

    typedef long Person_ID;

    class Person

    {

       public:

          Person ( String name, Person_ID id );

          ~Person();

          Calendar_Class Calendar(); // место доступа для добавления встреч

       protected:

          Calendar_Class calendar;

          Person_ID id;

          String name;

    };

    class Calendar_Class

    {

       public:

          Calendar_Class();

          ~Calendar_Class();

          void Add( const Meeting& ); // добавляем встречу в календарь

          void Delete( const Meeting& );

          Meeting* Lookup( Time ); // проверяем, не назначена ли уже встреча на это чис-

          Block( Time, Duration, String reason = "" );

          // резервируем время...

       protected:

          OrderedListOfMeeting meetings;

    };

    class Meeting

    {

       public:

          Meeting( ListOfPerson&, Room room, Time when, Duration duration, String purpose = "" );

          ~Meeting();

       protected:

          ListOfPerson people;

          Room room;

          Time when;

          Duration duration;

          String purpose;

    };

    День 19

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

    1. Какова разница между шаблоном и макросом?

    Шаблоны являются средствами программирования языка C++, поддерживающими контроль за соответствием типов данных. Макросы выполняются препроцессором и не обеспечивают безопасности работы с типами.

    2. В чем состоит отличие параметра шаблона от параметра функции?

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

    3. Чем отличается обычный дружественный шаблонный класс от дружественного шаблонного класса, специализированного по типу?

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

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

    Да. Создайте для конкретного экземпляра шаблона функцию, специализированную по типу. Чтобы изменить выполнение, например, для массивов целых чисел, помимо функции Array<t>::SomeFunction(), создайте также функцию Array<int>::SomeFunction().

    5. Сколько создается статических переменных-членов, если поместить один статический член в определение класса шаблона?

    По одной для каждого экземпляра класса.

    6. Что представляют собой итераторы?

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

    7. Что такое объект функции?

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

    Упражнения 

    1. Создайте шаблон на основе данного класса List:

    class List

    {

       public:

          List():head(0),tail(0), tbeCount(0) { }

          virtual ~List();

          void insert( int value );

          void append( int value );

          int is_present( int value ) const;

          int is_empty() const { return head == 0: }

          int count() const { return theCount; }

       private:

          class ListCell

          {

             public:

                ListCell(int value, ListCell *cell = ):val(value),next(cell){ }

                int val;

                ListCell *next;

          };

          ListCell *head;

          ListCell *tail;

          int theCount;

    };

    Вот один из способов выполнения этого шаблона:

    template <class Type>

    class List

    {

       public:

          List():head(0),tail(0),theCount(0) { }

          virtual ~List();

          void insert( Type value );

          void append( Type value );

          int is_present( Type value ) const;

          int is_empty() const { return head == 0; }

          int count() const { return theCount; }

       private:

          class ListCell

          {

             public:

                ListCell(Type value, ListCell *cell = O):val(value),next(cell) { }

                Type val;

                ListCell *next;

          };

          ListCell *head;

          ListCell *tail;

          int theCount;

    };

    2. Напишите выполнение обычной (не шаблонной) версии класса List.

    void List::insert(int value)

    {

       ListCell *pt = new ListCell( value, head );

       assert (pt ! = 0);

       // эта строка добавляется для обработки хвостового узла

       if ( head -= 0 ) tail = pt,

          head = pt;

       theCount++;

    }

    void List::append( int value )

    {

       ListCell *pt = new ListCell( value );

       if ( head — 0 )

          head = pt;

       else

          tail->next = pt:

       tail = pt;

       theCount++;

    }

    int List::is_present( int value ) const

    {

       if ( head == 0 )

          return 0;

       if ( head->val — value || tail->val == value )

          return 1;

       ListCell *pt = head->next;

       for (; pt != tail; pt = pt->next)

          if ( pt->val == value )

             return 1;

       return 0;

    }

    3. Напишите шаблонный вариант выполнения.

    template <class Type>

    List<Type>::~List()

    {

       ListCell *pt = head;

       while ( pt )

       {

          ListCell *tmp = pt;

          pt = pt->next;

          delete tmp;

       }

       head = tail = 0;

    }

    template <class Type>

    void List<Type>::insert(Type value)

    {

       ListCell *pt = new ListCell( value, head );

       assert (pt ! = 0);

       // эта строка добавляется для обработки хвостового узла

       if ( head == 0 )

          tail = pt;

       head = pt;

       theCount++;

    }

    template <class Type>

    void List<Type>::append( Type value )

    {

       ListCell *pt = new ListCell( value );

       if ( head == 0 )

          head = pt;

       else

          tail->next = pt;

       tail = pt;

       theCount++;

    }

    template <class Type>

    int List<Type>::is_present( Type value ) const

    {

       if ( head == 0 )

          return 0;

       if ( head->val == value || tail->val == value )

          return 1;

       ListCell *pt = head->next;

       for (; pt != tail; pt = pt->next)

          if ( pt->val — value )

             return 1;

       return 0;

    }

    4. Объявите три списка объектов: типа Strings, типа Cat и типа int.

    List<String> string_list;

    List<Cat> Cat_List;

    List<int> int_List;

    5. Жучки: что неправильно в приведенном ниже программном коде? (Предположите, что определяется шаблон класса List, а Cat — это класс, определенный выше в данной книге.)

    List<Cat> Cat_List;

    Cat Felix;

    CatList.append( Felix );

    cout << "Felix is " << ( Cat_List.is_present( Felix ) ) ? "" : "not " << "present\n";

    ПОДСКАЗКА (поскольку задание не из самых легких): подумайте, чем тип Cat отличается от типа int.

    В классе Cat не определен оператор operator==. Все операции, в которых сравниваются значения членов класса iist, таких как is_present, будут вызывать ошибку компиляции. Для уменьшения вероятности возникновения таких ошибок перед объявлением шаблона поместите обширный комментарий, в котором должно быть указано, какие операторы следует определить в классе для успешного выполнения всех его методов.

    6. Объявите дружественный оператор operator== для класса List.

    friend int operator==( const Type& lhs, const Type& rhs );

    7. Напишите выполнение дружественного оператора operator== для класса List.

    template <class Type>

    int List<Type>::operator==( const Type& lhs, const Type& rhs )

    {

       // сначала сравниваем размеры списков

       if ( lhs.theCount != rhs.theCount )

          return 0; // списки различны

       ListCell *lh = lhs.head;

       ListCell *rh = rhs.head;

       for(; lh != 0; lh = lh.next. rh = rh.next )

          if ( lh.value != rh.value )

             return 0;

       return 1; // если они не различны, то совпадают

    }

    8. Грешитли оператор operator== той же проблемой, которая существует в упражнении 5?

    Да. Поскольку сравнение массива включает сравнение элементов, то для элементов также должен быть определен оператор operator!=.

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

    // шаблон swap:

    // должен иметь оператор присваивания и конструктор-копировщик, определенные для

    класса Туре,

    template <class Type>

    void swap( Type& lhs, Type& rhs)

    {

       Type temp( lhs );

       lhs = rhs;

       rhs = temp;

    }

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

    #include <list>

    template<class T, class A>

    void ShowList(const iist<T, А>& aList); // отображаем свойства вектора

    typedef list<Student> SchoolClass;

    int main()

    {

       Student Harry("Harry". 18);

       Student Sally("Sally", 15);

       Student Bill( "Bill", 17);

       Student Peter("Peter", 16);

       SchoolClass GrowingClass;

       GrowingClass.push_back(Harry);

       GrowingClass.push_back(Sally);

       GrowingClass.push_back(Bill);

       GrowingClass.push_back(Peter);

       ShowList(GrowingClass);

       cout << "Один год спустя:\n";

       for (SchoolClass::iterator i = GrowingClass.begin(); i != GrowingClass.end(); ++i)

          i->SetAge(i->GetAge() + 1);

       ShowList(GrowingClass);

       return 0;

    }

    //

    // Отображаем свойства списка

    //

    template<class T, class A>

    void ShowList(const list<T, А>& aList)

    {

       for (list<T, A>::const_iterator ci = aList.begin(); ci != aList.end(); ++ci)

          cout << *ci << "\n";

       cout << endl;

    }

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

    #include <algorithm>

    template<class T>

    class Print

    {

       public:

          void operator()(const T& t)

          {

             cout << t << "\n";

          }

    }

    template<class T, class A>

    void ShowList(const list<T, A>& aList)

    {

       Print<Student> PrintStudent;

       for_each(aList.begin(), aList.end().PrintStudent):

         cout << endl;

    }

    День 20

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

    1. Что такое исключение?

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

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

    2. Для чего нужен блок try?

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

    3. Для чего используется оператор catch?

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

    4. Какую информацию может содержать исключение?

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

    5. Когда создается объект исключения?

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

    6. Следует ли передавать исключения как значения или как ссылки?

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

    7. Будет ли оператор catch перехватывать производные исключения, если он настроен на базовый класс исключения?

    Да, если исключение будет передано как ссылка.

    8. Если используются два оператора catch, один из которых настроен на базовое сообщение, а второй ~ на производное, то в каком порядке их следует расположить?

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

    9. Что означает оператор catch(...)?

    Оператор catch(...) будет перехватывать все исключения любого типа.

    10. Что такое точка останова?

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

    Упражнения 

     1. Запишите блок try и оператор catch для отслеживания и обработки простого исключения.

    #include <iostream.h>

    class OutOfMemory {};

    int main()

    {

       try

       {

          int *myInt = new int;

          if (myInt == 0)

             throw OutOfMemory();

       }

       catch (OutOfMemory)

       {

          cout << "Unable to allocate memory!\n";

       }

       return 0;

    }

    2. Добавьте в исключение, полученное в упражнении 1, переменную-член и метод доступа и используйте их в блоке оператора catch.

    #include <iostream.h>

    #include <stdio.h>

    #include <string.h>

    class OutOfMemory;

    {

       public:

          OutOfMemory(char *):

          char>> GetString() { return itsString; }

       private:

          char>> itsString;

    };

    OutOfMemory::OutOfMemory(char * theType)

    {

       itsString = new char[80];

       char warning[] = "Out Of Memory! Can't allocate room for: ";

       strncpy(itsString, warning,60);

       strncat(itsString,theType,19);

    }

    int main()

    {

       try

       {

         int *myInt = new int;

         if (myInt == 0)

            throw OutOfMemory("int");

       }

       catch (OutOfMemory& t:heException)

       {

          cout << theException.GetString();

       }

       return 0;

    }

    3. Унаследуйте новое исключение от исключения, полученного в упражнении 2. Измените блок оператора catch таким образом, чтобы в нем происходила обработка как производного, так и базового исключений.

    1: #include <iostream.h>

    2:

    3: // Абстрактный тип исключений

    4: class Exception

    5: {

    6:    public:

    7:       Exception(){}

    8:       virtual ~Exceptiori(){}

    9:       virtual void PrintError() = 0;

    10: };

    11:

    12: // Производный класс для обработки проблем памяти

    13: // Обратите внимание: в этом классе не производится выделение памяти

    14: class OutOfMemory : public Exception

    15: {

    16:    public:

    17:       OutOfMemory(){}

    18:       ~OutOfMemory(){}

    19:       virtual void PrintError();

    20:    private:

    21: };

    22:

    23: void OutOfMemory::PrintError()

    24: {

    25:    cout << "Нет памяти !!\n";

    26: }

    27:

    28: // Производный класс для обработки ввода неверных чисел

    29: class RangeError : public Exception

    30: {

    31:    public:

    32:       RangeError(unsigned long number){badNumber = number:}

    33:       ~RangeError(){}

    34:       virtual void PrintError();

    35:       virtual unsigned long GetNumber() { return badNumber; }

    36:       virtual void SetNumber( unsigned long number) {badNumber = number;}

    37:    private:

    38:       unsigned long badNumber;

    39: };

    40:

    41: void RangeError::PrintError()

    42: {

    43:    cout << "Number out of range. You used " << GetNumber() << "N\n";

    44: }

    45:

    46: void MyFunction(); // прототип функции

    47:

    48: int main()

    49: {

    50:    try

    51:    {

    52:       MyFunction();

    53:    }

    54:    // Чтобы использовать только один оператор catch,

    55:    // примените для этого виртуальные функции

    56:    catch (Exceptions theException)

    57:    {

    58:       theException.PrintError();

    59:    }

    60:    return 0;

    61: }

    62:

    63: void MyFunction()

    64: {

    65:    unsigned int *myInt = new unsigned int;

    66:    long testNumber;

    67:    if (myInt == 0)

    68:       throw 0ut0fMemory();

    69:    cout << "Enter an int: ";

    70:    cin >> testNumber;

    71:    // эту проверку лучше заменить серией

    72:    // проверок, чтобы выявить неверные данные, введенные пользователем

    73:    if (testNumber > 3768 || testNumber < 0)

    74:       throw RangeError(testNumber);

    75:

    76:    *mylnt = testNumber;

    77:    cout << "Ok. myInt: " << *myInt;

    78:    delete myInt;

    79: }

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

    1: #include <iostream.h>

    2:

    3: // Абстрактный тип исключений

    4: class Exception

    5: {

    6:    public:;

    7:       Exception(){ }

    8:       virtual ~Exception(){}

    9:       virtual void PrintError() = 0;

    10: };

    11:

    12: // Производный класс для обработки проблем памяти

    13: // Обратите внимание: в этом классе не производится выделение памяти!

    14: class OutOfMemory : public Exception

    15: {

    16:    public:

    17:       OutOfMemory(){}

    18:       ~OutOfMemory(){}

    19:       virtual void PrintError();

    20:    private:

    21: };

    22:

    23: void OutOfMemory::PrintError()

    24: {

    25:    cout << "Нет памяти!!\n";

    26: }

    27:

    28: // Производный класс для обработки ввода неверных чисел

    29: class RangeError : public Exception

    30: {

    31:    public:

    32:       RangeError(unsigned long number){badNumber = number;}

    33:       ~RangeError(){ }

    34:       virtual void PrintError();

    35:       virtual unsigned long GetNumber() { return badNumber; }

    36:       virtual void SetNumber(unsigned long number) {badNumber = number;}

    37:    private:

    38:       unsigned long badNumber;

    39: };

    40:

    41: void RangeError::PrintError()

    42: {

    43:    cout << " Number out of range. You used " << GetNumber() << "!!\n";

    44: }

    45:

    46: // прототипы функций

    47: void MyFunction();

    46: unsigned int * FunctionTwo();

    49: void FunctionThree(unsigned int *);

    50:

    51: int main()

    52: {

    53:    try

    54:    {

    55:       MyFunction();

    56:    }

    57:    // Чтобы использовать только один оператор catch,

    58:    // примените для этого виртуальные функции.

    59:    catch (Exception& theException)

    60:    {

    61:       theException.PrintError();

    62:    }

    63:    return 0;

    64: }

    65:

    66: unsigned int >> FunctionTwo()

    67: {

    68:    unsigned int <<royInt = new unsigned int;

    69:    if (myInt == 0)

    70:       throw OutOfMemory();

    71:    return myInt;

    72: }

    73:

    74: void MyFunction()

    75: {

    76:    unsigned int *myInt = FunctionTwo{ };

    77:

    78:    FunctionThree(myInt);

    79:    cout << "0k. myInt: " << *myInt;

    80:    delete myInt;

    81: }

    82:

    83: void FunctionThree(unsigned int *ptr)

    84: {

    85:    long testNumber;

    86:    cout << "Enter an int: ";

    87:    cin >> testNumber;

    88:    // эту проверку лучше заменить серией

    89:    // проверок, чтобы выявить неверные данные, введенные пользователем

    90:    if (testNumber > 3768 || testNumber < 0)

    91:       throw RangeError(testNumber);

    92:    *ptr = testNumber;

    93: }

    5. Жучки: что неправильно в следуюшем коде?

    #include "string.h" // класс строк

    class xOutOfMemory

    {

       public:

          xOutOfMemory( const String& where ) : location( where ){ }

          ~xOutOfMemory(){ }

          virtual String where(){ return location };

       private:

          String location;

    }

    main()

    {

       try

       {

          char *var = new char;

          if ( var == 0 )

             throw xOutOfMemory();

       }

       catch( xOutOfMemory& theException )

       {

          cout << "Out of memory at " << theException.location() << "\n";

       }

    }

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

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

    День 21

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

    1. Для чего нужны средства зашиты от повторного включения?

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

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

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

    3. Каковаразница между директивами #define debug 0 и #undef debug?

    Директива #define debug 0 определяет лексему debug и присваивает ей 0 (нуль). Поэтому везде, где встретится лексема debug, она будет заменена символом 0. Директива #undef debug удаляет любое определение лексемы debug, в результате чего любой экземпляр лексемы debug, обнаруженный в файле, будет оставаться неизменным.

    4. Что делает оператор дополнения до единицы?

    Инвертирует значение каждого бита переменной.

    5. Чем отличается оператор побитового ИЛИ от оператора исключающего побитового ИЛИ?

    Оператор побитового ИЛИ возвращает значение TRUE (ИСТИНА), если установлен один из битов (или оба сразу). Оператор исключающего ИЛИ возвращает TRUE только в том случае, если данный бит установлен лишь в одном операнде, но не в обоих сразу.

    6. Какова разница между операторами & и &&?

    Оператор & — это побитовое И, а && — это логическое И.

    7. Какова разница между операторами | и || ?

    Оператор | — это побитовое ИЛИ, а || — этологическое ИЛИ.

     Упражнения

    1. Создайте защиту от повторного включения файла заголовка STRING.H.

    #ifndef STRING_H

    #define STRING_H

    ...

    #endif

    2. Напишите макрос assert(), который

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

    • будет печатать сообщение (без имени файла и номера строки), если уровень отладки равен 1;

    • не будет ничего делать, если уровень отладки равен 0.

    1: #include <iostream.h>

    2:

    3: #ifndef DEBUG

    4: #define ASSERT(x)

    5: #elif DEBUG — 1

    6: #define ASSERT(x)

    7: if (! (x))

    S: {

    9:    cout << "ERROR!! Assert " << #x << " failed\n";

    10: }

    11: #elif DEBUG == 2

    12: #define ASSERT(x)

    13: if (! (x) )

    14: {

    15:    cout << " ERROR!! Assert " << #x << " failed\n";

    16:    cout << " on line " << __LINE__ << "\n";

    17:    cout << " in file " << __LINE__ << "\n";

    18: }

    19: #endif

    3. Напишите макрос DPrint, который проверяет, определена ли лексема DEBUG, и, если да, выводит значение, передаваемое как параметр.

    #ifndef DEBUG:

    #define DPRINT(string)

    #else

    #define DPRINT(STRIN6) cout << #STRING:

    #endif

    4. Напишите программу, которая складывает два числа без использования операции сложения (+). Подсказка: используйте побитовые операторы!

    Если рассмотреть сложение двух битов, то можно заметить, что ответ будет содержать два бита: бит результата и бит переноса. Таким образом, при сложении двух единиц в двоичной системе бит результата будет равен нулю, а бит переноса — единице. Если сложить два двоичных числа 101 и 001, получим следующие результаты:

    101 // 5

    001 // 1

    110 // 6

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

    Левый бит lhs Правый бит rhs Перенос Результат

    0 0 0 0

    0 1 0 1

    1 0 0 1

    1 1 1 0

    Рассмотрим логику бита переноса. Если оба суммируемых бита (lhs и rhs) равны 0 или хотя бы один из них равен 0, бит переноса будет равен 0. И только если оба бита равны 1, бит переноса будет равен 1. Такая ситуация в точности совпадает с определением побитового оператора И (&).

    Если подобным образом рассмотреть логику бита результата, то окажется, что она совпадает с выполнением оператора побитового исключающего ИЛИ (^): если любой из суммируемых битов (но не оба сразу) равен I, бит результата равен 1, в противном случае — 0.

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

    #include <iostream.h>

    unsigned int add( unsigned int lhs, unsigned lnt rhs )

    {

       unsigned int result, carry;

       while ( 1 )

       {

          result = lhs ^ rhs;

          carry = lhs & rhs;

          if ( carry == 0 )

             break;

          lhs = carry << 1;

          rhs = result;

       };

       return result;

    }

    int main()

    {

       unsigned long a, b;

       for (;;)

       {

          cout << "Enter two numbers. (0 0 to stop): ";

          cin << a << b:

          if (!a && !b)

             break;

          cout << a << " + " << b << " = " << add(a,b) << endl;

       }

       return 0;

    }

    В качестве альтернативного варианта эту проблему можно решить с помошью рекурсии:

    #include <iostream.h>

    unsigned int add( unsigned int lhs, unsigned int rhs )

    {

       unsignod int carry = lhs & rhs;

       unsigned int result = lhs * rhs;

       if ( carry )

          return add( result, carry << 1 );

       else

          return result;

    }

    int main()

    {

       unsigned long a, b;

       for (;;)

       {

          cout << "Enter two numbers. (0 0 to stop): **;

          cin << a << b;

          if (!a && !b)

             break;

          cout << a << " + " << b << " = " << add(a,b) << endl;

       }

       return 0;

    }







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх