Arvot ja viitteet¶
Pythonissa ei ole muuttujia ainakaan siinä merkityksessä kuin millaisiksi muuttujat perinteisesti ajatellaan.
Pythonissa on nimiä ja tietoalkioita (olioita) sekä mekanismi, jolla
nimi saadaan sidottua tietoalkioon siten, että myöhemmin
ohjelmakoodissa tietoalkiota voidaan käsitellä (eli siihen voidaan
viitata) nimen avulla. Käytännössä tämä tarkoittaa sitä, että Pythonin
=
-käskyn avulla tietoalkioille voi antaa nimiä.
Edellisellä ohjelmointikurssilla (Ohjelmointi 1: Johdanto) tätä nimen ja tietoalkion suhdetta havainnollistettiin siten, että esimerkiksi koodista:
a = 42
b = a
c = 42
piirrettiin suunnilleen seuraava kuvio:
Eli on olemassa vain yksi tietoalkio 42
, jolla voi olla
tarvittaessa yksi tai useampia nimiä (a
, b
ja c
),
joiden avulla tietoalkiota voidaan käsitellä. Tällaista tapaa nimetä
tietoalkioita kutsutaan viitesemantiikaksi.
C++:ssa oletusmekanismi on erilainen: sijoitusoperaattori (=
) luo
tietoalkiosta uuden erillisen kopion:
int a = 42;
int b = a;
int c;
c = 42;
jolloin kuvallinen havainnollistus näyttääkin seuraavalta:
Eli nyt ohjelmassa onkin käsiteltävänä useita eri tietoalkioita, jotka
kuitenkin esittävät samaa arvoa, kokonaislukua 42
.
Tätä mekanismia, jossa käsiteltävänä olevasta tietoalkiosta syntyy alustuksen ja sijoituksen yhteydessä uusi kopio, kutsutaan arvosemantiikaksi.
Edellä esitetty havaintokuva ei ole niin havainnollinen kuin voisi toivoa, vaan on parempi miettiä arvosemantiikan toimintaa hiukan yksityiskohtaisemmalla tasolla:
- Arvosemantiikkaa noudattavassa ohjelmointikielessä muuttuja ei tarkoita jollekin tietoalkiolle annettua nimeä, vaan se on tulkittava tietylle keskusmuistialueelle annetuksi nimeksi.
- Lisäksi sijoitus ja alustus tulkitaan kyseisellä muistialueella olevan tiedon korvaamiseksi jollakin uudella tiedolla.
Tästä tulee termi muuttuja: muuttujan nimeämän muistialueen sisältö (siis muuttujaan talletettu arvo) voi muuttua. Tältä pohjalta voisi siis myös esittää väitteen, että Pythonissa ei ole varsinaisia muuttujia, sillä Pythonilla ohjelmoitaessa ainoa asia, joka vaihtelee ohjelman suorituksen kuluessa on se, mistä nimestä mihin arvoon viiva piirretään.
Kuvallisesti havainnollisempi tapa esittää muuttujia C++:ssa on tilanteesta riippuen jompikumpi seuraavista:
tai toteutusteknisemmällä tasolla
jossa lokerot kuvaavat keskusmuistissa olevaa tilaa (muistialueita), joihin tietoa voidaan tallentaa.
Tässä vaiheessa kysymys kuuluu: Miksi oli tarpeen miettiä näin
perustavalla tavalla viite- ja arvosemantiikan eroa? Esimerkki vastaa
kysymykseen. Tutkitaan jälleen kahta ohjelmaa, joissa käytetään hiukan
monimutkaisempaa tietotyyppiä. Python-ohjelmassa tietotyyppinä on
list
, jota C++:ssa vastaa lähes yksi yhteen kirjastotyyppi
deque
.
def main():
varasto1 = [ 3, 9, 27, 81, 243 ]
varasto2 = varasto1
varasto2[3] = 0
print(varasto1[3], varasto2[3])
main()
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> varasto1 = { 3, 9, 27, 81, 243 };
deque<int> varasto2;
varasto2 = varasto1; // Kopioidaan varasto1 varasto2:een
varasto2.at(3) = 0; // Indeksointi at-metodilla (varasto2.at(3)) vastaa Pythonin varasto2[3]:a
cout << varasto1.at(3) << " "
<< varasto2.at(3) << endl;
}
Edellisestä esimerkistä huomaamme, että kun C++:ssa luodaan uusi
deque
, pitää kertoa, minkä tyyppisiä alkioita se sisältää. Tämä
ilmoitetaan kulmasulkeiden sisällä, eli edellisessä esimerkissä
kumpikin deque
sisältää kokonaislukuja.
Kaiken sen pohjalta, mitä viite- ja arvosemantiikasta nyt tiedetään,
osaatko kertoa,
mitä eroa edellisten ohjelmien toiminnassa on? Python tulostaa: 0
0
ja C++: 81 0
.
Miksi? Selitys on se, että C++:ssa sijoitus varasto2 = varasto1
luo uuden kopion deque
-rakenteesta. Kun kopiosta (varasto2
)
sitten muutetaan yhtä alkiota, alkuperäisen rakenne (varasto1
)
säilyy muuttumattomana.
Pythonissa taas sekä varasto1
että varasto2
ovat vain
kaksi eri nimeä samalla listalle, jolloin toisen muuttaminen näkyy
myös toisessa.
C++:n viitteet¶
Myös C++:ssa voi halutessaan luoda viitteitä eli antaa samalle muuttujalle useita nimiä.
Viite luodaan C++:ssa lisäämällä muuttujan määrittelyssä tietotyypin
nimen ja muuttujan nimen väliin &
-merkki. Viite on myös
alustettava sillä muuttujalla, johon viitteen halutaan viittaavaan
(siis muuttujalla, jolle halutaan antaa lisänimi).
kohdetyyppi& viitteen_nimi = kohdemuuttuja;
Voimme toteuttaa edellisen deque
-rakenteita käsittelevän
ohjelman myös näin:
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> varasto1 = { 3, 9, 27, 81, 243 };
deque<int>& varasto2 = varasto1;
varasto2[3] = 0;
cout << varasto1[3] << " "
<< varasto2[3] << endl;
}
Tämä toteutus toimii samoin kuin edellinen Python ohjelma, eli
tulostaa 0 0
.
Edellisessä esimerkissä C++:n viitteet eivät ole kovin hyödyllisiä, mutta pian käytämme viitteitä funktion parametrien tyyppinä. Tämä johtaa hyödyllisempään käyttötarkoitukseen, koska funktiossa voidaan viitteen avulla käsitellä suoraan parametrina annettua tietoalkiota sen kopion sijasta.
(Nice to know: Viittausoperandin &
paikka voi vaihdella: ei ole
väliä, onko se kiinni operaattorissa, operandissa vai erotettu
välilyönnein molemmista:
int kokonaisluku = 42;
int& viite1 = kokonaisluku;
int &viite2 = kokonaisluku;
int & viite3 = kokonaisluku;
Loogisesti ajateltuna operandi kuuluu kuitenkin yhteen tietotyypin kanssa, joten selkeyden vuoksi kannattaa käyttää ensimmäiseksi esitettyä käytäntöä.)