Skocz do zawartości

Wskazniki dla początkujących


Krawi92

Pomocna odpowiedź

Cytat

Więc zamiast tego spróbuj je przećwiczyć. Powiedzmy że masz za zadanie zrobić pięć funkcji które nie pobierają ani nie zwracają żadnej wartości. Zbuduj tablicę wskaźników na te funkcje. Jak byś to zrobił?

@_LM_ W nawiązaniu do twojego pytania, spróbuję odpowiedzieć. Nie wiem czy miałeś do na myśli. Oto pseudokod.

#include <avr/io.h>

void migaj_LED1(void);
void migaj_LED2(void);
void migaj_LED3(void);
void migaj_LED4(void);
void migaj_LED5(void);

typedef void (*Twsk) (void); // nowy typ wskaznika na funkcje typu void
Twsk wskaznik[5]; // Stworzenie tablicy 5 elementowej typu Twsk
enum {led1,led2,led3,led4,led5};

int main () {
// przypisanie wskazników do funkcji
	wskaznik[led1] = migaj_LED1;
	wskaznik[led2] = migaj_LED2;
	wskaznik[led3] = migaj_LED3;
	wskaznik[led4] = migaj_LED4;
	wskaznik[led5] = migaj_LED5;

}

void migaj_LED1(void){
// migamy led
}
void migaj_LED2(void){
	// migamy led
}
void migaj_LED3(void){
	// migamy led
}
void migaj_LED4(void){
	// migamy led
}
void migaj_LED5(void){
	// migamy led
}

 

Edytowano przez Krawi92
Link do komentarza
Share on other sites

Podłączyłem sobie kilka led, żeby nie patrzeć tylko na kompilacje. 5 funkcji miga 5 roznymi diodami i do funkcji odwołuje się rozumiem przez wskaźnik. 

wskaznik[led1]();

W ten sposób jak np: w pętli while wywołam funkcję to jest ok

Link do komentarza
Share on other sites

Teraz pytanie może nie na miejscu, bo wiem że powinienem sam gdzieś czytać o tym itp. Po co takie odwoływanie się za pomocą wskaźnika do funkcji ? Rozumiem, że na takim pseudokodzie ciężko to zrozumieć, bo na razie nie widzę tu sensu. 

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

5 minut temu, Krawi92 napisał:

Teraz pytanie może nie na miejscu, bo wiem że powinienem sam gdzieś czytać o tym itp. Po co takie odwoływanie się za pomocą wskaźnika do funkcji ? Rozumiem, że na takim pseudokodzie ciężko to zrozumieć, bo na razie nie widzę tu sensu. 

#ifndef MENULIB_ACTION_H
#define MENULIB_ACTION_H

#include "actions/i_action_metadata.h"

class action_t;

// Callback definition
typedef void (*callback_t)(action_t* action, i_action_metadata* metadata);

// Action types
typedef enum {
    ON_PRESS = 1,
    ON_RELEASE = 2,
    ON_HOLD = 4,
    ON_DECREASE = 8,
    ON_INCREASE = 16,
    ON_CHANGE = (ON_DECREASE | ON_INCREASE)
} action_type_t;

class action_t {
private:
    callback_t callback;
    action_type_t actionType;
public:
    action_t(callback_t callback, action_type_t actionType);
    void execute(i_action_metadata* metadata);
    action_type_t getActionType();
};


#endif //MENULIB_ACTION_H

By zrobić coś takiego:

#define MENU_INCREASE_DECREASE_OPTION(title, listener_increase, listener_decrease) (new menu_option_t(title))->add_listener(new action_t(listener_increase, action_type_t::ON_INCREASE))->add_listener(new action_t(listener_decrease, action_type_t::ON_DECREASE))

W skrócie klasa action_t opisuje zdarzenie występujące po wprowadzeniu danych do urządzenia (np. zmiany wartości potencjometru czy wciśnięcia przycisku). Dzięki temu mogę mieć wiele akcji, które wykonują się w momencie, w którym np. wartość potencjometru wzrośnie.

Potem mogę je wywołać wszystkie w bardzo prosty sposób:

menu_common_t::execute(ON_INCREASE, nullptr);

W skrócie wskaźników do funkcji używasz wtedy, gdy chcesz przypisać funkcję do jakiejś zmiennej i przechowywać ją w obiekcie skonstruowanym tak, że pozwala użytkownikowi dodać funkcję do wykonania bez ingerencji w strukturę obiektu. Brzmi skomplikowanie... W skrócie - jak chcesz mieć możliwość przypisania funkcji, której jeszcze nie masz, a potem nie będziesz mógł edytować tego kodu, bo będzie to np. biblioteka.

