Popularny post Gieneq Napisano Sierpień 30, 2021 Popularny post Udostępnij Napisano Sierpień 30, 2021 (edytowany) Udało Ci się napisać pierwsze programy z wykorzystaniem tablic. Ta część kursu jest bardziej teoretyczna, ale pozwoli Ci zapoznać się z organizacją pamięci w mikrokontrolerze ATmega328P. Dowiesz się o różnych typach zmiennych i gdzie są zapisywane. Przyda się to w nabraniu świadomości jak działa tablica. Spis treści #1 – wstęp i praktyka #2 – organizacja pamięci (czytasz ten artykuł) #3 – tablice w pamięci #4 – tablica jest wskaźnikiem #5 – znaki, cstring #6 – argumenty funkcji #7 – przykład #8 – tablice wielowymiarowe #9 – tablice dynamiczne #10 – zakończenie Język Arduino? Zanim przejdę do sedna sprawy zadam szybkie pytanie: W jakim języku programuje się Arduino? Nie jest to takie oczywiste, bo często spotyka się zapis C/C++ lub informację "dialekt C++". Są to poprawne informacje, ale aby być bardzie szczegółowym, wystarczy sprawdzić co znajduje się w pliku związanym z kompilacją. Zakładając, że używamy Arduino z mikrokontrolerem AVR ATmega328P przechodzimy do katalogu np. C:\Program Files (x86)\Arduino\hardware\arduino\avr i tam szukamy pliku związanego z biblioteką płytek pobranej w menadżerze płytek. Wewnątrz pliku platform.txt znajdziemy blok okomentowany jako domyślne ustawienia, gdzie jedna z linii informuje nas o użyciu C++11: compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto Podobną informację znajdziemy w logu kompilacji z Arduino IDE. Jeszcze jednym ciekawym sposobem jest wypisanie predefiniowanej nazwy preprocesora __cplusplus identyfikującej standerd C++. Wypisanie liczby 201103 będzie oznaczać C++11: Serial.println(__cplusplus); Wiedząc to możemy być bardzie świadomi co nam wolno, a czego nie Przykładowo, niektórzy zwolennicy czystego C będą na pewno wystrzegać się klas, wydedukowanego typu danych auto, wyrażeń lambda i innych zdobyczy "zinkrementowanego C", na których bazuje część bibliotek Arduino. Na szczęście tablice występują w C więc nie jest to temat dyskusyjny, ale w ramach kursu skorzystamy z innych dobrodziejstw C++11. Szczegóły dotyczące języka i procesu kompilacji dostępne są w osobnej serii artykułów. Optymalizacja Domyślnie w Arduino IDE włączona jest optymalizacja rozmiaru pliku wynikowego -Os. Jest to potrzebne dla mikrokontrolerów o ograniczonych zasobach (ATmega328P posiada tylko 2 kB RAM). Może to jednak powodować problemy zwłaszcza, gdy eksperymentujemy z pamięciĄ, więc tymczasowo polecam wyłączyć optymalizację podmieniając w pliku platform.txt -Os na -O0: Później warto jednak do niej wrócić, optymalizacja rozmiaru -Os może zmniejszyć kod programu nawet o ponad 80%! Organizacja pamięci operacyjnej Aby lepiej zrozumieć dalszą treść, niezbędna jest podstawowa wiedza na temat organizacji pamięci operacyjnej RAM. Jak wiemy mikrokontroler ATmega328P posiada 2 kB pamięci SRAM. Przeglądając 18 stronę dokumentacji znajdziemy diagram: wynika z niego, że pamięć operacyjna zaczyna się w adresie 0x0100, a kończy w adresie 0x08FF. Oznacza to, że mamy do dyspozycji 2048 bajtów pamięci, czyli tyle ile zadeklarował producent: 2 kB (albo bardziej jednoznacznie 2 KiB). Pamięć ta jest dalej dzielona na obszary przeznaczone do konkretnych zadań: Jak możemy zauważyć zaznaczone są tu 4 istotne obszary: sekcja .data, sekcja .bss, sterta (ang. heap), stos (ang. stack) . Dygresja o wskaźnikach Niestety bez tego nie przejdziemy dalej. Wskaźniki straszą początkujących, ale kiedyś trzeba je polubić, bo bez nich za daleko się nie zajedzie. Na szczęście dla potrzeb poznania adresów zmiennych nie będziemy potrzebować zbyt wiele. Wskaźnik na zmienną służy do wskazywania... czyli pokazywania, gdzie w pamięci znajduje się zmienna. Informacja gdzie jest dana zmienna zawarta jest w adresie (typie danych wskaźnika). To wszystko? W zasadzie tak Działa to na podobnej zasadzie co pocztowe dane adresowe. Przykładowo, zdefiniujmy zmienną typu int o nazwie velocity i przypiszmy jej wartość 12: int velocity = 12; // alternatywny zapis: int velocity {12}; To co się wydarzyło to zarezerwowanie w pamięci obszaru, gdzie zapisana została wartość 12. Jak duży to obszar? Z kursu Arduino dowiedziałeś się, że różne typy danych charakteryzują się różną wielkością (liczoną w bajtach) i przeznaczeniem. Zmienne typu int na tym mikrokontrolerze zajmują w pamięci 2 B i można to sprawdzić operatorem sizeof(). Serial.println(sizeof(velocity)); W tym przypadku użyliśmy operatora na zmiennej, ale można też użyć na samym typie danych: Serial.print("Size of char: "); Serial.println(sizeof(char)); Serial.print("Size of int: "); Serial.println(sizeof(int)); Serial.print("Size of long: "); Serial.println(sizeof(long)); Serial.print("Size of float: "); Serial.println(sizeof(float)); W wyniku otrzymamy: Size of char: 1 Size of int: 2 Size of long: 4 Size of float: 4 Dobrze wiemy jak sprawdzić ile pamięci zajmują zmienne, ale gdzie te wskaźniki? Śpieszę z wyjaśnieniem. Przypatrzmy się jeszcze raz zmiennej velocity. Wiemy, że zajmuje 2 B pamięci. Aby uzyskać adres tego obszaru pamięci użyjmy jednoargumentowego operatora & udostępniającego adres: int *address_velocity = &velocity; A co to za stwór po lewej stronie? Tak wygląda typ danych skojarzony ze wskaźnikiem na zmienną int. Choć gwiazdkę stawia się przy nazwie zmiennej to typ danych to int * – tak się przyjęło (choć możesz spotkać też inne zapisy – ze spacją lub bez niej). Przykładowo w środowisku Visual Studio Code dostępne są do wyboru wszystkie sposoby zapisu: Dla innych typów danych będą inne skojarzone z nimi wskaźniki – dla long będzie long *, dla char będzie char * itd. Skoro jest to tylko adres, to dlaczego istnieją osobne typy danych, a nie jeden dla adresów? Dzieje się tak, gdyż wskaźnik niesie też dodatkową informację, np. o tym jak wiele miejsca zajmuje zmienna, co przyda się, gdy wrócimy do tematu tablic. Istnieje uniwersalny wskaźnik, który wskazuje obszar pamięci, ale nie posiada informacji o rozmiarze danych: void *. Aby poznać adres wskaźnika możemy zrzutować go na int i wyświetlić w monitorze portu szeregowego: int velocity = 12; int *address_velocity = &velocity; void setup() { Serial.begin(9600); Serial.print(F("0x")); Serial.println((int)address_velocity, HEX); } Ważne jest aby wypisać liczbę w systemie heksadecymalnym (HEX). W wyniku otrzymamy adres zmiennej: 0x100 Dlaczego akurat taki adres, o tym za chwilę. Na razie zakończę temat wskaźników jeszcze jedną informacją – dysponując wskaźnikiem można uzyskać wartość, na którą wskazuje (obszar pamięci pod adresem wskaźnika). Służy do tego jednoargumentowy operator *, który używamy następująco: Serial.println(*address_velocity); W wyniku z powrotem otrzymamy wartość 12. Kapitanie... do brzegu – obszary pamięci Tak, tak... już już wracam do tematu Wiemy jak sprawdzić adres zmiennej w pamięci i wiemy, że są jakieś obszary pamięci: .data, .bss, heap i stack. Co dokładnie oznaczają? .data – ten obszar pamięci jest na samym początku SRAM i zaczyna się od adresu 0x100 (adres ten zdążyliśmy już zauważyć w zmiennej predkosc). Sekcja .data służy do przechowywania danych statycznych to znaczy takich, które istnieją przez cały czas działania programu. Do tych zmiennych należą: zmienne globalne (te które zainicjalizowaliśmy "na górze" kodu) ale mające wartość inną niż zero (zero jest domyślnie wartością zmiennych niezainicjowanych... tak wiem, pokręcone), zmienne lokalne z słowem kluczowym static, czyli lokalne zmienne statyczne (też w wywołaniach funkcji) z przypisaną niezerową wartością. .bss – kolejny obszar służący do przechowywania danych statycznych ale niezainicjowanych lub posiadających wartość 0. Do tych zmiennych należą: zmienne globalne niezainicjowane lub o wartości 0, statyczne zmienne lokalne niezainicjowane lub o wartości 0. Uwaga! Zmienne statyczne to nie zmienne stałe, ich wartość może się zmieniać. Przykłady zmiennych statycznych: int velocity = 5; // globalna zmienna, zainicjowana, niezerowa – .data int momentum; // globalna zmienna, niezainicjowana, domyślnie wartość 0 – .bss void setup() { // wewnątrz funkcji – zmienne lokalne static int acceleration = 2; // statyczna zmienna lokalna, zainicjowana, niezerowa – .data static int steps_numer; // statyczna zmienna lokalna, niezainicjowana, domyślna wartość 0 – .bss steps_numer = 5; // niezerowa wartość, ale dalej w .bss } Zmienne statyczne mogą się przydać w sytuacji, gdy projektujemy funkcję, która powinna mieć zapamiętany jakiś swój wewnętrzny stan. Zamiast tworzyć zmienną globalną, która byłaby widoczna w wielu miejscach w kodzie i mogłaby być myląca, to można utworzyć lokalną zmienną statyczną, której wartość nie zostanie utracona nawet po wyjściu z funkcji, ale będzie widoczna tylko wewnątrz tej funkcji. Takie zachowanie zmiennej może być zaskakujące, ale dzieje się tak, gdyż adres obszaru pamięci jest na stałe zarezerwowany. Jeżeli utworzymy zmienną jedynie przez definicję, to jej początkowa wartość będzie wynosić 0 lecz przy kolejnych wywołaniach wartość będzie aktualizowana. Przykładowo następujący kod zawiera statyczną zmienną lokalną i intuicja podpowiada, że pewnie jej wartość zostanie utracona po wyjściu z funkcji. void use_counter() { static int count; Serial.print(F("Function used: ")); Serial.print(count++); Serial.println(F(" times.")); } void loop() { use_counter(); delay(1000); } Efekt działania jednak wskazuje, że zmienna nie jest wymazana: Function used: 0 times. Function used: 1 times. Function used: 2 times. Function used: 3 times. Function used: 4 times. Zmienne i funkcje statyczne na pewno spotkasz nieraz w plikach bibliotecznych, gdyż użycie ich poprawia hermetyzację danych (widoczność tylko w ograniczonym zakresie) i wymusza pierwszeństwo przy kompilacji. Nie można tego mylić z metodami statycznymi klas, gdzie static oznacza przynależność do klasy, a nie poszczególnych instancji. stack – specjalnie zmieniłem kolejność, bo stos jest bardziej intuicyjny. Jest to obszar pamięci dynamicznej (czyli takiej która jest zapełniania i zwalniana w czasie działania programu) zaczynający się od końca RAM i rosnący w stronę początku pamięci. Zapełnianie stosu następuje automatycznie gdy: wywoływane są funkcje, tworzone są zmienne lokalne. Dane są dopisywane jedna po drugiej, stąd są one ciasno upchane (nie posiada luk). Posługując się przykładem z ilustracji jest to podobne do poukładanej ścianki narzędziowej, gdzie pobieramy i odkładamy narzędzia wiszące obok siebie. Zwalnianie stosu następuje automatycznie wraz z zakończeniem wywołania funkcji. Przykład zmiennej zapisanej na stosie: void setup() { int local_variable = 5; } Uwaga! Zmienne lokalne zapisane na stosie domyślnie mają wartość losową. Dzieje się dlatego, że przy większej liczbie danych (np. w pętli) ustawianie każdorazowo wartości na 0 pogarszałoby czas wykonania. Przykładowo: int local_variable; Serial.print(F("0x")); Serial.print((int)&local_variable, HEX); Serial.print(F(", value: ")); Serial.println(local_variable); Jak widać adres zmiennej jest bardzo blisko końca: 0x8FF i wskazywana wartość jest zupełnie przypadkowa: 0x8F6, value: -29696 heap – sterta to również obszar pamięci dynamicznej ale przeznaczony do przechowywania danych, gdy tworzone są nowe obiekty (alokowana pamięć) w czasie działania programu. Dokonuje się tego: używając operatora new, który przydziela pamięć do obiektu lub tablicy, alokując blok pamięci funkcją malloc(). Różnica polega na tym, że obiekty te tworzy programista – dodawanie i ściąganie obiektów ze starty nie jest zautomatyzowane. Sterta jest bardziej chaotyczna, może posiadać luki (można zwolnić dowolny blok pamięci pozostawiając dziurę). Choć możliwa jest większa swoboda (np. rozszerzenie obszaru pamięci w tablicy dynamicznej) to może to wiązać się, z przeniesieniem bloku pamięci. W ogólności dostęp jest wolniejszy od obiektów ze stosu. Posługując się przykładem stołu warsztatowego, jest to blat na którym sporo się dzieje – w różnych miejscach dokładane i przenoszone są materiały i narzędzia. Więcej na temat zagrożeń używania pamięci dynamicznej w części kurs o tablicach znaków. Dobrym podsumowaniem będzie analiza adresów zmiennych z przykładu: int zm_glob_niezer = 12; int zm_glob_zer = 0; int zm_glob_niezainc; static int zm_glob_niezer_static = 18; static int zm_glob_zer_static = 0; void setup() { int zm_lok_niezer = 33; int zm_lok_niezain; static int zm_lok_zer_stat = 6; static int zm_lok_niezain_stat; String *napis = new String("3333"); Serial.begin(9600); Serial.print(F("0x")); Serial.print((int)&zm_glob_niezer, HEX); Serial.print(F(" - zm_glob_niezer, ")); Serial.println(zm_glob_niezer); Serial.print(F("0x")); Serial.print((int)&zm_glob_zer, HEX); Serial.print(F(" - zm_glob_zer, ")); Serial.println(zm_glob_zer); Serial.print(F("0x")); Serial.print((int)&zm_glob_niezainc, HEX); Serial.print(F(" - zm_glob_niezainc, ")); Serial.println(zm_glob_niezainc); Serial.print(F("0x")); Serial.print((int)&zm_glob_niezer_static, HEX); Serial.print(F(" - zm_glob_niezer_static, ")); Serial.println(zm_glob_niezer_static); Serial.print(F("0x")); Serial.print((int)&zm_glob_zer_static, HEX); Serial.print(F(" - zm_glob_zer_static, ")); Serial.println(zm_glob_zer_static); Serial.print(F("0x")); Serial.print((int)&zm_lok_niezer, HEX); Serial.print(F(" - zm_lok_niezer, ")); Serial.println(zm_lok_niezer); Serial.print(F("0x")); Serial.print((int)&zm_lok_niezain, HEX); Serial.print(F(" - zm_lok_niezain, ")); Serial.println(zm_lok_niezain); Serial.print(F("0x")); Serial.print((int)&zm_lok_zer_stat, HEX); Serial.print(F(" - zm_lok_zer_stat, ")); Serial.println(zm_lok_zer_stat); Serial.print(F("0x")); Serial.print((int)&zm_lok_niezain_stat, HEX); Serial.print(F(" - zm_lok_niezain_stat, ")); Serial.println(zm_lok_niezain_stat); Serial.print(F("0x")); Serial.print((int)napis, HEX); Serial.print(F(" - napis, ")); Serial.println(*napis); } void loop() { } W wyniku otrzymamy coś takiego: 0x100 - zm_glob_niezer, 12 0x1A4 - zm_glob_zer, 0 0x1A6 - zm_glob_niezainc, 0 0x102 - zm_glob_niezer_static, 18 0x1A8 - zm_glob_zer_static, 0 0x8F2 - zm_lok_niezer, 33 0x8F4 - zm_lok_niezain, -26879 0x104 - zm_lok_zer_stat, 6 0x1AA - zm_lok_niezain_stat, 0 0x25B - napis, 3333 Widzimy wyraźnie adresy zainicjowanych danych statycznych (wartości bliskie 0x100). Nieco większe wartości przyjmują dane z obszaru niezainicjowanych danych statycznych (.bss). Następnie widoczny jest spory skok adresowania w przypadku zmiennych lokalnych umieszczonych na stosie (w tym tych niezainicjowanych), gdzie została odczytana losowa wartość. Na sam koniec została zaalokowana pamięć w obszarze sterty przy pomocy operatora new, które mają adresy większe od tych z .bss. Do tego czym jest operator new jeszcze kiedyś wrócimy. Ląd na horyzoncie – gdzie te tablice? Dygresja od dygresji ale bez tego nie da się omówić tematu Do czego to wszystko ma się przydać? W kolejnej części zostanie omówiony sposób tworzenia tablic (statycznych), ale tym razem ze świadomością co się dokładnie dzieje, a gdy dojdziemy do tematu alokacji dynamicznej, tworzenia obiektów to zrozumiemy dlaczego ta sterta jest trochę niebezpieczna. Oczywiście, bez tego da się żyć, dlatego też pierwsza część jest typowo praktyczna. Sam przeżyłem bez znajomości tego tematu kilka lat, napisałem w tym czasie wiele programów, brałem udział nawet w konkurach z algorytmiki i coś wygrałem! Ale apetyt rośnie w miarę jedzenia i warto kiedyś pochylić się nad tym tematem Edytowano Październik 29, 2021 przez Gieneq 4
Gieneq Wrzesień 13, 2021 Autor tematu Udostępnij Wrzesień 13, 2021 Kolejna część kursu, tym razem postanowiłem zagłębić się w temat pamięci SRAM. Myślę, że w tym temacie można jeszcze sporo napisać, ale ten stopień szczegółowości powinien umożliwić nabudowanie świadomości jak te 2 KiB pamięci są używane, co się właściwie dzieje ze zmiennymi, do czego jest specyfikator static. 1
SOYER Luty 20, 2022 Udostępnij Luty 20, 2022 (edytowany) Też dobry odcinek, muszę jeszcze raz przeczytać. Dalej nie wiem po kiego grzyba te wskaźniki są potrzebne, aż poczytałem jeszcze to ale dalej nie kapuję na co mi znać adres w pamięci jakiejś zmiennej. Po co się do niej do niej odwoływać przez wskaźnik, a nie bezpośrednio. Dlaczego mam pytać Piotrka co Krzysiu ma w kieszeni skoro Krzysiu stoi tuż obok i mogę jego zapytać. Edytowano Luty 20, 2022 przez SOYER
ethanak Luty 20, 2022 Udostępnij Luty 20, 2022 27 minut temu, SOYER napisał: na co mi znać adres w pamięci jakiejś zmiennej. int divide(int a, int b, int *remainder) { if (remainder) *remainder = a % b; return a/b; } // i gdzieś w kodzie int wynik1, wynik2, reszta1, reszta2; wynik1=divide(dzielna1, dzielnik1, &reszta1); wynik2=divide(dzielna2, dzielnik2, &reszta2); Na przykład po to. 1
farmaceuta Luty 20, 2022 Udostępnij Luty 20, 2022 40 minut temu, SOYER napisał: Dalej nie wiem po kiego grzyba te wskaźniki są potrzebne, aż poczytałem jeszcze to ale dalej nie kapuję na co mi znać adres w pamięci jakiejś zmiennej. Ja rzadko uzywam, ale juz widze ze wskaznik to takie cos co bardzo potrafi zycie uproscic Np. kopiujesz blok pamieci do innego bloku, ale chcesz tylko przekopiowac polowe...i jak to zrobic? Albo kombinowac z for, albo uzyc wskaznika i przesunac sie na odpowiedni adres...kilka szlaczkow i gotowe 44 minuty temu, SOYER napisał: Po co się do niej do niej odwoływać przez wskaźnik, a nie bezpośrednio. A co jesli masz np. 5 zmiennych static w funkcji, i z tej funkcji wchodzisz w inna funkcje w ktorej chcesz zmienic wartosci tych powyzej? Zmienne nie sa juz widziane globalnie, wiec bez wskaznika juz nie dobierzesz sie do oryginalow.. (mozna referencje, ale wskaznik daje znacznie wiecej mozliwosci) 1
farmaceuta Luty 20, 2022 Udostępnij Luty 20, 2022 Jako inny przyklad...to masz 10 roznych struktur, ale chcesz wszystkie wysylac poprzez jedna funkcje...to mozesz sobie stworzyc uniwersalna funkcje gdzie pierwszy argument to wskaznik na adres konkretnej struktury(rzutowany na bajt np) a drugi to rozmiar konkretnej struktury...(mozna tez chyba ze wskaznikiem typu void kombinowac, ale ja sie nie znam)...bardzo duzo funkcji bibliotecznych dziala tylko na wskaznikach/adresach...tak ze w zaawansowanym programowaniu(ktorego nigdy nie doswiadcze) bez powyzszego ni hu hu...
ethanak Luty 20, 2022 Udostępnij Luty 20, 2022 No to może dodatkowo dwie funkcje z biblioteki: qsort i bsearch. @farmaceuta masz przy okazji swój void * 1
SOYER Luty 20, 2022 Udostępnij Luty 20, 2022 58 minut temu, ethanak napisał: int divide(int a, int b, int *remainder) { if (remainder) *remainder = a % b; return a/b; } // i gdzieś w kodzie int wynik1, wynik2, reszta1, reszta2; wynik1=divide(dzielna1, dzielnik1, &reszta1); wynik2=divide(dzielna2, dzielnik2, &reszta2); Na przykład po to. Nie łapię, robimy funkcję z 3 argumentami, tym trzecim jest wskaźnik(czyli adres) zmiennej reminder w ciele tej funkcji sprawdzamy czy zawartość zmiennej reminder jest różna od zera, jeśli tak to resztę z dzielenia zapisujemy jako wskaźnik do zmiennej reminder(czyli ta reszta jest od teraz adresem tej zmiennej) i teraz co to ma wspólnego z tym: wynik1=divide(dzielna1, dzielnik1, &reszta1); do zmiennej wynik1 zapisujemy to co zwraca funkcja divide(a zwraca int-a z dzielenia a/b), jednak do tej funkcji przekazujemy, a, b i ??adres zmiennej reszta1?? Dobrze przeczytałem kod? Jednak nie widzę nadal zależności, co i po co?
farmaceuta Luty 20, 2022 Udostępnij Luty 20, 2022 (edytowany) Zwracasz wynik dzielenia tradycyjnie retur'nem i jednoczesnie reszte z dzielenia poprzez wskaznik... Nie adresem...uzycie gwiazdki wyluskuje wartosc spod tego wskaznika a nie adres...gdyby nie bylo gwiazdki to twoja reszta byla by nowym adresem...ogolnie katastrofalne skutki Edytowano Luty 20, 2022 przez farmaceuta
ethanak Luty 20, 2022 Udostępnij Luty 20, 2022 (edytowany) @SOYER Żle. Porównaj: wynik1 = divide(dzielna1, dzielnik1, &reszta1); wynik2 = divide(dzielna2, dzielnik2, NULL); 10 minut temu, SOYER napisał: w ciele tej funkcji sprawdzamy czy zawartość zmiennej reminder jest różna od zera Nie. Sprawdzamy, czy wskaźnik jest różny od zera, i jeśli tak to pod wskazany adres wstawiamy resztę. Edytowano Luty 20, 2022 przez ethanak
farmaceuta Luty 20, 2022 Udostępnij Luty 20, 2022 void fun1() { static byte q = 9; fun2(&q); //pobiera adres } void fun2(byte * s) { //tworzy wskaznik i przypisuje adres "q" *s++; // zwieksza zawartosc "q" o 1 } takie proste cos np. pokazuje ze wskaznik sie przydaje...wyobraz sobie ze masz 10 takich zmiennych, albo strukture, ktore musza byc statyczne w funkcji z jakichs powodow...wtedy juz bylby problem bez wskaznika..
SOYER Luty 20, 2022 Udostępnij Luty 20, 2022 @ethanakZałapałem z tego szkicu tyle, że *reminder jest wskaźnikiem do zmiennych( reszta1, reszta2) zależnie od tego co liczymy. Wynik to int z dzielenia, a resztę z tego dzielenia przechowujemy w kolejnej zmiennej. Więc ten przykład na to jak to zrobić by zmienne reszta nie były globalne, czy inaczej?
farmaceuta Luty 20, 2022 Udostępnij Luty 20, 2022 (edytowany) 18 minut temu, SOYER napisał: @ethanak Więc ten przykład na to jak to zrobić by zmienne reszta nie były globalne, czy inaczej? Nie nie...chodzi o to ze wchodzac do funkcji nie wiesz ktora zmienna zostala podana jako argument...czy reszta1/reszta2...wiec uzywasz wskaznika zeby bylo prosto i szybko, bo return'a uzyc nie mozesz bo zostal juz zarezerwowany dla wynik1/2 Wracajac do bibliotek to zwroc uwage ze czesto jako argumenty podajesz "&zmienna"...dlaczego? Bo inaczej w funkcjach zrobil bys kopie i na nich operowal a czesto chodzi o operacje na oryginalach wiec musisz uzyc wskaznika... Edytowano Luty 20, 2022 przez farmaceuta 1
SOYER Luty 20, 2022 Udostępnij Luty 20, 2022 23 minuty temu, farmaceuta napisał: nie...chodzi o to ze wchodzac do funkcji nie wiesz ktora zmienna zostala podana jako argument...czy reszta1/reszta2...wiec uzywasz wskaznika zeby bylo prosto i szybko, Ok, załapałem, dzięki. Teraz to logiczne, przykład ethanaka jak zwykle hydrozagadka na początku, teraz widzę jakie to banalne. Faktycznie może to upraszczać pisanie. Ja dotychczas takich rzeczy nie miałem potrzeby używać, albo nie wiedziałem, że tak można;). ethanak, farmaceuta 1
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »