Giáo trình C++ - Các lớp

Một điều mới trong đoạn mã này là toán tử phạm vi :: được dùng trong khai báo set_values(). Nó được sử dụng để khai báo ở bên ngoài các thành viên của một lớp. Chú ý rằng chúng ta đã định nghĩa đầy đủ hàm area() ngay bên trong lớp trong khi hàm set_values() mới chỉ được khai báo mẫu còn định nghĩa của nó nằm ở ngoài lớp. Trong phần khai báo ở ngoài này chúng ta bắt buộc phải dùng toán tử ::.

Sự khác biệt duy nhất giữa việc khai báo đầy đủ một hàm bên trong lớp và việc chỉ khai báo mẫu là trong trường hợp thứ nhất hàm sẽ được tự động coi là inline bởi trình dịch, còn trong trường hợp thứ hai nó sẽ là một hàm thành viên bình thường.

Lý do khiến chúng ta khai báo x và y là các thành viên private vì chúng ta đã định nghĩa một hàm để thâótc với chúng (set_values()) và không có lý do gì để truy nhập trực tiếp đến các biến này. Có lẽ trong ví dụ rất đơn giản này bạn không thấy được một tiện ích lớn khi bảo vệ hai biến này nhưng trong các dự án lớn hơn nó có thể là rất quan trọng khi đảm bảo được rằng các giá trị đó không bị thay đổi một cách không mong muốn.

 

