Baru-baru ini saya memutuskan bahwa saya akhirnya harus belajar C / C ++, dan ada satu hal yang tidak terlalu saya mengerti tentang pointer atau lebih tepatnya, definisi mereka.
Bagaimana dengan contoh ini:
int* test;
int *test;
int * test;
int* test,test2;
int *test,test2;
int * test,test2;
Sekarang, menurut pemahaman saya, tiga kasus pertama semuanya melakukan hal yang sama: Test bukanlah int, tetapi pointer ke satu.
Kumpulan contoh kedua sedikit lebih rumit. Dalam kasus 4, baik test dan test2 akan menjadi pointer ke int, sedangkan dalam kasus 5, hanya test yang merupakan pointer, sedangkan test2 adalah int "nyata". Bagaimana dengan kasus 6? Sama seperti kasus 5?
c++
c
pointers
declaration
Michael Stum
sumber
sumber
int*test;
?Foo<Bar<char>>
yang>>
harus ditulis> >
agar tidak diperlakukan sebagai-shift kanan.++
operator increment tidak dapat dipisahkan oleh spasi, pengenal tidak dapat dipisahkan oleh spasi (dan hasilnya masih legal untuk compiler tetapi dengan perilaku runtime yang tidak ditentukan). Situasi sebenarnya sangat sulit untuk didefinisikan mengingat kekacauan sintaks C / C ++.Jawaban:
4, 5, dan 6 adalah hal yang sama, hanya tes adalah sebuah pointer. Jika Anda menginginkan dua petunjuk, Anda harus menggunakan:
int *test, *test2;
Atau, bahkan lebih baik (untuk memperjelas semuanya):
int* test; int* test2;
sumber
Ruang putih di sekitar tanda bintang tidak memiliki arti. Ketiganya memiliki arti yang sama:
int* test; int *test; int * test;
"
int *var1, var2
" Adalah sintaksis jahat yang hanya dimaksudkan untuk membingungkan orang dan harus dihindari. Ini berkembang menjadi:int *var1; int var2;
sumber
int*test
.int *test
( google-styleguide.googlecode.com/svn/trunk/… ).Banyak pedoman pengkodean menyarankan Anda hanya mendeklarasikan satu variabel per baris . Ini untuk menghindari kebingungan apa pun yang Anda alami sebelum mengajukan pertanyaan ini. Sebagian besar programmer C ++ yang pernah bekerja dengan saya tampaknya tetap berpegang pada ini.
Sedikit tambahan yang saya tahu, tetapi sesuatu yang menurut saya berguna adalah membaca deklarasi secara terbalik.
int* test; // test is a pointer to an int
Ini mulai bekerja dengan sangat baik, terutama ketika Anda mulai mendeklarasikan pointer const dan menjadi sulit untuk mengetahui apakah pointer itu adalah const, atau apakah hal yang ditunjuk pointer itu adalah const.
int* const test; // test is a const pointer to an int int const * test; // test is a pointer to a const int ... but many people write this as const int * test; // test is a pointer to an int that's const
sumber
Gunakan "Aturan Spiral Searah Jarum Jam" untuk membantu mengurai deklarasi C / C ++;
Juga, deklarasi harus berada dalam pernyataan terpisah jika memungkinkan (yang seringkali benar).
sumber
int* x;
gaya daripadaint *x;
gaya tradisional . Tentu saja, jarak tidak menjadi masalah bagi kompiler, tetapi itu mempengaruhi manusia. Penolakan sintaks yang sebenarnya mengarah pada aturan gaya yang dapat mengganggu dan membingungkan pembaca.Seperti yang disebutkan orang lain, 4, 5, dan 6 adalah sama. Seringkali, orang-orang menggunakan contoh-contoh ini untuk membuat argumen bahwa
*
variabel tersebut dimiliki oleh variabel, bukan tipe. Meskipun ini adalah masalah gaya, ada beberapa perdebatan mengenai apakah Anda harus memikirkan dan menulisnya seperti ini:int* x; // "x is a pointer to int"
atau begini:
int *x; // "*x is an int"
FWIW Saya di kubu pertama, tapi alasan orang lain membuat argumen untuk bentuk kedua adalah karena (kebanyakan) memecahkan masalah khusus ini:
int* x,y; // "x is a pointer to int, y is an int"
yang berpotensi menyesatkan; sebaliknya Anda juga akan menulis
int *x,y; // it's a little clearer what is going on here
atau jika Anda benar-benar menginginkan dua petunjuk,
int *x, *y; // two pointers
Secara pribadi, saya katakan simpan itu ke satu variabel per baris, maka tidak masalah gaya mana yang Anda sukai.
sumber
int *MyFunc(void)
? a*MyFunc
adalah fungsi yang mengembalikanint
? tidak. Jelas kita harus menulisint* MyFunc(void)
, dan sayMyFunc
adalah fungsi yang mengembalikan aint*
. Jadi bagi saya ini jelas, aturan parsing tata bahasa C dan C ++ salah untuk deklarasi variabel. mereka harus menyertakan kualifikasi penunjuk sebagai bagian dari tipe bersama untuk seluruh urutan koma.*MyFunc()
adalah sebuahint
. Masalah dengan sintaks C adalah mencampurkan prefiks dan sintaks postfix - jika hanya postfix yang digunakan, tidak akan ada kebingungan.int const* x;
, yang menurut saya menyesatkana * x+b * y
.#include <type_traits> std::add_pointer<int>::type test, test2;
sumber
#include <windows.h>LPINT test, test2;
Dalam 4, 5 dan 6,
test
selalu merupakan penunjuk dantest2
bukan penunjuk. Spasi putih (hampir) tidak pernah signifikan dalam C ++.sumber
Alasan di C adalah Anda mendeklarasikan variabel seperti yang Anda gunakan. Sebagai contoh
char *a[100];
mengatakan itu
*a[42]
akan menjadichar
. Dana[42]
penunjuk karakter. Dan dengan demikiana
adalah array pointer karakter.Ini karena penulis kompilator asli ingin menggunakan parser yang sama untuk ekspresi dan deklarasi. (Bukan alasan yang masuk akal untuk pilihan desain bahasa)
sumber
char* a[100];
juga menyimpulkan bahwa*a[42];
akan menjadi achar
dana[42];
penunjuk karakter.a[42]
is achar
pointer, and*a[42]
is a char.Menurut saya, jawabannya adalah KEDUA, tergantung situasinya. Secara umum, IMO, lebih baik meletakkan tanda bintang di sebelah nama penunjuk, daripada jenisnya. Bandingkan misalnya:
int *pointer1, *pointer2; // Fully consistent, two pointers int* pointer1, pointer2; // Inconsistent -- because only the first one is a pointer, the second one is an int variable // The second case is unexpected, and thus prone to errors
Mengapa kasus kedua tidak konsisten? Karena eg
int x,y;
mendeklarasikan dua variabel dengan tipe yang sama tetapi tipenya hanya disebutkan sekali dalam deklarasi. Ini menciptakan preseden dan perilaku yang diharapkan. Danint* pointer1, pointer2;
tidak konsisten dengan itu karena dideklarasikanpointer1
sebagai pointer, tetapipointer2
merupakan variabel integer. Jelas rentan terhadap kesalahan dan, karenanya, harus dihindari (dengan meletakkan tanda bintang di sebelah nama penunjuk, bukan jenisnya).Namun , ada beberapa pengecualian di mana Anda mungkin tidak dapat menempatkan tanda bintang di sebelah nama objek (dan di mana Anda meletakkannya) tanpa mendapatkan hasil yang tidak diinginkan - misalnya:
MyClass *volatile MyObjName
void test (const char *const p) // const value pointed to by a const pointer
Akhirnya, dalam beberapa kasus, mungkin lebih jelas untuk menempatkan tanda bintang di sebelah nama jenis , misalnya:
void* ClassName::getItemPtr () {return &item;} // Clear at first sight
sumber
Saya akan mengatakan bahwa konvensi awal adalah meletakkan bintang di sisi nama penunjuk (sisi kanan deklarasi
dalam bahasa pemrograman c oleh Dennis M. Ritchie bintang-bintang berada di sisi kanan pernyataan.
dengan melihat kode sumber linux di https://github.com/torvalds/linux/blob/master/init/main.c kita dapat melihat bahwa bintang juga ada di sisi kanan.
Anda dapat mengikuti aturan yang sama, tetapi itu bukan masalah jika Anda meletakkan bintang di sisi tipe. Ingatlah bahwa konsistensi itu penting, jadi selalu tetapi bintang di sisi yang sama terlepas dari sisi mana yang Anda pilih.
sumber
Pointer adalah pengubah tipe. Yang terbaik adalah membacanya dari kanan ke kiri untuk lebih memahami bagaimana tanda bintang mengubah jenisnya. 'int *' dapat dibaca sebagai "pointer ke int '. Dalam beberapa deklarasi, Anda harus menentukan bahwa setiap variabel adalah sebuah pointer atau akan dibuat sebagai variabel standar.
1,2 dan 3) Tes bertipe (int *). Spasi putih tidak masalah.
4,5 dan 6) Tes bertipe (int *). Test2 adalah tipe int. Sekali lagi, spasi tidak penting.
sumber
Ada tiga bagian dari teka-teki ini.
Bagian pertama adalah bahwa spasi dalam C dan C ++ biasanya tidak signifikan selain memisahkan token yang berdekatan yang tidak dapat dibedakan.
Selama tahap preprocessing, teks sumber dipecah menjadi urutan token - pengidentifikasi, punctuator, literal numerik, literal string, dll. Urutan token tersebut kemudian dianalisis untuk sintaks dan artinya. Tokeniser bersifat "serakah" dan akan membuat token valid terpanjang yang memungkinkan. Jika Anda menulis sesuatu seperti
tokenizer hanya melihat dua token - pengenal
inttest
diikuti oleh tanda pengenal;
. Itu tidak dikenaliint
sebagai kata kunci terpisah pada tahap ini (yang terjadi nanti dalam proses). Jadi, agar baris dibaca sebagai deklarasi integer bernamatest
, kita harus menggunakan spasi untuk memisahkan token pengenal:int test;
The
*
karakter bukan bagian dari identifier apapun; itu adalah token terpisah (punctuator) dengan sendirinya. Jadi jika Anda menulisint*test;
kompilator melihat 4 token terpisah -
int
,*
,test
, dan;
. Jadi, spasi putih tidak signifikan dalam deklarasi pointer, dan semuaint *test; int* test; int*test; int * test;
ditafsirkan dengan cara yang sama.
Bagian kedua dari teka-teki adalah bagaimana deklarasi sebenarnya bekerja di C dan C ++ 1 . Deklarasi dipecah menjadi dua bagian utama - urutan penentu deklarasi (penentu kelas penyimpanan, penentu jenis, penentu jenis, dll.) Diikuti dengan daftar deklarator (mungkin diinisialisasi) yang dipisahkan koma . Dalam deklarasi
unsigned long int a[10]={0}, *p=NULL, f(void);
specifiers deklarasi yang
unsigned long int
dan declarators yanga[10]={0}
,*p=NULL
, danf(void)
. Deklarator memperkenalkan nama hal yang dideklarasikan (a
,,p
danf
) bersama dengan informasi tentang array-ness, pointer-ness, dan function-ness. Deklarator mungkin juga memiliki penginisialisasi terkait.Jenisnya
a
adalah "larik 10 elemenunsigned long int
". Jenis tersebut sepenuhnya ditentukan oleh kombinasi penentu deklarasi dan deklarator, dan nilai awal ditentukan dengan penginisialisasi={0}
. Demikian pula, tipep
adalah "pointer tounsigned long int
", dan sekali lagi tipe itu ditentukan oleh kombinasi penentu deklarasi dan deklarator, dan diinisialisasi keNULL
. Dan jenisnyaf
adalah "fungsi yang kembaliunsigned long int
" dengan alasan yang sama.Ini adalah kunci - tidak ada penentu tipe "penunjuk-ke" , sama seperti tidak ada penentu jenis "larik-dari", sama seperti tidak ada penentu jenis yang "mengembalikan fungsi". Kami tidak dapat mendeklarasikan sebuah array sebagai
int[10] a;
karena operan
[]
operatornyaa
, bukanint
. Begitu pula di deklarasiint* p;
operannya
*
adalahp
, bukanint
. Tetapi karena operator indirection adalah unary dan whitespace tidak signifikan, compiler tidak akan mengeluh jika kita menuliskannya dengan cara ini. Namun, itu selalu diartikan sebagaiint (*p);
.Karena itu, jika Anda menulis
int* p, q;
operan dari
*
adalahp
, jadi itu akan diartikan sebagaiint (*p), q;
Jadi, semuanya
int *test1, test2; int* test1, test2; int * test1, test2;
melakukan hal yang sama - dalam ketiga kasus,
test1
adalah operan*
dan karenanya memiliki tipe "pointer toint
", sementaratest2
memiliki tipeint
.Deklarator bisa menjadi rumit secara sewenang-wenang. Anda dapat memiliki array pointer:
T *a[N];
Anda dapat memiliki pointer ke array:
T (*a)[N];
Anda dapat memiliki fungsi yang mengembalikan pointer:
T *f(void);
Anda dapat memiliki petunjuk ke fungsi:
T (*f)(void);
Anda dapat memiliki array pointer ke fungsi:
T (*a[N])(void);
Anda dapat memiliki fungsi yang mengembalikan pointer ke array:
T (*f(void))[N];
Anda dapat memiliki fungsi yang mengembalikan pointer ke array pointer ke fungsi yang mengembalikan pointer ke
T
:T *(*(*f(void))[N])(void); // yes, it's eye-stabby. Welcome to C and C++.
dan kemudian Anda memiliki
signal
:void (*signal(int, void (*)(int)))(int);
yang berbunyi
signal -- signal signal( ) -- is a function taking signal( ) -- unnamed parameter signal(int ) -- is an int signal(int, ) -- unnamed parameter signal(int, (*) ) -- is a pointer to signal(int, (*)( )) -- a function taking signal(int, (*)( )) -- unnamed parameter signal(int, (*)(int)) -- is an int signal(int, void (*)(int)) -- returning void (*signal(int, void (*)(int))) -- returning a pointer to (*signal(int, void (*)(int)))( ) -- a function taking (*signal(int, void (*)(int)))( ) -- unnamed parameter (*signal(int, void (*)(int)))(int) -- is an int void (*signal(int, void (*)(int)))(int); -- returning void
dan ini hanya menggores permukaan dari apa yang mungkin. Tetapi perhatikan bahwa array-ness, pointer-ness, dan function-ness selalu menjadi bagian dari deklarator, bukan penentu tipe.
Satu hal yang harus diperhatikan -
const
dapat memodifikasi tipe pointer dan tipe menunjuk-ke:const int *p; int const *p;
Kedua hal di atas mendeklarasikan
p
sebagai pointer ke sebuahconst int
objek. Anda dapat menulis nilai baru untukp
menyetelnya agar mengarah ke objek yang berbeda:const int x = 1; const int y = 2; const int *p = &x; p = &y;
tetapi Anda tidak dapat menulis ke objek yang diarahkan ke:
*p = 3; // constraint violation, the pointed-to object is const
Namun,
int * const p;
mendeklarasikan
p
sebagaiconst
pointer ke non-constint
; Anda dapat menulis ke hal yangp
ditunjukkanint x = 1; int y = 2; int * const p = &x; *p = 3;
tetapi Anda tidak dapat menetapkan
p
untuk menunjuk ke objek lain:p = &y; // constraint violation, p is const
Yang membawa kita ke bagian ketiga dari teka-teki - mengapa deklarasi disusun seperti ini.
Maksudnya adalah bahwa struktur deklarasi harus mencerminkan struktur ekspresi dalam kode ("deklarasi meniru penggunaan"). Sebagai contoh, misalkan kita memiliki array pointer untuk
int
dinamaiap
, dan kita ingin mengaksesint
nilai yang ditunjukkan olehi
elemen 'th. Kami akan mengakses nilai itu sebagai berikut:printf( "%d", *ap[i] );
The ekspresi
*ap[i]
memiliki tipeint
; dengan demikian, pernyataanap
tertulis sebagaiint *ap[N]; // ap is an array of pointer to int, fully specified by the combination // of the type specifier and declarator
Deklarator
*ap[N]
memiliki struktur yang sama dengan ekspresi*ap[i]
. Operator*
dan[]
berperilaku dengan cara yang sama dalam deklarasi yang mereka lakukan dalam ekspresi -[]
memiliki prioritas yang lebih tinggi daripada unary*
, jadi operand of*
adalahap[N]
(diurai sebagai*(ap[N])
).Sebagai contoh lain, misalkan kita memiliki pointer ke array
int
bernamapa
dan kita ingin mengakses nilaii
elemen 'th. Kami akan menulisnya sebagaiprintf( "%d", (*pa)[i] );
Jenis ekspresi
(*pa)[i]
adalahint
, sehingga deklarasi ditulis sebagaiint (*pa)[N];
Sekali lagi, aturan prioritas dan asosiatif yang sama berlaku. Dalam hal ini, kami tidak ingin mendereferensi
i
elemen 'thpa
, kami ingin mengaksesi
elemen' thpa
poin apa , jadi kami harus secara eksplisit mengelompokkan*
operator denganpa
.Operator
*
,[]
dan()
semuanya adalah bagian dari ekspresi dalam kode, jadi semuanya adalah bagian dari deklarator dalam deklarasi. Deklarator memberi tahu Anda cara menggunakan objek dalam ekspresi. Jika Anda memiliki deklarasi sepertiint *p;
, itu memberi tahu Anda bahwa ekspresi*p
dalam kode Anda akan menghasilkanint
nilai. Dengan ekstensi, ini memberitahu Anda bahwa ekspresip
menghasilkan nilai tipe "pointer keint
", atauint *
.Jadi, bagaimana dengan pemeran dan
sizeof
ekspresi, di mana kita menggunakan hal-hal seperti(int *)
atausizeof (int [10])
atau seperti itu? Bagaimana cara membaca sesuatu sepertivoid foo( int *, int (*)[10] );
Tidak ada deklarator, bukankah operator
*
and[]
memodifikasi tipe secara langsung?Ya, tidak - masih ada deklarator, hanya dengan pengenal kosong (dikenal sebagai deklarator abstrak ). Jika kami mewakili sebuah identifier kosong dengan λ simbol, maka kita dapat membaca hal-hal sebagai
(int *λ)
,sizeof (int λ[10])
, danvoid foo( int *λ, int (*λ)[10] );
dan mereka berperilaku persis seperti deklarasi lainnya.
int *[10]
mewakili larik yang terdiri dari 10 penunjuk, sedangkanint (*)[10]
mewakili penunjuk ke larik.Dan sekarang bagian opini dari jawaban ini. Saya tidak menyukai konvensi C ++ yang menyatakan pointer sederhana sebagai
T* p;
dan menganggapnya sebagai praktik yang buruk karena alasan berikut:
T* p, q;
, semua duplikat dari pertanyaan - pertanyaan itu, dll.);T* a[N]
asimetris dengan penggunaan (kecuali jika Anda terbiasa menulis* a[i]
);T* p
konvensi dengan rapi, yang ... tidak );Pada akhirnya, ini hanya menunjukkan pemikiran yang bingung tentang bagaimana sistem tipe kedua bahasa bekerja.
Ada alasan bagus untuk menyatakan item secara terpisah; mengatasi praktik yang buruk (
T* p, q;
) bukanlah salah satunya. Jika Anda menulis deklarator dengan benar (T *p, q;
), kemungkinan kecil Anda akan menimbulkan kebingungan.Saya menganggapnya mirip dengan sengaja menulis semua
for
loop sederhana Anda sebagaii = 0; for( ; i < N; ) { ... i++ }
Valid secara sintaksis, tetapi membingungkan, dan maksudnya cenderung disalahartikan. Namun,
T* p;
konvensi tersebut mengakar dalam komunitas C ++, dan saya menggunakannya dalam kode C ++ saya sendiri karena konsistensi di seluruh basis kode adalah hal yang baik, tetapi itu membuat saya gatal setiap kali melakukannya.sumber
Sebuah aturan praktis yang baik, banyak orang tampaknya memahami konsep ini dengan: Dalam C ++ banyak makna semantik diturunkan oleh pengikatan kiri kata kunci atau pengenal.
Ambil contoh:
int const bla;
Const berlaku untuk kata "int". Hal yang sama berlaku untuk tanda bintang penunjuk, tanda tersebut berlaku untuk kata kunci di sebelah kiri. Dan nama variabel sebenarnya? Yup, itu dinyatakan dengan apa yang tersisa darinya.
sumber