1.11. Con trỏ

Chúng ta đã bước đến bài học cuối cùng trong chương đầu tiên nói về các khái niệm cơ bản trong C++. Hãy chú ý, kiến thức trong bài học này tương đối khó hơn nếu so sánh với những gì đã học từ trước tới giờ.

Chủ đề về con trỏ luôn được coi là 1 trong những bài học khó nhất nhưng bắt buộc phải biết khi bạn học lập trình. Đây cũng là 1 trong những bài học phức tạp nhất trong giáo trình của chúng ta. Sau khi học xong và hiểu được nó, các bạn sẽ thấy rất nhiều thứ trở nên đơn giản và rõ ràng hơn nhiều.

Con trỏ được sử dụng trong tất cả các chương trình C++. Thậm chí kể cả bạn cũng đã sử dụng chúng mà không biết vì cho đến giờ thì các bạn vẫn chưa từng chính thức thao tác trực tiếp với chúng. Từ bài này sẽ bắt đầu, chúng ta sẽ học quản lý 1 cách tinh tế từng phần bộ nhớ của máy tính.

Như tôi đã nói nhiều lần, với những bài học khó, đừng ngại đọc đi đọc lại nhiều lần để hiểu rõ. Bài này được đánh giá với độ khó cao nên tôi sẽ không nghi ngờ là các bạn sẽ còn quay lại đây nhiều trong tương lai.

Các địa chỉ trong bộ nhớ

Các bạn có còn nhớ bài học về quản lý bộ nhớ trong đó tôi đã giới thiệu với các bạn về khái niệm biến chứ? Nếu không nhớ rõ, hãy đọc lại nó và trên hết là nhớ lấy các sơ đồ chúng ta đã vẽ để mô tả về bộ nhớ.

Chúng ta đã biết rằng khi chương trình khai báo 1 biến, máy tính sẽ “cho mượn” 1 chỗ trong bộ nhớ của nó và gắn lên đấy 1 cái nhãn có tên của biến.

int main(){
    int tuoiNguoiDung(16);
    return 0;
}

Đoạn mã trên đã được chúng ta biểu diễn thế này.

Đơn giản biết chừng nào ! Đáng buồn là tôi phải nói cho các bạn biết rằng tôi đã nói dối. Ít nhất tôi đã đơn giản hóa mọi thứ đi nhiều. Khi các bạn nhận ra rằng với máy tính mọi thứ đều được sắp xếp có trật tự và logic thì các bạn cũng sẽ thấy rằng hình vẽ trên của chúng ta không còn chính xác lắm.

Bộ nhớ của máy tính đúng thật là được tạo thành từ các ô nhớ, có thể lên đến hàng tỉ đối với 1 chiếc máy tính đời mới. Vậy nên cần có 1 hệ thống quản lý để giúp máy có thể tìm lại được ô nhớ mà mình cần. Vì thế mỗi ô nhớ bản thân nó sẽ gắn với 1 số thứ tự duy nhất mà chúng ta gọi là địa chỉ (address).

Trong sơ đồ chúng ta nhìn thấy các ô nhớ và cả địa chỉ của chúng. Chương trình chỉ sử dụng 1 ô nhớ để lưu trữ biến, đó là ô 53768.

! Ta không thể lưu 2 biến vào cùng 1 ô nhớ

Quan trọng ở đây là mỗi biến sở hữu 1 địa chỉ và mỗi địa chỉ chỉ ứng với 1 biến duy nhất.

Địa chỉ cung cấp cho ta 1 cách thức khác để có thể tiếp cận biến như trong hình. Vậy là ta sẽ có 2 cách:

  • Sử dụng tên biến như chúng ta vẫn làm từ trước đến giờ.
  • Sử dụng địa chỉ theo kiểu như yêu cầu máy tính “Hãy hiển thị giá trị ô 53768” hay “Hãy cộng giá trị của 2 ô 53768 và 12345”.

