C

Liittynyt
30.10.2016
Viestejä
112
Löytyykö foorumilta C kielellä koodaavia? Minä olen jonkin verran koodannut C:llä yliopistossa ja lisäksi on tullut tehtyä pari pientä ja yksinkertaista sulautettujen projektia kotona. Nyt pitäisi jälleen aloittaa kyseisen kielen kertaaminen, jotta voisi jatkossa tehdä vähän haastavampia projekteja ja ehkä pääsisin hyödyntämään sitä töissäkin tulevaisuudessa.

Viime aikoina on tullut etsittyä netistä erilaisia interaktiivisia tutoriaaleja, joilla voi kerrata asioita. Tällä hetkellä tuollainen "korjaa virhe koodissa" tai "lisää puuttuvat osat" -harjoittelu tuntuu olevan mielekkäämpää kuin omien pikkuohjelmien kirjoittaminen ja kääntäminen. Esimerkiksi codewarsista ja tältä sivulta löytyy yksinkertaisia tehtäviä kertaukseen:

Learn C - Free Interactive C Tutorial
 
Yhden kesän olin harjoittelussa paikassa, jossa jouduin käyttämään C:tä sulautettujen järjestelmien kanssa. Pääasiassa C on oikein mukava ja kompakti kieli, mutta siitä puuttuu paljon modernien kielten mukavia ominaisuuksia. Omaan makuuni isoimpia vikoja ovat ehkä helposti määritettävät nimiavaruudet ja se, että buildien tekemiseen ei ole mitään universaalia ja helppoa standardia. Samoin ns. tagged unionien puute on vaikuttanut isosti siihen, miksi muutamassa harrasteprojektissa olen valinnut jonkin toisen kielen. Jossain tilanteessa myös geneeriset tyypit olisivat muistaakseni olleet hyödyllisiä...
 
Omaan makuuni isoimpia vikoja ovat ehkä helposti määritettävät nimiavaruudet ja se, että buildien tekemiseen ei ole mitään universaalia ja helppoa standardia. Samoin ns. tagged unionien puute on vaikuttanut isosti siihen, miksi muutamassa harrasteprojektissa olen valinnut jonkin toisen kielen. Jossain tilanteessa myös geneeriset tyypit olisivat muistaakseni olleet hyödyllisiä...

Ainoa minulle vastaan tullut kieli, jossa käännös ja kieli ovat olleet naimisissa keskenään on Smalltalk. Mitä tarkoitat tagged unionilla? Geneerinen ohjelmointi on mahdollista myös C:llä, se vain ei ole implisiittisesti tyyppiturvallista, kuten sukulaiskielessään C++:ssa.
 
Ainoa minulle vastaan tullut kieli, jossa käännös ja kieli ovat olleet naimisissa keskenään on Smalltalk. Mitä tarkoitat tagged unionilla? Geneerinen ohjelmointi on mahdollista myös C:llä, se vain ei ole implisiittisesti tyyppiturvallista, kuten sukulaiskielessään C++:ssa.
Ei tarvitse kieltä ja kääntämistä sentään naittaa keskenään. Tarkoitin enemmänkin jotain sensuuntaista, että kielen kylkeen on rakennettu jonkinlaisen standardin asemassa oleva järjestelmä kääntämistä varten. Esimerkiksi C#:ssa siihen on MSBuild, Rustissa Cargo ja Haskellissa Cabal/Stack, ja juurikaan muita ei taideta käyttää. Sen sijaan C:ssä on (varsinkin) Linux-maailmassa makefilet, Windows-puolella jotain Visual Studion omaa (MSBuild tms.?), ja lisäksi monella IDE:llä on omansa. Päälle vielä korkeamman tason systeemit (esim. CMake), jotka voivat tuottaa useampia em. jutuista (esim. makefile). Sen verran tosin myönnän mutuilevani, että oletan C:n olevan tässä yhtä hajaantunut kuin C++, jossa nuo väitteeni ainakin pätevät.

Tagged union on käytännöss union, joka tietää, mikä kentistä on käytössä. Jos on siis union, joka sisältää vaikka kokonaisluvun ja char-osoittimen (esim. merkkijonon), tagged union tietää, kumman se oikeasti sisältää. C:ssä pystyy toki lisäämään itse yhden kentän, joka kertoo, mikä kenttä on käytössä, mutta onhan se aika tympeää puuhaa. Esimerkiksi Javan enumit taitavat olla käytännössä tagged unioneita, jos en ole ihan väärässä. Summatyypeiksihän noita myös kutsutaan, jos tuo nimitys on tutumpi.

