246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 78
Текст из файла (страница 78)
Это объявление можно размещать в любом месте объявлениякласса,чтонеизменитегосуть.Врезультатеобъявленияклассакакдругавсезакрытыеметодыи переменные-члены класса PartNode становятся доступными любой функции-члену классаPartsList.В строке 160 были внесены изменения в вызове функции-члена GetFirst() с учетомпоявившихся новых возможностей. Теперь вместо возвращения pHead->GetPart эта функцияможетвозвращатьзакрытуюпеременную-членpHead->itsPart.АналогичнымобразомвфункцииInsert() можно написать pNode->itsNext = pHead вместо переменной-члена pHead>SetNext(pHead).Вданномслучаевнесенныеизменениясущественнонеулучшиликодпрограммы,поэтомунет особых причин делать класс PartsList другом PartNode.
В данном примере просто хотелосьпроиллюстрировать,какработаетключевоесловоfriend.Объявление классов-друзей следует применять с осторожностью. Класс объявляется какдруг какого-либо иного класса в том случае, когда два класса тесно взаимодействуют друг сдругом и открытие доступа одного класса к данным и методам другого класса существенноупрощаеткодпрограммы.Однакозачастуюпрощеорганизоватьвзаимодействиемеждуклассамиспомощьюоткрытыхметодовдоступа.Примечание:От начинающих программистов C++ часто можно услышать замечание,что объявление классов-друзей противоречит принципу инкапсуляции, лежащему в основеобъектно-ориентированного программирования.
Это, честно говоря, довольно широкораспространенная бессмыслица. Объявление класса-друга просто расширяет интерфейсдругогокласса,чтовлияетнаинкапсуляциюнебольше,чемоткрытоенаследованиеклассов.ДружественныйклассОбъявление одного класса другом какого-либо иного с помощью ключевого слова friend вобъявлении второго класса открывает первому классу доступ к членам второго класса. Инымисловами, я могу объявить вас своим другом, но вы не можете объявить себя моим другом.Пример:classPartNode{public:friendclassPartsList://обьявлениеклассаPartsListдругомPartNodeФункциидрузьяИногдабываетнеобходимопредоставитьправадоступаневсемуклассу,атолькооднойилинескольким функциям-членам.
Это реализуется посредством объявления друзьями функцийчленовдругогокласса.Причемобъявлятьдругомвеськлассвовсенеобязательно.Фактическидругомможнообъявитьлюбуюфункцию,независимооттого,являетсялионафункцией-членомдругогоклассаилинет.ФункциидрузьяиперегрузкаоператораВлистинге15.1представленклассString,вкоторомперегружаетсяoperator+.Внемтакжеобъявляется конструктор, принимающий указатель на константную строку, поэтому объектклассаStringможносоздаватьизстрокисконцевымнулевымсимволом.Примечание:СтрокивСиC++представляютсобоймассивысимволов,заканчивающиесяконцевым нулевым символом.
Такая строка получается, например,в следующем выраженииприсвоения:myString[]="HelloWorld".Но чего невозможно сделать в классе String, так это получить новую строку в результатесложенияобъектаэтогоклассасмассивомсимволов:charcString[]={"Hello"};StringsString("Worid");StringsStringTwo=cString+sString;//ошибка!Строки нельзя использовать с перегруженной функции operator+.
Как объяснялось назанятии 10, выражение cString + sString на самом деле вызывает функцию cString.operator+(sString). Поскольку функция operator+() не может вызываться для символьной строки, даннаяпопыткаприведеткошибкекомпиляции.Этупроблемуможнорешить,объявивфункцию-другавклассеString,котораяперегружаетoperator+такимобразом,чтобысуммироватьдваобъектаString.Соответствующийконструкторкласса String преобразует строки в объекты String, после чего вызывается функция-другoperator+,выполняющаяконкатенациюдвухобъектов.Листинг15.8.Функция-другoperator+1://Листинг15.8.Операторыдрузья2:3:#include<iostream.h>4:#include<string.h>5:6://Рудиментарныйклассstring7:classString8:{9:public:10://constructors11:String();12:String(constchar*const);13:String(constString&);14:~String();15:16://перегруженныеоператоры17:char&operator[](intoffset);18:charoperator[](intoffset)const;19:Stringoperator+(constString&);20:friendStringoperator+(constString&,constString&);21:voidoperator+=(constString&);22:String&operator=(constString&);23:24://методыобщегодоступа25:intGetLen()const{returnitsLen;}26:constchar*GetString()const{returnitsString;}27:28:private:29:String(int);//закрытыйконструктор30:char*itsString;31:unsignedshortitsLen;32:};33:34://конструктор,заданныйпоумолчанию,создаетстрокудлиной0байт35:String::String()36:{37:itsString=newchar[1];38:itsString[0]='\0';39:itsLen=0;40://cout<<"\tDefaultstringconstructor\n";41://ConstructorCount++:42:}43:44://закрытыйконструктор,используемыйтолько45://методамиклассадлясозданияновойстроки46://указанногоразмера,заполненнойнулями.47:String::String(intlen)48:{49:itsString=newchar[len+1];50:for(inti=0;i<=len;i++)51:itsString[i]='\0';52:itsLen=len;53://cout<<"\tString(int)constructor\n";54://ConstructorCount++;55:}56:57://Преобразуетмассивсимволоввстроку58:String::String(constchar*constcString)59:{60:itsLen=strlen(cString);61:itsString=newchar[itsLen+1];62:for(inti=0;i<itsLen;i++)63:itsString[i]=cString[i];64:itsString[itsLen]='\0';65://cout<<"\tString(char*)constructor\n";66://ConstructorCount++;67:}68:69://конструктор-копировщик70:String::String(constString&rhs)71:{72:itsLen=rhs.GetLen();73:itsString=newchar[itsLen+1];74:for(inti=0;i<itsLen;i++)75:itsString[i]=rhs[i];76:itsString[itsLen]='\0';77://cout<<"\tString(String&)constructor\n";78://ConstructorCount++;79:}80:81://деструктор,освобождаетзанятуюпамять82:String::~String()83:{84:delete[]itsString;85:itsLen=0;86://cout<<"\tStringdestructor\n";87:}88:89://этотоператоросвобождаетпамять,азатем90://копируетстрокуиразмер91:String&String::operator=(constString&rhs)92:{93:if(this==&rhs)94:return<<this;95:delete[]itsString;96:itsLen=rhs.GetLen();97:itsString=newchar[itsLen+1];98:for(inti=0;i<itsLen;i++)99:itsString[i]=rhs[i];100:itsString[itsLen]=1\0';101:return*this;102://cout<<"\tStringoperator=\n";103:}104:105://неконстантныйоператориндексирования,106://возвращаетссылкунасимвол,которыйможно107://изменить!108:char&String::operator[](intoffset)109:{110:if(offset>itsLen)111:returnitsString[itsLen-1];112:else113:returnitsString[offset];114:}115:116://константныйоператориндексирования,117://используетсядляконстантныхобъектов(см.конструктор-копировщик!)118:charString::operator[](intoffset)const119:{120:if(offset>itsLen)121:returnitsString[itsLen-1];122:else123:returnitsString[offset];124:}125://создаетновыйобъектString,добавляя126://текущийобьекткrhs127:StringString::operator+(constString&rhs)128:{129:inttotalLen=itsLen+rhs.GetLen();130:Stringtemp(totalLen);131:inti,j;132:for(i=0;i<itsLen;i++)133:temp[i]=itsString[i];134:for(j=0,i=itsLen;j<rhs.GetLen();j++,i++)135:temp[i]=rhs[j];136:temp[totalLen]='\0';137:returntemp;138:}139:140://создаетновыйобъектString141://издвухобъектовклассаString142:Stringoperator+(constString&lhs,constString&rhs)143:{144:inttotalLen=lhs.GetLen()+rhs.GetLen();145:Stringtemp(totalLen);146:inti,j;147:for(i=0;i<lhs.GetLen();i++)148:temp[i]=lhs[i];149:for(j=0,i=lhs.GetLen();;j<rhs.GetLen();j++,i++)150:temp[i]=rhs[j];151:temp[totalLen]='\0';152:returntemp;153:}154:155:intmain()156:{157:Strings1("String0ne");158:Strings2("StringTwo");159:char*c1={"C-String0ne"};160:Strings3;161:Stnngs4;162:Strings5;163:164:cout<<"s1:"<<s1.GetString()<<endl;165:cout<<"s2:"<<s2.GetString()<<endl;166:cout<<"c1:"<<c1<<endl;167:s3=s1+s2;168:cout<<"s3:"<<s3.GetString()<<endl;169:s4=s1+cl;170:cout<<"s4:"<<s4.GetStnng()<<endl;171:s5=c1+s2;172:cout<<"s5:"<<s5.GetString()<<endl;173:return0;174:}Результат:s1:String0nes2:StringTwoc1:C-StringOnes3:StringOneStringTwos4:StringOneC-StringOnes5:C-StringOneStringTwoАнализ:ОбъявлениявсехметодовклассаString,заисключениемoperator+,осталисьтакимиже, как в листинге 15.1.
В строке 20 листинга 15.8 перегружается новый operator+, которыйпринимает две ссылки на константные строки и возвращает строку, полученную в результатеконкатенацииисходныхстрок.ЭтафункцияобъявленакакдругклассаString.Обратитевнимание,чтофункцияoperator+неявляетсяфункцией-членомэтогоилилюбогодругого класса. Она объявляется среди функций-членов класса string как друг, но не как членкласса.Темнеменееэтовсежеполноценноеобъявлениефункции,инетнеобходимостиещеразобъявлятьвпрограммепрототипэтойфункции.Выполнениефункцииoperator+определяетсявстроках142—153.Определениевыполненияфункциианалогичноприведенномувверсиипрограммы,представленнойвлистинге15.1,затемисключением что функция принимает в качестве аргументов две строки, обращаясь к ними спомощьюоткрытыхметодовдоступакласса.Перегруженный оператор применяется в строке 171, где выполняется конкатенация двухстрок.Функции-друзьяДляобъявленияфункциикакдругаклассаиспользуетсяключевоесловоfriend,закоторымследует объявление функции Это не предоставляет функции доступ к указателю this, нообеспечиваетдоступковсемзакрытымизащищеннымданнымифункциям-членам.Пример:classPartNode{//...//сделаемфункцию-члендругогоклассадругомэтогоклассаfriendvoidPartsList::Insert(Part*)//сделаемдругомглобальнуюфункциюfriendintSomeFunction();//...};ПерегрузкаоператоравыводаНастало время снабдить наш класс String возможностью использовать объект cout длявывода своих данных так же, как при выводе данных базовых типов.
До сих пор для выводазначенияпеременной-членаприходилосьиспользоватьследующеевыражение:cout<<theString.GetString();Неплохобылобыиметьвозможностьнаписатьпростоcout<<theString;Для этого необходимо перегрузить функцию operator<<(). Более подробно использованиепотоковiostreamsдлявыводаданныхобсуждаетсяназанятии16.Авлистинге15.9объявляетсяперегрузкафункцииoperator<<какдруга.Листинг15.8.Перегрузкаoperator<<()1:#include<iostream.h>2:#include<string.h>3:4:classString5:{6:public:7://конструкторы8:String();9:String(constchar*const);10:String(constString&);11:~String();12:13://перегруженныеоператоры14:char&operator[](intoffset);15:charoperator[](intoffset)const;16:Stringoperator+(constString&);17:voidoperator+=(constString&);18:String&operator=(constString&);19:friendostream&operator<<20:(ostream&theStream,String&theString);21://Общиеметодыдоступа22:intGetLen()const{returnitsLen;}23:constchar*GetString()const{returnitsString;}24:25:private:26:String(int);//закрытыйконструктор27:char*itsString;28:unsignedshortitsLen;29:};30:31:32://конструктор,заданныйnoумолчанию,создаетстрокудлиной0байт33:String::String()34:{35:itsString=newchar[1];36:itsString[0]='\0';37:itsLen=0;38://cout<<"\tDefaultstringconstructor\n";39://ConstructorCount++;40:}41:42://закрытыйконструктор,используемыйтолько43://методамиклассадлясозданияновойстроки44://указанногоразмера,заполненнойзначениямиNULL.45:String::String(intlen)46:{47:itsString=newchar[k:.H];48:for(inti=0;i<=len;i++)49:itsString[i]='\0';50:itsLen=len;51://cout<<"\tString(int)constructor\n";52://ConstructorCount++;53:}54:55://Преобразуетмассивсимволоввстроку56:String::String(constchar*constcString)57:{58:itsLen=strlen(cString);59:itsString=newchar[itsLen+1];60:for(inti=0;i<itsLen;i++)61:itsString[i]=cString[i];62:itsString[itsLen]='\0';63://cout<<"\tString(char*)constructor\n";64://ConstructorCount++;65:}66:67://конструктор-копировщик68:String::String(constString&rhs)69:{70:itsLen=rhs.GetLen();71:itsString=newchar[itsLen+1];72:for(inti=0;i<itsLen;i++)73:itsString[i]=rhs[i];74:itsString[itsLen]='\0';75://cout<<"\tString(String&)constructor\n";76://ConstructorCount++;77:}78:79://деструкторосвобождаетзанятуюпамять80:String::~String()81:{82:delete[]itsString;83:itsLen=0;84://cout<<"\tStringdestructor\n";85:}86:87://операторравенстваосвобождаетпамять,азатем88://копируетстрокуиразмер89:String&String::operator=(constString&rhs)90:{91:if(this==&rhs)92:return*this;93:delete[]itsString;94:itsLen=rhs.GetLen();95:itsString=newchar[itsLen+1];96:for(inti=0;i<itsLen;i++)97:itsString[i]=rhs[i];98:itsString[itsLen]='\0';99:return*this;100://cout<<"\tStringoperator=\n";101:}102:103://неконстантныйоператориндексирования,104://возвращаетссылкунасимвол,которыйможно105://изменить!106:char&String::operator[](intoffset)107:{108:if(offset>itsLen)109:returnitsString[itsLen-1];110:else111:returnitsString[offset];112:}113:114://константныйоператориндексирования,115://используетсядляконстантныхобъектов(см.конструктор-копировщик!)116:charString::operator[](intoffset)const117:{118:if(offset>itsLen)119:returnitsString[itsLen-1];120:else121:returnitsString[offset];122:}123:124://создаетновуюстроку,добавляятекущую125://строкукrhs126:StringString::operator+(constString&rhs)127:{12S:inttotalLen=itsLen+rhs.GetLen();129:Stringtemp(totalLen);130:inti,j;131:for(i=0;i<itsLen;i++)132:temp[i]=itsString[i];133:for(j=0;j<rhs.GetLen();j++,i++)134:temp[i]=rhs[];135:temp[totalLen]='\0';136:returntemp;137:}138:139://изменяеттекущуюстроку,ничегоневозвращая140:voidString::operator+=(constString&rhs)141:{142:unsignedshortrhsLen=rhs.GetLen();143:unsignedshorttotalLen=itsLen+rhsLen;144:Stringtemp(totalLen);145:inti,j;146:for(i=0;i<itsLen;i++)147:temp[i]=itsString[i];148:for(j=0,i=0;j<rhs.GetLen();j++,i++)149:temp[i]=rhs[i-itsLen];150:temp[totalLen]='\0';151:*this=temp;152:}153:154://intString::ConstructorCount=155:ostream&operator<<(ostream&theStream,String&theString)156:{157:theStream<<theString.itsString;158:returntheStream;159:}160:161:intmain()162:{163:StringtheString("Helloworld.");164:cout<<theString;165:return0;166:}Результат:Helloworld.Анализ:Встроке19operator<<объявляетсякакфункция-друг,котораяпринимаетссылкина ostream и String и возвращает ссылку на ostream.