Możesz sobie pokopać tutaj by znaleźć przykład użycia: https://github.com/H1M4W4R1/MCU_Menu_Library (tak to ten sam kod).

Link do komentarza
Share on other sites

A to jest dosyć proste: powiedzmy że chcesz funkcje migaj wywołać w zależności od jakiejś zmiennej, bez wskaźników zrobiłbyś to na switch case:

#include <avr/io.h>

void migaj_LED1(void);
void migaj_LED2(void);
void migaj_LED3(void);
void migaj_LED4(void);
void migaj_LED5(void);



int main () {

	uint8_t licznik;
	
	for(;;){
		
		switch(licznik){
		case 0:migaj_LED1();
		break;
		case 1:migaj_LED2();
		break;
		case 2:migaj_LED3();
		break;
		case 3:migaj_LED4();
		break;
		case 4:migaj_LED5();
		break;
		default:break;
		
		}
	}
}
}

void migaj_LED1(void){
// migamy led
}
void migaj_LED2(void){
	// migamy led
}
void migaj_LED3(void){
	// migamy led
}
void migaj_LED4(void){
	// migamy led
}
void migaj_LED5(void){
	// migamy led
}

Zaś mając tablicę wskaźników na te funkcje zrobisz to w jednej linii

#include <avr/io.h>

void migaj_LED1(void);
void migaj_LED2(void);
void migaj_LED3(void);
void migaj_LED4(void);
void migaj_LED5(void);

typedef void (*Twsk) (void); // nowy typ wskaznika na funkcje typu void
Twsk wskaznik[5]; // Stworzenie tablicy 5 elementowej typu Twsk
enum {led1,led2,led3,led4,led5};

int main () {
// przypisanie wskazników do funkcji
	wskaznik[led1] = migaj_LED1;
	wskaznik[led2] = migaj_LED2;
	wskaznik[led3] = migaj_LED3;
	wskaznik[led4] = migaj_LED4;
	wskaznik[led5] = migaj_LED5;
	
	uint8_t licznik;
	
	for(;;){
		if(wskaznik[licznik])wskaznik[licznik](); // sprawdzanie czy ptr przypisany, jesli tak to wykonaj
	}

}

void migaj_LED1(void){
// migamy led
}
void migaj_LED2(void){
	// migamy led
}
void migaj_LED3(void){
	// migamy led
}
void migaj_LED4(void){
	// migamy led
}
void migaj_LED5(void){
	// migamy led
}

 

  • Pomogłeś! 1
Link do komentarza
Share on other sites

Podstawą korzystania z wskaźników na funkcje jak i inne obiekty jest sprawdzenie czy on na coś wskazuje. Dlatego że odwołanie się do wskaźnika na NULL lub nieokreślone miejsce w programie, doprowadzi w najlepszym wypadku do resetu mikrokontrolera, w gorszym do nieoczekiwanej podmiany danych w pamięci SRAM stąd ta instrukcja w main if(wskaznik[licznik])wskaznik[licznik]();

Edytowano przez _LM_
  • Pomogłeś! 1
Link do komentarza
Share on other sites

@Krawi92 Zastosowań wskaźników do funkcji jest całkiem sporo. Bardzo prosty, wręcz książkowy przykład to funkcja sortująca - powiedzmy coś takiego:

void swap(int *xp, int *yp)
{
    int temp = *xp;
    *xp = *yp;
    *yp = temp;
}
 
// A function to implement bubble sort
void bubbleSort(int arr[], int n)
{
    int i, j;
    for (i = 0; i < n-1; i++)    
     
    // Last i elements are already in place
    for (j = 0; j < n-i-1; j++)
        if (arr[j] > arr[j+1])
            swap(&arr[j], &arr[j+1]);
}

W takim kodzie masz na stałe zaszyte porównywanie elementów, czyli (arr[j] > arr[j+1]). Gdybyś chciał posortować elementy powiedzmy malejąco, zamiast rosnąco, musiałbyś napisać nową funkcję. Jedną z metod napisania ogólnego kodu jest przekazanie jeszcze jednego parametru do bubbleSort(), czyli funkcji porównującej. Użycie jej zamiast > sprawi, że można będzie ten sam kod używać do różnych sortowań. Jako ćwiczenie możesz spróbować taki kod napisać.

Kolejne zastosowania wskaźników funkcji to funkcje zwrotne (callback). Często wykonanie jakiegoś kodu zajmuje sporo czasu, a na koniec trzeba o tym powiadomić inny fragment programu. Przekazanie wskaźnika do funkcji to dość dobre rozwiązanie.

