۰۪۪۫۫●۪۫۰-SolitaryWolf--IT--Let's share together-۰۪۪۫۫●۪۫۰

The place for everyone to discuss anything that you like..specially for Information Technology... Software,Hardware,Program C,C++,C# and more...


1. Mảng 2 chiều

Cách dễ nhất để hiểu được bản chất của mảng n chiều là ta bắt đầu từ mảng 1 chiều. Theo định nghĩa, Mảng là một dãy các phần tử nằm liên tiếp nhau trong bộ nhớ. Các phần tử của mảng có chung 1 kiểu và có cùng 1 tên. Để phần biệt các phần tử với nhau, ta dùng chỉ số. Kiểu các phần tử ở đây có thể là char, int, double, struct, user defined type v.v... Một cách tổng quát, ta có thể viết như sau:
Code:
type array[size]; 
trong bộ nhớ, mảng được biểu diễn thế này:
Code:
|sizeof(type)|....|sizeof(type)|

Ta có thể định nghĩa một kiểu mới là một mảng như sau:
Code:
 typedef int ArrayOfInt[10];  

Sau khi định nghĩa thì ta có thể dùng kiểu của chúng ta như những kiểu khác, ví dụ khai báo 1 biến:
Code:
ArrayOfInt my_array

Ở đây my_array sẽ là một mảng có 10 phần tử kiểu int. Để truy cập đến từng phần tử thì ta dùng như với mảng bình thường:
Code:
my_array
Code:
[0] = 0

Kiểu do chúng ta định nghĩa cũng có thể là phần tử của 1 mảng:
Code:
ArrayOfInt array[5]; 

Trong trường hợp này, mảng array sẽ được biểu diễn trong bộ nhớ như sau:
Code:
|sizeof(ArrayOfInt)|...|sizeof(ArrayOfInt)| 

Chắc bạn cũng đã đoán ra, array ở đây chính là mảng 2 chiều. Compiler có đủ thông minh để tự làm được những bước kể trên, vì thế để khai báo 1 mảng 2 chiều thì chúng ta chỉ việc khai báo thế này là đủ:
Code:
int array[5][10]; 

Khi ta muốn truy cập đến 1 phần tử của mảng 2 chiều thì ta dùng cú pháp quen thuộc:
Code:
el = array[2][3]; 

Khi gặp những biểu thức như vậy thì đầu tiên, trình dịch sẽ tính xem array[2] là gì:
Code:
el = *(array + 2)[3]; 

Biểu thức *(array + 2) có nghĩa là ta muốn truy cập đến phần tử thứ 3 của array. Mặt khác, phần tử của array là mảng có 10 phần tử kiểu int, nên chỉ số 3 tiếp theo sẽ được tính theo cú pháp của mảng. Kết quả ta sẽ có:
Code:
el = *(*(array + 2) + 3); 

để tính được biểu thức *(array + 2) thì trình dịch cần phải biết rõ kiểu ArrayOfInt có bao nhiêu bytes, nói cách khác là ta cần phải cung cấp rõ là ArrayOfInt là mảng có bao nhiêu phần tử. Chính vì lý do đó mà khi khai báo 1 mảng nhiều chiều, thì ta chỉ có thể bỏ qua kích thước thứ nhất, những kích thước còn lại ta cần phải chỉ rõ. Khi truyền mảng nhiều chiều cho hàm thì cũng có yêu cầu tương tự:
Code:
    
void func(int arr[][10])
   {
       ...
   }
   func(array); 
               
Nhờ vào số 10 ta cung cấp, trình dịch có thể tìm được phần tử ta muốn truy cập arr[m][n] dựa theo công thức trên. Cũng từ công thức trên, ta suy ra một điều là với mảng:
[CODE]int array[5][10]; [CODE]

thì array có thể coi như là:
Code:
int (*array)[10]; 

Tức là, có thể coi array là mảng mà các phần tử của nó là con trỏ đến mảng có 10 phần tử kiểu int. Và nếu ta muốn gán array cho 1 con trỏ thì ta cũng phải khai báo con trỏ đó theo cú pháp tương tự:
Code:
    
int array[5][10];
   int (*pointer)[10];
   pointer = array;           
Dấu ngoặc ở đây không được phép thiếu, nếu không thì ta sẽ thu được kết quả khác:
Code:
    
int *pointer[10]; 
  
Trường hợp này, trình dịch sẽ hiểu là pointer là mảng có 10 phần tử là con trỏ đến kiểu int. Nếu ta khai báo con trỏ khác đi thì sao?
Code:
int (*pointer)[11];
khi đó phép gán pointer=array không còn hợp lệ nữa và trình dịch sẽ báo lỗi ngay.


3. Con trỏ đến con trỏ
 

Tương tự như mảng 2 chiều, để hiểu được con trỏ đến con trỏ thì tốt nhất là ta băt đầu với con trỏ. Trước hết, con trỏ là một biến mà giá trị của nó là 1 đia chỉ trong bộ nhớ. Để có thể thực hiện được các phép tính số học với con trỏ thì ta cần phải chỉ rõ là con trỏ đó trỏ đên kiểu gì. Dạng tỗng quát là:
Code:
type *pointer              
Trường hợp type là một con trỏ thì sao? khi đó ta có con trỏ đến con trỏ. Điều đó hoàn toàn hợp lệ, vì con trỏ là 1 biến nên nó cũng có địa chỉ trong bộ nhớ, giống như 1 biến kiểu char, int v.v... Khi ta dùng toán tử lấy giá trị của biến do con trỏ đến con trỏ đang chỉ tới thì ta thu được type, tức là con trỏ. Ví dụ:
Code:
 int i;
   int *pi=&i;
   int **ppi = &pi

khi đó *ppi sẽ cho ta con trỏ đến kiểu int. Trường hợp ta muốn lấy giá trị của biến do con trỏ int đang trỏ tới thì ta phải dùng 2 dấu *. Tức là:
Code:
int value = **ppi
  
Biểu thức này mới nhìn có vẻ khó hiểu, nhưng nếu ta cứ suy luận từng bước sẽ thấy ngay. Trước hết *ppi là con trỏ kiểu int. Để lấy giá trị do con trỏ đến int chỉ tới thì ta dung dấu *. Như vậy ta phải cần *(*ppi), cũng tức là **ppi.

Nếu bạn để ý thì với con trỏ đến int, tớ dùng tên pi, còn con trỏ đến con trỏ đến int, tớ dùng ppi. Đó là tớ đã áp dụng Hungarian Notation. Cách viết này nhiều lúc sẽ giúp chúng ta dễ hiểu hơn. Cụ thể là với cách viết này, mỗi dấu * sẽ trung hoà 1 chữ 'p'. Như thế:
Code:
  *ppi <==> pi (tức là con trỏ đến int)
   **ppi <==> i (tức là kiểu int            

nhờ đó mỗi khi mà ta nghi hoăc không biết *ppi có kiểu gì thì chỉ cần quan sát kỹ là sẽ thấy ngay. Nói chung cách viết này cũng có nhiều người ủng hộ và nhiều người chống đối. Bạn đầu khi bạn mới làm quen với con trỏ thì nên áp dụng kiểu viết do. Nó sẽ giúp bạn dễ hiểu hơn đó.

Cũng giống như con trỏ đến int khác con trỏ đến char, con trỏ đến con trỏ đến int sẽ khác con trỏ đến con trỏ đến char. Và nếu ta tìm cách gán địa chỉ của 1 con trỏ cho một con trỏ đến con trỏ thì ta phải bảo đảm là kiểu của chúng giống nhau. Nếu không thì trình dịch sẽ báo lỗi.

3. Cấp phát bộ nhớ động cho mảng 2 chiều

Vấn đề này được khá nhiều người quan tâm tới. Như ta đã biết, trong C muốn cấp phát bộ nhớ động thì gọi hàm malloc(). Nhưng mà hàm này thì chỉ nhận tống số bytes cần cấp phát và trả lại kiểu void *. Vậy thì làm thế nào để cấp phát 1 vùng bộ nhớ, sau đó ta có thể dùng cú pháp của mảng 2 chiều để truy cập? Giả sư ta cần cấp phát mảng 2 chiều 5 x 10 có kiểu int. Có 1 số cách như sau:

- Dùng con trỏ đến con trỏ:
Code:
 int **pp = (int **)malloc(5 * sizeof(int *));
   for (int i = 0; i < 5; i++)
      pp[i] = (int *)malloc(10 * sizeof(int)); 



Cách này quá phức tạp. Thêm nữa là các phần tư của mảng sẽ không nằm liền nhau bởi vì ta phải gọi nhiều lần malloc() để cấp phát.

- Cải tiến cách trên một chút:
Code:
int **pp = (int **)malloc(5 * sizeof(int *));
   int *p = (int *)malloc(5 * 10 * sizeof(int));
   for (int i = 0; i < 5; i++)
       pp[i] = p + i * 10

Bây giờ thì các phần tử của mảng đã nằm liền nhau, nhưng cách này cũng chưa phải là tối ưu. Vừa tốn bộ nhớ (thêm 5 con trỏ kiểu int), vừa phải code nhiều hơn. Khi phải giải phóng bộ nhớ thì ta cũng phải giải phóng 2 lần.

- Dựa vào điều ta đã biết là:
Code:
  type array[M][N] ==> type (*array)[M


Ta có thể khai báo 1 con trỏ như vậy và cấp phát bộ nhớ động cho nó. Đại khái nó sẽ thế này:
Code:
   int (*p)[10] = (int (*)[10])malloc(5 * sizeof (int [10]));           


Cách này bảo đảm là các phần tử nằm liền nhau, và ta cũng không tốn thêm bộ nhớ. Khi giải phóng thì chỉ cần 1 lần. Và nó cũng gần giống mảng tĩnh nhất so với 2 cách trước. Tuy nhiên nó rắc rối quá, dễ gây nhầm lẫn, và làm cho người khác nhìn vào thấy khó hiểu.

- Dùng typedef để đơn giản hoá vấn đề

Cách này theo đánh giá chủ quan của cá nhân tớ là hay nhất. Ngắn gọn, dễ hiểu và hiệu quả. Cách làm như sau:
Code:
typedef int MYARR[10];
   MYARR *p = (MYARR *)malloc(5 * sizeof (MYARR)); 



Trường hợp muốn cho mã nguồn portable thì có thể làm thế này:
Code:
const int M = 5;
   const int N = 10;

   typedef int MYTYPE;

   typedef MYTYPE MYARR[N];
   MYARR *p = (MYARR *)malloc(5 * sizeof (MYARR)); 

Khi đó, muốn thay đổi kích thước của mảng, ta chỉ việc thay giá trị cho 2 hằng M và N. Muốn thay kiểu phần tử của mảng thì chỉ cần thay kiểu của MYTYPE.

1 comments:

It's a shame you don't have a donate button! I'd certainly donate to this excellent blog! I suppose for now i'll
settle for book-marking and adding your RSS feed to
my Google account. I look forward to brand new
updates and will share this site with my Facebook group.
Talk soon!

Also visit my homepage dolnoslaska agencja reklamowa

Post a Comment

Followers!

About this blog

When you're sad and have sorrow just visit me...^^

Total Pageviews

Most Viewer