Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lucik_op.doc
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
3 Мб
Скачать
☆

Задание значений параметров класса по умолчанию

Если в функцию сортировки числовой информации, принадлежащей некоторому классу, передавать символьные строки (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;

}

В результате выполнения программы получим:

24

В этом примере при компиляции производится генерация экземпляров шаблона структуры в выражении val = Factorial<n-1>::val * n. Кроме того приводится пример иописаения массива ms размерность val которого также определяется при компиляции.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]