Задание значений параметров класса по умолчанию
Если в функцию сортировки числовой информации, принадлежащей некоторому классу, передавать символьные строки (char *), то желаемый результат (отсортированные строки) не будет получен. Как известно, в этом случае произойдет сравнение указателей, а не строк. #include "iostream.h" #include "string.h" #include "typeinfo.h" Class CompareNumb { public: static bool sravn(int a, int b){return a<b;} }; Class CompareString { public: static bool sravn(char *a, char *b){return strcmp(a,b)<0;} }; template <class T,class Compare> Class vect { T *ms; int size; public: vect(int SIZE):size(SIZE) { ms=new T[size]; const type_info & t=typeid(T); // получение ссылки t на const char* s=t.name(); // объект класса type_info for(int i=0;i<size;i++) // в описании типа if(!strcmp(s,"char *")) cin >> (*(ms+i)=(T)new char[20]); // ввод символьных строк else cin >> *(ms+i); // ввод числовой информации } void sort_vec(vect<T,Compare> &); }; template <class T,class Compare> void vect<T,Compare>::sort_vec(vect<T,Compare> &vec) { for(int i=0;i<size-1;i++) for(int j=i;j<size;j++) if(Compare::sravn(ms[i],ms[j])) { T tmp=ms[i]; ms[i]=ms[j]; ms[j]=tmp; } for(i=0;i<size;i++) cout << *(ms+i) << endl; }; Класс Compare должен содержать логическую функцию sravn(), сравнивающую два значения типа Т. void main() { vect<int,CompareNumb> vec1(3); vec1.sort_vec(vec1); vect<char *,CompareString> vec2(3); vec2.sort_vec(vec2); } Нетрудно заметить, что для всех типов, для которых операция меньше (<) имеет нужный смысл, можно написать следующий шаблон класса сравнения. template<class T> Class Compare { public: static bool sravn(T a, T b) { const type_info & t=typeid(T); // получение ссылки t на const char* s=t.name(); // объект класса type_info if(!strcmp(s,"char *")) return strcmp((char *)a,(char *)b)<0; else return a<b; } }; template <class T,class Compare> Class vect { // класс vect приведен выше }; Чтобы сделать запись класса более простой, воспользуемся возможностью задания значений некоторых параметров класса по умолчанию. template <class T,class C = Compare<T> >
void vect<T,C>::sort_vec(vect<T,C> &vec) { for(int i=0;i<size-1;i++) for(int j=i;j<size;j++) if(C::sravn(ms[i],ms[j])) { T tmp=ms[i]; ms[i]=ms[j]; ms[j]=tmp; } for(i=0;i<size;i++) cout << *(ms+i) << endl; }; void main() { vect<int,Compare<int> > vec1(3); vec1.sort_vec(vec1); vect<char *,Compare<char *> > vec2(3); vec2.sort_vec(vec2); vect<long,Compare<long> > vec3(3); vec3.sort_vec(vec3); } В инструкции vect<int,Compare<int > > vec1(3) содержится пробел между угловыми скобками. При его отсутствии компилятор спутает >> с операцией >> (сдвига).
Свойства в С++ В общем случае, свойство это пара функций (public), одна из которых отвечает за установку компонент-данных (private) объекта, а другая за их считывание. Такое решение позволяет обеспечить инкапсуляцию данных. Необходимость использования свойств возникает тогда, когда при изменении некоторого параметра требуется произвести ещё некоторые действия. В языках программирования (таких как "Visual Basic" или "Delphi"), обращение к свойствам объекта производится оператором присваивания, как при обращении к компонентам-данным класса в C++. оbj.data = value производится неявный вызов функции. Наиболее простой способ обеспечения инкапсуляции в C++ заключается в написании пары функций типа get_val() и put_val() для каждого параметра. Заметим, что именно так реализованы свойства в технологии Automation. Это можно продемонстрировать на следующем примере простого класса: Class cls { int m; public: int get_val() { return m; } void put_val(int val) { m = val; } }; В этом случае для обращения к такому свойству программист должен написать вызов соответствующей функции. Разработчики Microsoft Visual C++ добавили в синтаксис языка несколько конструкций, позволяющих использовать свойства в операторах присваивания и вообще обращению с ними, как с компонентами-данными. В частности, модификатор _declspec получил дополнительный параметр "property". Это позволяет в классе объявить "виртуальную" переменную и связать её с соответствующими функциями. Теперь класс может выглядеть примерно так:
class cls { int m; public: _declspec(property(get=get_val, put=put_val)) int V; int get_val() { return m; } void put_val(int v) { m = v; } }; В секции public содержится строка _declspec(property(get=get_val, put=put_val)) int V; в которой объявляется "виртуальная" переменная V типа int, при обращении к которой фактически будут вызваться функции get_val и put_val. Теперь доступ к данным объекта класса cls может быть выполнен следующим образом: cls obj; obj.V = 50; // при этом выполняется вызов put_val() int k = obj.V; // при этом выполняется вызов get_val() Модификатор _declspec(property) был введён для встроенной в компилятор поддержки технологии СОМ. Дело в том, что директива импорта библиотеки типа (что бы знать, что это такое, читайте книжки по COM) #import заставляет компилятор VC автоматически генерировать вспомогательные классы-обёртки для объектов СОМ. По аналогии с Visual Basic свойства сделаны индексными. Для этого, после объявления "виртуальной переменной" требуется поставить квадратные скобки: _declspec(property(get=get_val, put=put_val)) int V []; После этого свойство V может принимать один или несколько параметров-индексов, передаваемых в квадратных скобках. Так, например, вызов obj.V["строка"] = 50; Будет преобразован в вызов функции obj.put_val("строка", 50); Основной недостаток описанного выше способа использования свойств в C++ является его зависимость от компилятора. Впрочем, другой, не менее известный компилятор "Borland C++ Builder" реализует концепцию свойств далёким от стандарта способом. В любом случае часто требуется (или хочется) достичь независимости от компилятора и соответствия кода программы стандарту C++. В тоже время язык C++ позволяет реализовать концепцию свойств. Для этого необходимо воспользоваться шаблонами и переопределить операторы присваивания и приведения типа. template <class T1, class T2> Class property { typedef T1 (T2::*get)(); // get – синоним для указателя на функцию // возвращающую T1 typedef void (T2::*set)(T1); T2 * m_owner; get m_get; set m_set; public: // Оператор приведения типа. Реализует свойство для чтения. operator T1() { // Здесь может быть проверка "m_owner" и "m_get" на NULL return (m_owner->*m_get)(); } // Оператор присваивания. Реализует свойство для записи. void operator =(T1 data) { // Здесь может быть проверка "m_owner" и "m_set" на NULL
(m_owner->*m_set)(data); } // Конструктор по умолчанию. property(): m_owner(0), m_get(0), m_set(0) {} // Инициализация объекта property void init(T2 * const owner, get getmethod, set setmethod) { m_owner = owner; // this указатель объекта класса Cls m_get = getmethod; // указатель на метод get_val() m_set = setmethod; // указатель на метод put_val() } }; Теперь класс, реализующий свойство можно написать так: Class cls { int m_val; int get_val() // { return m_val; } void put_val(int val) // { m_val = val; } public: property <int, cls> Val; cls() // Конструктор по умолчанию { Val.init(this, get_val, put_val); } };
main() { cls obj; // Далее вызывается оператор присваивания переменной-члена // obj.Val, и, следовательно, функция val.put_val() obj.Val = 50; // Далее вызывается оператор приведения типа переменной-члена // obj.Val, и, следовательно, функция val.get_val() int z = obj.Val; } Как можно видеть, получились настоящие свойства средствами только стандартного синтаксиса С++. Однако, описанный метод не лишен недостатков: - при каждом обращении к свойству происходит два вызова функции. - использование таких свойств требует дополнительных затрат памяти из-за того, что на каждое свойство требуется 3 дополнительных указателя. - использование шаблонов приводит к увеличению размеров исполняемого кода, поскольку компилятор будет генерировать отдельный класс для каждой пары T1 и T2. - для каждого свойства необходимо не забыть произвести инициализацию в конструкторе класса-владельца.
*** ВСТАВИТЬ ЗАГОЛОВОК *** В рассматриваемом ниже примере вычисления факториала производится на этапе компиляции. #include <iostream> using namespace std; template<int n> // описание шаблона структуры Factorial struct Factorial { enum { val = Factorial<n-1>::val * n }; // вычисление факториала }; template<> // инициализация первого экземпляра struct Factorial<0> // шаблона структуры Factorial (при n==0) { enum { val = 1}; // исходное значение val равное 1 }; int main() { int ms[Factorial<4>::val]; // описание массива ms типа int // размерностью val cout << Factorial<4>::val << endl; // вывод значения факториала return 0; } В результате выполнения программы получим: В этом примере при компиляции производится генерация экземпляров шаблона структуры в выражении val = Factorial<n-1>::val * n. Кроме того приводится пример иописаения массива ms размерность val которого также определяется при компиляции.
Пространства имен При совпадении имен разных элементов в одной области действия часто возникает конфликт имен. Наиболее часто это возникает при использовании различных пакетов библиотек, содержащих, например, одноименные классы. Пространства имен используются для разделения глобального пространства имен, что позволяет уменьшить количество конфликтов. Синтаксис пространства имен некоторым образом напоминает синтаксис структур и классов. После ключевого слова namespace следует необязательное имя пространства имен, затем описывается пространство имен, заключенное в фигурные скобки. namespace NAME { int a; doudle b; char *fun(char *,int); class CLS { ... public: ... } } Далее, если обращение к элементам пространства имен производится вне контекста, его имя должно быть полностью квалифицировано, используя:: NAME::b=2; NAME:: fun(str,NAME:: a); Внутри пространства имен можно поместить группу объявлений классов, типов и функций. Реализация функций пространства имен должна находиться вне самого пространства имен. Это позволит не только отделить реализацию функций от их объявления, но и избежать загромождения пространства имен. По существу, namespace определяет область видимости. Использование безымянного пространства имен (отсутствует имя пространства имен) позволяет определить уникальность объявленных в нем идентификаторов с областью видимости в пределах файла. Контексты пространства имен могут быть вложены. namespace NAME1 { int a; namespace NAME2 { int a; int fun1(){return NAME1:: a}; // возвращается значение первого a int fun2(){return a}; // возвращается значение второго a } } NAME1::NAME2::fun1(); // вызов функции Если в каком-то месте программы интенсивно используется некоторый контекст и все имена уникальны по отношению к нему, то можно сократить полные имена, объявив контекст текущим с помощью оператора using. Если элементы пространства имен будут интенсивно использоваться, то можно использовать ключевое слово using для упрощения доступа к ним. Ключевое слово using используется и как директива, и для объявления. Синтаксис слова using определяет, является ли оно директивой или объявлением.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|