Kysymykset ja vastaukset

 

Tälle sivulle on kerätty lähinnä asioita, jotka ovat liian hankalia asiayhteydessään mainittavaksi. Niin, ja sinäkin saat sitten kysyä. Tuntuu hieman skitsofreniselta esittää itselleen kysymyksiä.

 

K: Luen tiedostosta/muusta syötteestä tietoa merkkitaulukkoon merkki kerrallaan. Kun vertailen sitten merkkijonoa: strcmp(input, "wiihaa"); tulee tulokseksi false, vaikka input onkin "wiihaa" kuten pitääkin. Mikä pissii?

V: Jos alustat merkkijonon char mjono[]="Moikkelis koikkelis."; tai luet siihen sisältöä käyttäjältä cin >> mjono; niin lisätään nollatavu merkkijonon perään automaattisesti. Jos kuitenkin luet itse syötettä merkki kerrallaan ja lopetat nollatavun sattuessa kohdalle, kopioimatta nollatavua, homma ei tietenkään toimi. mjono sisältää tekstin "wiihaa" ja toinen vertailtava merkkijono on "wiihaa\0", siis päättyy nollatavuun; tällöin ne eivät ole vastaavia ja vertailut eivät ole koskaan tosia. Ongelman saat ratkaistua yksinkertaisesti sijoittamalla ennen vertailua nollatavun lukemasi merkkijonon loppuun, siis mjono[loppuKohta]=NULL;. Toinen vaihtoehto on lukea syötteestä myös nollatavu mukaan, jos se sellaisen sisältää. Mutta esimerkiksi kun luetaan tiedostosta yksittäisiä sanoja keskeltä lausetta, ei niissä ole nollatavua valmiina perässä ja sinun pitää se itse lisätä.

 

K: Kun käsken ifstream file("files\file.txt") ei tiedostoa löydy. Alihakemistossa on kuitenkin tuollainen tiedosto kuten pitääkin. Onko minua huijattu?

V: C++ käyttää merkkijonoissa ohjausmerkkejä, jotka alkavat \ -merkillä. Jotta kääntäjä tajuaa sinun haluavan \ -merkin eikä mitään ohjausmerkkiä, pitää sinun käyttää \\ -ohjausmerkkiä, joka siis vastaa \ -merkkiä. Oikea syntaksi on siis ifstream file("files\\file.txt").

 

K: Kuinka voin käyttää tekemääni funktiota toisessa ohjelmassa? Kuinka tehdään kirjastoja C++:lla?

V: Jos haluat päästä tosi helpolla, niin kirjoitat funktion kokonaan omaan tiedostoon (funktio.h) ja sitten laitat sinne missä funktiota tarvitaan:

#include"funktio.h"

Tuossa on se vika, että kun include heittää funktion kohdallaan, niin se käännetään aina uudelleen - vaikka sitä ei mitenkään olisi muuteltukaan. Kun on kysymys isommasta projektista ja jos kääntäjä ei vielä ole niin hirveän nopea, niin sillä on tosissaan väliä. Oikein se tehdään niin, että funktion määrittely kirjoitetaan .h tai .hpp-tiedostoksi (funktio.h) ja itse funktion runko erilliseen tiedostoon (funktio.cpp) (katso seuraava kysymys, jossa on arvokas vinkki tähän).

Sitten voidaan joko:
a) Kääntää funktio kirjastoksi (.lib tai .a) ja ottaa kirjasto mukaan sinne missä sitä tarvitaan. Näin on tehty kääntäjän mukana tulevien funktioiden kanssa. Tällä tavalla funktiota voi levittää binäärimuodossa, joten voit laittaa oman apuohjelmakirjastosi levitykseen niin että kukaan ei voi muutella sitä, koska lähdekoodia ei tarvita mukana. Menetelmän heikkous on siinä, että binääri toimii vain yhdessä ympäristössä: joudut kääntämään monta eri versiota kirjastosta, mikäli haluat laajan käyttäjäkunnan (dos, win32, linux, kaikista debug- ja normaaliversio). Kirjaston tekeminen on kääntäjäkohtaista, yleensä se tapahtuu valitsemalla asetuksista projektin kohteeksi (target) kirjasto (library).
b) Tehdä projekti, jossa on mukana monta tiedostoa, siis vaikka main.cpp ja funktio.cpp. Kääntäjä automaattisesti kääntää ne, ja vain ne, osat uudelleen mitä olet muuttanut. Tämä on ehdottomasti kätevämpi tapa kun tarvitset apuohjelmanpätkiä vain itse. Myös julkiseen levitykseen erilliset lähdekooditiedostot ovat käteviä, koska ei tarvita erillisiä binäärejä eri alustoille (jokainen kääntää omansa) ja lähdekoodi yleensä vie vähemmän tilaa ja kopiointiaikaa kuin binäärikoodi.

 

K: Olen tehnyt oman juttuja.h-tiedoston. Käytän sitä monissa paikoissa ja sisällytän sen myös erääseen toiseen vibat.h-tiedostoon. Kun sisällytän ohjelmaani molemmat tiedostot, juttuja.h ja vibat.h, tulee juttuja.h kahdesti, koska se sisällytetään yksinään ja vielä vibat.h:n mukana. Ja sehän ei mene kääntäjästä läpi. Miten homma hoidetaan elegantisti?

V: Kun tehdään .h-tiedostoja on tapana tehdä näin - tai pikemminkin kun teet .h-tiedostoja, tee aina näin:

#ifndef _OMA_H
#define _OMA_H

// sitten itse tiedoston sisalto

#endif

Idea siis on, että ekalla kerralla _OMA_H ei tietenkään ole määritelty. Siis if-not-defined (#ifndef) toteutuu ja _OMA_H määritellään ja tiedoston sisältö käsitellään. Mutta tokalla kerralla kun riviin törmätään, _OMA_H on jo määritelty ja loput tiedostosta jätetään käsittelemättä. Näin itse sisältö ei tule määritellyksi kahdesti missään yhteydessä ja ongelma on ratkaistu ja kaikilla on kivaa ja serpentiiniä ja vappuneniä riittää kaikille.

 

K: Miten saa määriteltyä sellaisen globaalin muuttujan, joka toimii myös toisissa tiedostoissa olevassa koodissa?

V: Ensimmäinen vastaus on: globaaleja muuttujia ei tarvita. Ne ovat yleensä merkki huonosta suunnittelusta. Globaali muuttuja on saatavilla joka puolella, joten sellainen voidaan myös sotkea mistä vaan. Hyvin suunnitellussa oliopohjaisessa C++-ohjelmassa ei ainakaan törmää koskaan globaalin muuttujan tarpeeseen. Poikkeuksena voisi mainita tietyt laitteistoläheiset toimenpiteet, joissa teknisistä syistä on pakko käyttää globaaleja muuttujia. Jos haluat globaaleja käyttää, niin perässä seuraa esimerkki. Maaginen sana on extern, joka siis ilmoittaa muuttujan olevan määritelty jossain muualla. Ohjelma on ihan teennäinen, ei noin oikeasti tietenkään saisi turhaan käyttää globaaleja. Mikäli #ifndef hämää, niin katso edellinen esimerkki.

Tiedosto main.cpp:

#include "program.hpp"

int luku;

int main()
{
	luku = 0;

	while (luku < 100)
	{
		TulostaLuku();
		luku++;
	}
	
	return EXIT_SUCCESS;
}

Tiedosto program.hpp:

#ifndef _program_hpp
#define _program_hpp

void TulostaLuku();

extern int luku;

#endif

Tiedosto tl.cpp:

#include <iostream.h>
#include "program.hpp"


void TulostaLuku()
{
	cout << luku << " ";
}

 

K: Miten saan lisättyä C++-ohjelmiini kuvia? Miten teen animaatioita C++:lla?

V: Tämä on ehdottomasti yleisin asia mitä minulta kysytään. C++ on kieli, jonka suunnittelussa on ollut päämääränä mm.: C++:aa voi käyttää kaikissa ohjelmointiympäristöissä ja C++ on suoraan yhteydessä tietokonelaitteistoon. Monissa eri ympäristöissä toimimisesta suoraa se, että ominaisuuksia, jotka ovat erilaisia eri ympäristöissä, kuten grafiikka, ei ole C++:n otettu mukaan. Kaikissa laitteissa kun ei ole edes näyttöä.

Toisaalta voi kysyä, että miksi Java, joka on vielä helpommin siirrettävissä laiteympäristöstä toiseen, mahdollistaa grafiikan käsittelyn kielen omilla kirjastoilla. Vastaus on siinä, että Java ei ole suorassa yhteydessä laitteistoon ja niinpä välissä oleva virtuaalikone voi tulkita ohjelman tekemät grafiikkaoperaatiot alustana toimivan laitteiston ymmärtämään muotoon. Tulkitseminen luonnollisesti vie aikaa ja niinpä Javalla ei kovin hämmentävään nopeuteen grafiikanpyörittelyssä pääsekään.

Mutta se, että C++-kielessä ei ole valmiita grafiikkaominaisuuksia, ei tarkoita sitä, että C++:lla ei voi käsitellä grafiikkaa. Päinvastoin, suurin osa hyvin graafisista sovelluksista, peleistä ja kuvankäsittelyohjelmista, on tehty C++:lla. Mitään standardia tapaa ei ole, vaan grafiikan käyttö riippuu aina siitä laiteympäristöstä missä toimitaan. Ja kun C++:lla tehdään alustalle räätälöityä grafiikkakoodia, on meno niin nopeata että varmasti karstat karisevat näytönohjaimesta!

Helpoiten grafiikka-asioissa pääsee liikkeelle käyttämällä valmista grafiikkakirjastoa. DOS/Windows/Unix-ympäristössä löytyy Allegro, joka on erittäin hyvä kirjasto grafiikan käsittelyyn - ja sovellukset ovat siirrettävissä ympäristöstä toiseen. Windowsissa voi käyttää Windowsin C-ohjelmointirajapintaa tai C++-tyylistä MFC-oliokirjastoa, jolla sama asia hoituu oliomaisesti - todellinen Windows-peliohjelmointi tosin vaatii jo DirectX-rajapinnan käyttöä. Mikäli haluaa kirjoittaa monessa eri ympäristössä toimivaa grafiikkakoodia, niin Prometheus TrueColor (PTC) -kirjasto on hyvä valinta. Tai on se hyvä grafiikkakirjasto, vaikka siirrettävyys ei kiinnostakaan. Joidenkin kääntäjien mukana tulee yksinkertaisia grafiikkakirjastoja, mutta ne ovat yleensä enemmän tai vähemmän kökköjä.

 

K: Olen lukenut erästä C++-kirjaa ja eteeni tuli seuraava kummallinen koodinpätkä. Mitäköhän mahtaa tarkoittaa tuo "TAI ja sijoitus" -operaatio?

#define CLIP_CODE_N 0x0008 
int p1_code = 0; 
p1_code |= CLIP_CODE_N; // rivi 1

V: Rivi 1 suorittaa bitti-TAI-operaation p1_code:n ja CLIP_CODE literaalivakion välillä ja sijoittaa tuloksen p1_code:en. Eli |= on samaa veljessarjaa kuin esimerkiksi +=. Eli toisin asia sanoiksi asetellen rivi 1 voitaisiin kirjoittaa myös:

p1_code = p1_code | CLIP_CODE_N; 

Bitti-TAI tekee TAI:n jokaisen bitin välillä, eli jos toinen tai molemmat biteistä on 1 niin lopputulos on 1. Kyseessä on inklusiivinen TAI, olemassa on myös ekslusiivinen TAI jossa vain toinen saa olla 1 jotta lopputulos on 1 (ekslusiivinen tai merkitään "hatulla" eli: ^).

Käytännössä |-operaatiota käytetään yleensä bittien kytkemiseen päälle, niinkuin tuossakin koodissa. Ensin tehdään bittimaski, jolla valitaan että mikä bitti halutaan päälle. Esimerkissä maski on 0x0008 eli binäärinä 0000000000001000. Kun suoritetaan TAI, niin niissä kohdin missä maskin bitti on 0, kopioituu alkuperäisen arvon (p1_code) bitti semmoisenaan. Siellä missä maskin arvo on 1, tulee tulokseen myös yksi. Siis koodi laittaa p1_code:n neljännen bitin päälle, jättäen muut alkuperäisiksi (eli nolliksi, koska p1_code alustettiin nollaksi).

Takaisin