Ngôn ngữ lập trình C - Chương 7: Con trỏ

Con trỏ là biến chứa địa chỉ của một biến khác. Con trỏ được sử dụng rất nhiều

trong C, một phần là do chúng đôi khi là cách duy nhất để biểu diễn tính toán, và

phần nữa do chúng thường làm cho chương trình ngắn gọn và có hiệu quả hơn các

cách khác .

Con trỏ đã từng bị coi như có hại chẳng kém gì lệnh goto do cách sử dụng

chúng đã tạo ra các chương trình khó hiểu. Điều này chắc chắn là đúng khi người ta

sử dụng chúng một cách lôn xộn và do đó tạo ra các con trỏ trỏ đến đâu đó không biết

trước được.

pdf19 trang | Chuyên mục: C/C++ | Chia sẻ: dkS00TYs | Lượt xem: 2114 | Lượt tải: 0download
Tóm tắt nội dung Ngôn ngữ lập trình C - Chương 7: Con trỏ, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
a[1][0] a[1][1] a[1][2] 
Địa chỉ 1 2 3 4 5 6 
Tên mảng a biểu thị địa chỉ đầu tiên của mảng. Phép cộng địa chỉ ở đây được thực 
hiện như sau : 
 C coi mảng hai chiều là mảng ( một chiều ) của mảng, như vậy khai báo 
 float a[2][3]; 
thì a là mảng mà mỗi phần tử của nó là một dãy 3 số thực ( một hàng của mảng ). 
Vì vậy : 
 a trỏ phần tử thứ nhất của mảng : phần tử a[0][0] 
 a+1 trỏ phần tử đầu hàng thứ hai của mảng : phần tử a[1][0] 
Chương 7: Con trỏ 
83 
 ........ 
7.3.3. Con trỏ và mảng hai chiều : 
 Để lần lượt duyệt trên các phần tử của mảng hai chiều ta có thể dùng con trỏ 
như minh hoạ ở ví dụ sau : 
float *pa,a[2][3]; 
pa=(float*)a; 
lúc đó : 
pa trỏ tới a[0][0] 
pa+1 trỏ tới a[0][1] 
pa+2 trỏ tới a[0][2] 
pa+3 trỏ tới a[1][0] 
pa+4 trỏ tới a[1][1] 
pa+5 trỏ tới a[1][2] 
Ví dụ : 
 Dùng con trỏ để vào số liệu cho mảng hai chiều. 
Cách 1 : 
#include "stdio.h" 
main() 
 { 
 float a[2][3],*pa; 
 int i; 
 pa=(float*)a; 
 for (i=0;i<6;++i) 
 scanf("%f",pa+i); 
 } 
Cách 2 : 
#include "stdio.h" 
Chương 7: Con trỏ 
84 
main() 
 { 
 float a[2][3],*pa; 
 int i; 
 for (i=0;i<6;++i) 
 scanf("%f",(float*)a+i); 
 } 
7.4. Kiểu con trỏ, kiểu địa chỉ, các phép toán trên con trỏ : 
7.4.1. Kiểu con trỏ và kiểu địa chỉ : 
 Con trỏ dùng để lưu địa chỉ. Mỗi kiểu địa chỉ cần có kiểu con trỏ tương ứng. 
Phép gán địa chỉ cho con trỏ chỉ có thể thực hiện được khi kiểu địa chỉ phù hợp với 
kiểu con trỏ. 
Ví dụ theo khai báo : 
 float a[20][30],*pa,(*pm)[30]; 
thì : 
pa là con trỏ float 
pm là con trỏ kiểu float [30] 
a là địa chỉ kiểu float [30] 
Vì thế phép gán : 
 pa=a; 
là không hợp lệ. Nhưng phép gán : 
 pm=a; 
7.4.2. Các phép toán trên con trỏ: 
 Có 4 phép toán liên quan đến con trỏ và đại chỉ là : 
 Phép gán. 
 Phép tăng giảm địa chỉ. 
 Phép truy cập bộ nhớ. 
 Phép so sánh. 
Chương 7: Con trỏ 
85 
Phép gán : 
 Phép gán chỉ thực hiện với các con trỏ cùng kiểu. Muốn gán các con trỏ khác 
