Низи. Повеќедимензионални низи

Еднодимензионалните низи претставуваат множества од повеќе елементи од ист тип. Во C++, еднодимензионални низи се креираат и употребуваат на сличен начин како и основните податочни типови (int, char, double, long long, итн), со тоа што е потребно, при декларацијата, покрај името на променливата (кое ќе го користиме за пристап до елементите од низата) и податочниот тип (од кој тип се елементите кои ја сочинуваат низата), да го наведеме и бројот на елементи.

На овој начин се овозможува креирање на групи од елементи кои соодветствуваат на множества податоци врз кои планираме да вршиме операции. На пример, може да креираме низа од 365 (или 366) double вредности кои ќе ја содржат измерената просечна температура за секој ден од годината. Потоа, можеме да вршиме разни математички операции врз елементите од оваа низа - да бараме минимум, максимум, просечна температура за одредени месеци или за целата година, итн. Доколку не користевме низи, ќе требаше да креираме 365 (или 366) посебни променливи - секоја со уникатно име.

Како и секоја друга променлива, така и низите мора да бидат декларирани пред да може да се користат. Во C++, низи се декларираат на следниот начин:

tip ime[N];

Притоа, tip го означува податочниот тип на секој елемент од низата, N е бројот на елементи, додека ime е името на променливата преку кое ќе пристапуваме до елементите на низата.

На пример, за да креираме низа од 6 double вредности, треба да напишеме: "double ime[6];". Со тоа, сме креирале променлива со име ime, која овозможува пристап до 6 вредности од тип double. Секоја од тие 6 вредности се нарекува елемент на низата.

Сите елементи од низата се сместуваат на последователни локации во меморијата (еден по друг - како што е дадено на сликата). За да пристапиме до одреден елемент од низата (да ја прочитаме неговата вредност, или да запишеме некоја вредност) се користи операторот [p] - p го означува индексот на елементот до кој сакаме да пристапиме. Индексите се движат од 0 (прв елемент) до N-1 (последен елемент) - каде N го означува бројот на елементи во низата! На пример, за да пристапиме до првиот елемент од низата ime треба да напишеме ime[0], за да пристапиме до вториот елемент треба да напишеме ime[1], и така натаму, до последниот елемент ime[5] (од 0 до 5 има точно 6 елементи). Притоа, ime[0], ime[1], ..., ime[5] се однесуваат како обични променливи од тип double и нивната вредност може да ја читаме и/или менуваме.

Следниве неколку наредби служат за креирање на низа array од целобројни елементи со големина 5, и за сместување на вредностите {0, 10, 20, 30, 40} во елементите од таа низа:

Извадок 12.1

int array[5];
array[0] = 0;
array[1] = 10;
array[2] = 20;
array[3] = array[2] + 10;             //30
array[4] = array[1] + array[3];       //40

Важно е да се напомене дека, при декларирањето на една низа, мора да наведеме константна големина (големина позната пред самото извршување на програмата). За ваквите низи уште се вели дека се статички креирани. Постојат и низи, за кои ќе зборуваме понатаму, кои се динамички креирани и, чија големина, може да ја наведеме при самото извршување на програмата (на пример, со new arr[M] - каде M е целобројна променлива).

Следната програма користи низа arr[10] за работа со 10 цели броеви. Притоа, броевите се читаат од стандарден влез (тастатура), а нивниот збир се печати на стандарден излез (екран).

Програма 12.1

#include <iostream>
using namespace std;

int main()
{
      int arr[10];
      
      for (int i=0; i<10; i++)
      {
            cin >> arr[i];                 //'i' se dvizi od 0 do 9!
      }
      
      int s = 0;
      
      for (int i=0; i<10; i++)
            s += arr[i];                   //'i' se dvizi od 0 do 9!
      
      cout << "Zbirot e: " << s << endl;
      return 0;
}

Како што може да забележите од изворниот код на програмата дадена погоре, до елементите од низата пристапуваме преку именување на низата (arr) и задавање на индексот на елементот до кој сакаме да пристапиме (arr[i]).

Операторот [p], кај еднодимензионалните низи, се користи на два начини: за дефинирање на големина на низата (p мора да биде константа) и за пристап до одреден елемент од низата (p може да биде константа или променлива).

