Viss, kas jums jāzina, ņemot vērā atsauci vai vērtību

Runājot par programmatūras inženieriju, ir diezgan daudz nepareizi izprastu jēdzienu un nepareizi lietotu terminu. Atsauce pret vērtību noteikti ir viens no tiem.

Es atceros to dienu, kad lasīju šo tēmu un katrs avots, kuru es apmeklēju, šķita pretrunā ar iepriekšējo. Pagāja kāds laiks, lai gūtu pārliecību par to. Man nebija izvēles, jo tas ir pamata priekšmets, ja esat programmatūras inženieris.

Pēc dažām nedēļām es saskāros ar nejauku kļūdu un nolēmu uzrakstīt rakstu, lai citiem cilvēkiem būtu vieglāk izdomāt visu šo lietu.

Es katru dienu kodēju Rubīnā. Es diezgan bieži izmantoju arī JavaScript, tāpēc šai prezentācijai esmu izvēlējies šīs divas valodas.

Lai saprastu visus jēdzienus, mēs izmantosim arī dažus Go un Perl piemērus.

Lai aptvertu visu tēmu, jums ir jāsaprot 3 dažādas lietas:

  • Kā valodā tiek ieviestas pamatā esošās datu struktūras (objekti, primitīvie tipi, maināmība).
  • Kā darbojas mainīga piešķiršana / kopēšana / atkārtota piešķiršana / salīdzināšana
  • Kā mainīgie tiek nodoti funkcijām

Pamatdatu tipi

Rubīnā nav primitīvu tipu, un viss ir objekts, ieskaitot veselus skaitļus un Būla vērtības.

Jā, Ruby vietnē ir TrueClass.

true.is_a? (TrueClass) => taisnība
3.is_a? (Vesels skaitlis) => taisnība
true.is_a? (objekts) => taisnība
3.is_a? (Objekts) => taisnība
TrueClass.is_a? (Objekts) => taisnība
Integer.is_a? (Objekts) => taisnība

Šie objekti var būt mainīgi vai negrozāmi.

Nemainīgs nozīmē, ka nekādā veidā nevar mainīt objektu pēc tā izveidošanas. Ar vienu object_id dotajai vērtībai ir tikai viens piemērs, un tas nemainās neatkarīgi no tā, ko jūs darāt.

Pēc noklusējuma Rubīnā nemainīgi objektu veidi ir šādi: Būla, Ciparu, Nulle un Simbols.

MRI objekta objekts_id ir tāds pats kā VĒRTĪBA, kas apzīmē objektu C līmenī. Vairumam objektu veidu šī VĒRTĪBA ir rādītājs vietai atmiņā, kur tiek glabāti faktiskie objekta dati.

Turpmāk objekta ID un atmiņas adresi lietosim aizstājami.

Izpildīsim dažus rubīna kodus MRI nemainīgam simbolam un mainīgai virknei:

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

Kā redzat, kamēr simbola versijā vienai un tai pašai vērtībai ir vienāds objekta_id, virkņu vērtības pieder dažādām atmiņas adresēm.

Atšķirībā no Ruby, JavaScript ir primitīvi veidi.

Tie ir - Būla, nulle, nenoteikts, virkne un skaitlis.

Pārējie datu tipi atrodas objektos (masīvs, funkcija un objekts). Šeit nav nekā iedomātā, tas ir daudz tiešāks nekā Rubīns.

[] Masīva instancē => taisnība
Objekta instancē => taisnība
Objekta 3 gadījumi => nepatiesi

Mainīga piešķiršana, kopēšana, atkārtota piešķiršana un salīdzināšana

Rubīnā katrs mainīgais ir tikai atsauce uz objektu (jo viss ir objekts).

