Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Пример оформления тестового задания.






//------------------------------------------------------52-08.cpp

//-------------------------------------------------------

void F(int *p, int *q, int n){

for (*q = 0; n > 0; n--)

* q = *q + *p++; }

void main(){

int x, A[5]={1, 3, 7, 1, 2};

F(A, & x, 5); printf(" x=%d\n", x); } // Выведет 13

Формальный параметр p используется в контексте *p++, что означает работу с последовательностью переменных, то есть с массивом. Число повторений цикла определяется параметром n, соответствующим размерности массива. Указатель q используется для косвенного обращения через него к отдельной переменной. Поэтому при вызове функции фактическими параметрами являются: имя массива – указатель на начало, адрес переменной – указатель на нее и константа – размерность массива, передаваемая по значению.

 

//-----------------------------------------------------------52-09

//-----------------------------------------------------------1

int F1(char *c){

for (int nc=0; *c! =0; c++)

if (*c! =' ' & & (c[1]==' ' || c[1]==0)) nc++;

return nc; }

//-----------------------------------------------------------2

int F2(char *c){

int nc; char *p;

for (nc=0, p=c; *c! =0; c++)

if (*c! =' ' & & (p==c || c[-1]==' ')) nc++;

return nc; }

//-----------------------------------------------------------3

void F3(char *c){

while (*c! =0) c++;

*c++='*'; *c=0; }

//----------------------------------- -----------------------4

void F4(char *c){

for (; *c! =0; c++)

if (*c==' ' & & c[1]==' '){

for (char *p=c; *p! =0; p++) *p=p[1];

c--;

}}

//-----------------------------------------------------------5

void F5(char *c, char *out){

for (; *c! =0; c++)

if (*c! =' '){

*out++=*c;

if (c[1]==' ') *out++=' ';

}

*out=' '; }

//-----------------------------------------------------------6

void F6(char *c, char *out){

for (; *c! =0; c++)

if (! (*c==' ' & & c[1]==' ')) *out++=*c;

*out=0; }

//-----------------------------------------------------------7

int F7(char *c){

for (int nc=0; *c! =0; c++)

if (*c> ='0' & & *c< ='9')

nc=nc+*c-'0';

return nc; }

//-----------------------------------------------------------8

void F8(char *p, int B[]){

int nc; char *c;

for (nc=0, c=p; *c! =0; c++)

if (*c! =' ' & & (c==p || c[-1]==' '))

B[nc++]=c-p; }

//-----------------------------------------------------------9

void F9(char *c, char *out){

for (char *p=c; *c! =0; c++)

if (*c! =' ' & & (c[1]==' ' || c[1]==0)){

for (char *q=c; q> =p & & *q! =' '; q--)

*out++=*q;

*out++=' ';

}

*out=0; }

//-----------------------------------------------------------10

int F10(char *c){

for (; *c! =0; c++)

if (*c> ='0' & & *c< ='9 ') break;

for (int s=0; *c> ='0' & & *c< ='9'; c++)

s=s*10+*c-'0';

return s; }

//-----------------------------------------------------------11

char *F11(char *c, int & m){

char *b=NULL;

for (m=0; *c! =0; c++)

if (*c==c[1]){

for (int k=2; *c==c[k]; k++);

if (k> m) m=k, b=c;

}

return b; }

//----------- ------------------------------------------------12

char *F12(char *c, int & m){

char *b=NULL;

int k=0;

for (; *c! =0; c++)

if (*c! =' ') k++;

else{

if (k! =0 & & k> m) m=k, b=c-k;

k=0;

}

return b; }

//------------------------------------------------------------13

char *F13(char *c, int & m){

char *b=NULL;

for (int k=0; *c! =0; c++)

if (*c! =' '){

for (k=1; c[k]! =' ' & & c[k]! =0; k++);

if (k> m) m=k, b=c;

c+=k-1;

}

return b;

}

//---------------------- -------------------------------------14

int F14(char *c){

int s;

for (; *c! =0; c++)

if (*c> ='0' & & *c< ='9' || *c> ='A' & & *c< ='F') break;

for (s=0; *c> ='0' & & *c< ='9' || *c> ='A' & & *c< ='F'; c++)

if (*c> ='0' & & *c< ='9')

s=s*16+*c-'0';

else

s=s*16+*c-'A'+10;

return s; }

//------------------------------------------------------------15

void F15(char *c){

for (char *p=c; *p! ='\0'; p++);

for (p--; p> c; p--, c++)

{ char s; s=*p; *p=*c; *c=s; }

}

//------------------------------------------------- ----------16

int F16(char *c){

for (int old=0, nw=0; *c! ='\0'; c++){

if (*c==' ') old = 0;

else { if (old==0) nw++; old=1; }

}

return nw; }

//------------------------------------------------------------17

void F17(int a, char *c){

for (int mm=a; mm! =0; mm /=10, c++);

for (mm=a, *c--='\0'; mm! =0; c--, mm=mm/10)

*c= mm % 10 + '0';

}

//------------------------------------------------------ -----18

void F18(int a, char *c){

for (int mm=a; mm! =0; mm /=16, c++);

for (mm=a, *c--='\0'; mm! =0; c--, mm=mm /16)

{

int v=mm % 16;

if (v < =9) *c = v + '0';

else *c = v - 10 + 'A';

}}

//------------------------------------------------------------19

int F19(char *p){

char *c; int ns;

for (c=p, ns=0; *c! ='\0'; c++) {

for (int k=0; c-k > =p & & c[k]! ='\0'; k++)

if (c[-k]! = c[k]) break;

if (k > =3) ns++;

}

return ns; }

//------------------------------------------------------------20

char *F20(char *c1, char *c2){

for (; *c1! ='\0'; c1++) {

for (int j=0; c1[j]==c2 [j]; j++);

if (c2[j]=='\0') return c1;

}

return NULL; }

//------------------------------------------------------------21

char *F21(char *c, int & s){

int n; char *z, *p;

for (; *c! =0; c++){

for (p=c, n=0; *p! ='\0'; p++)

if (*p==*c) n++;

if (n > s) { z=c; s=n; }

}

return z;

}

//------------------------------------------------------------22

void F22(double x, char *c){

x-=(int)x;

int i;

for (*c++='.', i=1; i< 6; i++) {

x *= 10.; *c++=(int)x + '0'; x -= (int)x;

}

*c='\0'; }