Arvelinkin että geneerinen ohjelmointi on mahdollista, mutta arvostan kovasti tyyppiturvallisuutta. Menisikö geneerinen ohjelmointi C:ssä yleisesti kenties void-osoitinten avulla?
 
Tuli noista unioneista mieleen, että aika harvoin tulee itse käytettyä niitä omissa koodeissa. Structia sen sijaan tulee käytettyä välillä vähän liikaakin :D Tuntuu jotenkin helpomalta hahmottaa asioita, kun toistensa kanssa tekemisissä olevat asiat yhden structin alle.

Muistelen, että C:ssä muistinhallinta oli hankalahkoa, koska siellä piti itse varmistaa ettei jokin aliohjelma jne varaa muistia silloin, kun se ei ole käytössä. Monissa muissa kielissä tuo muistinhallinta taitaa olla enemmän automatisoitu. Minulle suurinta tuskaa aiheuttavat osoittimet. Jotenkin tuntuu, että heti kertauksen jälkeen ne ymmärtää joten kuten, mutta parin kk jälkeeen niiden toiminnan hahmottaminen tuntuu vaikealta. Menee aina pitkään hahmottaa miten jokin koodi toimii, jos siellä on käytetty paljon osoittimia.

ps. Mukava nähdä, että foorumilta löytyy vielä ihmisiä, jotka koodaavat C:llä. :)
 
C:llä olisi kiva toteuttaa jotain oikeaa projektia, jotta sen oppisi ihan oikeasti hallitsemaan ainakin kohtuullisesti. Mutta sellaista en ole keksinyt, joten lähdin ratkomaan Project Eulerin tehtäviä sen avulla. Tietysti tuohon olisi olemassa paremminkin soveltuvia kieliä, mutta jospa matkan varrella ainakin oppisi toteuttamaan C:llä sellaisia rakenteita, joita on muissa kielissä valmiina.
 
Tuli noista unioneista mieleen, että aika harvoin tulee itse käytettyä niitä omissa koodeissa. Structia sen sijaan tulee käytettyä välillä vähän liikaakin :D Tuntuu jotenkin helpomalta hahmottaa asioita, kun toistensa kanssa tekemisissä olevat asiat yhden structin alle
Aika vähänhän noita unioineita useimmissa projekteissa taitaa tarvita. Joissain projekteissa tarvetta voikin sitten olla enemmän, jolloin C:ssä toivoisi olevan myös näppärämpi union-tyyppi.

Muistelen, että C:ssä muistinhallinta oli hankalahkoa, koska siellä piti itse varmistaa ettei jokin aliohjelma jne varaa muistia silloin, kun se ei ole käytössä. Monissa muissa kielissä tuo muistinhallinta taitaa olla enemmän automatisoitu.
Useimmat modernit kielet hoitavat muistinhallinna roskienkeruun avulla. Käytännössä moderneissa kielissä muistia varataan melkeinpä automaattisesti aika surutta eikä sen ohjemoijan tarvitse murehtia sen vapauttamisesta itse, koska ajoympäristö käy aika ajoin tarkistamassa, mitkä osat muistista ovat käytössä ja mitkä eivät ja sen perusteella vapauttaa käyttämätöntä muistia jälleen käytettäväksi. C:ssä taas muistin varaaminen pitää tehdä itse ja roskienkeruun puuttumisen takia se pitää myös vapauttaa itse. Lisäksi manuaalisessa muistinhallinnassa pitää huolehtia siitä, ettei palauta mistään funktiosta pino-osoittimia, koska pinossa oleva data käytännössä tuhoutuu funktiosta palatessa (tai olettaakseni oikeastaan hyvin pian ohjelman suorituksen jatkuessa, kun pinoon kirjoitetaan muuta dataa edellisen päälle).

Molemmissa muistinhallintatavoissa on toki puolensa. Roskienkeruu on helppoa mutta vähemmän suorituskykyistä, ja nähtävästi varsinkin kovaa reaaliaikaista suorituskykyä vaativissa ohjelmistoissa, kuten esimerkiksi peleissä, roskienkeruu saattaa keskeyttää suorituksen ihan havaittavissa olevan pitkäksi ajaksikin. Esimerkiksi peleissä roskienkeruu voisi pahimmillan näkyä säännöllisesti toistuvana nykimisenä. Manuaalinen muistinhallinta taas on suorituskykyisempää, mutta sen kanssa on helpompi tehdä virheitä. Vaikeaa sekään ei sinänsä ole. C:ssä muistinhallinta taitaa aika pitkälle onnistua jo sillä, että jokaista malloc-kutsua kohti on jossain vaiheessa myös free-kutsu.

Minulle suurinta tuskaa aiheuttavat osoittimet. Jotenkin tuntuu, että heti kertauksen jälkeen ne ymmärtää joten kuten, mutta parin kk jälkeeen niiden toiminnan hahmottaminen tuntuu vaikealta. Menee aina pitkään hahmottaa miten jokin koodi toimii, jos siellä on käytetty paljon osoittimia.
Minulla taas ei ole vaikeuksia muistaa, vaikka edellisestä isommasta C-koodailusta on kulunut kolmisen vuotta. :D Ehkä kaipaat vielä lisää harjoitusta ja ymmärryksen syventämistä tuon osalta, niin pysyy paremmin mielessä?a
 
Muistelen, että C:ssä muistinhallinta oli hankalahkoa, koska siellä piti itse varmistaa ettei jokin aliohjelma jne varaa muistia silloin, kun se ei ole käytössä.

Tai lähinnä täytyy pitää huoli, että muistia varataan kerran tarvittava määrä, ei kirjoitella varaamattomaan muistiin mitään ja varattu muisti vapautetaan kerran kun sitä ei enää tarvita. Lisäksi on hyviä työkaluja, joilla voi testat tähän liittyviä bugeja.
 
Aika vähän tulee käytettyä nykyään, C++:aakin alkaa näkymään paikoissa jotka ennen oli pyhitetty vain C:lle ja sillä välttää osan ongelmista, aukoista ja mundaaneista turhista jutuista mitä C vaatii (toki paska C++ on hidasta, bloattia ja hasardia mutta turha siitä on valittaa).

Mutta onhan C toki "lingua franca" jonka osaamisesta on hyötyä aina.

Molemmissa muistinhallintatavoissa on toki puolensa. Roskienkeruu on helppoa mutta vähemmän suorituskykyistä, ja nähtävästi varsinkin kovaa reaaliaikaista suorituskykyä vaativissa ohjelmistoissa, kuten esimerkiksi peleissä, roskienkeruu saattaa keskeyttää suorituksen ihan havaittavissa olevan pitkäksi ajaksikin. Esimerkiksi peleissä roskienkeruu voisi pahimmillan näkyä säännöllisesti toistuvana nykimisenä. Manuaalinen muistinhallinta taas on suorituskykyisempää, mutta sen kanssa on helpompi tehdä virheitä. Vaikeaa sekään ei sinänsä ole. C:ssä muistinhallinta taitaa aika pitkälle onnistua jo sillä, että jokaista malloc-kutsua kohti on jossain vaiheessa myös free-kutsu.
Lisäksi esim. C++ tarjoaa työkalut abstrahoida muistinhallintaa roimasti ilman perffin heikkenemistä RAII:n hengessä. Esimerkiksi vaikka STL:n containerit ei vaadi manuaalista muistinhallintaa tai gc:tä, pelien kanssa välillä pitää käyttää muita ratkaisuja mutta niilläkin usein vältetään C-henkinen manuaalinen ja virheherkkä alloc/free -rumba.

Rust on hienompine tyyppijärjestelmineen vielä askel eteenpäin tästä.
 
Viimeksi muokattu:
Sain sydänlaakin jo puolessa välissä ketjua. Olkaa ystävällisiä älkääkä kommentoiko, jos ette oikeasti tiedä kielestä yhtään mitään.

C:tä ei kannata käyttää muualla kuin raudanläheisessä ohjelmoinnissa koska sen ilmaisuvoima on heikko ja abstraktiotaso matala. Jos kuitenkin operoi tuossa maailmassa niin pitää ymmärtää aikalailla siitä miten rauta ohjelmistoa kuljettaa.

Paljon C:tä tuottavampia, siirrettävämpiä ja käyttäjäystävällisiä kieliä on olemassa. Jos ei oikeasti tarvitse tai halua vääntää käyttöjärjestelmätason koodia ei sitä kannata opetella.

C:n union -tietotyyppi on kyllä tagged union siinä mielessä kuin mitä esimerkiksi wikipedia määrittelee asian, mutta ilmeisesti ajattelit reflektiivistä tietotyyppiä?
 
Sain sydänlaakin jo puolessa välissä ketjua. Olkaa ystävällisiä älkääkä kommentoiko, jos ette oikeasti tiedä kielestä yhtään mitään.
Tämä viittaa nyt ilmeisesti erityisesti tuohon omaan Project Euler -kommenttiini? No, niin tai näin, en kyllä ymmärrä, mitä vikaa on opetella C:tä muuten vain, vaikka mielenkiinnosta. Itseäni ainakin C:ssä juuri kiinnostaa sen raudanläheisyys ja erot modernimpiin kieliin, vaikka en C:tä tule todennäköisesti koskaan tarvitsemaan tai osaamaan sillä tasolla, että siitä suoraan mitään hyötyä olisi.

En minä siis halua kenenkään varpaille tässä astua tai teeskennellä olevani mikään asiantuntija, mutta ei kai siitä sydäriä silti tarvitse saada.
 
C on kielenä itsenään todella simppeli. Juuret sillä on siinä että tarvittiin siirrettävä kieli ettei kaikkea tarvinnut koodata assemblerilla, eikä sen lähtökohdat ole mihinkään siitä muuttuneet. Ongelmat tulee lähinnä siitä että kaikki tarvitsee tehdä pääsääntöisesti itse ja laitteistonsa pitää tuntea. Hyvä ja lähes ainoa kieli tehdä uuden laitteiston kääntäjä, Linuxin ajuri tai vaikkapa RTOS adaptaatio. Muuhun hommaan jossa jotakin muuta kieltä voi käyttää niin turhan työläs. Algoritmien ja tietorakenteiden opetteluun se on varmasti ihan mielenkiintoinen. Melkein kannattaisi kuitenkin käyttää C++ ympäristöä ja koodata sen avulla jonkin C:n tyylioppaan mukaisesti. Itse käytän vallan nykyään Visual Studio Codea. Vaikka sitäkin vaivaa hieman ähky niin ei se yhtä mammutti ole kuin Eclipse. Toisaalta jos Qt teknologiana kiinnostaa, niin QtCreator on ihan pätevä C/C++ editori myös.
 
Sain sydänlaakin jo puolessa välissä ketjua. Olkaa ystävällisiä älkääkä kommentoiko, jos ette oikeasti tiedä kielestä yhtään mitään.
Jos minun viesteissäni on jotain virheitä, korjaa ihmeessä niin osaan minäkin toivottavasti ottaa opikseni.

C:n union -tietotyyppi on kyllä tagged union siinä mielessä kuin mitä esimerkiksi wikipedia määrittelee asian, mutta ilmeisesti ajattelit reflektiivistä tietotyyppiä?
Wikipedian artikkelin mukaan tagged unionissa on oltava myös ns. tag-kenttä, joka kertoo, mikä kenttä on kulloinkin käytössä:

Wikipedia sanoi:
In computer science, a tagged union, also called a variant, variant record, discriminated union, disjoint union, or sum type, is a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which one is in use.
C:n unioneissa tällaista ei ole. Joka tapauksessa minulle on noin yleisesti ottaen yhdentekevää, millä tavalla tavalla summatyypin aktiivisen kentän saa kaivettua esille, kunhan se on rakennettu jollain helppokäyttöisellä tavalla kieleen. Yksi tapa tehdä asia on juurikin tuo tag-kenttä, mutta muitakin tapoja lienee.
 
Minulla on JetBrains CLion käytössä, vaikkakin kuitenkin aika usein pieniä ohjelmia tehdessä tulee käytettyä pelkkää Vimiä.
noh alottelijalle mikään eksperttieditori ei ole kovin suoiteltava. Syntaksin korostus on melkein pakollinen ja koodin täydennys plussaa. Jälkimmäinen tosin jossakin vaiheessa alkaa olemaan enempi tiellä.
 
C:n unioneissa tällaista ei ole. Joka tapauksessa minulle on noin yleisesti ottaen yhdentekevää, millä tavalla tavalla summatyypin aktiivisen kentän saa kaivettua esille, kunhan se on rakennettu jollain helppokäyttöisellä tavalla kieleen. Yksi tapa tehdä asia on juurikin tuo tag-kenttä, mutta muitakin tapoja lienee.

Koodi:
union memBlock
{
    uint8_t as_byte;
    int8_t as_octet;
    uint16_t as_word;
    int16_t as_signed_short;
    uint32_t as_dword;
    int32_t as_signed_int;
};

union structEquivalent
{
    uint8_t as_array[8U];
    struct
    {
        uint32_t first;
        uint32_t second;
    } as_struct;
};

