mgr inż. Wacław Libront * Bobowa 2019

ZSO Bobowa, ul. Długoszowskich 1, 38-350 Bobowa, tel: 0183514009, fax: 0183530221, email: sekretariat@zsobobowa.eu, www: zsobobowa.eu

Lekcja 7

Teksty w C++

  1. char
  2. konwersja
  3. string
  4. ćwiczenia
  5. konwersja
  6. dwójkowe-dziesiętne
  7. getline
  8. zadania
  9. rozwiązania

Zmienne znakowe – char
Przykłady w ramce opisują deklarowanie i inicjowanie zmiennej znakowej, operacje dodawania liczby do zmiennej znakowej, różnicę pomiędzy znakiem ‘0’ liczbą 0 oraz konwersję znaku na liczbę i liczby na znak, kody ASCII i tzw. rzutowanie.

char znak;
znak='A';
cout << znak;	//A
//konwersja – znak-liczba
cout << znak << int(znak) << endl;//A 65
cout << znak << (int) znak << endl;//A 65
cout << znak << char(65) << endl;//A A
//znaki jak liczby!!!
cout << znak+0 << znak+1 << endl;//65 66
cout << '1'+'1' << 1+1 << endl; //98 2

Konwersja
W języku C++ instrukcja cout << ‘1’+’1’ wyświetli na ekranie liczbę 98 (bo kod ASCII dla znaku ‘1’ to 49). Wszystkie normalne języki programowania "widząc" znak traktują go jak znak i można go co najwyżej skleić z innym znakiem. Sam sposób zapisu konwersji znak-liczba też można w C++ "udziwnić". Typowy sposób to np. char(65). W C++ można dodatkowo (char)65, albo też (char)(65).


Zadanie – Tablica ASCII
Wyświetl na ekranie tablicę kodów ASCII. Uporządkuj ją w 8 kolumnach: znak-kod.

for (int i=33; i<256; i++){
  cout.width(4);
  cout  << (char) (i);
  cout.width(4);
  cout << i;
  if (i % 8 == 0) cout << endl;
}

Znaki w kodzie ASCII, które są widoczne na ekranie konsoli mają numery w zakresie 32-255. Na przykład 32 to spacja, '1' znak jedynki to 48, 'A' duże A to 65, 'a' małe a 97. Spośród znaków niedrukowalnych: 13 - ENTER, 9-BACKSPACE, 27-ESC. Ta znajomość okazuje się bardzo przydatna na przykład podczas tworzenia konsolowych gier.


Zmienne napisowe STRING
W standardowym C nie było mowy o typowych zmiennych tekstowych (string), jak w innych językach programowania. Twórca – dążąc do minimalizacji wymyślił łańcuchy znakowe (tablice), co w praktyce spowodowało mnóstwo problemów. Co więcej, niektóre wersje kompilatorów (np. Dev) potrafią operować na napisach bez dodatkowych bibliotek, ale już MSVC++ musi mieć zadeklarowaną bibliotekę <string>

Uwaga. Pierwsza litera w napisie ma w tablicy znaków numer zero!

LENGTH - długość napisu

APPEND - doklejanie na końcu

INSERT - wstawianie

ERASE - usuwanie

AT lub [] – znak – znak

FIND - wyszukiwanie

SUBSTR - wycinanie

string nap="Wacław Libront";
cout << nap << endl;
//długość napisu - LENGTH
int dlug=nap.length();
//doklejanie za pomocą operatora +
nap="mgr inż "+nap;
cout << nap << endl;
//doklejanie na końcu - APPEND
nap=nap.append(" Bobowa");
cout << nap << endl; 
//wstawianie do napis - INSERT
nap=nap.insert(15,"Stanisław ");
cout << nap << endl; 
//usuwanie fragmentu napisu - ERASE
nap=nap.erase(15,10);
cout << nap << endl; 
//wybieranie znaku z napisu - AT
char znak=nap.at(6);
cout << znak << endl;
//wybieranie znaku z napisu - tablica
znak=nap[6];
cout << znak << endl; 
//wyszukiwanie znaków i napisów - FIND
int spacja=nap.find(" ");
cout << spacja << endl;
//wyszukanie kolejnej spacji w tekście
cout << nap.find(" ",spacja+1) << endl;
//wycinanie fragmentów - SUBSTR
string frag=nap.substr(spacja+1,3);
cout << frag << endl;

