PDA

View Full Version : [TUTOR]Dịch những khai báo phức tạp của C/C++ ntn?



woodyvn
30-01-2003, 00:08
by: Prasanth M J
Bài này sẽ hướng dẫn bạn cách dịch các khai báo phức tạp như vậy. Bạn sẽ có thể dịch được những khai báo phức tạp nhất vào cuối bài học này.

1 - Hãy bắt đầu với những khai báo đơn giản


int iMyVar;

khai báo này là đơn giản nhất và nó khai báo một biến kiểu nguyên tên là iMyVar.


int iMyVar[10];

nó đơn giản là một mảng có 10 phần tử kiểu nguyên.


int *iMyVar[10];

Cái gì vậy? Nó là một mảng các con trỏ kiểu nguyên hay là một con trỏ tới một mảng kiểu nguyên có 10 phần tử?

Nó chính là một mảng các con trỏ kiểu nguyên. Tại sao vậy?

Các dấu ngoặc vuông '[]' có thứ tự ưu tiên cao hơn '*'. Vậy chúng ta dịch chúng như thế nào?

iMyVar là

một mảng ----> iMyVar[10]

các con trỏ ----> *

kiểu nguyên ----> int

Nhưng, bạn sẽ khai báo một con trỏ tới một mảng kiểu nguyên ntn?

Nó sẽ như sau:


int (*iMyVar)[10];

và iMyVar bây giờ là một con trỏ tới một mảng 10 phần tử kiểu nguyên.

2 - Công thức

Vậy chúng ta sẽ dịch một khai báo ntn?
Đây là công thức:
#1 - Bắt đầu từ tên biến,
#2 - Tìm bất cứ [] hay () ở ngay ben phải nó, dịch nó ra.
#3 - Tìm ở bên trái bất cứ * nào
#4 - Lặp lại các bước (2, 3) cho đến khi gặp các dấu ngoặc đóng lại (...)
#5 - Thêm vào kiểu dữ liệu.


int (*iMyVar)[10];

Hãy áp dụng công thức trên cho khai báo này:

# Ngay bên phải của iMyVar không có [] hay (), trước dấu ngoặc đóng.
Do đó ta tìm bên trái của nó và dịch như sau: iMyVar là

con trỏ tới ...

# Chúng ta ra khỏi dấu ngoặc và ngay ở bên phải là ký hiệu mảng [10].
Ta dịch nó như sau:

mảng của 10 phần tử ...

# Thêm kiểu dữ liệu ở đầu bên trái

kiểu nguyên.

Vậy iMyVar là một con trỏ tới một mảng của 10 phần tử kiểu nguyên. Được đấy chứ?

Bây giờ, hãy thử dịch:


char *(*myvar)[5];

Bắt đầu từ biến myvar: myvar là một ...
# nó được bao trong các dấu ngoặc đơn nên ta dịch * trước

con trỏ tới ...

# sau dấu ngoặc đơn ở bên phải là [5] và được dịch là:

một mảng của 5 ...

# bây giờ sang bên trái

con trỏ tới ...

# thêm kiểu dữ liệu ở tận cùng bên trái

kiểu ký tự

Ghép tất cả lại với nhau chúng ta được, myvar là một con trỏ tới một mảng của 5 con trỏ tới kiểu ký tự (hay ngắn gọn myvar là một con trỏ tới một mảng của 5 con trỏ ký tự). Nó được minh hoạ như sau:



myvar ----> | | | | | |
| |
| |
| +-----> biến ký tự
+-----> biến ký tự

Dưới đây là một chương trình minh hoạ khai báo trên của myvar



#include <stdio.h>

void main()
{
char *(*myvar)[5]; // myvar la con tro toi mang 5 con tro kieu char
char *char_pntr_array[5]; // mot mang 5 con tro kieu char
char c1 = 'A', c2 = 'B';

char_pntr_array[0] = &c1;
char_pntr_array[1] = &c2;

myvar = &char_pntr_array;

printf("\nc1 = %c", *(*myvar)[0]);
printf("\nc2 = %c", *(*myvar)[1]);
}

3 - Các con trỏ hàm (function pointer):

Đây là một ví dụ về một con trỏ hàm:


int (*ptrFunc)(int);

Ở đây, ptrFunc là một con trỏ tới một hàm, có một đối số nguyên và trả lại kiểu nguyên.