//------------------------------------------------------------23

void F23(char *c){

int cm=0;

for (char *p=c; *c! ='\0'; c++) {

if (c[0]=='*' & & c[1]=='/') { cm--, c++; continue; }

if (c[0]=='/' & & c[1]=='*') { cm++, c++; continue; }

if (cm==0) *p++ = *c;

}

*p=0; }

//------------------------------------------------------------24

int *F24(int *p, int *q){ return *p > *q? p: q; }

//------------------------------------------------------------25

void F25(int *p1, int *p2)

{ int c; c = *p1; *p1 = *p2; *p2 = c; }

//------------------------------------------------------------26

void F26(int *p, int *q, int n){

for (*q = *p; n > 0; n--, p++)

if (*p > *q) *q = *p;

}

//------------------------------------------------------------27

int *F27(int *p, int n)

{ int *q;

for (q = p; n > 0; n--, p++)

if (*p > *q) q = p;

return q; }

Определите, каким образом программа передает параметры (по значению или по ссылке) и как это влияет на результат.

 

//------------------------------------------------------------28

int inc1(int vv) { vv++; return vv; }

void main1(){ int a, b=5; a=inc1(b); }

//------------------ ---------------------------------------- 29

int inc2(int & vv) { vv++; return vv; }

void main2(){ int a, b=5; a=inc2(b); }

//----------------------------------------------------------- 30

int inc3(int & vv) { vv++; return vv; }

void main3(){ int a, b=5; a=inc3(+ +b); }

//----------------------------------------------------------- 31

int & inc4(int & vv) { vv++; return vv; }

void main4(){ int a, b=5; a=inc4(b); }

//----------------------------------------------------------- 32

int inc5(int & x) { x++; return x+1; }

void main5 ()

{ int x, y, z; x = 5; y = inc5(x); z=inc5(x); z=inc5(z); }

//----------------------------------------------------------- 33

int & inc6(int & x){ x++; return x; }

void main6 ()

{ int x, y, z; x = 5; y = inc6(x); z = inc6(inc6(x)); }

//---------------------------- -------------------------------34

int inc7(int x) { x++; return x+1; }

void main7 () { int x, y, z; x = 5; y=inc7(x);

z = inc7(inc7(x)); }

//------------------------------------------------------------ 35

int & F38(int & n1, int & n2){

return n1 > n2? n1: n2; }

void main8(){ int x=5, y=6, z; z=F38(x, y); F38(x, y)++; }

//------------------------------------------------------------ 36

int & F39(int & n1, int & n2){

return n1 > n2? n1: n2; }

void main10(){ int x=5, y=6, z; F39(x, y)=0; F39(x, y)++; }

 

3 Основні операції над вказівниками

4 Багаторівнева непряма адресація

У мові Сі можна використовувати багаторівневу непряму адресацію, тобто непряму адресацію на 1, 2 і т.д. рівні. При цьому для оголошення і звертання до значень за допомогою покажчиків можна використовувати відповідно кілька символів зірочка: *. Зірочки при оголошенні ніби уточнюють призначення імені змінної, визначаючи рівень непрямої адресації для звертання до значень за допомогою цих покажчиків. Приклад оголошення змінної і покажчиків для багаторівневої непрямої адресації можна привести наступний:

int i = 123 /* де: i - ім'я змінної */

int *pi = & i; /* pi - покажчик на змінну і */

int **ppi = & pi; /* ppi - покажчик на покажчик на змінну pi */

int * * *pppi = & ppi; /* pppi - вказівник на 'вказівник на 'вказівник на змінну ppi' */

Для звертання до значень за допомогою вказівників можна прийняти наступне правило, що жорстко зв'язує форму звертання з оголошенням цих вказівників:

• повна кількість зірочок непрямої адресації, рівна кількості зірочок при оголошенні вказівника, визначає значення змінної;

• зменшення кількості зірочок непрямої адресації додає до імені змінної слово " вказівник", причому цих слів може бути стільки, скільки може бути рівнів непрямої адресації для цих імен вказівників, тобто стільки, скільки зірочок стоїть в оголошенні вказівника.



Наприклад, після оголошення:

int i, *pi=& i;

звертання у виді:

*pi - визначає значення змінної, pi - покажчик на змінну i.

А при звертанні до змінних можна використовувати різну кількість зірочок для різних рівнів адресації:

pi, ppi, pppi - 0-й рівень адресації, пряма адресація;

*pi, *ppi, *pppi - 1-й рівень непрямої адресації

**ppi, **pppi - 2-й рівень непрямої адресації

***pppi - 3-й рівень непрямої адресації

Таким чином, до вказівників 1-го і вище рівнів непрямої адресації можливі звертання і з меншою кількістю зірочок непрямої адресації, аніж задано при оголошенні вказівника. Ці звертання визначають адреси, тобто значення вказівників визначеного рівня адресації. Відповідність між кількістю зірочок при звертанні за допомогою вказівника і призначенням звертання за вказівником для наведеного прикладу ілюструє таблиця 1.12 (де Р.н.а. - рівень непрямої адресації):

 

Таблиця 1.12. Відповідність між кількістю уточнень (*) і результатом звертання за допомогою вказівника

Звертання Результат звертання Р.н.а.
i значення змінної i  
*pi значення змінної, на яку вказує pi вказівник на  
pi змінну типу int, значення pi  
**ppi значення змінної типу int  
*PPi вказівник на змінну типу int  
ppi вказівник на " вказівник на змінну типу int', значення вказівника ppi  
***pppi значення змінної типу int;  
**pppi вказівник на змінну типу int  
*pppi вказівник на 'вказівник на змінну типу int'  
pppi вказівник на 'вказівник на 'вказівник на змінну типу int', значення вказівника pppi  

5 Операції над вказівниками

Мова Сі надає можливості для виконання над вказівниками операцій присвоювання, цілочисельної арифметики та порівнянь. Мовою Сі можливо:

1. присвоїти вказівнику значення адреси даних, або нуль;

2. збільшити (зменшити) значення вказівника;

3. додати або відняти від значення вказівника ціле число;

4. скласти або відняти значення одного вказівника від іншого;

5. порівняти два вказівники за допомогою операцій відношення.

