Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

Подписываем
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предоплата всего
Подписываем
PAGE 20
Указатель this
Каждый раз при создании объекта класса строится указатель, называемый this, и содержащий адрес этого объекта. Правильнее сказать , указатель определяется не в момент создания объекта , а в момент вызова любого из методов объекта.
В связи с неявным определением this является константным указателем, т.е. по умолчанию происходит определение:
имя класса *const this = адрес обрабатываемого объекта
При работе с компонентами класса можно использовать указатель this
Эквивалентно:
Class point { int x, y ; Class point { int x, y ;
public: public:
point( int xx=0, int yy=0) point( int xx=0, int yy=0)
{this-> x=xx ; this ->y =yy ;} ; { x=xx ; y =yy ;} ;
void print ( void) void print ( void)
{ cout<< this->x <<” “ << this->y;} ; {cout<< x <<” “ <<y;} ;
} ; };
В таком использовании нет никаких преимуществ.
Иногда используется при конфликте имен, когда имена формальных параметров функций совпадают с именами компонентов класса:
Class point { int x, y ; Class point { int x, y ;
public: public:
point( int x=0, int y=0) point( int x=0, int y=0)
{this-> x=x ; this ->y =y ;} ; { point::x=x ; point::y =y ;} ;
// используя this // используя квалифицированное имя
…
Часто в функциях - членах класса параметры функции имеют объект некоторого класса (или указатель на объект), а объект еще не существует. Для этого также используется указатель this, обозначающий указатель на тот объект, для которого вызывается данная функция .
Class A
{ int x, y ;
public:
A ( int xx=0, int yy =0){ x=xx ; y = yy; }
A func ( ) ;
…
}
A A :: func( ) { if ( x%2) x++; // функция x преобразующая в четное
return * this;
}
void main ()
{ A a1 (17, 55)
A a2 = a1 . func ();
}
/* Можно объявить так
A* A:: func ( )
{ …
return this} */
Чаще всего this используется при организации связанных списков, звеньями которых должны быть объекты класса и встает необходимость включать в связи указатель на тот объект, который в данный момент обрабатывается
Пример: Очередь
#include <iostream.h>
#include<stdio.h>
Class que
{ static que*first ; // указатель(адрес)первого элемента очереди
que*next ; // указатель на следующий элемент очереди
char bukva; //содержимое элемента очереди
public: // общедоступные функции
que(char c) { bukva = c } ; // конструктор
void add (void) ; // функция добавления элемента в очередь
static void print (void); // вывод содержимого очереди
}
// определения функций:
void que::add(void)
{ que* list= first ; // вспомогательный указатель устанавливается на
// начало очереди
que * uk ; // вспомогательный указатель для продвижения по
//очереди
while(list!=NULL) { uk = list ; list=list->next }//продвижение по очереди
if( uk!=NULL) {uk->next=this; }//присоединение в конец очереди
else first = this; // очередь пустая
this->next=NULL; }
void que::print (void)
{ que *list = first ; // устанавливаем на начало очереди
if ( list = = NULL) {cout << “ список пуст”; return; }
else cout<<”содержимое списка :”
while( list!=NULL)
{ cout<< list->bukva; list= list->next; }//выводим и продвигаемся по
// очереди
}
que * que :: first = NULL; //инициализация статического компонента
void main( )
{ //формируем объекты класса
que A( a) ; que B(b ) ; que C(c); que D(d);
que::print( ); // выводим фразу, что список пуст
A.add( ); B.add( ); C.add( ); D.add( );//включаем в список
// элементы
que::print( ); } //выводим список
Перегрузка функций
С++ позволяет определить в программе произвольное количество функций с одним именем, при условии что все они имеют разный состав параметров , или сигнатуру.
void F ( int );
void F (int, int);
void F ( char* );
При вызове перегруженной функции компилятор анализирует ее сигнатуру и выбирает из списка одноименных функций ту функцию, сигнатура которой соответствует вызываемой.
При этом возвращаемый функцией тип значения не имеет
void F ( int)
int F (int)
не являются перегруженными, компилятор их не различает.
Для того чтобы различать одноименные функции компилятор использует кодирование имен функций (уточненные имена). Функциям даются имена, в которые входят имя класса, если эта функция компонентная, имя функции и список параметров.
class MyClass { …
void Func ( int ); @MyClass@Func$qi
void Func (int, int); @MyClass@Func$qii
void Func ( char* ); @ MyClass@Func$qpzc
… };
Как правило, для глобальных функций используется перегрузка, когда данные и код для их обработки различны, но при этом удобно, чтобы функции назывались одинаково.
Если различаются только типы данных, то удобнее использовать шаблон.
Перегрузка глобальных функций:
#include<iostream.h>
void Print ( int i) { cout<<”\n int = ” <<i; }
void Print ( int * pi ) { cout<<”\n pointer = ” <<pi<< “, значение =”<<*pi; }
void Print ( char*s) { cout<<”\n string = ” <<s; }
void main ()
{ int a=2 ;
int* b = & a;
char *c= “yyyy”
char d [] =”xxxx”
Print(a); Print(b); Print(c); Print (d);
}
Результат:
int=2
pointer =0x12340ffe , значение = 2
string = yyyy
string = xxxx
Перегрузка функций методов класса
…
class Men {
char* name;
int age;
public:
void Name (char* n) { name=n;} // перегруженная функция Name для
//инициализации
char* Name ( ) { return name; } // перегруженная функция для Name
//чтения
void Age ( int a ) {age = a; } // перегруженная функция Age для
//инициализации
int Age ( ) { return age ;} // перегруженная функция Age для
// чтения
void main ( )
{ Men*m = new Men;
m->Name(“ Ivans”);
m-> Age(45);
cout<< m->Name ( )<< “ “ <<m->Age( );
}
Перегрузка функций действует в пределах их видимости, в пределах класса.
Функции Name( ) или Age( ) , объявленные в другом классе, - это совсем другие функции.
Перегрузка обычных функций не дает, каких либо существенных преимуществ, это просто удобно.
Перегрузка конструкторов
Перегрузка конструкторов приобретает особое значение, из-за того, что конструктору нельзя назначать произвольное имя, оно всегда должно совпадать с именем класса.
Наличие в классе нескольких конструкторов , имеющих одно имя, но разный состав параметров, возможно благодаря реализации средства перегрузки функций.
Таким образом, перегрузка для конструкторов просто неизбежна.
Мы уже рассматривали несколько конструкторов, которые могут быть включены в класс
class A {
int x , y ;
public:
A ( int, int ); // конструктор с параметрами для инициализации
// данных
A( ); // конструктор по умолчанию
A ( A& ) ; // конструктор копирования
… } ;
Часто при разработке класса предусматривается несколько вариантов инициализации объектов и значит должно быть несколько конструкторов с параметрами:
Class Window {
int x; // х-координата начала окна
int y ; // y-координата начала окна
int w ; // ширина окна
int h ; // высота окна
Window ( ) // конструктор по умолчанию
{ x = y = 0; w = h= 100 ; }
Window ( int w1 , int h1 ) // конструктор инициализации размеров окна
{ x = y = 0 ; w = w1; h = h1 ; }
Window ( int x1 , int y1 , int w1, int h1 ) // инициализация положения и
{ x = x1; y = y1 ; w = w1 ; h = h1 } //размеров окна
Window ( Window & win ) // конструктор копирования данных окна
{ x = win.x; y = win.y ; w = win.w ; h = win.h ; }
} ;
…
Window w1;
Window w2 ( 300 , 250) ;
Window w3 (5 ,10 , 400 , 300 )
Window w4 = w3;
Перегрузка стандартных операций
Перегрузка операций - это распространение действий стандартных операций на операнды, для которых эти операции не предполагались или предание стандартным операциям другое назначение.
В синтаксисе языка С++ такого правила нет.
Например, сложение комплексных чисел предполагает почленное сложение действительных и мнимых частей чисел слагаемых. Обычная операция + умеет складывать только два числа, а не пару чисел.
Хотелось бы распространить эту операцию для класса комплексных чисел, т. е. придать ей несколько другой смысл, перегрузить операцию.
Классы дают такую возможность!
Если операнды операции (или хотя бы один) объекты некоторого класса, т.е. введенного пользователем типа, можно использовать специальную функцию “операцию функцию”, определяющую новое поведение операции.
Формат определения операции-функции:
<Тип возвращаемого значения > operator <знак операции>
( спецификация формальных параметров)
{ тело операции-функции }
Механизм перегрузки операций во многом схож с механизмом определения функций, если принять что
Часто перегрузка операции не меняет общий смысл операции, однако в некоторых случаях перегрузка совершенно изменяет назначение операции.
Так стандартные операции “>>” и “ << “ - это битовые сдвиги, однако при использовании их с потоковыми объектами эти операции приобретают смысл “ извлечения из потока или переслать в поток ”.
Большинство операций перегружаемы, однако, не все.
Операции, не допускающие перегрузки:
. - операция доступа к элементу класса
.* - операция доступа к указателю на элемент класса
?: - условная операция
: : - операция разрешения области видимости
sizeof - размер объекта
# - препроцессорное преобразование строк
## - препроцессорная конкатенация строк.
Перегрузку можно проводить тремя вариантами:
2) операция функция - глобальная функция :
а) операция - функция является дружественной функцией класса;
б) операция - функция является недружественной функцией класса, но
хотя бы один параметр функции (недружественной) был бы объектом некоторого класс или ссылкой на объект.
Количество параметров операции-функции определяется арностью операции и тем, является ли функция глобальной или компонентной.
Рассмотрим все эти случаи на примере перегрузки операция сложения + для объектов некоторого класса А.
При этом функция не должна быть статической, так как будет вызываться, и обрабатывать обычные (не статические) данные конкретного объекта.
При определении компонентная функции имеет на один параметр меньше, чем арность операции. Первым операндом такой операции по умолчанию является тот объект, для которого вызывается функция.
Определим такую компонентную функцию внешне, а в классе представим только прототип функции:
Class A {
…
A operator + ( A obj );
…
};
A A :: operator + ( A obj)
{тело перегрузки}
Пусть B , C , D - объекты класса A , выражение B = C+D следует трактовать как вызов компонентной функции с именем operator+ для объекта C:
B= C.operator+ (D);
Таким образом, С это тот объект, для которого вызывается операция функция, а объект D ее параметр.
Бинарная операция “+ “ выглядит не симметрично.
а) дружественной функцией классу A:
сlass A { …
friend A operator + ( A obj1, A obj2);
};
A operator + ( A obj1, A obj2)
{ тело функции перегрузки}
б) не является дружественной функцией классу A:
class A { … };
A operator + ( A obj1, A obj2)
{ тело функции перегрузки}
Если B, C и D объекты класса А, выражение B= C+D трактуется как вызов:
B= operator+ (C,D);
где operator+ рассматривается как имя функции, C и D ее параметры.
В случае 2) перегрузка симметрична относительно слагаемых.
Следует помнить при написании тела перегрузки, что дружественные функции имеют доступ к закрытым данным класса, а обращение к закрытым данным класса из обычной внешней функции не возможно. Поэтому для перегрузки, как правило, используют дружественные функции.
Рассмотрим перегрузку ряда операций для класса Complex:
class Complex {
float re , im ;
public:
Complex ( float, float ) ; // конструктор для инициализации
friend float real (Complex); // функция друг для получения re
friend float image (Complex); // функция друг для получения im
Complex operator ( ) ;// перегрузка одноместной операции ”-” , метод
//класса
Complex operator ( Complex&) ; // перегрузка двуместной операции
// ”-” , метод класса
friend Complex operator + ( Complex&, Complex&) ; // перегрузка “+”-
//друг
};
Complex :: Complex (float r , float i )
{re = r ; im = i ; }
Complex Complex:: operator-( )
{ return Complex (-re, -im); }
Complex Complex :: operator-( Complex& z) // передача по ссылке , чтобы
{ return Complex (re z.re, im- z.im); } // не копировать объект в стек
Complex operator+( Complex &z1, Complex &z2)
{ return Complex (z1.re + z2.re , z1.im+ z2.im ) ; }
float real (Complex z){ return z.re ; }
float image (Complex z ){ return z.im ;}
void main ( )
{
Complex c1 (1, 2) ; // создание объекта c1
Complex c2 (3, 4) ; // создание объекта c2
Complex c3 = - c2 ; // копирование в c3 результата вызова c2.operator-( ); Complex c4 =c2-c1; // копирование в c4 результата вызова c2.operator- ( с1 );
Complex c5= c2+c1// копирование в c5 результата вызова operator+( с2,с1 );
Видим в двух последних строках, что синтаксис правил использования функций - перегрузки операций, определенных как методы класса или как друзья класса, совершенно одинаков.
Однако определение операций-функций как дружественных создает преимущество в смысле симметрии и, главное, в тех случаях, когда для выполнения действий над операндами требуется преобразование типа операндов.
Дело в том, что компилятор автоматически выполняет преобразование типа для аргументов функций, но не для объекта, для которого вызывается функция-член.
Если функция-оператор (другое название операции - функции) реализуется как друг и получает оба аргумента как параметры функции, то компилятор выполняет автоматическое преобразование типов двух аргументов операции.
Пример :
1. #include<string.h>
#include <iostream.h>
class stroka
{ char*ch; // указатель на строку на символьный массив
int len ; // длина строки
public:
stroka( char*cch) // конструктор1
{ len = strlen(cch) ; ch=new char [len+1]; strcpy (ch,cch); }
stroka(int N=20) // конструктор 2
{ ch = new char[N+1]; len=0; ch[0]='\0' ;}
int len_str(void) { return len;} // возвращает длину строки
char * string (void) {return ch;} // возвращает указатель на строку
void vivod ( ) // выводит данные
{ cout<< "строка: " <<ch <<" , длина строки="<<len;};
~stroka( ) // деструктор
{ delete [ ]ch ; }
stroka& operator+ (stroka& ); //прототип функции-оператора - метод
} ;
stroka& stroka::operator+ (stroka&A)
{ int N = len+ A.len_str();
stroka*ptr=new stroka(N); // создаем динамический объект
// выделяем память на суммарную строку
strcpy( ptr->string() , ch ) ; // копируем в новую строку первую строку
strcat (ptr->string() , A.string () ) ; // присоединяем к ней и вторую строку
return *ptr ; // возвращаем объект, а не указатель на объект
}
void main ( )
{
stroka X ("Миру -"); // объявлена три объекта
stroka Y(" мир!");
stroka Z;
Z=X+Y ; // эквивалентно Z =X.operator + (Y);
Z.vivod( ) ;
(X+Y).vivod();
}
2. #include<string.h>
#include <iostream.h>
class stroka {
char*ch; // указатель на строку на символьный массив
int len ; // длина строки
public:
stroka( char*cch) // конструктор1
{ len = strlen(cch) ; ch=new char [len+1];
strcpy (ch,cch); }
stroka(int N=20) // конструктор 2
{ ch = new char[N+1];
len=0;
ch[0]='\0' ;}
int len_str(void) { return len;} // возвращает длину строки
char * string (void) {return ch;} // возвращает указатель на строку
void vivod ( ) // выводит данные
{ cout<< "строка: " <<ch <<" , длина строки="<<len;};
~stroka( ) // деструктор
{ delete [ ]ch ; }
friend stroka& operator+(stroka&, stroka&);//функция-операция друг
} ;
stroka& operator+(stroka&A, stroka & B)
{ int N = A.len_str ()+ B.len_str();
stroka*ptr=new stroka(N);
// выделяем память на суммарную строку
strcpy( ptr->string() , A.string() ) ;// копируем в новую строку первую строку
strcat(ptr->string() , B.string () ) ; // присоединяем к ней и вторую строку
return *ptr ; //возвращаем оъект , а не указатель на объект ( разыменовали)
}
void main ( )
{
// прототип операции-функции
// stroka& operator+(stroka&A, stroka&B);
stroka X ("Миру -"); // объявлена три объекта
stroka Y(" мир!");
stroka Z;
Z=X+Y + " Нет войне!";// эквивалентно Z =operator + (X,Y);
// Z = operator + (Z , " Нет войне!");
Z.vivod( ) ; }
В предпоследней строке происходит автоматическое преобразование типа строки “Нет войне” к типу stroka, то есть создается безвмянный временный объект.
Если бы мы использовали функцию-операцию, представленную как компонентную и строка “Нет войне” стояла бы на первом месте, то компилятор рассматривая ее как объект, для которого вызывается функция операция, вывел бы сообщение об ошибке ( illegal structure operation).
Таким образом, дружественные функции операции позволяют правильно произвести преобразование типа.
Рассмотрим еще несколько важных особенностей механизма перегрузок (расширения действия ) стандартных операций С++:
1) C++ запрещает вводить операции с новым обозначением.
2) Нельзя изменить приоритет стандартной операции, перегрузив ее.
3) Нельзя изменять арность операции.
4) Перегрузка бинарной операции определяется либо как компонентная функция класса с одним параметром, либо как внешняя функция, возможно дружественная, с двумя параметрами:
выражение: X <операция>Y
соответствует вызовам:
X. operator <операция> (Y) // если операция-функция - метод класса
или:
operator <операция> (X, Y) //если операция-функция -внешняя
5) Перегрузка унарной операции определяется либо как компонентная функция без параметра, либо как внешняя функция, возможно дружественная, с одним параметром:
выражение : <операция> X
соответствует вызовам :
X. operator <операция> ( ) // если операция-функция - метод класса
или:
operator <операция> (X ) // если операция-функция внешняя
Рассмотрим перегрузку унарной операции ““ для класса “вектор”:
class vector
{
int N // размерность пространства
double*x // указатель на массив координат
friend vector& operotor (vector&) ;
public:
vector (int n , double * xn) // конструктор
{N=n ; x=xn;}
void vivod()
{for(int i =0 ; i<N; i++)
cout<<”\t”<<x[i];}
};
vector& operotor (vector&v) // определение функции-операции
{for(int i=0; i< v.N; i++)
v.x[i]=-v.x[i];
return v; }
void main( )
{double A []= {1.0, 2.0, 3.0}
vector v(3,A);// инициируем объект
v.vivod
v=-v;
v.vivod;}
Рассмотрим перегрузку операций инкремента (++) и декремента (- -), которые могут быть префиксными и постфиксными .
Принято соглашение, что префиксные операции (++) и (- -), ничем не отличаются от обычной перегрузки унарных операций. Т.е. дружественные функции перегрузки содержат один параметр, а компонентные функции перегрузки не имеют параметров и определяют те же префиксные операции.
Постфиксные операциифункции должны иметь еще один дополнительный параметр типа int и тогда компонентная функция перегрузки имеет только один параметр int , а дружественная два первый типа класс, а второй типа int .
Операция функция вызывается с нулевым целым параметром.
Рассмотрим перегрузку префиксных и постфиксных операций для класса “пара чисел”, при этом перегрузку операции инкремента произведем с помощью дружественной функции-операции, а декремента с помощью компонентной функции- операции.
#include <iostream.h>
class pair {
int N
double X
public:
pair ( int n , double x)
{ N=n; X= x; }
friend pair & operator ++ ( pair &) ; // префиксная
friend pair & operator++ ( pair& , int ) ; // постфиксная
pair& operator- - ( ) // префиксная
{ N= N-1 ; X - = 1.0 ;
return * this ; }
pair& operator- - ( int k ) // постфиксная
{ N= N-1+k ; X - = 1.0 +k;
return * this ;}
void display( )
{ cout<< “\n N = “ <<N <<” X= “ << X ; }
} ;
pair & operator ++ ( pair & P) // префиксная
{ P. N + = 1 ; P.X +=1.0 ;
return P; }
pair & operator ++ ( pair & P, int k) // постфиксная
{ P. N + = 1+k ; P.X +=1.0 +k ;
return P; }
void main ( )
{ pair A ( 9, 19.0); A.display( );
++A; A.display ();
- - A; A.display ();
A++ ; A.display ();
A- - ; A.display (); }
Результат:
N = 9 X = 19
N = 10 X = 20
N = 9 X = 19
N = 10 X = 20
N = 9 X = 19
Перегрузка операции присваивания
Перегрузку операции ”=” разрешается реализовывать только через компонентную функцию класса!
Рассмотрим перегрузку для класса комплексных чисел:
Complex& Complex :: operator = ( Complex & z)
{ re = z.re ; im = z. im ;
return * this ; }
При выполнении
c2=c1 ;
операция функция вызывается для объекта с2 , объект с1 передается в нее как параметр. Данным объекта с2 присваиваются данные объекта с1.
Функция возвращает текущий объект для того, чтобы можно было использовать операцию множественного присваивания :
с3 = с2 = с1 ;
Заметим, что в данном случае перегружать операцию присваивания нет особой необходимости. Если не перегружать “=” , то компилятор генерирует операцию присваивания по умолчанию, которая выполняет побайтовое копирование данных.
Однако в некоторых случаях, при наличии в классе данных, являющихся указателями на динамические участки памяти, буквальное копирование может привести к утечкам памяти.
При прямом копировании объект2=объект1 указателю объекта2 присвоится значение указателя объекта1. Участок памяти с данными 2 станет недоступным программе происходит утечка памяти. Уничтожение объекта 1 автоматически уничтожит объект2.
В этом случае необходимо перегрузить операцию присваивания:
class A { int* data;
…
A ( int a)
{ data = new int;
*data= a; }
A ( A&z)
{ data = new int;
data=*(z.data);}
A& operator= ( const A & z)
{delete data; или { *data = *(z.data);
data = new int; return *this ;}
*data = *(z.data);
return * this}
…
} ;
Различие между копированием и присваиванием:
Дублирование объекта может быть выполнено и с помощью операции присваивания и с помощью вызова конструктора копирования.
class A { int x;
public:
A ( int ax=0){ x=ax; }
int GetX ( ) { return x; }
… } ;
void main ()
{ A m1(5) , m2;
m2 = m1; // операция присваивания
A m3 = m1 ; // дублирование вызовом конструктора копирования
cout<< m1.GetX ( ) << m2.GetX ( ) << m3.GetX ( );
}
В предложении:
m2 = m1 ;
передача значений выполняется в уже созданный объект. Дублирование объекта производится операцией присваивания по умолчанию.
В предложении:
A m3 = m1 ;
создается новый объект, и ему передаются данные копируемого объекта. В этом случае вызывается конструктор копирования по умолчанию.
В ряде случаев объекты должны создаваться в единственном экземпляре, создание идентичных объектов должно быть запрещено.
Блокировка копирования и присваивания:
Для того, чтобы исключить непреднамеренное дублирование объекта класса, надо в его закрытой части объявить конструктор копирования и перегрузку операции присваивания.
private :
A ( A& );
A operator = ( A &) ;
И тогда ни одно из приведенных в главной функции дублирований объектов скомпилировать не удастся.
Преобразование типов в классах пользователя
Иногда в выражениях целесообразно использовать переменные класса (объекты) с переменными других типов, для этого необходимо определить правила преобразования.
Это можно сделать с помощью перегруженного конструктора класса или с помощью операции функции преобразования типов.
Рассмотрим опять класс Complex.
Недостаток описания состоит в том, что нельзя выполнять комплексные операции комплексного числа с арифметическими данными:
Complex c1( 5.5, 6.5);
Complex c2= c1+ 2;
Complex c2= c1+ 0;
В этом случае компилятор будет пытаться создать временный объект Complex (2), но наш конструктор с двумя параметрами, поэтому скомпилировать нам это не удастся.
Надо объявить конструктор такого вида:
Complex (float r =0, float i =0)
{ re = r ; im = i ; },
который позволит, например, создавать объекты:
Complex c1( 1.5, 2.5);
Complex c1( 4.5);
Complex c1;
Рассмотрим операторы описанные выше:
В результате создается объект с2 с данными (7.5, 6.5).
2) 0 преобразуется в целое 48, которое преобразуется в вещественное
48.0, создается временный объект с данными ( 48.0, 0.0).
В результате создается объект с данными (53.5, 6.5).
Иногда бывает необходимость преобразовать переменные некоторого класса в базовые типы.
Проблема решается с помощью специального оператора преобразования типа.
Рассмотрим это преобразование для класса stroka. Преобразование типа stroka в тип char*.
Чтобы объекты класса можно было бы передавать функциям модуля string.h, надо в описание класса включить компонентную функцию:
1)
operator char* ( ) { return ch; } (1)
И тогда в предложении
stroka s1(“string”)
char* s = s1;
произойдет преобразование объекта s1 к типу char* , правило (1), и переменной s присвоится значение“string”.
2)
Аналогично можно определить в классе преобразование:
operator int ( ) { return len; } (2)
И тогда в предложении :
int l = s1;
переменная класса (объект s1) преобразуется к целому значению по правилу описанному в операторе преобразования (2).
Вопросы: