понедельник, 6 января 2014 г.

Решения упражнений главы 4 "Типы и объявления" из книги Б.Страуструпа "Язык программирования C++"

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

Не малую путаницу в книги Б.Страуструпа вносят и "издержки" перевода, которые с легкостью могут поставить в тупик начинающего изучать язык C++. Отдельного упоминания заслуживают упражнения для самостоятельного выполнения, решая которые, приходится "переосмысливать" только что прочитанную главу :-).

Я не отношу себя к знатокам C++, скорее просто к "любителям", но, тем не менее, постараюсь в ряде заметок дать ответы и решения на задания одной из самых фундаментальных книг автора - "Язык программирования C++" (обращаю внимание, что для этого мною будет использоваться книга 3-го "специального" издания издательства Бином, ISBN 978-5-7989-0425-9, в версии 2011 года. Хотя в английской версии уже существует и четвертное издание, в большей степени затрагивающее "новое" в C++ привнесенное стандартом C++11. Что это за новшества, можно кратко почерпнуть из этого интервью).

Я категорично "ЗА" самостоятельное изучение и поиск решения на задания, поэтому воспринимаю свою затею как дополнительный стимул таки "добить" эту книгу :D, но может кому поможет лучше разобраться в изучаемом материале...

Итак, упражнения Главы 4 "Типы и объявления".

Упражнение 1.


Запустить программу "Hello, world!" (§3.2). Если эта программа не компилируются, обратитесь к §B.3.1.

Задание не представляет собой ничего сложного - обращаемся к разделу §3.2 и просто набираем код "автора" в своей любимой IDE, а затем его компилируем.
#include <iostream>

int main()
{
   std::cout << "Hello, world!\n";
}
У кого могут быть проблемы с компиляцией? Собственно те, у кого старые .... даже старинные компиляторы C++. С учетом того, что третья редакция книги вышла в свет в 2000 году, я, полагаю, уже не найдутся такие индивиды, кто изучает язык C++ с использованием компиляторов того времени ... Тем не менее, вопрос весьма важный и, так же как и автор книги, настоятельно рекомендую изучить материал в приложении §B.3.1.


Упражнение 2.

Для каждого объявления из §4.9 выполните следующее: если объявление не является определением, переделайте его в определение; если же объявление является определением, напишите для него соответствующее объявление, не являющееся одновременно и определением.

В соответствии с заданием, имеем:
char ch;
string s;
int count=1;
const double pi=3.1415926535897932385;
extern int error_number;
const char* name = "Njal";
const char* season[] = {"spring", "summer", "fall", "winter"};

struct Date{int d, m, y};
int day(Date* p) {return p->d;}
double sqrt(double);
template<class T> T abs(T a) {return a<0 ? -a : a;}

typedef complex<short> Point;
struct User;
enum Beer {Carlsberg, Tuborg, Thor};
namespace NS {int a};
Как следует из текста самого раздела 4.9, определениями не являются только:
extern int error_number;
double sqrt(double);
struct User;
typedef complex<short> Point; //отсутствует в русской версии
И если с первыми тремя примерами тут более-менее все понятно, то вот с typedef complex<short> Point не все так гладко. Мало того, что в книге автор использовал двусмысленные выражения о "природе" typedef, так еще и переводчики упустили эту строчку по тексту, тем самым поставив в тупик часть читателей. Вопрос, чем же является typedef - объявлением или определением - поднимался и на http://stackoverflow.com. Если вы не владеете достаточными знаниями английского языка, подведу краткий итог - typedef, согласно стандарта, является объявлением. Что подтверждается правилом изложенном в самой книге: "В программах на C++ для каждой именованной сущности должно быть ровно одно определение. Объявлений же может быть сколько угодно". Т.е. ничего не мешает написать в тесте программы:
typedef complex<short> Point;
typedef complex<short> Point;
typedef complex<short> Point;
typedef complex<short> Point;
и при этом компилятор не выдаст никакой ошибки.
С учетом того, что typedef задает лишь новое имя (синоним) для типа данных, а не создает какие-либо переменные или сами новые типы, то выполнить условия задания (переделав объявление в определение) представляется лишь созданием нового типа:
class Point: public complex<short>{/*...*/};
либо созданием экземпляра Point:
Point p;
Пожалуй, это был единственный сложный момент в данном задании, остальное все делается проще, по уже существующей аналогии. Итак, оставшиеся объявление, переделанные в определения:
int error_number;
double sqrt(double) {/*...*/};
struct User {string name, address;};

