Страница 1 из 2

Головоломка для извращенцев: классы, поля, методы...

Добавлено: 04 июл 2005, 08:54
BreakPointMAN
Доброе время суток всем!

Вообщем, дружан подкинул мне такую задачку...
Представим, что у нас имеется класс:

Код: Выделить всё

class A
   {
    private:
       int data;
    public:
       A():data(0){};

       int getData() {return data;}
       void setData(int _data) {data=_data;}
   }
Надеюсь, что затруднений особых он не вызовет.
Вот, каким образом мы обращаемся к переменной data? Через функции доступа - getData и setData, т.е. примерно так:

Код: Выделить всё

A x;
x.setData(2005);
cout<<x.getData();
или так:

Код: Выделить всё

A* y=new A;
y->setData(2005);
cout<<y->getData();
Товарищу моему такой вариант не слишком понравился... Говорит, что не наглядно и не слишком красиво... Хотелось бы видеть что-то вроде:

Код: Выделить всё

x.data=2005;
y->data=2005;
Ну, говорю, тогда помести переменную data в public:-секцию и не мучайся!..
Но тут возникает одно НО: нужен контроль за тем, что присваивается нашей переменной... Ну, если мы используем функцию доступа, то все просто:

Код: Выделить всё

...
void setData(int _data)
   {
    if (f(_data))     data=_data;
   }
...
Тут f - это функция, принимающая int и возвращающая bool, а в функции может быть записано какое-то условие, ну, например,

Код: Выделить всё

bool f(int arg) {if (arg%2==0) return true; else return false;}
Разрешит изменение переменной data только в том случае, если мы захотим присвоить ей четное число; если число будет нечетным - то переменная меняться не должна.

Вопрос в том, как сделать это, чтобы синтаксис работы с классом был такой:

A a;
a.data=10; // переменная изменится и станет равной 10
a.data=-7; // переменная не изменится, и останется равной 10

???

Добавлено: 04 июл 2005, 08:56
BreakPointMAN
Помучавшись немного, я пришел к такому варианту... УСПОКОЙТЕ МЕНЯ - Я ИЗВРАЩЕНЕЦ ИЛИ НЕТ?! Или есть варианты проще? %))

Код: Выделить всё

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

bool f(int arg) {if (arg%2==0) return true; else return false;}


