Các dạng bài tập với con trỏ và lời giải năm 2024

Con trỏ trong lập trình là 1 định nghĩa hơi khó nhằn đối với các bạn mới học về C++. Không ngoa khi nói rằng C++ khó vì có con trỏ. Tuy nhiên ví như làm chủ được con trỏ, bạn có thể hiểu và thao tác với dữ liệu trong bộ nhớ máy tính, và những kiến ​​thức liên quan mà bạn học được thông qua con trỏ cũng rất hữu ích cho việc học các ngôn ngữ hướng đối tượng sau này như Java chẳng hạn.

Hãy cùng tìm hiểu con trỏ trong C++ là gì, quan hệ giữa con trỏ và địa chỉ trong C++, cấu trúc, vai trò và cách sử dụng con trỏ sau bài học này nhé.

Danh Mục Bài Viết

I. Khai Báo Con Trỏ Trong C++

Để khai báo con trỏ trong C++, chung ta sử dụng với cấu trúc ngữ pháp sau đây:

type *p;

Trong đó type là kiểu dữ liệu của con trỏ, và p là tên con trỏ. Lưu ý là kiểu dữ liệu của con trỏ phải giống với kiểu dữ liệu của dữ liệu cần lưu địa chỉ trong con trỏ.

Ví dụ, chúng ta khai báo con trỏ tên p với kiểu int như sau:

int *p; Lưu ý là các cách viết sau đây cũng được chấp nhận khi khai báo con trỏ trong C++:

int* p; int * p;

Các dạng bài tập với con trỏ và lời giải năm 2024
Khai Báo Con Trỏ Trong C++

II. Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++

Ngôn ngữ C++ hỗ trợ ba loại cấp phát bộ nhớ cơ bản, hai loại trong số đó bạn đã được học ở các bài học trước:

1. Cấp phát bộ nhớ tĩnh (Static memory allocation):

  • Xảy ra trên các biến tĩnh và biến toàn cục.
  • Vùng nhớ của các loại biến này được cấp phát một lần khi chương trình bắt đầu chạy và vẫn tồn tại trong suốt thời gian tồn tại của chương trình.
  • Kích thước của biến/mảng nên được biết tại thời điểm biên dịch chương trình.

2. Cấp phát bộ nhớ tự động (Automatic memory allocation):

  • Xảy ra trên các tham số hàm và biến cục bộ.
  • Vùng nhớ của các loại biến này được cấp phát khi chương trình đi vào khối lệnh và được giải phóng khi khối lệnh bị thoát.
  • Kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình.

3. Cấp phát bộ nhớ động (Dynamic memory allocation) sẽ được kể tới trong bài học này.

Trong tất cả những trường hợp, cấp phát bộ nhớ tĩnh và tự động có thể đáp ứng tốt các đề nghị của chương trình. Tuy nhiên, ta cùng xem ví dụ bên dưới:

Ví dụ: Chúng ta cần sử dụng một chuỗi để lưu tên của người dùng, nhưng chúng ta không biết tên của họ dài bao nhiêu cho đến khi họ nhập tên. Hoặc chúng ta cần lưu trữ danh sách nhân viên trong một công ty, nhưng chúng ta không biết trước được công ty đó sẽ có bao nhiêu nhân viên.

Đối với cấp phát bộ nhớ tĩnh và tự động, kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình. Vì vậy, điều tốt nhất chúng ta có thể làm là cố gắng đoán một kích thước tối đa của các biến đó:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên Khuyết điểm của cách khai báo trên:

+ Gây lãng phí bộ nhớ nếu các biến không thực sự sử dụng hết kích thước khi khai báo. Ví dụ: nếu công ty chỉ có 100 nhân viên, chúng ta có 400 vùng nhớ nhân viên không được sử dụng tới.

