Показивач (програмирање) — разлика између измена

Садржај обрисан Садржај додат
м r2.7.2) (Робот: додато ky:Көрсөткүч
Нема описа измене
Ред 1:
'''Показивач''' (понекад '''поинтер''', према {{јез-енг|pointer}}) представља [[промјенљивапроменљива (програмирање)|промјенљивупроменљиву]] специјалног типа у неким програмским језицима попут -{[[Програмски језик C|C]]}--а, -{[[C++]]}--а, [[Програмски језик Паскал|Паскала]] итд. Показивач има сврху да чува адресу меморијске локације неке друге промјенљивепроменљиве, [[константа|константе]] или показивача.
 
Основна својства показивача су:
* показивач може мијењатимењати вриједноствредност (тј. може показивати на разне локације за вријемевреме свог радног вијекавека)
* преко њега се може добити вриједноствредност промјенљивепроменљиве чију адресу чува (на коју ''показује'')
* преко њега се може мијењатимењати вриједноствредност промјенљивепроменљиве на коју показује
 
Када узимамо или мијењамомењамо вриједноствредност елемента на којег показивач показује, кажемо да га ''дереференцирамо''.
 
=== Типови показивача ===
У већини програмских језика који подржавају показиваче, показивачи се дијеле на типске и бестипске.
 
Уз '''типске показиваче''' се веже информација о типу промјенљивихпроменљивих на које ће дати показивач моћи показивати, односно чије ће адресе моћи чувати. Када декларишемо типски показивач, он до краја свог вијекавека има исти тип.
 
'''Бестипски показивачи''' немају одређен тип промјенљивепроменљиве на које ће моћи показивати, те могу показивати на све промјенљивепроменљиве подједнако. На уштрб тога, међутим, постоје одређена ограничења везана за ову врсту показивача:
* није дозвољено дереференцирање (видјетивидети поглавље „[[#Референцирање и дереференцирање|Референцирање и дереференцирање]]“, ниже)
* није дозвољена показивачка аритметика у језицима у којима је подржана за типске показиваче
 
Ред 20:
 
=== Референцирање и дереференцирање ===
Референцирање је процес у којем показивачу додјељујемододељујемо адресу одређене меморијске локације. Након тога кажемо да показивач ''показује'' на ту меморијску локацију.
 
Дереференцирање је процес у којем преко показивача који већ показује на неку меморијску локацију приступамо самој локацији, било ради читања њене вриједностивредности, било ради њеног мијењањамењања. Забрањено је, и најчешће узрокује прекид рада програма, дереференцирање показивача који:
* садржи вриједност која служи за означавање да показивач ни не показује ни на шта (најчешће вриједноствредност 0)
* показује на адресу која се не налази у меморијском простору процеса који извршава програм. То су најчешће показивачи са неиницијализованим вриједностимавредностима, који садрже адресу која се случајно затекла у простору заузетом за дати показивач.
 
=== Показивачка аритметика ===
Ред 31:
== Показивачи у програмском језику -{C}- ==
=== Типови показивача ===
У програмском језику -{[[C]]}-, показивачи се дијеледеле на типске и бестипске.
 
Поред показивача на ''обичне'' промјенљиве, постоје и показивачи на функције. Показивачи на функције се могу просљеђивати другим функцијама као аргументи и преко њих се могу позивати функције на које показују, што им је и примарна примјенапримена.
 
=== Декларација ===
Типски показивачи се декларишу на сљедећиследећи начин:
 
-{<source lang="c">
tip_pokazivaca * ime_pokazivaca;
</source>}-
Прво се наводи тип промјенљивепроменљиве на какве ће моћи показивати показивач, затим слиједиследи звјездицазвездица (<code>*</code>) и на крају име показивачке промјенљиве терминиране тачка-зарезом. За име показивача важе уобичајена правила именовања у п. ј. -{C}-.
 
Бестипски показивачи се декларишу на сљедећисл'едећи начин:
-{<source lang="c">
void * ime_pokazivaca;
Ред 55:
</source>}-
 
СлиједиСледи примјерпример за декларацију показивача на одређену функцију:
-{<source lang="c">
/* декларација произвољне функције */
Ред 68:
 
=== Референцирање ===
[[Слика:PointerExample.png|десно|мини|250п|Показивач показује на промјенљивупроменљиву тако што чува у себи њену адресу]]
Да бисмо додијелилидоделили одређену вриједноствредност показивачкој промјенљивојпроменљивој, користимо уобичајен оператор додјеледоделе:
 
-{<source lang="c">
Ред 77:
Пошто је сврха показивача да чувају адресу неког другог податка, морамо да:
 
* знамо унаприједунапред на којој тачно локацији се налази одговарајућа промјенљивапроменљива
* користимо оператор <code>&</code> да бисмо добили адресу дате промјенљивепроменљиве
* позовемо неку од уграђених функција које нам алоцирају нову меморију и враћају адресу алоцираног простора
 
Ред 86:
int * p1;
int a = 3;
p1 = 0x4CC7EC5C; /* изузетно је опасно и не препоручује се додјељиватидодељивати адресе директно */
p1 = &a; /* користимо оператор & за добијање адресе од a да бисмо је додијелилидоделили промјенљивојпроменљивој p1 */
p1 = malloc(sizeof(int) ); /* користимо функцију malloc да алоцирамо простор величине једне цјелобројнецелобројне промјенљивепроменљиве. */
/* malloc враћа адресу новоалоцираног простора */
</source>}-
Ред 97:
</source>}-
 
Код показивача на функције важи нешто другачија синтакса референцирања. Ако постоји функција <code>f</code>, и показивач <code>pf</code> декларисан тако да може на њу да показује, онда се референцирање врши једноставном додјеломдоделом промјенљивепроменљиве <code>f</code> промјенљивојпроменљивој <code>pf</code>, без коришћења обичних заграда, као код самог позива:
-{<source lang="c">
pf = f;
Ред 103:
 
=== Дереференцирање ===
Дереференцирање је процес у којем преко показивача који већ показује на неку меморијску локацију приступамо самој локацији. За ову операцију се користи оператор звјездице (<code>*</code>) испред имена показивачке промјенљивепроменљиве:
-{<source lang="c">
int * p1;
Ред 111:
</source>}-
 
Показивачи на функције се деференцирају на нешто другачији начин. Заправо, код њих се не може говорити о правом ''дереференцирању'', него радије само о позивању функције преко показивача. Наиме, оног тренутка када је нпр. показивач <code>pf</code> почео да показује на функцију <code>f</code>, он је постао алиас (лажно име, друго име) за функцију <code>f</code>, и користи се равноправно, на исти начин као и сама функција. СлиједиСледи примјер:
-{<source lang="c">
int saberi(int a, int b )
Ред 129:
</source>}-
 
Слиједи примјерпример просљеђивањапрослеђивања показивача на функцију другој функцији, која је позива преко тог показивача:
-{<source lang="c">
int saberi(int x, int y )
Ред 138:
int pozovi(int (*psaberi)(int, int), int x, int y )
{
return psaberi(x, y ); /* позивамо функцију преко показивача, и просљеђујемопрослеђујемо јој преостала два аргумента */
}
 
Ред 150:
 
=== Показивачка аритметика ===
У показивачкој аритметици програмског језика -{C}- не важе уобичајена правила при рачунању, тј. адресе које показивачи чувају неће се сабирати и одузимати као обични бројеви. Начин рачунања ће заправо зависити од тога који је тип промјенљивепроменљиве на коју показивачи показују, односно који је тип самих показивача. Сабирање и одузимање показивача са цијелимцелим бројем се врши по сљедећојследећој формули:
 
-{<source lang="c">
Ред 165:
Показивачи се могу и међусобно одузимати, када важе слична правила као код показивача и цијелог броја.
 
Важе сљедећаследећа рестриктивна правила везана за показиваче:
* показивачи се не смијусмеју множити нити дијелитиделити цијелимцелим бројем
* показивачи се не смијусмеју међусобно сабирати, множити нити дијелитиделити
 
=== ПримјенаПримена ===
У програмском језику -{C}-, најважније примјенепримене показивача су сљедећеследеће:
* пренос аргумената функцији ''по референци''
* имплементација низова
* имплементација динамичких структура
====Пренос аргумената функцији по референци====
У програмском језику -{C}-, [[функција (програмирање)|функције]] су такве да добијају увијекувек '''копије''' аргумената које јој просљеђујемопрослеђујемо, а никад оригиналне аргументе. На овај начин функција не може имати вањске ефекте јер све што добија од функције-позиваоца су копије оригиналних аргумената који остају нетакнути. Овакав начин преноса аргумената се популарно назива ''пренос аргумената по вриједности'', јер функција добија практично само вриједностивредности оригиналних аргумената али никад и директно њих саме.
 
Проблеми се јављају, међутим, у случајевима када желимо да функција ипак измијени одређене промјенљивепроменљиве које јој прослиједимопроследимо као аргументе. Та се потреба јавља нпр. када желимо функцију која ће имати више резултата, јер помоћу уобичајене кључне ријечиречи <code>return</code> она може вратити само један резултат. Тада користимо тзв. ''пренос аргумената по референци'' који уствари представља праксу да умјестоуместо оригиналног елемента прослиједимопроследимо његову адресу као аргумент. Ова адреса представља показивач на оригинални елемент, па иако се опет поштује правило да функција добија копију аргумента који јој се прослиједипроследи, овај пут она добија копију '''адресе''', што је ипак сасвим довољно да се оригиналном аргументу приђе директно, дереференцирањем.
 
Погледајмо сљедећиследећи примјерпример:
-{<source lang="c">
void povecaj(int c )
Ред 194:
}
</source>}-
Из претходног параграфа закључујемо да функција <code>povecaj</code> неће извршити своју улогу јер на мјестоместо промјенљиве <code>c</code> добија '''копију''' од <code>x</code>. На тај начин копија од <code>x</code> бива повећана за <code>1</code>, али не и оригинални елемент <code>x</code> у функцији <code>main</code>. Да бисмо поправили кôд, мораћемо извршити пар измјена:
* функција <code>povecaj</code> треба да се прилагоди тако да прихвата адресу цјелобројне промјенљиве, а не њу саму
* када добије адресу, дереференцираће је и повећати оно што се налази на тој адреси за <code>1</code>
Ред 213:
}
</source>}-
Користећи пренос аргумената по референци, програмер такође штеди на копирању аргумената при позивању једне функције из друге. Ако је аргумент који преносимо велик податак, нпр. [[структура]] од више елемената чија укупна величина превазилази величину показивачке промјенљивепроменљиве на датом систему, добро га је прослиједитипроследити по референци јер на тај начин копирамо мање података.
 
====Имплементација низова====
[[Слика:PointerArray.gif|десно|мини|Скица уређења низа у меморији]]
У програмском језику -{C}- не постоје низови као уграђени типови. Напротив, они се имплементирају преко осталих уграђених типова и показивача. Ово важи и за једнодимензионалне и вишедимензионалне низове (матрице, коцке итд.).
Сваки низ и -{C}--у се састоји од два физичка дијела - елемената који сачињавају низ и једног показивача који показује на почетак тог низа, тј. његов први елемент (видјетивидети слику десно). Дакле, када представимо низ на сљедећиследећи начин:
-{<source lang="c">
int a[10];
Ред 224:
тада настаје <code>10</code> елемената низа (који ниједан нема своје право име), и једна показивачка промјенљива <code>a</code> преко које се приступа свим елементима низа користећи синтаксу <code>a[0]</code> (први елемент, јер у -{C}--у индексирање креће од нуле, а не од јединице), <code>a[1]</code>, <code>a[2]</code>, итд.
 
Низови се у -{C}--у имплементирају преко показивача захваљујући специфичној показивачкој аритметици. Ако је <code>a</code> показивач на први елемент низа, онда <code>*a</code> представља сам елемент. Даље, пошто показивачка аритметика диктира да <code>a+1</code> буде показивач на сљедећи елемент низа, онда ће <code>*(a+1)</code> представљати сам други елемент низа. Идући даље, примјећујемопримећујемо да је <code>a[n]</code> еквивалентно са <code>*(a+n)</code>. То је управо оно што компајлер види када путем оператора индекса референцирамо одређени елемент низа.
 
Ако идемо даље, и представимо матрицу реалних бројева <code>М</code> димензија <code>m x n</code>, тада је стање у меморији сљедећеследеће:
* <code>M</code> представља показивач на први елемент низа од <code>m</code> показивача.
* сваки од <code>m</code> показивача показује на по први елемент низа од <code>n</code> реалних бројева (видјети слику десно).
[[Слика:MatrixPointer.png|десно|мини|Скица уређења вишедимензионалног низа (матрице) у меморији]]
 
Када користимо статичке декларације низова, оваква стања меморије се формирају аутоматски, али је такође могуће формирати их ручно, користећи обичну декларацију показивача и неку од уграђених -{C}--ових функција за алоцирање меморије. СлиједиСледи примјер:
-{<source lang="c">
char ** alocirajMatricu(int m, int n )
Ред 269:
== Показивачи у програмском језику Паскал ==
=== Типови показивача ===
У програмском језику Паскал, показивачи се дијеледеле на типске и бестипске.
 
Паскал не подржава показиваче на функције.
 
=== Декларација ===
Типски показивачи се декларишу на сљедећиследећи начин:
 
-{<source lang="pascal">
ime_pokazivaca: ^tip_pokazivaca;
</source>}-
Прво се наводи име показивачке промјенљивепроменљиве и двотачка (као код декларације осталих елементарних типова промјенљивихпроменљивих у Паскалу), затим слиједиследи капица (<code>^</code>) и на крају тип промјенљивепроменљиве на какве ће показивач моћи показивати. За име показивача важе уобичајена правила именовања у п. ј. Паскал.
 
Бестипски показивачи се декларишу на сљедећиследећи начин:
-{<source lang="c">
ime_pokazivaca: pointer;
</source>}-
 
УмјестоУместо типа и капице, овде се наводи кључна ријечреч <code>pointer</code>.
 
=== Референцирање ===
Референцирање је процес у којем показивачу додјељујемо адресу одређене меморијске локације. Да бисмо додијелилидоделили одређену вриједноствредност показивачкој промјенљивојпроменљивој, користимо један од следећа два начина:
* уобичајен оператор додјеледоделе
* команду <code>new</code> ради алокације меморије и истовремене додјеледоделе адресе те меморије показивачу
 
СлиједеСледе примјерипримери за објеобе ставке, редом:
 
-{<source lang="pascal">
Ред 300:
a: integer;
begin
p1 := @a; (* користимо оператор @ за добијање адресе од a да бисмо је додијелилидоделили промјенљивојпроменљивој p1 *)
new(p1) (* користимо команду new да алоцирамо простор величине једне цјелобројнецелобројне промјенљивепроменљиве (у складу са декларацијом показивача) *)
end.
</source>}-
 
У случајевима када се жели показати да показивач не чува никакву адресу, тада му додјељујемододељујемо нулу помоћу уграђене константе <code>nil</code>:
-{<source lang="pascal">
p := nil;
Ред 325:
Показивачка аритметика није подржана у стандардном Паскалу. Постоје поједине екстензије Паскала које дозвољавају показивачку аритметику, као што је -{[[GPC]]}- ([[Гну]]-ова екстензија Паскала). Показивачка аритметика Паскала је и тада једноставна и ограничена. Код ње '''важе''' уобичајена правила при рачунању, тј. адресе које показивачи чувају ће се сабирати и одузимати као обични бројеви, на тај начин додавајући и одузимајући тачно задати број бајтова од адресе коју показивач чува.
 
Погледајмо сљедећиследећи примјер:
-{<source lang="pascal">
var
Ред 338:
</source>}-
 
Команде <code>inc</code> и <code>dec</code> служе да повећају односно смање вриједноствредност показивача за онолико бајтова колико заузима промјенљивапроменљива на коју показује. Дакле, команде <code>inc</code> и <code>dec</code> се врше по следећим формулама:
 
-{<source lang="pascal">
Ред 345:
</source>}-
 
Ово обезбјеђујеобезбеђује нпр. итерирање по низу служећи се једним показивачем.
 
=== Примјена ===
У програмском језику Паскал, показивачи немају превелику примјенупримену, за разлику од програмског језика -{C}-. Паскал садржи низ као уграђени тип (користећи кључне ријечиречи <code>array ... of</code>), преношење аргумента функцији по референци је обезбијеђенообезбеђено синтаксом језика (користећи кључну ријеч <code>var</code>), те је једина примјена показивача у Паскалу најчешће само имплементација [[Динамичке структуре података|динамичких структура података]].
 
==Показивачи у осталим програмским језицима==
===Ада===
[[Ада (програмски језик)|Ада]] подржава само типске показиваче и конверзија између показивача различитих типова је подржана само у одређеном броју случајева. Сви показивачи се подразумијеваноподразумевано иницијализију на нулу (<code>null</code>), и дереференцирање показивача са том вриједношћувредношћу изазива [[изузетак (програмирање)|изузетак]]. У програмском језику Ада, показивачи се називају ''приступни типови'', и не подржавају показивачку аритметику, без коришћења екстензија (-{System.Storage_Elements}-)
 
===-{C++}-===
Ред 358:
 
===-{C#}-===
У програмском језику -{C#}-, показивачи су подржани али само под одређеним условима: било који блок кода који садржи показиваче мора бити означен као „несигуран“, користећи кључну ријечреч <code>unsafe</code>. Програми или модули који садрже такве блокове обично захтијевајузахтевају више нивое дозвола од стране корисника да би се покренули. Синтакса је јако слична синтакси програмског језика -{C}-.
 
===Фортран===