int main(int argc, char* argv[])
{
        class A
           {

             /*    Определяем локальный класс, в котором, собственно и
                будет храниться "настоящая" переменная, содержащая наши
                реальные данные   */

             class DATA
                {
                 private:
                    int realdata; //  <-- вот где теперь хранится значение    [1]
                 public:

                    //   [2]   Каким значением, по умолчанию, должна быть инициализирована
                    // наша переменная? Это определяет конструктор по умолчанию:

                    DATA():realdata(0){};


                    //   [3]   Конструктор, принимающий целочисленное значение и инициализирующий
                    // им нашу переменную, нужен для того, чтобы можно было в дальнейшем
                    // создать _безымянный_ объект типа DATA, когда будет выполняться
                    // операция присваивания, в правой части которой будет целочисленное
                    // значение

                    DATA(int _DATA){realdata=_DATA;}


                    //     [4]   Следующая оператор-функция является операцией преобразования:
                    // в тех случаях, когда требуется значение типа int, а на деле используется
                    // типа DATA, эта функция вернет значение нашей переменной:

                    operator int() const {return realdata;}


                    //     [5]  А вот, собственно, и добрались до самого главного. Именно
                    // перегруженная операция присваивания позволит нам осуществить задуманное.
                    // Она будет вызываться каждый раз, когда мы имеем примерно такой код:
                    //
                    //      A* a=new A;
                    //      a->data=10;        <-- вот тут будет вызвана операция присваивания,
                    //                             но перед этим будет создан безымянный объект
                    // типа DATA, значением поля realdata которого является 10. Создание такого
                    // объекта нам обеспечит конструктор [3], определенный выше. И уже он будет
                    // являться вторым параметром операции присваивания (т.е. аргументом rhs;
                    // а первый параметр - неявный, это есть сам объект, для которого выполняется
                    // присваивание).

                    DATA& operator=(const DATA& rhs)
                       {
                        if(this==&rhs) return *this;
                        if(f(rhs)) realdata=rhs;    // <-- вызов функции f, именно от возвращенного
                        return *this;               //     ей результата (true/false) будет зависеть,
                                                    //     изменится или нет значение нашей
                                                    //     переменной. Функция должна принимать
                                                    //     значение типа int (т.е. такое же,
                                                    //     как и у нашей переменной), а возвращать
                                                    //     значение bool;
                                                    // кроме того, здесь у нас работает операция
                                                    // преобразования [4] - иначе мы не могли бы
                                                    // выполнить присваивание realdata=rhs,
                                                    // т.к. rhs имеет тип DATA&, а realdata -
                                                    // тип int... да и вызвать функцию f тоже бы
                                                    // так не смогли...

                       }

                };

            public:
               DATA data; // ну якобы наше поле... %)))
           };


        // ********************************************************************
        // До сих пор у нас было определение класса A. А теперь тестим...

        A a; // по умолчанию data == 0

        a.data=148; // четное число присвоено будет
        cout <<"a.data == " << a.data <<"\n";

        a.data=-17; // а нечетное нет... останется тем же, чтобы было присвоено раньше (148)
        cout <<"a.data == " << a.data <<"\n";


        // Можно создавать и динамически:

        A* b=new A;

        b->data=53; // нечетное число присвоено не будет, по умолчанию data было ==0
                    // таким же и останется
        cout <<"b->data == " << b->data <<"\n";



        b->data=-200;  // а четное - спокойно присвоится
        cout <<"b->data == " << b->data <<"\n";


        getch();
        return 0;
}


Добавлено: 04 июл 2005, 10:24
Absurd
Твой кореш - Дельфийский удозвон. Нефиг навязывать нормативы одного языка другому языку. В Java тоже property нет - и что?
obj.setValue(13.666);
Дешево, надежно, практично, наглядно и мы видим что мы не просто пишем в ячейку, а вызываем какой-то метод.

Добавлено: 04 июл 2005, 11:43
WinMain
Вообще можно поискать готовые шаблоны типа CProperty<> или CPropertyImpl<>, которые используются как члены классов.

А в твоём случае обращаться к полю data можно не через функции, а через перегруженные операторы неявным образом. Например:

Код: Выделить всё

A a;
a << 2005;
cout << a;
В этом случае поле data в коде вообще не фигурирует, а вместо функций используются перегруженные операторы << и >>.

Добавлено: 04 июл 2005, 14:09
BreakPointMAN
Absurd писал(а):Твой кореш - Дельфийский удозвон.
Ну, только не надо так грубо... угу, раньше он на Delphi сидел... хотя сейчас - под VC.NET и Builder...
Absurd писал(а): Нефиг навязывать нормативы одного языка другому языку. В Java тоже property нет - и что?
obj.setValue(13.666)]
Да я ничего и не говорю... %))) Задачка меня заинтересовала как забавная головоломка. Впрочем, как оказалось, я изобрел велосипед, но, СЛАВА БОГУ, я не такой извращенец, как уже себе вообразил... %))))
WinMain писал(а):Вообще можно поискать готовые шаблоны типа CProperty<> или CPropertyImpl<>, которые используются как члены классов.
Угу, спасибо за информацию! %))) Буду знать...
WinMain писал(а):А в твоём случае обращаться к полю data можно не через функции, а через перегруженные операторы неявным образом. Например:

Код: Выделить всё

A a]

В этом случае поле data в коде вообще не фигурирует, а вместо функций используются перегруженные операторы << и >>.[/quote][/quote]
ммм... я фиг знает, но этот вариант, я думаю, не очень подходит, в классе A, как я понимаю, много разных полей будет, не только одно целочисленное data. Иначе нафиг такой огород было бы городить? %))