Vẫn là 1 số người hay thắc mắc sẽ hỏi là viêc này thì được lợi ích gì bởi vì sử dụng tên biến như những cái nhãn đã khá đơn giản và hiệu quả rồi. Đúng là như vậy, tuy nhiên, tôi chỉ có thẻ nói với các bạn, việc truy cập dựa vào địa chỉ đôi khi trong 1 số trường hợp là cần thiết.

Hãy bắt đầu bằng cách tìm ra đia chỉ của 1 biến nhé!

Hiển thị địa chỉ

Trong C++, để nhận lấy địa chỉ của 1 biến, chúng ta sử dụng dấu &. Lấy ví dụ nếu tôi muốn địa chỉ của biến tuoi thì tôi cần phải viết &tuoi.

#include <iostream>
using namespace std;

int main(){
   int tuoi(16);
   cout << "Dia chi o nho la : " << &tuoi << endl;
   //Hien thi dia chi o nho
   return 0;
}

Kết quả nhận được là :

! Khi chạy chương trình trên máy của bạn kết quả chắc chắn sẽ khác vì ô nhớ được sử dụng thay đổi mỗi lần chạy khác nhau của chương trình và khác nhau với mỗi máy tính.

Mặc dù kết quả có chứa các chữ nhưng tôi đảm bảo với các bạn rằng đây là 1 số, chỉ đơn giản là được viết trong hệ thập lục phân (hexa), một cách khác để biểu diễn 1 số. Máy tính rất thích làm việc với hệ này thay vì hệ thập phân như con người hay dùng (bới vì máy tính thì nghĩ khác với con người – Imitation game). Giá trị trên tương đương với 2686716 trong hệ thập phân của chúng ta cho những ai muốn biết. Tuy nhiên nó chỉ mang tính chất tham khảo chứ không có ý nghĩa sử dụng thực tế.

Chắc chắn là chỉ hiển thị giá trị địa chỉ này thì không có nhiều lợi ích cho lắm. Các bạn chỉ cần nhớ lấy cú pháp cần sử dụng với dấu & là được.

! Chúng ta cũng từng sử dụng dấu & khi khai báo tham chiếu. Cùng 1 dấu được sử dụng cho 2 mục đích khác nhau vậy nên chú ý đừng nhầm lẫn.

Tiếp đây là cách chúng ta sử dụng các địa chỉ này.

Con trỏ

Các địa chỉ cũng chỉ là các con số. Chúng ta đã biết nhiều kiểu dữ liệu để lưu trữ số như int, unsigned int, double. Vậy có thể nào lưu giá trị của địa chỉ trong 1 biến ?

Câu trả lời là « có » nhưng không phải sử dụng những kiểu dữ liệu mà bạn đã biết mà là 1 kiểu đặc biệt : con trỏ (pointer).

Con trỏ là 1 biến chứa giá trị địa chỉ của 1 biến khác.

Hãy nhớ lấy câu này! Nó sẽ giúp bạn rất nhiều hơn bạn tưởng.

Khai báo 1 con trỏ

Khi khai báo con trỏ, chúng ta cần 2 thông tin :

  • Kiểu dữ liệu
  • Tên con trỏ

Với tên con trỏ, các bạn cần áp dụng tất cả các quy tắc chúng ta đã biết với các loại tên khác như tên biến, tên hàm, vv…

Kiểu dữ liệu của con trỏ thì hơi đặc biệt. Bạn cần phải chỉ ra kiểu dữ liệu của biến được lưu trong địa chỉ theo sau đó là dấu * như trong câu lệnh dưới đây.

int *conTro;

Câu lệnh này khai báo 1 con trỏ chứa địa chỉ của 1 biến kiểu int.

! Chúng ta cũng có thể viết int* conTro; (nghĩa là dấu * sát với kiểu dữ liệu thay vì sát với tên biến). Cú pháp này có đôi chút bất tiện vì không cho phép chúng ta khai báo đồng thời nhiều biến trên cúng 1 dòng. Cần chú ý là nếu bạn viết là int* conTro1, conTro2, conTro3; thì chỉ có conTro1 là 1 biến con trỏ, 2 biến còn lại chỉ là biến kiểu số nguyên bình thường.