docx8 trang | Chuyên mục: C/C++ | Chia sẻ: tuando | Lượt xem: 696 | Lượt tải: 0download
Tóm tắt nội dung Giáo trình C++ - Các lớp, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
ất từ các thành viên của cùng một lớp. Bối rối? Đây là ví dụ đầy đủ về lớp CRectangle:
// classes example
#include 
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area (void) {return (x*y);}
};
void CRectangle::set_values (int a, int b) {
x = a;
y = b;
}
int main () {
CRectangle rect;
rect.set_values (3,4);
cout << "area: " << rect.area();
}
area: 12
Một điều mới trong đoạn mã này là toán tử phạm vi :: được dùng trong khai báo set_values(). Nó được sử dụng để khai báo ở bên ngoài các thành viên của một lớp. Chú ý rằng chúng ta đã định nghĩa đầy đủ hàm area() ngay bên trong lớp trong khi hàm set_values() mới chỉ được khai báo mẫu còn định nghĩa của nó nằm ở ngoài lớp. Trong phần khai báo ở ngoài này chúng ta bắt buộc phải dùng toán tử ::.
Sự khác biệt duy nhất giữa việc khai báo đầy đủ một hàm bên trong lớp và việc chỉ khai báo mẫu là trong trường hợp thứ nhất hàm sẽ được tự động coi là inline bởi trình dịch, còn trong trường hợp thứ hai nó sẽ là một hàm thành viên bình thường. 
Lý do khiến chúng ta khai báo x và y là các thành viên private vì chúng ta đã định nghĩa một hàm để thâótc với chúng (set_values()) và không có lý do gì để truy nhập trực tiếp đến các biến này. Có lẽ trong ví dụ rất đơn giản này bạn không thấy được một tiện ích lớn khi bảo vệ hai biến này nhưng trong các dự án lớn hơn nó có thể là rất quan trọng khi đảm bảo được rằng các giá trị đó không bị thay đổi một cách không mong muốn.  
Một ích lợi nữa của lớp là chúng ta có thể khai báo một vài đối tượng khác nhau từ nó. Ví dụ, tiếp sau đây là ví dụ trước về lớp CRectangle, tôi chỉ thêm phần khai báo thêm đối tượng rectb.
// class example
#include 
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area (void) {return (x*y);}
};
void CRectangle::set_values (int a, int b) {
x = a;
y = b;
}
int main () {
CRectangle rect, rectb;
rect.set_values (3,4);
rectb.set_values (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 30
Chú ý rằng lời gọi đến rect.area() không cho cùng kết quả với rectb.area() vì mỗi đối tượng của lớp CRectangle có các biến và các hàm của riêng nó  
Trên đây là những khái niệm cơ bản về đối tượng và lập trình hướng đối tượng. Trong đối tượng các dữ liệu và các hàm là các thuộc tính thay vì trước đây đối tượng là các tham số của hàm trong lập trình cấu trúc. Trong bài này các phần tiếp sau chúng ta sẽ nói đến những lợi ích của phương thức này. 
Constructors và destructors
Nói chung các đối tượng cần phải khởi tạo các biến hoặc cấp phát bộ nhớ động trong quá trình tạo ra chúng để có thể hoạt động tốt và tránh được việc trả về các giá trị không mong muốn. Ví dụ, điều gì sẽ xảy ra nếu chúng ta gọi hàm area() trước khi gọi hàm set_values?Có lẽ kết quả sẽ là một giá trị không xác định vì các thành viên x và y chưa được gán một giá trị cụ thể nào. 
Để tránh điều này, một lớp cần có một hàm đặc biệt: một constructor, hàm này có thể được khai báo bằng cách đặt tên trùng với tên của lớp. Nó sẽ được gọi tự động khi một khai báo một đối tượng mới hoặc cấp phát một đối tượng có kiểu là lớp đó. Chúng ta thêm một constructor vào lớp CRectangle:
// classes example
#include 
class CRectangle {
int width, height;
public:
CRectangle (int,int);
int area (void) {return (width*height);}
};
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle rect (3,4);
CRectangle rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 30
Như bạn có thể thấy, kết quả của ví dụ này giống với ví dụ trước. Trong trường hợp này chúng ta chỉ thay thế hàm  set_values bằng một hàm constructor. Hãy chú ý cách mà các tham số được truyền cho constructor khi một đối tượng được tạo ra: 
CRectangle rect (3,4);
CRectangle rectb (5,6);
Bạn có thể thấy rằng constructor không có giá trị  trả về, ngay cả kiểu void cũng không. Điều này luôn luôn phải như vậy.  
Destructor làm các chức năng ngược lại. Nó sẽ được tự động gọi khi một đối tượng được giải phóng khỏi bộ nhớ hay phạm vi tồn tại của nó đã kết thúc (ví dụ như nếu nó được định nghĩa là một đối tượng cục bộ bên trong một hàm và khi hàm đó kết thúc thì phạm vi tồn tại của nó cũng hết) hoặc nó là một đối tượng đối tượng được cấp phát động và sẽ giải phóng bởi toán tử delete. 
Destructor phải có cùng tên với tên lớp với dấu (~) ở đằng trước và nó không được trả về giá trị nào. 
Destructor đặc biệt phù hợp khi mà một đối tượng cấp phát bộ nhớ động trong quá trình tồn tại của nó và trong thời điểm bị huỷ bỏ chúng ta muốn giải phóng bộ nhớ mà nó sử dụng.
// example on constructors and destructors
#include 
class CRectangle {
int *width, *height;
public:
CRectangle (int,int);
~CRectangle ();
int area (void) {return (*width * *height);}
};
CRectangle::CRectangle (int a, int b) {
width = new int;
height = new int;
*width = a;
*height = b;
}
CRectangle::~CRectangle () {
delete width;
delete height;
}
int main () {
CRectangle rect (3,4), rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
rect area: 12
rectb area: 30
Quá tải các Constructors
Như bất kì hàm nào khác, một constructor có thể được quá tải bởi một vài hàm có cùng tên nhưng khác kiểu hay khác số tham số. Nhớ rằng ở một thời điểm trình dịch chỉ thực hiện một hàm phù hợp (xem phần 2.3, Hàm-II). Do vậy chỉ một hàm constructor phù hợp được gọi vào thời điểm một đối tượng lớp được khai báo. 
Trong thực tế, khi khai báo một lớp mà chúng ta không chỉ định một hàm constructor nào thì trình dịch sẽ tự động tạo ra hai constructor quá tải ("constructor mặc định" và "copy constructor"). Ví dụ, đối với lớp: 
class CExample {
public:
int a,b,c;
void multiply (int n, int m) { a=n; b=m; c=a*b; };
};
trình dịch sẽ tự động cho rằng lớp có hai constructor sau: 
Constructor rỗng
Đây là một constructor không có tham số. Nó chẳng làm gì cả. 
CExample::CExample () { }; 
Copy constructor
Đây là một constructor có một tham số cùng kiểu với lớp. Nó thực hiện một việc là gán tất cả các biến thành viên không tĩnh (nonstatic) của lớp giá trị của biến tương ứng của đối tượng tham số. 
CExample::CExample (const CExample& rv) {
a=rv.a; b=rv.b; c=rv.c;
}
Cần phải nhấn mạnh rằng các constructor mặc định này chỉ tồn tại nếu không có constructor được khai báo.
Tất nhiên là bạn có thể quá tải constructor của nó để cung cấp các constructor khác nhau cho các mục đích khác nhau:
// overloading class constructors
#include 
class CRectangle {
int width, height;
public:
CRectangle ();
CRectangle (int,int);
int area (void) {return (width*height);}
};
CRectangle::CRectangle () {
width = 5;
height = 5;
}
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle rect (3,4);
CRectangle rectb;
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
}
rect area: 12
rectb area: 25
Trong trường hợp này rectb được khai báo không dùng tham số, vì vậy nó được khởi tạo với constructor không có tham số, constructor này đặt width và height bằng 5.
Chú ý rằng nếu chúng ta khai báo một đối tượng mới và không muốn truyền tham số cho nó thì không cần phải cặp ngoặc đơn (): 
CRectangle rectb; // đúng
CRectangle rectb(); // sai!
Con trỏ tới lớp
Tạo con trỏ trỏ tới các lớp là hoàn toàn hợp lệ, để có thể làm việc này chúng ta hiểu rằng một khi đã được khai báo, lớp trở thành một kiểu dữ liệu hợp lệ, vì vậy chúng ta có dùng tên lớp là kiểu cho con trỏ. Ví dụ: 
CRectangle * prect; 
là một con trỏ trỏ tới một đối tượng của lớp CRectangle.
Tương tự với cấu trúc, để tham chiếu trực tiếp tới một thành viên của một đối tượng được trỏ bởi một con trỏ bạn nên dùng toán tử ->. Đây là một ví dụ:
// pointer to classes example
#include 
class CRectangle {
int width, height;
public:
void set_values (int, int);
int area (void) {return (width * height);}
};
void CRectangle::set_values (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle a, *b, *c;
CRectangle * d = new CRectangle[2];
b= new CRectangle;
c= &a;
a.set_values (1,2);
b->set_values (3,4);
d->set_values (5,6);
d[1].set_values (7,8);
cout << "a area: " << a.area() << endl;
cout area() << endl;
cout area() << endl;
cout << "d[0] area: " << d[0].area() << endl;
cout << "d[1] area: " << d[1].area() << endl;
return 0;
}
a area: 2
*b area: 12
*c area: 2
d[0] area: 30
d[1] area: 56
Dưới đây là là bảng tóm tắt về các chức năng của các toán tử lớp (*, &, ., ->, [ ]) và các con trỏ lớp xuất hiện trong ví dụ trên: 
*x có thể được đọc là: trỏ bởi x
&x có thể được đọc là: địa chỉ của x
x.y có thể được đọc là: thành viên y của đối tượng x
(*x).y có thể được đọc là: thàn viên y của đối tượng được trỏ bởi x
x->y có thể được đọc là: thành viên y của đối tượng được trỏ bởi x(tương đương với dòng trên)
x[0] có thể được đọc là: đối tượng đầu tiên được trỏ bởi x
x[1] có thể được đọc là: đối tượng thứ hai được trỏ bởi x
x[n] có thể được đọc là: đối tượng thứ (n+1)được trỏ bởi x
Hãy chắc chắn rằng bạn hiểu tất cả các điều này trước khi đi tiếp. Nếu có nghi ngờ gì, hãy đọc lại các bài "3.3, Con trỏ" and "3.5, Các cấu trúc".
Các lớp được định nghĩa bằng từ khoá struct
Ngôn ngữ C++ đã mở rộng từ khoá struct của C làm cho nó cũng có các chức năng như từ khoá class ngoại trừ một điều là các thành viên của nó mặc định là public thay vì private.
Tuy nhiên, mặc dù cả class và struct gần như là tương đương trong C++, struct thường chỉ dùng các cấu trúc dữ liệu còn class thì dùng cho các lớp có cả các thủ tục và hàm thành viên.

File đính kèm:

  • docxgiao_trinh_c_cac_lop.docx