Viskas, ką turite žinoti remdamiesi verte

Kalbant apie programinės įrangos inžineriją, yra nemažai klaidingai suprastų sąvokų ir netinkamai vartojamų terminų. Neabejotinai viena iš jų yra nuoroda ir vertė.

Prisimenu tą dieną, kai skaičiau temą ir kiekvienas mano aplankytas šaltinis atrodė prieštaraujantis ankstesniam. Prireikė šiek tiek laiko, kad gerai suvokčiau. Neturėjau pasirinkimo, nes tai yra pagrindinis dalykas, jei esi programinės įrangos inžinierius.

Po kelių savaičių įbėgau į nemalonią klaidą ir nusprendžiau parašyti straipsnį, kad kitiems žmonėms būtų lengviau išsiaiškinti visą tai.

Aš kasdien koduoju į „Ruby“. Aš taip pat gana dažnai naudoju „JavaScript“, todėl šiam pristatymui pasirinkau šias dvi kalbas.

Norėdami suprasti visas sąvokas, naudosime ir kai kuriuos „Go“ ir „Perl“ pavyzdžius.

Norėdami suvokti visą temą, turite suprasti 3 skirtingus dalykus:

  • Kaip pagrindinės duomenų struktūros yra įgyvendinamos kalboje (objektai, primityvūs tipai, keičiamumas).
  • Kaip veikia kintama priskyrimas / kopijavimas / perkėlimas / palyginimas
  • Kaip kintamieji perduodami funkcijoms

Pagrindiniai duomenų tipai

„Ruby“ nėra primityvių tipų ir viskas yra objektas, įskaitant sveikuosius skaičius ir loginius ženklus.

Taip, „Ruby“ yra „TrueClass“.

true.is_a? (TrueClass) => teisinga
3.is_a? (Sveikasis skaičius) => tiesa
tiesa.is_a? (objektas) => tiesa
3.is_a? (Objektas) => tiesa
TrueClass.is_a? (Objektas) => tiesa
Integer.is_a? (Objektas) => tiesa

Šie objektai gali būti keičiami arba nekintami.

Nekeičiamas reiškia, kad jokiu būdu negalima pakeisti objekto jį sukūrus. Yra tik vienas nurodytos vertės egzempliorius su vienu object_id ir jis nesikeičia, nesvarbu, ką darote.

Pagal numatytuosius nustatymus „Ruby“ nekintami objektų tipai yra šie: Boolean, skaitinis, nulis ir Symbol.

MRT objekto objektas_id yra tas pats, kaip VERTĖ, žyminti objektą C lygyje. Daugeliui objektų ši vertė yra rodyklė į vietą atmintyje, kur saugomi faktiniai objekto duomenys.

Nuo šiol pakaitomis naudosime object_id ir atminties adresą.

Paleiskime keletą „Ruby“ kodų MRT nekintamam simboliui ir nekintančiai eilutei:

: symbol.object_id => 808668
: symbol.object_id => 808668
'string'.object_id => 70137215233780
'string'.object_id => 70137215215120

Kaip matote, kol simbolio versija ta pačia verte išlaiko tą patį objekto_idą, eilutės reikšmės priklauso skirtingiems atminties adresams.

Skirtingai nuo „Ruby“, „Java“ yra primityvių tipų.

Jie yra - loginiai, niekiniai, neapibrėžti, eilutė ir skaičius.

Likę duomenų tipai priklauso objektams (masyvas, funkcija ir objektas). Čia nėra nieko išgalvoto, jis yra daug tiesesnis nei „Ruby“.

[] masyvo egzempliorius => tiesa
Objekto egzempliorius => tiesa
3 egzemplioriai objekto => klaidingi

Kintamasis priskyrimas, kopijavimas, perdavimas ir palyginimas

Rubine kiekvienas kintamasis yra tik nuoroda į objektą (nes viskas yra objektas).