Внимавајте: Во C++ не е грешка (гледано синтаксички) доколку пристапите до елемент кој не се наоѓа во границите на низата (на пример, да се обидете да пристапите до десеттиот елемент на низа со големина 5). Но, ваквите грешки може да предизвикаат проблем за време на извршување на програмата (т.н. runtime error). Велиме "може" бидејќи оперативниот систем ќе ја "сруши" вашата програма само доколку се обидете да пристапите до меморија која тој не ви ја доделил (сакате да прочитате или запишете податок кој и припаѓа на друга програма). Програмата може да се сруши и доколку на таа локација во меморијата се чуваат податоци од друг тип.

Иницијализација

По правило, откако ќе ја декларирате низата, потребно е истата да ја иницијализирате, т.е. да им доделите вредности на нејзините елементи. Како и кај основните податочни типови, вредноста на овие елементи е неодредена - може да биде што било што се наоѓало на таа позиција во меморијата пред стартување на вашата програма. Ова правило не важи за низите кои се дефинирани глобално (надвор од која било функција) - по правило, глобалните низи се иницијализираат на основната вредност (целата меморија се полни со 0).

На елементите може да им доделиме вредност по креирање на низата (како во примерите дадени погоре), или да им доделиме вредност при самата декларација на низата. Во C++, ова се прави со набројување на вредностите:

int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Со ова сме креирале низа од 10 елементи, каде array[0]=1, array[1]=2, ..., array[8]=9 и array[9]=10. Доколку не ги специфицираме вредностите на сите елементи (на пример, декларираме низа со големина 10, но наведеме само 5 вредности при иницијализацијата {1,2,3,4,5}), наведените вредности ќе им се доделат на првите елементи од низата (почнувајќи од array[0]), а на останатите елементи ќе им се додели вредност 0. Ова може да го искористиме за сместување на вредност 0 во сите елементи од одредена низа:

int array[100] = {0}; //site 100 elementi se 0

Можно е и креирање на низи без задавање на нивна големина (int array[]). Тогаш, компајлерот сам ќе ја одреди големината на низата според бројот на елементи во листата за иницијализација:

int array[] = {1, 2, 3, 4, 5}; //niza od 5 elementi

Низите не нудат метод преку кој може да се одреди нивната големина. Во C++, можеме да го искористиме операторот sizeof() за да откриеме колку бајти зафаќа една низа - sizeof(array), но и од колку елементи е составена самата таа низа - преку делење на меморијата која ја зафаќа низата (sizeof(array)) со меморијата која ја зафаќа еден од нејзините елементи (на пример, sizeof(array[0])). Бидејќи сите елементи зафаќаат иста количина на меморија (затоа што се од ист податочен тип), оваа едноставна математичка операција (sizeof(array)/sizeof(array[0])) ќе го даде точниот број на елементи во низата.

Следната програма демонстрира некои од операциите за кои зборувавме досега:

Програма 12.2

#include <iostream>
using namespace std;

int main()
{
      int arr[10] = {1, 2, 3, 4, 5};
      
      //izlez: '1 2 3 4 5 0 0 0 0 0'
      for (int i=0; i<10; i++)
            cout << arr[i] << " ";
      
      cout << endl;
      
      double niza[] = {10.0, 15.0, 20.0, 25.0, 30.0};
      cout << sizeof(niza) << endl;                         //pechati '40' (5 * 8)
      
      cout << (sizeof(niza)/sizeof(niza[0]));               //pechati '5'
      
      return 0;
}

Низи од знаци

C++ овозможува работа со текстуални податоци на неколку начини. Покрај со користење на класата string (за која накратко зборувавме во претходните предавања), во C++ е дозволена работа со текстуални податоци и преку нивно третирање како низа од знаци - низа од примитивниот податок char.

Бидејќи ваквиот начин на работа со текстуални податоци се користел и пред појавата на C++, овие низи се среќаваат и под името "C string-ови".

Во овој дел, накратко, ќе позборуваме за попримитивниот начин на работа со текстуални податоци. Денес, на програмерите им се препорачува да ја користат класата string и да го избегнат директното користење на ваквите низи од знаци.

Низи од знаци се креираат на ист начин како и останатите еднодимензионални низи:

char ime[N];

Притоа, ime го означува името на променливата, додека N го означува бројот на знаци (од колку елементи е составена низата ime). Еден од овие N знаци (зависно од текстуалниот податок кој ќе се чува во низата) ќе биде еднаков на '\0' - и ќе претставува т.н. null знак или null terminator. Овој знак е потребен за да го означи крајот на текстуалниот податок и да ни овозможи во низа со големина N да чуваме текстуални елементи кои се пократки од N знаци. На следната слика е претставен начинот на сместување на текстуални податоци во низата од знаци name[13]:

На овој начин, кога сакаме да печатиме податоци, програмата знае дека треба да печати знаци сé додека не стигне до знакот '\0'. Сите знаци по '\0' се неважни и не се користат.

C++ овозможува иницијализација на овие низи од знаци на 2 начини:

Извадок 12.2

char name[] = {'D', 'a', 'r', 'k', 'o', '\0'};         //treba da se navede '\0'
char name[] = "Darko";                                       //se podrazbira '\0'

И двата начина прикажани погоре креираат низа name со големина 6. Низата го содржи текстуалниот податок "Darko" и null знак '\0' за означување на крај на текстот. Бидејќи низите имаат големина која се дефинира при нивното креирање, не е дозволено подоцнежно менување на нивната вредност преку користење на операторот '=' (кај string класата ова е дозволено - таа работи со динамички резервирана меморија). На пример, следниов код е погрешен:

Извадок 12.3

char name[] = "Darko";
name = "Petar";             //GRESHKA!

Следната програма прикажува како може да се користат низите од знаци во комбинација со потоците cin и cout:

Програма 12.3

#include <iostream>
using namespace std;

int main()
{
      char array[] = "Darko";
      array[0] = 'M';                      //obichna niza
      
      cout << array << endl;               //pechati 'Marko'
      
      char text[100];
      
      cout << "Vnesi eden zbor: ";
      cin >> text;                         //prochitaj eden zbor
      cout << text;                        //go pechati vneseniot zbor
      
      return 0;
}

Во програмата дадена погоре, наредбата cin >> text; чита еден збор внесен од страна на корисникот и, истиот го сместува во променливата text. На крајот од текстот, автоматски, се додава и null знак. Бидејќи cin чита податоци до првото појавување на празно место, tab или знак за нов ред, доколку сакаме да прочитаме цел ред текст (наместо само еден збор или број), потребно е да ја искористиме функцијата cin.getline(char[], N) - слична функција како што постоеше и за класата string.

Интересно, во C++, секогаш кога користиме наводници за креирање на текстуален податок (cout << "Darko"), во позадина ние всушност работиме со C string-ови (низи од знаци со null знак). Кога креираме променлива од тип string и, преку операторот '=', и доделуваме текстуална вредност (string str = "Darko"), програмата автоматски го претвора податокот од низа од знаци со null знак во string.

Постојат 7 основни функции кои овозможуваат работа со текстуални низи (сите дефинирани во датотеката "<cstring>"):

  • strlen(char[] niza) - ја враќа должината на низата niza (без null знак)
  • strcpy(char[] destinacija, char[] izvor) - копира текстуална низа (од izvor во destinacija), вклучувајќи го и null знакот. Внимавајте: низата destinacija треба да има доволна големина за да ги собере сите знаци од izvor.
  • strncpy(char[] destinacija, char[] izvor, int N) - копира најмногу N знаци од текстуална низа (од izvor во destinacija). Знакот '\0' ќе се ископира само доколку се појави во првите N знаци - инаку, истиот мора да го додадеме самите. Внимавајте: низата destinacija треба да има доволна големина за да ги собере сите потребни знаци.
  • strcmp(char[] prva, char[] vtora) - споредува две низи (резултатот е 0 ако низите се еднакви)
  • strncmp(char[] prva, char[] vtora, int N) - споредува N знаци (или помалку, ако '\0' се појави претходно) од низите prva и vtora (резултатот е 0 ако првите N знаци од низите се еднакви)
  • strcat(char[] prva, char[] vtora) - ја надоврзува низата vtora на prva. Внимавајте: низата prva треба да има доволна големина за да ги соберете знаците од двете низи.
  • strncat(char[] prva, char[] vtora, int N) - ги надоврзува првите N знаци (или помалку, ако '\0' се појави претходно) од низата vtora на prva. Внимавајте: низата prva треба да има доволна големина за да ги собере сите потребни знаци.

Следнава програма употребува некои од функциите споменати погоре:

Програма 12.4

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
      char a[] = "prva niza";
      char b[] = "vtora niza";
      
      char c[] = "tekst";
      char d[] = "tekst";
      
      cout << strlen(a) << endl;          //pechati '9'
      cout << strlen(c) << endl;          //pechati '5'
      
      char t[100];
      strcpy(t, "Nekoja vrednost");       //t="Nekoja vrednost"
      strcpy(t, "Neshto drugo");          //t="Neshto drugo"
      
      strcpy(t, a);                       //t="prva niza"
      strncpy(t, b, 4);                   //t="vtor niza" (ne se kopira '\0')
      t[4] = '\0';                        //t="vtor"
      
      strcat(t, b);                       //t="vtorvtora niza"
      
      cout << t << endl;                  //pechati 'vtorvtora niza'
      
      cout << strcmp(t, a) << endl;       //pechati '1'
      cout << strcmp(c, d) << endl;       //pechati '0'
      
      return 0;
}

