Програмен език C++

Част 18

Низове (Strings)

(съдържание)

В част 16 се запознахме с класа employee, една от променливите на който е дефинирана като char name[20];. Това на практика е масив от символи, и за да го инициализираме от клавиатурата или разпечатаме на екрана се нуждаем от цикли, при което манипулираме с името буква по буква. Това изглежда доста странно, тъй като оперирането с текстова информация е една от главните задачи на повечето компютърни програми, а самото писането на цикли е трудоемка и лесно объркваема работа. Ето защо в C и C++ има програмна структура наречена низ (string), с която се оперира сравнително лесно. Тази структура ще наричаме Си-низ (C-string), или само низ или стринг. Тази програмна структура трябва да я отличаваме от класа string, който се поддържа от стандартната C++ библиотека.

! В българската програмистка общност низовете се наричат стрингове, низа - стринг, и затова ние ще се предържаме към това популярно име, както и към строгото име низ, като в текста двете имена са взаимно заменими.

1. Низови променливи (String Variables). Стингът представлява низ от символи, който завършва с нулевия символ (null character): това не е символът '0', а символът, който има номер нула. Този нулев символ може да се представи по следния начин - '\0'. Използването на този специален символ за край на низа премахва необходимостта от запазване дължината на низа в отделна променлива както това бе направено в променливата n в класа employee - вижте част 16. Вместо това процедурите, който манипулират низовете следят за този специален символ и така откриват края на низовете.

Декларирането на стрингова променлива става така както се декларира масив, например:

char name[80];

Програмата запазва 80 байта за променливата с име name. Въпреки че горната декларация е декларация на масив, то обектите cin и cout знаят как да работят със Си-стринговете. Ето защо следните програмни редове са напълно легални в C++:

cin >> name;       // get text from user, store in name
cout << name;      // display text entered by user

При първия програмен ред, потребителят изписва буквите на името и натиска клавиша <Enter>, с което съобщава на обекта cin, че е завършил с изписването на стринга name. Програмата автоматично завършва вкарания стринг със специалния символ '\0'. Разбира се, дължината на стринга трябва да бъде равна на броя вкарани букви плюс единица, понеже последният символ е нулевият,  '\0'. Това означава, че ако при горната декларация на name от 80 символа вкараме име, което е дълго 80 или повече символа, то последният символ няма да е '\0', а това би довело до грешки в по-нататъшната работа с този стринг.

2. Низови константи (String Constants). Стринговете, точно както и масивите могат да се инициализират при тяхната дефиниция (вижте част 15 ). Например,

char name[10] = {'P', 'l', 'a', 'm', 'e', 'n', '\0'};

Разбира се, този запис е много тромав, затова има по-елегантна инициализация на стринговете като тази по-долу.

char name[10] = "Plamen";

Програмата автоматично инициализира седмия символ (който е последен) да е '\0'. Само трябва да бъдем внимателни и да осигурим размер на стринга по-голям с единица от максималното число букви, които ще пазим в него. Така например, долният код дава грешна инициализация, която може да се види чрез разпечатването на екрана със cout.

char name[6] = "Plamen";

cout << endl << name << endl;

Тъй като първите шест (а това са всичките) символи се инициализират с Plamen, то последният символ е 'n', а не '\0'. А това води до безсмислен изход с втория програмен ред - при мен на тестовата програма се получи името, последвано от няколко много странни символа -- това е така, защото cout търси нулевия символ за да спре отпечатването на стринга на екрана и при липса на нулев символ отпечатва, докато намери в паметта нулев символ.

Букви заградени в кавички (а не апострофи!) се нарича стрингова (или низова) константа (string constant) - например, "Plamen" е стрингова константа.

Друго предимство на този вид инициализация е, че можем да изпуснем размера на стринга, при което компилаторът сам ще определи размер, с единица по-голям от броя букви в инициализиращата стрингова константа.

char name[] = "Plamen";