+ Thứ hai, hầu hết các biến thông thường (bao gồm mảng tĩnh) được cấp phát trong một phần bộ nhớ gọi là ngăn xếp (stack). Kích thước bộ nhớ stack cho một chương trình khá nhỏ (khoảng 1Mb với Visual Studio), nếu yêu cầu cấp phát vùng nhớ vượt quá con số này, chương trình sẽ bị đóng bởi hệ điều hành với lỗi stack overflow.

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow + Thứ ba, điều gì xảy ra nếu công ty có 600 nhân viên, trong khi mảng staff chỉ có 500 phần tử. Lúc này, chương trình sẽ bị giới hạn bởi kích thước được khai báo ban đầu.

Để giải quyết những hạn chế trên, cấp phát bộ nhớ động được ra đời.

Các dạng bài tập với con trỏ và lời giải năm 2024
Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++

III. Con Trỏ Trong C++ Dùng Để Làm Gì

Tác dụng của con trỏ trong c chính là để lưu giữ địa chỉ của dữ liệu trong bộ nhớ máy tính, và bằng cách truy cập vào địa chỉ này, chúng ta có thể lấy được giá trị của dữ liệu tại đó.

Ngoài ra thì giá trị của con trỏ cũng là một số, nên chúng ta cũng có thể thực hiện các phép tính toán với con trỏ, ví dụ như cộng thêm hoặc hoặc trừ đi một số lượng đơn vị.

Do đó, con trỏ trong C sẽ được dùng để làm 1 trong 2 công việc sau đây trong chương trình:

  1. Thao tác với địa chỉ bằng các phép tính toán với số được lưu trong nó
  2. Thao tác với giá trị tại địa chỉ mà nó lưu mà thôi.
    Các dạng bài tập với con trỏ và lời giải năm 2024
    Con Trỏ Trong C++ Dùng Để Làm Gì

IV. Mảng Con Trỏ Trong C++

Trước khi chúng ta hiểu về khái niệm mảng các con trỏ, chúng ta xem xét ví dụ sau, mà sử dụng một mảng gồm 3 số integer:

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

Khi code trên được biên dịch và thực thi, nó cho kết quả sau:

Gia tri cua var[0] = 10 Gia tri cua var[1] = 100 Gia tri cua var[2] = 200 Có một tình huống khi chúng ta muốn duy trì một mảng, mà có thể lưu giữ các con trỏ tới một kiểu dữ liệu int hoặc char hoặc bất kỳ kiểu nào khác. Sau đây là khai báo một mảng của các con trỏ tới một integer:

int *contro[MAX]; Nó khai báo contro như là một mảng các con trỏ MAX kiểu integer. Vì thế, mỗi phần tử trong contro, bây giờ giữ một con trỏ tới một giá trị int. Ví dụ sau sử dụng 3 số integer, mà sẽ được lưu giữ trong một mảng các con trỏ như sau:

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; int *contro[MAX]; for (int i = 0; i < MAX; i++) {

  contro[i] = &var[i]; // gan dia chi cua so nguyen.  
} for (int i = 0; i < MAX; i++) {
  cout << "Gia tri cua var[" << i << "] = ";  
  cout << *contro[i] << endl;  
} return 0; }

Khi code trên được biên dịch và thực thi, nó cho kết quả sau:

Gia tri cua var[0] = 10 Gia tri cua var[1] = 100 Gia tri cua var[2] = 200

Bạn có thể sử dụng một mảng các con trỏ tới ký tự để lưu giữ một danh sách các chuỗi như sau:

int *p; 0

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Các dạng bài tập với con trỏ và lời giải năm 2024
Mảng Con Trỏ Trong C++

V. Con Trỏ Hàm Trong C++

Con trỏ hàm là một biến lưu trữ địa chỉ của một hàm, thông qua biến đó, ta có thể gọi hàm mà nó trỏ tới.

Cú pháp khai báo con trỏ hàm:

(*)();

int *p; 1

Chú ý: Dấu ngoặc () quanh *fcnPtr là bắt buộc.

VI. Con Trỏ Trong Class C++