a = 'styga'
b = a
# Jei iš naujo priskiriate tą pačią reikšmę
a = 'styga'
įveda b => 'eilutę'
iškelia == b => tikrosios # vertės yra vienodos
pateikia a.object_id == b.object_id => false # atminties adr-s. skirtis
# Jei dar kartą priskiriate reikšmę
a = 'nauja eilutė'
įveda => „naują eilutę“
įveda b => 'eilutę'
pateikia == b => klaidingos # reikšmės skiriasi
pateikia a.object_id == b.object_id => false # atminties adr-s. taip pat skiriasi

Kai priskiriate kintamąjį, tai yra nuoroda į objektą, o ne į patį objektą. Nukopijavus objektą b = a, abu kintamieji nurodys tą patį adresą.

Šis elgesys vadinamas nuoroda kopija.

Griežtai kalbant „Ruby“ ir „JavaScript“, viskas nukopijuota pagal vertę.

Tačiau kalbant apie objektus, reikšmės būna tų objektų atminties adresai. Dėl to mes galime modifikuoti reikšmes, esančias tuose atminties adresuose. Vėlgi, tai vadinama kopija pagal pamatinę vertę, tačiau dauguma žmonių tai vadina kopija pagal nuorodą.

Tai būtų kopija pagal nuorodą, jei, priskyręs a „naujai eilutei“, b taip pat nurodytų tą patį adresą ir turėtų tą pačią „naujos eilutės“ vertę.

Kai deklaruojate b = a, a ir b nurodo tą patį atminties adresąPriskyrę a (a = 'eilutę'), a ir b nurodo skirtingus atminties adresus

Tas pats su nekintančiu veidu, pavyzdžiui, sveikasis skaičius:

a = 1
b = a
a = 1
iškelia b => 1
pateikia == b => true # palyginimą pagal vertę
pateikia a.object_id == b.object_id => true # palyginimą pagal atminties adr.

Kai priskiriate a tam pačiam sveikam skaičiui, atminties adresas lieka tas pats, nes nurodytas sveikasis skaičius visada turi tą patį object_id.

Kaip matote, kai lyginate bet kurį objektą su kitu, jis lyginamas pagal vertę. Jei norite sužinoti, ar jie yra tas pats objektas, turite naudoti object_id.

Pažiūrėkime „JavaScript“ versiją:

var a = 'styga';
var b = a;
a = 'styga'; # a priskiriama tai pačiai vertei
console.log (a); => 'styga'
console.log (b); => 'styga'
console.log (a === b); => teisinga // palyginimas pagal vertę
var a = [];
var b = a;
console.log (a === b); => tiesa
a = [];
console.log (a); => []
console.log (b); => []
console.log (a === b); => klaidingas // palyginimas pagal atminties adresą

Išskyrus palyginimą - „JavaScript“ pagal vertę naudoja primityvius tipus ir pagal objektus. Panašu, kad elgesys yra panašus į „Ruby“.

Na, ne visai.

Primityviosios „JavaScript“ vertės nebus dalijamos keliems kintamiesiems. Net jei kintamuosius nustatysite lygius vienas kitam. Kiekvienas kintamasis, atspindintis primityvią vertę, garantuojamas priklausymas unikaliai atminties vietai.

Tai reiškia, kad nė vienas iš kintamųjų niekada nenurodys to paties atminties adreso. Taip pat svarbu, kad pati vertė būtų saugoma fizinės atminties vietoje.

Mūsų pavyzdyje, kai deklaruojame b = a, b iškart parodys kitą atminties adresą su ta pačia „eilutės“ reikšme. Taigi jums nereikia iš naujo priskirti a, kad nurodytumėte kitą atminties adresą.

Tai vadinama nukopijuota pagal vertę, nes jūs neturite prieigos tik prie atminties adreso.

Kai deklaruojate a = b, jis priskiriamas reikšmei, taigi a ir b nurodo skirtingus atminties adresus

Pažiūrėkime geresnį pavyzdį, kur visa tai svarbu.