Dấu chỉ báo hàm '()' có thứ tự ưu tiên cao hơn dấu chỉ báo con trỏ '*'
Do đó nếu bạn viết:


int *ptrFunc(int);

thì sẽ là một hàm có một đối số nguyên và trả lại một con trỏ kiểu nguyên.

Chúng ta sẽ dịch khai báo dưới đây như thế nào?


int (*myvar[10])(long, char);

Hãy áp dụng công thức của chúng ta. Bắt đầu từ myvar,

# ngay bên phải myvar là ký kiệu mảng. Do đó

myvar là một mảng có 10 ...

# hãy nhìn sang trái

con trỏ tới ...

# chúng ta đi ra ngoài dấu ngoặc. Hãy nhìn sang bên phải

hàm có các đối số kiểu long và char và ...

# thêm vào kiểu dữ liệu:

trả lại kiểu nguyên.

Ghép lại ta có, myvar là một mảng 10 con trỏ hàm tới các hàm có đối số long và char và trả lại kiểu nguyên.

Dưới đây là đoạn mã minh hoạ:


#include <stdio.h>

int myfunc1(long l, char c)
{
printf("\nmyfunc1 l=%d, c=%c", l, c);
return 1; //so 1 chu khong phai ky tu l
}

int myfunc2(long l, char c)
{
printf("\nmyfunc2 l=%d, c=%c", l, c);
return 2;
}

void main()
{
int (*myvar[10])(long, char);

myvar[0] = myfunc1;
myvar[1] = myfunc2;

myvar[0](10, 'A');
myvar[1](20, 'B');
}

4 - Hàm trả lại một con trỏ tới một hàm


void (*RetFunc())();

Cái gì vậy? Đừng để các dấu ngoặc làm cho sợ hãi. Tuy nhiên cuộc sống sẽ trở nên khốn khổ nếu thiếu chúng :-)

Hãy áp dụng "công thức thần kỳ" của chúng ta ở đây:

RetFunc là một ...

# nhìn bên phải

hàm, (không có đối số)

# nhìn bên trái

trả lại một con trỏ,

# ra ngoài ngoặc đơn, nhìn sang bên phải

tới một hàm (không có đối số),

# và bây giờ bên trái

không trả lại gì cả hay là void.

Ghép lại với nhau, ta có RetFunc là một hàm trả lại một con trỏ hàm tới một hàm không trả lại gì cả (hay trả lại kiểu void).

Hãy xem một ví dụ về một hàm như thế:


void func()
{
printf("\nfunc()");
}

void (*RetFunc())()
{
return func;
}

Bây giờ giả sử chúng ta muốn một con trỏ tới RetFunc. Chúng ta sẽ khai báo như thế nào?

Chúng ta muốn một con trỏ tới một hàm


(*myptr)()

mà trả lại một con trỏ hàm,


(*(*myptr)())()

của một hàm không có đối số và trả lại kiểu void


void (*(*myptr)())();

Sau đây là đoạn mã minh hoạ:


#include <stdio.h>

void func()
{
printf("\n func()");
}

void (*RetFunc())()
{
return func;
}

void main()
{
void (*(*pvar)())();

pvar = RetFunc;

pvar()();
}

5 - Hàm trả lại một con trỏ tới một mảng

Chúng ta có thể trả lại một mảng từ một hàm được không?

Không, nhưng chúng ta có thể trả lại một con trỏ. Nếu bạn muồn trả lại một mảng float, bạn co thể trả lại một float*.

Nhưng nếu chúng ta muốn trả lại một mảng thì sao?

Hãy nhớ lại ví dụ trước của chúng ta, một con trỏ tới một mảng 5 phần tử nguyên


int (*arr)[5];

Nếu chúng ta muốn trả lại một con trỏ tới một mảng 5 phần tử nguyên, chúng ta sẽ khai báo như thế nào?


int (*GetArray())[5];

có đúng không? Chúng ta hãy cùng kiểm tra,

GetArray là một ...

# nhìn sang phải

hàm (không có đối số)...

# nhìn sang trái

trả lại một con trỏ...

# ra khỏi dấu ngoặc đơn, nhìn sang phải

tới một mảng 5 phần tử ...

# nhìn sang trái

kiểu nguyên.

Tại sao khai báo đó lại không như thế này:

int *GetArray()[5] ?

xin dành câu trả lời cho các bạn :-)

6 - Hàm trả lại con trỏ tới một mảng các con trỏ hàm


