Главная страница
Навигация по странице:

  • Использование функций-"друзей" для перегрузки унарных операторов

  • Узелок на память.

  • Перегрузка операторов отношения и логических операторов

  • Подробнее об операторе присваивания

  • Перегрузка оператора индексации массивов ([])

  • Перегрузка оператора "()"

  • Перегрузка других операторов

  • Еще один пример перегрузки операторов

  • Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс


    Скачать 9.37 Mb.
    НазваниеГерберт Шилдт С базовый курс
    АнкорШилдт c++_базовый_курс издание 3.pdf
    Дата13.02.2017
    Размер9.37 Mb.
    Формат файлаpdf
    Имя файлаШилдт c++_базовый_курс издание 3.pdf
    ТипКнига
    #2637
    страница16 из 33
    1   ...   12   13   14   15   16   17   18   19   ...   33

    Перегрузка операторов с использованием функций-не членов класса
    Бинарные операторные функции, которые не являются членами класса, имеют два
    параметра, а унарные (тоже не члены) — один.
    Перегрузку оператора для класса можно реализовать и с использованием функции, не являющейся членом этого класса. Такие функции часто определяются "друзьями" класса.
    Как упоминалось выше, функции-не члены (в том числе и функции-"друзья") не имеют указателя this. Следовательно, если для перегрузки бинарного оператора используется функция-"друг", явным образом передаются оба операнда. Если же с помощью функции-
    "друга" перегружается унарный оператор, операторной функции передается один оператор.
    С использованием функций-не членов класса нельзя перегружать такие операторы:
    =, (), [] и ->.
    Например, в следующей программе для перегрузки оператора "+" вместо функции-члена используется функция-"друг".
    // Перегрузка оператора "+" с помощью функции-"друга".

    #include
    using namespace std;
    class three_d {
    int x, y, z; // 3-мерные координаты public:
    three_d() { x = у = z = 0; }
    three_d(int i, int j, int k) { x = i; у = j; z = k; }
    friend three_d operator+(three_d op1, three_d op2);
    three_d operator= (three_d op2); // Операнд op1 передается неявно.
    void show();
    };
    // Теперь это функция-"друг".
    three_d operator+(three_d op1, three_d op2)
    {
    three_d temp;
    temp.x = op1.x + op2.x;
    temp.у = op1.у + op2.y;
    temp.z = op1.z + op2.z;
    return temp;
    }

    // Перегрузка присваивания.
    three_d three_d::operator=(three_d op2)
    {
    x = op2.x;
    у = op2.у;
    z = op2.z;
    return *this;
    }
    // Отображение координат X, Y, Z.
    void three_d::show()
    {
    cout << x << ", ";
    cout << у << ", ";
    cout << z << "\n";
    }
    int main()
    {
    three_d a(1, 2, 3), b(10, 10, 10), c;
    a.show();
    b.show();
    с = a + b; // сложение объектов а и b
    c.show();
    c=a+b+c; // сложение объектов a, b и с с.show();
    с = b = а; // демонстрация множественного присваивания с.show();
    b.show();
    return 0;
    }
    Как видите, операторной функции operator+() теперь передаются два операнда. Левый операнд передается параметру op1, а правый — параметру ор2.
    Во многих случаях при перегрузке операторов с помощью функций-"друзей" нет никакого преимущества по сравнению с использованием функций-членов класса. Однако возможна ситуация (когда нужно, чтобы слева от бинарного оператора стоял объект встроенного типа), в которой функция-"друг" оказывается чрезвычайно полезной. Чтобы понять это, рассмотрим следующее. Как вы знаете, указатель на объект, который вызывает операторную функцию-член, передается с помощью ключевого слова this. При использовании бинарного оператора функцию вызывает объект, расположенный слева от него. И это замечательно при условии, что левый объект определяет заданную операцию.
    Например, предположим, что у нас есть некоторый объект ob, для которого определена операция сложения с целочисленным значением, тогда следующая запись представляет собой вполне допустимое выражение.
    ob + 10; // будет работать
    Поскольку объект ob стоит слева от оператора "+", он вызывает перегруженную операторную функцию, которая (предположительно) способна выполнить операцию сложения целочисленного значения с некоторым элементом объекта ob. Но эта инструкция работать не будет.
    10 + ob; // не будет работать
    Дело в том, что в этой инструкции объект, расположенный слева от оператора "+",
    представляет собой целое число, т.е. значение встроенного типа, для которого не определена ни одна операция, включающая целое число и объект классового типа.
    Решение описанной проблемы состоит в перегрузке оператора "+" с использованием двух функций-"друзей" В этом случае операторной функции явным образом передаются оба
    аргумента, и она вызывается подобно любой другой перегруженной функции, т.е. на основе типов ее аргументов. Одна версия операторной функции operator+() будет обрабатывать аргументы объект + int-значение, а другая — аргументы int-значение + объект. Перегрузка оператора "+" (или любого другого бинарного оператора) с использованием функций-
    "друзей" позволяет ставить значение встроенного типа как справа, так и слева от оператора.
    Реализация этого решения показана в следующей программе.
    #include
    using namespace std;
    class CL {
    public:
    int count;
    CL operator=(CL obj);
    friend CL operator+(CL ob, int i);
    friend CL operator+(int i, CL ob);
    };
    CL CL::operator=(CL obj)
    {
    count = obj.count;
    return *this;
    }
    // Эта версия обрабатывает аргументы
    // объект + int-значение.
    CL operator+(CL ob, int i)
    {

    CL temp;
    temp.count = ob.count + i;
    return temp;
    }
    // Эта версия обрабатывает аргументы
    // int-значение + объект.
    CL operator+(int i, CL ob)
    {
    CL temp;
    temp.count = ob.count + i;
    return temp;
    }
    int main()
    {
    CL o;
    o.count = 10;
    cout << o.count << " "; // выводит число 10
    o=10+o; // сложение числа с объектом cout << o.count << " "; // выводит число 20
    o=o+12; // сложение объекта с числом cout << 0.count; // выводит число 32
    return 0;
    }
    Как видите, операторная функция operator+() перегружается дважды, позволяя тем самым предусмотреть два возможных способа участия целого числа и объекта типа CL в операции сложения.
    Использование функций-"друзей" для перегрузки унарных операторов
    С помощью функций-"друзей" можно перегружать и унарные операторы. Но это потребует от программиста дополнительных усилий. Для начала мысленно вернемся к исходной версии перегруженного оператора "++", определенной для класса three_d и реализованной в виде функции-члена. Для удобства приведем код этой операторной функции здесь.
    // Перегрузка префиксной формы оператора "++".
    three_d three_d::operator++()
    {
    х++;
    у++;
    z++;
    return *this;
    }
    Как вы знаете, каждая функция-член получает (в качестве неявно переданного) аргумент
    this, который является указателем на объект, вызвавший эту функцию. При перегрузке унарного оператора с помощью функции-члена аргументы явным образом не передаются вообще. Единственным аргументом, необходимым в этой ситуации, является неявный указатель на вызывающий объект. Любые изменения, вносимые в данные объекта, повлияют на объект, для которого была вызвана эта операторная функция. Следовательно, при выполнении инструкции х++ (в предыдущей функции) будет инкрементирован член х
    вызывающего объекта.
    В отличие от функций-членов, функции-не члены (в том числе и "друзья" класса) не получают указатель this и, следовательно, не имеют доступа к объекту, для которого они были вызваны. Но мы знаем, что "дружественной" операторной функции операнд передается явным образом. Поэтому попытка создать операторную функцию-"друга"
    operator++() в таком виде успехом не увенчается.
    // ЭТОТ ВАРИАНТ РАБОТАТЬ НЕ БУДЕТ
    three_d operator++(three_d op1)
    {
    op1.x++;
    op1.y++;
    op1.z++;
    return op1;
    }
    Эта функция неработоспособна, поскольку только копия объекта, активизировавшего вызов функции operator++(), передается функции через параметр op1. Таким образом,
    изменения в теле функции operator++() не повлияют на вызывающий объект, они изменяют только локальный параметр.
    Если вы хотите для перегрузки операторов инкремента или декремента использовать функцию-"друга", необходимо передать ей объект по ссылке. Поскольку ссылочный параметр представляет собой неявный указатель на аргумент, то изменения, внесенные в параметр, повлияют и на аргумент. Применение ссылочного параметра позволяет функции успешно инкрементировать или декрементировать объект, используемый в качестве операнда.
    Если для перегрузки операторов инкремента или декремента используется функция-
    "друг", ее префиксная форма принимает один параметр (который и является операндом), а постфиксная форма — два параметра (вторым является целочисленное значение, которое не используется).
    Ниже приведен полный код программы обработки трехмерных координат, в которой используется операторная функция-"друг" operator++(). Обратите внимание на то, что перегруженными являются как префиксная, так и постфиксная формы операторов инкремента.
    // В этой программе используются перегруженные
    // операторные функции-"друзья" operator++() .
    #include
    using namespace std;
    class three_d {
    int x, y, z; // 3-мерные координаты public:
    three_d() { x = у = z = 0; }
    three_d(int i, int j, int k) {x = i; у = j; z = k; }
    friend three_d operator+(three_d op1, three_d op2);
    three_d operator=(three_d op2);
    // Эти функции для перегрузки
    // оператора "++" используют ссылочные параметры.
    friend three_d operator++(three_d &op1);
    friend three_d operator++(three_d &op1, int notused);
    void show();
    };
    // Теперь это функция-"друг".
    three_d operator+(three_d op1, three_d op2)
    {
    three_d temp;
    temp.x = op1.x + op2.x;
    temp.у = op1.у + op2.y;
    temp.z = op1.z + op2.z;
    return temp;
    }
    // Перегрузка оператора "=".
    three_d three_d::operator=(three_d op2)
    {
    x = op2.x;
    у = op2.y;
    z = op2.z;
    return *this;
    }
    /* Перегрузка префиксной версии оператора "++" с использованием функции-"друга". Для этого необходимо использование ссылочного параметра.
    */
    three_d operator++(three_d &op1)
    {
    op1.х++;
    op1.у++;
    op1.z++;
    return op1;
    }
    /* Перегрузка постфиксной версии оператора "++" с использованием функции-"друга". Для этого необходимо использование ссылочного параметра.
    */
    three_d operator++(three_d &op1, int notused)
    {
    three_d temp = op1;
    op1.x++;
    op1.у++;
    op1.z++;
    return temp;
    }
    // Отображение координат X, Y, Z.
    void three_d:: show()
    {
    cout << x << ", ";
    cout << у << ", ";
    cout << z << "\n";
    }
    int main()
    {
    three_d a(1, 2, 3), b(10, 10, 10), c;
    a.show();
    b.show();
    с = a + b; // сложение объектов а и b c.show();
    c=a+b+c; // сложение объектов a, b и с
    с.show();
    с = b = a; // демонстрация множественного присваивания с.show();
    b.show();
    ++c; // префиксная версия инкремента c.show();
    с++; // постфиксная версия инкремента с. show();
    а = ++с; // Объект а получает значение объекта с после инкрементирования.
    a.show(); // В этом случае объекты а и с с.show(); // имеют одинаковые значения координат.
    а = C++; // Объект а получает значение объекта с до инкрементирования.
    a.show(); // В этом случае объекты а и с с.show(); // имеют различные значения координат.
    return 0;
    }
    Узелок на память. Для реализации перегрузки операторов следует использовать
    функции-члены. Функции-"друзья" используются в C++ в основном для обработки
    специальных ситуаций.

    Перегрузка операторов отношения и логических операторов
    Операторы отношений (например, "==" или "<") и логические операторы (например,
    "&&" или "||") также можно перегружать, причем делать это совсем нетрудно. Как правило,
    перегруженная операторная функция отношения возвращает объект класса, для которого она перегружается. А перегруженный оператор отношения или логический оператор возвращает одно из двух возможных значений: true или false. Это соответствует обычному применению этих операторов и позволяет использовать их в условных выражениях.
    Рассмотрим пример перегрузки оператора "==" для уже знакомого нам класса three_d.
    // Перегрузка оператора "=="
    bool three_d::operator==(three_d op2)
    {
    if((x == op2.x) && (y == op2.y) && (z == op2.z)) return true;
    else return false;
    }
    Если считать, что операторная функция operator==() уже реализована, следующий фрагмент кода совершенно корректен.
    three_d а, b;
    // ...
    if(а == b) cout << "а равно b\n";
    else cout << "а не равно b\n";
    Поскольку операторная функция operator==() возвращает результат типа bool, ее можно использовать для управления инструкцией if. В качестве упражнения попробуйте реализовать и другие операторы отношений и логические операторы для класса three_d.
    Подробнее об операторе присваивания
    В предыдущей главе рассматривалась потенциальная проблема, связанная с передачей объектов функциям и возвратом объектов из функций. В обоих случаях проблема была вызвана использованием конструктора по умолчанию, который создает побитовую копию объекта. Вспомните, что решение этой проблемы лежит в создании собственного конструктора копии, который точно определяет, как должна быть создана копия объекта.
    Подобная проблема может возникать и при присваивании одного объекта другому. По умолчанию объект, находящийся с левой стороны от оператора присваивания, получает побитовую копию объекта, находящегося справа. К печальным последствиям это может привести в случаях, когда при создании объект выделяет некоторый ресурс (например,
    память), а затем изменяет его или освобождает. Если после выполнения операции присваивания объект изменяет или освобождает этот ресурс, второй объект также
    изменяется, поскольку он все еще использует его. Решение этой проблемы состоит в перегрузке оператора присваивания.
    Чтобы до конца понять суть описанной проблемы, рассмотрим следующую
    (некорректную) программу.
    // Ошибка, генерируемая при возврате объекта из функции.
    #include
    #include
    #include
    using namespace std;
    class sample {
    char *s;
    public:
    sample() { s = 0; }
    sample(const sample &ob); // конструктор копии

    sample() {
    if(s) delete [] s;
    cout << "Освобождение s-памяти.\n";
    }
    void show() { cout << s << "\n"; }
    void set(char *str);
    };
    // Конструктор копии.
    sample::sample(const sample &ob)
    {
    s = new char[strlen(ob.s) +1];
    strcpy(s, ob.s);
    }
    // Загрузка строки.
    void sample::set(char *str)
    {
    s = new char[strlen(str) +1];
    strcpy(s, str);
    }
    // Эта функция возвращает объект типа sample.
    sample input()
    {
    char instr[80];
    sample str;
    cout << "Введите строку: ";
    cin >> instr;
    str.set(instr);
    return str;
    }
    int main()

    {
    sample ob;
    // Присваиваем объект, возвращаемый
    // функцией input(), объекту ob.
    ob = input(); // Эта инструкция генерирует ошибку!!!!
    ob.show();
    return 0;
    }
    Возможные результаты выполнения этой программы выглядят так.
    Введите строку: Привет
    Освобождение s-памяти.
    Освобождение s-памяти.
    Здесь "мусор"
    Освобождение s-памяти.
    В зависимости от используемого компилятора, вы можете увидеть "мусор" или нет.
    Программа может также сгенерировать ошибку во время выполнения. В любом случае ошибки не миновать. И вот почему.
    В этой программе конструктор копии корректно обрабатывает возвращение объекта функцией input(). Вспомните, что в случае, когда функция возвращает объект, для хранения возвращаемого ею значения создается временный объект. Поскольку при создании объекта- копии конструктор копии выделяет новую область памяти, член s исходного объекта и член
    s объекта-копии будут указывать на различные области памяти, которые, следовательно, не станут портить друг друга.
    Однако ошибки не миновать, если объект, возвращаемый функцией, присваивается объекту ob, поскольку при выполнении присваивания по умолчанию создается побитовая копия. В данном случае временный объект, возвращаемый функцией input(), копируется в объект ob. В результате член ob.s указывает на ту же самую область памяти, что и член s
    временного объекта. Но после присваивания в процессе разрушения временного объекта эта память освобождается. Следовательно, член ob.s теперь будет указывать на уже освобожденную память! Более того, память, адресуемая членом ob.s, должна быть освобождена и по завершении программы, т.е. во второй раз. Чтобы предотвратить возникновение этой проблемы, необходимо перегрузить оператор присваивания так, чтобы объект, располагаемый слева от оператора присваивания, выделял собственную область памяти.

    Реализация этого решения показана в следующей откорректированной программе.
    // Эта программа работает корректно.
    #include
    #include
    #include
    using namespace std;
    class sample {
    char *s;
    public:
    sample(); // обычный конструктор sample(const sample &ob); // конструктор копии
    sample() {
    if(s) delete [] s;
    cout << "Освобождение s-памяти.\n";
    }
    void show() { cout << s << "\n"; }
    void set(char *str);
    sample operator=(sample &ob); // перегруженный оператор присваивания
    };

    // Обычный конструктор.
    sample::sample()
    {
    s = new char('\0'); // Член s указывает на null-строку.
    }
    // Конструктор копии.
    sample::sample(const sample &ob)
    {
    s = new char[strlen(ob.s)+1];
    strcpy(s, ob.s);
    }
    // Загрузка строки.
    void sample::set(char *str)
    {
    s = new char[strlen(str)+1];
    strcpy(s, str);
    }
    // Перегрузка оператора присваивания.
    sample sample::operator=(sample &ob)
    {
    /* Если выделенная область памяти имеет недостаточный размер,
    выделяется новая область памяти. */
    if(strlen (ob.s) > strlen(s)) {
    delete [] s;
    s = new char[strlen(ob.s)+1];
    }
    strcpy(s, ob.s);
    return *this;
    }
    // Эта функция возвращает объект типа sample.
    sample input()
    {
    char instr[80];
    sample str;
    cout << "Введите строку: ";
    cin >> instr;
    str.set(instr);
    return str;
    }
    int main()
    {
    sample ob;
    // Присваиваем объект, возвращаемый
    // функцией input(), объекту ob.
    ob = input(); // Теперь здесь все в порядке!
    ob.show();
    return 0;
    }
    Эта программа теперь отображает такие результаты (в предположении, что на приглашение "Введите строку: " вы введете "Привет").
    Введите строку: Привет
    Освобождение s-памяти.
    Освобождение s-памяти.
    Освобождение s-памяти.
    Привет
    Освобождение s-памяти.
    Как видите, эта программа теперь работает корректно. Вы должны понимать, почему выводится каждое из сообщений "Освобождение s-памяти. ". (Подсказка: одно из них вызвано инструкцией delete в теле операторной функции operator=().)
    Перегрузка оператора индексации массивов ([])
    В дополнение к традиционным операторам C++ позволяет перегружать и более "экзотические", например, оператор индексации массивов ([]). В C++ (с точки зрения механизма перегрузки) оператор "[]" считается бинарным. Его можно перегружать только для класса и только с использованием функции-члена. Вот как выглядит общий формат операторной функции-члена operator[]().
    тип имя_класса::operator[](int индекс)
    {
    // ...
    }
    Оператор "[]" перегружается как бинарный оператор.
    Формально параметр индекс необязательно должен иметь тип int, но операторные функции operator[]() обычно используются для обеспечения индексации массивов, поэтому в общем случае в качестве аргумента этой функции передается целочисленное значение.
    Предположим, у нас определен объект ob, тогда выражение
    ob[3]
    преобразуется в следующий вызов операторной функции operator[]():
    ob.operator[](3)
    Другими словами, значение выражения, заданного в операторе индексации, передается операторной функции operator[]() в качестве явно заданного аргумента. При этом указатель
    this будет указывать на объект ob, т.е. объект, который генерирует вызов этой функции.
    В следующей программе в классе atype объявляется массив для хранения трех int- значений. Его конструктор инициализирует каждый член этого массива. Перегруженная операторная функция operator[]() возвращает значение элемента, заданного его параметром.
    // Перегрузка оператора индексации массивов
    #include
    using namespace std;
    const int SIZE = 3;
    class atype {
    int a[SIZE];
    public:
    atype() {
    register int i;
    for(i=0; i }
    int operator[](int i) {return a[i];}
    };
    int main()
    {
    atype ob;
    cout << ob[2]; // отображает число 2
    return 0;
    }
    Здесь функция operator[]() возвращает значение i-го элемента массива a. Таким образом,
    выражение ob[2] возвращает число 2, которое отображается инструкцией cout.
    Инициализация массива a с помощью конструктора (в этой и следующей программах)
    выполняется лишь в иллюстративных целях.
    Можно разработать операторную функцию operator[]() так, чтобы оператор "[]" можно было использовать как слева, так и справа от оператора присваивания. Для этого достаточно указать, что значение, возвращаемое операторной функцией operator[](), является ссылкой.
    Эта возможность демонстрируется в следующей программе.
    // Возврат ссылки из операторной функции operator()[].
    #include
    using namespace std;
    const int SIZE = 3;
    class atype {
    int a[SIZE];
    public:
    atype() {
    register int i;
    for(i=0; i }
    int &operator[](int i) {return a[i];}
    };
    int main()
    {
    atype ob;
    cout << ob[2]; // Отображается число 2.
    cout <<" ";
    ob[2] = 25; // Оператор "[]" стоит слева от оператора "=".
    cout << ob[2]; // Теперь отображается число 25.
    return 0;
    }
    При выполнении эта программа генерирует такие результаты.
    2 25
    Поскольку функция operator[]() теперь возвращает ссылку на элемент массива,
    индексируемый параметром i, оператор "[]" можно использовать слева от оператора присваивания, что позволит модифицировать любой элемент массива. (Конечно же, его по- прежнему можно использовать и справа от оператора присваивания.)
    Одно из достоинств перегрузки оператора "[]" состоит в том, что с его помощью мы можем обеспечить средство реализации безопасной индексации массивов. Как вы знаете, в
    C++ возможен выход за границы массива во время выполнения программы без
    Соответствующего уведомления (т.е. без генерирования сообщения о динамической ошибке). Но если создать класс, который содержит массив, и разрешить доступ к этому массиву только через перегруженный оператор индексации "[]", то возможен перехват индекса, значение которого вышло за дозволенные пределы. Например, следующая программа (в основу которой положен код предыдущей) оснащена средством контроля попадания в допустимый интервал.
    // Пример организации безопасного массива.
    #include
    #include
    using namespace std;
    const int SIZE = 3;
    class atype {
    int a[SIZE];
    public:
    atype() {
    register int i;
    for(i=0; i }
    int &operator[] (int i);
    };
    // Обеспечение контроля попадания в допустимый интервал для класса atype.
    int &atype:: operator [](int i)
    {
    if(i<0 || i>SIZE-1) {
    cout << "\n Значение индекса ";
    cout << i << " выходит за границы массива. \n";
    exit(1);
    }
    return a[i];

    }
    int main()
    {
    atype ob;
    cout << ob[2]; // Отображается число 2.
    cout << " ";
    ob[2] =25; // Оператор "[]" стоит в левой части.
    cout << ob[2]; // Отображается число 25.
    ob[3] = 44; // Генерируется ошибка времени выполнения.
    // поскольку значение 3 выходит за границы массива.
    return 0;
    }
    При выполнении эта программа выводит такие результаты.
    2 25
    Значение индекса 3 выходит за границы массива.
    При выполнении инструкции ob[3] = 44;
    операторной функцией operator[]() перехватывается ошибка нарушения границ массива,
    после чего программа тут же завершается, чтобы не допустить никаких потенциально возможных разрушений.
    Перегрузка оператора "()"
    Возможно, самым интригующим оператором, который можно перегружать, является оператор "()" (оператор вызова функций). При его перегрузке создается не новый способ вызова функций, а операторная функция, которой можно передать произвольное число параметров. Начнем с примера. Предположим, что некоторый класс содержит следующее
    объявление перегруженной операторной функции.
    int operator()(float f, char *p);
    И если в программе создается объект ob этого класса, то инструкция ob (99.57, "перегрузка");
    преобразуется в следующий вызов операторной функции operator():
    operator() (99.57, "перегрузка");
    В общем случае при перегрузке оператора "()" определяются параметры, которые необходимо передать функции operator(). При использовании оператора "()" в программе задаваемые при этом аргументы копируются в эти параметры. Как всегда, объект, который генерирует вызов операторной функции (ob в данном примере), адресуется указателем this.
    Рассмотрим пример перегрузки оператора "()" для класса three_d. Здесь создается новый объект класса three_d, координаты которого представляют собой результаты суммирования соответствующих значений координат вызывающего объекта и значений, передаваемых в качестве аргументов.
    // Демонстрация перегрузки оператора "()".
    #include
    using namespace std;
    class three_d {
    int x, y, z; // 3-мерные координаты public:
    three_d() { x = у = z = 0; }
    three_d(int i, int j, int k) {x = i; у = j; z = k; }
    three_d operator()(int a, int b, int c);
    void show();
    };

    // Перегрузка оператора "()".
    three_d three_d::operator()(int a, int b, int c)
    {
    three_d temp;
    temp.x = x + a;
    temp.у = у + b;
    temp.z = z + c;
    return temp;
    }
    // Отображение координат x, y, z.
    void three_d::show()
    {
    cout << x << ", ";
    cout << у << ", ";
    cout << z << "\n";
    }
    int main()
    {
    three_d ob1(1, 2, 3), ob2;
    ob2 = ob1(10, 11, 12); // вызов функции operator()
    cout << "ob1: ";
    ob1.show();
    cout << "ob2: ";
    ob2.show();
    return 0;
    }
    Эта программа генерирует такие результаты.
    ob1: 1, 2, 3
    ob2: 11, 13, 15
    Не забывайте, что при перегрузке оператора "()" можно использовать параметры любого типа, да и сама операторная функция operator() может возвращать значение любого типа.
    Выбор типа должен диктоваться потребностями конкретных программ.
    Перегрузка других операторов
    За исключением таких операторов, как new, delete, ->, ->* и "запятая", остальные С++- операторы можно перегружать таким же способом, который был показан в предыдущих примерах. Перегрузка операторов new и delete требует применения специальных методов,
    полное описание которых приводится в главе 17 (она посвящена обработке исключительных ситуаций). Операторы ->, ->* и "запятая" — это специальные операторы, подробное рассмотрение которых выходит за рамки этой книги. Читатели, которых интересуют другие примеры перегрузки операторов, могут обратиться к моей книге Полный справочник по
    C++.
    Еще один пример перегрузки операторов
    Завершая тему перегрузки операторов, рассмотрим пример, который часто называют квинтэссенцией примеров, посвященных перегрузке операторов, а именно класс строк.
    Несмотря на то что С++-подход к строкам (которые реализуются в виде символьных массивов с завершающим нулем, а не в качестве отдельного типа) весьма эффективен и гибок, начинающие С++-программисты часто испытывают недостаток в понятийной ясности реализации строк, которая присутствует в таких языках, как BASIC. Конечно же, эту ситуацию нетрудно изменить, поскольку в C++ существует возможность определить класс строк, который будет обеспечивать реализацию строк подобно тому, как это сделано в других языках программирования. По правде говоря, "на заре" развития C++ реализация класса строк была забавой для программистов. И хотя стандарт C++ теперь определяет строковый класс, который описан ниже в этой книге, вам будет полезно реализовать простой вариант такого класса самим. Это упражнение наглядно иллюстрирует мощь механизма перегрузки операторов.

    Сначала определим "классовый" тип str_type.
    #include
    #include
    using namespace std;
    class str_type {
    char string[80];
    public:
    str_type(char *str = "") { strcpy(string, str); }
    str_type operator+(str_type str); // конкатенация строк str_type operator=(str_type str); // присваивание строк
    // Вывод строки void show_str() { cout << string; }
    };
    Как видите, в классе str_type объявляется закрытый символьный массив string,
    предназначенный для хранения строки. В данном примере условимся, что размер строк не будет превышать 79 байт. В реальном же классе строк память для их хранения должна выделяться динамически, и это ограничение действовать не будет. Кроме того, чтобы не загромождать логику этого примера, мы решили освободить этот класс (и его функции- члены) от контроля выхода за границы массива. Безусловно, в любой настоящей реализации подобного класса должен быть обеспечен полный контроль за ошибками.
    Этот класс имеет один конструктор, который можно использовать для инициализации массива string с использованием заданного значения или для присваивания ему пустой строки в случае отсутствия инициализатора. В этом классе также объявляются два перегруженных оператора, которые выполняют конкатенацию и присваивание. Наконец,
    класс str_type содержит функцию show_str(), которая выводит строку на экран. Вот как выглядит код операторных функций operator+() и operator=().
    // Конкатенация двух строк.
    str_type str_type::operator+(str_type str) {
    str_type temp;
    strcpy(temp.string, string);
    strcat(temp.string, str.string);
    return temp;
    }
    // Присваивание одной строки другой.
    str_type str_type::operator=(str_type str) {
    strcpy(string, str.string);
    return *this;
    }
    Имея определения этих функций, покажем, как их можно использовать на примере следующей функции main().
    int main()
    {
    str_type а("Всем "), b("привет"), с;
    с = а + b;
    с.show_str();
    return 0;
    }
    При выполнении эта программа выводит на экран строку Всем привет. Сначала она конкатенирует строки (объекты класса str_type) а и b, а затем присваивает результат конкатенации строке c.
    Следует иметь в виду, что операторы "=" и "+" определены только для объектов типа
    str_type. Например, следующая инструкция неработоспособна, поскольку она представляет собой попытку присвоить объекту а строку с завершающим нулем.
    а = "Этого пока делать нельзя.";
    Но класс str_type, как будет показано ниже, можно усовершенствовать и разрешить выполнение таких инструкций.
    Для расширения круга операций, поддерживаемых классом str_type (например, чтобы можно было объектам типа str_type присваивать строки с завершающим нулем или конкатенировать строку с завершающим нулем с объектом типа str_type), необходимо перегрузить операторы "=" и "+" еще раз. Вначале изменим объявление класса.
    class str_type {
    char string{80];
    public:
    str_type(char *str = "") { strcpy(string, str); }
    str_type operator+(str_type str); /* конкатенация объектов типа str_type*/
    str_type operator+(char *str); /* конкатенация объекта класса str_type со строкой с завершающим нулем */
    str_type operator=(str_type str); /* присваивание одного объекта типа str_type другому */
    char *operator=(char *str); /* присваивание строки с завершающим нулём объекту типа str_type */
    void show_str() { cout << string; }
    };
    Затем реализуем перегрузку операторных функций operator+() и operator=().
    // Присваивание строки с завершающим нулем объекту типа str_type.
    str_type str_type::operator=(char *str)
    {
    str_type temp;
    strcpy(string, str);
    strcpy(temp.string, string);
    return temp;
    }
    // Конкатенация строки с завершающим нулем с объектом типа str_type.
    str_type str_type::operator+(char *str)
    {
    str_type temp;
    strcpy(temp.string, string);
    strcat(temp.string, str);
    return temp;
    }
    Внимательно рассмотрите код этих функций. Обратите внимание на то, что правый аргумент является не объектом типа str_type, а указателем на символьный массив с завершающим нулем, т.е. обычной С++-строкой. Но обе эти функции возвращают объект типа str_type. И хотя теоретически они могли бы возвращать объект любого другого типа,
    весь смысл их существования и состоит в том, чтобы возвращать объект типа str_type,
    поскольку результаты этих операций принимаются также объектами типа str_type.
    Достоинство определения строковой операции, в которой в качестве правого операнда участвует строка с завершающим нулем, заключается в том, что оно позволяет писать
    некоторые инструкции в естественной форме. Например, следующие инструкции вполне законны.
    str_type a, b, c;
    a = "Привет всем"; /* присваивание строки с завершающим нулем объекту */
    с = а + " Георгий"; /* конкатенация объекта со строкой с завершающим нулем */
    Следующая программа включает дополнительные определения операторов "=" и "+".
    // Усовершенствование строкового класса.
    #include
    #include
    using namespace std;
    class str_type {
    char string[80];
    public:
    str_type(char *str = "") { strcpy(string, str); }
    str_type operator+(str_type str);
    str_type operator+(char *str);
    str_type operator=(str_type str);
    str_type operator=(char *str);
    void show_str() { cout << string; }
    };
    str_type str_type::operator+(str_type str)

    {
    str_type temp;
    strcpy(temp.string, string);
    strcat(temp.string, str.string);
    return temp;
    }
    str_type str_type::operator=(str_type str)
    {
    strcpy(string, str.string);
    return *this;
    }
    str_type str_type::operator=(char *str)
    {
    str_type temp;
    strcpy(string, str);
    strcpy(temp.string, string);
    return temp;
    }
    str_type str_type::operator+(char *str)
    {
    str_type temp;
    strcpy(temp.string, string);
    strcat(temp.string, str);
    return temp;
    }
    int main()
    {
    str_type а("Привет "), b("всем"), с;
    с = а + b;
    с.show_str();
    cout << "\n";
    а = "для программирования, потому что";
    а.show_str();
    cout << "\n";
    b = с = "C++ это супер";
    с = c + " " + а + " " +b;
    с.show_str();
    return 0;
    }
    При выполнении эта программа отображает на экране следующее.
    Привет всем для программирования, потому что

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

    1   ...   12   13   14   15   16   17   18   19   ...   33


    написать администратору сайта