Операция () также должна быть нестатической функцией класса.
Переопределение операции () позволяет использовать синтаксис вызова функции применительно к объекту класса (имя объекта с круглыми скобками). Количество операндов в скобках может быть любым.
Аргументы и возвращаемое значение операционной функции могут иметь любые типы, допустимые для функций. Функция operator() вызывается путем применения списка аргументов к объекту того класса, в котором он определен.
Перегрузка операции индексирования массива [ ]
Эта операция всегда определяется как нестатический член класса и, так как подразумевается поведение индексируемого объекта как массива, то ей следует возвращать ссылку. Это позволяет использовать эту операцию не только в правой, но и в левой части операции присваивания, облегчая доступ к необходимым элементам данных.
Переопределение операции [] позволяет использовать синтаксис доступа к элементам массива (имя объекта с квадратными скобками).
Перегрузка операции запятая,
В число операций с необычным синтаксисов перегрузки входит также операция запятая. Она вызывается для объектов, между которыми поставлена запятая, но он не вызывается в списках аргументов функций. При перегрузке следует понимать и различать эти случаи.
Операция «запятая» используется для связки нескольких выражений. Она вызывает выполнение последовательности действий. Левая сторона операции «запятая» всегда вычисляется как void (то есть не имеет значения). В результате значение операнда, находящегося с правой стороны от знака операции, станет значением разделенного запятыми выражения. Так в выражении х = (у=3, у+1); в первую очередь переменной y присваивается значение 3, а затем значение 4 присваивается переменной х. Скобки необходимы, поскольку операция «запятая» имеет самый низкий приоритет.
Об операции «запятая» следует думать как об обычном слове «и» в нормальном русском языке, когда оно используется в выражении «сделай это, и это, и это».
Оператор преобразования типа
Для преобразования типа из некоторого класса в какой-либо другой тип должна использоваться перегруженная операция преобразования типа. Она обязательно принадлежит классу и является операторной функцией без аргументов и возвращаемого значения (не возвращает даже void).
Оператор преобразования типа реализует создание объекта типа данных, указанном в его названии, на основании объекта класса, в котором он определен. Каким образом будет выполняться преобразование задает код тела оператора.
Синтаксис определения операции преобразования имеет вид
operator <тип данных>() {<тело операции>}
Преобразование типа необходимо, когда требуется по объекту пользовательского типа получить объект стандартного типа (например из class clname - int).
Помимо приведения пользовательского типа данных (класса) к стандартному, возможно реализовать приведение объекта одного класса к объекту другого класса. Для этого необходимо:
1. В каждом из классов объявить конструктор, аргументом которого является ссылка на объект или значение объекта другого класса.
2. В каждом классе определить оператор преобразования типа, имя которого является названием того класса, к объекту которого приводится объект искомого класса.
Разбор программы
Разработать ООП для подсчета числа различных и совпадающих латинских букв без различия регистра в любой паре их слов, которые заданы аргументами командной строки ее вызова. Программная реализация вычислений должна быть основана на разработке класса подмножества латинских букв, состав которого кодируется в приватном поле двоичными разрядами целого числа без знака. Конструктор класса должен обеспечивать его инициализацию по заданной или пустой строке символов. Кроме того, должна быть предусмотрена компонентная перегрузка операторов “()” и “,” для эффективного вычисления расстояния Хемминга и скалярного произведения бинарных кодов заданных строк при количественной оценке их различия и совпадения, соответственно. При этом операнды скалярного произведения следует заключать в круглые скобки, а скобочная перегрузка Хемминга должна вызываться пустым набором H (из косметических соображений и по формальным правилам). Также в обоих операциях должен применяться эффективный метод подсчета единичных разрядов. Результаты вычислений должны отображаться строками стандартного вывода. Отображение вычислительных формул должны обеспечивать дружественная перегрузка оператора << класса потока стандартного вывода и(или) компонентный оператор преобразования типа латинского набора в адрес строки его символов.
Пример результатов работы программы:
$ AOO XeniX Uetrix
H(einx, eirtux) = 4
(EINX, EIRTUX) = 3
Комментарии к постановке и решению задачи:
Расстояние Хемминга – число позиций, в которых соответствующие символы двух слов одинаковой длины различны.
Пример расчета расстояния Хемминга:
Подсчитываемые символы выделены в словах цветом.
В программе происходит сравнение двух векторов длины 32 разряда (unsigned int = 4 байта = 32 бита), таким образом обеспечивается условие равенства длин сравниваемых векторов. Кодировка исходных слов обеспечивает, что одноименные буквы будут находиться в идентичных позициях исходных векторов.
Так кодировка слов XeniX и Uetrix будет выглядеть следующим образом:
z | y | x | w | u | v | t | s | r | q | p | o | n | m | l | k | j | i | h | g | f | e | d | c | b | a | ||||||
Красным выделены позиции, по которым слова не совпадают и учитываются при подсчете, а желтым – по которым слова совпадают между собой.
Для определения таковых позиций в программе используется оператор ^ - побитовое исключающее или, которое имеет следующую таблицу истинности (1 в тех позициях, в которых значения бит различны):
Скалярное произведение в контексте данного задания определяет количество позиций по которым векторы совпадают между собой.
z | y | x | w | u | v | t | s | r | q | p | o | n | m | l | k | j | i | h | g | f | e | d | c | b | a | ||||||
Красным выделены позиции, по которым слова совпадают и учитываются при подсчете, а желтым – по которым слова не совпадают между собой.
Для определения таковых позиций в программе используется оператор & - побитовое И, которое имеет следующую таблицу истинности (1 в тех позициях, в которых оба бита имеют значение 1):
В методе быстрого подсчета единичных разрядов реализована следующая идея.
Если число уменьшить на единицу, то в его бинарном коде самый правый единичный разряд обратится в 0, все стоящие правее него разряды станут единичными, а разряды, стоящие слева, останутся неизменными. Это связано с необходимостью увеличения числа разрядов при увеличении максимального для заданного количества разрядов числа
(1 1 1 1 2 + 1 --–> 1 0 0 0 0 2)
(1 0 0 0 0 2 – 1 --–> 1 1 1 1 2)
Например,
140010 = 1 0 1 0 1 1 1 1 0 0 0 2 - 1 =
139910 = 1 0 1 0 1 1 1 0 1 1 1 2
Если впоследствии выполнить операцию побитового И между числом и числом, на единицу меньше его, то все разряды, начиная с последнего единичного в исходном числе и правее обнулятся (так как они различны), а все разряды левее останутся как в исходном числе. Например,
140010 = 1 0 1 0 1 1 1 1 0 0 0 2
&
139910 = 1 0 1 0 1 1 1 0 1 1 1 2
__________________________
= 1 0 1 0 1 1 1 0 0 0 0 2
Таким образом, количество единиц в исходном числе уменьшится на 1.
Если выполнять данную последовательность операций (уменьшить на единицу и выполнить операцию побитового И) до тех пор пока в числе имеются единичные разряды, то количество таких операций будет равно числу единичных разрядов.
#include <stdlib.h>
#include <iostream>
using namespace std;
class Alpha // класс множества латинских букв
{
private:
unsigned bin; // бинарный код подмножества латинских букв
public:
Alpha () { bin=0; }; // конструктор по умолчанию
Alpha (char*); // конструктор инициализации по стороке
int operator, (Alpha&); // перегразка оператора,
int operator () (Alpha&, Alpha&); // перегрузка оператора ()
int pop (unsigned); // быстрый подсчет единичных разрядов
friend ostream& operator << (ostream&, Alpha&); // перегрузка оператора <<
operator char*(); // перегрузка оператора приведение типа
};
// конструктор множества букв по строке
Alpha::Alpha (char* s)
{
bin=0;
while(*s)
{
bin |= (1 << (tolower(*s)-'a'));
s++;
}
}
// оператор вывода подмножеств букв
ostream& operator << (ostream& out, Alpha& z)
{
unsigned bit=1;
int i;
for (i=0; i<26; i++)
{
if((z.bin & bit) > 0)
out << (char)('a'+i);
bit = bit << 1;
}
return out;
}
//оператор преобразования множества в строку
Alpha::operator char* ()
{
static char s[32];
unsigned b=bin;
int i=0;
int j=0;
while(b>0)
{
if (b & 1)
s[j++] = 'A' + i;
i++;
b >>= 1;
}
s[j] = 0;
return (s);
}
// скалярное произведение
int Alpha::operator, (Alpha& y)
{
return pop(bin & y.bin); // ^ - оператор побитового И
// pop – функция быстрого подсчета ед. разрядов
}
// расстояние Хемминга
int Alpha::operator () (Alpha& x, Alpha& y)
{
return pop(x.bin ^ y.bin); // ^ - оператор побитового исключающего ИЛИ
// pop – функция быстрого подсчета ед. разрядов
}
// быстрый подсчет единичных разрядов
int Alpha::pop(unsigned b) // в функцию передается беззнаковое число
{
int i=0; // счетчик единичных разрядов
while (b!= 0) // пока в числе имеются 1 разряды
{
b = b & (b - 1); // число уменьшается на 1 и применяется &
i++; // счетчик разрядов увеличивается на 1
}
return (i); // возврат числа единичных разрядов
}
//основная функция
int main (int argc,char* argv[])
{
Alpha A(argv[1]); // буквы 1-го аргумента
Alpha B(argv[2]); // буквы 2-го аргумента
Alpha H; // пустой набор – интерфейсный объект
// операции вычисления расстояния Хемминга
int d = H(A, B); // вычисление расстояния Хемминга
// =H.operator () (A, B);
int s = (A, B); // вычисление скалярного произведения
// =A.operator, (B);
cout << "H (" << A << ", " << B << ") = " << d << "\n";
// красным выделен перегруженный
// в классе оператор <<
cout << "(" << (char*) A; // красным выделен перегруженный
cout << ", " << (char*) B; // в классе оператор приведения типа
cout << ") = " << s << "\n";
return (0);
}