Tải bản đầy đủ (.pdf) (5 trang)

Báo cáo khoa học: "một số ứng dụng của con trỏ trong c và C++" pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (121.18 KB, 5 trang )

một số ứng dụng của con trỏ
trong c v C++



PGS. TS. Phạm văn ất
Khoa Công nghệ thông tin - Trờng ĐH GTVT
Tóm tắt: Bi báo ny trình bầy một số ứng dụng của con trỏ trong các vấn đề quan trọng
v lý thú sau đây của C/C++:
+ Xây dựng hm với số đối bất định
+ Xây dựng toán tử gán cho lớp dẫn xuất
+ Sử dụng hiệu quả các vùng nhớ
Summary: In this paper, we will present some applications of the pointer in the important
and interesting problems of C/C++ such as:
+ Creating functions with variable argument lists.
+ Creating assignment operator for derived classes.
+ Using the memories efficiently
1. Hm với đối số bất định
Nh đã biết, trong các giáo trình C/C++
thờng chỉ hớng dẫn cách xây dựng hàm với
một số cố định các đối. Mỗi đối cần có một
tham số (cùng kiểu với nó) trong lời gọi hàm.
Tuy nhiên một vài hàm chuẩn của C lại không
nh vậy, mà linh hoạt hơn, chẳng hạn khi
dùng hàm printf hay scanf thì số tham số mà
ta cung cấp cho hàm là không cố định cả về
số lợng lẫn kiểu cách. Ví dụ trong câu lệnh:
printf(\n Tổng = %d , 3+4+5);
có 2 tham số, nhng trong câu lệnh:
printf(\n Hà Nội);
chỉ có một tham số.


Nh vậy cần phân biệt các khái niệm
sau:
Đối số cố định đợc khai báo trong dòng
đầu của hàm, nó có tên và kiểu.
Tham số ứng với đối số cố định gọi là
tham số cố định.
Các đối bất định đợc khai báo bởi ba
dấu chấm: bất định cả về số lợng và kiểu.
Các tham số bất định (ứng với các đối bất
định) là một danh sách giá trị với số lợng và
kiểu tuỳ ý (không xác định).
Trong các mục 2 - 5 dới đây sẽ trình bầy
cách xây dựng các hàm với đối số bất định.
Công cụ chủ yếu đợc dùng là con trỏ và
danh sách.
ii. Biến con trỏ
Biến con trỏ (hay con trỏ) dùng để chứa
địa chỉ của biến, mảng, hàm, Có nhiều kiểu
địa chỉ, vì vậy cũng có nhiều kiểu con trỏ. Biến
con trỏ đợc khai báo theo mẫu:
Kiểu *Tên_biến_con_trỏ ;
Ví dụ:
float *px ; /* px là con trỏ thực */
Các phép toán quan trọng trên con trỏ

gồm:
+ Gán địa chỉ một vùng nhớ cho con
trỏ (dùng toán tử gán, phép lấy địa chỉ, các
hàm cấp phát bộ nhớ).
+ Truy nhập vào vùng nhớ mà địa chỉ

của nó chứa trong con trỏ, dùng phép toán:
*Tên_con_trỏ
(Để ý ở đây có 2 vùng nhớ: Vùng nhớ của
biến con trỏ và vùng nhớ mà địa chỉ đầu của
nó chứa trong biến con trỏ).
+ Cộng địa chỉ để con trỏ chứa địa chỉ
của phần tử tiếp theo, dùng phép toán:
++ Tên_con_trỏ hoặc Tên_con_trỏ ++
Chú ý rằng các phép toán trên chỉ có thể
thực hiện đối với con trỏ có kiểu.
iii. Danh sách không cùng kiểu
Dùng con trỏ có kiểu chỉ quản lý đợc
danh sách các giá trị cùng kiểu, ví dụ dẫy số
thực, dẫy số nguyên, dẫy các cấu trúc,
Khi cần quản lý một danh sách các giá trị
không cùng kiểu ta phải dùng con trỏ không
kiểu (con trỏ void) khai báo nh sau:
void * Tên_con_trỏ ;
Con trỏ void có thể chứa các địa chỉ có
kiểu bất kỳ, và dùng để trỏ đến vùng nhớ chứa
danh sách cần quản lý. Một chú ý quan trọng
là mỗi khi gửi vào hay lấy ra một giá trị từ
vùng nhớ, thì tuỳ theo kiểu giá trị mà ta phải
dùng phép chuyển kiểu thích hợp đối với con
trỏ. Ví dụ sau minh hoạ cách lập một danh
sách gồm một số nguyên, một số thực và một
chuỗi ký tự. Chúng ta cần một bộ nhớ để chứa
số nguyên, số thực và địa chỉ chuỗi và dùng
các con trỏ void để quản lý vùng nhớ này.
void *list , *p ; /* Con trỏ list trỏ tới đầu

danh sách */
/* p dùng để duyệt qua các phần tử của
danh sách */
list = malloc(sizeof(int) + sizeof(float) +
+ sizef(char*) );
plist;
*(int*)p) = 12; /* Đa số nguyên 12 vào
danh sách */
((int*)p)++ ; /* Chuyển sang phần tử
tiếp theo */
*((float*)p) = 3.14; /* Đa số thực 3.14
vào danh sách */
((float*)p)++ ; /* Chuyển sang phần
tử tiếp theo */
*((char**)p) = HA NOI; /* Đa địa chỉ
chuỗi HA NOI vào danh sách */
/* Nhận các phần tử của danh sách */
p=list; /* Về đầu danh sách */
int a = *((int*)p); /* Nhận phần tử thứ
nhất */
((int*)p)++ ; /* Chuyển sang phần tử
tiếp theo */
float x= *((float*)p); /* Nhận phần tử thứ
hai */
((float*)p)++ ; /* Chuyển sang phần tử
tiếp theo */
char *str = *((char**)p) ; /* Nhận phần
tử thứ ba */

