[All Language] Giải thích về mảng và con trỏ

Chào các bạn !
Mình thấy có nhiều bạn đang không rõ về vấn đề này. Là một tay ngang, mình sẽ giải thích những gì mình biết về nó. Hy vọng giúp ích các bạn. Nếu có thiếu sót hay sai, các bạn chia sẻ thêm.
Lets go !

  1. Việc đầu tiên các bạn nên hiểu về cách máy tính lưu dữ liệu trên RAM.
    Bộ nhớ RAM của máy tính được cấu tạo từ rất nhiều ô nhớ. Mỗi ô nhớ này lưu được 8bit dữ liệu, tức là 1 byte. bộ nhớ RAM 1G sẽ có 1 tỷ ô nhớ này. Vị trí các ô nhớ hoàn toàn xác định và cố định và được phân biệt, truy xuất thông qua địa chỉ của nó.

  2. Mảng.
    Mảng là một số biến cùng loại được sắp xếp cạnh nhau liên tục trên bộ nhớ RAM. Các phần tử trong mảng sẽ được phân biệt với nhau bởi chỉ số của phần tử đó.
    Khi ta khai báo 1 mảng :

unsigned int arr[n];

Máy tính sẽ xác định số ô nhớ cần thiết và cấp số ô nhớ đó trên RAM trong vùng RAM chưa được sử dụng cho chương trình ( ở đây là 4*n ô nhớ vì mỗi biến int là chiếm 4 ô nhớ - 4byte) . Các ô nhớ này liên tiếp nhau và do đó địa chỉ của nó cũng liên tiếp nhau. Vị trí các ô nhớ này ở đâu trên RAM thì chương trình hoàn toàn kiểm soát được.
Vị trí (địa chỉ ) của mảng này ở đâu là cố định, không thể thay đổi được trừ khi tạo mảng mới.

  1. Con trỏ.
    Khi nói về con trỏ thường chúng ta cảm thấy nó hư hư thực thực. Vậy nó là gì ?
    Nó là biến lưu địa chỉ của ô nhớ trên RAM. Khi chúng ta có được địa chỉ của một ô nhớ chúng ta sẽ làm việc được với dữ liệu của ô nhớ đó.
    Khi chúng ta khai báo :
unsigned int *ptr = new unsigned int[n];

Máy tính sẽ cấp cho chương trình 4*n ô nhớ trên RAM và báo cho chương trình biết vị trí của nó ở đâu, chính là giá trị của con trỏ ptr. Con trỏ ptr sẽ lưu vị trí của mảng.
Khi ta khai báo :

unsigned int Arr[n];
unsigned int *ptr = &Arr;

Thì máy tính sẽ lấy vị trí (địa chỉ) của mảng Arr lưu vào biến con trỏ ptr. Và khi chúng ta có được vị trí của mảng trên RAM thông qua con trỏ thì chúng ta có thể làm việc với mảng mà con trỏ đang trỏ tới.
Đến đây các bạn có thể hình dung mảng giống như 1 folder trên máy tính, có có chứa trong đó 1 hoặc nhiều file.
Còn con trỏ là cái shortcut của folder đó. Bạn có thể chỉnh cho cái shortcut đó chỉ đến folder khác.

  1. Điểm khác biệt của con trỏ và mảng.
    Mảng : Được cấp phát trên vùng RAM chưa sử dụng, số phần tử cố định, địa chỉ trên RAM cố định (trừ khi khai báo mảng mới), không được phép truy xuất ngoài phạm vi mảng.
    Khi chúng ta khai báo mảng 10 phần tử, chúng ta chỉ truy xuất được trong phạm vi đó, tức là từ phần tử 0 đến 9. Ngoài khoảng này, chương trình báo lỗi out of index.
    Con trỏ : Có thể trỏ tới bất kì vị trí nào trên RAM, không có phần tử, địa chỉ trên RAM cố định, địa chỉ trỏ tới có thể thay đổi sau khi khai báo.
    Chúng ta có thể thay đổi địa chỉ mà con trỏ đang trỏ tới một cách dễ dàng như sau :
unsigned int Arr1[n];
unsigned char Arr[m];
unsigned int *ptr = &Arr1[0]; // con trỏ đang trỏ tới Arr1[0]
ptr=(unsigned char*)&Arr2[0]; // lúc này nó lại trỏ tới Arr2[0];
ptr=1234; // lúc này nó lại trỏ tới ô nhớ có địa chỉ 1234 trên RAM.
  1. Chú ý khi dùng con trỏ.
    Mặc dù con trỏ có nhiều ưu điểm như ta có thể trỏ tới địa chỉ bất kì trên RAM và làm việc với dữ liệu có địa chỉ được trỏ tới (khi hack game chẳng hạn là ta phải dùng con trỏ để trỏ tới vùng dữ liệu của game) hoặc trong một số trường hợp chúng ta sẽ có được tốc độ cao hơn trong xử lý nhưng bạn sẽ thấy một số ngôn ngữ không cho dùng con trỏ và sẽ báo unsafe - không an toàn. Vậy tại sao ?
    Do con trỏ có thể trỏ tới bất kì chỗ nào trên RAM nên nó có thể thay đổi dữ liệu của chương trình khác và gây cho máy tính làm việc không bình thường.
    -> Con trỏ là nguy hiểm nếu không kiểm soát được.

