• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Tworzenie wielowymiarowych tablic w funkcji i przekazywanie ich przez wskaźnik/referencję

Object Storage Arubacloud
0 głosów
208 wizyt
pytanie zadane 14 grudnia 2022 w C i C++ przez Krzysztofs1234 Użytkownik (890 p.)

Dzień dobry,

deklaruję tablicę dwuwymiarową (w początkowym etapie jest to rzecz jasna wskaźnik do wskaźnika). Chciałbym ją przekazać do funkcji, aby ta utworzyła dla niej liczbę wierszy i kolumn - analogicznie jak to zrobiłem z tablicą jednowymiarową.

Niestety nie wiem nawet, jak to działa z tablicą jednowymiarową, po prostu mi się udało. Utworzyłem wskaźnik, w 15 linijce przekazałem adres tego wskaźnika mimo, że parametr to miał być wskaźnik do adresu (6 linijka). W ciele funkcji z kolei nie posługuję się "*" przy "A". Też nie do końca rozumiem, dlaczego. Szukałem wszędzie odpowiedzi, ale nie wiem, co w zasadzie dzieje się z pamięcią, co jest przekazywane do funkcji i na czym ona pracuje. Chciałbym potrafić to, co określiłem w pierwszym akapicie, dla tablic 1, 2, 3, ..., n- wymiarowych.

Uprzejmie proszę o pomoc.

#include <iostream>
using namespace std;


void createArray(int*& A);
void create2dArray(int**& A);


int main()
{
    unsigned i, j;
    int* A;
    int** B;

    createArray(A);
    create2dArray(B);

    for(i=0; i<5; i++) cout << A[i] << " ";
    cout << endl << endl;
    for(i=0; i<5; i++)
    {
        for(j=0; j<3; j++) cout << B[i] << " ";
        cout << endl;
    }

    return 0;
}


void createArray(int*& A)
{
    A=new int[5];

    for(unsigned i=0; i<5; i++)
    {
        A[i]=7;
    }
}


void createArray(int**& A)
{
    unsigned i, j;

    A=new int*[5];
    for(i=0; i<5; i++)
    {
       A[i]=new int[3];
        for(j=0; i<3; j++) A[i][j]=i+j;
    }
}

 

2 odpowiedzi

+2 głosów
odpowiedź 14 grudnia 2022 przez mokrowski Mędrzec (156,220 p.)

Jeśli decydujesz się na obsługę tablic n-wymiarowych na poziomie wskaźników do nich, sensownym pomysłem jest potraktowanie pamięci jako ciągłej. Alokacja która często poruszana w różnych tutorialach/kursach oraz (niestety) często na uczelniach, w postaci podwójnego/potrójnego/... wskaźnika, nie ma w praktyce sensu. Takie alokowanie wierszy będzie powodowało fragmentowanie pamięci, złą trafialność w cache oraz problemy w obsłudze.

Przy alokacji dynamicznej poprzez new, skazujesz się także na konieczność pamiętania o użyciu delete... a tu jest dużo niuansów o których nie warto teraz... 

W trybie krok po kroku i nieco "postarzając" do dzisiejszych standardów C++:

#include <iostream>

int * createArray(const std::size_t size) {
	return new int[size];
}

int * create2DArray(const std::size_t x, const std::size_t y) {
	return new int[x * y];
}

void fillArray(int * const tab, const std::size_t size) {
	for (unsigned i = 0U; i < size; ++i) {
		tab[i] = i;
	}
}

void fill2DArray(int * const tab, const std::size_t x, const std::size_t y) {
	for (unsigned row = 0U; row < y; ++row) {
		for (unsigned col = 0U; col < x; ++col) {
			tab[row * x + col] = row * col;
		}
	}
}

void showArray(const int * const tab, const std::size_t size) {
	std::cout << "1D Array:\n[ ";
	for (unsigned i = 0U; i < size; ++i) {
		std::cout << tab[i] << ' ';
	}
	std::cout << "]\n";
}

void show2DArray(const int * const tab, const std::size_t x, const std::size_t y) {
	std::cout << "2D Array:\n[\n";
	for (unsigned row = 0U; row < y; ++row) {
		std::cout << "[ ";
		for (unsigned col = 0U; col < x; ++col) {
			std::cout << tab[row * x + col] << ' ';
		}
		std::cout << "]\n";
	}
	std::cout << "]\n";
}