int main()
{
    union memBlock memPool[0xFF];
    union structEquivalent odd;
    union memBlock* freeList = memPool;
    freeList->as_byte = 0xAD;
    odd.as_array[0U] = freeList->as_byte;
    odd.as_struct.second = freeList->as_dword;
    exit(0);
}
 
Koodi:
union memBlock
{
    uint8_t as_byte;
    int8_t as_octet;
    uint16_t as_word;
    int16_t as_signed_short;
    uint32_t as_dword;
    int32_t as_signed_int;
};

union structEquivalent
{
    uint8_t as_array[8U];
    struct
    {
        uint32_t first;
        uint32_t second;
    } as_struct;
};

int main()
{
    union memBlock memPool[0xFF];
    union structEquivalent odd;
    union memBlock* freeList = memPool;
    freeList->as_byte = 0xAD;
    odd.as_array[0U] = freeList->as_byte;
    odd.as_struct.second = freeList->as_dword;
    exit(0);
}
Niin yllä puhuttiin unionin käytöstä mahdollisesti useamman eri tyyppisen arvon kanssa ts. vieden tilaa vain maksimityypin verran, sitä varten kielessä ei ole mitään sisäänrakennettua tapaa vaan pitää itse träkätä jollain lisäkentälä mikä lisää virhealtista tilanhallintaa. Toki tämä on ihan luonnollista C:n kanssa mutta taas yksi syy lisää miettiä jotain modernimpia vaihtoehtoja.
 
Koodi:
union memBlock
{
    uint8_t as_byte;
    int8_t as_octet;
    uint16_t as_word;
    int16_t as_signed_short;
    uint32_t as_dword;
    int32_t as_signed_int;
};

union structEquivalent
{
    uint8_t as_array[8U];
    struct
    {
        uint32_t first;
        uint32_t second;
    } as_struct;
};

int main()
{
    union memBlock memPool[0xFF];
    union structEquivalent odd;
    union memBlock* freeList = memPool;
    freeList->as_byte = 0xAD;
    odd.as_array[0U] = freeList->as_byte;
    odd.as_struct.second = freeList->as_dword;
    exit(0);
}
Nähtävästi pointtini meni vähän ohi... Ehkä @peksi onnistui tuossa ylempänä selittämään asian tarpeeksi hyvin? Pointtini oli siis se, että on tilanteita, joissa esim. funktion paluuarvo ei aina olekaan yhtä tyyppiä, vaan funktio voi palauttaa erityyppisiä arvoja eri kerroilla. Hyvin yksinkertaisena esimerkkinä voi olla vaikka funktio result parse_int(char * c), missä result voi olla joko int tai jonkinlainen virhe. Käytännössä C:ssä result olisi varmaankin toteuttava jokseenkin näin:

Koodi:
union result
{
    enum tag_t tag;
    int ok;
    enum error_t error;
}

enum tag_t { OK, ERROR }
enum error_t { NOT_INT, OUT_OF_RANGE }
Milläs hankkiudut tag-kentästä eroon C:ssä? Et millään, ja tämä oli se pointti. Olisi kiva, jos kieleen olisi sisäänrakennettuna jokin ominaisuus, jonka avulla tietäisi automaattisesti, oliko tulos ok vai error. Se tosin voisi jonkin verran sotia C:n perusideaa vastaan joten ymmärrän, miksei sellaista ole, mutta voin silti olla tyytymätön tilanteeseen. Tämä esimerkki on vähän turhankin yksinkertainen ja tehtävissä jollain muullakin tavalla ihan tarpeeksi fiksusti, mutta ihan hyvin voi tulla tilanteita, joissa mahdollisia paluutyyppejä on vielä enemmän, jolloin muilla tavoilla tekeminen voi alkaa mennä varsin mielenkiintoiseksi.
 
Nähtävästi pointtini meni vähän ohi... Ehkä @peksi onnistui tuossa ylempänä selittämään asian tarpeeksi hyvin? Pointtini oli siis se, että on tilanteita, joissa esim. funktion paluuarvo ei aina olekaan yhtä tyyppiä, vaan funktio voi palauttaa erityyppisiä arvoja eri kerroilla. Hyvin yksinkertaisena esimerkkinä voi olla vaikka funktio result parse_int(char * c), missä result voi olla joko int tai jonkinlainen virhe. Käytännössä C:ssä result olisi varmaankin toteuttava jokseenkin näin:

Koodi:
union result
{
    enum tag_t tag;
    int ok;
    enum error_t error;
}

enum tag_t { OK, ERROR }
enum error_t { NOT_INT, OUT_OF_RANGE }
Milläs hankkiudut tag-kentästä eroon C:ssä? Et millään, ja tämä oli se pointti. Olisi kiva, jos kieleen olisi sisäänrakennettuna jokin ominaisuus, jonka avulla tietäisi automaattisesti, oliko tulos ok vai error. Se tosin voisi jonkin verran sotia C:n perusideaa vastaan joten ymmärrän, miksei sellaista ole, mutta voin silti olla tyytymätön tilanteeseen. Tämä esimerkki on vähän turhankin yksinkertainen ja tehtävissä jollain muullakin tavalla ihan tarpeeksi fiksusti, mutta ihan hyvin voi tulla tilanteita, joissa mahdollisia paluutyyppejä on vielä enemmän, jolloin muilla tavoilla tekeminen voi alkaa mennä varsin mielenkiintoiseksi.
Miksi yhden funktion pitäisi edes pystyä palauttamaan montaa eri tyyppiä olevia paluuarvoja? Paljon järkevämpää olisi tehdä jokaiselle tyypille oma funktionsa vaikkapa näin:
Koodi:
enum error_codes {
        E_CODE_SUCCESS,
        // ...
};

int str_to_int(char const *str, int *result)
{
        int error_code = E_CODE_SUCCESS;

        *result = 0;

        // ...

        return error_code;
}

int str_to_double(char const *str, double *result)
{
        // ...
}
 
Miksi yhden funktion pitäisi edes pystyä palauttamaan montaa eri tyyppiä olevia paluuarvoja? Paljon järkevämpää olisi tehdä jokaiselle tyypille oma funktionsa vaikkapa näin:
Koodi:
enum error_codes {
        E_CODE_SUCCESS,
        // ...
};

int str_to_int(char const *str, int *result)
{
        int error_code = E_CODE_SUCCESS;

        *result = 0;

        // ...

        return error_code;
}

int str_to_double(char const *str, double *result)
{
        // ...
}
Koska parametrin käyttäminen paluuarvon välittämiseen on seuraus teknisestä rajoitteesta eikä niinkään intuitiivinen suunnitteluratkaisu. Jos se on mahdollista, boilerplaten määrä jää pieneksi, ja on yhtä tehokasta palauttaa niin tulos kuin mahdollinen virhekin normaalina paluuarvona, miksi kukaan käyttäisi parametreja paluuarvoihin? C:ssä tämä on kyllä mahdollista mutta boilerplatea tulee ikävä määrä, ja monissa tapauksissa tehokkuuskin voinee kärsiä. Intuitiivisuuskin kärsii, kun funktiokutsuille tulee enemmän parametreja ja tuloksen selvittämiseksi pitää tyypillisesti tarkistaa kaksi täysin eri arvoa (funktion normaali paluuarvo ja paluuarvona käytetty parametri). Entä jos mahdollisia paluutyyppejä on enemmänkin kuin kaksi, kuten esimerkiksi parsereissa voi käydä? Onnistuu toki C:ssä mainiosti, mutta voisi onnistua helpomminkin. Vertailun vuoksi sama esimerkki Rustissa on varsin intuitiivinen:

Koodi:
// Standardikirjaston tyyppi, mutta vastaavan voi määritellä itsekin.
// Olkoon tässä esillä vähän kattavamman kuvan saamiseksi.
enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn parse_int(s: &str) -> Result<i32, ParseError> {
    // ...
}

fn main() {
    let result = parse_int("asdf");
    match result {
        Err(_) -> println!("Error")
        Ok(x) -> println!("{}", x.to_str())
    }
}
parse_intillä on parametreja vain yksi, joten parametrien tyypeistä tai järjestyksestä ei tule epäselvyyttä. Lisäksi tulosta tarkastellessa riittää yhden arvon tarkastelu, ja kyseisestä arvosta saa suoraan kaivettua tarkempaa tietoa. Sama yleistyy myös helposti useammille paluutyypeille.
 
Koska parametrin käyttäminen paluuarvon välittämiseen on seuraus teknisestä rajoitteesta eikä niinkään intuitiivinen suunnitteluratkaisu. Jos se on mahdollista, boilerplaten määrä jää pieneksi, ja on yhtä tehokasta palauttaa niin tulos kuin mahdollinen virhekin normaalina paluuarvona, miksi kukaan käyttäisi parametreja paluuarvoihin? C:ssä tämä on kyllä mahdollista mutta boilerplatea tulee ikävä määrä, ja monissa tapauksissa tehokkuuskin voinee kärsiä. Intuitiivisuuskin kärsii, kun funktiokutsuille tulee enemmän parametreja ja tuloksen selvittämiseksi pitää tyypillisesti tarkistaa kaksi täysin eri arvoa (funktion normaali paluuarvo ja paluuarvona käytetty parametri). Entä jos mahdollisia paluutyyppejä on enemmänkin kuin kaksi, kuten esimerkiksi parsereissa voi käydä? Onnistuu toki C:ssä mainiosti, mutta voisi onnistua helpomminkin. Vertailun vuoksi sama esimerkki Rustissa on varsin intuitiivinen:

Koodi:
// Standardikirjaston tyyppi, mutta vastaavan voi määritellä itsekin.
// Olkoon tässä esillä vähän kattavamman kuvan saamiseksi.
enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn parse_int(s: &str) -> Result<i32, ParseError> {
    // ...
}

fn main() {
    let result = parse_int("asdf");
    match result {
        Err(_) -> println!("Error")
        Ok(x) -> println!("{}", x.to_str())
    }
}
parse_intillä on parametreja vain yksi, joten parametrien tyypeistä tai järjestyksestä ei tule epäselvyyttä. Lisäksi tulosta tarkastellessa riittää yhden arvon tarkastelu, ja kyseisestä arvosta saa suoraan kaivettua tarkempaa tietoa. Sama yleistyy myös helposti useammille paluutyypeille.
Älä käytä C:tä, jos tapa jolla C:ssä tehdään asiat ei miellytä?
 
Älä käytä C:tä, jos tapa jolla C:ssä tehdään asiat ei miellytä?
En käytäkään, jos jokin muu kieli mielestäni sopii tarkoitukseen paremmin. Alun perinhän tämä lähti liikkeelle siitä, kun totesin joitakin minua C:ssä ärsyttäviä asioita. Kai luit ekan viestini? Tosin valtava C:lle tehtyjen kirjastojen määrä kyllä pakottaa usein ainakin harkitsemaan myös C:tä.
 
En käytäkään, jos jokin muu kieli mielestäni sopii tarkoitukseen paremmin. Alun perinhän tämä lähti liikkeelle siitä, kun totesin joitakin minua C:ssä ärsyttäviä asioita. Kai luit ekan viestini? Tosin valtava C:lle tehtyjen kirjastojen määrä kyllä pakottaa usein ainakin harkitsemaan myös C:tä.
Luin toki. Pointtini oli lähinnä se, että nuo mainitsemasi asiat on kyllä mahdollista toteuttaa C:llä, mutta se ei välttämättä ole se järkevin lähestymistapa ongelmaan. Varsinkaan silloin, jos käytettävissä olisi jokin toinen ohjelmointikieli, josta nuo ominaisuudet löytyvät sisäänrakennettuina.
 
Itse jonkun verran C:llä tehnyt ihan harraste pohjalta noitten microcontrollereiden kanssa, nykyään yrittänyt tuollakin kyllä siirtyä C++ ennemminkin.

Eipä taida C:n voittanutta kuitenkaan vielä olla noitten microprossujen kanssa, ellei sitten assembleria halua kirjoittaa suoraan, tietty tämäkin varmaan muuttumassa kovin kun ARM piireissä on niin tolkuton määrä kaikkea muistia ettei enää tarvitse miettiä mistä säästää yhden tavun.
 
Paluuarvon tulkinta on kutsujan vastuulla ja toisaalta kutsu joka näyttää kielen syntaksissa siltä että paluuarvotyyppejä on useita on oikeasti ajoympäristön ja/tai kääntäjän kulmasta kaksi erillistä funktiota. C ei arvaile tietotyyppejä suorituksenaikaisesti joten kaikki tällainen dynaamisuus pitää emuloida. Oletinkin että hait takaa reflektiota etkä C:n tyylistä implisiittisesti nimettyjen kenttien käyttöön perustuvaa tulkintaa unionin sisällön esitystavasta.
 