Chúng ta có thể khai báo con trỏ trên bất cứ kiểu dữ liệu nào.

double *conTroA; //Con tro chua dia chi chua so thap phan

unsigned int *conTroB; // Con tro chua dia chi chua so nguyen duong

string *conTroC; //Con tro chua dia chi chua chuoi ky tu

vector<int> *conTroD; // Con tro chua dia chi chua mang dong cac so nguyen

int const *conTroE; // Con tro chua dia chi chua hang so nguyen

Hiện thời, sau khi khai báo thì con trỏ đang chứa giá trị không xác định. Tình huống này là rất nguy hiểm vì khi bạn thao tác với con trỏ này, bạn không biết là mình đang thay đổi ô nhớ nào. Ô nhớ này có thể là bất cứ ô nào, có thể là chứa mật khẩu Windows của bạn hoặc cũng có thể là chứa giá trị thông báo thười gian trong máy tính. Ví dụ như thế hy vọng sẽ giúp các bạn hiểu hậu quả nghiêm trọng có thể sinh ra khi thao tác sai lầm trên các con trỏ. Vậy nên tuyệt đối không bao giờ được khai báo 1 con trỏ mà không khởi tạo cho nó 1 giá trị.

Để đảm bảo nhất thì mỗi khi khai báo 1 con trỏ, hãy gán cho nó giá trị bằng 0.

double *conTroA(0);
unsigned int *conTroB(0);
string *conTroC(0);
vector<int> *conTroD(0);
int const *conTroE(0);

Nếu các bạn chú ý sơ đồ mà tôi đã vẽ bên trên thì ô nhớ đầu tiên có địa chỉ là 1. Trong thực tế thì địa chỉ số 0 không hề tồn tại. Khi bạn khởi tạo giá trị 0 cho con trỏ thì có nghĩa là con trỏ này không hề chứa địa chỉ của ô nhớ nào cả.

! Tôi nhắc lại là việc khởi tạo giá trị 0 cho con trỏ khi khai báo là rất quan trọng.

Lưu trữ địa chỉ

Sau khi đã tạo ra biến con trỏ, việc tiếp theo là gán cho nó 1 giá trị. Các bạn hẳn chưa quên cách để lấy địa chỉ của 1 biến mà tôi đã nói ngay bên trên.

int main(){
   int tuoi(16);   //Bien chua kieu du lieu int
   int *ptr(0);   //Con tro chua dia chi 1 so nguyen
   ptr = &tuoi;   //Luu dia chi cua ‘tuoi’ vao trong con tro ‘ptr’
   return 0;
}

Câu lệnh ptr = &tuoi; chính là lệnh chúng ta cần quan tâm. Câu lệnh này ghi địa chỉ của tuoi vào con trỏ ptr. Chúng ta nói là « con trỏ ptr trỏ lên biến tuoi».

Sơ đồ sau miêu tả quá trình xảy ra trong bộ nhớ.

Trong sơ đồ này, chúng ta vẫn có thể thấy bộ nhớ được chia thành nhiều ô nhỏ cùng với biến tuoi nằm trong ô 53768. Khác biệt là bây giờ có thêm ô 14566 chứa biến tên là ptr mang giá trị địa chỉ của tuoi, nghĩa là 53768.

Nhiều bạn sẽ hỏi là « Tại sao lại cần lưu địa chỉ của biến trong 1 ô nhớ khác nữa ?» thì xin trả lời là « Hãy từ từ, mọi thứ sẽ dần sáng tỏ thôi. ». Trước mắt, hãy tập trung hiểu kỹ sơ đồ trên.

Hiển thị địa chỉ

Giống như tất cả các biến khác, chúng ta cũng có thể hiển thị ra màn hình giá trị của con trỏ.