kiểu phải dùng phép ép kiểu như ví dụ sau : 
 int x; 
 char *pc; 
 pc=(char*)(&x); 
Phép tăng giảm địa chỉ : 
 Để minh hoạ chi tiết cho phép toán này, ta xét ví dụ sau : 
Các câu lệnh : 
 float x[30],*px; 
 px=&x[10]; 
cho con trỏ px là con trỏ float trỏ tới phần tử x[10]. Kiểu địa chỉ float là kiểu địa chỉ 4 
byte, nên các phép tăng giảm địa chỉ được thực hiện trên 4 byte. Vì thế : 
 px+i trỏ tới phần tử x[10+i] 
 px-i trỏ tới phần tử x[10-i] 
Xét ví dụ khác : 
Giả sử ta khai báo : 
 float b[40][50]; 
Khai báo trên cho ta một mảng b gồm các dòng 50 phần tử thực. Kiểu địa chỉ của b là 
50*4=200 byte. 
Do vậy : 
 b trỏ tới đầu dòng thứ nhất ( phần tử b[0][0]). 
 b+1 trỏ tới đầu dòng thứ hai ( phần tử b[1][0]). 
 .......... 
 b+i trỏ tới đầu dòng thứ i ( phần tử b[i][0]). 
Phép truy cập bộ nhớ : 
 Con trỏ float truy nhập tới 4 byte, con trỏ int truy nhập 2 byte, con trỏ char 
truy nhập 1 byte. Giả sử ta có cá khai báo : 
Chương 7: Con trỏ 
86 
 float *pf; 
 int *pi; 
 char *pc; 
Khi đó : 
 Nếu trỏ pi trỏ đến byte thứ 100 thì *pf biểu thị vùng nhớ 4 byte liên tiếp từ 
byte 100 đến 103. 
 Nếu trỏ pi trỏ đến byte thứ 100 thì *pi biểu thị vùng nhớ 2 byte liên tiếp từ 
byte 100 đến 101. 
 Nếu trỏ pc trỏ đến byte thứ 100 thì *pc biểu thị vùng nhớ 1 byte chính là byte 
100. 
Phép so sánh : 
 Cho phép so sánh các con trỏ cùng kiểu, ví dụ nếu p1 và p2 là các con trỏ cùng 
kiểu thì nếu : 
 p1<p2 nếu địa chỉ p1 trỏ tới thấp hơn địa chỉ p2 trỏ tới. 
 p1=p2 nếu địa chỉ p1 trỏ tới cũng là địa chỉ p2 trỏ tới. 
 p1>p2 nếu địa chỉ p1 trỏ tới cao hơn địa chỉ p2 trỏ tới. 
Ví dụ : 
Ví dụ 1 : 
 Đoạn chương trình tính tổng các số thực dùng phép so sánh con trỏ : 
 float a[100],*p,*pcuoi,tong=0.0; 
 int n; 
 pcuoi=a+n-1; /* Địa chỉ cuối dãy*/ 
 for (p=a;p<=pcuoi;++p) 
 s+=*p; 
Ví dụ 2 : 
Chương 7: Con trỏ 
87 
 Dùng con trỏ char để tách các byte của một biến nguyên, ta làm như sau : 
 Giả sử ta có lệnh : 
 unsigned int n=0xABCD; /* Số nguyên hệ 16*/ 
 char *pc; 
 pc=(char*)(&n); 
Khi đó : 
 *pc=0xAB (byte thứ nhất của n) 
 *pc+1=0xCD (byte thứ hai của n) 
7.4.3. Con trỏ kiểu void : 
 Con trỏ kiểu void được khai báo như sau : 
 void *tên_con_trỏ; 
 Đây là con trỏ đặc biệt, con trỏ không kiểu, nó có thể nhận bất kỳ kiểu nào. 
Chẳng hạn câu lệnh sau là hợp lệ : 
 void *pa; 
 float a[20][30]; 
 pa=a; 
 Con trỏ void thường dùng làm đối để nhận bất kỳ địa chỉ kiểu nào từ tham số 