Một con trỏ tới một lớp trong C++ được thực hiện theo cách giống hệt như một con trỏ tới một cấu trúc; và để truy cập các thành viên của một con trỏ tới một lớp bạn sử dụng toán tử truy cập thành viên trong C++ là toán tử ->, như khi bạn thực hiện với các con trỏ tới cấu trúc. Cũng như với tất cả con trỏ, bạn phải khai báo con trỏ trước khi sử dụng nó.

Bạn thử ví dụ sau để hiểu khái niệm con trỏ tới một lớp trong C++:

int *p; 2

Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:

Các dạng bài tập với con trỏ và lời giải năm 2024
Con Trỏ Trong Class C++

VII. Con Trỏ Trong Struct C++

Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++. Chúng ta cùng tiếp tục tìm hiểu về kiểu dữ liệu tự định nghĩa thông qua từ khóa struct mà ngôn ngữ C++ hỗ trợ. Trong bài học này, mình sẽ trình bày về kiểu struct khi sử dụng kết hợp với con trỏ.

Như các bạn đã học trong bài trước, sau khi chúng ta tự định nghĩa một struct, compiler sẽ coi tên gọi của struct đó như là một kiểu dữ liệu. Điều này có nghĩa khi chúng ta sử dụng các kiểu dữ liệu built-in để tạo ra các biến, tham chiếu hoặc con trỏ thì chúng ta cũng có thể sử dụng kiểu struct để tạo ra biến struct, tham chiếu struct và con trỏ kiểu struct (Pointer to struct).

Pointer to struct

Đầu tiên, chúng ta cùng định nghĩa một kiểu dữ liệu theo ý muốn. Dưới đây, mình định nghĩa một kiểu dữ liệu có tên là Letter:

int *p; 3

Trong struct Letter mình chưa định nghĩa các trường dữ liệu, lúc này Letter là một kiểu dữ liệu rỗng. Nhưng ngôn ngữ C++ vẫn đặt kích thước của kiểu Letter này là 1 bytes.

Mục đích là để đảm bảo địa chỉ của 2 biến được tạo ra sẽ có địa chỉ khác nhau. Tuy nhiên, định nghĩa ra một struct rỗng không có tác dụng gì trong chương trình, chúng ta cùng thêm vào một số trường dữ liệu cho Letter:

int *p; 4

Một lá thư sẽ có thông tin về người gửi và người nhận, nên mình thêm vào 2 trường dữ liệu kiểu C-style string dùng để lưu thông tin mà người dùng điền vào một lá thư.

Mình vừa định nghĩa xong một kiểu dữ liệu mới để phục vụ cho chương trình của mình. Bây giờ chúng ta cùng tạo ra một đơn vị từ kiểu dữ liệu trên (mình thao tác luôn trong hàm main):

int *p; 5

Với mỗi biến kiểu Letter được tạo ra, chương trình sẽ yêu cầu cấp phát 100 bytes (50 bytes cho trường dữ liệu from và 50 bytes cho trường dữ liệu to), và chắc chắn rồi, biến đó sẽ có một địa chỉ xác định được thông qua toán tử address-of.

int *p; 6

Ở đoạn chương trình trên, mình in ra địa chỉ của biến myLetter, đồng thời in ra luôn địa chỉ của trường dữ liệu from của biến myLetter. Kết quả cho thấy 2 địa chỉ được in ra có giá trị hoàn toàn giống nhau. Điều này có nghĩa địa chỉ của trường dữ liệu đầu tiên trong một biến struct cũng là địa chỉ của biến struct đó.

Các bạn có thể liên hệ struct với mảng một chiều trong C/C++, khi mảng một chiều mà tập hợp các phần tử có cùng kiểu dữ liệu được bao bọc bởi tên mảng một chiều và địa chỉ của mảng một chiều cũng là địa chỉ của phần tử đầu tiên trong mảng, một biến struct sẽ bao gồm tập hợp các trường dữ liệu mà địa chỉ của biến struct sẽ là địa chỉ của trường dữ liệu được khai báo đầu tiên trong struct.