И определения, переделанные в объявления:
extern char ch;
extern string s;
extern int count;
extern const double pi;
extern const char* name;
extern const char* season[];

struct Date;
int day(Date* p);
template<class T> T abs(T a);
Чего тут не хватает? Праааавильно, не хватает "объявлений" для:
enum Beer {Carlsberg, Tuborg, Thor};
namespace NS {int a};
Реализовать их на C++ стандарта 2003 года не представляется возможным. Лишь в стандарте C++11 появилась возможность предварительного объявления перечислений, например:
enum Beer : unsigned int;
Пространства имен же, по сути, можно только определить. Но не удивлюсь, если автор в качестве решения рассматривает вариант объявления переменной а:
namespace NS{extern int a;}


Упражнение 3.

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

Комментарии излишни. Листинг:
#include <iostream>

using namespace std;

enum Absinthe{HYPNO, TUNEL, XENTA};
enum Colors{GREEN, YELLOW=10000, BLACK=9};

int main()
{
    cout << "char - " << sizeof(char) << endl;
    cout << "bool - " << sizeof(bool) << endl;
    cout << "int - " << sizeof(int) << endl;
    cout << "double - " << sizeof(double) << endl;
    cout << "int *p - " << sizeof(int *) << endl;
    cout << "void *p - " << sizeof(void *) << endl;
    cout << "enum Absinthe - " << sizeof(Absinthe) << endl;
    cout << "enum Colors - " << sizeof(Colors) << endl;
    return 0;
}


Упражнение 4.

Напишите программу, которая выводит буквы 'a' - 'z' и цифры '0' - '9' и их десятичные коды. Повторите все для иных символов, имеющих зрительные образы. Выведите числовые коды в шестнадцатеричном виде.

Листинг:
#include <iostream>

using namespace std;

int main()
{
    //выводим символы a-z и их десятичные коды
    cout << "a - z: " << endl;
    for (char ch = 'a'; ch <= 'z'; ch++)
        {
        cout << ch << " - " << int(ch) << "; ";
    }
    //выводим символы 0-9 и их десятичные коды
    cout << endl << endl << "0 - 9: " << endl;
    for (char ch = '0'; ch <= '9'; ch++)
        {
        cout << ch << " - " << int(ch) << "; ";
    }
    //выводим символы с ASCII-кодами 128-138 и их десятичные коды
    cout << endl << endl << "ASCII 128 - 138: " << endl;
    for (int i = 128; i <= 138; i++)
        {
        cout << char(i) << " - " << i << "; ";
    }
    //выводим символы с ASCII-кодами 128-138 и их hex-коды
    cout << endl << endl << "ASCII 128 - 138 in HEX: " << endl;
    for (int i = 128; i <= 138; i++)
        {
        cout << char(i) << " - " << hex << i << "; ";
    }
    return 0;
}
На что в этом задании стоит обратить внимание - на вывод числовых значений в шестнадцатеричном формате. В книге, до этого параграфа включительно, этот момент не раскрыт и может оказаться затруднительным тем, кто изучает C++ по ней. Чтобы изменить представление числа с десятичной формы (по-умолчанию) на шестнадцатеричную - потоку вывода (cout) передается манипулятор hex. С момента его передачи потоку и до закрытия этого потока (либо ввода иного манипулятора задающего разрядность чисел) все числа будут представляться в шестнадцатеричной форме. О том, какие еще существуют манипуляторы потоков и что они есть на самом деле, вы можете почитать тут.


Упражнение 5.

Каковы на вашей машине минимальные и максимальные значения для следующих типов: char, short, int, long, float, double, long double и unsigned?