thực. Trong thân hàm phải dùng phép chuyển đổi kiểu để chuyển sang dạng địa chỉ 
cần sử lý. 
Chú ý : 
 Các phép toán tăng giảm địa chỉ, so sánh và truy cập bộ nhớ không dùng được 
trên con trỏ void. 
Ví dụ : 
 Viết hàm thực hiện công ma trận : 
 void congmt(void *a,void *b,void *c,int N,int N, int m); 
Chương 7: Con trỏ 
88 
 { 
 float *pa,*pb,*pc; 
 int i,j; 
 pa=(float*)a; 
 pb=(float*)b; 
 pc=(float*)c; 
 for (i=1;i<m;++i) 
 for (j=1;j<m;++j) 
 *(pc+i*N+j)=*(pa+i*N+j)+*(pb+i*N+j); 
 } 
 Vì đối là con trỏ void nên nó có thể nhận được địa chỉ của các ma trận trong 
lời gọi hàm. Tuy nhiên ta không thể sử dụng trực tiếp các đối con trỏ void trong thân 
hàm mà phải chuyển kiểu của chúng sang thành float. 
7.5. Mảng con trỏ : 
 Mảng con trỏ là sự mở rộng khái niệm con trỏ. Mảng con trỏ là một mảng mà 
mỗi phần tử của nó chứa được một địa chỉ nào đó. Cũng giống như con trỏ, mảng con 
trỏ có nhiều kiểu : Mỗi phần tử của mảng con trỏ kiểu int sẽ chứa được các địa chỉ 
kiểu int. Tương tự cho các mảng con trỏ của các kiểu khác. 
 Mảng con trỏ được khai báo theo mẫu : 
 Kiểu *Tên_mảng_con_trỏ[N]; 
 Trong đó Kiểu có thể là int, float, double, char ... còn Tên_mảng_con_trỏ là 
tên của mảng, N là một hằng số nguyên xác định độ lớn của mảng. 
 Khi gặp khai báo trên, máy sẽ cấp phát N khoảng nhớ liên tiếp cho N phần tử 
của mảng Tên_mảng_con_trỏ. 
Ví dụ : 
 Lệnh : 
 double *pa[100]; 
Khai báo một mảng con trỏ kiểu double gồm 100 phần tử. Mỗi phần tử pa[i] có thể 
dùng để lưu trữ một địa chỉ kiểu double. 
Chương 7: Con trỏ 
89 
Chú ý : 
 Bản thân các mảng con trỏ không dùng để lưu trữ số liệu. Tuy nhiên mảng con 
trỏ cho phép sử dụng các mảng khác để lưu trữ số liệu một cách có hiệu quả hơn theo 
cách : chia mảng thành các phần và ghi nhớ địa chỉ đầu của mỗi phần vào một phần 
tử của mảng con trỏ. 
 Trước khi sử dụng một mảng con trỏ ta cần gán cho mỗi phần tử của nó một 
giá trị. Giá trị này phải là giá trị của một biến hoặc một phần tử mảng. Các phần tử 
của mảng con trỏ kiểu char có thể được khởi đầu bằng các xâu ký tự. 
Ví dụ : 
 Xét một tổ lao động có 10 người, mã của mỗi người chính là số thứ tự. Ta lập 