Và như các bạn cũng đã học về con trỏ (Pointer), kiểu dữ liệu của con trỏ dùng để xác định kiểu dữ liệu của vùng nhớ mà con trỏ có thể trỏ đến. Vậy thì để cho con trỏ trỏ đến một địa chỉ của biến kiểu struct, chúng ta cần có một con trỏ cùng kiểu struct với biến được trỏ đến.

int *p; 7

Dù kích thước của kiểu dữ liệu struct có lớn bao nhiêu, biến con trỏ cũng chỉ có kích thước 4 bytes trên hệ điều hành 32 bits và kích thước 8 bytes trên hệ điều hành 64 bits (đủ để trỏ đến toàn bộ địa chỉ trên bộ nhớ ảo).

Access struct members

Trong bài học trước, các bạn đã biết cách truy cập đến các trường dữ liệu của các biến struct thông qua member selection operator (dấu chấm). Nhưng khi sử dụng Pointer to struct, member selection operator được sử dụng dưới cách viết khác. Để phân biệt sự khác nhau khi sử dụng member selection operator cho biến struct thông thường và một Pointer to struct, các bạn cùng xem ví dụ bên dưới:

int *p; 8

Như các bạn thấy, kết quả của việc truy xuất giá trị thông qua tên biến struct và con trỏ kiểu struct là hoàn toàn giống nhau, và chúng đều dùng toán tử member selection. Tuy nhiên, để phân biệt biến con trỏ và biến thông thường, biến con trỏ kiểu struct sẽ truy cập đến các trường dữ liệu trong vùng nhớ bằng toán tử (->). Hai toán tử này cùng tên, chỉ khác nhau về cách biểu diễn.

Một số nhầm lần khi sử dụng struct và Pointer to struct

Khi mới tìm hiểu về Pointer to struct, các bạn có thể bị nhầm lẫn giữa cách khởi tạo hoặc gán giá trị cho biến struct thông thường và biến con trỏ struct.

int *p; 9

Đoạn chương trình trên báo lỗi vì biến con trỏ chỉ nhận giá trị là địa chỉ. Tuy nhiên, lỗi này có thể thấy dễ dàng vì Visual studio đưa ra thông báo lỗi ngay. Dưới đây là cách gán giá trị đúng khi mình sử dụng toán tử dereference cho biến con trỏ struct để thay đổi giá trị bên trong vùng nhớ:

int* p; int * p; 0

Hoặc một cách khác là chúng ta cấp phát vùng nhớ cho biến con trỏ struct, và dereference đến đó để gán giá trị cho nó:

int* p; int * p; 1

Và các bạn lưu ý khi sử dụng biến kiểu con trỏ struct thì chúng ta sử dụng toán tử member selection này (->). Có một số bạn nhầm lẫn giữa biến con trỏ struct và trường dữ liệu kiểu con trỏ. Ví dụ:

int* p; int * p; 2

Mình thêm vào struct một trường dữ liệu kiểu con trỏ char nhưng việc truy xuất đến trường dữ liệu này không có gì thay đổi khi mình sử dụng biến struct thông thường.

int* p; int * p; 3

Sẽ phức tạp hơn một chút khi các bạn sử dụng các nested struct. Ví dụ:

int* p; int * p; 4

Như các bạn thấy, từ biến con trỏ pAccount truy xuất vào các trường dữ liệu bên trong thì mình dùng toán tử (->), nhưng trường dữ liệu Date trong struct BankAccount là biến thông thường, nên mình dùng dấu chấm để truy xuất dữ liệu ngày đăng kí.

Các dạng bài tập với con trỏ và lời giải năm 2024
Con Trỏ Trong Struct C++

VIII. Con Trỏ Null Trong C++

Con trỏ NULL trong C++ là một hằng với một giá trị là 0 được định nghĩa trong một vài thư viện chuẩn, gồm iostream.

int* p; int * p; 5

Kết quả:

int* p; int * p; 6