Змінній-вказівнику можна надати певне значення за допомогою одного із способів:

1. присвоїти вказівнику адресу змінної, що має місце в ОП, або нуль, наприклад:

pi = & j; pi = NULL;

2. оголосити вказівник поза функцією (у тому числі поза main()) або у будь-якій функції, додавши до нього його інструкцію static; при цьому початковим значенням вказівника є нульова адреса (null);

3. присвоїти вказівнику значення іншого вказівника, що до цього моменту вже має визначене значення; наприклад: pi = pj; це - подвійна вказівка однієї і тієї ж змінної;

4. присвоїти змінній-вказівнику значення за допомогою функцій calloc() або malloc() - функцій динамічного виділення ОП.

Усі названі дії над вказівниками будуть наведені у прикладах програм даного розділу. Розглянемо кілька простих прикладів дій над вказівниками.

Зміну значень вказівника можна робити за допомогою операцій: +, ++, -, —. Бінарні операції (+ та -) можна виконувати над вказівниками, якщо обидва вказівники посилаються на змінні одного типу, тому що об'єм ОП для різних типів даних може вирізнятися.

Наприклад, значення типу int займає 2 байти, а типу float - 4 байти. Додавання одиниці до вказівника додасть „квант пам'яті", тобто кількість байтів, що займає одне значення типу, що адресується. Для вказівника на елементи масиву це означає, що здійснюється перехід до адреси наступного елемента масиву, а не до наступного байта. Тобто значення вказівника при переході від елемента до елемента масиву цілих значень буде збільшуватися на 2, а типу float - на 4 байти. Результат обчислення вказівників визначений у мові Сі як значення типу int.

Приклад програми зміни значення вказівника на 1 квант пам'яті за допомогою операції „++" і визначення результату обчислення вказівників даний на такому прикладі:

#include< stdio.h>

void main ()

{

int a[] = { 100, 200, 300 };

int *ptr1, *ptr2;

ptr1=a; /*- ptrl одержує значення адреси a[0] */

ptr2 = & a[2]; /*- ptr2 одержує значення адреси a[2] */

ptr1++; /* збільшення значення ptrl на квант ОП: */

ptrl = & а[1]*/ptr2++; /* збільшення значення ptr2 на квант ОП: ptr2 = & а[3]*/

printf (" ptr2 - ptr1 = %d\n", ptr2 - ptr1);

}

Результат виконання програми:

ptr2 - ptr1 = 2

Результат 2 виконання операції віднімання визначає 2 кванти ОП для значень типу int:

ptr2 - ptr1 = & а[3] - & а[1] = (а + 3) - (а + 1) = 2;

У наступному Сі-фрагменті продемонстрований приклад програми для виведення значень номерів (індексів) елементів масивів, адрес першого байта ОП для їх розміщення та значень елементів масивів. Справа в тому, що в Сі є дуже важлива властивість - ім'я масиву еквівалентно адресу його нульового елемента: х == & х[0]. Вказівник pi і pf спочатку містять значення адрес нульових елементів масивів, а при виведенні складаються з i-номером елемента масиву, визначаючи адресу i-елемента масиву. Для одержання адрес елементів масивів у програмі використовується додавання вказівників-констант х та у, та змінних-вказівників pi і pf з цілим значенням змінної i. Зміна адрес у програмі дорівнює кванту ОП для даних відповідного типу: для цілих - 2 байти, для дійсних - 4 байти.

#include< stdio.h>

void main()

{

int x[4], *pi = x, i; float y[4], *pf = y;

printf(" \nномер елемента адреси елементів масивів: \n" " i pi+i x + i & x[i] pf+i у+i & y[i]\n");

for (i = 0; i < 4; i++)

printf(" %d: %6u %6u %6u %6u %6u %6u\n", i, pi + i, x + i, & x[i], pf + i, y + i, & y[i]);

Результати виконання програми:

номер елемента адреси елементів масивів:  
i pi+i. x+i & x[i] pf+i У+i & y[i]
0:            
1:            
2:            
3:            

}

Мовою Сі можна визначити адреси нульового елемента масиву х як х або & х[0]: х == & х[0]. Краще і стисло використовувати просто х -це базова адреса масиву. Ту саму адресу елемента масиву можна представити у вигляді: х + 2 == & х[2]; х + і == & х[і].

Те саме значення можна представити у вигляді:

*(х + 0) == *х == х[0] - значення нульового елемента масиву х;

*(х + 2) == x[2] - значення другого елемента масиву х;

*(х + i) == x[i] - значення i-го елемента масиву х.

А операції над елементами масиву х можна представити у вигляді:

*х + 2== х[0] +2; *(х + і) - 3 == x[i] - 3;

6 Проблеми, пов'язані з вказівниками

Проблеми, пов'язані з вказівниками, виникають при некоректному використанні вказівників. Усі застереження щодо некоректного використання вказівників відносяться до мови Сі так само, як і до багатьох інших низькорівневих мов програмування. Некоректним використанням вказівників може бути:

• спроба працювати з неініціалізованим вказівником, тобто з вказівником, що не містить адреси ОП, що виділена змінній;

• втрата вказівника, тобто значення вказівника через присвоювання йому нового значення до звільнення ОП, яку він адресує;

• незвільнення ОП, що виділена за допомогою функції malloc();

• спроба повернути як результат роботи функції адресу локальної змінної класу auto (про функції та класи змінних йтиметься далі);

Запит на виділення ОП з купи робиться за допомогою функцій calloc() та malloc(). Повернення (звільнення) ОП робиться за допомогою функції free(). Розглянемо деякі проблеми, пов'язані з вказівниками.

При оголошенні вказівника на скалярне значення будь-якого типу оперативна пам'ять для значення, що адресується, не резервується. Виділяється тільки ОП для змінної-вказівника, але вказівник при цьому не має значення. Якщо вказівник має специфікатор static, то ініціюється початкове значення вказівника, рівне нулю (особливості статичних змінних, про що йтиметься в окремому розділі). Приклад ініціалізації вказівників нульовими значеннями при їх оголошенні:

static int *pi, *pj; /* pi = NULL; pj= NULL; */

Розглянемо приклад, що містить грубу помилку: спробу працювати з непроініціалізованим покажчиком.

int *х; /* змінній-покажчику 'х' виділена ОП, але 'х' не містить значення адреси ОП для змінної */