#include <iostream>
using namespace std;

int main(){
   int tuoi(16);
   int *ptr(0);
   ptr = &tuoi;
   cout << "Dia chi cua 'tuoi' la : " << &tuoi << endl;
   cout << "Gia tri cua con tro la : " << ptr << endl;  
   return 0;
}

Kết quả nhận được cho ta thấy là giá trị chủa con trỏ chính là địa chỉ của biến được trỏ.

Truy nhập vào giá trị được trỏ

Tôi đã nói bên trên là nhiệm vụ của con trỏ là cho phép truy nhập vào biến mà không cần thông qua tên biến. Để làm thế thì cú pháp là chỉ cần viết dấu * trước tên con trỏ là đủ

int main(){
   int tuoi(16);
   int *ptr(0);
   ptr= &tuoi;                      
   cout << "Gia tri duoc tro la : " << *ptr << endl;
   return 0;
}

Khi xử lý dòng lệnh cout << *ptr; thì máy tính sẽ thực hiện những việc sau :

  1. Đi đến ô nhớ có nhãn là ptr.
  2. Lấy ra giá trị được lưu trong ô nhớ đó.
  3. Đi đến ô nhớ có địa chỉ là giá trị vừa nhận được.
  4. Đọc ra giá trị trong ô nhớ vừa rồi.
  5. Hiển thị kết quả (ở đây là 16) ra màn hình.

Sử dụng dấu * đã giúp chúng ta truy nhập vào giá trị được trỏ. Thuật ngữ chuyên môn gọi việc này là tham chiếu ngược.

Vẫn là câu hỏi như bên trên được đặt ra, đó là mục đích của việc này là gì. Xin hãy kiên nhẫn và đọc tới cuối bài học.

Tóm lược lại về cú pháp

Chắc mọi người cũng đồng ý là cú pháp của con trỏ cũng khá phức tạp, luôn luôn cần chú ý vì dấu * và dấu & đều được dùng cho nhiều mục đích khác nhau. Tôi xin tóm lược lại 1 chút cho dễ nhớ.

Ví dụ với 1 biến số nguyên so kiểu int

  • so là tên biến, cho phép truy nhập vào giá trị biến
  • &so cho phép truy nhập vào giá trị của địa chỉ của biến

Với 1 con trỏ int *conTro

  • conTro là tên con trỏ cho phép truy nhập vào giá trị của con trỏ, nghĩa là địa chỉ của ô nhớ được trỏ đến.
  • *conTro cho phép truy nhập vào giá trị lưu trong ô nhớ được trỏ đến.

Đây là những điều quan trong cần nhớ trong phần này. Tôi đề nghị các bạn thử tự viết 1 chương trình hiển thị các giá trị khác nhau như giá trị con trỏ, địa chỉ biến, địa chỉ được trỏ đến, vv… để làm rõ các khái niệm trong đầu.

Không có lập trình viên tài năng nào mà không trải qua bước học lập trình với con trỏ. Và tôi cũng xin đảm bảo với bạn là họ cũng hết sức đau đầu khi bắt đầu trải qua bước này. Vậy nên nếu bạn chưa hiểu ngay cũng không cần hoảng hốt, hãy từ từ đọc lại và ghi nhớ từng chút một.

Phân bổ động bộ nhớ

Nếu các bạn vẫn háo hức muốn sử dụng con trỏ thì sau đây sẽ là công dụng đầu tiên.

Bộ nhớ tự động quản lý

Trong bài học trước đây nói về các biến, tôi đã từng giải thích cho các bạn việc khai báo biến được chia ra thành 2 bước :

  1. Yêu cầu máy tính cấp cho 1 vùng nhớ. Việc này gọi là phân bổ bộ nhớ.
  2. Lưu giá trị vào trong ô nhớ được cấp. Việc này là khởi tạo giá trị biến.