Trên hầu hết các hệ điều hành, các chương trình không được phép truy cập bộ nhớ tại địa chỉ 0, vì bộ nhớ đó được dự trữ bởi hệ điều hành. Tuy nhiên, địa chỉ bộ nhớ 0 có ý nghĩa đặc biệt, nó chỉ ra rằng con trỏ không được trỏ tới một vị trí ô nhớ có thể truy cập. Nhưng theo qui ước, nếu một con trỏ chứa giá trị 0, nó được xem như là không trỏ tới bất cứ thứ gì.

Để kiểm tra một con trỏ null trong C++, bạn có thể sử dụng lệnh if như sau:

int* p; int * p; 7

Các dạng bài tập với con trỏ và lời giải năm 2024
Con Trỏ Null Trong C++

IX. Nhập Xuất Mảng Bằng Con Trỏ Trong C++

Bằng cách sử dụng con trỏ mảng, chúng ta có thể chỉ định vị trí các phần tử trong mảng, cũng như là truy cập và lấy giá trị của các phần tử đó.

Ứng dụng điều này, chúng ta cũng có thể nhập xuất mảng bằng con trỏ trong C++ như sau.

Nhập mảng bằng con trỏ trong C++

Trong bài Nhập xuất mảng trong C++ chúng ta đã biết cách tạo hàm nhập trực tiếp các giá trị từ bàn phím vào mảng như sau:

int* p; int * p; 8

Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần nhập.

Để nhập mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng giá trị con trỏ thay cho mảng, và thay vì dùng index để chỉ định vị trí nhập dữ liệu, thì chúng ta sẽ sử dụng trực tiếp giá trị con trỏ để chỉ định vị trí cần nhập. Chúng ta viết hàm nhập mảng bằng con trỏ trong C++ như sau:

int* p; int * p; 9

Lưu ý tại đây chúng ta sử dụng lệnh cin để nhập dữ liệu vào mảng, nên cần chỉ định bản thân dữ liệu mà con trỏ chỉ đến bằng cách thêm dấu hoa thị * trước con trỏ.

Tuy nhiên nếu sử dụng hàm scanf được kế thừa từ ngôn ngữ C để nhập mảng bằng con trỏ trong C++, thay vì chỉ định dữ liệu thì chúng ta cần chỉ định địa chỉ của dữ liệu đã được lưu vào con trỏ như sau:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 0

Xuất mảng bằng con trỏ trong C++

Trong bài xuất xuất mảng trong C++ chúng ta đã biết cách tạo hàm xuất trực tiếp các giá trị của mảng như sau:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 1

Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần xuất.

Để xuất mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng con trỏ thay cho mảng, và thay vì truy xuất giá trị các phần tử của mảng bằng index thì chúng ta sẽ dùng tên con trỏ và dấu hoa thị để xuất giá trị đó. Chúng ta viết hàm xuất mảng bằng con trỏ trong C++ như sau:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 2

Chương trình mẫu nhập xuất mảng bằng con trỏ trong C++

Dưới đây là chương trình mẫu sử dụng các hàm trên để nhập xuất mảng bằng con trỏ trong C++:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 3

Kết quả:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 4

Các dạng bài tập với con trỏ và lời giải năm 2024
Nhập Xuất Mảng Bằng Con Trỏ Trong C++

X. Con Trỏ Và Tham Chiếu Trong C++

C và C++ hỗ trợ con trỏ cái mà khác với hầu hết các ngôn ngữ lập trình khác. Các ngôn ngữ khác bao gồm C ++, Java, Python, Ruby, Perl và PHP đều hỗ trợ tham chiếu.

Nhìn bề ngoài, cả hai tham chiếu và con trỏ đều rất giống nhau, cả hai đều được sử dụng để có một biến cung cấp quyền truy cập cho một biến khác. Với việc cả hai đều cung cấp nhiều khả năng giống nhau, người ta thường không rõ điều gì khác biệt giữa các cơ chế khác nhau này. Trong bài viết này, tôi sẽ cố gắng minh họa sự khác biệt giữa con trỏ và tham chiếu.

Con trỏ: Con trỏ là một biến chứa địa chỉ bộ nhớ của một biến khác. Một con trỏ cần được deference với toán tử * để truy cập vào vị trí bộ nhớ mà nó trỏ tới.

