Kõik, mida peate teadma, lähtudes võrdlusest või väärtusest

Tarkvaraehituse osas on üsna palju valesti mõistetud mõisteid ja valesti kasutatud termineid. Võrdluse teel vs väärtuse järgi on kindlasti üks neist.

Ma mäletan seda päeva tagasi, kui lugesin seda teemat ja kõik allikad, mille läbi käisin, näisid olevat eelmisega vastuolus. Selle mõistmiseks kulus natuke aega. Mul polnud valikut, kuna see on põhiteema, kui olete tarkvarainsener.

Sattusin mõni nädal tagasi vastikusse vea ja otsustasin kirjutada artikli, nii et teistel inimestel on kogu selle asja mõistmine lihtsam.

Ma koodin iga päev Ruby's. Kasutan JavaScripti ka üsna sageli, nii et valisin selle esitluse jaoks need kaks keelt.

Kõigi mõistete mõistmiseks kasutame ka mõnda Go ja Perli näidet.

Terve teema mõistmiseks peate mõistma 3 erinevat asja:

  • Kuidas aluseks olevaid andmestruktuure keeles rakendatakse (objektid, primitiivsed tüübid, muudetavus).
  • Kuidas muutuv määramine / kopeerimine / ümber määramine / võrdlus töötab
  • Kuidas muutujaid funktsioonidele antakse?

Alusandmetüübid

Rubinis pole primitiivseid tüüpe ja kõik on objekt, sealhulgas täisarvud ja tõeväärtused.

Ja jah, Rubinis on TrueClass.

true.is_a? (TrueClass) => tõene
3.is_a? (Täisarv) => tõsi
true.is_a? (objekt) => true
3.is_a? (Objekt) => tõsi
TrueClass.is_a? (Objekt) => tõene
Integer.is_a? (Objekt) => õige

Need objektid võivad olla muutlikud või muutumatud.

Muutumatu tähendab, et mingil moel ei saa objekti pärast selle loomist muuta. Antud väärtuse jaoks on objekt_id ainult üks eksemplar ja see jääb samaks sõltumata sellest, mida teete.

Vaikimisi on rubiinis muutumatud objektide tüübid: Boolean, Numbriline, null ja Symbol.

MRT-s on objekti object_id sama kui VÄÄRTUS, mis tähistab objekti C-tasemel. Enamiku tüüpi objektide korral on see VÄÄRTUS mälu asukohta, kus talletatakse tegelikke objektide andmeid.

Nüüdsest kasutame objekti_id ja mäluaadressi vaheldumisi.

Käivitame MRI-s muutumatu sümboli ja muutumatu stringi jaoks Ruby-koodi:

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

Nagu näete, kui sümboliversioon hoiab sama objekti sama objekti jaoks sama väärtust, kuuluvad stringi väärtused erinevatele mäluaadressidele.

Erinevalt Rubiinist on JavaScriptil primitiivsed tüübid.

Need on - Boolean, null, defineerimata, string ja number.

Ülejäänud andmetüübid lähevad objektide (massiivi, funktsiooni ja objekti) katuse alla. Siin pole midagi väljamõeldud - see on palju sirgem kui Ruby.

[] massiivi eksemplar => tõene
Objekti eksemplar => tõene
3 objekti objekti => vale

Muutuv määramine, kopeerimine, ümber määramine ja võrdlemine

Ruby keeles on iga muutuja lihtsalt viide objektile (kuna kõik on objekt).

a = 'string'
b = a
# Kui määrate sama väärtusega a uuesti
a = 'string'
paneb b => 'stringi'
paneb == b => õige # väärtused on samad
paneb a.object_id == b.object_id => false # memory adr-s. erinevad
# Kui määrate väärtuse mõnele teisele väärtusele
a = 'uus string'
paneb => 'uus string'
paneb b => 'stringi'
paneb a == b => vale # väärtused on erinevad
paneb a.object_id == b.object_id => false # memory adr-s. erinevad ka

Muutuja määramisel on see viide objektile, mitte objektile endale. Objekti kopeerimisel b = a osutavad mõlemad muutujad samale aadressile.

Seda käitumist nimetatakse võrdlusväärtuse järgi kopeerimiseks.

Rangelt öeldes on rubiinides ja JavaScriptides kõik kopeeritud väärtuse järgi.

Objektide puhul on väärtused aga nende objektide mäluaadressid. Tänu sellele saame muuta mäluaadressides olevaid väärtusi. Jällegi nimetatakse seda koopiaks võrdlusväärtuse järgi, kuid enamik inimesi viitab sellele koopiale viite abil.

Oleks kopeerimine viitega, kui pärast uue nime määramist uuele stringi osutaks b ka samale aadressile ja sama 'uue stringi' väärtusele.

Kui kuulutate b = a, osutavad a ja b samale mälu aadressilePärast a (a = 'string') ümber määramist osutavad a ja b erinevatele mäluaadressidele

Sama muutumatu tüübiga nagu Integer:

a = 1
b = a
a = 1
paneb b => 1
paneb == b => true # väärtuse võrdluse
paneb a.object_id == b.object_id => true # võrdluse mälu adr järgi.

Kui määrate a samale täisarvule, jääb mälu aadress samaks, kuna antud täisarvul on alati sama objekt_id.

Nagu näete mõne objekti võrdlemisel teisega, võrreldakse seda väärtusega. Kui soovite vaadata, kas need on sama objekt, peate kasutama objekti_id.

Vaatame JavaScripti versiooni:

var a = 'string';
var b = a;
a = 'nöör'; # a omistatakse samale väärtusele
console.log (a); => 'string'
console.log (b); => 'string'
console.log (a === b); => tõene // väärtuste võrdlus
var a = [];
var b = a;
console.log (a === b); => tõsi
a = [];
console.log (a); => []
console.log (b); => []
console.log (a === b); => vale // võrdlus mäluaadressi järgi

Välja arvatud võrdlus - JavaScripti kasutatakse väärtustena primitiivsete tüüpide ja viidete korral objektide jaoks. Käitumine tundub olevat sama nagu Ruby puhul.

Noh, mitte päris.

Primitiivseid väärtusi JavaScriptis ei jagata mitme muutuja vahel. Isegi kui seate muutujad üksteisega võrdseks. Iga muutuja, mis esindab primitiivset väärtust, kuulub garanteeritult unikaalsesse mälukohta.

See tähendab, et ükski muutuja ei osuta kunagi samale mälu aadressile. Samuti on oluline, et väärtus ise salvestataks füüsilises mälukohas.

Meie näites, kui kuulutame b = a, osutab b kohe teisele mäluaadressile, millel on sama „stringi” väärtus. Nii et te ei pea teist mäluaadressile osutamist a ümber määrama.

Seda nimetatakse väärtuse järgi kopeerituks, kuna teil pole juurdepääsu ainult mälu aadressile.

Kui kuulutate a = b, omistatakse sellele väärtus, nii et a ja b osutavad erinevatele mäluaadressidele

Vaatame paremat näidet, kus see kõik oluline on.

Kui muudame mäluaadressis sisalduvat väärtust rubiinides, on kõigil aadressile osutavatel viidetel sama värskendatud väärtus:

a = 'x'
b = a
a.koer ('y')
paneb => 'xy'
paneb b => 'xy'
b.koer ('z')
paneb => 'xyz'
paneb b => 'xyz'
a = 'z'
paneb => 'z'
paneb b => 'xyz'
a [0] = 'y'
paneb => 'y'
paneb b => 'xyz'

Võib arvata, et JavaScriptis muutub ainult a väärtus, kuid ei. Te ei saa isegi algväärtust muuta, kuna teil pole otsest juurdepääsu mäluaadressile.

Võib öelda, et määrasite x-i, kuid see omistati väärtusele, nii et mäluaadress hoiab väärtust x, kuid te ei saa seda muuta, kuna teil pole sellele viiteid.

var a = 'x';
var b = a;
a.koer ('y');
console.log (a); => 'x'
console.log (b); => 'x'
a [0] = 'z';
console.log (a); => 'x';

JavaScripti objektide käitumine ja juurutamine on sama, mis Ruby muudetavatel objektidel. Mõlemad koopiad peavad olema kontrollväärtused.

JavaScripti primitiivsed tüübid kopeeritakse väärtuse järgi. Käitumine on sama nagu Ruby muutumatute objektide puhul, mida kopeeritakse võrdlusväärtuse järgi.

Ah?

Jällegi, kui kopeerite midagi väärtuse järgi, tähendab see, et te ei saa algset väärtust muuta (muteerida), kuna mälu aadressile pole viidet. Kirjutuskoodi seisukohast on see sama asi nagu muutumatute üksuste olemasolu, mida te ei saa muteerida.

Kui võrrelda Ruby'i ja JavaScripti, on vaikimisi ainus andmetüüp, mis käitub erinevalt String (sellepärast kasutasime Stringit ülaltoodud näidetes).