a = 'virkne'
b = a
# Ja jūs atkārtoti piešķirat vērtību ar tādu pašu vērtību
a = 'virkne'
liek b => 'virkne'
liek == b => patiesās # vērtības ir vienādas
liek a.object_id == b.object_id => false # memory adr-s. atšķirties
# Ja jūs atkārtoti piešķirat vērtību citai
a = 'jauna virkne'
ievieto => 'jaunu virkni'
liek b => 'virkne'
liek a == b => nepatiesas # vērtības ir atšķirīgas
liek a.object_id == b.object_id => false # memory adr-s. atšķiras arī

Piešķirot mainīgo, tā ir atsauce uz objektu, nevis pašu objektu. Kopējot objektu b = a, abi mainīgie rādīs uz vienu un to pašu adresi.

Šo uzvedību sauc par kopiju pēc atsauces vērtības.

Stingri runājot rubīnos un JavaScript, viss tiek kopēts pēc vērtības.

Tomēr, runājot par objektiem, vērtības ir šo objektu atmiņas adreses. Pateicoties tam, mēs varam mainīt vērtības, kas atrodas tajās atmiņas adresēs. Atkal to sauc par kopiju pēc atsauces vērtības, bet vairums cilvēku to sauc par kopiju pēc atsauces.

Tas būtu kopija ar atsauci, ja pēc atkārtotas piešķiršanas “jaunajai virknei” b arī norādītu uz to pašu adresi un to pašu “jaunās virknes” vērtību.

Kad jūs deklarējat b = a, a un b norāda uz to pašu atmiņas adresiPēc a (a = “virknes”) atkārtotas piešķiršanas, a un b norāda uz dažādām atmiņas adresēm

Tas pats ar nemainīgu tipu kā Integer:

a = 1
b = a
a = 1
liek b => 1
liek == b => true # salīdzinājumu pēc vērtības
liek a.object_id == b.object_id => true # salīdzinājumu pēc atmiņas adr.

Piešķirot a tam pašam skaitlim, atmiņas adrese paliek nemainīga, jo dotajam veselajam skaitlim vienmēr ir tas pats object_id.

Kā redzat, salīdzinot jebkuru objektu ar citu, tas tiek salīdzināts ar vērtību. Ja vēlaties pārbaudīt, vai tie ir viens un tas pats objekts, jums jāizmanto objekts_id.

Apskatīsim JavaScript versiju:

var a = 'virkne';
var b = a;
a = 'virkne'; # a ir piešķirta tai pašai vērtībai
console.log (a); => “virkne”
console.log (b); => “virkne”
console.log (a === b); => patiess // salīdzinājums pēc vērtības
var a = [];
var b = a;
console.log (a === b); => taisnība
a = [];
console.log (a); => []
console.log (b); => []
console.log (a === b); => nepatiess // salīdzinājums pēc atmiņas adreses

Izņemot salīdzinājumu - JavaScript izmanto pēc vērtības primitīvajiem tipiem un pēc atsauces objektiem. Uzvedība izskatās tāda pati kā Rubīnā.

Nu, ne gluži.

Primitīvās vērtības JavaScript netiks dalītas starp vairākiem mainīgajiem. Pat ja mainīgos lielumus iestatīsit vienādus. Katram mainīgajam, kas apzīmē primitīvu vērtību, tiek garantēts, ka tas pieder pie unikālas atmiņas vietas.

Tas nozīmē, ka neviens no mainīgajiem nekad nenorādīs uz to pašu atmiņas adresi. Ir arī svarīgi, lai pati vērtība tiktu saglabāta fiziskās atmiņas vietā.

Mūsu piemērā, kad mēs deklarējam b = a, b uzreiz norādīs uz citu atmiņas adresi ar tādu pašu “virknes” vērtību. Tātad jums nav jāpiešķir a, lai norādītu uz citu atmiņas adresi.

To sauc par kopiju pēc vērtības, jo jums nav piekļuves tikai atmiņas adresei šai vērtībai.

Kad jūs deklarējat a = b, tam tiek piešķirta vērtība, tāpēc a un b norāda uz dažādām atmiņas adresēm

Apskatīsim labāku piemēru, kur tas viss ir svarīgi.