Việc này được thực hiện 1 cách tự động bới chương trình. Thêm vào đó, khi kết thúc 1 hàm, chương trình cũng trả lại vùng nhớ đó. Chúng ta gọi là giải phóng bộ nhớ. Và việc này cũng được thực hiện hoàn toàn tự động.

Những việc này chúng ta cũng có thể tự làm để có quản lý ở mức tinh tế hơn. Dưới đây là cách thức để thực hiện chúng.

Phân bổ 1 vùng nhớ

Để tự thực hiện 1 yêu cầu phân bổ vùng nhớ, chúng ta cần sử dụng phép toán new. new yêu cầu máy tính cấp cho một 1 nhớ và trả về 1 con trỏ trỏ tới ô nhớ được cấp.

int *conTro(0);
conTro = new int;

Câu lệnh thứ 2 xin cấp 1 ô nhớ để lưu số nguyên và gán địa chỉ ô nhớ đó cho giá trị của con trỏ.

Hình vẽ bên trên khá giống với cái sơ đồ lúc trước, bao gồm:

  • Ô nhớ 14536 chứa giá trị số nguyên chưa khởi tạo
  • Ô nhớ 53771 chứa 1 con trỏ trỏ về ô nhớ bên trên

Không có gì mới mẻ nhưng quan trọng là chúng ta có 1 biến trong ô nhớ 14563 mà không có nhãn. Cách duy nhất để sử dụng ô nhớ này là thông qua con trỏ.

! Nếu bạn thay đổi giá trị của con trỏ, chúng ta sẽ không còn cách nào có thể truy cập vào ô nhớ trên nữa, và vì thế không thể thay đổi hay xóa nó. Thế nên ô nhớ đó vẫn sẽ tiếp tục tồn tại và chiếm vùng nhớ trong bộ nhớ. Hiện tượng này được gọi là rò rỉ bộ nhớ.

Một khi vùng nhớ đã được phân bổ, ta có thể sử dụng biến này như tất cả các biến thông thường khác, chỉ khác là chúng ta cần phải sử dụng đến tham chiếu ngược thay vì dùng tên biến.

int *conTro(0);
conTro = new int;
*conTro = 2; //Truy nhap den o nho de thay doi gia tri

Dữ liệu đã được ghi vào ô nhớ không có nhãn lúc trước.

Hoàn toàn tương tự như cách chúng ta sử dụng 1 biến thông thường. Bây giờ, khi đã dùng xong, cũng nên học cách trả ô nhớ lại cho máy tính.

Giải phóng bộ nhớ

Sau khi sử dụng xong, chúng ta cần trả lại ô nhớ đã mượn cho máy tính bằng cách dùng phép toán delete.

int *conTro(0);
conTro = new int;
delete conTro; //Giai phong o nho

Ô nhớ được trả lại sẽ được máy tính dùng vào việc khác. Con trỏ vẫn còn tồn tại nhưng các bạn không thể truy nhập vào đó nữa.

Như có thể thấy trong hình, con trỏ của chúng ta đang trỏ vào 1 ô nhớ mà có thể đang được sử dụng bới 1 chương trình khác. Cần phải ngăn chặn tình huống này bằng cách gán cho con trỏ giá trị địa chỉ là 0 sau khi thực hiện phép toán delete. Như thế, mũi tên màu vàng trong hình sẽ biến mất. Quên làm việc này có thể dẫn đến việc chương trình bị treo khi đang chạy.

int *conTro(0);
conTro = new int;
delete conTro; //Giai phong o nho
conTro = 0;       //Con tro khong tro vao bat cu o nho nao

! Đừng quên giải phóng các ô nhớ mà bạn đã dùng xong. Nếu không, khi mà chương trình dùng nhiều ô nhớ đến khi không còn các ô nhớ trống sẽ dẫn đến chương trình đang chạy bị lỗi.

Ví dụ tổng kết

1 ví dụ quen thuộc nhưng được viết lại bằng cách sử dụng con trỏ : yêu cầu người dùng nhập số tuổi và in giá trị ra màn hình.