Добавлено: 07 июл 2005, 23:18
Serg79
Народ, Вы вобще понимаете что Вы пишете или нет, Вы уже сдесь столько дров наломали. Не забывайте, что разговор идет о языке С++. Просто перегрузите оператор "=":

Код: Выделить всё

#include <iostream>
using namespace std;

class A
{
	int data;
public:
	A() { data = 0; }
	A(int temp) { data = temp; }
	void setData(int temp) { data = temp; }
	int getData() { return data; }
	A operator=(int temp);
};

A A::operator=(int temp)
{
	data = temp;
	return *this;
}

int main()
{
	A ob1, ob2(5), *ob3;

	ob3 = new A;

	ob3->setData(20);

	cout << ob1.getData() << " ";
	cout << ob2.getData() << " ";
	cout << ob3->getData() << endl;

	ob1 = ob2 = *ob3 = 10;

	cout << ob1.getData() << " ";
	cout << ob2.getData() << " ";
	cout << ob3->getData() << endl;

	return 0;
}
А если надо хранить только четные значения (управлять доступом по условию) смотри ниже:

Код: Выделить всё

#include <iostream>
using namespace std;

class A
{
	int data;
public:
	A() { data = 0; }
	A(int temp) { temp%2 ? data = 0 : data = temp; }
	void setData(int temp) { if(!(temp%2)) data = temp; }
	int getData() { return data; }
	A operator=(int temp);
};

A A::operator=(int temp)
{
	if(!(temp%2)) data = temp;
	return *this;
}

int main()
{
	A ob1, ob2(5), *ob3;

	ob3 = new A;

	ob3->setData(21);

	cout << ob1.getData() << " ";
	cout << ob2.getData() << " ";
	cout << ob3->getData() << endl;

	ob1 = ob2 = *ob3 = 10;

	cout << ob1.getData() << " ";
	cout << ob2.getData() << " ";
	cout << ob3->getData() << endl;

	ob1 = ob2 = *ob3 = 11;

	cout << ob1.getData() << " ";
	cout << ob2.getData() << " ";
	cout << ob3->getData() << endl;

	return 0;
}
Не забывайте что речь идет о С++!!!

Добавлено: 08 июл 2005, 01:13
BreakPointMAN
Serg79 писал(а):Народ, Вы вобще понимаете что Вы пишете или нет, Вы уже сдесь столько дров наломали. Не забывайте, что разговор идет о языке С++. Просто перегрузите оператор "="... Не забывайте что речь идет о С++!!!
Мы-то понимаем, что мы пишем... А Вы, вероятно, невнимательно читаете... В противном случае Вы бы увидели, что речь идет о присваивании поля класса, а не всего класса. Чувствуете разницу?

Предположим, есть у нас класс:

Код: Выделить всё

class A
   &#123]
Можно перегрузить оператор присваивания для класса A, и тогда будет работать:
[code]
A object;
object=16;
Но нельзя написать:

Код: Выделить всё

object.a=16;
Поместить описание переменной a в public:-секцию - не выход, так как это откроет прямой доступ для ее изменения, а нам нужен контроль за всеми присваиваемыми значениями. Как вариант, можно вместо int a; использовать другой класс-обертку и переопределить присваивание для него. Что сдесь и было сделано.
В принципе, можно было бы воспользоваться первым вариантом (с перегрузкой оператора = для класса A), но в классе могут быть НЕСКОЛЬКО полей, за которыми так же нужен контроль...

Добавлено: 12 июл 2005, 10:02
Serg79
Если в классе несколько полей то можно перегружать оператор "=" столько раз сколько нужно.

Код: Выделить всё

#include <iostream>
using namespace std;

struct Str
{
	int a;
	double b;
};

class A
{
	int dt1;
	double dt2;
	Str st;
public:
	A() { dt1 = 0; dt2 = 0; st.a = 0; st.b = 0; }
	A(int x1, double x2, Str &x3)
		{ dt1 = x1; dt2 = x2; st.a = x3.a; st.b = x3.b; }
	void setData(int x1, double x2, Str &x3)
		{ dt1 = x1; dt2 = x2; st.a = x3.a; st.b = x3.b; }
	void set_int(int x) { dt1 = x; }
	void set_double(double x) { dt2 = x; }
	void set_Str(Str &x) { st.a = x.a; st.b = x.b; }
	int get_int() { return dt1; }
	double get_double() { return dt2; }
	Str get_Str() { return st; }
	A operator=(int temp);
	A operator=(double temp);
	A operator=(Str temp);
	A operator=(A temp);
};