Како што веќе споменавме, обидете се да не користите C string-ови во вашиот код. Едноставно, класата string (која ќе ја разгледаме во детали подоцна), ги нуди сите можности кои ги нудат текстуалните низи споменати погоре, но е многу полесна за користење.

Програма 12.5

#include <iostream>
#include <string>
using namespace std;

int main()
{
      string a, b;
      a = "prva";
      b = "vtora";
      
      cout << a.size() << endl;       //pechati '4'
      cout << b.size() << endl;       //pechati '5'
      
      a = a + " " + b;
      cout << a << endl;              //pechati 'prva vtora'
      
      b = b + b;
      cout << b << endl;              //pechati 'vtoravtora';
      
      return 0;
}

Повеќедимензионални низи

Повеќедимензионалните низи претставуваат низи од низи. Сите елементи од една повеќедимензионална низа мора да бидат од ист тип - важи истото ограничување што важеше и кај еднодимензионалните низи.

Да разгледаме една матрица (дводимензионална низа) која содржи елементи од податочниот тип int:

Дводимензионалните низи, во C++, се декларираат со следната наредба:

tip ime[R][C];

Притоа, tip го означува податочниот тип на секој од елементите во дводимензионалната низа, ime го означува името на променливата, додека R и C го означуваат бројот на редови и колони, соодветно. На пример, матрицата од сликата дадена погоре може да се декларира со наредбата "int mat[5][4];"

Индексите на елементите во дводимензионалните низи се движат од 0 до R-1 (за редовите) и од 0 до C-1 (за колоните) - слично како и кај еднодимензионалните низи. Пристапот до елементите се изведува преку наведување на името на променливата (со која е декларирана дводимензионалната низа) и индексот на редот и колоната, наведени едно по друго (mat[0][0], mat[0][1], mat[0][2], ..., mat [1][0], mat [1][1], ..., mat [2][0], mat [2][1], итн).

Следната програма ја печати таблицата за множење на броевите од 1 до 5:

Програма 12.6

#include <iostream>
using namespace std;

int main()
{
      int m[6][6];
      
      for (int r=0; r<=5; r++)
            for (int c=0; c<=5; c++)
            {
                  m[r][c] = r*c;
            }
      
      for (int r=1; r<=5; r++)
      {
            //'\t' - specijalen znak za TAB
            for (int c=1; c<=5; c++)
                  cout << " | " <<r << "*" << c << "=" << m[r][c] << "\t";
            
            cout << endl;
      }
      
      return 0;
}

Програмата печати (на екран):

| 1*1=1     | 1*2=2     | 1*3=3     | 1*4=4     | 1*5=5
| 2*1=2     | 2*2=4     | 2*3=6     | 2*4=8     | 2*5=10
| 3*1=3     | 3*2=6     | 3*3=9     | 3*4=12    | 3*5=15
| 4*1=4     | 4*2=8     | 4*3=12    | 4*4=16    | 4*5=20
| 5*1=5     | 5*2=10    | 5*3=15    | 5*4=20    | 5*5=25

Бидејќи повеќедимензионалните низи претставуваат низи од низи, тие можат да се иницијализираат преку наведување на вредностите за секоја од низите, посебно. На пример, следните две наредби може да се искористат за декларирање и иницијализација на матрица со 2 реда и 3 колони:

Извадок 12.4