Rubīnā, ja mēs modificējam vērtību, kas atrodas atmiņas adresē, tad visām atsaucēm, kas norāda uz adresi, būs tāda pati atjauninātā vērtība:

a = 'x'
b = a
a.kaķis ('y')
ievieto => 'xy'
liek b => 'xy'
b.kaķis ('z')
ievieto => 'xyz'
liek b => 'xyz'
a = 'z'
liek => “z”
liek b => 'xyz'
a [0] = 'y'
liek => 'y'
liek b => 'xyz'

Varētu domāt, ka JavaScript mainās tikai vērtības vērtība, bet nē. Jūs pat nevarat mainīt sākotnējo vērtību, jo jums nav tiešas piekļuves atmiņas adresei.

Varētu teikt, ka esat piešķīris “x”, bet tam tika piešķirta vērtība, tāpēc atmiņas adresei ir vērtība “x”, taču jūs to nevarat mainīt, jo uz to nav atsauces.

var a = 'x';
var b = a;
a.kaķis ('y');
console.log (a); => “x”
console.log (b); => “x”
a [0] = 'z';
console.log (a); => 'x';

JavaScript objektu uzvedība un ieviešana ir tāda pati kā Ruby mainīgajiem objektiem. Abas kopijas ir atsauces vērtība.

JavaScript primitīvie tipi tiek kopēti pēc vērtības. Uzvedība ir tāda pati kā Ruby nemainīgajiem objektiem, kuri tiek kopēti ar atsauces vērtību.

Vai?

Ja atkal kaut ko kopējat pēc vērtības, tas nozīmē, ka jūs nevarat mainīt (mutēt) sākotnējo vērtību, jo nav atsauces uz atmiņas adresi. Rakstīšanas koda skatījumā tas ir tas pats, kas piemīt nemainīgām entītijām, kuras jūs nevarat mutēt.

Ja salīdzina Ruby un JavaScript, vienīgais datu tips, kas pēc noklusējuma “uzvedas” atšķirīgi, ir virkne (tāpēc iepriekšējos piemēros mēs izmantojām virkni).

Rubīnā tas ir mainīgs objekts, un tas tiek kopēts / nodots ar atsauces vērtību, savukārt JavaScript - primitīvs tips un kopēts / nodots pēc vērtības.

Ja vēlaties klonēt (nekopēt) objektu, tas jums tas jādara tieši abās valodās, lai jūs varētu pārliecināties, ka sākotnējais objekts netiks pārveidots:

a = {'name': 'Kate'}
b = a.klons
b ['name'] = 'Anna'
ievieto => {: name => "Kate"}
var a = {'name': 'Kate'};
var b = {... a}; // ar jauno ES6 sintakse
b ['vārds'] = 'Anna';
console.log (a); => {vārds: "Kate"}

Ir ārkārtīgi svarīgi to atcerēties, pretējā gadījumā, vairākkārt piesaucot kodu, jums parādīsies šķebinošas kļūdas. Labs piemērs būtu rekursīvā funkcija, kurā jūs izmantojat objektu kā argumentu.

Vēl viens ir React (JavaScript interfeisa ietvarstruktūra), kur vienmēr ir jānodod jauns objekts atjaunināšanas stāvoklim, jo ​​salīdzinājums darbojas, pamatojoties uz objekta ID.

Tas notiek ātrāk, jo jums nav jāiet cauri objektam pa rindai, lai redzētu, vai tas ir mainīts.

Kā mainīgie tiek nodoti funkcijām

Mainīgo nodošana funkcijām darbojas tāpat kā kopēšana vieniem un tiem pašiem datu tipiem lielākajā daļā valodu.

JavaScript versijā primitīvie tipi tiek kopēti un nodoti pēc vērtības, un objekti tiek kopēti un nodoti pēc atsauces vērtības.

Es domāju, ka tas ir iemesls, kāpēc cilvēki runā tikai par garāmgājību vai ar atsauci, un šķiet, ka nekad nepiemin kopēšanu. Es domāju, ka viņi pieņem, ka kopēšana darbojas tāpat.