A A::operator=(int temp)
{
	dt1 = temp;
	return *this;
}

A A::operator=(double temp)
{
	dt2 = temp;
	return *this;
}

A A::operator=(Str temp)
{
	st.a = temp.a;
	st.b = temp.b;
	return *this;
}

A A::operator=(A temp)
{
	dt1 = temp.dt1;
	dt2 = temp.dt2;
	st.a = temp.st.a;
	st.b = temp.st.b;
	return *this;
}

int main()
{
	A *ob2;
	Str st = { 2, 3.66 };
	Str st2;
	A ob1(5,0.6,st);

	ob2 = new A;

	cout << ob1.get_int() << " ";
	cout << ob1.get_double() << endl;

	st2 = ob1.get_Str();
	cout << st2.a << " " << st2.b << endl;

	*ob2 = ob1 = 6;
	*ob2 = ob1 = 0.26;

	cout << ob1.get_int() << " ";
	cout << ob1.get_double() << endl;

	cout << ob2->get_int() << " ";
	cout << ob2->get_double() << endl;

	st2 = ob2->get_Str();
	cout << st2.a << " " << st2.b << endl;

	
	return 0;
}
Эта схема будет работать и в более сложных ситуациях. И ее можно развивать настолько насколько фантазии хватит. Но помещать переменые класа в секцию "public" Я думаю не стоит. Тогда вообще зачем нужно создавать класс?

Добавлено: 12 июл 2005, 23:57
BreakPointMAN
Serg79 писал(а):Если в классе несколько полей то можно перегружать оператор "=" столько раз сколько нужно <...>
уффф... приехали!.. второй (вернее, третий) раз говорю: речь идет про присваивание полей класса!.. перечитайте повнимательнее самый первый пост!..
BreakPointMAN писал(а):<...>
Вот, каким образом мы обращаемся к переменной data? Через функции доступа - getData и setData, т.е. примерно так:

Код: Выделить всё

A x]
или так:
[code]
A* y=new A;
y->setData(2005);
cout<<y->getData();
Товарищу моему такой вариант не слишком понравился... Говорит, что не наглядно и не слишком красиво... Хотелось бы видеть что-то вроде:

Код: Выделить всё

x.data=2005;
y->data=2005;
<...>
Можно переопределить оператор присваивания для всего класса целиком, можно переопределить его для разных типов данных, стоящих в правой части оператора присваивания, МОЖНО!

Но если соблюдать вышеизложенные условия - т.е. соблюдать такой синтаксис, иметь возможность выполнять присваивания для нескольких разных полей и одновременно с этим контролировать - что же это у нас там такое присваивается?! - сделать это можно только с помощью так называемого Property. Кстати, в BCB и MSVC уже есть такая шняга, поэтому можно было весь этот огород не городить... %)

А если Ваш вариант - с перегрузкой для разных типов данных - так кто же не дает? Только что делать, если мы имеем три поля, все они типа int? И при этом нужен контроль, синтаксис такой вот... ))

Задача интерестная

Добавлено: 13 июл 2005, 14:48
ssDev
На самом деле задача интересная и с хорошей утилизацией я бы сделал так:

Код: Выделить всё

template<class T> class tTest{
protected:
	T* d; 
public:
	tTest(){}
	void setUp(T* x){d=x;}
	T& operator=(const T v){*d=v;return *d;}
};

class A{
protected:
	int m_data;
public:
	tTest<int> data;
	A(){data.setUp(&m_data);}
};
В клас tTest можно вставлять произвольные условия и использовать его в других задачах.
К томуже доступ удобный и можно сделать его для нескольких членов класса A :evil: