Uživatel:Jkl~cswikiversity/Studuji cpp/Opakování základů jazyka C

Na této stránce si dělám stručné výpisky ze seriálu C/C++ na linuxsoft.cz

Varování: toto jsou moje poznámky a já jejich smysl (většinou) chápu.
Pokud je nechápete vy, ještě to nemusí znamenat, že je chyba ve vašem přijímači :-).

Klasické céčko

editovat

Kapitoly 1-4

editovat
  • nejjednodušší výstup stringuje přes puts("whatever");
  • kompilace - pokud nechci klasické a.out, tak je lépe použít cc zdrojak.c -o vystup - rovnou bude mít práva 777
  • Klasické číselné typy (default je signed):
  1. (un)signed char + float
  2. (un)signed short + double
  3. (un)signed int + long double
  4. (un)signed long
  • globální proměnné definované "venku" těla a funkcí
  • lokální proměnné MUSÍ BÝT DEFINOVÁNY před voláním funkcí
  • typ konstant lze vnutit - F float, L long double
  • lomítka, která jsem neznal:
    • \a = bell
    • \f = nová stránka
  • Konstanty (stringové) se dají hezky skládat:
puts(
"Toto je jeden řádek \n"
"a tohle druhý"
);

Pointery

editovat
  • int *pi;
  • &pi - adresa pi
  • *pi - hodnota na kterou ukazuje
  • všechny funkce se volají hodnotou !! -> chcemeli proměnnou měnit, musíme předat pointer
void nuluj(*pi int){
*pi=0;
}
//
int main(void){
 int i=5;
 nuluj(&i);
 return(i);
}
  • uložení stringu - const char *s = "Nějaký text";
  • printf - parametry
    • %i - int (decimal)
    • %o, %u, %x, %X unsigned - octal,decimal, hex (0a a 0A)
    • %e - double (1.5e12)
    • %f double (1500000000000)
    • %c chr(i)
    • %s const char * - string končící #0
    • %p void * - adresa paměti v hex
  • printf vrací počet vypsaných znaků
  • v případě přetypování to tam nezapomenout napsat !!

Kapitoly 5-8

editovat
  • [1]
  • chci-li pomocí printf napsat místo "ff" "0xff" tak použiju %#x místo %x
  • printf("Tak takhle se dá taky napsat pí: %#+.4f\n\n",i);

Operátory

editovat
  • i=j=0; // vyhodnocuje se zprava, elegantně nulujeme dvě proměnné na jednou
  • výsledkem dělení dvou intů je int, pokud jmenovatele nepřetypujeme (třebas double)
  • kromě i+=1 lze psát i řeba i*=15;
    • návratovou hodotou i++ je i, ale ++i je i+1 !
  • klasické logické operátory not (!), and (&&) a or (||) dělají to co se od nich očekává
  • klasické zanořování ifů by mělo fungovat (na rozdíl od některých pohybných iplementací jiných jazyků ;-) )
  • podmíněný výraz (i==1:"ano":"ne") nesmí být podle definice vlevo, jedině vpravo (nevrací l-hodotu).
    • elegantní prevence dělení nulou: vysledek = i ? 128 / i : 0;
  • while() {} - pascalské while() do begin
  • do {} while () - pascalské repeat .. until
  • for jako v PHPku (no ono je to veskutečnosti obráceně, žejo ...)
  • continue přeskočí zbytek aktuální smyčky
  • break ukončí smyčku
  • label: .... goto label;
    • narozdíl od Pascalu nemusí být label jinak definován (fuj, to je prasárna)
Tak tedy zatím to opakování Céčka jde docela rychle ...

Kapitoly 9-13

editovat
  • pole lze zcela či částečně naplnit již při definici - int b[5] = {1, 2};
    • nebo int xor[2][2] = {{0, 1}, {1, 0}};
  • je legální přiřazení pole pointru typu pi=policko;*pi=5;
  • výpočty s pointery uvažují jejich velikost - a kroky tomu odpovídají - čili máme-li pointer na 2B typ, pak rozdíl v adres "4" neznamená 4 ale 8 bytů !

Stringy