một hàm để khi biết mã số của nhân viên thì xác định được họ tên của nhân viên đó. 
#include "stdio.h" 
#include "ctype.h" 
void tim(int code); 
main() 
 { 
 int i; 
 tt:printf("\n Tim nguoi co so TT la :"); 
 scanf("%d",&i); 
 tim(i); 
 printf("Co tiep tuc nua khong C/K : '); 
 if (tupper(getch())='C') 
 goto tt; 
 } 
void tim(int code); 
 { 
 static char *list[]= { 
 "Khong co so thu tu nay " 
 " Nguyen Van Toan" 
Chương 7: Con trỏ 
90 
 "Huynh Tuan Nghia" 
 "Le Hong Son" 
 "Tran Quang Tung" 
 "Chu Thanh Tu" 
 "Mac Thi Nga" 
 "Hoang Hung" 
 "Pham Trong Ha" 
 "Vu Trung Duc" 
 "Mai Trong Quat" 
 }; 
 printf("\n\n Ma so : %d",code); 
 printf(": %s",()); 
 } 
7.6. Con trỏ tới hàm : 
7.6.1. Cách khai báo con trỏ hàm và mảng con trỏ hàm : 
 Ta sẽ trình bày quy tắc khai báo thông qua các ví dụ : 
Ví dụ 1: 
 Câu lệnh : 
 float (*f)(float),(*mf[50])(int); 
Để khai báo : 
 f là con trỏ hàm kiểu float có đối là float 
 mf là mảng con trỏ hàm kiểu float có đối kiểu int ( có 50 phần tử ) 
Ví dụ 2: 
 Câu lệnh : 
 double (*g)(int, double),(*mg[30])(double, float); 
Để khai báo : 
Chương 7: Con trỏ 
91 
 g là con trỏ hàm kiểu double có các đối kiểu int và double 
 mg là mảng con trỏ hàm kiểu double có các đối kiểu double và float ( có 30 
phần tử ) 
7.6.2. Tác dụng của con trỏ hàm : 
 Con trỏ hàm dùng để chứa địa chỉ của hàm. Muốn vậy ta thực hiện phép gán 
tên hàm cho con trỏ hàm. Để phép gán có ý nghĩa thì kiểu hàm và kiểu con trỏ phải 
tương thích. Sau phép gán, ta có thể dùng tên con trỏ hàm thay cho tên hàm. 
Ví dụ 1: 
#include "stdio.h" 
double fmax(double x, double y ) /* Tính max x,y */ 
{ 
 return(x>y ? x:y); 
} 
double (*pf)(double,double)=fmax; /*Khai báo và gán tên hàm cho con trỏ hàm */ 
main() /* Sử dụng con trỏ hàm*/ 
 { 
 printf("\n max=%f",pf(5.0,9.6)); 
 } 
Ví dụ 2: 
#include "stdio.h" 
double fmax(double x, double y ) /* Tính max x,y */ 
{ 
 return(x>y ? x:y); 
} 
double (*pf)(double,double); /* Khai báo con trỏ hàm*/ 
main() /* Sử dụng con trỏ hàm*/ 
 { 
Chương 7: Con trỏ 
92 
 pf=fmax; 
 printf("\n max=%f",pf(5.0,9.6)); 
 } 
7.6.3. Đối của con trỏ hàm : 
C cho phép thiết kế các hàm mà tham số thực trong lời gọi tới nó lại là tên của 
một hàm khác. Khi đó tham số hình thức tương ứng phải là một con trỏ hàm. 
Cách dùng con trỏ hàm trong thân hàm : 
Nếu đối được khai báo : 
 double (*f)(double, int); 
thì trong thân hàm ta có thể dùng các cách viết sau để xác định giá trị của hàm ( do 
con trỏ f trỏ tới ) : 
 f(x,m) hoặc (f)(x,m) hoặc (*f)(x,m) 
ở đây x là biến kiểu double còn m là biến kiểu int. 
Ví dụ : 
Dùng mảng con trỏ để lập bảng giá trị cho các hàm : x*x, sin(x), cos(x), 
exp(x) và sqrt(x). Biến x chay từ 1.0 đến 10.0 theo bước 0.5 
#include "stdio.h" 
#include "math.h" 
double bp(double x) /* Hàm tính x*x */ 
 { 
 return x*x; 
 } 
main() 
 { 
Chương 7: Con trỏ 
93 
 int i,j; 
 double x=1.0; 
 typedef double (*ham)(double); 
 ham f[6]; /* Khai bao mảng con trỏ hàm*/ 
 /* Có thể khai báo như sau double (*f[6](double)*/ 
 f[1]=bp; f[2]=sin; f[3]=cos; f[4]=exp; f[5]=sqrt; 
 /* Gán tên hàm cho các phần tử mẩng con trỏ hàm */ 
 while (x<=10.0) /* Lập bảng giá trị */ 
 { 
 printf("\n"); 
 for (j=1;j<=5;++j) 
 printf("%10.2f ",f[j](x)); 
 x+=0.5; 
 } 
 } 

File đính kèm:

  • pdfCHUONG_7.pdf