Jei „Ruby“ pakeisime reikšmę, esančią atminties adrese, tada visos nuorodos, nurodančios adresą, turės tą pačią atnaujintą vertę:

a = 'x'
b = a
a.sąmonė („y“)
nurodo => „xy“
iškelia b => 'xy'
b.sąmonė („z“)
pateikia => 'xyz'
iškelia b => 'xyz'
a = „z“
pateikia => „z“
iškelia b => 'xyz'
a [0] = 'y'
iškelia => 'y'
iškelia b => 'xyz'

Galite pamanyti „JavaScript“ tik pasikeis a reikšmė, bet ne. Net negalite pakeisti pradinės vertės, nes neturite tiesioginės prieigos prie atminties adreso.

Galima sakyti, kad jūs priskyrėte „x“, bet jis buvo priskiriamas vertei, taigi atminties adresas turi reikšmę „x“, bet jūs negalite jo pakeisti, nes neturite nuorodos į jį.

var a = 'x';
var b = a;
a.sąmonė ('y');
console.log (a); => „x“
console.log (b); => „x“
a [0] = 'z';
console.log (a); => 'x';

„JavaScript“ objektų elgsena ir įgyvendinimas yra tokie patys kaip „Ruby“ keičiamų objektų. Abi kopijos yra nuorodos vertės.

„JavaScript“ primityvieji tipai yra nukopijuoti pagal vertę. Elgesys yra toks pats kaip ir „Ruby“ nekintamų objektų, kurie nukopijuojami pagal pamatinę vertę.

Huh?

Vėlgi, kai ką nors nukopijuoji pagal vertę, tai reiškia, kad negali pakeisti (mutuoti) pradinės vertės, nes nėra nuorodos į atminties adresą. Rašymo kodo požiūriu tai yra tas pats dalykas, kaip nekeičiami subjektai, kurių negalite mutuoti.

Jei palyginsite „Ruby“ ir „JavaScript“, vienintelis duomenų tipas, kuris pagal numatytuosius nustatymus „elgiasi“ skirtingai, yra eilutė (štai kodėl aukščiau pateiktuose pavyzdžiuose mes naudojome eilutę).

„Ruby“ yra keičiamas objektas ir yra nukopijuojamas / perduodamas pagal referencinę vertę, o „JavaScript“ yra primityvaus tipo ir nukopijuojamas / perduodamas pagal vertę.

Kai norite klonuoti (nekopijuoti) objekto, turite tai aiškiai padaryti abiem kalbomis, kad galėtumėte įsitikinti, jog originalus objektas nebus modifikuotas:

a = {'vardas': 'Kate'}
b = a.klonas
b ['vardas'] = 'Anna'
pateikia => {: name => „Kate“}
var a = {'vardas': 'Kate'};
var b = {... a}; // su nauja ES6 sintakse
b ['vardas'] = 'Anna';
console.log (a); => {vardas: "Kate"}

Labai svarbu tai atsiminti, nes priešingu atveju, naudodamiesi savo kodu daugiau nei vieną kartą, susidursite su keistais klaida. Puikus pavyzdys būtų rekursinė funkcija, kai objektą naudojate kaip argumentą.

Kitas yra „React“ („JavaScript“ sąsajos sąranka), kur visada turite perduoti naują objektą, kad atnaujintumėte būseną, nes palyginimas veikia remiantis objekto ID.

Tai yra greičiau, nes jums nereikia eiti per objektą eilute po eilę, kad pamatytumėte, ar jis buvo pakeistas.

Kaip kintamieji perduodami funkcijoms

Kintamųjų perdavimas funkcijoms veikia taip pat, kaip kopijuojant tuos pačius duomenų tipus daugumoje kalbų.

„JavaScript“ programoje primityvūs tipai yra nukopijuojami ir perduodami pagal vertę, o objektai nukopijuojami ir perduodami pagal referencinę vertę.