Tham chiếu: Một biến tham chiếu là một bí danh, tức là một tên khác của một biến đã tồn tại. Một tham chiếu, giống như một con trỏ, cũng được hiện thựcbằng cách lưu trữ địa chỉ của một đối tượng. Một tham chiếu có thể được coi là một con trỏ hằng (đừng nhầm với một con trỏ đến một giá trị không đổi!) với tính năng tự động chuyển hướng, tức là trình biên dịch sẽ áp dụng toán tử * cho bạn.

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 5

Sự khác nhau giữa Con trỏ và Tham chiếu

Khởi tạo

Một con trỏ có thế được khai báo và khởi tạo đồng thời hoặc trên nhiều dòng.

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 6

Trong khi tham chiếu phải được khởi tạo cùng lúc với khai báo.

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 7

Chú ý: Sự khác biệt này có thể thay đổi từ trình biên dịch này sang trình biên dịch khác. Sự khác biệt trên là đối với Turbo IDE.

Gán lại giá trị

Một con trỏ có thể được gán lại. Thuộc tính này hữu ích cho việc triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. Xem ví dụ sau:

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 8

Trong khi đó, tham chiếu không gán lại mà phải được gán ngay lúc khởi tạo.

char name[30]; // tên không quá 30 ký tự Staff staff[500]; // công ty không quá 500 nhân viên 9

Địa chỉ bộ nhớ

Một con trỏ có địa chỉ bộ nhớ và kích thước riêng của nó trên ngăn xếp trong khi một tham chiếu chia sẻ cùng địa chỉ bộ nhớ (với biến gốc) nhưng cũng chiếm một số không gian trên ngăn xếp.

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 0

Giá trị NULL

Con trỏ có thể được gán NULL trực tiếp, trong khi tham chiếu không thể. Các ràng buộc liên quan đến tham chiếu (không NULL, không gán lại) đảm bảo rằng các hoạt động cơ bản không rơi vào trường hợp ngoại lệ.

Con trỏ đến con trỏ

Bạn có thể có các con trỏ đến con trỏ cung cấp nhiều cấp độ chuyển hướng bổ sung. Trong khi đó, các tham chiếu chỉ cung cấp một mức chuyển hướng.

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 1

Các phép tính toán học

Các phép toán số học khác nhau có thể được thực hiện trên con trỏ trong khi không có thứ gọi là Số học tham chiếu. (Nhưng bạn có thể lấy địa chỉ của một đối tượng được trỏ bởi một tham chiếu và thực hiện số học con trỏ trên đó như &obj + 5)

Khi nào sử dụng cái nào

Hiệu suất hoàn toàn giống nhau, vì các tham chiếu được triển khai bên trong dưới dạng con trỏ. Tuy nhiên, bạn vẫn có thể ghi nhớ một số điểm để quyết định khi nào sử dụng cái gì:

Sử dụng tham chiếu:

  • Trong các tham số hàm và kiểu trả về.

Sử dụng con trỏ:

  • Sử dụng con trỏ nếu cấn tính toán con trỏ số học hoặc NULL. Ví dụ đối với mảng (Lưu ý rằng truy cập mảng được thực hiện bằng cách sử dụng số học con trỏ).
  • Để triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. và các thuật toán của chúng vì để trỏ ô khác nhau, chúng ta phải sử dụng khái niệm con trỏ.
    Các dạng bài tập với con trỏ và lời giải năm 2024
    Con Trỏ Và Tham Chiếu Trong C++

XI. Ý Nghĩa Con Trỏ This Trong C++

Con trỏ this trong C++

This là một con trỏ đặc biệt dùng để trỏ đến địa chỉ của đối tượng hiện tại. Như vậy để truy cập đến các thuộc tính, phương thức của đối tượng hiện tại thì ta sẽ sử dụng con trỏ this. Hãy xem ví dụ dưới đây.

Ví dụ

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 2

