Lập trình C++ - Chương 8: Tái định nghĩa

Chương này thảo luận vềtái định nghĩa hàmvà toán tửtrong C++. Thuật ngữ

tái định nghĩa (overloading) nghĩa là ‘cung cấp nhiều định nghĩa’. Tái định

nghĩa hàmliên quan đến việc định nghĩa các hàm riêng biệt chia sẻcùng tên,

mỗi hàmcó một dấu hiệu duy nhất. Tái định nghĩa hàmthích hợp cho:

• Định nghĩa các hàmvềbản chất là làmcùng công việc nhưng thao tác

trên các kiểu dữliệu khác nhau.

•Cung cấp các giao diện tới cùng hàm.

Tái định nghĩa hàm(function overloading) là một tiện lợi trong lập trình.

Giống nhưcác hàm,các toán tửnhận các toán hạng (các đối số) và trảvề

một giá trị. Phần lớn các toán tửC++ có sẵn đã được tái định nghĩa rồi. Ví dụ,

toán tử+ cóthể được sửdụng đểcộng hai sốnguyên, hai sốthực, hoặc hai

địa chỉ. Vì thế, nó có nhiều định nghĩa khác nhau. Các định nghĩaxây dựng

sẵn cho các toán tử được giới hạn trênnhững kiểu có sẵn. Các định nghĩa

thêmvào có thể được cung cấp bởi các lập trình viên sao cho chúng cũng có

thểthao tác trêncác kiểu người dùng định nghĩa. Mỗi định nghĩa thêm vào

được cài đặt bởi một hàm.

pdf24 trang | Chuyên mục: C/C++ | Chia sẻ: dkS00TYs | Lượt xem: 2131 | Lượt tải: 1download
Tóm tắt nội dung Lập trình C++ - Chương 8: Tái định nghĩa, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
m.elems; 
} 
 Điều này giống y hệt như trong việc khởi tạo ngầm định và được gọi là 
gán ngầm định. Nó cũng có cùng vấn đề như trong khởi tạo ngầm định và có 
thể khắc phục bằng cách tái định nghĩa toán tử = một cách rõ ràng. Ví dụ đối 
với lớp Matrix thì việc tái định nghĩa toán tử = sau đây là thích hợp: 
Matrix& Matrix::operator = (const Matrix &m) 
{ 
 if (rows == m.rows && cols == m.cols) { // phải khớp 
 int n = rows * cols; 
 for (register i = 0; i < n; ++i) // sao chép các phần tử 
 elems[i] = m.elems[i]; 
 } 
 return *this; 
} 
 Thông thường, đối với bất kỳ lớp X đã cho thì toán tử = được tái định 
nghĩa bằng thành viên sau của X: 
X& X::operator = (X&) 
Chương 8: Tái định nghĩa 140 
Toán tử = chỉ có thể được tái định nghĩa như là thành viên và không thể được 
định nghĩa toàn cục. 
8.10.Tái định nghĩa new và delete 
Các đối tượng khác nhau thường có kích thước và tần số sử dụng khác nhau. 
Kết quả là chúng có những yêu cầu bộ nhớ khác nhau. Cụ thể các đối tượng 
nhỏ không được điều khiển một cách hiệu quả bởi các phiên bản mặc định 
của toán tử new và delete. Mọi khối được cấp phát bởi toán tử new giữ một vài 
phí được dùng cho mục đích quản lý. Đối với các đối tượng lớn thì điều này 
không đáng kể nhưng đối với các đối tượng nhỏ thì phí này có thể lớn hơn 
chính các khối. Hơn nữa, có quá nhiều khối nhỏ có thể làm chậm chạp dữ dội 
cho các cấp phát và thu hồi theo sau. Hiệu suất của chương trình bằng cách 
tạo ra nhiều khối nhỏ tự động có thể được cải thiện đáng kể bởi việc sử dụng 
một chiến lược quản lý bộ nhớ đơn giản hơn cho các đối tượng này. 
 Các toán tử quản lý lưu trữ động new và delete có thể được tái định nghĩa 
cho một lớp bằng cách viết chồng lên định nghĩa toàn cục của các toán tử này 
khi được sử dụng cho các đối tượng của lớp đó. 
 Ví dụ giả sử chúng ta muốn tái định nghĩa toán tử new và delete cho lớp 