double m[2][3] = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
double m[][3] =   {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
double m[][] =   {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};       //GRESHKA

Притоа, првата наредба експлицитно го наведува бројот на редови во матрицата, додека кај втората наредба тоа се прави автоматски (од страна на компајлерот). Важно е да се наведе дека, при ваквиот начин на иницијализација, може да го избегнеме експлицитното наведување на само една димензија (онаа првата - најзначајната). Оттаму, третата наредба е невалидна и ќе предизвика пријавување на грешка. Експлицитното наведување на димензиите овозможува, како и кај еднодимензионалните низи, доделување вредност 0 на одредена група на елементи:

int m[10][10] = {0};

Како што деклариравме еднодимензионални и дводимензионални низи, можно е да се декларираат, и користат, низи со повеќе од 2 димензии (т.н. повеќедимензионални низи). Во C++, ова се прави со едноставно наведување на големината на секоја од димензиите:

tip dim2[S1][S2];                  //dvodimenzionalna niza
tip dim3[S1][S2][S3];              //trodimenzionalna niza
tip dim4[S1][S2][S3][S4];          //cetiridimenzionalna niza
. . . . . . . .

Слични правила важат и за пристап до елементите - потребно е, со користење на операторот [M], да го наведеме индексот за секоја димензија: dim3[0][2][4], dim4[2][0][3][1], итн.

Забележете дека реалната големина на повеќедимензионалните низи е еднаква на производот од големината (во бајти) на податочниот тип на секој од елементите (tip) и големината на секоја од димензиите (S1*S2*S3*...*SN). На пример, наредбата "int k[100][15][20];" ќе креира повеќедимензионална низа која зафаќа податочен простор од 120000 бајти (100*15*20 = 30000 елементи од по 4 бајти), или околу 117 килобајти.

Следнава програма го илустрира начинот на декларирање, иницијализација и употреба на тродимензионалните низи:

Програма 12.7

#include <iostream>
using namespace std;

int main()
{
      int t[5][6][6] = {0};
      
      for (int i=1; i<=4; i++)
      {
            for (int j=1; j<=5; j++)
            {
                  for (int k=1; k<=5; k++)
                  {
                        t[i][j][k] = i*j*k;
                        cout << i << "*" << j << "*" << k;
                        cout << " = " << t[i][j][k] << "\t";
                  }
                  
                  cout << endl;
            }
            
            cout << "- - - - - - - - - - - - - - -" << endl;
      }
      
      return 0;
}

Програмата печати (на екран):

1*1*1 = 1      1*1*2 = 2      1*1*3 = 3      1*1*4 = 4      1*1*5 = 5
1*2*1 = 2      1*2*2 = 4      1*2*3 = 6      1*2*4 = 8      1*2*5 = 10
1*3*1 = 3      1*3*2 = 6      1*3*3 = 9      1*3*4 = 12     1*3*5 = 15
1*4*1 = 4      1*4*2 = 8      1*4*3 = 12     1*4*4 = 16     1*4*5 = 20
1*5*1 = 5      1*5*2 = 10     1*5*3 = 15     1*5*4 = 20     1*5*5 = 25
- - - - - - - - - - - - - - -
2*1*1 = 2      2*1*2 = 4      2*1*3 = 6      2*1*4 = 8      2*1*5 = 10
2*2*1 = 4      2*2*2 = 8      2*2*3 = 12     2*2*4 = 16     2*2*5 = 20
2*3*1 = 6      2*3*2 = 12     2*3*3 = 18     2*3*4 = 24     2*3*5 = 30
2*4*1 = 8      2*4*2 = 16     2*4*3 = 24     2*4*4 = 32     2*4*5 = 40
2*5*1 = 10     2*5*2 = 20     2*5*3 = 30     2*5*4 = 40     2*5*5 = 50
- - - - - - - - - - - - - - -
3*1*1 = 3      3*1*2 = 6      3*1*3 = 9      3*1*4 = 12     3*1*5 = 15
3*2*1 = 6      3*2*2 = 12     3*2*3 = 18     3*2*4 = 24     3*2*5 = 30
3*3*1 = 9      3*3*2 = 18     3*3*3 = 27     3*3*4 = 36     3*3*5 = 45
3*4*1 = 12     3*4*2 = 24     3*4*3 = 36     3*4*4 = 48     3*4*5 = 60
3*5*1 = 15     3*5*2 = 30     3*5*3 = 45     3*5*4 = 60     3*5*5 = 75
- - - - - - - - - - - - - - -
4*1*1 = 4      4*1*2 = 8      4*1*3 = 12     4*1*4 = 16     4*1*5 = 20
4*2*1 = 8      4*2*2 = 16     4*2*3 = 24     4*2*4 = 32     4*2*5 = 40
4*3*1 = 12     4*3*2 = 24     4*3*3 = 36     4*3*4 = 48     4*3*5 = 60
4*4*1 = 16     4*4*2 = 32     4*4*3 = 48     4*4*4 = 64     4*4*5 = 80
4*5*1 = 20     4*5*2 = 40     4*5*3 = 60     4*5*4 = 80     4*5*5 = 100
- - - - - - - - - - - - - - -

Дозвола за користење: CC BY-NC 2.5 ©