Programowanie w C Typ wskaźnikowy do typu znakowego i operacje na łańcuchach Artur Opaliński (pokój E112) e-mail: (p. wykład administracyjny) URL: (p. wykład administracyjny)
Treść wykładu Podstawowe funkcje i operacje na łańcuchach Kopiowanie, Sklejanie Wskaźnik do typu znakowego Zaawansowane operacje na łańcuchach Cięcie Przeszukiwanie Konwersje 2
Wskaźnik do typu znakowego Deklaracja: char *wskaznik1; char* wskaznik2; //najbezpieczniej //uwypukla typ char*wskaznik3; //bez zalet? :-) Uwaga na: char* zmienna1,zmienna2; //zmienna2 jest typu char! Typ zgodny z typem łańcuchowym Może służyć do wskazania na zmienną łańcuchową, lub jej część Stąd nazwa wskaźnika do typu znakowego, gdyż wskazuje wówczas konkretny znak w łańcuchu Zaś pozostałe znaki łańcucha można znaleźć tuż za nim Jeden wskaźnik do typu znakowego może w jednej chwili wskazywać na tylko jeden znak w łańcuchu 3
Wskaźnik do typu znakowego char *wskaznik; char napis[ ]="W stepie szerokim"; Zmienna napis: W s t e p i e s z e r o k i m \0 Zmienna wskaznik: wskaznik nawet nie ma kształtu jak char ;-) 4
Wskaźnik do typu znakowego Wskaźnik do typu znakowego różni się od typu łańcuchowego Wskaźnik do typu znakowego nie zawiera łańcucha znaków Nie rezerwuje na to miejsca w pamięci Może tylko wskazać na istniejący już łańcuch (odwoływać się) Zmienna typu łańcuchowego oczywiście może zawierać łańcuch znaków Rezerwuje na łańcuch miejsce w pamięci Nazwa zmiennej łańcuchowej pozwala się do tego zarezerwowanego miejsca odwoływać Konsekwencja: char *wskaznik; char napis[ ]="W stepie szerokim"; wskaznik=napis; // poprawne, wskaznik wskaze na napis 5 // napis=wskaznik; niepoprawne, napis może wskazywać
Wskaźnik do typu znakowego Inicjalizacja Zmienna wskaźnikowa może być zainicjalizowana i wskazywać znaki: zawarte w zamiennej łańcuchowej: char napis[ ] ="ktorego okiem nawet nie zmierzysz sokolim"; wskaznik=napis; zawarte w stałej łańcuchowej: wskaznik="stan, unies glowe!"; Inicjalizacja podczas deklaracji: char *wskaznik1="stan, unies glowe!"; char napis[12]; char *wskaznik2=napis; pusty // wskazuje na napis, chwilowo 6
Wskaźnik do typu znakowego Podstawowe operacje Wskaźnik może być zainicjalizowany, a mimo to na nic nie wskazywać char *wskaznik=null; Na wskaźnikach można prowadzić operacje arytmetyczne (choć operacje te mają nieco odmienne reguły): char napis[20]="stan, unies glowe!"; char *wskaznik; int dlugoscnapisu; wskaznik=napis; // wskaźnik pokazuje na "Stan, unies glowe!" wskaznik++; // wskaźnik pokazuje na "tan, unies glowe!" wskaźnik=wskaźnik+16; // wskaźnik pokazuje na "!" dlugoscnapisu=wskaznik napis; printf("dlugosc napisu=%d\n", dlugoscnapisu); 7
Wskaźnik do typu znakowego Podobieństwa do tablicy znakowej char napis[] ="Burzac pomniki, oszczedzajcie cokoly. Zawsze moga sie przydac. [S.J.Lec]"; int a; // bardziej elegancko: size_t a; a=strlen("burzac "); // na koncu jest spacja; razem 7 znakow // obecna zawartosc 'napis': puts(napis); printf("%s", napis+a); // co znaczy "+a"? puts(napis+a); // co znaczy "+a"? // jaka czesc napisu zmieniamy? fgets(napis+a, sizeof(napis) - a, stdin); // co znaczy "+a", "-a"? // nowa zawartosc 'napis': puts(napis); printf("%s", napis+a); puts(napis+a); // czym sie roznia: printf("%s", napis+a ); printf("%c", napis[a] ); 8
Wskaźnik do typu znakowego DEMO: debugger + wskaznik.c #include <stdio.h> #include <string.h> main() { char napis[ ]="W stepie szerokim"; // 17 znakow +1 char *wskaznik; puts(napis); //Pod wieloma względami char* jest jak int: wskaznik=null; wskaznik++; printf("wskaznik=%d (jako int)\n", wskaznik); printf("wskaznik=%p (jako wskaznik w hex)\n", wskaznik); 9
Wskaźnik do typu znakowego DEMO: debugger + wskaznik.c (c.d.) wskaznik=napis; // wartosc a wskazywane dane printf("wskazywany tekst=>%s<\n",wskaznik); strcpy(napis,"stan, unies glowe"); printf("wskazywany tekst='%s'\n",wskaznik); /* wskaznik sie NIE zmienil, zmienil sie wskazywany tekst! */ wskaznik=wskaznik+6; // teraz WSKAZNIK sie zmienil puts(wskaznik); // co bedzie? strcpy(wskaznik, "i poloz sie"); //co bedzie? puts(napis); // ryzykowne: zmienic napis[], bez odwolania do niego wskaznik="ktorego okiem nawet sokolim nie zmierzysz"; //gdzie to sie miesci (to 41 znakow+1)? */ puts(wskaznik); 10 puts(napis); }
Operacje na łańcuchach Wiele operacji na łańcuchach prowadzi się z użyciem funkcji zdefiniowanych w pliku nagłówkowym string.h Funkcje te często posługują się wskaźnikiem do typu znakowego Znane: strcpy(), strcat(), strlen() 11
Operacje na łańcuchach Cięcie łańcuchów Kopiowanie ograniczonej ilości znaków: strncpy() char *strncpy(char *restrict s1, const char *restrict s2, size_t n) Jak wyciąć i skopiować tylko pierwsze 5 znaków łańcucha? char zrodlo[20]; char cel[6]; // 5+1=6 strncpy(cel, zrodlo, 5); Jak wyciąć i skopiować tylko ostatnich 5 znaków łańcucha? char *wskaznik; wskaznik=zrodlo+strlen(zrodlo)-5; strcpy(cel,wskaznik); strncpy() wymaga kontroli, czy nie nastąpi nadpisanie 12 pamięci
Operacje na łańcuchach Cięcie łańcuchów Doklejanie ograniczonej ilości znaków: strncat() char *strncat(char *restrict s1, const char *restrict s2, size_t n) Jak wyciąć i dokleić tylko fragment łańcucha? char pelnasciezkapliku[100]; // np.: "C:\Moje Dokumenty\pismo.doc" char komunikat[100]; strcpy(komunikat, "Sciezka wskazuje na dysk "); strncat(komunikat, pelnasciezkapliku, 2); // np. dokleja tylko "C:" 13
Operacje na łańcuchach Przeszukiwanie Szukanie znaku w łańcuchu: strchr() char *strchr(const char *s, int c) Jak znaleźć początek słowa w zdaniu? char zdanie[ ]="Nulla dies sine linea."; char *pozycjaspacji; pozycjaspacji=strchr(zdanie,' '); pozycjaspacji++; // przesuniecie na pierwszy znak za spacja1 // mamy już początek drugiego słowa pozycjaspacji=strchr(pozycjaspacji,' '); //szukamy już tylko za spacja1 //mamy drugą spację w zdaniu zapewne początek trzeciego słowa N u l l a d i e s s i n e l i n e a \0 pozycjaspacji 1 2 3 14
Operacje na łańcuchach Przeszukiwanie Szukanie (pod)łańcucha w łańcuchu: strstr() char *strstr(const char *s1, const char *s2) Jak znaleźć słowo sine w zdaniu? char zdanie[ ]="Nulla dies sine linea."; char slowo[ ]="sine"; char *pozycjaslowa; pozycjaslowa = strstr(zdanie, slowo); if ( pozycjaslowa == NULL ) printf("nie znaleziono slowa %s w zdaniu %s\n", slowo, zdanie); N u l l a d i e s s i n e l i n e a \0 pozycjaslowa 1 15
Operacje na łańcuchach Przeszukiwanie strchr() oraz strstr() zwracają NULL, jeżeli nie znalazły szukanego elementu NULL - specjalna, zarezerwowana do tego celu stała W wielu kompilatorach równa 0 (pośrednio wynika z zaleceń standardu), ale nie należy na tym polegać Stąd po wywołaniu strchr() lub strstr() należy pamiętać o sprawdzeniu, czy nie zwróciły NULL, np: if(strchr(napis, znak) == NULL) printf( Nie znaleziono znaku %c\n, znak); pozycja = strstr(napis1, napis2); if(pozycja == NULL) printf( Nie znaleziono lancucha %s\n, napis2); 16
Podobieństwa i różnice char *i char[ ] char napis1[] ="Burzac pomniki, oszczedzajcie cokoly. Zawsze moga sie przydac. [S.J.Lec]"; char napis2[]="sily"; char *w; w=strstr(napis1, "cokoly"); // wskaznik do slowa printf("szukane slowo rozpoczyna sie pod indeksem:", w-napis1); strcpy(napis1 + w, napis2); strcpy(w, napis2); // niepoprawne. Dlaczego? // co się wydarzy? (c.d.n.) 17
Podobieństwa i różnice char *i char[ ] (c.d.) char *napis3="strzez sie Idow Marcowych" ; // Cezar char *napis4="tego, kto czytal jedna ksiazke"; // Casanova w=strstr(napis3, "Idow"); strcpy(w, napis4); // niepoprawne. Dlaczego? napis3=napis1; w=strstr(napis3, "oszczedzajcie"); strcpy(w, napis4); // ktora zmienna zmieni wartosc? if(sizeof(napis1) < strlen(napis3)) //czy jest miejsce? strcpy(napis1, napis3); strcpy(w, napis1); // niepoprawne; w juz wyznaczylismy w=strstr(napis1, "Idow"); strcpy(w, napis4); // jaka bedzie wartoec lancucha? 18
Operacje na łańcuchach Porównywanie Porównanie dwóch łańcuchów: strcmp() int strcmp(const char *s1, const char *s2) Równość łańcuchów oznacza identyczność wszystkich znaków na tych samych pozycjach, do '\0' włącznie Równość łańcuchów sygnalizowana jest zwróceniem wartości 0 (zero) W przypadku nierówności łańcuchów, zwracana jest różnica między wartością dwóch pierwszych różniących się bajtów s1 i s2 if(strcmp("jablko", "Pomarancza") == 0) puts("lanuchy sa takie same"); else puts("lanuchy sa rozne"); 19
Operacje na łańcuchach Kontrola zbioru znaków Długość początkowej części łańcucha, która składa się wyłącznie z podanych znaków: strspn() size_t strspn(const char *s1, const char *s2) liczbawiodacychbialychznakow = strspn(napis,"\n\t "); lub: liczbawiodacychznakow = strspn(napis,"0123456789+-" ); Długość początkowej części łańcucha, która nie zawiera ani jednego z podanych znaków: strcspn() size_t strcspn(const char *s1, const char *s2) liczbaniecyfrwliczbie = strcspn(napis, +-,. ); Funkcje przydatne do kontroli danych wejściowych 20
Operacje na łańcuchach I jeszcze problemy ze scanf() Poprawnie napisany kod powinien działać niezależnie od danych wejściowych Dwukrotnie wywołany scanf( %f,...) tak nie działa (przy błędnych danych): float a,b; printf("podaj dwe liczby rzeczywiste: "); scanf("%f", &a); // podaj 14,5 zamiast 14.5 scanf("%f", &b); //podaj dowolna liczbe printf("pobralem: %f i %f\n", a,b); scanf() jest bardzo uproszczoną funkcją, przeznaczoną tylko do: wczesnych etapów nauki programowania drobnych, własnych testów programisty Nie powinna występować w kodzie prawdziwej aplikacji 21
Operacje na łańcuchach Rozbicie łańcucha na elementy Rozwiązanie niektórych problemów ze scanf(): sscanf() sscanf() działa jak scanf(), ale pobiera dane nie z stdin, lecz z łańcucha podanego jako pierwszy argument: char przedsionek[10]; /*co najmniej rozmiar przewidywany na poprawne dane */ fgets(przedsionek, sizeof(przedsionek), stdin); // tu ew. przejrzenie/wyczyszczenie bufora sscanf(przedsionek, %f, &liczbarzeczywista); W ten sposób bufor wejściowy niezależnie od dopasowania do formatu pobieranej liczby zostanie skonsumowany zawsze warto go wyczyścić bo możliwe są też inne błędy Analogicznie można sscanf(przedsionek, %d, &liczbaint); 22
Operacje na łańcuchach Konwersje Zamiana łańcucha znaków napis zawierającego liczbę w postaci dziesiętnej na liczbę typu long int do zmiennej dlugiint (wymaga stdlib.h), np: dlugiint=strtol(napis, NULL, 10); Próba zamiany łańcucha znaków napis na liczbę typu float do zmiennej rzeczywista (wymaga stdlib.h), np: rzeczywista=strtof(napis, NULL); Próba zamiany łańcucha znaków napis na liczbę typu double do zmiennej podwojnarzeczywista (stdlib.h),np: podwojnarzeczywista=strtod(napis, NULL); 23
Operacje na łańcuchach Konwersje (c.d.) Zamiana liczby (inf, float) na łańcuch znaków: float liczbarzeczywista =12.5; int liczbacalkowita = -4; char napis[100]; sprintf(napis,"%f", liczbarzeczywista); //napis zawiera znaki: 12.5000 sprintf(napis,"%d", liczbacalkowita); //napis zawiera dwa znaki: -4 Połączenie liczby ze stałą tekstową: char napis[100]; int wiek; wiek = 5; sprintf(napis, "Mam %d lat", wiek); //napis zawiera: "Mam 5 lat" Połączenie liczy ze zmiennymi łańcuchowymi: char napis[100]; char marka[]="bmw"; float pojemnosc=5.5; char model[]="turbo"; 24 sprintf(napis, "%s %.1f %s", marka, pojemnosc, model); /*napis zawiera: "BMW 5.5 turbo" */
Co trzeba umieć? Rozumieć pojęcie wskaźnika do typu znakowego Manipulować łańcuchami Z użyciem wskaźników do typu znakowego Z użyciem podanych funkcji ze string.h Cięcie, Przeszukiwanie Konwersje Uwaga na ilość i kolejność parametrów funkcji 25