V.4.1 - Con trỏ và các phép toán trên con trỏ
Trong phần đầu trình bày về kiểu dữ liệu và các phép toán chúng ta cũng đã đề cập tới kiểu con trỏ, trong phần này chúng ta dề cập chi tiết hơn về con trỏ và các phép toán có thể sử dụng trên chúng.
Con trỏ là kiểu dữ liệu mà một thành phần kiểu này có thể lưu trữ địa chỉ của một thành phần nào đó (có thể là biến, hằng, hàm), hoặc ta nói nó trỏ tới thành phần đó.
M ột con trỏ lưu trữ địa chỉ của một thành kiểu T thì ta nói p là con trỏ kiểu T, đặc biệt nếu T là một kiểu con trỏ, hay nói cách khác, p lưu trữ địa chỉ của một con trỏ khác thì ta nói p là con trỏ trỏ tới con trỏ. Cú pháp khai báo con trỏ
<kiểu> * <tên_con_trỏ>;
Ví dụ:
int *p; // p là con trỏ kiểu int
float * q ; // q là con trỏ kiểu float
char *s ; // s là con trỏ kiểu char hay xâu kí tự
int ** r; // r là con trỏ tới con trỏ kiểu int
C ũng giống như biến bình thường khi khai báo một biến con trỏ, chương trình dịch cũng cấp phát vùng nhớ cho biến đó, lưu ý rằng giá trị trong vùng nhớ đó đang là bao nhiêu thì quan niệm đó là địa chỉ mà con trỏ này trỏ tới. Vì vậy các bạn phải chú ý khi dùng con trỏ phải bảo đảm nó trỏ tới đúng vùng nhớ cần thiết. M ột con trỏ chưa lưu trữ địa chỉ của thành phần nào ta gọi là con trỏ rỗng và có giá trị là NULL (là một hằng định nghĩa sẵn thực ra = 0). Khi gặp các lệnh khai báo biến trong chương trình thì chương trình dịch sẽ cấp phát vùng nhớ phù hợp và 'gắn' tên biến với vùng nhó đó.
Khi con trỏ trỏ tới một vùng nhớ ví dụ như p trỏ tới luong thì khi truy xuất *p chính là giá trị của vùng nhớ do p trỏ tới tức là *p ⇔ luong. V ới con trỏ trỏ tới một con trỏ khác chẳng hạn như ví dụ sau:
int a = 10;
int *pa;
int **ppa;
pa = &a; // p trỏ tới a
ppa = &pa; // ppa trỏ tới pa thì chúng ta có:
*ppa ⇔ pa ⇔ &a;
**ppa ⇔ *pa ⇔ a;
• Các phép toán trên con trỏ (địa chỉ )
a. Phép so sánh hai con trỏ
Trên con trỏ tồn tại các phép so sánh (= =, !=, <, <=, >,>=) hai con trỏ bằng nhau là hai con trỏ cùng trỏ tới một đối tượng (có giá trị bằng nhau), ngược lại là khác nhau. Con trỏ trỏ tới vùng nhớ có địa chỉ nhỏ hơn là con trỏ nhỏ hơn.
b. Phép cộng con trỏ với số nguyên
Giả sử p là con trỏ kiểu T, k là số nguyên thì 0 cũng là con trỏ kiểu T, không mất tổng quát giả sử p trỏ tới phần tử t thì
- p+1 là con trỏ trỏ tới một phần tử kiểu T kế tiếp sau t à p+2 trỏ tới một phần tử kiểu T kế tiếp sau t 2 phần tử,...
- p -1 là con trỏ trỏ tới một phần tử kiểu T kế tiếp trước t
- p -2 trỏ tới một phần tử kiểu T kế tiếp trước t hai phần tử,...
- tổng quát p+k trỏ tới phần tử cách t một khoảng k phần tử kiểu T (nếu k >0 dịch về phía địa chỉ lớn, k<0 thì dịch về phía địa chỉ nhỏ).
Ví dụ:
int a; // giả sử a có địa chỉ 150
int *p;
p = &a;
thì p+1 là con trỏ kiểu nguyên và p+1 trỏ tới địa chỉ 152; p + k trỏ tới 150 +2*k. c. Phép trừ hai con trỏ N ếu p, q là hai con trỏ cùng kiểu T thì p-q là số nguyên là số các phần tử kiểu T nằm giữa hai phần tử do p và q trỏ tới.
Ví dụ:
int *p, *q;
giả sử p trỏ tới phần tử có địa chỉ 180, q trỏ tới phần tử có địa chỉ 160 thì
0 = = 10;
float *r1, *r2;
giả sử r1 trỏ tới phần tử có địa chỉ 120, r2 trỏ tới phần tử có địa chỉ 100 thì
0 = = 5;