SỬ DỤNG HÀM TRONG C
CHƯƠNG 6 KIỂU DỮ LIỆU CON TRỎ
6.2 CÁC PHÉP TOÁN TRÊN CON TRỎ
Các giá trị có thể được gán cho biến con trỏ thông qua toán tử &. Câu
lệnh gán sẽ là:
p = &a;
Lúc này địa chỉ của a sẽ được lưu trong biến trỏ p. Cũng có thể gán giá trị cho con trỏ thông qua một biến con trỏ khác trỏ đến một phần tử dữ liệu có cùng kiểu, ví dụ:
p1 = &a;
p2 = p1;
Thực tế, khi một con trỏ được khai báo, nó thường nhận một giá trị ngẫu nhiên. Như vậy mặc dù con trỏ chưa được gán giá trị nhưng có thể điều kiện kiểm
tra khác NULL vẫn thỏa mãn và có thể gây ra các lỗi bộ nhớ (run time error) trong quá trình thực hiện chương trình. Để tránh điều đó nên gán giá trị NULL cho mỗi con trỏ ở thời điểm khai báo, ví dụ:
int *p = NULL;
Giá trị NULL cũng có thể được gán đến một con trỏ bằng
số 0 như sau:
p = 0;
Các biến cũng có thể được gán giá trị thông qua con trỏ của chúng. Ví dụ:
102
int a;
int *p = &a;
*p = 10;
Khi đó a có giá trị bằng 10
Nói chung, các biểu thức có chứa con trỏ cũng theo cùng qui luật như các biểu thức khác trong
C. Điều quan trọng cần chú ý phải gán giá trị cho biến con trỏ trước khi sử dụng chúng; nếu không chúng có thể trỏ đến một giá trị không xác định nào đó.
6.2.2 Phép toán số học trên con trỏ
Các phép toán số học trên con trỏ chỉ bao gồm các phép cộng, trừ. Cụ thể hơn, ta giả sử p là một con trỏ trỏ đến một biến kiểu nguyên a:
int a, *p;
p = &a;
Khi đó các phép toán số học có thể thực hiện trên p là phép tăng/giảm, cộng/trừ con trỏ với một số nguyên, tăng/giảm giá trị của biến được lưu trữ ở ô nhớ đang được trỏ bởi p (xem cụ thể trên bảng Bảng 6. 1).
Phép toán Ý nghĩa
++p hoặc p++ Trỏ đến số nguyên được lưu trữ kế tiếp sau a --p hoặc p -- Trỏ đến số nguyên được lưu trữ liền trước a p + i Trỏ đến số nguyên được lưu trữ i vị trí trước a p - i Trỏ đến số nguyên được lưu trữ i vị trí sau a ++*p hoặc (*p)++ Tăng giá trị của a lên 1
*p++ Tác động đến giá trị của số nguyên được lưu trữ kế tiếp sau a Bảng 6. 1: Một số phép toán số học trên con trỏ.
Mỗi khi một con trỏ được tăng giá trị, nó sẽ trỏ đến ô nhớ của phần tử kế tiếp.
Mỗi khi con trỏ được giảm giá trị, nó sẽ trỏ đến vị trí của phần tử đứng trước nó.
Với những con trỏ trỏ tới các ký tự, nó xuất hiện bình thường, bởi vì mỗi ký tự chiếm 1 byte. Tuy nhiên, tất cả những con trỏ khác sẽ tăng hoặc giảm trị tuỳ thuộc vào độ dài kiểu dữ liệu mà chúng trỏ tới. Ví dụ sau đây sẽ minh họa điều này.
Ví dụ 6.4:
#includ e<stdio .h> int main(){
printf("\ncharacter data type:\n"); charc='A', *p1
= &c;
printf("c = %c, &c =
%d\n", c, &c);
printf ("++p1 = %d\n", + +p1);
printf ("--p1 = %d\n", --p1);
printf("--- ---\n"); printf("\ninterger data type:\n");
inta=50, *p2=&a;
printf("a = %d, &a =
%d\n",a, &a);
printf ("++p2 = %d\n", + +p2);
printf ("--p2 = %d\n", --p2);
printf("--- ---\n"); printf("\nfloat data type:\n");
floatf=10.0, *p3=&f;
103
printf("f = %f, &f =
%d\n",f, &f);
printf ("++p3 = %d\n", + +p3);
printf ("--p3 = %d\n", --p3);
printf("--- ---\n"); printf("\ndouble data type:\n");
doubled=150.0, *p4=&d;
printf("d = %lf, &d =
%d\n",d, &d); printf ("+
+p4 = %d\n", ++p4);
printf ("--p4 = %d\n", --p4);
printf("---\n");
}
Kết quả thực hiện chương trình như sau:
character data type:
c = A, &c
= 1245027
++p =
1245028
--p = 1245027
--- ---
interger data type:
a = 50, &a
= 1245000
++p =
1245004
--p = 1245000
--- ---
float data type:
f = 10.000000, &f
= 1244976 ++p = 1244980
--p = 1244976
--- ---
double data type:
d = 150.000000, &d
= 1244948 ++p = 1244956s
--p = 1244948
Chương trình trên kiểm tra kết quả của các phép toán tăng, giảm đối với các
con trỏ của
các kiểu dữ liệu khác nhau. Trong đó, khai báo 4 con trỏ p1, p2, p3, p4 tương ứng
trỏ tới các
biến c (kiểu char), a (kiểu int), f (kiểu float), d (kiểu double). Mỗi lần tăng/giảm giá
trị, p1 chỉ
dịch chuyển 1 ô nhớ (kiểu char chiếm 1 byte), p2 dịch chuyển 4 ô nhớ (đối với hệ điều hành
32 bit, kiểu int chiếm 4 byte), p3 dịch chuyển 4 ô nhớ (kiểu float chiếm 4 byte) và p4 dịch chuyển 8 ô nhớ (kiểu double chiếm 8 byte).
Ngoài các toán tử tăng/giảm, các số nguyên cũng có thể được cộng vào và trừ ra với con trỏ. Ngoài phép cộng và trừ một con trỏ với một số nguyên, không có một phép toán nào khác có thể thực hiện được trên các con trỏ. Nói rõ hơn, các con trỏ không thể được nhân hoặc chia. Cũng như kiểu float và double không thể được cộng hoặc trừ với con trỏ.
6.2.3 Phép toán so sánh con trỏ
Trong C, hai con trỏ có cùng kiểu dữ liệu có thể được so sánh với nhau trong một biểu thức quan hệ. Giả sử ptr_a và ptr_b là hai biến con trỏ trỏ đến các phần tử dữ liệu a và b. Trong trường hợp này, các phép so sánh sau đây là có thể thực hiện:
104
ptr_a < ptr_b Trả về giá trị true nếu a được lưu trữ ở vị trí trước b ptr_a > ptr_b Trả về giá trị true nếu a được lưu trữ ở vị trí sau b
ptr_a <= ptr_b Trả về giá trị true nếu a được lưu trữ ở vị trí trước b hoặc ptr_a và ptr_b trỏ đến cùng một vị trí
ptr_a >= ptr_b Trả về giá trị true nếu a được lưu trữ ở vị trí sau b hoặc ptr_a và ptr_b trỏ đến cùng một vị trí
ptr_a == ptr_b Trả về giá trị true nếu cả hai con trỏ ptr_a và ptr_b trỏ đến cùng một phần tử dữ liệu.
ptr_a != ptr_b Trả về giá trị true nếu cả hai con trỏ ptr_a và ptr_b trỏ đến các phần tử dữ liệu khác nhau nhưng có cùng kiểu dữ liệu.
ptr_a == NULL Trả về giá trị true nếu ptr_a được gán giá trị NULL (0) Tương tự, nếu ptr_begin và ptr_end trỏ đến các phần tử của cùng
một mảng thì
ptr_end - ptr_begin
sẽ trả về số byte cách biệt giữ hai vị trí mà chúng trỏ đến.
6.2.4 Con trỏ không kiểu (void) và phép ép kiểu con trỏ
Con trỏ con trỏ không kiểu (con trỏ void ) là con trỏ có thể trỏ đến bất kỳ kiểu dữ liệu nào. Xem xét ví dụ sau:
Ví dụ 6.4:
#includ e<stdio .h> int main(){
int a=10;
f l o
a t f
= 5 0
; v o i d
* p
;
printf("with assignment p
= &a:\n");
p = &a;
printf(" a=%d, &a=%d, p=%d\n", a, &a, p);
printf("--- ---\n");
printf("with assignment p =
&f:\n");
p = &f;
printf(" f=%0.2f, &f=%d, p=%d\n", f, &f, p);
}
Trong đoạn chương trình trên, p được khai báo là một con trỏ void, a là một biến kiểu int, f là biến kiểu float. Với lệnh gán p = &a, p sẽ trỏ tới biến nguyên a.
Với lệnh gán p = &f, p sẽ trỏ đến biến thực f.
Kết quả thực hiện của chương trình như sau:
with assignment p = &a:
a=10, &a=1245024, p=1245024 ---
---
with assignment p
= &f
f=50.00, &a=1245012, p=1245012
C cho phép ép một con trỏ kiểu void thành một con trỏ có kiểu dữ liệu bất kỳ.
Điều này sẽ được minh họa một cách cụ thể hơn trong ví dụ sau.
Ví dụ 6.5:
#include <stdio.h>
105
int main(){
i n t a
= 5 0
; v o i d
* p
; int *ip = 0;
printf("Before assigning and casting:\n");
p = &a;
printf(" &a = %d, p = %d, ip = %d\n",
&a, p, ip); printf("\nAfter assigning and casting:\n");
ip = (int*) p;
printf("&a = %d, p = %d, ip = %d\n",
&a, p, ip);
printf(" a = %d, *ip = %d\n", a, *ip);
}
Trong đoạn chương trình trên, p là một con trỏ kiểu void, ip là một con trỏ kiểu int. Để gán con trỏ kiểu void p cho con trỏ kiểu int ip cần phải ép p về kiểu (int *) trước khi gán.
6.2.5 Con trỏ đa cấp
Con trỏ đa cấp là con trỏ trỏ tới địa chỉ của một con trỏ khác. Người ta sử dụng
con trỏ đa
cấp để lấy địa chỉ của con trỏ ở cấp thấp hơn.Ví dụ một con trỏ cấp 2 có thể trỏ
tới địa chỉ của
con trỏ cấp 1.
Cú pháp tổng quát của khai báo con trỏ đa cấp như sau:
<Kiểu dữ liệu> (Số lượng dấu * ) Tên biến trỏ ; Ví dụ: Một con trỏ p cấp 2 có kiểu dữ liệu thực, được khai báo như sau: float **p;
Chương trình sau đây sẽ sử dụng một con trỏ q (cấp 2) để lấy địa chỉ của
một con trỏ p
(cấp 1).
Ví dụ 6.6:
#include
<stdio.h
> int main(){
int **q;
int *p;
int a = 100;
printf("with assingment p
= &a:\n");
p = &a;
printf("&a=%d, p=%d,&p=%d,a=%d, *p=
%d\n",&a,p,&p,a,*p); printf("\nwith assingment q = &p:\n");
q = &p;
printf("&p=%d, q=%d, &q=%d, p=%d, *q=%d\n",&p,q,&q,p,*q);
}
Kết quả thực hiện của chương trình trên như sau:
with assingment p = &a:
&a=1245000,
p=1245000,&p=1245012,a=100, *p=100 with assingment q = &p:
&p=1245012, q=1245012, &q=1245024, p=1245000, *q=1245000 Lệnh gán q = &p sẽ cho con trỏ q trỏ đến địa chỉ ô nhớ lưu trữ con cho p. Do vậy, phép toán *q sẽ lấy giá trị của ô nhớ đang trỏ bởi biến q có nghĩa là *q = p.
106