Autor | Zpráva | ||
---|---|---|---|
RastyAmateur Profil |
Dobrý večer,
včera jsem četl jeden tutorial, a byl tam příklad k funkcím. Bylo to o tom, jak něco přidat do pole pomocí funkce. Mám pocit, že to bylo nějak takto. function pridat(&$pole,$prvek){ $pole[] = $prvek; } vysvětlovali to tam tak, že "&" udělá, aby se obsah toho parametru měnil i mimo funkci. Je rozdíl, mezi tímto, a když ve funkci napíšu global $pole ?
Děkuji Přidávám přímo stránku, kde to psali, je to pod nadpisem "Předání proměnné parametrem" |
||
Alphard Profil |
RastyAmateur:
„Je rozdíl, mezi tímto, a když ve funkci napíšu global $pole ?“
Ano, v tomto případě funkce modifikuje proměnnou, kterou jí někdo explicitně předá, ne pořád tu jednu stejnou jako v případě globální proměnné. Navíc tato technika neskrývá závislosti. Sice bych to z důvodu horší přehlednosti neoznačil za best practice, ale je to docela běžná metoda (zvláště v jiných jazycích). |
||
RastyAmateur Profil |
#3 · Zasláno: 25. 12. 2015, 20:04:21
Moc to nechápu, ale věřím, že zrovna já to zatím chápat nemusím. Děkuji
|
||
Keeehi Profil |
#4 · Zasláno: 25. 12. 2015, 20:45:11
RastyAmateur:
Tak si tedy představ, že to máš s tím global $pole; No jo, jenže co když to máš přímo funkci v proměnné $myPreciousArray . To pak budeš tvořit toto?
$pole = $myPreciousArray; pridat(1); $myPreciousArray = $pole; |
||
RastyAmateur Profil |
#5 · Zasláno: 25. 12. 2015, 21:00:23
Jo takhle! Tak v tomto případě to chápu. Děkuji Keeehi. Stejně si myslím, že zrovná já to spíš nepoužiji, ale není na škodu to vědět. :)
|
||
CZechBoY Profil |
#6 · Zasláno: 25. 12. 2015, 21:41:11
Rozdíl je v tom, že buď si proměnnou vezmeš z globálních proměnných nebo pomocí parametru.
Pokud před parametr funkce uvedeš ampersand(&) tak do ní můžeš i zapisovat a změní se ta proměnná i v tom místě odkud voláš tu funkci. Např. // globalni promenna $globalni = 5; zmenNa3($globalni); echo $globalni; // ted je 3 function zmenNa3 (&$cislo) { $cislo = 3; } Potom můžeš teda magicky přistoupit k té globální proměnné, což hodně sníží čitelnost kodu. Např. $databaze = new Databaze(...); $clanky = nahodneClanky(10); function nahodneClanky ($pocet) { global $databaze; $sql = 'SELECT ...'; return $databaze->query($sql); } Lepší je použít třídy a globální proměnné schovat do properties. Např. class Clanky { private $databaze; function __construct ($databaze) { $this->databaze = $databaze; } function nahodneClanky ($pocet) { $sql = 'SELECT ...'; return $this->databaze->query($sql); } } |
||
Alphard Profil |
#7 · Zasláno: 25. 12. 2015, 22:00:09
CZechBoY [#6]:
To tvé vysvětlení se mi bohužel moc nezdá, myslíš to dobře, ale bojím se, že to začátečníka ještě víc zmate. „Pokud před parametr funkce uvedeš ampersand(&) tak do ní můžeš i zapisovat“ Referencování funguje obecněji, nejen u funkcí, ale to je jen pro doplnění. „Potom můžeš teda magicky přistoupit k té globální proměnné“ Pojmu magický bych se v souvislosti s globálností proměnných raději vyhnul. PHP už použivá terminologii Magic Methods v souvislosti s OOP. „Lepší je použít třídy a globální proměnné schovat do properties“ Pak už nejsou přeci globální. Navíc příklad pracující s OOP značně komplikuje původní dotaz. |
||
RastyAmateur Profil |
Zeptám se takto: mají tyto dva způsoby zápisu stejný výsledek?
Pro pořádek je napíšu $a = 3; function plus1(&$cislo){ return $cislo + 1;} function plus2(){ global $a; ........} Omlouvam se, ale píšu z mobilu, snad v tom neni chyba.. |
||
Keeehi Profil |
#9 · Zasláno: 26. 12. 2015, 13:02:06
Výsledek ano. Ovšem ta druhá vytváří závislost na tom, jak se proměnná jmenuje a to je špatně. V určitém kontextu by mělo být možné proměnnou přejmenovat a přitom nerozbít funkčnost kódu. Což druhá funkce neumožňuje.
|
||
RastyAmateur Profil |
#10 · Zasláno: 26. 12. 2015, 20:45:43
Takže to chápu dobře, děkuji
|
||
juriad Profil |
#11 · Zasláno: 27. 12. 2015, 11:02:23
Zkusím to ještě popsat trošku jinak - z pohledu teorie.
Při každém volání funkce se hodnoty (týká se to všeho kromě objektů*) předané parametrem kopírují. To znamená, že vevnitř funkce se pracuje s kopií. To má několik důsledků: 1) nemůžu nic rozbít. Když změním kopii, původní hodnota zůstane stejná. 2) kopírování něco stojí, takže se může zdát, že je to neefektivní. Tady přichází na pomoc trik, který PHP a další jazyky používají - Copy On Write. Dokud se z proměnné jen čte, používá se původní, a kopie se vytvoří až v okamžiku prvního zápisu, což může mít nějaké zajímavé důsledky pro výkonnost programu. ** Aby se tento mechanismus kopírování potlačil, tedy aby funkce mohla měnit proměnnou, která ji byla předána parametrem, je možné před ní napsat znak &. Toto se nazývá předání referencí namísto hodnotou. Pak se nic nekopíruje, ale použije se vždy (pro čtení i zápis) původní proměnná. Toto má zase důsledky: 1) můžeš měnit obsah původní proměnné. Proč to může být užitečné povím později. 2) PHP na toto není optimalizované, takže je to pomalejší. Zkus si $pole do sumAbs2 předat referencí. Uvidíš, že časy jsou pak stejné bez ohledu na vstupní pole, ale citelně pomalejší. Podobně pro sumAbs1, tam je to přimo pohroma (vždyť z proměnné jen čteme, takže předání referencí bylo vlastně zbytečné). Různé programovací jazyky jsou různě striktní k tomu, jak funkce komunikuje se zbytkem světa. Funkce vždy má nějaké parametry a návratovou hodnotu. Parametrů může být více, ale návratová hodnota je zpravidla jen jedna. Co když funkce chce vrátit víc hodnot? To je asi nejčastější důvod, proč lidé začinají uvažovat o tom, že by si nějakou hodnotu vrátili skrze měnitelný parametr. Některé jazyky přímo takové parametry nazývají čistě výstupní (out, příkladem je parametr $matches ve funkci preg_match), pak jim volající zvenku nedává hodnotu, naopak si ji vyzvedne. Existují také smíšené (inout) parametry, kterým volající předá nějakou hodnotu a pak si z nich vyzvedne jinou (nebo stejnou, příkladem je funkce sort, o ní později). Některé jazyky zakazují používání parametrů k tomuto účelu a předávání referencí neobsahují vůbec, takové pak zpravidla umožňují vrátit seznam proměnných. PHP pro toto má konstrukci list - funkce formálně vrátí pole, ale to si volající okamžitě rozbalí do seznamu proměnných. Toto doporučuji používat místo nějakých vystupních parametrů. Jak už zmínil Alphard, reference jsou obecnější koncept a lze je použít při snad každém přiřazení do proměnné v jakékoli podobě (přiřazení pomocí =, konstrukt foreach, parametr funkce, návratová hodnota funkce). Alespoň z počátku, když použiješ referenci, napiš si komentář, k čemu je na tom místě dobrá, a proč to nešlo řešit jinak. Občas se reference používají jako hack, jak zapsat něco úsporněji na úkor čitelnosti. Viz Diskuse JPW: syntaxe apostrofy, uvozovky u mysql. Silně doporučuji si promyslet, co to vlastně dělá a proč to funguje. A co by se stalo bez té reference. Prohlédni si funkci array_walk a vysvětli, proč je pole předáváno jako reference. Vysvětli si poznámku u parametru callback. Naimplementuj si vlastní funkci array_walk, která bude fungovat úplně stejně. Někeré funkce jsou cíleně navrženy, aby měnily své argumenty. Příkladem je už zmíněná array_walk, ale ještě markantnější je to u funkce sort. Sort je obecně dost zajímavý příklad, vem si obyčejný quicksort (vybrat náhodně jeden prvek pole, menší prvky přeházet na začátek, větší na konec a zavolat se rekurzivně na menší a větší prvky). Tu by to dokonce bez referencí bylo hodně nepohodlné naprogramovat. (Zkus si to s referencemi a bez nich.) A ohledně používání globálních proměnných, reference lze jimi do jistě míry simulovat, ale jakmile přijde na rekurzi, máš velký problém, protože potřebuješ použít to samé jméno globální proměnné znovu. (Zkus vymyslet jak to vyřešit třeba na příkladu toho sortu). Existují i jazyky, které zakazují jakoukoli změnu proměnné, do které už jednou bylo něco přiřazeno. Z toho důvodu v nich neexistuje například for cyklus. Všechno je řešeno jen pomocí rekurze. Chceš sečíst prvky pole? Tak to je přece: součet(prázdné pole) = 0; součet(pole) = první prvek + součet(zbytek pole). Kupodivu takové jazyky jsou použitelné a často mnohem příjemnější a elegantnější. Jeden takový jazyk vyžaduje, aby funkce byly závislé jen a pouze na svých parametrech a to do té míry, že nesmí ani jen tak něco vypsat na výstup, protože i samotná konzole musí být uvedena jako jeden z parametrů. Má to důsledky pro testování, protože máš zaručeno, že funkce se bude chovat naprosto stejně a nezávisle na svém okolí.
Závěr tedy je, že reference jsou často užitečné, jsou součástí jazyka a je nutné s nimi počítat. Nejedná se o žádnou magii, není se třeba jich bát, ale je potřeba počítat s tím, že mnohým lidem se jejich použití nemusí líbit ať už z důvodu čitelnosti kódu nebo zkrátka kvůli tomu, že je neznají. A ty, RastyAmateure, pokud chceš, můžeš si ty navržené úložky zkusit naprogramovat, a pak si je tu nechat zkontrolovat. * objekty se také někdy nazývají referenční datové typu. Objekty se vždy předávají referencí, takže pozor na to, jejich vnitřní stav lze měnit. Pokud se uvede ještě explicitně reference, jde o referenci na referenci na objekt. A půjde tedy nejen změnit vnitřní stav objektu, ale i samotný objekt, který proměnná referencuje. (Toto si přečti ještě jednou na závěr, snad to bude trochu jasnější.) <?php class X { public $i; function __construct($i) { $this->i = $i; } } # přidej referenci k parametru function zmenObjekt($x) { $x->i = 5; $x = new X(10); } $x = new X(1); zmenObjekt($x); echo $x->i; # vypíše 5 bez reference # vypíše 10 s referencí ** to ilustruje třeba tento skriptík, který počítá součet abolutních hodnot prvků v poli: <?php function sumAbs1($pole) { $s = 0; $c = count($pole); for ($i = 0; $i < $c; $i++) { if ($pole[$i] < 0) { $s -= $pole[$i]; } else { $s += $pole[$i]; } } return $s; } function sumAbs2($pole) { $s = 0; $c = count($pole); for ($i = 0; $i < $c; $i++) { if ($pole[$i] < 0) { $pole[$i] = -$pole[$i]; } $s += $pole[$i]; } return $s; } function timeit($pole) { $t1 = microtime(true); for ($i = 0; $i < 10; $i++) { sumAbs1($pole); } $t2 = microtime(true); for ($i = 0; $i < 10; $i++) { sumAbs2($pole); } $t3 = microtime(true); echo "read only: ", $t2 - $t1, "\n"; echo "with write: ", $t3 - $t2, "\n"; } $a = range(0, 100000); timeit($a); # read only: 0.14093899726868 # with write: 0.13014888763428 # ta -1 způsobí zápis do pole v sumAbs2, proto je to pomalejší $b = range(-1, 100000); timeit($b); # read only: 0.14374899864197 # with write: 0.26178503036499 |
||
RastyAmateur Profil |
#12 · Zasláno: 27. 12. 2015, 14:33:07
juriad:
V první řadě také děkuji za reakci. Určitě to dalo hodně práce toto vypsat, proto budu reagovat... "Zkus si $pole do sumAbs2 předat referencí" - to chápu. Jelikož PHP pro reference není optimalizované, bude to pomalejší. Chápu i to, že je to absolutně zbytečné, když v tom poli nic neměníme, jen z něj čteme... "Prohlédni si funkci array_walk a vysvětli, proč je pole předáváno jako reference. Vysvětli si poznámku u parametru callback. Naimplementuj si vlastní funkci array_walk, která bude fungovat úplně stejně." - pokud jsem si správně přeložil a správně pochopil definici z php.net, tak by tato funkce měla vzít $pole, a s každým prvkem vykonat nějakou funkci (callback). Cílem funkce je tedy asi upravit vše z pole. Kdyby se to nezměnilo i mimo funkci (nebylo to přes reference), ta funkce by asi ztratila význam. Kdybych měl udělat vlastní funkci, tak bych to udělal nějak takto: function array_walk_rasty(&$array,$callback){ foreach($array as $one){ $callback($one); # Nevím, jak z proměnné udělat funkci... } } Takhle se to zdá hrozně jednoduché, takže to bude určitě špatně. Musí tam být jistě nějaké zabezpečení, a to převedení proměnné na funkci také nebude jednoduché... "...protože potřebuješ použít to samé jméno globální proměnné znovu. (Zkus vymyslet jak to vyřešit třeba na příkladu toho sortu)." - tak asi bych byl nucen použít něco, co zde již napsal Keeehi (#4): $array = $jinyNazev; function nazev(){ global $array; } $jinyNazev = $array; Jistě pro to jde udělat i jiný, lehčí způsob, ale i toto by snad mělo fungovat. K té části s objekty - třídy a objekty teprve začínám, a už jsem s nimi teoreticky i skončil. Chtěl jsem jen vědět, co ty třídy jsou, k čemu se používají a jak to vlastně funguje. Ten script od tebe - nejsem si jist, zda-li jsem ho správně pochopil. To tu ovšem nechci řešit, jelikož s třídami se aktuálně nechci zabývat. Stačí mi jen ty základy, které vím z tutoriálu a míchat do nich ještě toto, to by na mě bylo aktuálně moc... Ještě jednou děkuji za reakci... |
||
juriad Profil |
#13 · Zasláno: 27. 12. 2015, 19:58:45
Ukázka toho array_walk:
<?php # chceme pole přetvořit tak, aby se každé písmeno daný-počet-krát zopakovalo $pole = array("a" => 4, "b" => 2, "c" => 3); # vytvoříme kopii, abychom měli $pole dostupné pro pozdější test $p = $pole; # reference je nutná, protože chceme měnit hodnotu přímo v poli a nikoli jen hodnotu lokální kopie v argumentu array_walk($p, function(&$v, $k) {$v = str_repeat($k, $v);}); var_dump($p); # reference je nutná, protože chceme, aby se všechny změny pole uvnitř funce projevily i venku function aw(&$array, $callback) { # reference je tu nutná, protože jinak by byly $key a $value jen kopie klíče a hodnoty v poli $array foreach ($array as $key => &$value) { # zde je reference nutná, protože chceme funkci call_user_func_array # předat pole, které obsahuje referenci na proměnnou $value a hodnotu proměnné $key # bez reference by se hodnota do pole jen zkopírovala # ještě existuje funkce call_user_func, ale ta neumí předávat reference call_user_func_array($callback, array(&$value, $key)); } } $p = $pole; aw($p, function(&$v, $k) {$v = str_repeat($k, $v);}); var_dump($p); Ukážu ještě to řazení: <?php # referenci potřebujeme, protože prohazuje prvky pole function swap(&$pole, $i, $j) { $tmp = $pole[$i]; $pole[$i] = $pole[$j]; $pole[$j] = $tmp; } #reference slouží jako inout parametr function mysort(&$pole, $od=NULL, $do=NULL) { # nastaví meze if ($od === NULL) $od = 0; if ($do === NULL) $do = count($pole); # je za koncem pole # máme seřadit maximálně jeden prvek if ($do - $od <= 1) { # ale takové pole už je seřazené return; } # vybere nějaký prvek, třeba ten první (v rozsahu) $pivot = $pole[$od]; # přesuneme menší prvky na začátek; $m je počet menších než pivot for ($i = $od, $m = 0; $i < $do; $i++) { # prvek je menší než pivot, přesuneme jej na m-tou pozici if ($pole[$i] < $pivot) { swap($pole, $i, $od + $m); $m++; } } # přesuneme pivoty; $p je počet stejných jako pivot for ($i = $od + $m, $p = 0; $i < $do; $i++) { # prvek je shodný s pivotem, přesuneme jej na m+p-tou pozici if ($pole[$i] == $pivot) { swap($pole, $i, $od + $m + $p); $p++; } } # a teď už nutně máme všechny prvky větší než pivot na konci pole # zavoláme se rekurzivně na menší prvky a větší prvky mysort($pole, $od, $od + $m); mysort($pole, $od + $m + $p, $do); } $pole = array_map(function() {return rand(0, 20);}, range(0, 20)); mysort($pole); var_dump($pole); Drobná úložka: <?php $x = [[1,2],[3,1],[2,[-3,4,4,[2],-2],[-1,4],0],2,[-1],[-5,-3,-2]]; # chci do každého pole přidat prvek takový, že součet v tom poli bude nejbližší násobek 3 # pro každé podpole platí to samé; každé podpole je bráno jako podíl po vydělení 3 # a chci to pole změnit na místě, nikoli vrátit nové function trinity(&$pole) { $soucet = 0; # reference, protože vnitřní pole budeme chtít modifikovat foreach ($pole as $key => &$value) { if (is_array($value)) { $soucet += trinity($value) / 3; } else { $soucet += $value; } } if ($soucet % 3 == 0) { # do nothing } elseif (($soucet + 1) % 3 == 0) { $pole[] = 1; $soucet++; } else { $pole[] = -1; $soucet--; } return $soucet; } trinity($x); var_dump($x); Proč to ukazuju? Zkus si představit tu obezličku s globální proměnnou. Ta ti tu nebude fungovat, protože by sis musel $pole na chvíli někam schovat (před 14. řadkem), nahradit jej za $value a po zavolání trinity jej opět obnovit. A to opravdu není pěkné: <?php $pole = [[1,2],[3,1],[2,[-3,4,4,[2],-2],[-1,4],0],2,[-1],[-5,-3,-2]]; # chci do každého pole přidat prvek takový, že součet v tom poli bude nejbližší násobek 3 # pro každé podpole platí to samé; každé podpole je bráno jako podíl po vydělení 3 # a chci to pole změnit na místě, nikoli vrátit nové function trinity() { global $pole; $soucet = 0; # musíme použít obyčejný cyklus for ($i = 0; $i < count($pole); $i++) { $value = $pole[$i]; if (is_array($value)) { $pole_zaloha = $pole; $pole = $value; $soucet += trinity() / 3; $value = $pole; $pole = $pole_zaloha; $pole[$i] = $value; } else { $soucet += $value; } } if ($soucet % 3 == 0) { # do nothing } elseif (($soucet + 1) % 3 == 0) { $pole[] = 1; $soucet++; } else { $pole[] = -1; $soucet--; } return $soucet; } trinity(); var_dump($pole); |
||
RastyAmateur Profil |
#14 · Zasláno: 27. 12. 2015, 20:40:35
Nemusel jsi to tu tak vypisovat... Ale děkuji a to obzvlášť za funkci call_user_func. nepochopil jsem vše, ale to s referencema ano, takže to účel splnilo. Akorát jsem nevěděl, že když je reference již v patametru funkce, že se i přes to musi referencovat ve foreach znovu.
|
||
Alphard Profil |
Malý dodatek k reálnému použití. Chápu, že se tady řeší referencování, ale zase opatrně s tím :-) Jeden z příkladů, který to odstartoval, je Diskuse JPW: syntaxe apostrofy, uvozovky u mysql
$data = array( "sloupec_1" => "hodnota 1", "sloupec_2" => "hodnota 2" ); array_walk($data, function(&$i,$k) {$i="`$k`=$i";}); $hodnota = implode($data, ', '); Z mého pohledu tady v PHP nemají být použity reference. Je škoda zahodit původní data, výkon tím určitě neušetříme, jen to komplikuje čitelnost. Hodila by se tady spíš funkce array_map, která vrací nové pole. Ta sice neumí callback funkci předat i klíč, ale to jde obejít jednoduchým workaroundem, který je mi teda milejší než použít reference. $hodnota = implode(array_map(function($k,$v) {return "`$k`=$v";}, array_keys($data), $data), ', '); |
||
RastyAmateur Profil |
#16 · Zasláno: 27. 12. 2015, 23:24:44
Obdivuji zásobu funkcí většiny lidí zde na foru
Já jich znám zpaměti asi tak 10 až 15 :D Alphard děkuji za postřeh. |
||
juriad Profil |
RastyAmateur:
php.net/manual/en/ref.array.php A věnuj hodinku jenom čtení a vymýšlení příkladů, ve kterých bys jednotlivé funkce použil. Stačí pak vědět, že taková funkce existuje. Až ji budeš potřebovat, najdeš ji během pár sekund. |
||
RastyAmateur Profil |
#18 · Zasláno: 28. 12. 2015, 15:39:03
juriad:
Asi bych řekl, že v PHP existuje přes 5500 funkcí. Některé s poli, čísly nebo řetězci. Obrázky, databázemi, ..... To je jedno. Děkuji za odkaz, určitě se na něj podívám. |
||
juriad Profil |
#19 · Zasláno: 28. 12. 2015, 17:04:22
RastyAmateur:
Já jich mám ve verzi 5.6.16 s několika málo rozšířeníma jen 1499, jak napoví výsledek funkce get_defined_functions. Ano i na toto je v PHP funkce. :-D |
||
RastyAmateur Profil |
juriad:
No to snad ne :D Ale někde jsem četl, že jich je přes 5500. Už si vzpomínám, četl jsem to na wikipedii v části "Výhody PHP" "Rozsáhlý soubor funkcí v základní knihovně PHP (přes pět a půl tisíce), další funkce v PECL." |
||
Keeehi Profil |
#21 · Zasláno: 28. 12. 2015, 18:06:08
Ono ani nejde o to ty funkce znát. Spíš je dobré jen vědět že existují. Konkrétní jméno, specifikaci, použití si vždy můžeš dohledat v dokumentaci. Je dobré hledat i funkce, o kterých ani nevím e existují. Protože je velmi pravděpodobné, že můj problém už někdo řešil a vytvořil na to už funkci.
|
||
RastyAmateur Profil |
#22 · Zasláno: 28. 12. 2015, 18:13:07
Keeehi:
Já vím, vždyť to tak dělám. Ale stejně nevím o mnoha funkcích... |
||
Časová prodleva: 8 let
|
0