Для выполнения этого задания необходимо обратиться к параграфам §4.6 и §22.2.
Листинг:
#include <iostream>
#include <limits>

using namespace std;

int main()
{
    cout << "CHAR min: " << (int)numeric_limits<char>::min() <<
            "; max: " << (int)numeric_limits<char>::max() << ";"
         << endl;
    cout << "SHORT min: " << numeric_limits<short>::min() <<
            "; max: " << numeric_limits<short>::max() << ";"
         << endl;
    cout << "INT min: " << numeric_limits<int>::min() <<
            "; max: " << numeric_limits<int>::max() << ";"
         << endl;
    cout << "LONG min: " << numeric_limits<long>::min() <<
            "; max: " << numeric_limits<long>::max() << ";"
         << endl;
    cout << "FLOAT min: " << numeric_limits<float>::min() <<
            "; max: " << numeric_limits<float>::max() << ";"
         << endl;
    cout << "DOUBLE min: " << numeric_limits<double>::min() <<
            "; max: " << numeric_limits<double>::max() << ";"
         << endl;
    cout << "LONG DOUBLE min: " <<
            numeric_limits<long double>::min() << "; max: " <<
            numeric_limits<long double>::max() << ";" << endl;
    cout << "UNSIGNED min: " << numeric_limits<unsigned>::min()
         << "; max: " << numeric_limits<unsigned>::max() << ";"
         << endl;
    return 0;
}


Упражнение 6.

Какова максимальная длина локальных имен на вашей машине? Какова максимальная длина для внешних имен на вашей машине? Есть ли ограничения на символы, которые можно использовать в именах?

Как следует из параграфа §4.9.3 книги сам язык программирования C++ не накладывает ограничения на длину имен. Не верить этому - нет оснований. Ограничения может накладывать компилятор, линкер и т.д. Поэтому все зависит от того, какой компилятор используется для разработки и в какой операционной системе. В общем случае, в среде MS Windows компиляторы ограничивают длину имен в 2048 символов (например, MS Visual C++, Intel C++ Compiler). Хотя для внешних имен, тот же Intel C++ Compiler, может использовать длину до 64 Кб. А вот по результатам тестов одного комрада, компилятор g++ и вовсе не лимитирует длину имен. Т.о., чтобы ответить на этот вопрос, необходимо обратиться к документации своего компилятора.
Относительно ограничений используемых символов в именах: по общему правилу разрешается использовать буквы латинского алфавита, цифры и знак подчеркивания. При этом все пользовательские имена должны начинаться с буквы. Разрешается начинать имена и со знака подчеркивания (он так же считается "буквой"), но делать этого строго не рекомендуется (во избежание конфликтов с "системными" именами).


Упражнение 7.

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

Для выполнения этого задания необходимо будет обратиться к параграфу §4.6 книги и к уже выполненным упражнениям 3 и 5. Автор не уточняет про использование знаковых/без знаковых типов, поэтому используем "классическую" знаковую модель типов:
bool->char->short->int->float->double
и, конечно, любимая реализация:
bool->char->int->double
Необходимо отметить, что это справедливо для моего компилятора (MinGW g++ 4.8), но не думаю, что будет отлично от любого другого современного компилятора C++.


P/s все вышеизложенное является лишь моим субъективным видением решения поставленных задач и не претендует на "истину в последней инстанции". Если Вы имеете какие-либо замечания, дополнения, etc. пишите в комментариях :-).

3 комментария:

  1. ) хватило только на упражнения от 4-ой главы?

    ОтветитьУдалить
  2. Решение упражнения 4 для случая вывода кодов символов в шестнадцатеричном виде.

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

    Решение же для начинающего, основываясь на материале усвоенном к этому моменту, может выглядеть например так:

    const char hex_char[]={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    cout<<"\nSymbols:\n";
    for (unsigned char c=128; c<=254; c++)
    cout<<c<<" - "<<hex_char[c/16]<<hex_char[c%16]<<"\n";

    ОтветитьУдалить
    Ответы
    1. Вот где легко набыдлокодить, тебе не стыдно за такое решение?

      Удалить