#include <iostream>
using namespace std;

int main(){
   int* conTro(0);
   conTro = new int;
   cout << "Ban bao nhieu tuoi ? ";
   cin >> *conTro;   //Ghi du lieu vao o nho tro boi 'conTro'
   cout << "Ban da " << * conTro << " tuoi." << endl;
   delete conTro;   //Giai phong o nho
   conTro = 0;       //Con tro khong tro vao bat cu o nho nao
   return 0;
}

Có đôi chút phức tạp hơn khi cần tự quản lý phân bổ động bộ nhớ. Trái lại thì chúng ta hoàn toàn làm chủ vùng nhớ về khía cạnh lúc nào cần yêu cầu và lúc nào thì cần giải phóng.

Trong phần lớn trường hợp thì việc này là không cần thiết. Thế nhưng khi bạn làm việc với Qt sau này, bạn sẽ rất hay dùng đến newdelete. Chúng ta sẽ nói rõ hơn vào lúc đó.

Khi nào thì sử dụng con trỏ

Sau đây là các trường hợp mà chúng ta cần thao tác với con trỏ trong chương trình.

  • Khi bạn cần tự mình quản lý việc cấp và giải phóng ô nhớ
  • Chia sẻ 1 biến giữa các hàm
  • Cần chọn 1 giá trị giữa nhiều tùy chọn.

Nếu bạn không nằm trong 3 trường hợp trên, bạn có thể không cần dùng đến con trỏ. Trong số 3 trường hợp này thì trường họp 1 vứa được nói đến bên trên vậy nên chúng ta sẽ tập trung vào 2 trường hợp còn lai trong phần tới đây.

Chia sẽ biến giữa các hàm

Bây giờ thì tôi vẫn chưa thể đưa ra 1 đoạn mã nguồn hoàn chỉnh cho các bạn xem. Khi nào chúng ta nói về lập trình hướng đối tượng trong chương sau, các bạn sẽ có cả đống mã nguồn ví dụ nếu muốn. Còn lúc này, tôi sẽ đưa ra 1 ví dụ trực quan hơn wink.

Đúng thế, đây là hình ảnh trong Warcraft III, trò chơi chiến thuật nổi tiếng của Blizzard. Những nhân vật trong trò chơi như thế này được thiết kế khá là phức tạp, tuy nhiên chúng ta vẫn có thể đoán ra 1 số cơ chế đã được áp dụng trong đó.

Trong hình, chúng ta có thể thấy quân human (màu đỏ) đang tấn công quân orc (màu xanh). Mỗi quân lính có một mục tiêu riêng xác định, ví dụ như quân lính ở chính giữa màn hình đang tấn công quân tướng cầm riu bên đối phương.

Trong chương sau, chúng ta sẽ xem cách để tạo ra các đối tượng, 1 kiểu biến “phức hợp” với những kiểu dữ liệu cũng đặc biệt không kém. Nói chung, trong C++, mọi thứ đều có thể biểu diễn dưới dạng các đối tượng.

Trong lúc này, chúng ta sẽ quan tâm những câu hỏi kiểu như “Làm sao để biểu diễn mục tiêu của 1 nhân vật bên đỏ?”.

Dù không thể viết ra được đoạn mã cụ thể nhưng chúng ta cũng có thể phán đoán phần nào. Đúng thế, chúng ta sẽ sử dụng con trỏ ! Mỗi nhân vật sẽ có 1 con trỏ trỏ về mục tiêu của mình để biết cần nhắm đến và tấn công cái gì. Đoạn mã sẽ kiểu kiểu như sau.

NhanVat *mucTieu; //Con tro tro len muc tieu la 1 nhan vat khac

Khi không có trận chiến, con trỏ sẽ trỏ về địa chỉ 0, nghĩa là không có mục tiêu. Còn khi có chiến đấu, con trỏ sẽ trỏ về 1 nhân vật bên địch. Nếu mục tiêu chết thì con trỏ sẽ chuyển sang 1 nhân vật mục tiêu khác.