Paluuarvon tulkinta on kutsujan vastuulla ja toisaalta kutsu joka näyttää kielen syntaksissa siltä että paluuarvotyyppejä on useita on oikeasti ajoympäristön ja/tai kääntäjän kulmasta kaksi erillistä funktiota. C ei arvaile tietotyyppejä suorituksenaikaisesti joten kaikki tällainen dynaamisuus pitää emuloida. Oletinkin että hait takaa reflektiota etkä C:n tyylistä implisiittisesti nimettyjen kenttien käyttöön perustuvaa tulkintaa unionin sisällön esitystavasta.
Eihän useampaan paluutyyppiin mitään lisäfunktioita tai suorituksenaikaisuutta tarvita... Esimerkiksi mainittu tagged union riittää siihen mainiosti kunhan kääntäjän vastuulle jättää vähän enemmän kuin C:ssä on tapana (minkä vuoksi ymmärrän siis oikein hyvin, miksei C:ssä ole kyseistä ominaisuutta). Toki jos ihan aidosti haluaa useita erilaisia paluutyyppejä, tilanne on eri, mutta ainakaan minä en kaipaa ihan sellaista - päinvastoin, en ole vielä törmännyt tilanteeseen, jossa siitä olisi mitään iloa.
 
Kaikki dynaaminen tyypitys on asia joka vaatii suorituksenaikaista tukea. Tapoja toteuttaa tämä ajoympäristössä on sitten moninaisia, osa enemmän osa vähemmän läpinäkyviä ohjelmoijalle. Staattisen tyypityksen käyttäminen polymorphismiin, joka on läpinäkyvä ohjelmoijalle vaaditaan aina automaattista tyyppien muokkaamista tai funktioiden instantioimista käännösaikana. Kääntäjä voi tehdä tämän tiettyyn rajaan saakka ohjelmoijalle valmiiksi, mutta C:ssä homman joutuu tekemään käsin. Operaattoreiden ja funktioiden kuormituksen puuttuessa tämä ei ole edes läpinäkyvää.

En tiedä oletko pythonia tai javascriptiä ohjelmoinut mutta ainakin jälkimmäinen perustuu pitkälle siihen että data voidaan käyttää monen tyyppisenä ja ajoympäristö hoitaa tyypinmuunnoksen automaattisesti jos siihen on vain tiedossa oleva tapa.
 
Kaikki dynaaminen tyypitys on asia joka vaatii suorituksenaikaista tukea. Tapoja toteuttaa tämä ajoympäristössä on sitten moninaisia, osa enemmän osa vähemmän läpinäkyviä ohjelmoijalle. Staattisen tyypityksen käyttäminen polymorphismiin, joka on läpinäkyvä ohjelmoijalle vaaditaan aina automaattista tyyppien muokkaamista tai funktioiden instantioimista käännösaikana. Kääntäjä voi tehdä tämän tiettyyn rajaan saakka ohjelmoijalle valmiiksi, mutta C:ssä homman joutuu tekemään käsin. Operaattoreiden ja funktioiden kuormituksen puuttuessa tämä ei ole edes läpinäkyvää.

En tiedä oletko pythonia tai javascriptiä ohjelmoinut mutta ainakin jälkimmäinen perustuu pitkälle siihen että data voidaan käyttää monen tyyppisenä ja ajoympäristö hoitaa tyypinmuunnoksen automaattisesti jos siihen on vain tiedossa oleva tapa.
Nimenomaan staattisella tyypityksellä pystyy kyllä käytännössä hoitamaan usean eri tyypin paluuarvot käytännössä. Tarkalleen ottaen silloin vain palautetaan jonkinlainen rakenne, joka voi sisältää vähän mitä sattuu, mutta "mitä sattuu" on kuitenkin ennalta määritetty. Esimerkiksi C:n union on tällainen, ja tag-kentällä höystettynä saa myös tiedon siitä, mikä kenttä sisältää validia dataa. Kääntäjälle on myös periaatteessa mahdollista lisätä tag-kenttä ilman, että ohjelmoijan tarvitsee sitä itse lisätä joka paikkaan. C:ssä tämä ei tietenkään onnistu, mutta esimerkiksi Rust on

Pythonista ja JavaScriptistä on sen verran kokemusta että tiedän, etten pidä dynaamisesta tyypityksestä edes etäisesti. Jos staattisesti tyypitetyssä kielessä on tietyt ominaisuudet (esim. tagged union tms.), dynaamisen tyypityksen mahdollistamat asiat pystyy tekemään myös staattisella kielellä eikä dynaamisesta tyypityksestä olekaan enää mitään isompaa etua.
 

Statistiikka

Viestiketjuista
261 811
Viestejä
4 548 009
Jäsenet
74 849
Uusin jäsen
ookooo

Hinta.fi

Back
Ylös Bottom