Bây h xét 3 dòng lệnh sau
int foo[5]; // foo là mãng gồm 5 phần tử kiểu int
char *foo; // foo là con trỏ tới kiểu char
double foo(); // foo là 1 hàm có kiểu trả về là double
Đơn giản nhỉ T_T, thế còn dòng này thì sao :
char *(*(**foo[][8])())[]; //oops, cái quái gì thế này ?????
Mặc dù sẽ chẳng có ai khai báo 1 kiểu lạ lùng thế này, nhưng nếu chúng ta hiểu rõ bản chất thì dùng có gặp câu lệnh khủng bố hơn thế chúng ta vẫn có thể dễ dàng hiểu được.Trong C++, chúng ta phải hiểu rõ 3 toán tử sau :
Toán tử * : ý nghĩa là ( trừ ý nghĩa dấu nhân ra ) nó đang trỏ tới 1 cái gì đó.
Toán tử [] : ý nghĩa là mãng của gì đó.
Toán tử () : hàm trả về cái gì đó, mặc dù trong đó sẽ có hoặc không có tham số, nhưng ta chú ý là dù có hay không toán tử này sẽ luôn ở bên phải 1 hàm nào đó, và 1 chú ý nữa trong 1 câu lệnh phức tạp thì dấu () thường ít có vai trò quan trọng hơn các toán tử còn lại, tại sao mình nói như vậy, đọc tiếp bạn sẽ hiểu ngay ...
Bây h chúng ta sẽ tìm hiểu độ ưu tiên của 3 toán tử mà chúng ta đã đề cập ở trên. Dễ thấy rằng thằng [] và () có độ ưu tiên hơn thằng *. Và chắc chắn điều này sẽ là 1 mấu chốt quan trọng trong việc chúng ta sẽ đọc và hiểu những khai báo phức tạp như thế nào .
Chúng ta sẽ có 1 quy luật nằm lòng như sau :
Đi về bên phải khi có thể và đi về bên trái khi bắt buộc.
Bây h còn chờ gì nữa nào, hãy thử đọc ví dụ sau :
long **foo[7];
Chú ý phần nào chúng ta đọc xong chúng ta sẽ xoá sổ chúng, để tiện cho việc theo dõi thằng nào đọc xong mình sẽ cho nó màu đen T_T.long **foo [7];
-> foo... có kiểu là long
long **foo [7];
Tại điểm này chúng ta đã loại bỏ được 2 thông số cần biết là tên và kiểu, bây h vì thằng [] có độ ưu tiên cao hơn, chúng ta sẽ tiếp tục đi về bên phải.
long **foo [7];
-> Bây h ta sẽ có : foo là mãng của 7 phần tử ...kiểu long.
long **foo [7];
Bây h chúng ta sẽ đã hết đi về bên phải được nữa rùi, bây h chúng ta sẽ quay lại xử lý dấu * đầu tiên.
-> foo là mãng của 7 phần tử trỏ tới...long.
long **foo [7];
Bây h là thằng * cuối cùng
-> foo là mãng của 7 phần tử trỏ tới trỏ tới kiểu long
Bingo !!! Phù, thế là xong, chua hơn dấm T_T.
Để chắc rằng chúng ta đã là master về cú pháp, ta sẽ chơi thêm 1 ví dụ thật gấu nữa :
char *(*(**foo [][8])())[]; //oops, what the hell T_T
Cũng theo qui luật trên, chúng ta cũng phân tích từng bước từng bước như vậy. Đầu tiênchar *(*(**foo[] [8])())[];
foo...có kiểu char. Tiếp theo :
char *(*(**foo[] [8])())[];
foo là mãng của....kiểu char.
Thường thì đến đây chúng ta sẽ nghĩ là phải đi về bên trái, nhưng hãy nhớ lại qui luật mà xem :
Đi về bên phải khi có thể và đi về bên trái khi bắt buộc.
Vì thế tiếp tục đi về bên phải nào :
char *(*(**foo[] [8])())[];
-> foo là mãng của mãng của 8 phần tử....kiểu char.
Oops, bây h chúng ta đã đụng phải thằng ngoặc mở, và tới đây chúng ta phải bắt buộc lùi lại để xử lý thằng bên trái thôi :
char *(*(**foo[] [8])())[];
-> foo là mãng của mãng của 8 phần tử trỏ tới trỏ tới hàm trả về....kiểu char.
Bây h ta sẽ đứng giữa 2 dấu * bên trái, và () bên phải, ở TH này chúng ta sẽ tiếp tuc đi về bên phải và sau đó lại lùi về trái, ta có :
-> char *(*(**foo[] [8])())[];
foo là mãng của mãng của 8 phần tử trỏ tới trỏ tới hàm trả về trỏ tới mãng của....kiểu char.
Bây h là giai đoạn cuối cùng :
char *(*(**foo[] [8])())[];
foo là mãng của mãng của 8 phần tử trỏ tới trỏ tới hàm trả về trỏ tới mãng của trỏ tới kiểu char.
Chú ý là mình dùng trỏ tới chứ không hề có con trỏ ở trên, nếu chúng ta hiểu nó là con trỏ là sẽ sai trầm trọng đó.
Có lẽ đọc vào cũng có hơi khó hiểu nhỉ, nhưng khi bạn đụng phải những câu lệnh phức tạp thì bạn sẽ thấy tác dụng của nó và hãy nhớ thật kĩ qui luật mà mình nên ra ở trên...!!
Thân !
0 comments:
Đăng nhận xét