editovat
  • char *s = "krása"; vyhradí konstantu v paměti, tudíž s[3]="v"; hodí segfault :-(
    • na to, aby to prošlo, museli bychom string definovat "natvrdo" jako inicializované pole - char s[6] = "krása"; a pak s[3]="v";
  • getchar() - čte znak std. vstupu
    • ale bacha, až po enteru !!
  • putchar(int) vypisuje jeden znak
  • puts - viz výše
  • char *gets(char *s); - čte string ukončený enterem
    • nelze omezit délku (no bezva :-( )
    • při chybě vrací null
  • elegantní vstup stringu:
char s[256];
puts("Zadej text:");
fgets(s, 256, stdin);
  • pokud by byl vstup delší než 256 znaků, zbyl v bufferu a až při dalším volání fgets by se načetl (což teda má taky svoje mouchy, ale dá se "pročistit"
  • ekvivalentem printf je scanf - používá v podstatě stejnou syntax, jen se předávají pointery
    • scanf("%i%lf", &i, &f); // načte 1 celé a 1 reálné číslo
    • scanf vrací počet načtených hodnot
  • atoi,atol,atof - převede char * s na int,long či double - ekvivalent paskalského val()
    • při chybě vracejí nulu -> je k ničemu. Proč to tam tedy píšou ?!
  • další ekvivalenty funkce val
    • long strtol(const char *nptr, char **endptr, int base);
    • double strtod(const char *nptr, char **endptr);
    • obsluha:
char *chyba;
l = strtol(s, &chyba, 10);
if (chyba == s) { // 0 je OK, jinak ukazuje kam jsme došli
   puts("Sorry vole, error !");
   return -1;
 }
  • modifikace printf vracející stringy
    • sprintf(výstupní_string,formátování,proměnné)
    • snprintf(výstupní_string,délka_výstupního_stringu,formátování,proměnné)
      • prý není v MS visual C++ (koho to ale zajmá, že ;-) ) a jmenuje se tam jinak
  • parametry funkce main:
    • int main(int argc, char *argv[])

Preprocesor

editovat
  • include bez <> vkládá z aktuálního adresáře (nebo z toho, kam ho nasměrujeme, že ...)
  • když náš kód nemá chybu, můžeme jí vygenerovat sami: #error Syntax error somwhere in the code.
  • #define #ifdef, #ifndef, #else a #endif + #undef (undefine)
    • gcc mujkod.c -DDEFINOVANA_PROMENNA
    • příklad:
#include <stdio.h>
int main(void){
 #ifdef POKUS
 puts("Toto je pokusný program\n");
 #else
 puts("Toto je odladěný program\n");
 #endif
 // some more code here ...  
 #ifndef POKUS
 puts("This is COOL software, btw ...\n");
 #endif
 return(0);
}
  • proměnná může mít samozřejmě i hodnotu - #define N 10
  • v define může být cokoliv, takže si můžeme napsat vlastní programovací jazyk ;-)
  • makro se může definovat na více řádků stejně jako se to dělá třeba v bashi
  • makro může mít parametry
    • #define moje_gets(s, size) fgets((s), (size), stdin) //přidá stdin, takže se s tím člověk nemusí zdržovat
    • může "volat" víc funkcí, čárka (,) místo středníku "zapomene" hodnotu.
  • uvnitře definice makra # obalí proměnnou uvozovkami, ## "přilepí" k předchozímu stringu
  • funkci můžeme i deklarovat jako v pascalských unitách (pokud jí potřebujeme definovat pro použití jinde). Konec hlídání pořadí ... hurá
  • pokud se před deklaraci funkce dá static tak je lokální jen pro daný soubor (ekvivalent nezahrnutí do interface unity). Čili implicitně je vše v interface ...

Kapitoly 15-18

editovat

Speciální proměnné

editovat
  • použití pointeru jako funkce viz můj (nebo koneckonců i jejich) demokód
    • void NapisStoTecek(void (* callback)(int)) ... a pak se dá callback normálně zavolat
      • NapisStoTecek(nejakafunkce);
  • proměnná funkce definovaná jako static je de facto globální (resp. pro všechny instance dané funkce)
  • proměnnou jiného souboru můžeme používat, pokud jí nadefinujeme
    • ekvivalentem PHPčkového global je konstrukce typu "extern int pocet;"
  • proměnnou definovanou jako register se překladač pokusí nacpat do registru
  • proměnnou definovanou jako volatile překladač kontroluje i když by si jinak myslel, že se nezměnila (může jí měnit třeba asynchronně běžící vlákno, že)

Hlavičkové soubory

editovat
  • v .h souboru mají být:
  1. hlavičky všech funkcí
  2. extern (globální) proměnné
  3. makra preprocesoru
  4. uživatelské proměnné
  • zabránění vícenásobnému vložení .hčka:
#ifndef _RUTINY_H
#define _RUTINY_H
extern int globalni_promenna;
void rutiny(void);
#endif

jednoduché, elegantní, jen by to člověka muselo napadnout ...

  • v .c části kódu už globální proměnná nemusí být definována jako extern (fakt ???)
  • jako parametry překladači musíme ale dát všechny použité .c soubory

makefile

editovat
  • gcc -c vytvoří jen .o (nemusíme pak znovu pouštět 1. fázi, něco jako .tpu)
  • syntax makefile:
cíl: závislost1 .. závislost moc
[tabelátor]akce
  • implicitní akce pro make je ta první, jinak se musí dát jako parametr
  • čili pro klasický Cčkový program
  1. zakladni ..všechny .o soubory -> gcc ..všechnyO... -o target
  2. knihovna.o knihovna.h knihovna.c ->gcc knihovna.c -c
  3. main.o main.c knihovna.h -> gcc main.c -c
  • lze definovat proměnné "VAR=hodnota" a pak jí volat ${VAR}
  • lze použít #komentář
  • cíl .PHONY znamená, že žádný soubor akci (třeba clean) neodpovídá.
  • když není splněná závislost a není defnovaný cíl, tak se zavolá ${CC} ${CFLAGS} ${CPPFLAGS} -c -o souborXY.o souborXY.c
    • čili je docela záhodno definovat CC, CFLAS (ev. CPPFLAGS)
  • ukázkový makefile - [5] a [6]

parametry překladu

editovat
  • O0-O3 je stupeň optimalizace
  • -g debug info

Ladící nástroje

editovat
  • gdb a jeho rozšíření
    • ddd, xxgdb nebo KDbg
  • tady je drobátko nepřehledný popis gdb :-(

Kapitoly 19-22

editovat
  • [7]
  • switch funkguje stejně jao PHP (opět je to spíš obráceně)
  • bitové operátory:
    • AND &
    • OR |
    • XOR ^
    • NOT ~
  • a*2 odpovídá a <<= 1
  • a div 4 odpovídá a>>=2

Dynamoická alokace paměti

editovat
  • void *malloc(size_t size); //vrací pointer na paměť nebo NULL při chybě
  • void free(void *ptr); //uvolnění paměti
  • void *memcpy(void *dest, const void *src, size_t n); //kopírování, nesmí se překrývat
  • void *memmove(void *dest, const void *src, size_t n); //přesun, může se překrývat
  • void *memset(void *s, int c, size_t n); // nasteví hodnotu c
  • int memcmp(const void *s1, const void *s2, size_t n); //poropvnání
    • s1[n]<s2[n] -> záporné číslo, s1[n]>s2[n] -> kladné číslo, OK-> nula
  • nezapomenout používat sizeof() !

String.h

editovat
  • size_t strlen(const char *s);//klasický strlen ;-)
    • poslední nula se do délky nezapočítává -> potřebujeme tedy strlen+1 bytů
  • char *strcpy(char *dest, const char *src); // ekvivalent memcpy pro string
    • vrací pointer na kopii
  • char *strcat(char *dest, const char *src);
    • připiš(kam,"co");
  • char *strdup(const char *s); // strcpy s tím, že se automaticky naalokuje paměť. Hezké, hezké ...
  • char *strstr(const char *haystack, const char *needle); //poloha jehly v kupce sena
  • int strcmp(const char *s1, const char *s2); //funguje stejně jako memcmp
  • strncpy a strncat mají jako poslední parametr délku kopírovaných dat
    • musíme rúčo připojit nulu

Definice typů

editovat
#ifdef 64bitPlatforma
 typedef int signed32;
#else
  typedef long signed32;
#endif
  • jinak lze použít stejně jako starý, dobrý, pascalský type :-)

Pascal:

var karlicek=record
  krestni,prijmeni:string[16];
end;

Céčko:

struct jmeno {
 char krestni[16];
 char prijmeni[16];
} karlicek;

.. ale zase se dá jméno recyklovat o kousek dál: struct jmeno lojzicek;

  • pokud nepoužijeme pointer, můžeme klasicky pascalisticky karlicek.prijmeni
  • nebo přes typedef struct {} NAZEVTYPU;
  • pokud pointer požijeme
JMENO *karlicek;
karlicek = (JMENO *) malloc(sizeof(JMENO));
karlicek->krestni ... //ekvivalent (*karlicek).krestni
  • sdílení paměti
  • nějak jsem to nepochopil :-(

Kapitoly 23-25

editovat

Klasická dynamická pole

editovat

Pascal

type pseznam=^seznam
type SEZNAM=record
 data:integer;
 dalsi:pseznam;
end;

Cčko:

typedef struct seznam {
  int data;
  struct seznam *dalsi;
} SEZNAM;

Soubory

editovat
FILE *f;
f= fopen("/etc/passwd", "r");
fclose(f);
  • a dále klasické fputs,fputc,fprintf
  • fgetc, fgets, fscanf - fgets uloží \n na konec
  • stdin,stdout,stderr
  • chceme-li soubor otevřít binárně, použijeme "b" - teda třeba rb - čti binárně
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • ekvivalenty seeK() a fpos() jsou
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
  • feof(f) a ferror(f) jsou ekvivalenty Pascalu (resp. to druhé vrací chyby po seeku)
int remove(const char *pathname); // erase(f);

Funkce s proměnným počtem parametrů v Cčku

editovat

Musíme dát pozor na typy a taky kolik dat čteme !

#include <stdarg.h>    // musíme naincludovat !
int suma(int i, ...) { // první proměnná musí být explicitně uvedena
 va_list p;            // proměnná typu va_list = variable list
 int j;
 //  
 if (!i) return 0;     // že by to šlo zavolat úplně bez parametru ? To budu muset někdy vyzkoušet ...
 va_start(p, i);       // nastaví p na první proměnnou, v našem případě i
 do {
   j = va_arg(p, int); // va_arg vrací hodnotu proměnné
   i += j; 
 } while (j);          // když nebude poslední hodnota nula, tak jsme v p*
 va_end(p);            // va_end ukončí práci se zásobníkem. Může to být jen formalita, ale je to zvykem
 return i;
}
int main(void) {
 int i;
 i = suma(1, 2, 3, 4, 5, 0);
 printf("Suma je %i\n", i);
 return 0;
}

Výčtový typ

editovat

Jde to přes typedef enum:

typedef enum {
  PRAHA, BRNO, OSTRAVA
} MESTA;

a pak pro proměnnou typu MESTA můžeme udělat

switch (mestaVar) {
 case PRAHA: puts("Čížek"); break;
//...
}

Kam dál

editovat
  • soustavy - svinská osmičková definovaná nulou na začátku !!:
012=10(dec)=0x0A
12(dec)=014=0x0C

Pojďte s námi do pohádky, projdeme se pamětí

editovat

S následujícím kouskem kódu od čertíka Bertíka se i bez Štěpánky můžeme projít pamětí a i když nemusíme potkat staré kamarády, je možné, že to co najdeme nehodíme do smetí :-)

#include <stdio.h>
int main (void) {
 char s[255];
 puts("\n\nZadej nějaký text:");
 fgets(s, 256, stdin);
 printf(s);
 return 0;
}

V čem je problém ? V ničem, stačí na výzvu zadat třeba "%c%c%c%c%c" v rozsahu a délce, která nás zajímá (tady max 128x) a printf si údaje prostě "odněkud" vezme -chtělo by to delší pole, co ? ;-)