Koło informatyczne - podstawowe operacje na tekstach

Czym są stałe łańcuchowe

Stała łańcuchowa jest ciągiem znaków umieszczonym w cudzysłowach. Np:

 

"Ruch uliczny"

 

Na poprzednich zajęciach używaliśmy stałych łańcuchowych do inicjalizacji tablic. Musisz tutaj dokładnie zrozumieć różnicę pomiędzy inicjalizacją tablicy, a operatorem przypisania. Inicjalizacja określa zawartość tablicy w momencie jej tworzenia. Dla tablic znakowych stosujemy następującą konstrukcję:

 

char nazwa_tablicy[] = "dowolny tekst";

 

Zwróć uwagę, że nie określamy liczby elementów tablicy. Kompilator sam to zrobi na podstawie dostarczonego tekstu. Rozmiar tablicy będzie taki, aby pomieścić wszystkie znaki tekstu oraz kończący je znak NUL.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_021
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  char s[] = "Ruch uliczny";  // inicjalizacja tablicy

  cout << s << endl;          // użycie tablicy 

  return 0;
}

 

Tablicę można również inicjować ciągiem pojedynczych znaków:

 

char nazwa_tablicy[] = {znak,znak,...,0};

 

W tym przypadku samemu należy zatroszczyć się o umieszczenie na końcu znaku NUL, jeśli tablica ma być w programie traktowana jako cstring.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_022
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  char s[] = {'A','B','C',0}; // inicjalizacja tablicy

  cout << s << endl;          // użycie tablicy 

  return 0;
}

 

W powyższym programie usuń z definicji końcowe 0 i sprawdź, co się wtedy stanie po uruchomieniu. Wytłumacz takie zachowanie programu.

 

Tak możemy inicjować tablicę. Lecz w C++ niedopuszczalna jest konstrukcja:

 

tablica_znakowa = "tekst";

 

Sprawdź, czy uda ci się uruchomić poniższy program:

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_023
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  char s[100];         // tworzenie tablicy 100 znaków

  s = "Ruch uliczny";  // ???

  cout << s << endl;   // użycie tablicy

  return 0;
}

 

Dlaczego powstaje błąd w wierszu:

 

s = "Ruch uliczny";

 

Błąd powstaje dlatego, iż C++ nie wie, w jaki sposób ma wykonać tę operację. Mamy tutaj do czynienia z operacją złożoną – w tablicy s należy umieścić znaki tworzące tekst. To wymaga pętli oraz sprawdzania różnych warunków:

Dlatego C++ pozostawia programiście decyzję, jak tę operację należy przeprowadzić (w dalszej części kursu poznamy klasę string, która zawiera odpowiednie metody do wykonywania tego typu działań, na razie w celach dydaktycznych pozostaniemy przez pewien czas przy tablicach znakowych, aby poznać ich zalety i wady).

Stała tekstowa "Ruch uliczny" w programie jest traktowana jako adres, wskaźnik stały. Tekst stałej jest umieszczany w pamięci programu, a wartością stałej staje się adres tego obszaru – bardziej konkretnie jest to wskaźnik stały do danych typu char (czyli typ wskaźnikowy char *). Aby się o tym przekonać, wystarczy ten adres zrzutować na adres do danych typu void i przesłać go w tej postaci do strumienia cout. Teraz strumień cout nie zinterpretuje tego adresu jako cstring, lecz jako zwykły wskaźnik i wypisze szesnastkowo jego wartość.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_024
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  cout << (void *) "Ruch uliczny" << endl;

  return 0;
}

 

Teraz rozumiesz, że komputer nie wie, jak ma przypisać tablicy wskaźnik do danych typu char. Co innego, jeśli zamiast tablicy s użyjemy wskaźnika p:

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_025
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  char * p;           // wskaźnik do danych char

  p = "Ruch uliczny"; // w p umieszczamy adres tekstu

  cout << p << endl;

  return 0;
}

 

Możesz się początkowo dziwić, że wskaźnik p i tablica znakowa s są traktowane przez strumień cout w identyczny sposób. Nie ma w tym żadnej tajemnicy. Wskaźniki i tablice są w C++ równoważne. Czym właściwie jest tablica? Obszarem pamięci, który przechowuje jej elementy. Nazwa tablicy odwołuje się do adresu tego obszaru, czyli tablica jest wskaźnikiem do typu danych, który posiadają jej elementy. Różnica jest tylko taka, iż adres tablicy jest stały, nie można go zmienić. Natomiast zmienna typu wskaźnik może przyjmować różne adresy. Poniższy przykład pokazuje równoważność wskaźnika i tablicy:

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_026
//-----------------------------------

#include <iostream>

using namespace std;

int main()
{
  char s[] = "Ruch uliczny"; // tablica
  char * p;                  // wskaźnik
  int i;

  p = s;             // p wskazuje elementy tablicy s

  for(i = 0; s[i]; i++)
    cout << p[i]     // wskaźnik jako tablica
         << " = "
         << *(s + i) // tablica jako wskaźnik
         << endl;

  return 0;
}

 