Ćwiczenia z napisami
Wpisz z klawiatury (lub zadeklaruj w zmiennej) napis, w którym będzie twoje Nazwisko i Imię.

  1. wyodrębnij nazwisko i imię, do dwóch zmiennych

  2. stwórz anagramy nazwiska i imienia

  3. zamieniamy miejscami nazwisko z imieniem

  4. zamień miejscami pierwsze litery - zlepek Nmię Iazwisko

  5. wyświetl inicjały NI

string NI="Libront Wacław";
//długość i spacja
int DLUG=NI.length();
int SPAC=NI.find(" ");
//wyodrębnienie wyrazów
string IMIE=NI.substr(0,SPAC);
string NAZW=NI.substr(SPAC+1,DLUG-SPAC);
cout << IMIE << " " << NAZW << endl;
//pierwsze litery - inicjały
char INII=IMIE[0];
char ININ=NAZW[0];
cout << INII << " " << ININ << endl;
// zamieniamy literki 
cout << IMIE[0] << NAZW.substr(1,NAZW.length());
cout << " ";
cout << NAZW[0] << IMIE.substr(1,IMIE.length());
cout << endl;
//zamieniamy na małe <> duże
for (int i=0; i < DLUG; i++)
  if (NI[i] >= 65 && NI[i] <= 90) 
    cout << char(NI[i]+32);
  else 
    if (NI[i] >= 97 && NI[i] <= 122) 
      cout << char(NI[i]-32);
    else cout << NI[i];
cout << endl;
//anagramy
for (int i=NAZW.length()-1; i >=0 ; i--) 
  cout << NAZW[i];
for (int i=IMIE.length()-1; i >= 0; i--) 
  cout << IMIE[i];
cout << endl;

Konwersja tekst-liczba
Dowolny napis przedstawiający liczbę przekonwertujemy na liczbę za pomocą funkcji atoi (na całkowitą) lub atof (rzeczywistą).

cout << atoi("213")+1 << endl;
cout << atof("213")+1.1 << endl;

Konwersja liczba tekst
Konwersja w drugą stronę jest w języku C problematyczna i twórcy nie wykazali się pomysłowością. Istnieje mnóstwo sposobów wymyślonych przez niezależnych twórców, ale żaden z nich nie jest doskonały i nie ma jednej wygodnej funkcji.

Zamiana liczby całkowitej na tekst za pomocą funkcji itoa jest jak najbardziej możliwa, ale … twórca wymyślił, że będzie też możliwość zamiany na dowolny system pozycyjny – w naszym przykładzie podstawą systemu jest 10.

int liczba=123;
char L[20];
itoa(liczba,L,10);
cout << strlen(L) << endl;

Nie ma niestety funkcji ftoa i jeśli chcemy zamienić liczbę rzeczywistą na tekst możemy posłużyć się biblioteką <sstream>. Do zmiennej strumieniowej wlewamy liczbę, a następnie zamieniamy ją na stringa.

#include <sstream>
...
stringstream zmienna;
zmienna << 123.31;
string tekst=zmienna.str();
cout << tekst.length() << endl;

Zamiana DWÓJKOWE-DZIESIĘTNE
Wpisz liczbę dwójkową z klawiatury (w postaci tekstu) i zamień na jej dziesiętny odpowiednik. Napisz odpowiednią funkcję zamieniającą dwójowy zapis na liczbę dziesiętną.

Jak zamienić liczbę dwójkową na dziesiątkową? Z matematycznego punktu wiedzenia nie jest to trudne – wystarczy zsumować tzw. wagi cyfr w liczbie dwójkowej. Co to oznacza? Każda cyfra w liczbie jest numerowana – najmniej znacząca (pierwsza z prawej) jest na pozycji zerowej, kolejna na pozycji pierwszej, itd. Wystarczy liczbę 2 (podstawę systemu dwójkowego) podnieść do potęgi, która jest cyfrą na tej pozycji . Te wszystkie potęgi (tzw. wagi bitów) należy zsumować – i to jest będzie liczba w systemie dziesiątkowym.

Funkcja DW_DZ() wykonuje te wszystkie operacje, w jak najbardziej „edukacyjny” sposób (istnieje wiele innych algorytmów). Podczas wyodrębniania poszczególnych bitów z napisu będziemy odwoływać się do tablicy napisu, pamiętając o tym, że bity musimy pobierać od tyłu!