Колкото и странно да изглежда, не можем да присвояваме стойност на стринг с оператора  =, въпреки че той се използва при инициализацията. Така например следният програмен ред дава грешка при компилацията

name = "Plamen"; // illegal

По късно в този курс ще се запознаем с така нареченото предефиниране на оператори (operator overloading), с което е възможно да предефинираме ролята на оператора за присвояване, =, и той да работи за обекти, които имитират стрингове.

3. Други функции за вход и изход на стрингови променливи. Нека предположим, че искаме да вкараме с помощта на обекта cin името и фамилията в променливата name. Осигурили сме достатъчен брой символи, в случая 80. Но въпреки това частта от програмен код, дадена по-долу не работи правилно.

 char name[80];
 cout << "Enter first name and family name: ";
 cin >> name;
 cout << "You have entered: " << name << endl;

По-долу е дадено едно взаимодействие на потребителя с програмата.

Enter first name and family name: Rosen Sedev
You have entered: Rosen

Оказва се, че обектът cin спира въвеждането на символите на името при достигане на първия интервал. Този недостатък може да се изправи с помощта на функцията get() на класа istream.

 char name[80];
 cout << "Enter first name and family name: ";
 cin.get(name, 80);
 cout << "You have entered: " << name << endl;

Този код ще прочете не повече от 79 символа от клавиатурата и ще ги въведе в променливата name. Наличието на интервали (един или повече) между името и фамилията не се отразява като при работа със cin. Прочитането на символите завършва с натискане на клавиша <Enter>. Ето този път какво се получава на екрана:

Enter first name and family name: Rosen Sedev
You have entered: Rosen Sedev

Въпреки, че горният код е кратък в него прозира един недостатък. Ако искаме да променим максималната дължина на името, то в кода на две места трябва да променяме числото 80 с новата дължина. При по-голяма и по-сложна програма този проблем става още по-неприятен. Решението му е да се използва числова константа, вместо самото число. Тогава при промяна на максималната дължина на името променяме само на едно място в програмата. Ето един по-прегледен и по-адаптивен код:

 const int SIZE = 80;
 char name[SIZE];
 cout << "Enter first name and family name: ";
 cin.get(name, SIZE);
 cout << "You have entered: " << name << endl;

Ключовата дума const стои преди типа на променливата int. Без нея компилаторът на Borland отказва да компилира програмата като дава следното съобщение:

Constant expression required.

Сред програмистите на C и C++ е прието константите да се обозначават с главни букви, ето защо ние избрахме името да е SIZE. В програмния език C константите се дефинираха с препроцесорната директива #define:

#define SIZE 80

В C++ горната директива работи, но се предпочита използването на ключовата дума const, тъй като типът на константата е ясен за потребителя, докато при директивата той не е даден.

При комбинация на функцията get() с обекта cin се появяват проблеми. Например следният код не работи правилно.

 const int SIZE = 80;
 int number;
 char name[SIZE];

 cout << "Enter employee number: ";
 cin >> number;
 cout << "Enter first name and family name: ";   // not working properly!
 cin.get(name, SIZE);
 cout << "You have entered: " << name << endl;

Вкарването на номера на служителя е съпроводено естествено с натискане на клавиша <Enter>,  което натискане генерира символ за край на ред, който не се чете в променливата number, но който символ се прочита от функцията cin.get(name, SIZE), и това предизвиква тя да не чете нищо повече. Ето код с използване на на функцията ignore() на класа istream. (допълнителният ред е удебелен за да си личи)

 const int SIZE = 80;
 int number;
 char name[SIZE];

 cout << "Enter employee number: ";
 cin >> number;

 cin.ignore(1, '\n');

 cout << "Enter first name and family name: ";
 cin.get(name, SIZE);
 cout << "You have entered: " << name << endl;

В този си запис функцията ignore() пропуска един символ '\n'.  Преспокойно можем да запишем cin.ignore(10, '\n');, т.е. функцията да пропуска десет символа и символа '\n'.

