Diskuze: Funkce a pointer na pole
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.


DarkCoder:17.8.2021 13:46
Hlavní chyba je způsob předávání pole funkci. Pro číselná pole funkce neumí zjistit uvnitř funkce počet prvků pole, je tedy tuto informaci předat jako druhý parametr. Velikost pole uvnitř hranatých závorek je ignorována. Takže správný prototyp funkce může vypadat např. takto:
int func(int arr[], int size);
// nebo
int func(int *arr, int size);
Ja bych to udelal takto
#define POCET 10
// Bubblesort s poitermi
void p_bubblesort( int* p_array )
{
int i , j ;
int aux ;
for( j=0 ; j< POCET; j++ )
{
for( i=0 ; i<POCET - 1 ; i++ )
{
if( p_array[ i ] > p_array[ j ] )
{
aux = p_array[ j ] ;
p_array[ j ] = p_array[ i ] ;
p_array[ i ] = aux ;
}
}
}
}
//a volani
p_bubblesort( pole) ;
protoze pole je vlastne pointer na int (10*int)
DarkCoder:17.8.2021 21:08
Takovéto řešení není šťastné a to z jednoho prostého důvodu. Pracuje se se symbolickou konstantou přímo uvnitř těla funkce. Funkce tak není pružná a z hlediska funkčnosti je omezená. Je třeba tvořit takové funkce, které poskytnou využití pro širokou škálu případů bez ztráty efektivity. Jak bychom pak například třídili jen část pole, popř. pole jiné velikosti? Redefinovat symbolickou konstantou by asi nebylo úplně elegantní řešení. Proto je dobré při předávání ukazatele na pole předávat i jeho velikost ve druhém parametrů funkce.
Petan:18.8.2021 6:53
Souhlasím, ale toto byl priklad kdy mel tyto hodnoty primo dane ve funkci.
DarkCoder:18.8.2021 8:38
To ano, ale to ještě neznamená, že je to správně. Tyto hodnoty by měli být závislé na tom co přichází z věnčí. A velikost pole popř. úsek, který se má třídit, může být proměnlivý. Tedy je potřeba, aby i vnitřek funkce se měnil.
DarkCoder:18.8.2021 10:31
Třídící algoritmus BubbleSort je obecný algoritmus a je holý nesmysl ho implementovat pro konkrétní hodnotu. Dost často bývá součástí vlastních knihoven, což je další důvod, proč se používá se samostatným parametrem udávající velikost pole.
Jak už jsem psal v příspěvku výše
void p_bubblesort( int p_array[10] )
Překladač nezná velikost pole (hodnota 10 v hranatých závorkách je překladačem ignorována) a funkce tak není obecná, jelikož se spoléhá na něco zvenčí (na nějakou globální proměnnou, statickou proměnnou, či symbolickou konstantu). Takovýto postup je z programátorského hlediska nesprávný..
Dobrý deň, priatelia. Ďakujem za vaše rady, ale asi došlo k nedorozumeniu. Moja otázka sa vzťahovala iba na zápis funkcie s pointermi. Bubblesort bol iba príklad funkcie ktorá pracuje s poliami....pre mňa je irelevantné či je funkcia pružná, či nepracuje pre konkrétny rozsah poľa N....chcem len vedieť ako zapísať funkciu, ktorá vezme nie pole, ale adresy prvkov poľa pole1 , spracuje ich a výsledok uloží do iného poľa pole2 pričom vráti pointery na toto pole2....
Poprosím na príklade súčtu dvoch vektorov v 3d priestore.Zdôrazňujem že je to úloha edukačného charakteru .
Verzia bez pointerov :
void sucet_vektorov( int vec1[3] , int vec2[3] , int vec3[3] )
{
for(int i=0 ; i<3 ; i++ )
vec3[i] = vec1[i]+vec[2] ;
}
Volanie funkcie v main:
int main()
{
int vektor1[3] = {4,6,8} ;
int vektor1[3] = {7,9,1} ;
int vektor3[3] ;
sucet_vektorov( vektor1,vektor2,vektor3 ) ;
//Vypis vektora:
for(int i=0 ; i<3 ; i++ )
printf("%d," , vektor3[ i ] ) ;
}
/code]
Toto je verzia, ktorá pracuje s prvkami poľa. Potrebume vedieť ako vyzerá verzia, ktorá vezme iba adresy vektorov, sčíta ich pomocou pointerov a ulozi vysledok. Dôležité je aj volanie funkcie z main.
DarkCoder:19.8.2021 11:09
Jak takovou funkci napsat jsem psal již v úvodním příspěvku. Tedy ještě jednou. Funkce nepřijímá pole jako celek, ale vždy pouze ukazatel na začátek pole. Existují tři zápisy:
void func1(int arr[]);
void func1(int arr[10]);
void func1(int *arr);
Všechny tyto zápisy jsou identické a všechny přebírají ukazatel na začátek pole. Používá se buď první způsob a zejména třetí způsob. V ani jednom zápisu však nefiguruje velikost pole a je tedy dobré tuto informaci předávat jako druhý parametr.
void func1(int arr[], int size);
void func1(int arr[10], int size);
void func1(int *arr, int size);
Toto neplatí, respektivě není třeba, pro znaková pole tvořící řetězec (nikoli znaky samotné). Ta jsou ukončena nulovým znakem.
Je zbytečné brát adresy jednotlivých prvků pole, když znám adresu pole. Adresy jednotlivých prvků pole lze pak získat pomocí ukazatelové aritmetiky. To se děje pak až uvnitř funkce..
Jak by vypadal příklad s vektory je zde:
#include <stdio.h>
#define SIZE 3
int* sum_vec(int* arr1, int* arr2, int* arr3, int dim);
void print_vec(int* arr, int dim);
int main(void) {
int v1[SIZE] = { 4,6,8 };
int v2[SIZE] = { 7,9,1 };
int v3[SIZE];
int* pvec = NULL;
pvec = sum_vec(v1, v2, v3, SIZE);
print_vec(pvec, SIZE);
return 0;
}
int* sum_vec(int* arr1, int* arr2, int* arr3, int dim) {
for (int i = 0; i < dim; i++) *(arr3 + i) = *(arr1 + i) + *(arr2 + i);
return arr3;
}
void print_vec(int* arr, int dim) {
for (int i = 0; i < dim; i++) printf("%d ", *(arr + i));
}
Funkce sum_vec() přebírá tři ukazatele na jednotlivá pole (tedy ukazatele na vektory) a velikost vektoru. Vrací ukazatel na výsledné pole (vektor). Funkce print_vec() přebírá ukazatel na pole a opět přebírá velikost tohoto pole. Dále byl deklarován ukazatel, který bude použit pro pole výsledku součtu vektorů. Je mu tedy přiřazena návratová hodnota funkce sum_vec(). Nakonec je výsledek zobrazen na obrazovku pomocí funkce print_vec() která přebírá ukazatel na pole a velikost tohoto pole. Pro ověření funkčnosti vrácené hodnoty funkce sum_vec() byl použit ukazatel pvec namísto ukazatele v3.
Priznávam že Vašim kódom nerozumiem. Zrejme preto, lebo zápis je úplne
odlišný od toho ktorý je v učebniciach. Kód už funguje ale neviem prečo.
Hlavne nerozumiem , ako je možné že do funkcie sum_vec ste zadal priamo pole
a nie pointery naňho. Vo vašom kóde mi chýba adresný operátor &.
Niekde by mala byť formula typu: niečo = &arr1[0] ; alebo niečo = arr1
;
Tiež nerozumiem zápisu return arr3....veď arr3 je celé pole, nie 1 premenná
....mne nikdy program nechce vratit cele pole, vždy len jednu premennú.
DarkCoder:20.8.2021 14:32
Hlavne nerozumiem , ako je možné že do funkcie sum_vec ste zadal priamo pole a nie pointery naňho.
Pole se nikdy funkci nepředává, vždy se funkci předává ukazatel na pole.. Pokud se bude předávat adresa jednoho prvku pole, pak se ve volání funkce vyskytne referenční operátor &. Je samozřejmě možné aby ukazatel na pole a adresa jednoho (prvního) prvku byla stejná. Pokud tedy budu mít prototyp funkce:
void func(int *arr);
pak máme-li např. pole v1[10], tak adresa předávaná funkci u následujících volání funkcí je stejná
func(v1); // bazová adresa pole v1
func(&v1[0]); // adresa prvního prvku pole v1
Pokud se ale předává ukazatel na začátek pole, používá se výhradně první varianta. V profi psaných programech se druhá varianta nepoužívá. Lze ji ovšem využít pro přístup k adrese daného prvku pole.
Podívej se na následující dva programy, jeden bez funkcí, druhý s funkcemi. První varianta by Ti měla býti jasná. Tam najdeš to na co si se ptal. Druhá varianta Ti nejspíš jasná nebude, neboť se ve funkci kde se předává pole ukazatelů vyskytuje vícenásobná dereference. Nicméně jako představa Ti to poslouží více než dobře..
Bez funkce
#include <stdio.h>
#define SIZE 3
int main(void) {
int v1[SIZE] = { 4,6,8 };
int* v2[SIZE];
for (int i = 0; i < SIZE; i++) v2[i] = &v1[i];
for (int i = 0; i < SIZE; i++) printf("%d ", *v2[i]);
return 0;
}
S Funkcí
#include <stdio.h>
#define SIZE 3
void get_adr(int* adr, int **arr, int pos);
int main(void) {
int v1[SIZE] = { 4,6,8 };
int* v2[SIZE];
for (int i = 0; i < SIZE; i++) get_adr(&v1[i], v2, i);
for (int i = 0; i < SIZE; i++) printf("%d ", *v2[i]);
return 0;
}
void get_adr(int* adr, int** arr, int pos) {
arr[pos] = adr;
}
Tiež nerozumiem zápisu return arr3....veď arr3 je celé pole, nie 1 premenná
arr3 je ukazatel na začátek pole deklarovaný jako formální parametr funkce. Opět, nevrací se celé pole, ale pouze ukazatel na pole.
Nakonec zmíním, že je důležité vědět čeho chci dosáhnout. Podle toho stavím kód programu. Pokud tedy vím že pracuji s prvky celého pole (traversuji), tak použiji ukazatel na začátek pole. Používá se mnohem častěji než práce s jednotlivými prvky pole.
Doporučuji si nejprve osvojit práci s běžnou proměnnou u funkce - volání hodnotou, odkazem, vrácení hodnoty a vrácení adresy přes návratový typ a přes argument funkce, atd.. a pak se pustit do práce s polem.. vědět vztah mezi indexací pole a ukazatelovou aritmetikou, vědět co představuje jméno pole bez indexu, umět předat ukazatel na pole, jeden prvek z pole, atd..
Daniel Bršťák:31.8.2021 13:00
Dobrý deň. Pokúšal som sa podľa Vášho návodu zkonštruovať analogický program pre súčet 2-rozmerného poľa ale nefunguje mi. Dokážete nájsť chybu ? Môj program je len 2d analóg vášho a nefunguje.
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define N 3
int *sucet_matic(int *arr1 ,int *arr2, int *arr3, int dim );
void print_matrix( int *matrix , int size) ;
int main()
{
int matrix1[N][N]={
{ 4,6,8 },
{ 4,6,8 },
{ 4,6,8 }} ;
int matrix2[N][N] = {
{ 7,9,1 },
{ 7,9,1 },
{ 7,9,1 } } ;
int matrix3[N][N] ;
int *pvec = NULL;
pvec=sucet_matic(matrix1 , matrix2,matrix3 , N ) ;
print_matrix(pvec , N ) ;
return 0;
}
int *sucet_matic(int *arr1, int *arr2, int
*arr3 , int dim)
{
int i , j ;
for( j =0 ; j < dim; j++ ){
for( i =0 ; i < dim; i++ ){
*(*(arr3 + i)+j) ==
*(*(arr1 + i )+j ) + *(*(arr2 + i)+j ) ;
}
}
return arr3;
}
void print_matrix( int *matrix, int size)
{
int i ,j ;
for( j=0 ; j< size ; j++ ){
for( i = 0 ; i < size ; i++ ){
printf("%d", *(*(matrix + i)+j) ) ;
printf('\t') ;
}
printf("\n") ;
}
}
DarkCoder:31.8.2021 14:56
Hlavní chybou je způsob předávání 2D pole funkci. Je třeba přesně specifikovat typ ukazatele. Budeme-li chtít napsat např. prototyp funkce print_matrix(), bude vypadat následovně:
void print_matrix(int mat[][N], int size);
// nebo
void print_matrix(int (*mat)[N], int size);
A to za předpokladu, že je definováno N.
Celý kód pak bude vypadat například takto:
#include <stdio.h>
#define N 3
void sucet_matic(int mat1[][N], int mat2[][N], int mat3[][N], int size);
void print_matrix(int mat[][N], int size);
int main(void){
int matrix1[N][N] = {
4,6,8,
4,6,8,
4,6,8
};
int matrix2[N][N] = {
7,9,1,
7,9,1,
7,9,1
};
int matrix3[N][N];
sucet_matic(matrix1, matrix2, matrix3, N);
print_matrix(matrix3, N);
return 0;
}
void sucet_matic(int mat1[][N], int mat2[][N], int mat3[][N], int size) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
*(*(mat3 + i) + j) = *(*(mat1 + i) + j) + *(*(mat2 + i) + j);
}
}
}
void print_matrix(int mat[][N], int size){
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
printf("%d ", *(*(mat + i) + j));
}
putchar('\n');
}
}
Není třeba přikládat matematickou knihovnu math.h ani knihovnu stdlib.h.
Další chyby ve tvém programu jsou, že ve funkci sucet_matic() používáš pro přiřazení znak operátor rovnosti == namísto operátoru přiřazení =. A funkce printf() pro výpis tabulátoru vyžaduje dvojité uvozovky pro ohraničení formátovacího řetězce, nikoli apostrofy. Ty se používají u funkce putchar() pro výpis jednoho znaku, tedy i escape sekvence.
Pán DarkCoder:
Vždy keď Vám napíšem fragment kódu, tak ho modifikujete tak že sa stratí
pointa otázky.Konkrétne:
Prečo ste moju funkciu sucet_matic prepísali bez pointerov ? Moja pôvodná
funkcia bola:
int *sucet_matic(int *arr1 ,int *arr2,int *arr3 , int dim ) ;
Vaša modifikovaná funkcia:
void sucet_matic(int mat1[][N],int mat2[][N],int mat3[][N], int size ) ;
Pritom ja som svoju funkciu definoval na základe Vašej funkcie súčtu vektorov, kde jediná zmena bola že sčítavam 2-rozmerné pole:
int* sum_vec(int* arr1, int* arr2, int* arr3 , int dim ) ;
Tú funciu som definoval na základe analógie s Vašou funkciou práve preto
aby ste nemuseli nič meniť-a Vy ste funkciu zase od základu zmenil.
Aký má zmysel prepis pola arr na mat ? Prestalo platiť že matica je
dvojrozmerné pole ?
'A to za předpokladu, že je definováno N' - veď som ho definoval jasne :
#define N 3
Neviem či je problém komunikačná bariéra medzi češtinou a slovenčinou,
alebo sme naladení na inej vlne , ale Vaše odpovede mi pochopenie skôr
stažujú než uľahčujú . Na toto treba ísť inak: predpokladám že sa
vyznáte aj v literatúre. V ktorej učebnici sú pointery vysvetlené
najzrozumiteľnejšie pre začiatočníka ? Pokúšam sa učiť z knihy Ritchie,
Kerninghan : Programovací jazyk C , ale aj tá kniha je nevhodná. Je to
referenčná príručka , nie učebnica.
DarkCoder:31.8.2021 18:30
Pointa otázky byla zodpovězena hned v první větě. Opakuji, že to co bylo špatně byl způsob předávání 2D pole funkci. Ostatní modifikuji dle sebe, neboť by to mělo naopak pomoci k pochopení činnosti celého programu. Arr jako formální parametr používám pro 1D pole, mat pro 2D pole. Samozřejmě si každý může zvolit libovolné platné jméno. Dále znovu opakuji, že se nepředává pole funkci ale vždy ukazatel na pole. Dále je obrovský rozdíl předat ukazatel na pole nebo ukazatel na N-prvkové pole. To je hlavní odlišnost v zápisu hlavičky funkce.
Funkci
int *sucet_matic(int *arr1 ,int *arr2,int *arr3 , int dim ) ;
nelze použít pro 2D pole! Pro 2D pole je třeba, aby funkce vypadala následovně:
void sucet_matic(int (*mat1)[N], iint (*mat2)[N], iint (*mat3)[N], int size);
// nebo
void sucet_matic(int mat1[][N], int mat2[][N], int mat3[][N], int size);
tedy formální parametr je ukazatel na N-prvkové pole typu int. V obou
případech se předává ukazatel.
Návratovou hodnotu jsem vypustil, jelikož ukazatel na cílové 2D pole je již
součástí argumentu funkce.
Shrnutí:
1D pole předané funkci:
void func(int arr[], int size);
// nebo
void func(int *arr, int size);
2D pole předané funkci
void func(int mat[][N], int size);
// nebo
void func(int (*mat)[N], int size);
Definování N je důležité. V opačném případě by došlo k chybě během překladu, proto to bylo zdůrazněno.
Co se týká knih, pár jsem jich přečetl. Kniha Ritchie, Kerninghan - Programovací jazyk C, je opravdu obecný základ.
Knih o ukazatelích je obrovské množství: Herb Schildt - Teach Yourself C, Herb Schildt - Complete References C, Stephen Prata - C Primer Plus, Reya Nadeya - Programming in C, Dewan Hrishikesh - Pointers in C, Reese Richard - Understanding and Using C Pointers, Robert J. Traister - Mastering C Pointers: Tools for Programming Power a mnoho dalších..
Dost možná pro české čtenáře něco o ukazatelích napíšu i já... nicméně to teď není na pořadu dne..
Obecně ale, čím více knížek pročteš, tím více Ti to dá. Z každé si něco odneseš. Něco Ti bude jasnější z jedné, něco z druhé..
DarkCoder:1.9.2021 14:56
Způsobů, jak předat 2D pole jako argument funkci, je samozřejmě mnohem více a záleží na situaci. Zde jsou některé z nich
Pokud jsou známy rozměry a jsou definovány jako makro:
void print_matrix(int mat[M][N]) {
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", *(*(mat + i) + j));
}
putchar('\n');
}
}
// volaní funkce
print_matrix(matrix);
Pokud jsou známy oba rozměry a jen druhý je definován jako makro:
void print_matrix(int mat[][N], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", *(*(mat + i) + j));
}
putchar('\n');
}
}
// volaní funkce
print_matrix(matrix, M);
Pokud jsou známy oba rozměry a jen druhý je definován jako makro (koncept ukazatele na pole):
void print_matrix(int (*mat)[N], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", *(*(mat + i) + j));
}
putchar('\n');
}
}
// volaní funkce
print_matrix(matrix, M);
Pokud jsou známy oba rozměry ale nedefinované jako makro (koncept prostého ukazatele):
void print_matrix(int* mat, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", *(mat + i * cols + j));
}
putchar('\n');
}
}
// volaní funkce
print_matrix((int *)matrix, M, N); // je nutné přetypování na ukazatel
Pokud je 2D pole vytvořeno dynamicky
void print_matrix(int** mat, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", *(*(mat + i) + j));
}
putchar('\n');
}
}
// volaní funkce
print_matrix(matrix, m, n);
Takže stačí si jen vybrat vhodný způsob dle situace..
+20 Zkušeností
+2,50 Kč

Daniel Bršťák:2.9.2021 9:10
Ďakujem. Kód konečne funguje tak ako má. Presne takáto kategorizácia rôznych zápisov je to čo mi v knihách chýba.
DarkCoder:2.9.2021 10:31
Tak to je fajn, že vše funguje tak jak má. Ano, to je také důvod proč chci napsat knihu i přesto, že na trhu už jich je bezpočet. Žádná totiž nemá takovouto strukturu, která vše čtenáři podává jasnou a srozumitelnou formou bez vší té omáčky kolem..
Zobrazeno 23 zpráv z 23.