21. září bude sraz! Od 18.00 v restauraci Tradice v Praze u Anděla
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
Moc to nechápu, ale věřím, že zrovna já to zatím chápat nemusím. Děkuji
Keeehi
Profil
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
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
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
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
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
Takže to chápu dobře, děkuji
juriad
Profil
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
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
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
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
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
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
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
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
Keeehi:
Já vím, vždyť to tak dělám. Ale stejně nevím o mnoha funkcích...

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0