Bardzo ważnym zastosowaniem jest również programowanie obiektowe w C. Wbrew często powtarzanej opinii jest to jak najbardziej możliwe oraz często stosowane. Wskaźniki funkcji zapisane w strukturach pozwalają na uzyskanie równie dobrych, a nawet lepszych rezultatów niż z użyciem C++.

  • Lubię! 2
Link do komentarza
Share on other sites

Podam jeszcze jeden prosty przykład: powiedzmy że z uart przylatuje jakiś znak i na jego podstawie masz wykonać akcję -> wywołać funkcję przypisaną do tego znaku. Można to zrobić w taki sposób że odgórnie połączymy spodziewane znaki z adresami funkcji które mają się wykonać

#include <avr/io.h>

#define MAXPTR 5

void migaj_LED1(void);
void migaj_LED2(void);
void migaj_LED3(void);
void migaj_LED4(void);
void migaj_LED5(void);

typedef void (*Twsk) (void); // nowy typ wskaznika na funkcje typu void

typedef struct{
	Twsk f;
	char c;
}io_t;

io_t fun[] = {
		{.c = 'A',.f = migaj_LED1},
		{.c = 'D',.f = migaj_LED2},
		{.c = 'E',.f = migaj_LED3},
		{.c = 'x',.f = migaj_LED4},
		{.c = 'q',.f = migaj_LED5}
		
};

int main(void){
	 
	char cl = uart_getc();
	
	for(;;){
		
		for(uint8_t i = 0; i<MAXPTR; i++){
			if(cl == fun[i].c){ // sprawdzamy co przyleciało z uart jesli znak znajduje sie w tablicy to nast krok 
				if(fun[i].f){
					fun[i].f(); //sprawdzenie i wywolanie funkcji
					break; // dalej nie trzeba szukac
				}
			}
		}
	}
}		
		

void migaj_LED1(void){
// migamy led
}
void migaj_LED2(void){
	// migamy led
}
void migaj_LED3(void){
	// migamy led
}
void migaj_LED4(void){
	// migamy led
}
void migaj_LED5(void){
	// migamy led
}

 

Edytowano przez _LM_
  • Pomogłeś! 1
Link do komentarza
Share on other sites

(edytowany)

Wrócę to kawałka kodu, gdzie za pomocą przycisku skakałem to funkcji.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd.h"
#include "twi.h"

uint8_t licznik;

int main(){

	microInit();
	i2cSetBitrate(100);
	sei();
	lcd_init();
	lcd_cls();
	czas.YY = 2000;

	// set time
//save_timedata(&time);
// INT0 interrupt
	EICRA |= (1<<ISC01);
	EIMSK |= (1<<INT0);
	PORTD |= (1<<PD2);

	while(1){

		if (!keylock1 && !(PIND & (1<<KEY1))){
			keylock1=1;
			licznik++;
			if (licznik > 3)licznik=0;
		}else if (keylock1 && (PIND & (1<<KEY1)))keylock1++;

		
		switch(licznik){
		case 0:RTC_Event();
			break;
		case 1:display_menu2(&czas);
			break;
		case 2:display_menu();
			break;
		case 3:zapisano_czas();
			break;
		}
 }
}

Rozumiem, ze mogę to zrobić tak. 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd.h"
#include "twi.h"

uint8_t licznik;
typedef void (*Twsk)(void); // nowy typ wskaznika na funkcje
Twsk wskTab[4]; // tablica wskazników na funkcje
enum{wyswietl_czas,ustaw_date,ustaw_czas,zapisz_dane}; // typ wyliczeniowy dla czytelnosci
int main(){
	// przypisanie wskaznikow do funkcji
	wskTab[wyswietl_czas]=RTC_Event;
	wskTab[ustaw_date]=(Twsk)display_menu2; // Jawne rzutowanie bo jedna funkcja przyjmowała
	wskTab[ustaw_czas]=display_menu; 		//jako argument wskaźnik do struktury Ttime.
	wskTab[zapisz_dane]=zapisano_czas;

	microInit();
	i2cSetBitrate(100);
	lcd_init();
	lcd_cls();

	EICRA |= (1<<ISC01);
	EIMSK |= (1<<INT0);
	PORTD |= (1<<PD2);
	czas.YY = 2000;
	sei();
	while(1){

		if (!keylock1 && !(PIND & (1<<KEY1))){
			keylock1=1;
			licznik++;
			if (licznik > 3)licznik=0;
		}else if (keylock1 && (PIND & (1<<KEY1)))keylock1++;

	if(wskTab[licznik])wskTab[licznik](); // jesli przypisano cos do ptr, wywolaj ptr na funkcje.
 }
}

 

Edytowano przez Krawi92
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.