iv. Hm với đối số bất định

+ Các đối bất định bao giờ cũng đặt sau
cùng và đợc khai báo bằng 3 dấu chấm. Ví
dụ hàm:
void f(int n, char *s, ) ;
có 2 đối cố định là n, s và các đối bất định.
+ Để nhận đợc các tham số bất định
trong lời gọi hàm ta cần lu ý các điểm sau:
- Các tham số bất định chứa trong một
danh sách. Để nhận đợc địa chỉ đầu danh
sách ta dùng một con trỏ void và phép gán
sau:
void *list ;

list = ;
- Dùng một tham số cố định kiểu chuỗi
để quy định số lợng và kiểu của mỗi tham số
bất định trong danh sách, ví dụ:
3i hiểu là: danh sách gồm 3 tham số
kiểu int.
5f hiểu là: danh sách gồm 5 tham số
kiểu float.
fissif hiểu là: danh sách gồm 6 tham số
có kiểu lần lợt là float, int, char*, char*, int và
float.
Một khi đã biết đợc địa chỉ đầu danh
sách, biết đợc số lợng và kiểu của mỗi tham
số, thì dễ dàng nhận đợc giá trị các tham số
để sử dụng trong thân hàm.
Ví dụ sau đây minh hoạ cách xây dựng
và sử dụng các hàm với tham số bất định.