int DW_DZ(string dw){
  const int POD=2;
  char bit;
  int waga;
  int poz=0;
  int dz=0;
  int dlug=dw.length();
  do {
    bit=dw[dlug-poz-1];
    waga=pow(POD,poz);
    if (bit != '0') 
      dz=dz+waga;
    poz++;
  } while (poz < dlug);
  return dz;
}

Zamiana DZIESIĘTNE- DWÓJKOWE
Wpisz z klawiatury liczbę dziesiętną. Na ekranie wypisz jej dwójkowy odpowiednik. Napisz funkcję zamieniającą liczby dziesiętne na dwójkowe, której wynikiem jest tekst zero-jedynkowy.

Dzielimy całkowicie liczbę przez dwa. Reszty z dzielenia (zera lub jedynki) sklejamy w liczbę dwójkową – kolejne bity doklejamy zawsze na początek! Wynik z dzielenia całkowitego przez dwa staje się na końcu naszą liczbą podstawową i całą operację powtarzamy, dopóki można wykonywać dzielenie.

UWAGA. Należy rozróżnić zmienną napisową dw, która na początki jest tzw. „łańcuchem pustym ”” od znaków ‘0’ i ‘1’w apostrofach, które są doklejane do łańcucha dw za pomocą zwykłej operacji dodawania.

string DZ_DW_(int dz){
  string dw="";
  int bit;
  do {
    bit=dz % 2;
    if (bit == 1) dw='1'+dw;
	else dw='0'+dw;
    dz=dz/2;
  } while (dz > 0);
  return dw;
}

Wczytywanie tekstów z klawiatury - getline
Do tej pory wczytywaliśmy z klawiatury (za pomocą polecenia cin) do zmiennych pojedyncze liczby lub teksty. Kłopoty pojawiają się ponieważ cin nie reaguje na spacje i entery. Wystarczy uruchomić sprawdzić ciąg instrukcji z ramki i wpisać z klawiatury wielowyrazowy napis, np. „Nazwisko Imię” rozdzielone spacją. Po wciśnięciu klawisza ENTER na ekranie wyświetlone zostanie tylko „Nazwisko”, gdyż cin wczytuje znaki do napotkania spacji! Jak sobie można z tym poradzić? Jednym z rozwiązań jest zastosowanie instrukcji getline(cin, zmienna).

UWAGA. Jeśli będziemy mieszać w programie cin i getline pojawią się kolejne problemy związane z tzw. buforem klawiatury :-)

string N;
cin >> N;
cout << N;

Zadanie – Ulica i numer domu
Wczytuj w pętli do tablicy ulicę wraz z numerem domu, aż do naciśnięcia klawisza ENTER – wczytania pustego tekstu.

Zadanie zrealizowano za pomocą instrukcji getline i pętli while, która kończy swoje działanie, gdy w zmiennej, do której wczytujemy napisy z klawiatury będzie tekst pusty. Na końcu wypisujemy wszystkie wczytane napisu na ekranie.

int ile=0;
string u;
string ulice[100];
do{
  cout << "Ulica: ";
  getline(cin,u);
  if (u != "") ulice[ile]=u;
  ile++;
} while(u != "");
for (int i=0; i < ile; i++) 
  cout << ulice[i] << endl;

Zadanie - Zamiana liczb
Korzystając z powyższych funkcji można napisać identycznie działające, które zamieniają dowolny system pozycyjny na dowolny inny w zakresie 2..16. Problemy mogą pojawiać się podczas podnoszenia do potęgi za pomocą funkcji POW, która ma problemy z liczbami całkowitymi (100^2=99) A skoro mamy do dyspozycji obie funkcje, to wykorzystamy je do napisania funkcji, która zamienia liczbę z dowolnego systemy na dowolny inny w zakresie 2-16.

//zamiana dziesiątkowego na dowolny 2-16
string DZ_XX(int dz, int podstawa){
  string X="0123456789ABCDEF";
  string xx="";
  int bit;
  do {
    bit=dz % podstawa;
    xx=X[bit]+xx;
	dz=dz/podstawa;
  } while (dz>0);
  return xx;
}
//zamiana dowolnego 2-16 na dziesiątkowy
int XX_DZ(string xx, int podstawa){
  string X="0123456789ABCDEF";
  char bit;
  int bitC;//bit zamieniony na cyfrę
  int poz=0;
  int dz=0;
  int dlug=xx.length();
  do {
    bit=xx[dlug-poz-1];
    bitC=X.find(bit);
    double w=pow(podstawa,poz);
    dz=dz+int(w)*bitC;
    poz++;
  } while (poz < dlug);
  return dz;
}
//zamiana z dowolnego na dowolny
string XX_XX(string xx, int p1, int p2){
  return DZ_XX(XX_DZ(xx,p1),p2);
}
...
//zamiana 123 w systemie siódemkowym na system trójkowy
cout << XX_XX("123",7,3) << endl;