a = 'b'
def izvade (virkne) # pagājusi ar atsauces vērtību
  string = 'c' # atkārtoti piešķirts, tāpēc nav atsauces uz oriģinālu
  liek auklu
beigas
izvade (a) => 'c'
liek a => 'b'
def output2 (string) #, kas pagājis ar atsauces vērtību
  string.concat ('c') # mēs mainām vērtību, kas atrodas adresē
  liek auklu
beigas
izvade (a) => 'bc'
liek => 'bc'

Tagad JavaScript:

var a = 'b';
funkcijas izvade (virkne) {// nodota pēc vērtības
  virkne = 'c'; // pārdalīts citai vērtībai
  console.log (virkne);
}
izvade (a); => 'c'
console.log (a); => 'b'
funkcija output2 (virkne) {// nodota pēc vērtības
  string.concat ('c'); // mēs to nevaram modificēt bez atsauces
  console.log (virkne);
}
output2 (a); => 'b'
console.log (a); => 'b'

Ja funkcijai JavaScript nododat priekšmetu (nevis primitīvu tipu, kā mēs), tas darbojas tāpat kā Rubīna piemērs.

Citas valodas

Mēs jau esam redzējuši, kā darbojas kopēšana / nodošana pēc vērtības un kopēšana / nodošana ar atsauces vērtību. Tagad mēs redzēsim, kas ir atsauce garām, un mēs arī atklāsim, kā mēs varam mainīt objektus, ja mēs ejam garām vērtībai.

Meklējot garām atsauces valodas, es nevarēju atrast pārāk daudz, un es izvēlējos Perlu. Apskatīsim, kā kopēšana darbojas Perlā:

mans $ x = 'virkne';
mans $ y = $ x;
$ x = 'jauna virkne';
drukāt "$ x"; => 'jauna virkne'
drukāt "$ y"; => “virkne”
my $ a = {data => "string"};
mans $ b = $ a;
$ a -> {data} = "jauna virkne";
izdrukāt "$ a -> {data} \ n"; => 'jauna virkne'
drukāt "$ b -> {data} \ n"; => 'jauna virkne'

Nu, šķiet, tas ir tas pats, kas Rubīnā. Es neesmu atradis nevienu pierādījumu, bet es teiktu, ka Perls tiek kopēts pēc virknes atsauces vērtības.

Tagad pārbaudīsim, ko nozīmē atsauce:

mans $ x = 'virkne';
drukāt "$ x"; => “virkne”
sub foo {
  $ _ [0] = 'jauna virkne';
  drukāt "$ _ [0]"; => 'jauna virkne'
}
foo ($ x);
drukāt "$ x"; => 'jauna virkne'

Tā kā Perl tiek pārsūtīts ar atsauci, ja veicat atkārtotu piešķiršanu funkcijas ietvaros, tas mainīs arī atmiņas adreses sākotnējo vērtību.

Par vērtību valodas izturēšanu esmu izvēlējies Go, jo pārskatāmā nākotnē plānoju padziļināt zināšanas par Go:

paketes galvenā
importēt "fmt"
func changeAddress (a * int) {
  fmt.Println (a)
  * a = 0 // iestatot atmiņas adreses vērtību uz 0
}
func changeValue (int) {
  fmt.Println (a)
  a = 0 // mēs mainām vērtību funkcijā
  fmt.Println (a)
}
func main () {
  a: = 5
  fmt.Println (a)
  fmt.Println (& a)
  changeValue (a) // a tiek nodots pēc vērtības
  fmt.Println (a)
  changeAddress (& a) // atmiņas vietas adrese tiek nodota pēc vērtības
  fmt.Println (a)
}
Apkopojot un palaižot kodu, iegūsit sekojošo:
0xc42000e328
5
5
0
5
0xc42000e328
0