Aš manau, kad tai yra priežastis, kodėl žmonės kalba tik apie praeinamą vertę ar praeitį, ir atrodo, kad niekada nemini kopijavimo. Manau, jie mano, kad kopijavimas veikia taip pat.

a = 'b'
def išvestis (eilutė) # perduota referencine verte
  string = 'c' # perleista, taigi jokios nuorodos į originalą
  deda stygas
galas
išėjimas (a) => 'c'
pateikia a => 'b'
def output2 (string) # perduota referencine verte
  string.concat ('c') # mes keičiame reikšmę, kuri yra nurodytame adrese
  deda stygas
galas
išėjimas (a) => 'bc'
pateikia => 'bc'

Dabar „JavaScript“:

var a = 'b';
funkcijos išvestis (eilutė) {// perduota pagal vertę
  eilutė = 'c'; // priskiriama kitai vertei
  console.log (eilutė);
}
išėjimas (a); => 'c'
console.log (a); => „b“
funkcijos išvestis2 (eilutė) {// perduota pagal vertę
  string.concat ('c'); // mes negalime jo modifikuoti be nuorodų
  console.log (eilutė);
}
išėjimas2 (a); => „b“
console.log (a); => „b“

Jei funkcijai „JavaScript“ perduosite objektą (ne tokį primityvų tipą kaip mes), jis veikia taip pat kaip ir „Ruby“ pavyzdys.

Kitos kalbos

Mes jau matėme, kaip veikia kopijavimas / išlaikymas pagal vertę ir kopijavimas / išlaikymas pagal referencinę vertę. Dabar pamatysime, kas yra nuoroda, ir taip pat sužinosime, kaip galime pakeisti objektus, jei praeiname pagal vertę.

Ieškodamas pravažiavimo nuorodų kalbomis, negalėjau rasti per daug ir galiausiai pasirinkau Perlą. Pažiūrėkime, kaip veikia kopijavimas Perle:

mano $ x = 'eilutė';
mano $ y = $ x;
$ x = 'nauja eilutė';
spausdinti „$ x“; => 'nauja eilutė'
spausdinti „$ y“; => 'styga'
my $ a = {data => "string"};
mano $ b = $ a;
$ a -> {data} = "nauja eilutė";
spausdinti „$ a -> {data} \ n“; => 'nauja eilutė'
spausdinti „$ b -> {duomenys} \ n“; => 'nauja eilutė'

Na, atrodo, kad tai tas pats, kaip ir „Ruby“. Neradau jokių įrodymų, bet sakyčiau, kad „Perlas“ nukopijuojamas pagal „String“ pamatinę vertę.

Dabar patikrinkime, ką reiškia nuoroda:

mano $ x = 'eilutė';
spausdinti „$ x“; => 'styga'
sub foo {
  $ _ [0] = 'nauja eilutė';
  spausdinti „$ _ [0]“; => 'nauja eilutė'
}
foo ($ x);
spausdinti „$ x“; => 'nauja eilutė'

Kadangi „Perl“ perduodamas kaip nuoroda, jei atliksite funkcijos perskirstymą, tai pakeis ir pradinę atminties adreso vertę.

Artimųjų kalbų kalba pasirinkau „Go“, nes artimiausioje ateityje ketinu gilinti savo „Go“ žinias:

pakuotės pagrindinis
importuoti „fmt“
func changeAddress (a * int) {
  fmt.Println (a)
  * a = 0 // nustatant atminties adreso reikšmę 0
}
func changeValue (int) {
  fmt.Println (a)
  a = 0 // keičiame reikšmę funkcijos viduje
  fmt.Println (a)
}
func main () {
  a: = 5
  fmt.Println (a)
  fmt.Println (& a)
  changeValue (a) // a perduodama pagal vertę
  fmt.Println (a)
  changeAddress (& a) // atminties adresas perduodamas pagal vertę
  fmt.Println (a)
}
Sudarę ir paleisdami kodą gausite šiuos duomenis:
0xc42000e328
5
5
0
5
0xc42000e328
0