Trong ví dụ này mình đã tạo ra ba thuộc tính để lưu trữ thông tin của một nhân viên đó là: manv, ten, tuoi. Ngoài ra mình có tạo thêm phương thức setData() dùng để gán dữ liệu cho sinh viên, và showData() dùng để hiển thị dữ liệu.

Trong phương thức setData() mình đã sử dụng từ khóa this->ten_thuoc_tinh để thực hiện phép gán dữ liệu cho các thuộc tính, còn ở phương thức showData() mình cũng sử dụng cú pháp tương tự để hiển thị dữ liệu của các thuộc tính. Như vậy công dụng của từ khóa this chính là một con trỏ và trỏ đến địa chỉ của đối tượng hiện tại.

Câu hỏi đặt ra là đối tượng hiện tại tại là gì? Để hiểu rõ hơn thì hãy xem đoạn code sử dụng class trên như sau:

Ví dụ

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 3

Trong ví dụ này mình đã tạo ra hai đối tượng sinh viên đó là n1 và n2, và con trỏ this của n1 sẽ trỏ đến chính đối tượng n1, con trỏ this của n2 sẽ trỏ đến chính đối tượng n2, đây ta gọi là đối tượng hiện tại.

Lưu ý: Trong các phương thức bình thường (không phải hàm khởi tạo) nếu bạn sử dụng tên của biến thì sẽ có hai trường hợp xảy ra.

  • Nếu biến đó không tôn tại trong phương thức mà nó lại trùng với tên thuộc tính thì mặc nhiên nó sẽ hiểu đó là thuộc tính.
  • Nếu biến đó có khai báo trong phương thức thì ta sẽ hiểu đó là biến bình thường, không phải là thuộc tính.

Một ví dụ khác về con trỏ this

Bạn hãy xem ví dụ dưới đây, đây là một ví dụ mình viết lại ở phần 1 và có một chút thay đổi.

Ví dụ

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 4

Các dạng bài tập với con trỏ và lời giải năm 2024
Ý Nghĩa Con Trỏ This Trong C++

XII. Con Trỏ Void Trong C++

Để tìm hiều về con trỏ void trong C++, trước hết bạn cần phải nắm vững các kiến thức cơ bản về con trỏ void. Đừng lo lắng vì Techacademy đã chuẩn bị cho bạn trong các bài viết sau đây:

Con trỏ void trong C++ là gì

Trong số các kiểu con trỏ, bạn có thể xác định một con trỏ hơi khác thường được gọi là con trỏ void.

Giống như các loại con trỏ khác trong C++ thì con trỏ void cũng được sử dụng để lưu trữ địa chỉ của một dữ liệu trong bộ nhớ máy tính.

Tuy nhiên điều đặc biệt ở đây là, với kiểu dữ liệu mà con trỏ void lưu giữ địa chỉ thì chương trình có thể truy cập đến địa chỉ của dữ liệu đó, nhưng không xác định được kiểu của nó. Nói cách khác thì con trỏ void được sử dụng để lưu giữ địa chỉ của các kiểu dữ liệu không tồn tại kiểu dữ liệu.

Khai báo con trỏ void trong C++

Cách khai báo con trỏ void trong C++ cũng tương tự như với các loại con trỏ khác, chúng ta viết kiểu void, rồi dấu hoa thị *, và cuối cùng là tên con trỏ void như sau:

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 5

Cách viết này cũng tương tự như với các kiểu con trỏ int hay char chẳng hạn:

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 6

Chúng ta cũng có thể thực hiện các thao tác như gán địa chỉ vào con trỏ, hoặc là in địa chỉ được gán vào con trỏ void tương tự như các loại con trỏ khác trong C++. Ví dụ:

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 7

Tuy nhiên, không giống như với các kiểu con trỏ khác thì chúng ta lại không thể thực hiện các thao tác với giá trị của biến mà con trỏ void trỏ đến, và vì thế cũng không thể biết được kiểu của dữ liệu đó là gì. Ví dụ, chúng ta không thể đọc được giá trị của biến thông qua con trỏ, vì lỗi sau đây sẽ xảy ra:

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 8

