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:

../../_images/muuttujakuva_1.png

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:

../../_images/muuttujakuva_2.png

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:

../../_images/muuttujakuva_3.png

tai toteutusteknisemmällä tasolla

../../_images/muuttujakuva_4.png

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.

../../_images/listakuva_2.png

Pythonissa taas sekä  varasto1  että  varasto2  ovat vain kaksi eri nimeä samalla listalle, jolloin toisen muuttaminen näkyy myös toisessa.

../../_images/listakuva_1.png

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.

../../_images/listakuva_3.png

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öä.)