Podstawowe operacje na łańcuchach znakowych

Przy przypisaniu adresu wskaźnikowi nie ma miejsca przesyłanie danych ze wskazywanego przez ten adres obszaru. Taką operację C++ może zawsze bezpiecznie wykonać. Co jednak zrobić, jeśli faktycznie chcemy umieścić w tablicy zadany tekst (stałą tekstową lub zawartość innej tablicy). Rozwiązaniem jest funkcja StrCpy() kopiująca tekst z jednego obszaru pamięci do drugiego.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_027
//-----------------------------------

#include <iostream>

using namespace std;

// Funkcja kopiuje tekst z obszaru s do d
//----------------------------------------
char * StrCpy(char * d, const char * s)
{
  char * p = d;          // ustawiamy p na początek obszaru d
  while((*p++ = *s++));  // kopiujemy znaki z s do d, aż do napotkania NUL
  return d;              // zwracamy adres d
}

int main()
{
  char s[100],t[100];
  char * p;

  StrCpy(s,"Ruch uliczny");
  p = StrCpy(t,s);

  cout << "s = " << s << endl
       << "t = " << t << endl
       << "p = " << p << endl;

  return 0;
}

 

Często musimy znać liczbę znaków zawartych w łańcuchu. Operację tę wykonuje funkcja StrLen():

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_028
//-----------------------------------

#include <iostream>

using namespace std;

// Funkcja oblicza liczbę znaków w tekście
//----------------------------------------
int StrLen(const char * s)
{
  int n = 0;         // zerujemy licznik znaków
  while(*s++) n++;   // zliczamy znaki w s
  return n;          // zwracamy licznik
}

int main()
{
  char s[100];

  cout << "Wpisz wiersz : ";

  cin.getline(s,100);

  cout << "Liczba znak\242w w tek\230cie jest r\242wna "
       << StrLen(s) << endl;

  return 0;
}

 

Kolejną pożyteczną funkcją jest łączenie tekstów, czyli konkatenacja. Polega to na tym, iż na koniec tekstu docelowego d jest dołączany tekst źródłowy s. Oba parametry są wskaźnikami do char. Obszar wskazywany przez d powinien być wystarczająco duży, aby pomieścić oba teksty po ich połączeniu.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_029
//-----------------------------------

#include <iostream>

using namespace std;

// Funkcja dołącza do d tekst s. Zwraca d
//---------------------------------------
char * StrCat(char * d, const char * s)
{
  char * p = d;          // p ustawiamy na początek obszaru d
  while(*p) p++;         // szukamy końca łańcucha w d
  while((*p++ = *s++));  // kopiujemy znaki z s na koniec tekstu w d
  return d;              // zwracamy adres początku obszaru d
}

int main()
{
  char s[100];
  char t[] = " A nam nic do tego!";

  s[0] = 0;   // teraz s zawiera pusty łańcuch

  StrCat(s,"Ala ma "); // do s wstawiamy tekst
  StrCat(s,"\276\242\210wika Bzika."); // dodajemy tekst
  StrCat(s,t); // dodajemy tekst z t

  cout << s << endl;

  return 0;
}

 

W ten sposób można sobie napisać wszystkie potrzebne funkcje. Jednakże programiści C++ (a właściwie C) zrobili to już dawno temu i definicje funkcji dla łańcuchów cstring umieścili w pliku nagłówkowym cstring. Trzy napisane przez nas funkcje: StrCpy(), StrLen() i StrCat() są tam obecne w prawie identycznej postaci (nazwy funkcji bibliotecznych pisane są małymi literami). Z funkcji bibliotecznych opłaca się korzystać, ponieważ zostały one dokładnie przetestowane i są stabilne w działaniu. Poniżej umieszczamy opis najczęściej używanych funkcji bibliotecznych wraz z krótkimi programami. Opisy reszty funkcji z łatwością znajdziesz w sieci Internet.

 

char * strcpy(char * d, const char * s);

Funkcja kopiuje łańcuch s do łańcucha d. Jako wynik zwraca d.
d  –  wskaźnik obszaru przeznaczenia, gdzie zostanie skopiowany łańcuch wskazywany przez s. Obszar wskazywany przez d powinien być wystarczająco duży na pomieszczenie znaków z obszaru s wraz z końcowym znakiem NUL. Obszary d i s nie powinny się pokrywać w pamięci.
s  –  wskaźnik obszaru źródłowego, z którego będzie pobierany łańcuch cstring.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_030
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[100],t[100];

  strcpy(s,"Pierwszy tekst");

  cout << "s = " << s << endl;

  strcpy(s,"Drugi tekst");

  cout << "s = " << s << endl;

  strcpy(t,s);

  cout << "t = " << t << endl;

  return 0;
}

 

int strlen (const char * s);

Funkcja zwraca liczbę znaków zawartych w łańcuchu s.
s  –  wskaźnik obszaru przechowującego łańcuch cstring.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_031
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char n[100];

  cout << "Wpisz swoje imi\251 : ";
  cin >> n;

  cout << "Liczba znak\242w twojego imienia = "
       << strlen(n) << endl;

  return 0;
}

 

