Saya selalu sedikit bingung ketika saya membaca kode orang lain yang telah mengetik pointer untuk fungsi dengan argumen. Saya ingat bahwa saya perlu waktu beberapa saat untuk mendapatkan definisi seperti itu ketika mencoba memahami algoritma numerik yang ditulis dalam C beberapa waktu lalu. Jadi, bisakah Anda membagikan tips dan pemikiran Anda tentang cara menulis typedef yang bagus untuk fungsi pointer (Do's and Do's not), mengapa bermanfaat dan bagaimana memahami karya orang lain? Terima kasih!
c
function-pointers
typedef
nbro
sumber
sumber
Jawaban:
Pertimbangkan
signal()
fungsi dari standar C:Jelas sangat jelas - ini adalah fungsi yang mengambil dua argumen, integer dan pointer ke fungsi yang mengambil integer sebagai argumen dan tidak menghasilkan apa-apa, dan itu (
signal()
) mengembalikan pointer ke fungsi yang mengambil integer sebagai argumen dan mengembalikan tidak ada.Jika Anda menulis:
maka Anda dapat mendeklarasikan
signal()
sebagai:Ini berarti hal yang sama, tetapi biasanya dianggap lebih mudah dibaca. Lebih jelas bahwa fungsi mengambil a
int
dan aSignalHandler
dan mengembalikan aSignalHandler
.Butuh sedikit membiasakan diri. Satu hal yang tidak dapat Anda lakukan, adalah menulis fungsi pengendali sinyal menggunakan
SignalHandler
typedef
definisi fungsi.Saya masih dari old-school yang lebih suka memanggil fungsi pointer sebagai:
Sintaks modern hanya menggunakan:
Saya bisa melihat mengapa itu bekerja - saya hanya lebih suka tahu bahwa saya perlu mencari di mana variabel diinisialisasi daripada untuk fungsi yang disebut
functionpointer
.Sam berkomentar:
Mari kita coba lagi. Yang pertama diangkat langsung dari standar C - saya mengetik ulang, dan memeriksa bahwa saya memiliki tanda kurung yang benar (tidak sampai saya memperbaikinya - ini adalah cookie yang sulit untuk diingat).
Pertama-tama, ingat bahwa
typedef
memperkenalkan alias untuk suatu tipe. Jadi, alias adalahSignalHandler
, dan tipenya adalah:Bagian 'mengembalikan apa-apa' dieja
void
; argumen yang merupakan bilangan bulat adalah (saya percaya) cukup jelas. Notasi berikut hanya (atau tidak) bagaimana C spell pointer berfungsi mengambil argumen seperti yang ditentukan dan mengembalikan tipe yang diberikan:Setelah membuat jenis penangan sinyal, saya bisa menggunakannya untuk mendeklarasikan variabel dan sebagainya. Sebagai contoh:
Perhatian Bagaimana cara menghindari menggunakan
printf()
pengatur sinyal?Jadi, apa yang telah kita lakukan di sini - selain menghilangkan 4 header standar yang diperlukan untuk membuat kode dikompilasi dengan bersih?
Dua fungsi pertama adalah fungsi yang mengambil integer tunggal dan tidak mengembalikan apa pun. Salah satu dari mereka sebenarnya tidak kembali sama sekali,
exit(1);
tetapi yang lain kembali setelah mencetak pesan. Ketahuilah bahwa standar C tidak mengizinkan Anda melakukan banyak hal di dalam penangan sinyal; POSIX sedikit lebih murah hati dalam apa yang diizinkan, tetapi secara resmi tidak memberi sanksi pada panggilanfprintf()
. Saya juga mencetak nomor sinyal yang diterima. Dalamalarm_handler()
fungsi tersebut, nilainya akan selaluSIGALRM
seperti itu adalah satu-satunya sinyal yang menjadi handler, tetapisignal_handler()
mungkin mendapatkanSIGINT
atauSIGQUIT
sebagai nomor sinyal karena fungsi yang sama digunakan untuk keduanya.Lalu saya membuat array struktur, di mana setiap elemen mengidentifikasi nomor sinyal dan pawang yang dipasang untuk sinyal itu. Saya memilih untuk mengkhawatirkan 3 sinyal; Saya sering khawatir
SIGHUP
,SIGPIPE
danSIGTERM
juga dan apakah mereka didefinisikan (#ifdef
kompilasi bersyarat), tetapi itu hanya mempersulit. Saya mungkin juga menggunakan POSIXsigaction()
sebagai gantisignal()
, tapi itu masalah lain; mari kita tetap dengan apa yang kita mulai.The
main()
iterates fungsi di atas daftar penangan yang akan diinstal. Untuk setiap penangan, pertama-tama panggilansignal()
untuk mencari tahu apakah proses saat ini mengabaikan sinyal, dan saat melakukannya, dipasangSIG_IGN
sebagai penangan, yang memastikan bahwa sinyal tetap diabaikan. Jika sinyal sebelumnya tidak diabaikan, maka ia akan memanggilsignal()
lagi, kali ini untuk memasang penangan sinyal yang disukai. (Nilai lainnya mungkinSIG_DFL
, penangan sinyal default untuk sinyal.) Karena panggilan pertama ke 'sinyal ()' mengatur penangan keSIG_IGN
dansignal()
mengembalikan penangan kesalahan sebelumnya, nilaiold
setelahif
pernyataan harusSIG_IGN
- maka pernyataan. (Yah, bisa jadi ituSIG_ERR
jika ada yang salah secara dramatis - tapi kemudian saya akan belajar tentang hal itu dari penembakan tegas.)Program kemudian melakukan tugasnya dan keluar secara normal.
Perhatikan bahwa nama fungsi dapat dianggap sebagai penunjuk ke fungsi dengan tipe yang sesuai. Ketika Anda tidak menerapkan kurung fungsi-panggilan - seperti dalam inisialisasi, misalnya - nama fungsi menjadi penunjuk fungsi. Ini juga mengapa masuk akal untuk menjalankan fungsi melalui
pointertofunction(arg1, arg2)
notasi; Ketika Anda melihatalarm_handler(1)
, Anda dapat mempertimbangkan bahwa itualarm_handler
adalah pointer ke fungsi dan oleh karena itualarm_handler(1)
adalah doa fungsi melalui pointer fungsi.Jadi, sejauh ini, saya telah menunjukkan bahwa
SignalHandler
variabel relatif lurus ke depan untuk digunakan, selama Anda memiliki beberapa jenis nilai yang tepat untuk ditugaskan padanya - yang merupakan apa yang disediakan oleh dua fungsi pengendali sinyal.Sekarang kita kembali ke pertanyaan - bagaimana kedua deklarasi untuk
signal()
saling berhubungan.Mari kita tinjau deklarasi kedua:
Jika kami mengubah nama fungsi dan jenisnya seperti ini:
Anda tidak akan memiliki masalah menafsirkan ini sebagai fungsi yang mengambil argumen
int
a dandouble
sebagai dan mengembalikandouble
nilai (kan? Anda mungkin lebih baik tidak mengaku jika itu bermasalah - tapi mungkin Anda harus berhati-hati dalam mengajukan pertanyaan dengan keras seperti ini jika ini masalah).Sekarang, alih-alih menjadi
double
,signal()
fungsi mengambilSignalHandler
argumen kedua, dan mengembalikannya sebagai hasilnya.Mekanika yang dengannya juga dapat diperlakukan sebagai:
sulit untuk dijelaskan - jadi saya mungkin akan mengacaukannya. Kali ini saya telah memberikan nama parameter - meskipun nama tidak kritis.
Secara umum, dalam C, mekanisme deklarasi adalah sedemikian rupa sehingga jika Anda menulis:
maka ketika Anda menulis
var
itu mewakili nilai yang diberikantype
. Sebagai contoh:Dalam standar,
typedef
diperlakukan sebagai kelas penyimpanan dalam tata bahasa, agak miripstatic
danextern
kelas penyimpanan.berarti bahwa ketika Anda melihat variabel tipe
SignalHandler
(misalkan alarm_handler) dipanggil sebagai:hasilnya telah
type void
- tidak ada hasil. Dan(*alarm_handler)(-1);
merupakan doaalarm_handler()
dengan argumen-1
.Jadi, jika kita menyatakan:
itu berarti:
mewakili nilai void. Dan oleh karena itu:
setara. Sekarang,
signal()
lebih kompleks karena tidak hanya mengembalikan argumenSignalHandler
, tetapi juga menerimaSignalHandler
argumen int dan a sebagai:Jika itu masih membingungkan Anda, saya tidak yakin bagaimana cara membantu - itu masih pada beberapa tingkat misterius bagi saya, tetapi saya sudah terbiasa dengan cara kerjanya dan karena itu dapat memberi tahu Anda bahwa jika Anda tetap menggunakannya selama 25 tahun lagi atau lebih, itu akan menjadi kebiasaan Anda (dan mungkin bahkan sedikit lebih cepat jika Anda pintar).
sumber
extern void (*signal(int, void(*)(int)))(int);
berarti satusignal(int, void(*)(int))
fungsi akan mengembalikan fungsi pointer kevoid f(int)
. Saat Anda ingin menentukan pointer fungsi sebagai nilai balik , sintaksnya menjadi rumit. Anda harus menempatkan tipe nilai pengembalian ke kiri dan daftar argumen di sebelah kanan , sementara itu adalah tengah yang Anda tetapkan. Dan dalam hal ini,signal()
fungsi itu sendiri mengambil penunjuk fungsi sebagai parameternya, yang memperumit banyak hal. Berita baiknya adalah, jika Anda dapat membaca yang ini, the Force sudah bersama Anda. :)&
di depan nama fungsi? Ini sama sekali tidak perlu; sia-sia, bahkan. Dan jelas bukan "sekolah tua". Sekolah lama menggunakan nama fungsi yang sederhana dan sederhana.Pointer fungsi seperti pointer lainnya, tetapi menunjuk ke alamat fungsi alih-alih alamat data (pada heap atau stack). Seperti halnya pointer apa pun, itu perlu diketik dengan benar. Fungsi ditentukan oleh nilai baliknya dan jenis parameter yang diterimanya. Jadi untuk sepenuhnya menggambarkan suatu fungsi, Anda harus memasukkan nilai baliknya dan jenis dari setiap parameter diterima. Ketika Anda mengetik definisi seperti itu, Anda memberinya 'nama yang bersahabat' yang membuatnya lebih mudah untuk membuat dan referensi referensi menggunakan definisi itu.
Jadi misalnya anggap Anda memiliki fungsi:
maka typedef berikut:
dapat digunakan untuk menunjuk ke
doMulitplication
fungsi ini . Ini hanya mendefinisikan pointer ke fungsi yang mengembalikan float dan mengambil dua parameter, masing-masing tipe float. Definisi ini memiliki nama yang ramahpt2Func
. Catatan yangpt2Func
dapat menunjuk ke fungsi APA SAJA yang mengembalikan float dan mengambil 2 float.Jadi Anda bisa membuat pointer yang menunjuk ke fungsi doMultiplication sebagai berikut:
dan Anda dapat menjalankan fungsi menggunakan pointer ini sebagai berikut:
Ini bacaan yang bagus: http://www.newty.de/fpt/index.html
sumber
pt2Func myFnPtr = &doMultiplication;
alih - alihpt2Func *myFnPtr = &doMultiplication;
sepertimyFnPtr
yang sudah menjadi pointer.myFunPtr
sudah menjadi penunjuk fungsi jadi gunakanpt2Func myFnPtr = &doMultiplication;
Cara yang sangat mudah untuk memahami typedef dari pointer fungsi:
sumber
cdecl
adalah alat yang hebat untuk menguraikan sintaks aneh seperti deklarasi fungsi pointer. Anda dapat menggunakannya untuk menghasilkannya juga.Sejauh tips untuk membuat deklarasi rumit lebih mudah diurai untuk pemeliharaan di masa depan (sendiri atau orang lain), saya sarankan membuat
typedef
potongan kecil dan menggunakan potongan-potongan kecil sebagai blok bangunan untuk ekspresi yang lebih besar dan lebih rumit. Sebagai contoh:daripada:
cdecl
dapat membantu Anda dengan hal-hal ini:Dan (sebenarnya) adalah persis bagaimana saya menghasilkan kekacauan gila di atas.
sumber
Output dari ini adalah:
22
6
Perhatikan bahwa, definer math_func yang sama telah digunakan untuk mendeklarasikan kedua fungsi tersebut.
Pendekatan typedef yang sama dapat digunakan untuk extern struct. (Menggunakan sturuct di file lain.)
sumber
Gunakan typedef untuk mendefinisikan tipe yang lebih rumit, yaitu pointer fungsi
Saya akan mengambil contoh mendefinisikan mesin-negara di C
sekarang kita telah mendefinisikan tipe yang disebut action_handler yang mengambil dua pointer dan mengembalikan sebuah int
tentukan state-machine Anda
Penunjuk fungsi untuk tindakan terlihat seperti tipe sederhana dan typedef terutama melayani tujuan ini.
Semua penangan acara saya sekarang harus mematuhi jenis yang ditentukan oleh action_handler
Referensi:
Pemrograman C ahli oleh Linden
sumber
Ini adalah contoh paling sederhana dari pointer fungsi dan array fungsi pointer yang saya tulis sebagai latihan.
sumber