Nội dung chính
1. Con trỏ số học
Ngôn ngữ C ++ cho phép bạn thực hiện các phép tính cộng hoặc trừ số nguyên trên con trỏ. Nếu ptr trỏ đến một số nguyên, ptr + 1 là địa chỉ của số nguyên tiếp theo trong bộ nhớ sau ptr. ptr – 1 là địa chỉ của số nguyên trước ptr.
Lưu ý rằng ptr + 1 không trả về địa chỉ bộ nhớ sau ptr, mà là địa chỉ bộ nhớ của đối tượng tiếp theo của kiểu mà ptr trỏ tới. Nếu ptr trỏ đến một số nguyên (giả sử 4 byte), ptr + 3 có nghĩa là 3 số nguyên (12 byte) sau ptr. Nếu ptr trỏ đến một ký tự, luôn là 1 byte, ptr + 3 có nghĩa là 3 ký tự (3 byte) sau ptr.
Khi tính toán kết quả của một biểu thức số học con trỏ, trình biên dịch luôn nhân toán hạng số nguyên với kích thước của đối tượng được trỏ tới. Điều này được gọi là chia tỷ lệ.
Hãy xem xét chương trình sau:
Kết quả tại máy của mình:
0012FF7C
0012FF80
0012FF84
0012FF88
Như bạn có thể thấy, mỗi địa chỉ này khác nhau 4 (7C + 4 = 80 trong hệ thập lục phân). Điều này là do một số nguyên là 4 byte trên máy của tác giả.
Chương trình tương tự sử dụng short thay vì int:
Trên máy của mình, kết quả này:
0012FF7C
0012FF7E
0012FF80
0012FF82
Bởi vì một đoạn ngắn là 2 byte, mỗi địa chỉ khác nhau 2.
2. Mảng được sắp xếp tuần tự trong bộ nhớ
Bằng cách sử dụng toán tử address-of (&), chúng ta có thể xác định rằng các mảng được sắp xếp tuần tự trong bộ nhớ. Có nghĩa là, các phần tử 0, 1, 2,… đều nằm kề nhau theo thứ tự.
Trên máy của mình, điều này được in:
Phần tử 0 ở địa chỉ: 0041FE9C
Phần tử 1 ở địa chỉ: 0041FEA0
Yếu tố 2 ở địa chỉ: 0041FEA4
Yếu tố 3 ở địa chỉ: 0041FEA8
Lưu ý rằng mỗi địa chỉ bộ nhớ này cách nhau 4 byte, kích thước của một số nguyên trên máy của mình.
3. Con trỏ số học, mảng và điều kỳ diệu đằng sau việc lập chỉ mục
Trong phần trên, bạn đã biết rằng các mảng được sắp xếp tuần tự trong bộ nhớ.
Trong bài – Con trỏ và mảng, bạn đã biết rằng một mảng cố định có thể phân rã thành một con trỏ trỏ đến phần tử đầu tiên (phần tử 0) của mảng.
Cũng trong phần trên, bạn đã biết rằng việc thêm 1 vào con trỏ sẽ trả về địa chỉ bộ nhớ của đối tượng tiếp theo thuộc loại đó trong bộ nhớ.
Do đó, chúng ta có thể kết luận rằng việc thêm 1 vào một mảng sẽ trỏ đến phần tử thứ hai (phần tử 1) của mảng. Chúng tôi có thể xác minh bằng thực nghiệm rằng điều này là đúng:
Lưu ý rằng khi thực hiện chuyển hướng thông qua kết quả của số học con trỏ, dấu ngoặc đơn là cần thiết để đảm bảo độ ưu tiên của toán tử là chính xác, vì toán tử * có ưu tiên cao hơn toán tử +.
Trên máy của mình, điều này được in:
0017FB80
0017FB80
7
7
Nó chỉ ra rằng khi trình biên dịch nhìn thấy toán tử chỉ số con ([]), nó thực sự chuyển nó thành một phép cộng và chuyển hướng con trỏ! Tổng quát hóa, array [n] giống như * (array + n), với n là một số nguyên. Toán tử chỉ số con [] có cả vẽ trông đẹp và dễ sử dụng (vì vậy bạn không cần phải nhớ dấu ngoặc đơn).
4. Sử dụng con trỏ để lặp qua một mảng
Chúng ta có thể sử dụng con trỏ và số học con trỏ để lặp qua một mảng. Mặc dù không thường được thực hiện theo cách này (sử dụng subcript thường dễ đọc hơn và ít mắc lỗi hơn), ví dụ sau đây cho thấy điều đó là có thể:
Làm thế nào nó hoạt động? Chương trình này sử dụng một con trỏ để lướt qua từng phần tử trong một mảng. Hãy nhớ rằng mảng phân rã thành các con trỏ đến phần tử đầu tiên của mảng. Vì vậy, bằng cách gán ptr cho tên, ptr cũng sẽ trỏ đến phần tử đầu tiên của mảng. Điều hướng thông qua ptr được thực hiện cho mỗi phần tử khi chúng ta gọi isVowel (* ptr), và nếu phần tử là một nguyên âm, numVowels được tăng dần. Sau đó, vòng lặp for sử dụng toán tử ++ để đưa con trỏ tới ký tự tiếp theo trong mảng. Vòng lặp for kết thúc khi tất cả các ký tự đã được kiểm tra.
Chương trình trên tạo ra kết quả:
Vì việc đếm các phần tử là phổ biến, nên thư viện thuật toán cung cấp std :: count_if, đếm các phần tử đáp ứng một điều kiện. Chúng ta có thể thay thế vòng lặp for bằng lời gọi std :: count_if.
std :: begin trả về một trình lặp (con trỏ) cho phần tử đầu tiên, trong khi std :: end trả về một trình vòng lặp cho phần tử sẽ là một sau phần tử cuối cùng. Trình lặp được trả về bởi std :: end chỉ được sử dụng như một điểm đánh dấu, việc truy cập vào nó gây ra hành vi không xác định vì nó không trỏ đến một phần tử thực.
std :: begin và std :: end chỉ hoạt động trên các mảng có kích thước đã biết. Nếu mảng được phân rã thành một con trỏ, chúng ta có thể tính toán bắt đầu và kết thúc theo cách thủ công.
Lưu ý rằng chúng tôi đang tính toán name + nameLength, không phải name + nameLength – 1, bởi vì chúng ta không muốn phần tử cuối cùng, mà phần tử giả vượt qua phần tử cuối cùng.
Tính toán bắt đầu và kết thúc của một mảng như thế này hoạt động cho tất cả các thuật toán cần đối số bắt đầu và kết thúc.
Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.
Nguồn và Tài liệu tiếng anh tham khảo:
Tài liệu từ cafedev:
- Full series tự học C++ từ cơ bản tới nâng cao tại đây nha.
- Ebook về C++ tại đây.
- Các series tự học lập trình MIỄN PHÍ khác
- Nơi liên hệ hợp tác hoặc quảng cáo cùng Cafedevn tại đây.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!