char * strcat(char * d, const char * s);

Funkcja dopisuje na końcu łańcucha d łańcuch s. Obszar wskazywany przez d musi być odpowiednio duży, aby pomieścił połączone łańcuchy. Obszary d i s nie powinny się pokrywać. Funkcja zwraca d.
d  –  wskaźnik obszaru przechowującego łańcuch cstring, do którego zostanie dołączony łańcuch wskazywany przez s.
s  –  wskaźnik obszaru przechowującego dołączany łańcuch.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_032
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[100];
  char s1[] = "Kocie ";
  char s2[] = "m\210ocie, ";
  char s3[] = "co siedzisz ";
  char s4[] = "na p\210ocie.";

  strcpy(s,s1);
  strcat(s,s2);
  strcat(s,s3);
  cout << strcat(s,s4) << endl;

  return 0;
}

 

const char * strchr( const char * s, int c);
      char * strchr(       char * s, int c);
Funkcja zwraca adres pierwszego wystąpienia znaku c w łańcuchu s. Jeśli znak c nie występuje w łańcuchu s, to zwracane jest NULL.
s  –  wskaźnik obszaru przechowującego łańcuch cstring, który zostanie przeszukany.
c  –  kod znaku, który będzie wyszukiwany w łańcuchu s.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_033
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[255];
  char * p;
  int a = 0;  // licznik literek a

  cout << "Wpisz wiersz : ";
  cin.getline(s,255);

  p = s;      // p wskazuje początek łańcucha s

  while((p = strchr(p,'a'))) // szukamy litery a
  {
    a++;      // zwiększamy licznik
    p++;      // wskaźnik przesuwamy na następny znak w s
  }

  cout << "Liczba literek a w wierszu = " << a << endl;

  return 0;
}

 

const char * strstr(const char * s1, const char * s2);
      char * strstr(      char * s1, const char * s2);

Funkcja zwraca adres pierwszego wystąpienia łańcucha s2 w łańcuchu s1. Jeśli łańcuch s2 nie występuje w łańcuchu s1, to zwracane jest NULL.
s1  –  adres łańcucha do przeszukania.
s2  –  adres łańcucha, który będzie poszukiwany w s1.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_034
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[255];
  char * p;

  cin.getline(s,255);

  p = s;

  while((p = strstr(p,"al")))
  {
    *p++ = 'A';
    *p++ = 'L';
  }

  cout << s << endl;

  return 0;
}

 

void * memset(void * d, int v, int n);

Ta funkcja nie odnosi się do łańcuchów cstring, lecz do obszarów pamięci komputera. Wypełnia ona w obszarze o adresie d dokładnie n bajtów pamięci wartością v. Obszar d powinien być wystarczająco duży, aby pomieścić te n bajtów.
d  –  adres wypełnianego obszaru.
v  –  dane do wypełnienia, traktowane jako char.
n  –  liczba bajtów do wypełnienia w obszarze d.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_035
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[1000];

  memset(s,'A',999);

  s[999] = 0;

  cout << s << endl;

  return 0;
}

 

void * memcpy(void * d, const void * s, int n);

Ta funkcja nie odnosi się do łańcuchów cstring, lecz do obszarów pamięci komputera. Kopiuje ona z obszaru wskazywanego przez s do obszaru wskazywanego przez d dokładnie n bajtów. Obszar d powinien być wystarczająco duży na pomieszczenie przesłanego bloku pamięci. Obszary d i s nie powinny się pokrywać.
d  –  adres obszaru, do którego będą skopiowane dane.
s  –  adres obszaru, z którego będą kopiowane dane.
n  –  liczba bajtów do skopiowania

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_036
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[100];
  char t[] = "OWA";
  char * p = s;

  cin.getline(s,100);

  while((p = strstr(s,"owa")))
  {
    memcpy(p,t,3);
    p += 3;
  }

  cout << s << endl;

  return 0;
}

 

void * memmove(void * d, const void * s, int n);

Funkcja kopiuje blok pamięci o rozmiarze n bajtów z obszaru wskazywanego przez s do obszaru wskazywanego przez d. Obszar d powinien być wystarczająco duży, aby pomieścić n bajtów. Obszary d i s mogą się częściowo pokrywać, co nie jest dozwolone przy strcpy() i memcpy().
d  –  adres obszaru, do którego będą skopiowane dane.
s  –  adres obszaru, z którego będą kopiowane dane.
n  –  liczba bajtów do skopiowania

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Łańcuchy znakowe
// PRG_037
//-----------------------------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  char s[100];

  strcpy(s,"abra cadabra ");

  memmove(s + 5,s,strlen(s)+1);

  cout << s << endl;

  return 0;
}

 

Zadanie

Wykorzystując podane funkcje, zaprojektuj nową funkcję strreplace(s1,s2,s3), która wyszuka w łańcuchu s1 wszystkie wystąpienia s2 i zastąpi je łańcuchem s3. Załóż, że obszar s1 jest wystarczająco duży, aby pomieścić zmodyfikowany łańcuch s1.

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.