2-Mavzu: Do'stona funktsiyalar va do'stona sinflar
Reja:
1. Do'stona funktsiyalar
2. Do'stona sinflar
Muammo
Oldingi darslarda biz sizning sinfingiz ma'lumotlari maxfiy bo'lishi kerakligi haqida gapirgan edik . Ammo, agar sizda ushbu sinf bilan ishlaydigan, lekin uning tarkibida bo'lmagan sinf va funksiya bo'lsa, vaziyat yuzaga kelishi mumkin . Masalan, ma'lumotlarni saqlaydigan sinf va bu ma'lumotlarni ekranda ko'rsatadigan funksiya (yoki boshqa sinf) mavjud. Sinf kodi va chiqish funktsiyasining kodi ajratilgan bo'lsa -da (kodni saqlashni soddalashtirish uchun), chiqish funktsiyasining kodi sinf ma'lumotlari bilan chambarchas bog'liq. Shuning uchun, sinf a'zolarini shaxsiy qilish orqali biz kerakli effektga erisha olmaymiz.
Bunday vaziyatda ikkita variant mavjud:
Sinf usullarini ochiq qiling va ular orqali funksiya sinf bilan o'zaro aloqada bo'ladi. Biroq, bu erda bir nechta nuanslar mavjud. Birinchidan, bu ommaviy usullarni aniqlash kerak bo'ladi, bu vaqt talab etadi va ular sinf interfeysini buzadi. Ikkinchidan, darsda siz har doim ochiq bo'lishi shart bo'lmagan va tashqaridan kirishni ta'minlaydigan usullarni ochishingiz kerak bo'ladi.
Do'stona sinflar va do'stona funktsiyalardan foydalaning, ular yordamida sinfning shaxsiy ma'lumotlariga kirish bilan xulosa chiqarish funktsiyalarini taqdim etishingiz mumkin. Bu xulosa chiqarish funktsiyasiga sinf a'zolarining barcha o'zgaruvchilari va usullariga to'g'ridan -to'g'ri kirishga imkon beradi, shu bilan birga sinf tanasidan tashqaridagi boshqa barcha funktsiyalar uchun sinf ma'lumotlariga shaxsiy kirishni saqlab qoladi! Bu darsda biz buni qanday amalga oshirilishini ko'rib chiqamiz.
Do'stona xususiyatlar
Do'stona funktsiya - bu sinf a'zolariga xuddi shu sinf a'zosidek kiradigan funktsiya. Boshqa barcha jihatlarda do'stlik funktsiyasi odatiy vazifadir. Bu oddiy funktsiya yoki boshqa sinfning usuli bo'lishi mumkin. Do'st funktsiyasini e'lon qilish uchun sinf do'sti qilmoqchi bo'lgan funktsiya prototipi oldidagi do'st kalit so'zidan foydalaning . Siz buni sinfning ochiq yoki shaxsiy zonasida e'lon qilishingiz muhim emas. Masalan:
class Anything
{
private:
int m_value;
public:
Anything() { m_value = 0; }
void add(int value) { m_value += value; }
// Делаем функцию reset() дружественной классу Anything
friend void reset(Anything &anything);
};
// Функция reset() теперь является другом класса Anything
void reset(Anything &anything)
{
// И мы имеем доступ к закрытым членам объектов класса Anything
anything.m_value = 0;
}
int main()
{
Anything one;
one.add(4); // добавляем 4 к m_value
reset(one); // сбрасываем m_value в 0
return 0;
}
|
Bu erda biz Anything sinfining ob'ektini oladigan va m_valueqiymatini belgilaydigan reset () funktsiyasini e'lon qildik 0. Reset () hech narsaning a'zosi bo'lmaganligi uchun reset () funktsiyasi odatda biror narsaning shaxsiy a'zolariga kira olmaydi. Biroq, bu funktsiya Anything sinfiga do'st bo'lgani uchun, u Anything shaxsiy a'zolariga kirish huquqiga ega.
Shuni esda tutingki, biz Anything ob'ektini reset () funktsiyasiga parametr sifatida berishimiz kerak. Buning sababi, reset () funktsiyasi sinf usuli emas. Bu * ko'rsatgichi yo'q va ob'ektni uzatishdan tashqari, u sinf bilan o'zaro aloqa qila olmaydi.
Yana bir misol:
class Something
{
private:
int m_value;
public:
Something(int value) { m_value = value; }
friend bool isEqual(const Something &value1, const Something &value2);
};
bool isEqual(const Something &value1, const Something &value2)
{
return (value1.m_value == value2.m_value);
}
|
Bu erda biz isEqual () funktsiyasini Something sinfiga do'st deb e'lon qildik. IsEqual () funktsiyasi nimadir sinfining ikkita ob'ektini parametr sifatida oladi. IsEqual () nimadir sinfining do'sti bo'lgani uchun, funksiya Something klassi ob'ektlarining barcha shaxsiy a'zolariga kira oladi. IsEqual () funktsiyasi ikkita ob'ektning a'zo o'zgaruvchilarining qiymatlarini solishtiradi va trueagar ular teng bo'lsa qaytaradi .
Do'stona funktsiyalar va bir nechta sinflar
Funktsiya bir vaqtning o'zida bir nechta sinflar uchun do'st bo'lishi mumkin, masalan:
#include
class Humidity;
class Temperature
{
private:
int m_temp;
public:
Temperature(int temp=0) { m_temp = temp; }
friend void outWeather(const Temperature &temperature, const Humidity &humidity);
};
class Humidity
{
private:
int m_humidity;
public:
Humidity(int humidity=0) { m_humidity = humidity; }
friend void outWeather(const Temperature &temperature, const Humidity &humidity);
};
void outWeather(const Temperature &temperature, const Humidity &humidity)
{
std::cout << "The temperature is " << temperature.m_temp <<
" and the humidity is " << humidity.m_humidity << '\n';
}
int main()
{
Temperature temp(15);
Humidity hum(11);
outWeather(temp, hum);
return 0;
}
|
Bu erda diqqat qilish kerak bo'lgan ikkita narsa bor. Birinchidan, outWeather () funktsiyasi har ikkala sinf uchun ham do'st bo'lgani uchun, u ikkala sinfning shaxsiy a'zolariga kira oladi. Ikkinchidan, yuqoridagi misolda quyidagi qatorga e'tibor bering:
Bu sinfning prototipi, bu kompilyatorga namlik sinfini biroz keyinroq belgilashimizni aytadi. Bu chiziqsiz, kompilyator Temperatura sinfidagi do'stona outWeather () funktsiyasining prototipini tahlil qilishda namlik nima ekanligini bilmaydigan xatoga yo'l qo'yadi. Sinf prototiplari funktsional prototiplar bilan bir xil vazifani bajaradi: ular kompilyatorga keyinchalik aniqlanadigan, lekin hozirda ishlatilishi kerak bo'lgan narsalar haqida aytib beradi. Biroq, funktsiyalardan farqli o'laroq, sinflar qaytish turi yoki parametrlariga ega emas, shuning uchun ularning prototiplari juda ixchamdir: ключевое слово class + имя класса + ;(masalan class Anything;).
Do'stona darslar
Bir sinf boshqa sinfga do'stona munosabatda bo'lishi mumkin. Bu birinchi sinfning barcha a'zolariga ikkinchi sinfning shaxsiy a'zolariga ruxsat beradi, masalan:
#include
class Values
{
private:
int m_intValue;
double m_dValue;
public:
Values(int intValue, double dValue)
{
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем класс Display другом класса Values
friend class Display;
};
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(Values &value)
{
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или сначала выводим double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
};
int main()
{
Values value(7, 8.4);
Display display(false);
display.displayItem(value);
return 0;
}
|
Display klassi Values sinfining do'sti bo'lgani uchun, Display a'zolaridan har biri maxfiy Values a'zolariga kira oladi. Dasturni bajarish natijasi:
8.4 7
Do'stona darslar haqida eslatmalar :
Birinchidan, Displey qadriyatlarning do'sti bo'lsa ham, Displey qiymatlar ob'ektlarining * ko'rsatgichiga to'g'ridan -to'g'ri kira olmaydi.
Ikkinchidan, agar Displey qadriyatlarning do'sti bo'lsa ham, bu qadriyatlar ham Displeyning do'sti degani emas. Agar siz ikkala sinfni do'stona qilishni xohlasangiz, har biri qarama -qarshi sinfni do'st sifatida ko'rsatishi kerak. Nihoyat, agar A sinf B va B C do'sti bo'lsa, bu A sinfining do'sti degani emas.
Do'stona funktsiyalar va sinflardan foydalanganda ehtiyot bo'ling, chunki bu inkapsulyatsiya tamoyillarini buzishi mumkin . Agar bitta sinf tafsilotlari o'zgarsa, do'stlar sinfining tafsilotlari ham o'zgarishga majbur bo'ladi. Shunday qilib, do'stona funktsiyalar va sinflar sonini va ulardan foydalanishni cheklang.
Do'stona usullar
Butun sinfni do'stona qilish o'rniga, biz faqat sinfning ba'zi usullarini do'stona qilishimiz mumkin. Ularning deklaratsiyalari oddiy do'st funktsiyalariga o'xshaydi, faqat имяКласса::boshida prefiks qilingan usul nomi bundan mustasno (masalan, Display::displayItem()).
Display :: displayItem () usulini Values sinfiga qulay qilish uchun avvalgi misolimizni qayta tuzamiz. Biz quyidagilarni qilishimiz mumkin edi:
class Display; // предварительное объявление класса Display
class Values
{
private:
int m_intValue;
double m_dValue;
public:
Values(int intValue, double dValue)
{
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem() другом класса Values
friend void Display::displayItem(Values& value); // ошибка: Values не видит полного определения класса Display
};
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(Values &value)
{
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
};
|
Biroq, bu ishlamaydi. Usulni sinfga qulay qilish uchun kompilyator do'stona usul aniqlangan sinfning to'liq ta'rifini (faqat uning prototipini emas) ko'rishi kerak. Tuzuvchi ketma -ket kod satrlarini ko'rib chiqish jarayonida Display sinfining to'liq ta'rifini ko'rmagan, lekin uning usulining prototipini ko'rishga muvaffaq bo'lganligi sababli, bu usulning ta'rif chizig'ida do'st sinfga xato bo'ladi. Qiymatlar (16 -qator).
Siz Display sinfining ta'rifini Values sinfining ta'rifidan yuqoriroq qilishga harakat qilishingiz mumkin:
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(Values &value) // ошибка: Компилятор не знает, что такое Values
{
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
};
class Values
{
private:
int m_intValue;
double m_dValue;
public:
Values(int intValue, double dValue)
{
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem() другом класса Values
friend void Display::displayItem(Values& value);
};
|
Biroq, hozir bizda boshqa muammo bor. Display :: displayItem () usuli parametr sifatida Values sinfining ob'ektiga havolani ishlatganligi uchun va biz faqat Display ta'rifini Values ta'rifidan yuqoriga o'tkazganimiz uchun, kompilyator Values nima ekanligini bilmasligidan shikoyat qiladi. Bor. Bu ayanchli aylana bo'lib chiqadi.
Yaxshiyamki, buni juda oson hal qilish mumkin:
Birinchidan, biz Values klassi uchun oldinga deklaratsiyadan foydalanamiz.
Ikkinchidan, biz Display :: displayItem () usulining ta'rifini Display sinfidan tashqariga o'tkazamiz va Values sinfining to'liq ta'rifidan keyin joylashtiramiz.
Bu shunday ko'rinadi:
#include
class Values; // предварительное объявление класса Values
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(Values &value); // предварительное объявление, приведенное выше, требуется для этой строки
};
class Values // полное определение класса Values
{
private:
int m_intValue;
double m_dValue;
public:
Values(int intValue, double dValue)
{
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem() другом класса Values
friend void Display::displayItem(Values& value);
};
// Теперь мы можем определить метод Display::displayItem(), которому требуется увидеть полное определение класса Values
void Display::displayItem(Values &value)
{
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
int main()
{
Values value(7, 8.4);
Display display(false);
display.displayItem(value);
return 0;
}
|
Endi hamma narsa to'g'ri ishlaydi. Bu biroz murakkab tuyulishi mumkin bo'lsa -da, harakatlanuvchi darslar va usullarning bu "raqsi" biz hamma narsani bitta faylda bajarishga harakat qilganimiz uchun kerak. Har bir sinf ta'rifini mos keladigan .cpp fayllaridagi usul ta'riflari bilan alohida sarlavha fayliga qo'yish eng yaxshi echim bo'ladi (bu haqda batafsil bu erda ). Shunday qilib, barcha sinf ta'riflari bir vaqtning o'zida barcha .cpp fayllarida ko'rinadi va harakatlar bilan "raqs" kerak bo'lmaydi!
Xulosa
Do'stona funktsiya / sinf - bu boshqa sinfning shaxsiy a'zolariga xuddi shu sinf a'zosidek kirish huquqiga ega bo'lgan funktsiya / sinf. Bu funksiya / sinfga boshqa sinfni shaxsiy a'zolarini oshkor qilishga majbur qilmasdan boshqa sinf bilan yaqindan ishlash imkonini beradi.
Sinov
Geometriya nuqtasi - bu kosmosdagi pozitsiya. Biz koordinata majmui sifatida 3D kosmosda bir nuqtasini aniqlash mumkin x, yva z. Misol uchun, Point(0.0, 1.0, 2.0)koordinata bir nuqta bo'ladi x = 0.0, y = 1.0va z = 2.0.
Fizikadagi vektor - bu uzunligi va yo'nalishi (lekin pozitsiyasi emas) bo'lgan miqdor. Biz qadriyatlar orqali 3D kosmosda bir vektor belgilashingiz mumkin x, yva zbirga vektor yo'nalishi vakili, x, yva bolta z. Masalan, Vector(1.0, 0.0, 0.0)yo'nalish faqat musbat o'qi bo'ylab xuzunligi bo'yicha ifodalanadigan vektor bo'ladi 1.0.
Nuqtani yangi joyga ko'chirish uchun nuqtaga vektor qo'llanilishi mumkin. Bu vektor yo'nalishini nuqta holatiga qo'shish orqali amalga oshiriladi. Masalan, Point(0.0, 1.0, 2.0) + Vector(0.0, 2.0, 0.0)nuqta beradi (0.0, 3.0, 2.0).
Nuqtalar va vektorlar ko'pincha kompyuter grafikasida ishlatiladi (nuqta - bu shaklning tepalarini ifodalash, vektorlar esa shaklni siljitish uchun ishlatiladi).
Quyidagi dastur asosida:
#include
class Vector3D
{
private:
double m_x, m_y, m_z;
public:
Vector3D(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{
}
void print()
{
std::cout << "Vector(" << m_x << " , " << m_y << " , " << m_z << ")\n";
}
};
class Point3D
{
private:
double m_x, m_y, m_z;
public:
Point3D(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{
}
void print()
{
std::cout << "Point(" << m_x << " , " << m_y << " , " << m_z << ")\n";
}
void moveByVector(const Vector3D &v)
{
}};
int main()
{
Point3D p(3.0, 4.0, 5.0);
Vector3D v(3.0, 3.0, -2.0);
p.print();
p.moveByVector(v);
p.print();
return 0; }
|
3-Mavzu: Merosxo'rlik va polimorfizm.
Do'stlaringiz bilan baham: |