Vậy chẳng phải con trỏ void trong C++ rất là vô dụng hay sao? Tất nhiên là không phải rồi, vì chúng ta sẽ cần tới con trỏ void trong các trường hợp như dưới đây:

Sử dụng con trỏ void trong C++

Con trỏ void trong C++ sẽ được sử dụng trong các trường hợp đặc biệt sau đây:

Con trỏ vạn năng giúp lưu giữ tất cả các loại giữ liệu trong C++

Một điều dễ hiểu là do con trỏ void không tồn tại kiểu của dữ liệu mà nó đang chỉ đến, nên nó có khả năng chấp nhận và lưu giữ địa chỉ của tất cả các loại giữ liệu khác nhau trong C++. Đây là điều mà các con trỏ khác trong C++ không làm được. Ví dụ như con trỏ kiểu int thì chỉ chấp nhận lưu địa chỉ của dữ liệu kiểu int, còn con trỏ kiểu char thì cũng chỉ có thể chấp nhận lưu giữ địa chỉ của kiểu giữ liệu char. Nhưng với con trỏ void, void chấp hết ^_ .

Ví dụ cụ thể, con trỏ void trong C++ dưới đây có thể lưu giữ địa chỉ của tất cả các loại giữ liệu mà không sợ lỗi xảy ra trong chương trình.

char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow 9

Con trỏ giúp cố ý ẩn kiểu dữ liệu

Vì con trỏ C++ không cho phép chúng ta đọc kiểu dữ liệu cũng như truy cập vào dữ liệu tại địa chỉ mà nó lưu giữ, nên con trỏ void có vai trò vô cùng quan trong khi chúng ta muốn ẩn kiểu dữ liệu nào đó trong chương trình.

Đây là một thuật toán vô cùng phức tạp đòi hỏi lượng kiến thức khá cao, chỉ dành cho các bạn thực sự pro và muốn tìm hiểu sâu về C++ mà thôi.

Các dạng bài tập với con trỏ và lời giải năm 2024
Con Trỏ Void Trong C++

XIII. Bài Tập Về Con Trỏ Trong C++

Trong chủ đề này, chúng ta cùng làm một số bài tập về Con trỏ trong C++.

Bài tập 1

Sử dụng con trỏ trong C++, bạn hãy viết một chương trình C++ để nhận dữ liệu từ người dùng và tìm giá trị lớn nhất của một tập dữ liệu nội bộ.

Lời giải

Dưới đây là chương trình C++ để giải bài tập trên. Mình sử dụng một hàm mà nhận mảng các giá trị dữ liệu và kích cỡ của nó. Hàm này trả về con trỏ mà trỏ tới giá trị lớn nhất.

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

0

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Các dạng bài tập với con trỏ và lời giải năm 2024
Bài Tập Về Con Trỏ Trong C++

Bài tập 2

Viết một chương trình C++ để nhận 5 giá trị nguyên từ bàn phím. 5 giá trị này sẽ được lưu trữ trong một mảng bởi sử dụng một con trỏ. Sau đó, in các phần tử của mảng trên màn hình.

Lời giải

Dưới đây là chương trình C++ để giải bài tập trên.

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

1

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Các dạng bài tập với con trỏ và lời giải năm 2024
Bài Tập Về Con Trỏ Trong C++

Sửa đổi lời giải trên để in các phần tử của mảng theo thứ tự đảo ngược bởi sử dụng một con trỏ.

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

2

Chạy chương trình C++ trên sẽ cho kết quả như hình sau:

Các dạng bài tập với con trỏ và lời giải năm 2024
Bài Tập Về Con Trỏ Trong C++

XIV. Ép Kiểu Con Trỏ Trong C++

Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác.

Cú pháp:

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

3

Ví dụ:

include

using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) {

  cout << "Gia tri cua var[" << i << "] = ";  
  cout << var[i] << endl;  
} return 0; }

4

Trong ví dụ trên, đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.

Phân loại ép kiểu trong C++

Trong C++, có hai loại ép kiểu dữ liệu:

  1. Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.