Zadania

1) Napisz funkcję ANAGRAM, która zamieni podany jako parametr tekst na jego odwrotność

2) Napisz funkcję, która utworzy tekst złożony z losowego zlepku liter podanych w parametrze

3) Napisz funkcję, która policzy ilość liter w tekście. Jako parametr podajemy tekst i literę

4) Napisz funkcję, która policzy wszystkie litery w tekście – bez spacji

5) Napisz funkcję PALINDROM, która sprawdzi czy wpisany wyraz jest palindromem (czytany od tyłu jest taki sam).
a) Zmodyfikuj funkcję aby sprawdzała całe zdania - usuń najpierw spacje z tekstu!

6) Mini szyfrowanie. Napisz funkcję, która zwróci zaszyfrowany tekst. Zasada szyfrowania: kod ASCII każdego znaku zwiększamy o konkretną liczbę

7) Mini szyfrowanie. Napisz funkcję, która zwróci zaszyfrowany tekst. Zasada szyfrowania: przestawiamy znaki w tekście 1-2 3-4 5-6 itd.

8) Napisz funkcję, która zliczy samogłoski (spółgłoski) w tekście

9) Napisz funkcję, która zliczy wyrazy w tekście. Zliczamy spacje +1

10) Dwie duże liczby znajdują się w łańcuchach tekstowych. Wykonaj ich dodawanie.


Przykładowe rozwiązania zadań
Jedynie ciężka umysłowa praca i zrozumienie działania programu może nauczyć programowania!

//zadanie 1
string ANAGRAM(string n){
	string m="";
	int d=n.length();
	for (int i=0; i < d; i++)
		m=n[i]+m;
	return m;
}
string napis;
cout << "napis: ";
cin >> napis;
cout << ANAGRAM(napis) << endl;
//zadanie 2
//lsosowy zlepek liter z napisu startowego
//ale wybieramy tylko po jednej
// i trzeba pamietac która była albo wycinać
string LOSOWO(string n){
	string m="";
	int d=n.length();
	for (int i=d; i > 0; i--){
		int nr=rand() % i;
		string litera=n.substr(nr,1);
		n=n.erase(nr,1);
		m=m+litera;
	}	
	return m;
}
string napis;
cout << "napis: ";
cin >> napis;
cout << LOSOWO(napis) << endl;
cout << LOSOWO(napis) << endl;
cout << LOSOWO(napis) << endl;
//zadanie 3
int ILE_LITER(string n, char l){
	int d=n.length();
	int ile=0;
	for (int i=0; i < d; i++)
		if (n[i] == l) ile++;
	return ile;
}
string nap;
char znak;
cout << "napis i litera: ";
cin >> nap;
cin >> znak;
cout << ILE_LITER(nap,znak) << endl;
//zadanie 4
//ile liter bez spacji
int ILE_ZNAKOW(string n){
	int d=n.length();
	int ile=0;
	for (int i=0; i < d; i++)
		if (n[i] != ' ') ile++;
	return ile;
}
//wczytujemy getline
string zdanie;
cout << "zdanie: ";
getline(cin,zdanie);
cout << ILE_ZNAKOW(zdanie) << endl;
//zadanie 5
//palindrom - wspak to samo
//wystarczy ze sprawdziwmy do połowy
//jesli palindrom to na końcu true
//jesli znaki różne to od razu kończy pętle z false
bool PALINDROM(string n){
	int d=n.length();
	for (int i=0; i <= d/2; i++){
		char z1=n[i];
		char z2=n[d-i-1];
		if (z1 != z2) return false;
	}
	return true;
}
string pal;
cout << "palindrom: ";
cin >> pal;
cout << PALINDROM(pal) << endl;