____ End ____

17 Likes

Bài viết bổ ích nhưng em vẫn chưa hiểu hết về con trỏ. Cách sử dụng rồi ứng dụng của nó. Khi nào thì dùng đến nó. Nó có ưu điểm gì ==

Chào các bạn !

Mình sẽ nêu một số ví dụ về khi nào sẽ sử dụng con trỏ và là ứng dụng của nó.

1.Lưu giữ biến, dữ liệu không xác định độ dài dữ liệu :

  • Chúng ta sẽ làm việc với file, sẽ đọc 1 file text, mp3 hay bmp. Chúng ta không biết được nội dung file cần đọc dài bao nhiêu byte để cấp phát bộ nhớ nếu dùng mảng, có một chúng ta sẽ sử dụng con trỏ.
    Chương trình khi đó sẽ đọc toàn bộ nội dung file lưu lên RAM và trả về cho chúng ta vị trí của vùng RAM đó. Chúng ta sẽ làm việc với dữ liệu đó bằng một con trỏ.
FILE *fp = fopen("info.bmp", "r"); // con trỏ fp sẽ trỏ đến vùng nhớ lưu dữ liệu file info.bmp

2.Khai báo vùng dữ liêu, và làm việc với dữ liệu tổng hợp với cú pháp đơn giản hơn :

  • Với ví dụ 1, thực ra dữ liệu sau khi đọc file nó gồm một số thông tin khác, cái chúng ta cần dùng là vùng dữ liệu thừ byte thứ 100và ta muốn đọc hay thay đổi dữ liệu ở vị trí x. Chắc chắn ta sẽ không thích kiểu này :
unsigned char v=(unsigned char)fp[100+x]; // đọc dữ liệu tại vị trí x trên vùng dữ liệu
fp[100+x]=0; // thay đổi dữ liệu tại vị trí x trên vùng dữ liệu

Mà sẽ thích kiểu này:

unsigned char *ptrdata = (unsigned char*)fp; // khai báo vùng dữ liệu data trên vùng dữ liệu của file
unsigned char v=ptrdata[x]; // đọc dữ liệu tại vị trí x trên vùng dữ liệu
ptrdata[x]=0; // thay đổi dữ liệu tại vị trí x trên vùng dữ liệu

3.Tương tác với dữ liệu của chương trình khác.
Bạn đang chơi game, bạn bị oánh gần chết, còn 10 máu nhưng bạn không muốn chết. Bạn phải làm sao ?
Bằng một số phần mềm, bạn biết biến máu trong chương trình gmae của bạn nằm tại ô nhớ 100000000 trên RAM và bạn muốn nó mãi là 100. Bạn phải làm sao ?
Bạn sẽ viết 1 tool, sử dụng con trỏ để trỏ tới ô nhớ 100000000 và set cho nó bằng 100.

void HoiManna(){
   unsigned int *ptrMau = (unsigned int*)(100000000); // con trỏ trỏ tới địa chỉ 100000000
   *ptrMau=10000; // Hồi sinh thành siêu nhân 10000 máu. Đã gian lận phải tới bến luôn :))
}
  1. Tăng tốc độ xử lý.
    Bạn có 1 mảng hoặc một vùng dữ liệu gồm 100000000 bytes nằm trên RAM. Nó là dữ liệu của 1 bức ảnh. Bạn muốn tăng từng byte một lên 1 đơn vị (tăng sáng ảnh). Có thể bạn sẽ làm thế này :
unsigned char imgData[100000000]; // dữ liệu ảnh.
for(int i=0;i<100000000;i++){
    imgData[i]++;
}

Hoặc cách này sẽ thực thi nhanh hơn:

unsigned char imgData[100000000]; // dữ liệu ảnh.
unsigned char *ptr=&imgData[0];
for(int i=0;i<100000000;i++){
    ptr[i]++;
}

Tôi có thử trên MSVC 2010 thấy cách 2 thực thi nhanh hơn cách 1. Nhưng gần đây có thử trên MSVC 2015 thì thấy như nhau.

2 Likes

Góp ý thôi: http://diendan.congdongcviet.com/threads/t42977::tim-hieu-ban-chat-cua-con-tro-tu-co-ban-den-nang-cao.cpp

cái này ko chắc. fopen chỉ đọc 1 phần thông tin rất nhỏ về file thôi, nó liên quan tới hệ điều hành nữa. Sau đó fget hay fread v.v… thì nó mới đọc dần từ disk lên memory, chứ ko phải mới fopen là đã load hết lên memory đâu :joy:

con trỏ thì có trong td::string (con trỏ tới chuỗi có độ dài bất kì), std::vector (con trỏ tới mảng động), std::set, std::map (con trỏ trong 1 node của binary tree). Hỏi con trỏ có ưu điểm gì thì y như hỏi biến số trong toán học có ưu điểm gì vậy :joy:

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?