Многомерни
масиви
Възможно е да бъдат дефинирани и многомерни масиви. Всяка
размерност се определя чрез собствена двойка от скоби. Например,
изразът
int ia[ 4 ][ 3 ];
дефинира двумерен масив. Първата
размерност се отнася за редове, а втората - за колони. Т.е. ia е двумерен масив,
притежаващ четири колони с по три елемента. Двумерните масиви обикновено се
наричат матрици.
Многомерните масиви могат също да бъдат
инициализирани.
int ia[ 4 ][ 3 ] ={ { 0, 1, 2 }, { 3, 4, 5 },
{ 6, 7,
8 }, { 9, 10, 11 } }
Вътрешните скоби, указващи разделянето по редове, не
са задължителни. Следната инициализация е еквивалентна на предходната, въпреки
че не е толкова ясна.
int ia[4][3] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11
};
Чрез дефиницията, която следва, се инициализират първите елементи на
всеки ред. Останалите елементи получават стойност нула.
int ia[4][3] = {
{0}, {3}, {6}, {9} };
Когато вътрешните скоби липсват резултатът ще бъде
съвсем различен. Със следната дефиниция
int ia[4][3] = { 0, 1, 2, 3
};
се инициализират първите три елемента на първия ред и първия елемент
на втория. Останалите елементи получават стойност 0. За да се индексират
многомерни масиви се използуват двойки скоби за всяка размерност. Например,
следната двойка от два вложени цикъла for инициализира двумерен
масив.
main() {
const rowSize = 4;
const colSize = 3;
int ia[
rowSize ][ colSize ];
for ( int i = 0; i < rowSize; ++i )
for ( int j =
0; j < colSize; ++j )
ia[ i ][ j ] = i + j;
}
В програмните
езици Паскал или Ада, многомерните масиви се индексират, като се използува
списък от стойности, разделени със запетая, който е затворен в една двойка от
скоби. Въпреки че изразът
ia[ 1, 2 ]
представя една синтактично
правилна конструкция както в С++, така и в Ада, значението му е съвсем различно
в двата езика.
- В Ада чрез този индекс се указва втория елемент на
първия ред. Стойността му е цялата величина на този елемент.
- В С++ изразът
указва третия ред на ia (да си припомним, че редовете и колоните се индексират,
като се започва от 0). Стойността му е указател към тип int*, адресиращ нулевия
елемент на този ред.
В С++ индексния израз от примера
ia[ 1, 2
]
се разглежда израз за последователно изпълнение, който връща цяла
стойност - в този случай:
ia[ 2 ]
Изразът за последователно
изпълнение представлява серия от изрази, разделени със запетая. Този израз се
изчислява от ляво на дясно. Резултатът му е стойността на най-десния израз.
Например, стойността на следния израз за последователно изпълнение е
3.
7, 6+4, ia[0][0] = 0, 4-1; // comma expression
Връзка между
типовете масив и указател
Дефиницията на един масив се състои от четири
различни елемента: спецификатор на тип, идентификатор, индексен оператор (“[]”)
и означение на размерността. Например,
char buf[ 8 ];
дефинира buf
като масив от 8 елемента от тип char. Подописание, осъществено чрез прилагне на
индексния оператор към идентификатора на масива, от вида
buf[ 0
];
връща стойността на първия елемент, който се съдържа в buf. Какво,
обаче, се случва, когато бъде пропуснат индексния оператор? Каква е стойността
на самия идентифкатор на масив?
buff;
Идентификаторът на масива
представя адреса на първия елемент, който се съдържа в buf. Това е еквивалентно
на
&buf[ 0 ];
Да си припомним, че прилагането на адресният
оператор към даннов обект връща указател от типа на обекта. В този случай,
обектът е от тип char, което означава, че buf трябва да връща стойност от тип
char*. Ако това е така, то един указател би могъл да бъде и идентификатор на
масив. Например,
char *pBuf = buff; // ok
pbuf и buf сега вече са
еквивалентни. Всички изчисления се извършват спрямо първия елемент на масива. За
да бъде адресиран следващия елемент, следователно, могат да се приложат следните
два метода, които също трябва да бъдат еквивалентни:
// equivalent
addressing methods
pBuff + 1;
&buf[ 1 ];
И изобщо
for (
int i = 0; i < arraySize; ++i )
{ pBuf + 1;
&buf[ i ]; }
От
това следва, че формите
// two equivalent accessing
methods
*pbuf;
buff[ 0 ];
са еквивалентни. Всеки от тях връща
стойността на нулевия елемент на масива. За да получи достъп до следващия
елемент програмистът трябва да използува един от следните два начина:
*(
pBuff + 1 );
buf[ 1 ];
или по-общо казано,
*( pBuff + index
);
buf[ index ];
Тези два метода за адрeсиране на елементи на масив
могат да бъдат използувани равностойно. Програмистите, които сега се запознават
със С++ често трудно боравят с указателния синтаксис. В началото те може да
искат да прилагат индексния оператор към указатели, който адресират масиви.
Следващата реализация на функция, която слепва два низа, е един такъв
пример.
#include <stream.h>
char *catString( char *st1, char
*st2 ) {
// append st2 to st1 if two distinct strings
// if st1 does
not address a string
// but st2 does, return st2
if ( st1 == 0
&& st2 )
return st2;
// unless st1 and st2 address
distinct
// strings, returns st1
if ( st2 == 0 || st1 == st2
)
return st1;
for ( int i = 0; st1[ i ] != ‘�’; ++i ) ;
// stpe
through to end of st1
for ( int j = 0; st2[ j ] !=; ++i, ++j )
st[ i ]
= st[ j ];
st2[ i ] = ‘�’;
return st1;
}
Еквивалентността на
указатели и масиви трябва да се разбира в смисъл на достъп или адресиране на
блокове от паметта. Методът, по който, обаче, идентификаторът на масив и
указателят адресират паметта е съществено различен.
Всяка дефиниция на
масив предизвиква отделяне на блок от памет. Идентификаторът на масива се
инициализира с този адрес, като той не може да бъде променян във вътрешността на
програмата. На практика идентификатора на масива се обработва като константа.
Следователно, опитът да бъде увеличен buf в следващата програма е некоректен.
Идентификаторът на масив не е стойност за запис.
char buf[] =
"rampion";
main() {
int cnt = 0;
while ( *buf ) { ++cnt;
++buf; //
error: may not increment buff
} }
Дефиницията на указател предлага
само възможност да се съхранява стойност на адрес от паметта. Програмистът
трябва да даде на указателя стойност, представляваща адрес на вече разположен в
паметта обект или на част от такъв, преди да може да бъде използван спокойно.
Това отделяне на памет често се прави и по време на изпълнение на програмата
като се използува оператора new. (Вж. раздел 5.1 за повече
информация).
Упражнение 1-13. Въпреки че следната програма се компилира
без предупреждения или съобщения за грешки, в нея има нещо неправилно. Къде е
проблема? Как ще го откриете?
char buf[] = "fiddleferns";
main()
{
char *ptr = 0;
for ( int i = 0; buf[ i ] != ‘�’; ++i;)
ptr[ i ] =
buf[ i ];
}