4. Примерна програма, която използва стрингове в обекти. Отново ще използваме пример, който е взет от книгата на Лафоор [1]. Тази програма съдържа подобрение на класа employee от част 16. Обърнете внимание на дефиницията на двете константи SMAX и EMAX веднага след препроцесорната директива #include <iostream.h> в началото на програмата. Тези константи не са дефинирани нито в класа, нито в главната функция на програмата. Константи или променливи, дефинирани по този начин се наричат външни (external variables). По този начин те са достъпни в цялата програма след точката си на дефиниция, докато константи или променливи, дефинирани в класа или в някоя функция са достъпни само там.

// stremp.cpp
// models database of employees
// uses switch statement for user choice

#include <iostream.h>

const int SMAX = 21;      // maximum length of strings
const int EMAX = 100;     // maximum number of employees

class employee
{
   private:
      char name[SMAX];    // name (20 chars max)
      int serial_number;

   public:
      void input()        // get data from user
      {
         cout << "   Enter name: ";
         cin.get(name, SMAX);

         cout << "   Enter serial number: ";
         cin >> serial_number;
      }

      void output()       // display employee data
      {
         cout << "   Name = " << name;
         cout << "\n   Serial number = " << serial_number;
      }
};  // class employee

void main()
{
   employee emps[EMAX];  // array of employee objects
   int n = 0;            // current number of objects in array
   int j;                // loop variable
   char choice = 'x';    // (ensure it's not 'q')

   while(choice != 'q')               // exit on 'q'
   {
      cout << "\n'a' to add an employee"
              "\n'd' to display all employees"
              "\n'q' to quit program"
              "\nEnter letter choice: ";
      cin >> choice;                  // get user's choice
      cin.ignore(10, '\n');           // eat extra '\n'

      switch(choice)
      {
         case 'a':                    // get employee data
            cout << "Enter data for employee " << (n+1) << endl;
            emps[n++].input();
            break;

         case 'd':                    // display all employees
            for (j = 0; j < n; j++)
            {
               cout << "\nData for employee " << (j+1) << endl;
               emps[j].output();
            }
            break;

         case 'q':                    // let the while loop
            break;                    // terminate the program

         default:
            cout << "Unknown command";
            break;
      }  // end switch
   }  // end while
}  //end main

Ако сравните класа employee от тази програма с този от програмата от част 16 ще забележите отпадането на циклите do и for във функциите на класа input() и output().

В програмата са дефинирани 100 обекта от клас employee. В главната функция се използва един цикъл while и един оператор за управление switch. Ето едно примерно взаимодействие на потребителя с програмата:

'a' to add an employee
'd' to display all employees
'q' to quit program
Enter letter choice: a
Enter data for employee 1
   Enter name: Plamen Penchev
   Enter serial number: 1111

'a' to add an employee
'd' to display all employees
'q' to quit program
Enter letter choice: a
Enter data for employee 2
   Enter name: Vesselin Kmetov
   Enter serial number: 2222

'a' to add an employee
'd' to display all employees
'q' to quit program
Enter letter choice: d

Data for employee 1
   Name = Plamen Penchev
   Serial number = 1111
Data for employee 2
   Name = Vesselin Kmetov
   Serial number = 2222
'a' to add an employee
'd' to display all employees
'q' to quit program
Enter letter choice:

В следващата лекция ще се занимаем с библиотечните функции, които обслужват Си-стринговете  (Library Functions for Strings).

(съдържание)
.
Литература
.

[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.
.
Автор: Проф. Процесор Аритметиков
.
[ това е материал от брой 20 от юни 2008 г. на списание "Коснос" www.kosnos.com ]

Keywords: С++,  OOP programming , C++ , Classes , Inheritance , Reusability , Creating New Data Types , Polymorphism and Overloading
Ключови думи: клас , обект, обектно ориентирано програмиране , полиморфизъм switch if else ?