Bạn có thể hình dung con trỏ giống như 1 mũi tên trỏ vào nhân vật bên địch.

Trong chương sau, chúng ta sẽ xem đoạn mã kiểu này được viết thế nào. Tôi đang nghĩ biết đâu chúng ta có thể viết 1 mini-RPG (trò chơi nhập vai) trong các bài tập ở chương sau nhỉ cool. Nhưng thôi, để sau hãy nói.

Chọn 1 giá trị giữa nhiều tùy chọn

Con trỏ còn cho phép chương trình thay đổi xử lý tùy thuộc vào lựa chọn của người dùng. Hãy lấy ví dụ của 1 bài kiểm tra trắc nghiệm, trong đó chúng ta yêu cầu người dùng chọn 1 trong 3 câu trả lời cho 1 câu hỏi. Sau khi người dùng trả lời xong, con trỏ sẽ trỏ đến câu trả lời được chọn.

#include <iostream>
#include <string>
using namespace std;

int main(){
    string cauTraLoiA, cauTraLoiB, cauTraLoiC;
    cauTraLoiA = "Nam 2000";
    cauTraLoiB = "Ngay tan the";
    cauTraLoiC = "Ky nguyen moi";

    cout << "Y nghia cua Y2K la gi ? " << endl; //Dat cau hoi
    cout << "A) " << cauTraLoiA << endl; //Hien thi 3 cau tra loi
    cout << "B) " << cauTraLoiB << endl;
    cout << "C) " << cauTraLoiC << endl;

    char cauTraLoi;
    cout << "Cau tra loi cua ban la (A,B hay C) : ";
    cin >> cauTraLoi; //Nhan cau tra loi tu nguoi dung

    string *cauTraLoiCaNhan(0); //Con tro tr ove cau tra loi
    switch(cauTraLoi){
    case 'A':
        cauTraLoiCaNhan = & cauTraLoiA; //Thay doi con tro tuy theo lua chon cua nguoi dung
        break;
    case 'B':
        cauTraLoiCaNhan = & cauTraLoiB;
        break;
    case 'C':
        cauTraLoiCaNhan = & cauTraLoiC;
        break;
    }
    //Hien thi cau tra loi duoc chon nho con tro
    cout << "Ban da chon cau tra loi la : " << * cauTraLoiCaNhan << endl;
    return 0;
}

Trong trường hợp này, biến sẽ mang 1 giá trị mà chúng ta không biết trước do nó phụ thuộc vào câu trả lời của người dùng. Vì thế chương trình sẽ diễn tiến khác nhau tùy theo giá trị được nhập.

Trong 3 trường hợp chúng ta đã nêu ở trên thì đây là trường hợp hiếm gặp nhất nhưng vẫn nên biết là đôi khi chúng ta sẽ rơi vào trường hợp này.

Tóm tắt bài hoc :
  • Mỗi biến được lưu trong bộ nhớ ở 1 địa chỉ khác nhau.
  • Mỗi địa chỉ chỉ có thể có 1 biến.
  • Có thể dùng dấu & để lấy ra giá trị của địa chỉ của biến, ví dụ : &tenBien.
  • Con trỏ là 1 biến chứa giá trị địa chỉ của 1 biến khác.
  • Cú pháp để khai báo con trỏ : int *conTro; (ví dụ cho những con trỏ về kiểu int).
  • Con trỏ có chứa địa chỉ ô nhớ mà nó trỏ đến. Để truy cập đến giá trị của ô nhớ đó thì phải dùng *conTro.
  • Chúng ta có thể tự quản lý bộ nhớ : phân bổ vùng nhớ với phép toán new và giải phóng vùng nhớ với phép toán delete.
  • Con trỏ là 1 khái niệm phức tạp và cần nhiều thời gian để có thể nắm vững nó. Lợi ích của con trỏ sẽ dần dần thể hiện ra trong phần sau của giáo trình.