*х = 123; /* - груба помилка! */

Таке присвоювання помилкове, тому що змінна-вказівник х не має значення адреси, за яким має бути розташоване значення змінної.

Компілятор видасть попередження:

Warning: Possible use of 'x' before definition

При цьому випадкове (непроініціалізоване) значення вказівника (сміття) може бути неприпустимим адресним значенням! Наприклад, воно може збігатися з адресами розміщення програми або даних користувача, або даних операційної системи. Запис цілого числа 123 за такою адресою може порушити працездатність програми користувача або самої OC. Компілятор не виявляє цю помилку, це повинен робити програміст!

Виправити ситуацію можна за допомогою функції malloc(). Форма звертання до функції malloc() наступна:

ім'я-вказівника = (тип-вказівника) malloc (об'єм -ОП);

де ім'я-вказівника - ім'я змінної-вказівника, тип-вказівника - тип значення, що повертається функцією malloc;

об'єм-ОП - кількість байтів ОП, що виділяються змінній, яка адресується.

Наприклад:

х = (int *) malloc (sizeof (int));

При цьому з купи виділяється 2 байти ОП для цілого значення, а отримана адреса його розміщення заноситься в змінну-вказівник х. Значення вказівника гарантовано не збігається з адресами, що використовуються іншими програмами, у тому числі програмами OC. Параметр функції malloc визначає об' єм ОП для цілого значення за допомогою функції sizeof(int). Запис (int *) означає, що адреса, що повертається функцією malloc(), буде розглядатися як вказівник на змінну цілого типу. Це операція приведення типів.

Таким чином, помилки не буде у випадку використання наступних операторів:

int *х; /* х - ім'я покажчика, він одержав ОП*/

х = (int *) malloc (sizeof(int));

/* Виділена ОП цілому значенню, на яке вказує 'x' */

*х = 123; /* змінна, на яку вказує 'х', одержала значення 123*/

Повернення (звільнення) ОП у купі виконує функція free(). її аргументом є ім'я вказівника, що посилається на пам'ять, що звільняється. Наприклад: free (x);

Щоб уникнути помилок при роботі з функціями не слід повертати як результат їхнього виконання адреси автоматичних (локальних) змінних функції. Оскільки при виході з функції пам'ять для всіх автоматичних змінних звільняється, повернута адреса може бути використаною системою й інформація за цією адресою може бути невірною. Можна повернути адресу ОП, що виділена з купи.

Одна з можливих помилок - подвійна вказівка на дані, розташовані у купі, і зменшення об'єму доступної ОП через незвільнення отриманої ОП. Це може бути для будь-якого типу даних, у тому числі для скаляра або масиву. Розглянемо випадок для скаляра.

Приклад фрагмента програми з подвійною вказівкою і зменшенням об'єму доступної ОП через незвільнення ОП наведений нижче:

#include< alloc.h>

void main ()

{

/* Виділення ОП динамічним змінним х, у и z: */

int *х = (int *) malloc (sizeof(int)),

*у = (int *) malloc (sizeof(int)),

*z = (int *) malloc (sizeof(int)); /* Ініціалізація значення покажчиків х, у, z; */

*х = 14; *у = 15; *z = 17; /*Динамічні змінні одержали конкретні цілі значення*/

y=x; /* груба помилка - втрата покажчика на динамічну змінну в без попереднього звільнення її ОП */

}

У наведеному вище прикладі немає оголошення імен змінних, є тільки вказівники на ці змінні. Після виконання оператора y = х; х та у є двома вказівниками на ту саму ОП змінної *х. Тобто *х = 14; і *у = 14. Крім того, 2 байти, виділені змінній, яку адресував y для розміщення цілого значення (*у), стають недоступними (загублені), тому що значення у, його адреса, замінені значенням х. А в купі ці 2 байти для *у вважаються зайнятими, тобто розмір купи зменшений на 2 байти. Відбулося зменшення доступної ОП. Цього слід уникати.

Щоб уникнути такої помилки треба попередньо звільнити ОП, виділену змінній *у, а потім виконати присвоювання значення змінній у. Наприклад:

Чи можна змінній-вказівнику присвоїти значення адреси в операторі оголошення? Наприклад: int *x = 12345;

Тут константа 12345 цілого типу, а значенням вказівника х може бути тільки адресою, вказівником на байт в ОП. Тому компілятор при цьому видасть повідомлення про помилку: Error PR.CPP 3: Cannot convert 'int to 'int *' Проте не викличе помилки наступне присвоювання: int a[5], *х = а;

Використання вказівників часто пов'язано з використанням масивів різних типів. Кожний з типів даних масивів має свої особливості. Тому далі розглянемо властивості вказівників для роботи з масивами.

Між вказівниками і масивами існує тісний взаємозв'язок. Будь-яка дія над елементами масивів, що досягається індексуванням, може бути виконана за допомогою вказівників (посилань) і операцій над ними. Варіант програми з вказівниками буде виконаний швидше, але для розуміння він складніший.

Як показує практика роботи на Сі, вказівники рідко використовуються зі скалярними змінними, а частіше - з масивами. Вказівники дають можливість застосовувати адреси приблизно так, як це робить ЕОМ на машинному рівні. Це дозволяє ефективно організувати роботу з масивами. Будь-яку серйозну програму, що використовує масиви, можна написати за допомогою вказівників.

Для роботи з масивом необхідно:

1. визначити ім'я масиву, його розмірність (кількість вимірів) і розмір - кількість елементів масиву;

free (у); /* у = х; /* п

9 Масиви

1.9.1 Основні поняття

62 Розділ 1. Мова програмування Сі

2. виділити ОП для його розміщення. У мові Сі можна використовувати масиви даних будь-якого типу:

• статичні: з виділенням ОП до початку виконання функції; ОП виділяється в стеку або в ОП для статичних даних;

• динамічні: ОП виділяється з купи в процесі виконання програми, за допомогою функцій malloc() і calloc().

Динамічні змінні використовують, якщо розмір масиву невідомий до початку роботи програми і визначається в процесі її виконання, наприклад за допомогою обчислення або введення.

Розмір масиву визначається:

1. для статичних масивів при його оголошенні; ОП виділяється до початку виконання програми; ім'я масиву - покажчик-константа; кількість елементів масиву визначається:

a. явно; наприклад: int а[5];

b. неявно, при ініціалізації елементів масиву; наприклад: int а[] = { 1, 2, 3 };

2. для динамічних масивів у процесі виконання програми; ОП для них запитується і виділяється динамічно, з купи; ім'я покажчика на масив - це змінна; масиви ці можуть бути:

a. одновимірні і багатовимірні; при цьому визначається кількість елементів усього масиву й ОП запитується для всього масиву;

b. вільні (спеціальні двовимірні); при цьому визначається кількість рядків і кількість елементів кожного рядка, і ОП запитується і виділяється для елементів кожного рядка масиву в процесі виконання програми; при використанні вільних масивів використовують масиви покажчиків;

Розмір масиву можна не вказувати. В цьому разі необхідно вказати порожні квадратні дужки:

1. якщо при оголошенні ініціалізується значення його елементів; наприклад:

static int а[] = {1, 2, 3}; char b[] = " Відповідь: ";

2. для масивів - формальних параметрів функцій; наприклад:

int fun1(int a[], int n); int fun2(int b[k][m][n]);

3. при посиланні на раніше оголошений зовнішній масив; наприклад:

int а[5]; /* оголошення зовнішнього масиву */ main ()

{

extern int а[]; /*посилання на зовнішній масив */

}

В усіх оголошеннях масиву ім'я масиву - це покажчик-константа! Для формування динамічного масиву може використовуватися тільки ім'я покажчика на масив - це покажчик-змінна. Наприклад:

int *m1 = (int *) malloc (100 * sizeof (int));

float *m2 = (float *) malloc (200 * sizeof (float));

де ml - змінна-вказівник на масив 100 значень типу int;

m2 - змінна-вказівник на масив 200 значень типу float.

Звільнення виділеної ОП відбувається за допомогою функції:

free (вказівник-змінна);

Наприклад:

free(ml);

free(m2);

Звертання до елементів масивів ml і m2 може виглядати так: m1[i], m2[j].

Пересилання масивів у Сі немає. Але можна переслати масиви поелементно або сумістити масиви в ОП, давши їм практично те саме ім'я.

Наприклад:

int *m1 = (int *) malloc(100 * sizeof(int));

int *m2 = (int *) malloc(100 * sizeof(int));

Для пересилання елементів одного масиву в іншій можна використати оператор циклу:

for (i = 0; i < 100; i++) m2[i] = ml [i];

Замість m2[i] = ml [i]; можна використовувати: *m2++ = *ml++; або: *(m2 + i) = *(ml + i);

За допомогою покажчиків можна сполучити обидва масиви й у такий спосіб:

free(m2); m2 = ml;

Після цього обидва масиви займатимуть одну й ту саму область ОП, виділену для масиву ml. Однак це не завжди припустимо. Наприклад, коли масиви розташовані в різних типах ОП: один - у стеку, інший - у купі. Наприклад, у функції main() оголошені:

int *m1 = (int *) malloc(100* sizeof(int));

int m2 [100];

У вищенаведеному прикладі ml - пакажчик-змінна, і масив ml розташований у купі, m2 - покажчик-константа, і масив m2 розташований у стеку. У цьому випадку помилковий оператор: m2 = ml; тому що m2 - це покажчик-константа. Але після free(ml) припустимим є оператор:

ml = m2; /* оскільки ml - вказівник-змінна */

Для доступу до частин масивів і до елементів масивів використовується індексування (індекс). Індекс - це вираз, що визначає адресу значення або групи значень масиву, наприклад адреса значень чергового рядка двовимірного масиву. Індексування можна застосовувати до вказівників-змінних на одновимірний масив - так само, як і до вказівників-констант.

Індексний вираз обчислюється шляхом додавання адреси початку масиву з цілим значенням для одержання адреси необхідного елемента або частини масиву. Для одержання значення за індексним виразом до результату - адреси елемента масиву застосовується операція непрямої адресації (*), тобто одержання значення за заданою адресою. Відповідно до правил обчислення адреси цілочисельний вираз, що додається до адреси початку масиву, збільшується на розмір кванта ОП типу, що адресується вказівником.

Розглянемо способи оголошення і формування адрес частини масиву й елементів одновимірних і багатомірних масивів за допомогою вказівників.

9.2 Оголошення та звертання в одновимірних масивах

Форма оголошення одновимірного масиву з явною вказівкою кількості елементів масиву:

тип ім'я_масива [кількість-елементів-масива];

Звертання до елементів одновимірного масиву в загальному випадку можна представити індексуванням, тобто у вигляді

ім'я-масива [вираз]; де ім'я-масиву - вказівник-константа;

вираз - індекс, число цілого типу; він визначає зсув - збільшення адреси заданого елемента масиву щодо адреси нульового елемента масиву.

Елементи одновимірного масиву розташовуються в ОП підряд: нульовий, перший і т д. Приклад оголошення масиву:

int а[10];

int *p = а; /* - р одержує значення а */

При цьому компілятор виділяє масив в стеку ОП розміром (sizeof(Type) * розмір-масиеу) байтів.

У вищенаведеному прикладі це 2 * 10 = 20 байтів. Причому а -вказівник-константа, адреса початку масиву, тобто його нульового елемента, р - змінна; змінній р можна присвоїти значення одним із способів:

р = а;

р = & а[0];

р = & a[i];

де & а[і] == (а + і) - адреса і-елемента масиву.

Відповідно до правил перетворення типів значення адреси i-елемента масиву на машинному рівні формується таким чином:

& а^]= а + i * sizeof(int);

Справедливі також наступні співвідношення:

& a == a+0 == & a[0] - адреса а[0] - нульового елемента масиву;

а+2 == & а [2] - адреса а[2] - другого елементи масиву;

а+i == & a[i] - адреса a[i] - i-го елемента масиву;

*а==* (а+0) ==* (& а[0]) ==a[0] - значення 0-ого елемента масиву;

*(а + 2) == а[2] - значення а[2] - другого елементи масиву;

*(а + i) == а [i] - значення a[i] - i-го елемента масиву;

* а + 2 == а[0] + 2 - сума значень а[0] і 2.

Якщо р - покажчик на елементи такого ж типу, які і елементи масиву a та p=a, то а та р взаємозамінні; при цьому:

p == & a[0] == a + 0;