int (*(*GetFuncArrayPtr())[5])(int);

Oa, đến hoa mắt vì một đống dấu ngoặc mất thôi.
Nào chúng ta hãy cùng dịch khai báo rắc rối này xem sao

Bắt đầu từ GetFuncArrayPtr

GetFuncArrayPtr là một ...

# nhìn sang phải

hàm (không đối số)...

# nhìn sang trái

trả lại một con trỏ tới ...

# ra khỏi dấu ngoặc, nhìn sang bên phải

một mảng có 5 ...

# nhìn sang bên trái

con trỏ tới ...

# ra khỏi dấu ngoặc, nhìn sang phải

các hàm có 1 đối số nguyên ...

# nhìn sang bên trái

và trả lại kiểu nguyên.

Ghép lại với nhau ta có, GetFuncArrayPtr là một hàm (không đối số) trả lại một con trỏ tới một mảng có 5 con trỏ tới các hàm có một đối số nguyên và trả lại kiểu nguyên.

Phù! Hãy lau những giọt mồ hôi trên trán của bạn đi :-)

Đây là một chương trình minh hoạ khai báo trên


#include <iostream.h>

int MyFunc1(int x)
{
return x + 10;
}

int MyFunc2(int y)
{
return y + 20;
}

int (*FuncPtrArray[5])(int); //it is global

int (*(*GetFuncArrayPtr())[5])(int)
{
FuncPtrArray[0] = MyFunc1;
FuncPtrArray[1] = MyFunc2;
return &FuncPtrArray;
}

int main()
{
cout << "Calling one ..." << GetFuncArrayPtr()[0][0](10) << endl;
cout << "Calling two ..." << (*GetFuncArrayPtr())[1](2) << endl;

// *GetFuncArrayPtr()[1](2) will not work. Why?

return 0;
}

7 - Làm thế nào để tránh các khai báo phức tạp

Trong thực tế, viết những khai báo phức tạp như vậy không được chú trọng trong lập trình chuẩn. Tính dễ đọc của mã sẽ bị ảnh hưởng. Vậy cách khắc phục là gì?

Sử dụng các typedef để làm giảm tính phức tạp. Chia một khai báo phức tạp thành những khai báo đơn giản hơn.

Hãy xem lại ví dụ trước.

Trước tiên khai báo typedef cho con trỏ hàm:


typedef int (*MY_FUNC_PTR)(int);

Từ lúc này trở đi, MY_FUNC_PTR biểu diễn cho một con trỏ tới một hàm có một đối số nguyên và trả lại kiểu nguyên.

Bây giờ chúng ta hãy định nghĩa kiểu, của con trỏ tới mảng các con trỏ hàm: MY_FUNC_PTR


typedef MY_FUNC_PTR (*PTR_TO_FPTR_ARRAY)[5];

Cuối cùng khai báo GetFuncArrayPtr


PTR_TO_FPTR_ARRAY GetFuncArrayPtr();

Dưới đây là chương trình được viết lại:


#include <iostream.h>

typedef int (*MY_FUNC_PTR)(int);
typedef MY_FUNC_PTR (*PTR_TO_FPTR_ARRAY)[5];

int MyFunc1(int x)
{
return x + 10;
}

int MyFunc2(int y)
{
return y + 20;
}

MY_FUNC_PTR FuncPtrArray[5];
PTR_TO_FPTR_ARRAY GetFuncArrayPtr()
{
FuncPtrArray[0] = MyFunc1;
FuncPtrArray[1] = MyFunc2;
return &FuncPtrArray;
}

int main()
{
cout << "Calling one ..." << GetFuncArrayPtr()[0][0](10) << endl;
cout << "Calling two ..." << GetFuncArrayPtr()[0][1](10) << endl;
return 0;
}

Đến đây là kết thúc các bài học của chúng ta về các khai báo. Hy vọng nó sẽ giúp cho các bạn hiểu được thậm chí những khai báo phức tạp nhất.

Happy coding and Happy New Year!!!
:D :D :D :D :D :D :D :D :D :D :D :D :D

Nowhereman
28-12-2003, 08:48
::: Hay dáy nhung doc xong toet' ca? mat roai` ::: bat den` ong di a' hong?mat' fai? doe kinh' ne` :p

duclee
28-12-2003, 09:11
rất hứng thú, tui chỉ đọc khỏang vài dòng thôi, nhưng khá là hấp dẫn!