int main() {
	unsigned dim_x = 10U;
	unsigned dim_y = 20U;

	int * table1D = createArray(dim_x);
	int * table2D = create2DArray(dim_x, dim_y);

	fillArray(table1D, dim_x);
	showArray(table1D, dim_x);

	fill2DArray(table2D, dim_x, dim_y);
	show2DArray(table2D, dim_x, dim_y);

	// Niefajne, niezręcze ale konieczne by nie wyciekała pamięć... 
	delete [] table2D;
	delete [] table1D;
}

Popatrz jak te indeksy są liczone i przekonaj się że... "pamięć jest płaska" :)

komentarz 14 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)

Dziękuję za odpowiedź wzbogaconą o łatwy do zrozumienia i rozjaśniający temat kod.

Zdaje mi się, że przez wyrażenie ""pamięć jest płaska" najbardziej przemawia:

tab[row * x + col]

więc chyba rozumiem, co chciałeś przekazać. Analogicznie dla tablicy 3-wymiarowej byłoby:

tab[layer * w + row * x + col]

Do macierzy się nada, choć jest moim zdaniem tutaj mniejsza kontrola błędów. Można przekroczyć liczbę drugiego i następnego wymiaru tablicy, np. mamy tablicę 3x5, a do zmiennych wskazujących na wiersz i kolumnę wpisać odpowiednio 2 i 6. Z kolei w przypadku, gdy niekoniecznie korzystamy z macierzy, to tutaj:

return new int[x * y]

stworzymy jedynie tablicę "prostokątną" albo w trójwymiarze - "prostopadłościenną". Zawsze długości "w podrzędnych wymiarach" będą takie same. Zawsze np. 3x5, a nie 3x{3,1,5}. 

komentarz 15 grudnia 2022 przez mokrowski Mędrzec (156,220 p.)

Co do tak obsługiwanych tablic "na surowo", porzuć nadzieję że będziesz miał jakąkolwiek kontrolę błędów. Tu jej nie ma bo kłóci się z założeniem ekstremalnej wydajności. Nawet w przypadku definiowania tablicy z n-indeksami, tej kontroli nie masz. To ułuda. W tym sensie także "pamięć jest płaska" i nastąpi zapis do dalszego wiersza/warstwy/... Takich tablic ani C ani C++ nie kontroluje. Same nawiasy prostokątne, to tak naprawdę jedynie cukier składniowy na artytmetykę wskaźnika.

#include <iostream>

int main() {
	int tab[] = {0, 10, 20, 30};
	std::cout << tab[2] << '\n';
	// Gdzie:
	// tab[2] oznacza...
	// *(tab + 2)
	// .. a to to samo co ...
	// *(2 + tab)
	// ... tak więc można zapisać...
	// 2[tab] ... choć to zaskakujące :)
	std::cout << 2[tab] << '\n';
}

Do budowania tablic trójkątnych lub innych... stosuje się nieco inne podejście... Aczkolwiek tu także, jeśli ma być szybkie, liczy się indeksy w płaskiej pamięci.

Jeśli chcesz czuć się bezpieczniej, to std::array, std::valarray czy std::vector, będą lepsze. Mają swoje nisze zastosowań i konsekwencje/specjalne właściwości.

–2 głosów
odpowiedź 14 grudnia 2022 przez Jaaqob Stępień Użytkownik (760 p.)
To jest przykład kodu dlaczego jestem zdania że C jest lepszy od C++.

Jak chcesz zwrócić jedną wartość to zrób to przy użyciu return, a nie przy użyciu referencji.
komentarz 14 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)
Mnie wcale nie chodzi o zwrócenie jednej wartości a całej tablicy, chyba że masz na myśli tablicę jako wartość. I właśnie chciałem to zrobić przy użyciu wskaźnika albo referencji.
komentarz 14 grudnia 2022 przez Jaaqob Stępień Użytkownik (760 p.)
To co zrobiłeś to zwróciłeś wyraził do tablicy przez referencje. Nic nie szkodzi na przeszkodzie żeby zwrócić wskaźnik do tablicy jako wartość.

Podobne pytania

0 głosów
2 odpowiedzi 7,463 wizyt
pytanie zadane 12 lutego 2017 w C i C++ przez Evelek Nałogowiec (28,960 p.)
0 głosów
1 odpowiedź 330 wizyt
0 głosów
2 odpowiedzi 827 wizyt
pytanie zadane 28 stycznia 2019 w C i C++ przez Michał_Warmuz Mądrala (5,830 p.)

92,676 zapytań

141,581 odpowiedzi

320,060 komentarzy

62,039 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...