Здавалка
Главная | Обратная связь

Создание безымянных пространств имен.



Давайте теперь разберёмся, каким образом можно гарантировать корректность имени, присвоенного пространству, ведь может получиться, что неким пространствам имён были присвоены одинаковые имена. Как избежать подобного конфликта, спросите Вы?! Как это ни странно - создав пространство имен без имени!!! Это связано с тем, что если создать безымянное пространство, С++ автоматически даст ему уникальное имя, которое мы с Вами, естественно, не увидим. Однако, то что имя будет уникальным - факт неоспоримый. Синтаксис объявления безымянного пространства имен следующий:

namespace { члены; }

В такой ситуации С++ позволяет обращаться к компонентам безымянного пространства через операцию :: без указания имени. Вам нет необходимости объявлять это пространство (да и возможности нет, так как имени Вы не знаете). Неявное объявление using namespace добавляется автоматически после объявления самого пространства имен. Так что, в итоге, код построится таким образом:

namespace a // предположительно имя полученное автоматически (неявно) { члены; } using namespace a;// автоматически прописавшееся объявление

Рассмотрим пример работы с безымянным пространством имен:

#include <iostream> using namespace std; namespace { void func(){cout<<"::func"<<"\n";} } void main() { ::func(); }

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

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Экзаменационные задания.

Для реализации всех заданий использовать ООП.

Реализация карточной игры - "Покер".

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

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

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Домашнее задание

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

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

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Урок №34.

  • Преобразование типов в стиле С++.
  • Стандартная библиотека шаблонов (STL) и её основные понятия (контейнер, итератор, алгоритм, функтор, предикат, аллокатор).
  • Класс auto_ptr.
  • Анализ и использование класса string.
  • Детальный анализ итератора.
  • Домашнее задание.
Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Преобразование типов в стиле С++.

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

(тип) выражение Например, double d;d=(double) 10/3;

Операторы приведения типа в языке С++.

Кроме стандартной формы в стиле С, язык С++ поддерживает дополнительные операторы приведения типа:

Const_cast

const_cast<тип> (объект)

Dynamic_cast

dynamic_cast<тип> (объект)

Reinterpret_cast

reinterpret_cast<тип>(объект)

Static_cast

static_cast<тип> (объект)

Рассмотрим данные конструкции более детально:

const_cast используется для явного переопределения модификаторов const и/или volatile. Новый тип должен совпадать с исходным типом, за исключением изменения его атрибутов const или volatile. Чаще всего оператор const_cast используется для снятия атрибута const. Например:

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

#include <iostream>using namespace std;// указатель на объект является константным,// следовательно, через него изменить значение// объекта нельзяvoid test_pow(const int* v){ int*temp; // снимаем модификатор const // и теперь можем изменять объект temp=const_cast<int*>(v); // изменение объекта *temp= *v * *v;}void main(){ int x=10; // на экране - 10 cout<<"Before - "<<x<<"\n\n"; test_pow(&x); // на экране - 100 cout<<"After - "<<x<<"\n\n";}

Примечание:Кстати, стоит упомянуть о том, что такое volatile!!! Данный модификатор даёт компилятору понять, что значение переменной, может быть изменено неявным образом. Например, есть некая глобальная переменная, её адрес передаётся встроенному таймеру операционной системы. В дальнейшем эта конструкция может использоваться для отсчёта реального времени. Естественно, что значение этой переменной изменяется автоматически без участия операторов присваивания. Так вот такая переменная должна быть объявлена с использованием ключевого слова volatile. Это связано с тем, что многие компиляторы не обнаружив ни одного изменения переменной с помощью какого-либо присваивания, считают, что она не изменяется и оптимизируют выражения с ней, подставляя на её место конкретное значение. При этом компилятор переменную не перепроверяет. Действительно, зачем, если переменная для него имеет характер "неизменяемой"?! volatile - будет блокировать подобные действия компиляторов.