//wersja palindroma dla całych zdań
//a to kanapa pana kota
//kobyła ma mały bok
//palindromowe zdania ze spacjami
//najpierw usuwamy spacje ze zdania
bool PALINDROM1(string n){
	int d=n.length();
	string m="";
	for (int i=0; i < d; i++)
		if (n[i] != ' ') m=m+n[i];
	d=m.length();
	for (int i=0; i <= d/2; i++){
		char z1=m[i];
		char z2=m[d-i-1];
		if (z1 != z2) return false;
	}
	return true;
}
string pal;
cout << "palindrom: ";
getline(cin,pal);
cout << PALINDROM1(pal) << endl;
//zadanie 6
string SZYFR1(string n, int kod){
	int d=n.length();
	for (int i=0; i < d; i++){
		// znak ASCII zamieniamy na liczbę
		//do liczby dodajemy kod
		//nową liczbę zamieniamy na znak ASCII
		//podmieniamy znak w napisie
		n[i] = char(int(n[i]) + kod);
	}
	return n;
}
string szy;
int kod;
cout << "napis: ";
getline(cin,szy);
cout << "kod: ";
cin >> kod;
szy=SZYFR1(szy,kod);
cout << szy << endl;
//sprawdzamy w drugą stronę
szy=SZYFR1(szy,-kod);
cout << szy << endl;
//zadanie 7
string SZYFR2(string n){
	int d=n.length();
	//jesli nieparzysta to zmniejszamy o jeden 
	//zeby nie brało znaku spoza napisu
	if (d%2 == 1) d=d-1;
	for (int i=0; i < d; i=i+2){
		//podmieniamy znaki w napisie
		char z1=n[i];
		char z2=n[i+1];
		n[i]=z2;
		n[i+1]=z1;
	}
	return n;
}
//zadanie 7 szyfr
string sz;
cout << "napis: ";
getline(cin,sz);
sz=SZYFR2(sz);
cout << sz << endl;
//sprawdzamy w drugą stronę
sz=SZYFR2(sz);
cout << sz << endl;
//zadanie 8
int SAMOGLOSKI(string n){
	string SA="aeiouyAEIOUY";
	int ile=0;
	int d=n.length();
	for (int i=0; i < d; i++)
		//szukamy znaku w SA
		if (SA.find(n[i]) < d) ile++;
	return ile;
}

string nap;
cout << "napis: ";
getline(cin,nap);
cout << SAMOGLOSKI(nap) << endl;
//zadanie 9
int WYRAZY(string n){
	int ile=0;
	int d=n.length();
	for (int i=0; i < d; i++)
		if (n[i] == ' ') ile++;
	//zliczamy spacje, ale wyrazów jeden wiecej
	return ile+1;
}

string nap;
cout << "napis: ";
getline(cin,nap);
cout << WYRAZY(nap) << endl;
//zadanie 10
string DODAWANIE(string liczba1, string liczba2){
	string wynik="";
	//dlugosci liczb
	int dl1=liczba1.length();
	int dl2=liczba2.length();
	//wyznaczenie dłuższej liczby
	//i uzupełnienie początku krótszej zerami
	int dl;
	string sroz="";
	int roz=abs(dl2-dl1);
	for (int i=1; i <= roz; i++)
		sroz=sroz+"0";
	if (dl1>dl2){
		dl=dl1;
		liczba2=sroz+liczba2;
	} else{
		dl=dl2;
		liczba1=sroz+liczba1;
	}
	//dodawanie znak-cyfra-znak
	int c1,c2,c3;
	int przenies=0;
	for (int i=dl-1; i >= 0; i--){
		//zamiana na liczbe!!!
		c1=int(liczba1[i])-48;
		c2=int(liczba2[i])-48;
		c3=c1+c2+przenies;
		przenies=0;
		if (c3 >= 10){
			c3=c3-10;
			przenies=1;
		}
		//zamiana na tekst!!!
		wynik=char(c3+48)+wynik;
	}
	if (przenies==1)
		wynik='1'+wynik;	
	return wynik;
}

//wczytanie tekstów liczb
string liczba1,liczba2,wynik;
cout<<"Podaj pierwszą liczbę: ";
cin>>liczba1;
cout<<"Podaj drugą liczbę: ";
cin>>liczba2;

wynik=DODAWANIE(liczba1,liczba2);

//wyniki na ekran
cout.width(20);
cout << liczba1 << endl;
cout.width(20);
cout << liczba2 << endl;
cout.width(20);
cout << wynik << endl;

//SZABLON
#include <stdlib.h>	//system
#include <iostream>	//cout
#include <iomanip>	//fixed
#include <cmath>	//pow
using namespace std;
int main(){
  setlocale(LC_ALL, "");


  system("pause");
}