p+2 == & a[2] == a + 2;

*(p + 2) == (& a[2]) == a[2] == p[2];

*(p + i) == (& a[i]) == a[i] == p[i];

Для a та p еквівалентні всі звертання до елементів a у вигляді:

a[i], *(a+i), *(i+a), i[a], та p[i],

*(p+i), *(i+p), i[p]

9.3 Оголошення та звертання до багатовимірних масивів

У даному розділі розглянемо оголошення і зв'язок вказівників і елементів багатомірних масивів - що мають 2 та більше вимірів.

Багатомірний масив у мові Сі розглядається як сукупність масивів меншої розмірності. Наприклад, двовимірний масив - це сукупність одновимірних масивів (його рядків), тривимірний масив - це сукупність матриць, матриці - сукупності рядків, а рядок - сукупність елементів одновимірного масиву.

Елементи масивів розташовуються в ОП таким чином, що швидше змінюються самі праві індекси, тобто елементи одновимірного масиву розташовуються підряд, двовимірного - по рядках, тривимірного - по матрицях, а матриці - по рядках.

Для звертання до елементів багатомірного масиву можна використовувати нуль і більш індексів (індексних виразів):

ім'я-масиву [вираз1][вираз2]...

Наприклад, для звертання:

• до одновимірного масиву можна використовувати одно-індексний вираз (індекс);

• до двовимірного - 1 або 2 індексний вираз;

• до тривимірного - 1, 2 або 3 індексний вираз і т.д.

При звертанні до багатомірних масивів одержання значення елемента масиву можливо тільки після визначення адреси елемента масиву, тобто при повній кількості індексів. При цьому обчислюються індексні вираз зліва на право, і доступу до значення виконується після обчислення останнього індексного виразу.

Приклад оголошення двовимірного масиву значень типу int:

int а^]^];

Цей масив складається з m одновимірних масивів (рядків), у кожному з яких утримується n елементів (стовпців). При роботі з цим двовимірним масивом можна використовувати одно або 2 індексний вираз. Наприклад:

а[і] [j]- містить 2 індекси; використовується для звертання до елемента і-рядка, j-стовпця масиву; обчислюються індексні вирази, визначається адреса елемента масиву і вилучається його значення;

a[i] - містить 1 індекс; визначає адресу одновимірного масиву: адреса початку і-рядка масиву;

а - не містить індексу і визначає адресу масиву, його нульового елемента.

Таким чином, звертання до двовимірних масивів за допомогою імені і тільки одного індексу визначає покажчик на початок відповідного рядка масиву (адреса його нульового елемента). Наприклад:

а[0] == & a[0][0] == a+0*n*sizeof(int);

а[1] == & а[1][0] == a+1*n*sizeof(int);

a[i] == & a[i][0] == a+i*n*sizeof(int);

Приклад оголошення тривимірного масиву:

int a[k][m][n];

- k- кількість матриць з m рядками і n стовпцями;

- m - кількість рядків (одновимірних масивів) у матриці;

- n - кількість стовпців (елементів у рядку) матриці.

Цей масив складається з k матриць, кожна з яких складається з m одновимірних масивів (рядків) по n елементів (стовпців). При звертанні до цього масиву можна використовувати імена:

a[l][i][j] - містить 3 індекси; використовується для звертання до елемента l-матриці, і-рядка. j-стовпця масиву; обчислюються індексні вирази, визначається адреса елемента масиву і вилучається його значення;

a[k] [i] - визначає одновимірний масив - адреса початку і-рядка; k -матриці;

a[k] - визначає двовимірний масив - адреса початку k - матриці, тобто нульового елемента його нульового рядка;

а - адреса початку масиву, нульового елемента нульового рядка нульової матриці.

Наприклад:

int b[3][4][5];

int i, *ip, *ipp; i = b[0][0] [1];

де: ір, ірр - покажчики на значення типу int.

Після ip = b[2][0]; ip є покажчиком на елемент 0-рядка 0-го стовпця 2-й матриці масиву, тобто Ь[2][0][0].

Після ipp = b[2]; ipp адресує 0-й рядок 2-ї матриці масиву, тобто містить адреса b[2] [0] [0].

де:

ipp b[2] [0]; = b[2];

Звертання до елементів багатомірного масиву більш детально розглянемо на прикладі двовимірного масиву. Наприклад:

int а[3][4]; /* а - вказівник-константа */

int *р = а; /* р - вказівник-змінна */

Після цього покажчик р можна використовувати замість покажчика а для звертання до рядків або елементів масиву а у вигляді: ім'я покажчика і зсув елемента щодо адреси початку масиву а.

В ОП елементи масиву а розташовуються таким чином, що швидше всіх змінюється самий правий індекс, тобто в послідовності:

а[0][0] а[0][1] а[0][2] а[0][3] а[1][0]... а[2][2] а[2][3].

При цьому для звертання до масиву а можна використовувати імена:

& a == а == & а[0][0] == *а

адреса а[0] [0] - елемента 0-ого рядка 0-ого стовпця масиву а;

**а == *(& а[0][0]) == а[0][0]

значення елемента нульового рядка нульового стовпця масиву а;

a[i] == (а + i) == *(а + i) == & а^] [0]

адреса елемента і-рядка 0-стовпця;

*a[i] == **(а + i) == М& а^]) == a[i][0]

значення 0-го елемента і-рядка;

a[i][j] == *(*(а + i) + j) == *(a[i] + j) == a[i][j]

значення елемента і-рядка j-стовпця масиву а;

де:

(а + i) == *(а + i) == a[i]

адреса 0-го елемента і-рядка == & а[і][0];

(*(а + i) + j)

адреса j-елемента і-рядка = & а[і] [j];

*(*(а + i) + j)

значення j-елемента і-рядка = а[і] [j].

Значення адреси початку і-рядка (адреси 0-елемента і-рядка) на машинному рівні формується у виді:

a[i] = а + i == (a+i*n*sizeof(int)), де n - кількість значень в одному рядку.

Таким чином, адреса (і+1)-рядка відстоїть від і-рядка на (n*sizeof(int)) байтів, тобто на відстань одного рядка масиву.

Вираз а[і] [j] компілятор Сі переводить в еквівалентний вираз:

*(*а + і) + j). Зрозуміло, запис а[і] [j] більш традиційний у математиці і більш наочний.