dynamic_cast проверяет законность выполнения заданной операции приведения типа. Если такую операцию выполнить нельзя, то выражение устанавливается равным нулю. Этот оператор в основном используется для полиморфных типов. Например, если даны два полиморфных класса, B и D, причем класс D выведен из класса B, то оператор dynamic_cast всегда может преобразовать указатель D* в указатель B*. Оператор dynamic_cast может преобразовать указатель B* в указатель D* только в том случае, если адресуемым объектом является объект D. И вообще, оператор dynamic_cast будет успешно выполнен только при условии, что разрешено полиморфное приведение типов (т.е. если новый тип можно законно применять к типу объекта, который подвергается этой операции). Рассмотрим пример, демонстрирующий всё вышесказанное:

#include <iostream>using namespace std;// базовый классclass B{ public: // виртуальная функция для // последующего переопределения в потомке virtual void Test(){ cout<<"Test B\n\n"; }};// класс-потомокclass D:public B{ public: // переопределение виртуальной функции void Test(){ cout<<"Test D\n\n"; }};void main(){ // указатель на класс-родитель // и объект класса-родителя B *ptr_b, obj_b; // указатель на класс-потомок // и объект класса-потомка D *ptr_d, obj_d; // приводим адрес объекта (D*) к указателю типа D* ptr_d= dynamic_cast<D*> (&obj_d); // если все прошло успешно - вернулся !0 // произошло приведение if(ptr_d){ cout<<"Good work - "; // здесь вызов функции класса-потомка // на экране - Test D ptr_d->Test(); } // если произошла ошибка - вернулся 0 else cout<<"Error work!!!\n\n"; // приводим адрес объекта (D*) к указателю типа B* ptr_b= dynamic_cast<B*> (&obj_d); // если все прошло успешно - вернулся !0 // произошло приведение if(ptr_b){ cout<<"Good work - "; // здесь вызов функции класса-потомка // на экране - Test D ptr_b->Test(); } // если произошла ошибка - вернулся 0 else cout<<"Error work!!!\n\n"; // приводим адрес объекта (B*) к указателю типа B* ptr_b= dynamic_cast<B*>(&obj_b); // если все прошло успешно - вернулся !0 // произошло приведение if(ptr_b){ cout<<"Good work - "; // здесь вызов функции класса-потомка // на экране - Test B ptr_b->Test(); } // если произошла ошибка - вернулся 0 else cout<<"Error work!!!\n\n"; // ВНИМАНИЕ!!! ЭТО НЕВОЗМОЖНО // попытка привести адрес объекта (B*) к указателю типа D* ptr_d= dynamic_cast<D*> (&obj_b); // если все прошло успешно - вернулся !0 // произошло приведение if(ptr_d) cout<<"Good work - "; // если произошла ошибка - вернулся 0 else cout<<"Error work!!!\n\n"; }Результат работы программы:Good work - Test DGood work - Test DGood work - Test BError work!!!

static_cast выполняет неполиморфное приведение типов. Его можно использовать для любого стандартного преобразования. При этом никакие проверки во время работы программы не выполняются. Фактически, static_cast - это аналог стандартной операции преобразования в стиле С. Например, так:

#include <iostream>using namespace std;void main(){ int i; for(i=0;i<10;i++) // приведение переменной i к типу double // результаты деления на экране, естественно // вещественные cout<<static_cast<double>(i)/3<<"\t"; }

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

#include <iostream>using namespace std;void main(){ // целочисленная переменная int x; // строка (указатель типа char) char*str="This is string!!!"; // демонстрируем строку на экран cout<<str<<"\n\n"; // на экране - This is string!!! // преобразуем указатель типа char в число x=reinterpret_cast<int>(str); // демонстрируем результат cout<<x<<"\n\n"; // на экране - 4286208}

Итак, мы познакомились с преобразованиями в стиле C++. Вполне очевидны положительные стороны этих новых способов. Во-первых данные преобразования позволяют сделать то, чего стандарт языка С даже не предполагал: снять модификатор const, или радикально поменять тип данных. Во-вторых, стиль С++ увеличивает контроль над преобразованиями и позволяет выявить ошибки с ними связанные на ранних этапах.

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Стандартная библиотека шаблонов (STL) и её основные понятия (контейнер, итератор, алгоритм, функтор, предикат, аллокатор).

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

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