Ja vēlaties mainīt atmiņas adreses vērtību, jums jāizmanto rādītāji un pēc vērtības jāpārvieto atmiņas adreses. Rādītājam ir vērtības atmiņas adrese.

Operators ģenerē rādītāju tā operandā, un * operators norāda rādītāja pamatvērtību. Tas būtībā nozīmē, ka jūs pārsūtāt vērtības atmiņas adresi ar &, un atmiņas adreses vērtību iestatāt ar *.

Secinājums

Kā novērtēt valodu:

  1. Izprotiet datu pamatā esošos datus valodā. Izlasiet dažas specifikācijas un spēlējieties ar tām. Parasti tas ir primitīvs tips un objekts. Pēc tam pārbaudiet, vai šie objekti ir mainīgi vai negrozāmi. Dažās valodās dažādiem datu tipiem tiek izmantota atšķirīga kopēšanas / nodošanas taktika.
  2. Nākamais solis ir mainīgā piešķiršana, kopēšana, atkārtota piešķiršana un salīdzināšana. Šī, manuprāt, ir vissvarīgākā daļa. Kad to iegūsit, varēsit izdomāt, kas notiek. Tas ļoti palīdz, ja, spēlējot apkārt, pārbaudāt atmiņas adreses.
  3. Mainīgo nodošana funkcijām parasti nav īpaša. Parasti tas darbojas tāpat kā kopēšana lielākajā daļā valodu. Tiklīdz jūs zināt, kā mainīgie tiek kopēti un pārdalīti, jūs jau zināt, kā tie tiek nodoti funkcijām.

Valodas, kuras mēs šeit izmantojām:

  • Iet: kopēts un nodots pēc vērtības
  • JavaScript: Primitīvie tipi tiek kopēti / nodoti pēc vērtības, objekti tiek kopēti / nodoti pēc atsauces vērtības
  • Rubīns: kopēts un nodots ar atsauces vērtību + maināmiem / nemainīgiem objektiem
  • Perls: kopēts ar atsauces vērtību un nodots pēc atsauces

Kad cilvēki saka, ka viņi ir izturējuši atsauci, viņi parasti saprot, ka izturējuši atsauces vērtību. Ja iet garām atsauces vērtībai, mainīgos lielumus apiet ar vērtību, bet šīs vērtības ir atsauces uz objektiem.

Kā redzējāt, Rubīns izmanto tikai atsauces vērtību, savukārt JavaScript izmanto jauktu stratēģiju. Tomēr gandrīz visiem datu tipiem uzvedība ir vienāda atšķirīgo datu struktūru ieviešanas dēļ.

Lielākā daļa vispārizglītojošo valodu tiek kopētas un nodotas pēc vērtības, vai arī kopētas un nodotas pēc atsauces vērtības. Pēdējo reizi: atsauces vērtības pārsniegšanu parasti sauc par atsauces vērtību.

Parasti garāmgājiena vērtība ir drošāka, jo neradīsit problēmas, jo sākotnējo vērtību nevar nejauši mainīt. Arī rakstīšana notiek lēnāk, jo, ja vēlaties mainīt objektus, ir jāizmanto norādes.

Tā ir tāda pati ideja kā ar statisko rakstīšanu un dinamisko rakstīšanu - attīstības ātrums uz drošības rēķina. Kā jūs uzminējāt, garāmgājiena vērtība parasti ir zemāka līmeņa valodu, piemēram, C, Java vai Go, iezīme.

Parole vai atsauces vērtība parasti tiek izmantota augstākā līmeņa valodās, piemēram, JavaScript, Ruby un Python.

Kad jūs atklāsit jaunu valodu, iziet procesu tāpat kā mēs šeit, un jūs sapratīsit, kā tā darbojas.

Šī nav viegla tēma, un es neesmu pārliecināts, ka viss ir pareizi, ko es šeit uzrakstīju. Ja jūs domājat, ka esmu pieļāvis dažas kļūdas šajā rakstā, lūdzu, dariet to zināmu komentāros.