# Task 4: Tìm hiểu chi tiết về con trỏ(Pointer) trong C/C++ DEADLINE 12h 18/12/2024 - Tìm hiểu về cơ chế hoạt động, cú pháp và một chường trình nhỏ để ví dụ. - Bài tập thực hành: Nhập giá trị cho các biến số nguyên x1, x2, x3, x4. Cho DUY NHẤT 1 biến pointer (int*) là p. 1 - In ra tất cả các giá trị của x1, x2, x3, x4 thông qua biến p 2- In ra trung bình nhân của x1, x2, x3, x4 thông qua biến p. Lưu ý: ko được truy cập trực tiếp vào vùng dữ liệu của x1, x2, x3, x4 nhưng được phép lấy địa chỉ của 4 biến này Chương trình cần chạy được như hình minh hoạ. ![image](https://hackmd.io/_uploads/ByDKK1kV1x.png) # CON TRỎ TRONG C++ ## 1.KHÁI NIỆM CON TRỎ - con trỏ là một biến có địa chỉ độc lập, nhưng giá trị trong vùng nhớ của con trỏ lại chính là địa chỉ của biến mà nó trỏ tới (hoặc một địa chỉ ảo). Con trỏ cho phép truy xuất và thao tác trực tiếp với vùng nhớ đó. ## 2. CÁCH SỬ DỤNG ### Khai báo con trỏ - Cú pháp khai báo và sử dụng con trỏ: - Để khai báo một con trỏ, ta sử dụng thêm toán tử * trong lời khai báo (không cần thiết phải đặt sát cạnh tên biến) - ``` Khi khai báo một biến con trỏ, thì biến đó chỉ được phép trỏ vào địa chỉ của các biến có cùng kiểu đã khai báo. ``` - Khai báo: ``` type* tên con trỏ ``` khai báo này được hiểu là con trỏ đang trỏ tới biến của ``` type ``` - Để gán giá trị cho con trỏ thì ta không thể trực tiếp gán được mà phải thông qua địa chỉ của biến. - Ví dụ: ```cpp! int x = 5; int* ptr = &x; cout << ptr << endl; // in ra địa chỉ của biến x cout << *ptr; // in ra giá trị biến x ``` - Một con trỏ sau khi được khai báo, hoàn toàn có thể trỏ đến địa chỉ nhiều biến khác nhau sau lần tham chiếu đầu tiên. ## 3. CÁC DẠNG CON TRỎ: ### 1. Con trỏ NULL - Con trỏ trong ngôn ngữ C/C++ vốn không an toàn. Nếu sử dụng con trỏ không hợp lý có thể gây lỗi chương trình. - Biến con trỏ có thể không cần khởi tạo giá trị ngay từ khi khai báo. Nhưng khi truy xuất giá trị của con trỏ bằng ```*``` khi chưa gán địa chỉ cụ thể cho con trỏ, chương trình có thể bị đóng. Nguyên nhân là do con trỏ đang nắm giữ một giá trị rác, giá trị rác đó có thể là địa chỉ thuộc một vùng nhớ đang được ứng dụng khác sử dụng, hoặc giá trị vượt quá giới hạn của bộ nhớ ảo. - Vì vậy, khi chưa gán địa chỉ cụ thể cho con trỏ, thì ta có thể dùng ```NULL ```, Tuy nhiên, đối với con trỏ, NULL là một giá trị đặc biệt, khi gán NULL cho con trỏ, điều đó có nghĩa là con trỏ đó chưa trỏ đến địa chỉ nào cả - Ví dụ: ```cpp! int* ptr = NULL; // hoặc dùng nullptr cũng được ``` ### 2. Con trỏ mảng. - Địa chỉ của mảng được quy ước là địa chỉ của phần tử đầu tiên trong mảng đó. - Ví dụ ```cpp! int arr[] = {1, 2, 3, 4, 5}; cout << &arr << endl; cout << &arr[0] << endl; cout << arr << endl; ``` - Kết quả khi chạy chương trình. ![image](https://hackmd.io/_uploads/SkWxsRGN1x.png) - Có thể thấy ngoài việc địa chỉ của ```arr[0]``` và của ```arr``` là giống nhau ta có thể sử dụng trực tiếp tên mảng một chiều là arr để truy cập vào địa chỉ của mảng một chiều. Vì thế, ta có thể in ra địa chỉ của cả 5 phần tử của mảng ```arr``` trên - Chương trình nhỏ ví dụ: ```cpp! cout << arr << endl; cout << arr + 1 << endl; cout << arr + 2 << endl; cout << arr + 3 << endl; cout << arr + 4 << endl; ``` - Kết quả khi chạy chương trình: ![image](https://hackmd.io/_uploads/HkyZ7Xm4yl.png) - Ngoài ra ta có thể dùng ```*``` để truy cập giá trị của từng phần tử của mảng hoặc có thể dùng các phép như tăng giảm như (++, --). Ví dụ: ```cpp! int arr[] = { 1, 2, 3, 4, 5 }; int* ptr = &arr[0]; cout << *(ptr + 1) << endl; // In ra phần tử arr[1]. cout << *(ptr + 2) << endl; // In ra phần tử arr[2]. ptr++; cout << *ptr << endl; // In ra phần tử arr[1]. ``` - Kết quả khi chạy chương trình: ![image](https://hackmd.io/_uploads/B1VON7X4Jl.png) ### 3. Con trỏ và hàm. - Hàm có hai kiểu nhận tham số là nhận giá trị hoặc là truyền tham chiếu - Chúng ta còn một kiểu truyền dữ liệu vào cho hàm nữa, đó là truyền địa chỉ vào hàm, vì vậy kiểu tham số của hàm có thể nhận giá trị là địa chỉ phải là một con trỏ. - Ví dụ: ```cpp! void func( int* ptr){ cout << *ptr << endl; // in ra giá trị a } int main(){ int a = 10; func(&a); return 0; } ``` - Nên nhớ giá trị địa chỉ được truyền vào hàm chỉ là bản copy, từ đó chúng ta có thể sử dụng toán tử dereference để thao tác với vùng nhớ tại địa chỉ đó. Chúng ta cũng có thể cho tham số của hàm trỏ đến địa chỉ khác, nhưng không ảnh hưởng gì đến con trỏ gốc. - Vậy nếu trong một số trường hợp cụ thể, ta muốn thay đổi địa chỉ được truyền vào hàm và cập nhật lại ra con trỏ tham số thực sự bên ngoài, thì ta sẽ sử dụng cách truyền tham chiếu. ### 4. Con trỏ và hằng số - Ví dụ: ```cpp! const int value = 5; int* ptr = &value; // Sẽ phát sinh dịch lỗi. ``` - Chương trình này sẽ lỗi vì ```value``` ở trên là hằng số và không thể thay đổi được bằng từ khóa ```const``` - Để chương trình có thể thực hiện thì ta đơn giản chỉ cần ```const``` trước chỗ khai báo con trỏ thôi. ```cpp! const int value = 5; const int *ptr = &value; ``` ### 5. Con trỏ hằng - Con trỏ hằng là loại con trỏ chỉ gán được địa chỉ một lần khi khởi tạo, điều này có nghĩa sau khi trỏ đến vùng nhớ nào đó thì nó không thể trỏ đi nơi khác được. - Để khai báo con trỏ hằng, chúng ta cần đặt từ khóa const giữa dấu * và tên con trỏ. ```cpp! int value = 5; int* const ptr = &value; ``` ### 6. Con trỏ Void. - Con trỏ ```*void``` là con trỏ có thể trỏ tới bất kì kiểu dữ kiệu nào. Tuy nhiên, khi sử dụng, cần phải ép kiểu sang đúng kiểu dữ liệu mà con trỏ đang trỏ tới. ```cpp! void* ptr; int a = 10; ptr = &a; cout << *(int*)ptr << endl; ``` # BÀI TẬP THỰC HÀNH Nhập giá trị cho các biến số nguyên x1, x2, x3, x4. Cho DUY NHẤT 1 biến pointer (int*) là p. 1 - In ra tất cả các giá trị của x1, x2, x3, x4 thông qua biến p 2- In ra trung bình nhân của x1, x2, x3, x4 thông qua biến p. ```cpp! #include<iostream> #include<cmath> using namespace std; int main(){ int x1, x2, x3, x4; cout << "Nhập lần lượt x1, x2, x3, x4: "; cin >> x1 >> x2 >> x3 >> x4; int* p = NULL; cout << "Kết quả 1: "; p = &x1; cout << *p << " "; p = &x2; cout << *p << " " ; p = &x3; cout << *p << " "; p = &x4; cout << *p << endl; int sum = 1; sum *= *p; p = &x3; sum *= *p; p = &x2; sum *= *p; p = &x1; sum *= *p; float result = sqrt(sqrt(sum)); cout << "Kết quả 2: "; cout << result << endl; return 0; } } ``` - Kết quả khi chạy chương trình: ![image](https://hackmd.io/_uploads/S1NINLkNJl.png)