Ruby keeles on see muudetav objekt ja seda kopeeritakse / antakse kontrollväärtuse järgi, JavaScriptis aga primitiivset tüüpi ja kopeeritakse / antakse edasi väärtuse järgi.

Kui soovite objekti kloonida (mitte kopeerida), peate seda tegema mõlemas keeles selgesõnaliselt, et saaksite veenduda, et algset objekti ei muudeta:

a = {'nimi': 'Kate'}
b = a.kloon
b ['nimi'] = 'Anna'
paneb = = {: name => "Kate"}
var a = {'nimi': 'Kate'};
var b = {... a}; // uue ES6 süntaksiga
b ['nimi'] = 'Anna';
console.log (a); => {nimi: "Kate"}

Äärmiselt oluline on seda meeles pidada, vastasel korral võib teie koodile korduvalt tuginedes ilmneda ebameeldivaid vigu. Hea näide oleks rekursiivne funktsioon, kus kasutate objekti argumendina.

Teine on React (JavaScripti kasutajaliidese raamistik), kus peate oleku värskendamiseks alati läbima uue objekti, kuna võrdlus põhineb objekti ID-l.

See on kiirem, kuna te ei pea objekti ridahaaval läbi minema, et näha, kas seda on muudetud.

Kuidas muutujaid funktsioonidele antakse?

Muutujate edastamine funktsioonidele toimib samamoodi nagu enamikes keeltes samade andmetüüpide kopeerimine.

JavaScriptis kopeeritakse ja antakse väärtuste järgi üle primitiivsed tüübid ning objektid kopeeritakse ja antakse edasi kontrollväärtuse alusel.

Arvan, et see on põhjus, miks inimesed räägivad ainult väärtusest mööda või mööda viitest ega näi kunagi mainimas kopeerimist. Ma arvan, et nad eeldavad, et kopeerimine töötab samamoodi.

a = 'b'
def väljund (string) # kontrollväärtusest möödunud
  string = 'c' # on ümber määratud, nii et puudub viide originaalile
  paneb nööri
lõpp
väljund (a) => 'c'
paneb a => 'b'
def output2 (string) # kontrollväärtusest möödunud
  string.concat ('c') # muudame väärtust, mis asub aadressil
  paneb nööri
lõpp
väljund (a) => 'bc'
paneb => 'bc'

Nüüd JavaScriptis:

var a = 'b';
funktsiooni väljund (string) {// väärtuse järgi edasi antud
  string = 'c'; // määrati teisele väärtusele
  console.log (string);
}
väljund (a); => 'c'
console.log (a); => 'b'
funktsiooni output2 (string) {// väärtusest möödunud
  string.concat ('c'); // me ei saa seda ilma viiteta muuta
  console.log (string);
}
väljund2 (a); => 'b'
console.log (a); => 'b'

Kui annate funktsioonile objekti (mitte primitiivse tüübi, nagu me tegime) JavaScripti kaudu, töötab see samamoodi nagu Ruby näide.

Teised keeled

Oleme juba näinud, kuidas toimib väärtuse järgi kopeerimine / edastamine ja võrdlusväärtuse järgi kopeerimine / edastamine. Nüüd näeme, mis viitega möödub, ja saame ka teada, kuidas saaksime objekte muuta, kui mööda läheme väärtusest.

Otsides võrdluskeeltest möödasõitu, ei leidnud ma neid liiga palju ja otsustasin valida Perli. Vaatame, kuidas kopeerimine Perlis töötab:

minu $ x = 'string';
minu $ y = $ x;
$ x = 'uus string';
printida "$ x"; => 'uus string'
trükkida "$ y"; => 'string'
minu $ a = {data => "string"};
minu $ b = $ a;
$ a -> {data} = "uus string";
printida "$ a -> {data} \ n"; => 'uus string'
printida "$ b -> {andmed} \ n"; => 'uus string'

Noh, see tundub olevat sama nagu Ruby puhul. Ma pole leidnud ühtegi tõendit, kuid ma ütleksin, et Perli kopeeritakse Stringi kontrollväärtuse järgi.

Vaatame nüüd, mida tähendab viide:

minu $ x = 'string';
printida "$ x"; => 'string'
sub foo {
  $ _ [0] = 'uus string';
  trükkida "$ _ [0]"; => 'uus string'
}
foo ($ x);
printida "$ x"; => 'uus string'

Kuna Perli läbitakse viitega, kui teete funktsiooni piires ümber määramise, muudab see ka mäluaadressi algväärtust.

Väärtuskeeleks minekuks olen valinud minna, kuna kavatsen lähitulevikus oma teadmisi süvendada:

paketi peamine
import "fmt"
func changeAddress (a * int) {
  fmt.Println (a)
  * a = 0 // mäluaadressi väärtuseks 0
}
func changeValue (a int) {
  fmt.Println (a)
  a = 0 // muudame väärtust funktsiooni piires
  fmt.Println (a)
}
func main () {
  a: = 5
  fmt.Println (a)
  fmt.Println (& a)
  changeValue (a) // a antakse väärtuse järgi
  fmt.Println (a)
  changeAddress (& a) // väärtuse järgi antakse a mäluaadress üle
  fmt.Println (a)
}
Koodi kompileerimisel ja käitamisel saate järgmise teabe:
0xc42000e328
5
5
0
5
0xc42000e328
0

Kui soovite mäluaadressi väärtust muuta, peate kasutama viiteid ja edastama mäluaadressid väärtuse järgi. Kursor hoiab väärtuse mäluaadressi.

Operaator genereerib kursori oma operandile ja * operaator tähistab osuti alusväärtust. See tähendab põhimõtteliselt seda, et edastate väärtuse mälu aadressi &-ga ja mäluaadressi väärtuse määrate tähega *.

Järeldus

Kuidas keelt hinnata:

  1. Mõistage keeles olevaid andmetüüpe. Lugege läbi mõned tehnilised andmed ja mängige nendega ringi. Tavaliselt taandub see primitiivsetele tüüpidele ja objektidele. Seejärel kontrollige, kas need objektid on muudetavad või muutumatud. Mõni keel kasutab erinevat tüüpi andmeside jaoks erinevat kopeerimise / edastamise taktikat.
  2. Järgmine samm on muutujate määramine, kopeerimine, ümber määramine ja võrdlemine. See on minu arvates kõige olulisem osa. Kui olete selle kätte saanud, saate aru saada, mis toimub. See aitab palju, kui kontrollite mäluaadresse ringi mängides.
  3. Muutujate edastamine funktsioonidele pole tavaliselt eriline. Tavaliselt töötab see samamoodi nagu enamikes keeltes kopeerimine. Kui teate, kuidas muutujaid kopeeritakse ja ümber määratakse, teate juba, kuidas need funktsioonidele edasi antakse.

Keeled, mida me siin kasutasime:

  • Mine: väärtuse järgi kopeeritud ja edastatud
  • JavaScript: Primitiivseid tüüpe kopeeritakse / antakse edasi väärtuse järgi, objekte kopeeritakse / antakse edasi võrdlusväärtuse järgi
  • Rubiin: kopeeritakse ja edastatakse kontrollväärtuse + muudetavate / muutumatute objektide järgi
  • Perl: kopeeritakse võrdlusväärtuse alusel ja edastatakse viitega

Kui inimesed ütlevad, et viitest möödunud, tähendavad nad tavaliselt etalonväärtusest möödumist. Võrdlusväärtusest möödumine tähendab, et muutujad lähevad väärtuse järgi ümber, kuid need väärtused on viited objektidele.

Nagu nägite, kasutab Ruby ainult möödasõiduväärtust, JavaScripti puhul aga segastrateegiat. Sellegipoolest on peaaegu kõigi andmetüüpide käitumine sama, mis tuleneb andmestruktuuride erinevast rakendamisest.

Enamik tavakeeli kas kopeeritakse ja antakse edasi väärtuse järgi või kopeeritakse ja antakse edasi kontrollväärtuse alusel. Viimast korda: möödasõidu kontrollväärtust nimetatakse tavaliselt viitena möödumiseks.

Üldiselt on möödasõiduväärtus turvalisem, kuna teil ei teki probleeme, kuna te ei saa juhuslikult algset väärtust muuta. Kirjutamine on aeglasem ka seetõttu, et objektide muutmiseks peate kasutama viiteid.

See on sama mõte nagu staatilise ja dünaamilise tippimise korral - arenduse kiirus turvalisuse hinnaga. Nagu arvasite, on väärtuse möödumine tavaliselt madalama taseme keelte, näiteks C, Java või Go, funktsioon.

Viite või võrdlusväärtuse korral kasutatakse tavaliselt kõrgema taseme keeli, nagu JavaScript, Ruby ja Python.

Kui avastad uue keele, läbida protsess nagu siin, ja saate aru, kuidas see töötab.

See pole lihtne teema ja ma pole kindel, et kõik, mis ma siin kirjutasin, on õige. Kui arvate, et olen selles artiklis mõned vead teinud, palun andke sellest kommentaarides teada.