Наша библиотека включает в себя четыре компонента, составляющие её внутреннюю структуру:

  1. Контейнер - блок для хранения данных, управления ими и размещения. Иными словами, это объект, предназначенный для хранения и использования других элементов.
  2. Алгоритм - специальная функция для работы с данными, содержащимися в контейнере.
  3. Итератор - специальный указатель, который позволяет алгоритмам перемещаться по данным конкретного контейнера.
  4. Функторы - механизм для инкапсуляции функций в конкретном объекте для использования его другими компонентами.

Кроме вышеописанных конструкций, в STL поддерживаются еще некоторые встроенные компоненты:

  1. Аллокатор - распределитель памяти. Такой распределитель памяти имеется у каждого конкретного контейнера. Данная конструкция просто-напросто управляет процессом выделения памяти для контейнера. Следует отметить, что по умолчанию распределителем памяти является объект класса allocator. Однако, можно определить свой собственный распределитель.
  2. Предикат - функция нестандартного типа, используемая в контейнере. Предикат бывает унарный и бинарный. Может возвращать логическое значение (истину либо ложь).

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

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Класс auto_ptr.

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

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

void f(){ FILE*f; if (!(f = fopen("test.txt", "rt"))) { //не удалось открыть файл - выходим exit(0); } //удалось, работаем с файлом дальше //но, здесь может возникнуть исключение //и до следующей строки мы не доберемся //соответственно файл не будет закрыт fclose(f); }

Решением данной проблемы является следующий набор действий:

  1. Создать класс.
  2. Реализовать конструктор, в котором будет открываться файл.
  3. Реализовать деструктор, в котором файл будет закрываться.
  4. Для начала работы с файлом необходимо создать объект этого класса. (Файл будет автоматически открыт в конструкторе).

А, теперь главное -

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

class FileOpen{ FILE* f; public: FileOpen(char* filename, char* mode) { if (!(f = fopen(filename, mode))) { exit(0); } } ~FileOpen(){ fclose(f); } }; void f(){ FileOpen MyFile("test.txt", "r+"); //здесь выполняем нужную работу с файлом }

Для усовершенствования нашего принципа мы можем использовать класс auto_ptr (automatic pointer - автоматический указатель). Данный класс предоставляется стандартной библиотекой С++ и предназначен для работы с объектами, которые обычно необходимо удалять явно (например, объекты, созданные динамически с помощью оператора new).

для создания объекта класса auto_ptr параметром конструктора должен быть указатель на объект, созданный динамически. Дальше c auto_ptr можно работать почти как с обычным указателем, который указывает на тот же динамический объект, на который указывал исходный указатель. Нам не нужно думать о явном удалении объекта, он будет автоматически удален деструктором класса auto_ptr.

Синтаксис класса auto_ptr в стандартной библиотеке выглядит так:

template<class X> class Std::auto_ptr{ X* ptr; public: //конструктор и деструктор explicit auto_ptr(X* p = 0)throw() { ptr = p; } ~auto_ptr() throw() { delete ptr; } //оператор разыменования позволяет получить объект X& operator*()const throw() { return *ptr; } //оператор -> позволяет получить указатель X* operator->()const throw() { return ptr; } };

Примечание:Кроме переопределения операторов -> и *, класс auto_ptr содержит две функции-члена:
X* get () const; - возвращает указатель на объект класса, скрывающегося под шаблоном X
X* release () const; - возвращает указатель на объект класса, скрывающегося под шаблоном X, но при этом забирает у auto_ptr права на этот объект. ВНИМАНИЕ!!! Сам объект не уничтожается!!!

Теперь, вполне естественно, рассмотрев общую конструкцию auto_ptr, полюбопытствовать, как он выглядит в работе:

#include <iostream>#include <memory>using namespace std; class TEMP{ public: TEMP(){ cout<<"TEMP\n\n"; } ~TEMP(){ cout<<"~TEMP\n\n"; } void TEST(){ cout<<"TEST\n\n"; }};void main(){ // создаём два автоматических указателя // под один из них выделяем память типа TEMP auto_ptr<TEMP>ptr1(new TEMP), ptr2; // передача права владения ptr2=ptr1; // вызов функции через автоматический указатель ptr2->TEST(); // присваивание автоматического указателя // обычному указателю на объект класса TEMP*ptr=ptr2.get(); // вызов функции через обычный указатель ptr->TEST();}
Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая






©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.