Jei norite pakeisti atminties adreso vertę, turite naudoti rodykles ir perduoti atminties adresus pagal vertę. Rodyklė laiko vertės atminties adresą.

Operatorius generuoja žymiklį į savo operandą, o * operatorius nurodo žymeklio pagrindinę vertę. Tai iš esmės reiškia, kad jūs perduodate reikšmės atminties adresą naudodami &, o atminties adreso vertę nustatote naudodami *.

Išvada

Kaip įvertinti kalbą:

  1. Suprasti pagrindinius duomenų tipus kalba. Perskaitykite kai kurias specifikacijas ir pažaisk su jais. Paprastai tai virsta primityviais tipais ir objektais. Tada patikrinkite, ar tie objektai yra nekintantys ar nekintami. Kai kurios kalbos naudoja skirtingą duomenų tipų kopijavimo / perdavimo taktiką.
  2. Kitas žingsnis yra kintamojo priskyrimas, kopijavimas, perkėlimas ir palyginimas. Manau, kad tai yra pati svarbiausia dalis. Kai gausite tai, galėsite išsiaiškinti, kas vyksta. Tai labai padeda, jei žaisdami tikrinsite atminties adresus.
  3. Paprastai kintamųjų perdavimas funkcijoms nėra ypatingas. Paprastai tai veikia taip pat, kaip kopijavimas daugeliu kalbų. Kai žinote, kaip nukopijuojami ir paskirstomi kintamieji, jūs jau žinote, kaip jie perduodami funkcijoms.

Kalbos, kurias mes čia vartojome:

  • Eiti: nukopijuota ir perduota pagal vertę
  • „JavaScript“: Pirmykščiai tipai yra nukopijuojami / perduodami pagal vertę, objektai nukopijuojami / perduodami pagal referencinę vertę
  • „Ruby“: nukopijuota ir perduodama pagal referencinę vertę + nekintančius / nekintamus objektus
  • „Perl“: nukopijuojama pagal pamatinę vertę ir perduodama pagal nuorodą

Kai žmonės sako, kad praėjo remdamiesi nuoroda, jie paprastai reiškia išlaikytą pagal pamatinę vertę. Praleidimas pagal referencinę vertę reiškia, kad kintamieji yra apeinami pagal reikšmę, tačiau šios vertės yra nuorodos į objektus.

Kaip matėte, „Ruby“ naudoja tik nuorodos vertę, o „JavaScript“ naudoja mišrią strategiją. Vis dėlto elgesys yra vienodas beveik visų tipų duomenims, nes duomenų struktūros yra skirtingos.

Didžioji dalis įprastų kalbų yra arba nukopijuotos, ir perduotos pagal vertę, arba nukopijuotos ir perduotos pagal referencinę vertę. Paskutinį kartą: pravažiavimo nuorodos vertė paprastai vadinama pravažiavimo nuoroda.

Apskritai, pravažiavimas pagal vertę yra saugesnis, nes nekils problemų, nes netyčia negalite pakeisti pradinės vertės. Taip pat rašyti lėčiau, nes norėdami pakeisti objektus turite naudoti rodykles.

Tai ta pati mintis, susijusi su statiniu spausdinimu ir dinaminiu rašymu - kūrimo greitis padidina saugą. Kaip atspėjote, reikšmė paprastai yra žemesnio lygio kalbų, tokių kaip C, Java ar Go, ypatybė.

Praeiti nuoroda arba nuorodos reikšmė dažniausiai naudojama aukštesnio lygio kalbose, tokiose kaip „JavaScript“, „Ruby“ ir „Python“.

Kai atrasite naują kalbą, pereikite prie proceso, kurį mes padarėme čia, ir jūs suprasite, kaip ji veikia.

Tai nėra lengva tema ir nesu tikras, kad viskas, kas čia parašiau, yra teisinga. Jei manote, kad padariau keletą klaidų šiame straipsnyje, praneškite man apie tai komentaruose.