До елементів двовимірного масиву можна звернутися і за допомогою скалярного покажчика на масив. Наприклад, після оголошення:

int a[m][n], *р = а;

* (p+i*n+j) - значення j - елемента i-рядка; де: n - кількість елементів у рядку;

i*n + j - змішання а[і] [j] - елемента відносно початку масиву а.

10 Масиви покажчиків

За допомогою масивів покажчиків можна формувати великі масиви і вільні масиви - колекції масивів будь-яких типів.

10.1 Робота з великими масивами

Розмір одного масиву даних повинний бути не більше 64 Кб. Але в реальних задачах можуть використовуватися масиви, що вимагають ОП, більшої ніж 64 Кб. Наприклад, масив даних типу float з 300 рядків і 200 стовпців потребує для розміщення 300 * 200 * 4 = 240000 байтів.

Для вирішення поставленої задачі можна використовувати масив покажчиків і динамічне виділення ОП для кожного рядка матриці. Рядок матриці не повинен перевищувати 64 Кб. У вищенаведеному прикладі ОП для рядка складає всього 800 байтів. Для виділення ОП з купи кожен рядок повинний мати покажчик. Для всіх рядків масиву треба оголосити масив покажчиків, по одному для кожного рядка. Потім кожному рядку масиву виділити ОП, привласнивши кожному елементу масиву покажчиків адресу початку розміщення рядка в ОП, і заповнити цей масив.

У запропонованому лістингу представлена програма для роботи з великим масивом цілих значень: з 300 рядків і 200 стовпців. Для розміщення він вимагає: 200 * 300 * 2 = 120000 байтів. При формуванні великого масиву використовується р - статичний масив вказівників

При виконанні програми перебираються /'-номери рядків масиву. Для кожного рядка за допомогою функції malloc() виконується запит ОП з купи і формується p[i] - значення вказівника на дані і-рядки. Потім перебираються і-номери рядків від 1 до 200. Для кожного рядка перебираються j-номери стовпчиків від 1 до 300. Для кожного і та j за допомогою генератора випадкових чисел формуються і виводяться *(р[і] + j) - значення елементів масиву. Після обробки масиву за допомогою функції ггее(р[і]) звільняється ОП виділена і-рядку масиву.

У наведеній нижче програмі використовуються звертання до Лц- -елементів масиву у вигляді: *(p[i]+j), де р[і] +j - адреса Лц-елемента масиву.

#include < conio.h>

#include < stdlib.h>

#include < stdio.h>

void main()

{

int *p[200], i, j; clrscr();

randomize();

for (i=0; i< 200; i++)

/* Запит ОП для рядків великого масиву: */

p[i] = (int*) malloc (300 * sizeof (int));

for (i = 0; i < 200; i++)

for (j = 0; j < 300; j++)

{

*(p[i] + j) = random(100);

printf(" %3d", *(p[i] + j));

if ((j + 1) % 20 == 0) printf (" \n");

}

/* Звільння ОП рядків великого масиву: */

for (i=0; i < 200; i++) free(p[i]);

}

У програмі використовується р - масив покажчиків.

10.2 Вільні масиви та покажчики

Термін „вільний" масив відносять до двовимірних масивів. Вони можуть бути будь-якого типу, у тому числі int, float, char і типу структура. Вільний масив - це двовимірний масив, у якому довжини його рядків можуть бути різними. Для роботи з вільними масивами використовуються масиви вказівників, що містять в собі кількість елементів, рівну кількості рядків вільного масиву. Кожен елемент масиву покажчиків містить адресу початку рядка значень вільного масиву. ОП виділяється для кожного рядка вільного масиву, наприклад за допомогою функції malloc(), і звільняється функцією free(). Для того щоб виконати функцію malloc(), треба визначити кількість елементів у рядку, наприклад із вводу користувача або яким-небудь іншим способом. У нульовому елементі кожного рядка вільного масиву зберігається число, рівне кількості елементів даного рядка Дані в кожен рядок можуть вводитися з файлу або з клавіатури в режимі діалогу. Приклад вільного масиву цілих чисел приведений на рис 1.12:

j і Кількість 0        
           
           
           

Рис. 12.Схема представлення вільного масиву цілих значень

У масиві на рис. 1.12 три рядки; у нульовому стовпці кожного рядка стоїть кількість елементів даного рядка. Далі - значення елементів матриці.

Приклад оголошення вільного масиву цілих, тобто статичного масиву покажчиків на дані типу int:

int *а[100];

Для масиву а приділяється ОП для 100 вказівників на значення цілого типу, по одному вказівнику на кожний з 100 рядків вільного масиву. Після визначення кількості елементів рядка для значень рядка повинна бути виділена ОП і сформоване значення вказівника в змінній a[i]. Цей вказівник посилається на область ОП, виділену для значень і-рядка матриці. Тільки після цього можна заносити в цю ОП значення елементів вільного масиву.

Реально ОП - це лінійна послідовність перенумерованих байтів. Елементи рядків вільного масиву можуть бути розташовані підряд або несуміжними відрізками ОП, виділеними для рядків.

11 Символьні рядки

11.1 Основні відомості про представлення рядків

Символьний рядок представляє собою набір з одного або більше символів.

Приклад: " Це рядок".

В мові Сі немає спеціального типу даних, який можна було б використовувати для опису рядків. Замість цього рядки представляються у вигляді масиву елементів типу char. Це означає, що символи рядка розташовуються в пам'яті в сусідніх комірках, по одному символу в комірці.

ц е   р я д о к \0

Рис. 1.13. Представлення рядка у вигляді масиву символів

Необхідно відмітити, що останнім елементом масиву є символ '\0'. Це нульовий символ (байт, кожний біт якого рівний нулю). У мові Сі він використовується для того, щоб визначати кінець рядка.

Примітка. Нульовий символ - це не цифра 0; він не виводиться на друк і в таблиці символів ASCII (див. додаток) має номер 0. Наявність нульового символу передбачає, що кількість комірок масиву повинна бути принаймні на одну більше, ніж число символів, які необхідно розміщувати в пам'яті. Наприклад, оголошення

char str[10];

передбачає, що рядок містить може містити максимум 9 символів. Основні методи ініціалізації символьних рядків.

char str1[]= " ABCdef";

