Qiymat va havola bo‘yicha uzatish: argumentlar qanday ishlaydi
Asoslar4 daqInnovialabs tahririyati
Qisqacha
Ko‘pchilik mashhur tillar qiymat nusxasini uzatadi, obyektlar uchun esa bu qiymat — havola: funksiya umumiy obyektni o‘zgartira oladi, lekin chaqiruvchi o‘zgaruvchisini qayta tayinlay olmaydi; haqiqiy havola bo‘yicha uzatish faqat til buni aniq ruxsat bergan joyda bor, masalan C# dagi ref yoki Go dagi ko‘rsatkichlar.
Qisqa javob
Uchta modelga duch kelasiz:
- Qiymat bo‘yicha uzatish — funksiya nusxa oladi. Uni o‘zgartirish chaqiruvchi kodga ta’sir qilmaydi.
- Havola bo‘yicha uzatish — funksiya chaqiruvchi o‘zgaruvchisining taxallusini oladi. Parametrga qiymat berish shu o‘zgaruvchini o‘zgartiradi.
- Birgalikda foydalanish bo‘yicha uzatish (call by sharing, «havola qiymat bo‘yicha uzatiladi») — funksiya o‘sha obyektga havola nusxasini oladi. U obyektni o‘zgartira oladi, lekin parametrni qayta tayinlash faqat lokal nusxani o‘zgartiradi.
Python, JavaScript va Java obyektlar uchun uchinchi modeldan foydalanadi. Shuning uchun «Java havola bo‘yicha uzatadi» degan mashhur gap noto‘g‘ri.
Tillar bo‘yicha
| Til | Standart holat | Haqiqiy havola bo‘yicha uzatish |
|---|---|---|
| Python | Birgalikda foydalanish (hammasi obyekt) | Yo‘q |
| JavaScript | Primitivlar uchun qiymat, obyektlar uchun birgalikda foydalanish | Yo‘q |
| Java | Primitivlar uchun qiymat, obyektlar uchun birgalikda foydalanish | Yo‘q |
| Go | Doim qiymat; slice va map ichki ko‘rsatkichlarga ega | Ko‘rsatkichlar (*T) |
| C# | struct uchun qiymat, class uchun birgalikda foydalanish | ref, out, in |
Python va JavaScript
def update(items):
items.append(4) # umumiy ro‘yxatni o‘zgartiradi: chaqiruvchi ko‘radi
items = [0] # lokal nomni qayta bog‘laydi: tashqarida ko‘rinmaydi
data = [1, 2, 3]
update(data)
print(data) # [1, 2, 3, 4]
JavaScript obyekt va massivlar bilan xuddi shunday ishlaydi. Satrlar va sonlar o‘zgarmas, shuning uchun ikkala tilda ham funksiya ularni joyida o‘zgartira olmaydi.
Java
Metod havolaning nusxasini oladi. list.add(x) chaqiruvchining ro‘yxatini o‘zgartiradi, list = new ArrayList<>() esa yo‘q. Java metodini chaqiruvchi o‘zgaruvchisini almashtirishga majburlab bo‘lmaydi.
Go
Go hamma narsani, jumladan strukturalarni ham nusxalaydi. Funksiya strukturani o‘zgartira olishi uchun ko‘rsatkich uzating:
func rename(u *User) { u.Name = "Ann" }
Slice va map — umumiy ma’lumotlarga ishora qiluvchi kichik sarlavhalar. Funksiya ichida s[0] ni o‘zgartirish tashqarida ko‘rinadi, lekin append yangi massiv ajratishi mumkin va slice qaytarilmasa, bu o‘zgarish ko‘rinmaydi.
C#
Strukturalar nusxalanadi, klass nusxalari esa umumiy. ref kalit so‘zi haqiqiy havola bo‘yicha uzatishni beradi: ref parametrga qiymat berish chaqiruvchi o‘zgaruvchisini o‘zgartiradi. out qo‘shimcha qiymatlarni qaytarish uchun, in esa faqat o‘qish uchun havola uzatadi va katta strukturalarni nusxalashdan qutqaradi.
Argumentni o‘zgartirish bilan bog‘liq odatiy xatolar
- Kutilmagan yon ta’sirlar. Yordamchi funksiya elementlarni o‘chirib, ro‘yxatni «tozalaydi» va boshqa joyda kerak bo‘lgan asl ma’lumotlar ham o‘zgarib ketadi.
- Python dagi o‘zgaruvchan standart qiymatlar. Standart obyekt bir marta yaratiladi va barcha chaqiruvlar uchun umumiy bo‘ladi:
def add_tag(tag, tags=[]): # xato: barcha chaqiruvlar uchun bitta ro‘yxat
tags.append(tag)
return tags
def add_tag(tag, tags=None): # tuzatish
tags = [] if tags is None else tags
tags.append(tag)
return tags
- UI dagi umumiy holat. React va shunga o‘xshash kutubxonalarda holatni joyida o‘zgartirish havolani o‘zgartirmaydi va yangilanish sezilmasligi mumkin.
- Go dagi
appendkutilmagan holatlari. Ikki slice bitta massivni bo‘lishishi va bir-birining ma’lumotlarini qayta yozishi mumkin.
Nusxalash: sayoz va chuqur
Mustaqil obyekt kerak bo‘lsa, uni nusxalang — lekin nusxa qanchalik chuqur ketishini tushuning.
- Sayoz nusxa (shallow copy) yangi tashqi konteyner yaratadi, ichki obyektlar esa umumiy qoladi.
- Chuqur nusxa (deep copy) hamma narsani rekursiv nusxalaydi, umumiy hech narsa qolmaydi.
| Til | Sayoz nusxa | Chuqur nusxa |
|---|---|---|
| Python | list(x), x.copy(), copy.copy(x) | copy.deepcopy(x) |
| JavaScript | [...arr], {...obj}, Object.assign | structuredClone(obj) |
| Java | new ArrayList<>(list), nusxa konstruktorlari | Qo‘lda yoki serializatsiya orqali |
| Go | copy(dst, src), maps.Clone | Qo‘lda |
| C# | MemberwiseClone, new List<T>(list) | Qo‘lda yoki serializatsiya orqali |
Lug‘atlar ro‘yxatining sayoz nusxasi lug‘atlarning o‘zini baribir bo‘lishadi. «Nusxaladim-ku, lekin baribir o‘zgardi» holatining eng ko‘p uchraydigan sababi shu.
Xatolardan qanday qochish kerak
- Argumentlarni o‘zgartirmang, agar bu funksiya nomidan aniq bo‘lmasa (
sort_in_place,appendTo). - Kirishlarni o‘zgartirish o‘rniga yangi qiymat qaytaring — bunday kodni test qilish osonroq.
- Til imkon bergan joyda o‘zgarmas ma’lumotlarni afzal ko‘ring: kortejlar,
frozenset,readonly, records, sayoz himoya uchunObject.freeze. - Chegarada nusxalang — ma’lumotlar modulga kirganda yoki saqlanganda ularni bir marta nusxalang.
- Chuqur nusxani ongli ravishda qiling. U sekinroq va tashqi resurslarga ega obyektlarda buzilishi mumkin, shuning uchun uni faqat to‘liq mustaqillik haqiqatan kerak joyda qo‘llang.