Hàm dùng để in các giá trị kiểu int, float và
char. Hàm có một tham số cố định để cho biết
có bao nhiêu giá trị và kiểu các giá trị cần in.
Kiểu quy định nh sau: i là int, f là float, s là
char*. Tham số này có 2 cách viết: lặp (gồm
một hằng số nguyên và một chữ cái định kiểu)
và liệt kê (một dẫy các chữ cái định kiểu). Ví
dụ:
4s có nghĩa in 4 chuỗi.
siif có nghĩa in một chuỗi, 2 giá trị
nguyên và một giá trị thực.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <conio.h>
#include <stdlib.h>
#include <stdarg.h>
void InDanhSachGiaTri(char *st, )
{
void *list ;
int gt_int ;
float gt_float;
char *gt_str;
int n,i ;
char kieu;
int lap;
list = ; /* list trỏ tới vùng nhớ chứa
danh sách địa chỉ các tham số */
lap = isdigit(st[0]) ;
if (lap)

n=st[0] - '0' ;
else
n=strlen(st);
for(i=0;i<n;++i)
{
if(lap)
kieu=st[1];
else
kieu = st[i];
switch(kieu)
{
case 'i' :
gt_int = *((int*)list);
((int*)list)++ ;
printf("\nGia tri %d =
%d",i,gt_int);
break;
case 'f' :
gt_float = (float)
(*((double*)list));
((double*)list)++ ;
printf("\nGia tri %d =
%0.2f",i,gt_float);
break;
case 's' :
gt_str = *((char**)list) ;
((char**)list)++ ;
printf("\nGia tri %d =
%s",i,gt_str);
}

}

}
void main()
{
float x=3.14;
int a=123, b=456, c=789;
char *tp="NHA TRANG";
InDanhSachGiaTri("3i",a,b,c);
InDanhSachGiaTri("2s","HANOI","NHA
TRANG");
InDanhSachGiaTri("ifsssffii",a,x,tp,tp,"
QUY NHON",x,6.28,a,246);
InDanhSachGiaTri("2f",6.28,x);
getch();
}
v. Hm không đối v hm với đối bất
định
Nhiều ngời nghĩ hàm khai báo nh sau:
void f();
là hàm không đối trong C. Trong C++ thì hiểu
nh thế là đúng, còn trong C thì đó là hàm có
đối bất định (hàm không đối trong C khai báo
nh sau: f(void) ). Do không có đối cố định
nào cho biết về số lợng và kiểu của các tham
số bất định, nên giải pháp ở đây là dùng các
biến toàn bộ. Rõ ràng giải pháp này không
thuận tiện cho ngời dùng vì phải khai báo
đúng tên biến toàn bộ và phải khởi gán giá trị
cho nó trớc khi gọi hàm. Ví dụ sau trình bầy

một hàm chỉ có đối bất định dùng để tính max
và min của các giá trị thực. Các tham số bất
định đợc đa vào theo trình tự sau: Địa chỉ
chứa max, địa chỉ chứa min, các giá trị thực
cần tính max, min. Chơng trình dùng biến
toàn bộ N để cho biết số giá trị thực cần tính
max, min.
int N;
void maxmin()
{
void *lt = ;
float *max, *min , tg;
int i;
max = *((float**)lt)++;
min = *((float**)lt)++;
*max = *min = (float) *((double*)lt)++;

for(i=1;i<N;++i)
{
tg= (float) *((double*)lt)++;
if(tg > *max) *max = tg;
if(tg < *min) *min = tg;
}
}
Sử dụng hm: Để tính max và min của
các giá trị thực x, y, z ta dùng các câu lệnh:
float smax, smin ; /* Dùng để chứa các
giá trị max và min */
N = 3; /* Số giá trị cần tính
max, min là 3 */

maxmin(&smax, &smin, x, y, z) ; /* Lời
gọi hàm */
vi. cách xây dựng toán tử gán
trong lớp dẫn xuất
Trớc hết cần xây dựng toán tử gán cho
lớp cơ sở (gọi là lớp A), sau đó để xây dựng
toán tử gán cho lớp dẫn xuất (lớp B), có thể
tiến hành theo 2 bớc:
Bớc 1: Sử dụng phép gán của lớp cơ sở
A để thực hiện việc gán trên các thuộc tính
thừa kế. Muốn vậy cần sử dụng con trỏ this
của lớp B và ép kiểu theo A để nhận đợc một
đối tợng kiểu A. Điều này đợc thực hiện
theo mẫu:
A *p ;
p = (A*)this ;
*p = A::operator=(b) ;
ở đây b (có kiểu B) là đối của toán tử gán của
lớp B.

Nhận xét: Có thể thay 3 câu lệnh trên
bằng một câu lệnh sau:
*(A*)this = A::operator=(b) ;
Bớc 2: Thực hiện phép gán trên các
thuộc tính của lớp dẫn xuất.
Sau đây là ví dụ minh hoạ:
class A
{
private:
char *strA;

public:
const A & operator = (const A & a)
{
if(this->strA!=NULL)
delete this->strA;
this->strA= strdup(a.strA);
return a;
}
} ;
class B : public A
{
private:
char *strB;
public:
const B & operator = (const
B & b)
{
// Gán các thuộc tính thừa
kế từ A
*(A*)this = A::operator=(b) ;
// Gán các thuộc tính của B
if(this->strB!=NULL)
delete this->strB;
this->strB = strdup( b.strB);
return b;
}
} ;
vii. Truy nhập linh hoạt tới các
vùng nhớ
Để truy nhập tới một vùng nhớ có độ lớn

tuỳ ý (giả sử 1000 byte), đầu tiên cần định
nghĩa một kiểu con trỏ 1000 byte theo mẫu:
typedef struct
{
char M[1000] ;
} *MEM ;
Sau đó dùng kiểu MEM để truy nhập tới
các vùng nhớ 1000 byte. Ví dụ sau minh hoạ
cách gán 2 hàng của ma trận chứa trong
mảng 2 chiều:
typedef struct
{
float M[100] ;
} *MEM ;
float a[100][100] , tg[100];
*(MEM)tg = *(MEM)(a+i) ; /* Gán
hàng i vào tg */
*(MEM)(a+i) = *(MEM)(a+j) ; /* Gán
hàng j vào hàng i */
*(MEM)(a+j) = *(MEM)tg ; /* Gán tg
vào hàng j */
viii. Kết luận
Con trỏ trong C/C++ là một công cụ
mạnh mẽ và linh hoạt. Để nâng cao kỹ thuật
lập trình C/C++ cần biết cách sử dụng con trỏ,
các ví dụ trên đã minh hoạ điều này.

Tài liệu tham khảo
[1]. Peter Norton. Advanced C Programming. Brady
Publishing, 1992.

[2]. Phạm Văn ất. Kỹ thuật lập trình C cơ sở và nâng
cao. NXB Khoa học và Kỹ thuật, Hà Nội, 1999.
[3]. Phạm Văn ất. C++ và lập trình hớng đối
tợng. NXB Khoa học và Kỹ thuật, Hà Nội, 2000Ă


×