char str2[] = {'A', 'B', 'C', 'd', 'e', 'f', 0};

char str3[100]; gets(str3);

char str4[100]; scanf(" %s", str4);

Усі константи-рядки в тексті програми, навіть ідентично записані, розміщуються за різними адресами в статичній пам'яті. З кожним рядком пов'язується сталий вказівник на його перший символ. Власне, рядок-константа є виразом типу „вказівник на char" зі сталим значенням - адресою першого символу.

Так, присвоювання p=" ABC" (p - вказівник на char) встановлює вказівник p на символ 'A'; значенням виразу *(" ABC" +1) є символ 'B'.

Елементи рядків доступні через вказівники на них, тому будь-який вираз типу „вказівник на char" можна вважати рядком.

Необхідно мати також на увазі те, що рядок вигляду „х" - не те ж саме, що символ 'x'. Перша відмінність: 'x' - об'єкт одного з основних типів даних мови Сі (char), в той час, як „х" - об'єкт похідного типу (масиву елементів типу char). Друга різниця: „х" насправді складається з двох символів - символу ' x' і нуль-символу.

X  
X \0

Рис. 14.Різниця між представленням 'x' та " x"

11.2 Функції роботи з рядками

1. Функції введення рядків.

Прочитати рядок із стандартного потоку введення можна за допомогою функції gets(). Вона отримує рядок із стандартного потоку введення. Функція читає символи до тих пір, поки їй не зустрінеться символ нового рядка '\n', який генерується натисканням клавіші ENTER. Функція зчитує всі символи до символу нового рядка, додаючи до них нульовий символ '\0'.

Синтаксис:

char *gets(char *buffer);

Як відомо, для читання рядків із стандартного потоку введення можна використовувати також функцію scanJQ з форматом %s. Основна відмінність між scanf() і gets() полягає у способі визначенні досягнення кінця рядка; функція scanf() призначена скоріше для читання слова, а не рядка. Функція scanf() має два варіанти використання. Для кожного з них рядок починається з першого не порожнього символу. Якщо використовувати %s, то рядок продовжується до (але не включаючи) наступного порожнього символу (пробіл, табуляція або новий рядок). Якщо визначити розмір поля як %10s, то функція scanf() не прочитає більше 10 символів або ж прочитає послідовність символів до будь-якого першого порожнього символу.

2. Функції виведення рядків.

Тепер розглянемо функції виведення рядків. Для виведення рядків можна використовувати функції puts() і printf().

Синтаксис функції puts():

int puts(char *string);

Ця функція виводить всі символи рядка string у стандартний потік виведення. Виведення завершується переходом на наступний рядок.

Різниця між функціями puts() і printf() полягає в тому, що функція printf() не виводить автоматично кожний рядок з нового рядка.

Стандартна бібліотека мови програмування Сі містить клас функцій для роботи з рядками, і всі вони починаються з літер str. Для того, щоб використовувати одну або декілька функції необхідно підключити файл string.h.

#include< string.h>

3. Визначення довжини рядка. Для визначення довжини рядка використовується функція strlen(). її синтаксис:

size_t strlen(const char *s);

Функція strlen() повертає довжину рядка s, при цьому завершуючий нульовий символ не враховується.

Приклад:

char *s= " Some string";

int len;

Наступний оператор встановить змінну len рівною довжині рядка, що адресується вказівником s:

len = strlen(s); /*

len == 11 */

4. Копіювання рядків. Оператор присвоювання для рядків не визначений. Тому, якщо si і s2 - символьні масиви, то неможливо скопіювати один рядок в інший наступним чином.

char s1[100];

char s2 [100];

s1 = s2; /* помилка */

Останній оператор (si=s2;) не скомпілюється.

Щоб скопіювати один рядок в інший необхідно викликати функцію копіювання рядків strcpy(). Для двох покажчиків si і s2 типу char * оператор

strcpy(s1, s2);

копіює символи, що адресуються вказівником s2 в пам' ять, що адресується вказівником si, включаючи завершуючі нулі.

Для копіювання рядків можна використовувати і функцію strncpy(), яка дозволяє обмежувати кількість символів, що копіюються.

strncpy(destantion, source, 10);

Наведений оператор скопіює 10 символів із рядка source в рядок destantion. Якщо символів в рядку source менше, ніж вказане число символів, що копіюються, то байти, що не використовуються встановлюються рівними нулю.

Примітка. Функції роботи з рядками, в імені яких міститься додаткова літера n мають додатковий числовий параметр, що певним чином обмежує кількість символів, з якими працюватиме функція.

5. Конкатенація рядків. Конкатенація двох рядків означає їх об' єднання, при цьому створюється новий, більш довгий рядок. Наприклад, при оголошенні рядка

char first[]= " Один ";

оператор

strcat(first, „два три чотири! ");

перетворить рядок first в рядок " Один два три чотири".

При викликанні функції strcat(s1, s2) потрібно впевнитися, що перший аргумент типу char * ініціалізований і має достатньо місця щоб зберегти результат. Якщо si адресує рядок, який вже записаний, а s2 адресує нульовий рядок, то оператор

strcat(s1, s2);

перезапише рядок si, викликавши при цьому серйозну помилку.

Функція strcat() повертає адресу рядка результату (що співпадає з її першим параметром), що дає можливість використати „каскад" декількох викликів функцій:

strcat(strcat(s1, s2), s3);

Цей оператор додає рядок, що адресує s2, і рядок, що адресує s3, до кінця рядка, що адресує si, що еквівалентно двом операторам:

strcat(s1, s2); strcat(s1, s3);

Повний список прототипів функцій роботи з рядками можна знайти в додатках на стор.

6. Порівняння рядків. Функція strcmp() призначена для порівняння двох рядків. Синтаксис функції:

int strcmp(const char *s1, const char*s2);

Функція strcmp() порівнює рядки si і s2 і повертає значення 0, якщо рядки рівні, тобто містять одне й те ж число однакових символів. При порівнянні рядків ми розуміємо їх порівняння в лексикографічному порядку, приблизно так, як наприклад, в словнику. У функції насправді здійснюється посимвольне порівняння рядків.

Кожний символ рядка і1 порівнюється з відповідним символом рядка і'2. Якщо і! лексикографічно більше то функція іігєтрі) повертає додатне значення, якщо менше, то - від'ємне.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.