Point sao cho các đối tượng Point được cấp phát từ một mảng: 
#include 
#include 
const int maxPoints = 512; 
class Point { 
 public: 
 //... 
 void* operator new (size_t bytes); 
 void operator delete (void *ptr, size_t bytes); 
 private: 
 int xVal, yVal; 
 static union Block { 
 int xy[2]; 
 Block *next; 
 } *blocks; // tro toi cac luu tru ranh 
 static Block *freeList; // ds ranh cua cac khoi da lien ket 
 static int used; // cac khoi duoc dung 
}; 
Tên kiểu size_t được định nghĩa trong stddef.h.. Toán tử new sẽ luôn trả về 
void*. Tham số của new là kích thước của khối được cấp phát (tính theo byte). 
Đối số tương ứng luôn được truyền một cách tự động tới trình biên dịch. 
Tham số đầu của toán tử delete là khối được xóa. Tham số hai (tùy chọn) là 
Chương 8: Tái định nghĩa 141 
kích thước khối đã cấp phát. Các đối số được truyền một cách tự động tới 
trình biên dịch. 
 Vì các khối, freeList và used là tĩnh nên chúng không ảnh hưởng đến kích 
thước của đối tượng Point. Những khối này được khởi tạo như sau: 
Point::Block *Point::blocks = new Block[maxPoints]; 
Point::Block *Point::freeList = 0; 
int Point::used = 0; 
 Toán tử new nhận khối có sẵn kế tiếp từ blocks và trả về địa chỉ của nó. 
Toán tử delete giải phóng một khối bằng cách chèn nó trước danh sách liên 
kết được biểu diễn bởi freeList. Khi used đạt tới maxPoints, new trả về 0 khi danh 
sách liên kết là rỗng, ngược lại new trả về khối đầu tiên trong danh sách liên 
kết. 
void* Point::operator new (size_t bytes) 
{ 
 Block *res = freeList; 
 return used < maxPoints 
 ? &(blocks[used++]) 
 : (res == 0 ? 0 
 : (freeList = freeList->next, res)); 
} 
void Point::operator delete (void *ptr, size_t bytes) 
{ 
 ((Block*) ptr)->next = freeList; 
 freeList = (Block*) ptr; 
} 
 Point::operator new và Point::operator delete được triệu gọi chỉ cho các đối 
tượng Point. Lời gọi new với bất kỳ đối số kiểu khác sẽ triệu gọi định nghĩa 
toàn cục của new, thậm chí nếu lời gọi xảy ra bên trong một hàm thành viên 
của Point. Ví dụ: 
Point *pt = new Point(1,1); // gọi Point::operator new 
char *str = new char[10]; // gọi ::operator new 
delete pt; // gọi Point::operator delete 
delete str; // gọi ::operator delete 
Khi new và delete được tái định nghĩa cho một lớp, new và delete toàn cục cũng 
có thể được sử dụng khi tạo và hủy mảng các đối tượng: 
Point *points = new Point[5]; // gọi ::operator new 
//... 
delete [] points; // gọi ::operator delete 
 Toán tử new được triệu gọi trước khi đối tượng được xây dựng trong khi 
toán tử delete được gọi sau khi đối tượng đã được hủy. 
Chương 8: Tái định nghĩa 142 
8.11.Tái định nghĩa ++ và -- 
Các toán tử tăng và giảm một cũng có thể được tái định nghĩa theo cả hai hình 
thức tiền tố và hậu tố. Để phân biệt giữa hai hình thức này thì phiên bản hậu 
tố được đặc tả để nhận một đối số nguyên phụ. Ví dụ, các phiên bản tiền tố và 
hậu tố của toán tử ++ có thể được tái định nghĩa cho lớp Binary như sau: 
class Binary { 
 //... 
 friend Binary operator ++ (Binary&); // tien to 
 friend Binary operator ++ (Binary&, int); // hau to 
}; 
 Mặc dù chúng ta phải chọn định nghĩa các phiên bản này như là các hàm 
bạn toàn cục nhưng chúng cũng có thể được định nghĩa như là các hàm thành 
viên. Cả hai được định nghĩa dễ dàng theo thuật ngữ của toán tử + đã được 
định nghĩa trước đó: 
Binary operator ++ (Binary &n) // tien to 
{ 
 return n = n + Binary(1); 
} 
Binary operator ++ (Binary &n, int) // hau to 
{ 
 Binary m = n; 
 n = n + Binary(1); 
 return m; 
} 
Chú ý rằng chúng ta đơn giản đã phớt lờ tham số phụ của phiên bản hậu tố. 
Khi toán tử này được sử dụng thì trình biên dịch tự động cung cấp một đối số 
mặc định cho nó. 
 Đoạn mã sau thực hiện cả hai phiên bản của toán tử: 
Binary n1 = "01011"; 
Binary n2 = "11010"; 
cout << ++n1 << '\n'; 
cout << n2++ << '\n'; 
cout << n2 << '\n'; 
Nó sẽ cho kết quả sau: 
0000000000001100 
0000000000011010 
0000000000011011 
 Các phiên bản tiền tố và hậu số của toán tử -- có thể được tái định nghĩa 
theo cùng cách này. 
Chương 8: Tái định nghĩa 143 
Bài tập cuối chương 8 
8.1 Viết các phiên bản tái định nghĩa của hàm Max để so sánh hai số nguyên, hai 
số thực, hoặc hai chuỗi, và trả về thành phần lớn hơn. 
8.2 Tái định nghĩa hai toán tử sau cho lớp Set: 
• Toán tử - cho hiệu của các tập hợp (ví dụ, s - t cho một tập hợp gồm các 
phần tử thuộc s mà không thuộc t). 
• Toán tử <= kiểm tra một tập hợp có chứa trong một tập hợp khác hay 
không (ví dụ, s <= t là true nếu tất cả các phần tử thuộc s cũng thuộc t). 
8.3 Tái định nghĩa hai toán tử sau đây cho lớp Binary: 
• Toán tử - cho hiệu của hai giá trị nhị phân. Để đơn giản, giả sử rằng toán 
hạng đầu tiên luôn lớn hơn toán hạng thứ hai. 
• Toán tử [] lấy chỉ số một bit thông qua vị trí của nó và trả về giá trị của 
nó như là một số nguyên 0 hoặc 1. 
8.4 Các ma trận thưa được sử dụng trong một số phương thức số (ví dụ, phân tích 
phần tử có hạn). Một ma trận thưa là một ma trận có đại đa số các phần tử của 
nó là 0. Trong thực tế, các ma trận thưa có kích thước lên đến 500 × 500 là 
bình thường. Trên một máy sử dụng biểu diễn 64 bit cho các số thực, lưu trữ 
một ma trận như thế như một mảng sẽ yêu cầu 2 megabytes lưu trữ. Một biểu 
diễn kinh tế hơn sẽ chỉ cần ghi nhận các phần tử khác 0 cùng với các vị trí của 
chúng trong ma trận. Định nghĩa một lớp SparseMatrix sử dụng một danh sách 
liên kết để ghi nhận chỉ các phần tử khác 0, và tái định nghĩa các toán tử +, -, 
và * cho nó. Cũng định nghĩa một hàm xây dựng khởi tạo ngầm định và một 
toán tử khởi tạo ngầm định cho lớp. 
8.5 Hoàn tất việc cài đặt của lớp String. Chú ý rằng hai phiên bản của hàm xây 
dựng ngầm định và toán tử = ngầm định được đòi hỏi, một cho khởi tạo hoặc 
gán tới một chuỗi bằng cách sử dụng char*, và một cho khởi tạo hoặc gán 
ngầm định. Toán tử [] nên chỉ mục một ký tự chuỗi bằng cách sử dụng vị trí 
của nó. Toán tử + cho phép nối hai chuỗi vào nhau. 
class String { 
 public: 
 String (const char*); 
 String (const String&); 
 String (const short); 
 ~String (void); 
 String& operator =(const char*); 
 String& operator =(const String&); 
 char& operator [](const short); 
 int Length(void) {return(len);} 
 friend String operator +(const String&, const String&); 
 friend ostream& operator << (ostream&, String&); 
Chương 8: Tái định nghĩa 144 
 private: 
 char *chars; // cac ky tu chuoi 
 short len; // chieu dai cua chuoi 
}; 
8.6 Một véctơ bit là một véctơ với các phần tử nhị phân, nghĩa là mỗi phần tử có 
giá trị hoặc là 0 hoặc là 1. Các véctơ bit nhỏ được biểu diễn thuận tiện bằng 
các số nguyên không dấu. Ví dụ, một unsigned char có thể bằng một véctơ bit 8 
phần tử. Các véctơ bit lớn hơn có thể được định nghĩa như mảng của các 
véctơ bit nhỏ hơn. Hoàn tất sự thi công của lớp Bitvec, như được định nghĩa 
bên dưới. Nên cho phép các véctơ bit của bất kỳ kích thước được tạo ra và 
được thao tác bằng cách sử dụng các toán tử kết hợp. 
enum Bool {false, true}; 
typedef unsigned char uchar; 
class BitVec { 
 public: 
 BitVec (const short dim); 
 BitVec (const char* bits); 
 BitVec (const BitVec&); 
 ~BitVec (void){ delete vec; } 
 BitVec& operator = (const BitVec&); 
 BitVec& operator &= (const BitVec&); 
 BitVec& operator |= (const BitVec&); 
 BitVec& operator ^ = (const BitVec&); 
 BitVec& operator <<= (const short); 
 BitVec& operator >>= (const short); 
 int operator [] (const short idx); 
 void Set (const short idx); 
 void Reset (const short idx); 
 BitVec operator ~ (void); 
 BitVec operator & (const BitVec&); 
 BitVec operator | (const BitVec&); 
 BitVec operator ^ (const BitVec&); 
 BitVec operator << (const short n); 
 BitVec operator >> (const short n); 
 Bool operator == (const BitVec&); 
 Bool operator != (const BitVec&); 
 friend ostream& operator << (ostream&, BitVec&); 
 private: 
 uchar *vec; 
 short bytes; 
}; 
Chương 8: Tái định nghĩa 145 

File đính kèm:

  • pdfC++Chuong_08.pdf