Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
h2. Osoitinmuuttujat ja muuttujan osoite

[Simppeli esimerkki]

\#include <stdio.h>
Muuttuja voi sisältää arvon tai osoitteen. Kun tietoa täytyy välittää funktioille suuri määrä yhdellä kertaa tai, kun tiedonkulun on oltava kaksisuuntaista, täytyy tieto välittää osoitteen avulla. Esimerkiksi haluttaessa välittää taulukko toiselle funktiolle, annetaan tyypillisesti kutsuttavalle funktiolle tiedoksi paikka, missä kohdassa muistia taulukko sijaitsee, eikä kaikkia taulukon alkioita erikseen. Kaikki merkkijonot välitetään funktioilta toiselle osoitteiden avulla. Osoitinmuuttuja esitellään seuraavasti:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int\* OsoitinKokonaisLukuun;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float\* OsoitinReaalilukuun;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char\* OsoitinMerkkiinTaiMerkkijonoon;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; double\* OsoitinPitkaanReaalilukuun;
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; void \*Tavuosoitin;

Tähti tyypin ja muuttujan nimen välissä kertoo, että kyseessä on osoitin. p kirjainta käytetään usein muuttujan edessä ilmoittamaan tätä (p = pointer = osoitin). Edellä esitellyillä osoitinmuuttujilla voidaan nyt osoittaa vastaavan tyypin muuttujiin. Osoittimen esittelyn jälkeen, osoitin on ns. "villi" osoitin (wild pointer), koska se ei osoita vielä mihinkään. Tällaisen osoittimen käyttö on erittäin vaarallista. Monesti osoitin laitetaan osoittamaan "ei minnekään" seuraavalla tavalla:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int\* pLuku=NULL;

Edellä esitetty esittely takaa sen ettei ko. osoitinta voi käyttää ennen kuin se on alustettu osoittamaan jotain muuttujaa esim. seuraavasti:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int Luku;//esitellään muuttuja nimeltään Luku
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pLuku=&Luku;//Sijoitetaan muuttujan Luku osoite osoiteeseen pLuku
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(pLuku==NULL) printf("VIRHE, LUVATON OSOITTIMEN KÄYtTÖ");

&-merkillä haetaan esitellyn muuttujan osoite.

h2. Osoitetta vastaava muuttuja

Monessa tapauksessa tiedetään, missä tieto on eli tiedetään tiedon osoite. Esim. kaikki tietokoneeseen liitetyt laitteet ovat tietyissä osoitteissa. Osoitetta vastaava muuttuja saadaan *

merkillä. esim.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char Merkki;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char\* pSarjaportti=0x0238;//sarjaportti on liitetty osoitteeseen 0x238
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Merkki= \*pSarjaportti;

Esim.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int Eka=5;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int \*pLuku=NULL;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int OmaLuku=0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pLuku = &Eka;//pLuku osoittamaan OmaLuku muuttujaa
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OmaLuku = \*pLuku;//pLuku osoitteen osoittaman muistipaikan sisältö OmaLuku muuttujalle

Edellä olevassa esimerkissä muuttujaan Eka sijoitetaan arvo viisi, pLuku laitetaan osoittamaan muuttujaa Eka. Seuraavaksi haetaan pLukua vastaava muuttuja \*:llä ja sijoitetaan se muuttujaan OmaLuku

Osoitin osoittaa muuttujan paikkaa muistissa. Osoitinmuuttuja on erityinen muuttuja, joka
sisältää tiettyyn tyyppiin määritellyn osoittimen. Osoittimilla on C-kielessä kolme tärkeää tehtävää: niillä voidaan osoittaa nopeasti vektorin tai taulukon elementteihin, ne mahdollistavat kaksisuuntaisen tiedon siirron funktioiden välillä ja kolmantena ne tukevat linkattuja listoja ja dynaamisia tietorakenteita.

Osoittimien oikea ymmärtäminen on erittäin tärkeää tehokkaan C-ohjelmoinnin kannalta. Osoittimet ovat yksi C:n vahvimmista, mutta myös vaarallisimmista piirteistä. Alustamaton osoitin saattaa aiheuttaa koko ohjelman romahtamisen ja mikä pahinta osoittimien aiheuttamia virheitä on erittäin vaikea löytää, koska ohjelman antamat virheilmoitukset ko. tapauksessa osoittavat tyypillisesti väärään paikkaan. Lisäksi ohjelma saattaa toimia pitkiäkin aikoja näennäisen virheettömästi.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int Luku;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int\* pLuku;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int*\* ppLuku;

&nbsp;&nbsp; Osoitteen osoite&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Osoite&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; Muuttuja
&nbsp;&nbsp; &&Luku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &Luku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Luku
&nbsp;&nbsp; &pLuku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; pLuku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;*pLuku
&nbsp;&nbsp; ppLuku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; \*ppLuku&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \**ppLuku

Yllä oleva taulukko esittää, kuinka voidaan siirtyä muuttujasta sen osoitteeseen ja päin vas\-
toin.

h2. Tiedonvälitys osoitteiden avulla

Esimerkki tiedonvälityksestä osoittimen avulla.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //esittelyt
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void JokuFunktio(int*);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void main();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void main()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
\{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int Luku=5; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JokuFunktio(&Luku); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Luvun arvo on %d",Luku); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getch(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void JokuFunktio(int\* pMuuttuja)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
\{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \*pMuuttuja = \*pMuuttuja * 2; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
Tähdellä on kolme merkitystä:
\* hakee osoitetta vastaavan muuttujan
\* esittelee osoitinmuuttujan
\* toimii myös kertomerkkinä

h2. Osoittimen määrittäminen vektorille

Osoitin vektorille on vektorin nimi ilman indeksiä.
Esim.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  int p*;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*kokonaislukuosoitin*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int testi\[20\]; /*20:n kokonailuvun vektori*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = testi;&nbsp;&nbsp;&nbsp;&nbsp; /*kokonaislukuvektorin ensimmäisen elementin osoitin sijoitetaan osoittimen p arvoksi*/

Edellenen käsky voidaan toteuttaa myös & merkillä
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testi = &testi\[0\];

h2. Osoitinaritmetiikkaa

Osoittimiin voidaan kohdistaa ainoastaan kaksi laskutoimitusta, yhteen\- ja vähennyslasku.
Laskenta on turvallisinta \+\+ ja \-\- operaattoreita käyttäen, jotta osoitin osoittaa aina muuttujan
alkuun.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int \*p;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; float \*f;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int lukuja\[10\];
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; float flukuja\[10\];
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; p=lukuja;//laitetaan kokonaislukuosoitin osoittamaan vektorin alkuun
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; f=flukuja;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; p++;//siirrytään seuraavaan alkioon
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; f++;

p:n arvo nousee laskutoimituksessa kahdella ja f:n arvo kahdeksalla(riippu ympäristöstä, tarkasta sizeof funktiolla lukualueiden koko), koska kokonaisluku on kaksi tavua ja liukuluku kahdeksan tavua pitkä.

h2. Osoittimien vertailu

Osoittimia voidaan vertailla keskenään esim.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (p<q) printf("p osoittaa pienempään muistiosoitteeseen kuin q);

Osoitinvertailuja joudutaan käyttämään erityisesti tapauksissa, joissa tarvitaan pinoa. Tyypillisiä sovellutuksia ovat kääntäjät, tulkit ja taulukkolaskentaohjelmat. (eli ei tavallista)

h2. Osoittimet ja vektorit

Osoittimien ja vektoreiden välillä on läheinen yhteys esim.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char strMerkkiJono\[80\], \*psrtMerkkiJono;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; psrtMerkkiJono = strMerkkiJono;
p1 asetettiin osoittamaan ensimmäistä vektorin elementtiä. Kun halutaan osoittaa neljättä el\-
ementtiä, voidaan kirjoittaa:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; str\[3\]
tai
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \*(p1+3)
Vektoreita voidaan osoittaa osoitinaritmetiikalla tai vektorin indeksien avulla. Osoitin arit\-
metiikka on ohjelman kannalta nopeampi tapa.

----
h2. void osoitin

void-tyyppinen osoitin on ns. geneerinen osoitin. Geneerinen osoitin voi osoittaa minkä tahansa tyypin muuttujaan. Esimerkiksi seuraavat ohjelmalauseet ovat sallittuja.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char MerkkiJono\[80\];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void\* OsoitinMihintahansa=NULL;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OsoitinMihintahansa=Merkkijono;

Geneerisen osoittimen inkrementointi ja degrementoinit muuttavat osoittimen asemaa yhden tavun verran. Geneerisiä osoittimia käytetään tyypillisesti laiteläheisessä ohjelmoinnissa.

----
h2. Osoitin paluuarvona

Paikallisen muuttujan osoitetta ei saa palauttaa, koska paikallinen muuttuja häviää funktiosta poistuttaessa.

int\* funktio()
{
 int a;      // tilapäinen muuttuja, vapautetaan funktiosta poistuttaessa
 int \*tulos = &a; // sijoitetaan tulos-osoittimeen a:n muistipaikan osoite
 return tulos;		// VIRHE\!\!\!  a on paikallinen muuttuja
 }

Oikea ratkaisu

int\* funktio(int\* a) //a on nyt kutsujan jonkin muuttujan osoite
{

  int \*tulos = a; // sijoitetaan tulos-osoittimeen a:n osoite
 return tulos;		// OIKEIN
 }

Toinen mahdollinen ratkaisu

int\* funktio()
{
 static int a;      // staattista muuttujaa ei vapauteta funktiosta poistuttaessa
 int \*tulos = &a; // sijoitetaan tulos-osoittimeen a:n muistipaikkan osoite
 return tulos;		// VIRHE\!\!\!
 }