Lập trình hướng đối tượng
Trang 1Mục lục
Mục lục 1
lời nói đầu 5
chơng 1: Mở đầu 6
Bài 1 So sánh giữa lập trình hớng đối tợng và lập trình hớng thủ tục 6
1 Lập trình hớng thủ tục 6
2 Lập trình hớng đối tợng 7
Bài 2 Giới thiệu ngôn ngữ C++ 9
1 Chú thích trong C và trong C++ 9
2 Các từ khóa mới 9
3 Phép chuyển kiểu bắt buộc 10
4 Khai báo biến 10
5 Hàm main 11
6 Truyền tham số cho hàm 11
7 Nhắc lại về biến con trỏ 15
8 Sử dụng tham số truyền ngầm định trong hàm .17 9 Cấp phát và giải phóng bộ nhớ 19
10 Các hàm tải bội (Overloaded function) 20
11 Hàm inline 22
12 Khả năng vào ra mới của C++ 26
Bài 3 Một số khái niệm cơ bản của lập trình h-ớng đối tợng 30
1 Khái niệm đối tợng (Object) 30
2 Lớp đối tợng (Class) 30
3 Trừu tợng hoá dữ liệu và bao gói thông tin (Abstraction and Encapsulation) 30
4 Sự kế thừa (Inheritance) 31
Trang 2chơng 2: Lớp các đối tợng 37
Bài 1 lớp các đối tợng 37
1 Khai báo lớp các đối tợng 37
2 Các thành phần trong lớp (Data member and function member) 41
3 Đối tợng trong tham số của hàm 52
Bài 2 Constructor và destructor 56
Bài 3 Toán tử tải bội và toán tử tải bội thân thiện 60
1 Khái quát chung 60
2 Toán tử tải bội 60
3 Bài tập 1 65
4 Toán tử tải bội thân thiện 66
5 Bài tập 2 75
6 Chuyển đổi kiểu 76
Bài 4 Vài vấn đề về sử dụng từ khoá const 84 1 Sử dụng biến kiểu const 84
2 Truyền tham số kiểu const cho hàm 85
3 Đối tợng hằng của lớp 87
4 Hàm thành phần const (hàm thành phần có khai báo const phía sau) 89
5 Con trỏ this kiểu const (hàm thành phần có khai báo const phía trớc) 92
6 Lớp các dãy bit 94
7 Nghiên cứu lớp lớp String 98
chơng 3: Sự kế thừa - inheritance 105
Bài 1 Sự tơng ứng trong kế thừa 105
1 Khái niệm chung 105
Trang 32 Kế thừa đơn 106
3 Thành phần protected trong kế thừa 108
4 Kế thừa đa mức 113
5 Kế thừa phân cấp 114
6 Kế thừa bội 114
7 Kế thừa kép (lai ghép) 116
8 Constructor trong các lớp kế thừa 120
Bài 2 Con trỏ xác định thành phần của lớp và con trỏ xác định đối tợng 124
1 Các ký hiệu cú pháp về con trỏ 124
2 Các ví dụ 125
3 Con trỏ xác định đối tợng 127
Bài 3 Hàm ảo và tơng ứng bội trong kế thừa .132
1 Các hàm dịch chuyển (Override function) 132
2 Con trỏ xác định đối tợng trong quan hệ kế thừa 133
3 Con trỏ hàm ảo và tơng ứng bội 135
4 Kế thừa lai ghép và lớp cơ sở ảo 138
5 Hàm rỗng (Null Function) 141
6 Hàm ảo thực sự và lớp trừu tợng (pure function & abstract class) 142
7 Hàm thân thiện ảo và toán tử ảo dịch chuyển 144
chơng 4: lớp mẫu và hàm mẫu 145
Khái quát 145
1 Hàm mẫu 145
2 Lớp mẫu 148
Phụ lục - Giới thiệu Một số bài toán phân tích thiết kế hớng đối tợng trên C++ 154
Trang 41 Sơ lợc 5 bớc thực hiện 154
2 Xây dựng lớp hình học phẳng 155
3 Quản lý các lô đất 165
Tài liệu tham khảo 174
Trang 5lời nói đầu
Trong những năm 1980, ngôn ngữ C đã khẳng định
đợc vị trí quan trọng trong các ngôn ngữ lập trình có cấu trúc bởi tính đa năng của mình Tuy nhiên khi độ phức tạp của bài toán khá lớn thì C không còn đáp ứng đợc đối với các dự án lớn Cần phải thiết kế những phần mềm mang những đợc những đặc tính của thế giới thực bên bên ngoài
Kỹ thuật lập trình xuất phát trên ý tởng này mang tên là là
kỹ thuật lập trình hớng đối tợng (Object Oriented Programing viết tắt là OOP) và trên kỹ thuật này nhiều trình biên dịch đã đợc thiết kế nh Smalltalk, C++
Lập trình hớng đối tợng đợc phát triển từ ngôn ngữ lập trình có cấu trúc nhng thay vì xoay quanh chức năng của nhiệm vụ đã đặt ra, lập trình hớng đối tợng lại đặt trọng tâm vào việc xử lý các dữ liệu để thực hiện các chức năng đó Trong lập trình hớng đối tợng, khái niệm về đối t-ợng (Object) trở thành một khái niệm trọng tâm và hầu nhmọi công việc trong một chơng trình đều đợc tiến hành trên các đối tợng này
Những vấn đề trọng tâm và mới mẻ của lập trình ớng đối tợng với C++ mà chúng ta cần phải tập trung nghiên cứu là:
h-• Sự bao gói (Encapsulation)
• Sự kế thừa (Inheritance)
• Sự tơng ứng bội (Polymorphism)
Trang 6h-* Vậy trọng tâm của lập trình hớng thủ tục là phân rã bài toán thành các hàm chức năng theo kỹ thuật top-down Bài toán đợc giải quyết bởi một số hàm chính Các hàm chính lại có thể phân chia thành các bài toán con - tức
là các hàm chức năng nhỏ hơn nữa Một số hàm có thể gọi thực hiện (dùng chung) một số hàm chức năng nhỏ hơn
Nh vậy, cấu trúc các hàm của một chơng trình lập trình ớng thủ tục là cấu trúc phân cấp các hàm:
h-* Việc chú ý tới các hàm trong chơng trình sẽ không quan tâm nhiều đến dữ liệu Dữ liệu của toàn bộ chơng trình đợc các hàm dùng chung và biến đổi từ hàm này sang hàm khác Bản thân trong các hàm cũng có dữ liệu riêng
main
Hàm4
Trang 7* Vậy đặc trng của lập trình hớng thủ tục là
1 Tập trung vào công việc cần thực hiện - thuật toán
2 Chơng trình đợc chia thành các hàm nhỏ
3 Phần lớn các hàm dùng chung dữ liệu
4 Dữ liệu trong chơng trình chuyển động từ hàm này sang hàm khác
5 Hàm biến đổi dữ liệu từ dạng này sang dạng khác
6 Thiết kế chơng trình theo kỹ thuật top-down
2 Lập trình hớng đối tợng
* Lập trình hớng đối tợng đặt trọng tâm vào đối tợng
và không cho dữ liệu chuyển động trong hệ thống Dữ liệu gắn chặt vào các hàm và thủ tục tạo thành một vùng riêng
và không cho các hàm bên ngoài truy nhập một cách tuỳ tiện
* Lập trình hớng đối tợng phân tích bài toán thành các thực thể gọi là đối tợng, sau đó xây dựng các hàm xung quanh đối tợng đó
* Dữ liệu của đối tợng chỉ có thể truy nhập đợc bởi các hàm của đối tợng Chỉ có một số hàm của đối tợng này
có thể gọi thực hiện các hàm của đối tợng khác biểu thị sự trao đổi thông báo giữa các đối tợng
Đối tượng A Dữ liệu
Trang 8* Nh vậy đặc trng của lập trình hớng đối tợng là:
1 Tập trung vào dữ liệu thay cho hàm
2 Chia chơng trình thành các đối tợng
3 Dữ liệu đợc thiết kế sao cho đặc tả đợc đối tợng
4 Các hàm đợc xây dựng trên các vùng dữ liệu của
đối tợng và đợc gắn liền với nhau trên cấu trúc dữ liệu đó
5 Dữ liệu đợc bao bọc, che dấu không cho các hàm ngoại lai truy nhập
6 Các đối tợng trao đổi thông báo với nhau thông qua các hàm
7 Dữ liệu và các hàm dễ dàng bổ sung vào đối tợng
8 Chơng trình đợc thiết kế theo kỹ thuật botom up
Trang 9Bài 2 Giới thiệu ngôn ngữ C++
Trong bài này: với mục đích so sánh ngôn ngữ C và
C++ sẽ làm rõ một số đặc điểm mạnh của C++ so với C,
đồng thời nhấn mạnh thêm hoặc giải thích chi tiết một số vấn đề quan trọng, thờng gặp khi lập trình với C++.
1 Chú thích trong C và trong C++
Trớc hết C++ đợc phát triển từ C nên nó vẫn sử dụng lại tất cả các cú pháp ngôn ngữ nh C và bổ sung thêm một
số cú pháp mới
C++ đa ra một kiểu chú thích mới để tiện chú thích trên một dòng nếu dùng ký pháp // và cũng tiện để viết trên nhiều dòng liên tục nếu dùng cặp ký pháp /* */
Chú thích trong C Chú thích trong C++
/* dong 1 chu thich cua C */
/* dong 2 chu thich cua C */
x=y=0; /*chu thich sau lenh
*/
/* Bat dau chu thich Dong 2 chu thichKet thuc chu thich */
x=y=10; // chu thich sau lenh
publicprivatecdeclpascaldeletetempled
Trang 103 Phép chuyển kiểu bắt buộc
Ngoài phép chuyển kiểu nh trong ngôn ngữ C, C++ còn đa ra phép chuyển kiểu mới Xét ví dụ sau đây:
int x=0;
long z=(long)x; // chuyen kieu cua C
long z=long(x); // chuyen kieu cua C++
Nh vậy phép chuyển kiểu trong C++ có dạng nh một hàm số chuyển kiểu đang đợc gọi Cách chuyển kiểu này
rõ ràng vừa khoa học vừa dễ dọc
4 Khai báo biến
Nhìn chung phạm vi hoạt động của biến phụ thuộc vào kiểu của biến và vị trí khai báo của biến trong chơng trình
Ngôn ngữ C đòi hỏi phải khai báo biến trớc phạm
vi mà các biến đang sử dụng Cách khai báo đơn giản nhất trong C là khai báo biến toàn cục lên trớc tất cả các hàm và khai báo biến cục bộ ở đầu thân từng hàm
Trong C++, khác với C, cho phép một cách khai báo biến mới, với cách khai báo này, một biến có thể khai báo
ở vị trí bất kỳ miễn sao là trớc khi sử dụng nó, phạm vi hoạt động của biến trong trờng hợp này là trong khối nơi biến đó đợc khai báo
Ví dụ 1: Khai báo sau đây trong C ++ chấp nhận
nh-ng tronh-ng C là lỗi vì C yêu cầu khai báo biến trớc lệnh
clrscr();
int x;
Ví dụ 2:
void simple()
{ int n; // khai bao cuc bo
for(int i=0; i<10; i++) // cho phep khai bao i trong for { int m;
if (i&1) n+=1
Trang 11Hàm main trong C không đợc định nghĩa kiểu.
Hàm main trong C++ đợc định nghĩa kiểu để trả lại một giá trị nguyên cho hệ điều hành DOS, do đó phải khai báo là kiểu int, ngầm định-vì vậy sẽ là int Tuy nhiên ta vẫn có thể định nghĩa kiểu void cho hàm main
Khi chơng trình bắt đầu thực hiện, hàm main đợc gọi bởi một chơng trình khởi động đặc biệt (start up code) có sẵn trong C++
Start up code trong C++ có 4 cách gọi (tùy chọn vào Option\ Application):
Dos standard (file C0.ASM)
Dos overlays (file C0.ASM)
Windows Application (file C0w.ASM)
Windows DLL (file C0d.ASM)
Giá trị trả lại của hàm main sẽ đợc Dos sử dụng để kiểm tra lỗi khi thực hiện chơng trình Giá trị này bằng 0 nếu không có lỗi Các giá trị khác 0 đợc C++ biện luận theo các lỗi khi thực hiện chơng trình
6 Truyền tham số cho hàm
C++ vẫn sử dụng 2 cách truyền tham số cho hàm trong C là truyền theo giá trị và truyền theo con trỏ
Tuy nhiên, C++ có thêm một cách mới để truyền tham số cho hàm là truyền tham số kiểu tham chiếu Cách mới này rất hay sử dụng vì thích hợp truyền tham số cho
Trang 12hàm là địa chỉ của đối tợng - một kiểu dữ liệu mới chỉ có trong C++ (kiểu lớp)
Dới đây ta nhắc lại 2 cách truyền thống và cung cấp cách mới thứ 3
6.1 Truyền tham số kiểu giá trị cho hàm
Theo cách truyền theo giá trị thì chỉ có bản sao của
đối đợc hàm sử dụng Trong thân hàm, các giá trị này có thể bị thay đổi nhng ra khỏi hàm thì giá trị ban đầu của đối vẫn giữ nguyên Đơng nhiên giá trị đầu vào của đối đợc giữ nguyên vì không phải là chính bản thân đối truyền cho hàm
mà là bản sao của đối đợc truyền cho hàm Sự thay đổi các giá trị trong hàm là chỉ thay đổi giá trị của bản sao của nó
void swap(int x,int y)
{int z=x; x=y; y=z;}
6.2 Truyền tham số kiểu con trỏ cho hàm
Khi đối truyền cho hàm kiểu con trỏ thì lúc gọi hàm các con trỏ này sẽ thay thế bằng địa chỉ của các biến để truyền vào hàm hoặc là một con trỏ hoàn toàn xác định nghĩa là đang chứa một địa chỉ của một biến nào đó Nh vậy về bản chất thì bản gốc của đối đợc truyền cho hàm
Kết quả
x = 10 y=5
Trang 13chứ không phải là bản sao của nó Vì vậy, những giá trị của
đối bị thân hàm làm thay đổi sẽ đợc giữ lại khi hàm kết thúc
- Khi khai báo: void swap(int*,int*)
- Khi định nghĩa: void swap(int *x,int *y) { // dùng
*x và *y; }
- Khi sử dụng: swap(&x,&y)
6.3 Truyền tham số kiểu tham chiếu cho hàm
Tác dụng của việc truyền tham số cho hàm theo tham chiếu cũng giống nh việc truyền tham số cho hàm theo con trỏ Bản chất của hai cách truyền tham số theo con trỏ và theo tham chiếu là truyền cho hàm địa chỉ của
đối chứ không phải là bản sao của đối Vậy các giá trị của
đối bị hàm thay đổi cũng sẽ đợc giữ lại khi ra khỏi hàm
Trang 14void swap(int &x,int &y)
{int z=x; nx=y; y=z;}
Rõ ràng việc sử dụng tham chiếu đơn giản hơn so với con trỏ Khi truyền theo tham chiếu thì ta chỉ báo hiệu
địa chỉ của đối ở phần khai báo còn lời gọi hàm và nội dung hàm ta vẫn viết tên đối một cách bình thờng
Hai cách truyền theo con trỏ và truyền theo tham chiếu khác nhau ở chỗ: truyền theo tham chiếu tức là làm việc trực tiếp với địa chỉ của đối truyền vào hàm, còn truyền theo con trỏ thì can thiệp gián tiếp vào địa chỉ của
đối, vì con trỏ trong trờng hợp này dùng để chứa địa chỉ của đối và truyền vào hàm
Điều khác biệt này thể hiện rõ hơn với các chú ý sau
Trang 157 Nhắc lại về biến con trỏ
7.1 So sánh con trỏ và tham chiếu địa chỉ của biến
Chẳng hạn nếu có khai báo:
int *p; và gán *p=10; thì đơng nhiên có thể viết (*p)++; nhng thậm chí có thể viết p++; Trong khi đó ta không thể có lệnh tăng địa chỉ (&x)++ nếu có khai báo int x;
C++ cho phép viết nh vậy và 2 cách viết có tác dụng khác nhau: Lệnh (*p)++ là tăng 1 đơn vị cho biến nguyên xác định bởi con trỏ p Lệnh p++ là tăng địa chỉ của con trỏ
p, tức là con trỏ p sẽ trỏ vào 2 byte nhớ tiếp theo Vậy lệnh p++ chỉ sử dụng khi dùng p để trỏ vào không phải là 1 số nguyên mà là một dãy các số nguyên chứa trong một dãy các biến động (các bytes nhớ) liên tiếp nhau
Biến con trỏ có 2 cách sử dụng trong chơng trình:
- Sử dụng con trỏ để chứa địa chỉ của biến
- Sử dụng con trỏ để xin cấp phát bộ nhớ động
Ví dụ 1 sau đây nói rằng khi có khai báo biến con trỏ int *p; thì việc sử dụng biến *p giống nh việc sử dụng một biến nguyên nào đó một cách bình thờng
7.2 Sử dụng con trỏ để chứa địa chỉ của biến.
Nếu ta có khai báo
Trang 17động, con trỏ p phải đợc giữ nguyên có vai trò lu giữ địa chỉ của danh sách Ta gọi là danh sách liên kết để tạo ra một cảm giác các biến động có liên kết với nhau Về mặt bản chất đó là các ô nhớ liên tục đợc cấp phát động, chứ không có liên kết gì cả Lúc chạy chơng trình, phép tăng
địa chỉ q++ làm cho q chỉ vào 2 bytes tiếp theo nghĩa là truy xuất đến biến động tiếp theo tơng ứng
8 Sử dụng tham số truyền ngầm định trong
hàm
C++ còn mạnh hơn C ở chỗ nó cho phép khởi tạo mặc định cho các tham số truyền cho hàm Ta gọi tắt các tham số có khởi tạo giá trị mặc định là các tham số ngầm
Ví dụ 1:
void f(int i = 100);
void main()
Trang 18Ví dụ 2:
- Khai báo sau đây là sai:
void g(int a = 1, int b, int c = 3, int d = 4); vì các tham số ngầm định phải khai báo cuối danh sách tham số
- Khai báo sau đây là đúng:
void g(int a, int b=2, int c = 3, int d = 4);
- Với khai báo đúng trên, xét các lời gọi hàm sau
đây:
g(); // sai vì a không có giá trị mặc địnhg(10,20,30,40); // đúng vì đầy đủ tham số
g(10,20,,40); // sai vì sử dụng ngắt quãng
g(10); // đúng và nhận các giá trị mặc định của b,c,d là 2,3,4
g(10,20) ; // đúng và nhận các giá trị mặc định của c, d là 3,4
Trong khi khai báo các hàm thành phần của lớp, đặc biệt là constructor (constructor) ngời ta rất hay sử dụng các tham số ngầm định
Trang 199 Cấp phát và giải phóng bộ nhớ
Trớc hết về cú pháp của lệnh cấp phát và giải phóng
bộ nhớ, C++ đã cung cấp thêm 2 lệnh mới ngắn gọn và dễ dùng hơn là new và delete
Xét ví dụ sau đây:
char *s;
char tg[100];
int len;
/* Cấp phát bộ nhớ cho con trỏ s
kiểu char */ /* để có thể chứa
Mặc dù new và malloc đều dùng để chỉ đến địa chỉ
đầu của vùng nhớ xin cấp phát nhng giữa chúng vẫn có
Trang 20{ int* ip = new int[0];
//trả lại giá trị không xác định của con trỏ
int* ip = (int*) malloc (0);
// trả lại giá trị NULL pointer
}
Qua ví dụ trên ta thấy toán tử new trả lại giá trị khác
0 của con trỏ thậm chí ta không đòi hỏi phải cấp phát bất
cứ một bytes nào trong bộ nhớ Giá trị này của con trỏ nếu
đợc sử dụng cho mục đích khác sẽ sinh lỗi trong chơng trình Ngợc lại hàm malloc trong trờng hợp này trả lại giá trị NULL, điều này có nghĩa là con trỏ cha trỏ đến bất cứ vị trí nào trong bộ nhớ
Cũng cần lu ý là trong C++, hàm malloc có kiểu trả lại là void* do đó khi sử dụng phải thực hiện việc chuyển
đổi kiểu tơng ứng với kiểu của đối tợng xin cấp phát bộ nhớ
Ngoài hai khác biệt trên về bản chất cả new và malloc đều dùng để cấp phát bộ nhớ động cho các biến trong quá trình thực hiện chơng trình và con trỏ trả lại theo hai cách này khi thành công đều không có gì khác biệt
Trong lập trình C++, toán tử new đợc a dùng vì nó mềm dẻo do khả năng định nghĩa chồng các toán tử của lớp
10 Các hàm tải bội (Overloaded function)
C++ đa ra một khả năng hoàn toàn mới và rất mạnh
so với C đó là khả năng định nghĩa chồng các hàm, nghĩa
là cho phép định nghĩa các hàm thực hiện các chức năng khác nhau nhng có cùng một tên Một hàm đợc định nghĩa chồng còn gọi là hàm tải bội (dịch từ cụm từ overloading)
Ngoài hàm tải bội, C++ còn cho phép toán tử tải bội
để bình thờng hóa các toán tử quen thuộc đối với một kiểu dữ liệu mới do ngời lập trình định nghĩa
Trang 21Khi các hàm đợc tải bội thì phải tuân theo 2 nguyên tắc sau đây:
+ Các hàm phải khác nhau về số lợng tham số hoặc kiểu dữ liệu của các tham số
+ Kiểu giá trị của hàm không cho phép phân biệt các hàm đợc tải bội nếu các hàm này có danh sách tham số nh nhau
Khi gọi các hàm tải bội, C++ sẽ đối sánh danh sách tham số và kiểu thích hợp với các tham số thực sự để cho thực hiện hàm thích hợp trong số các hàm tải bội
Ví dụ 1: Tải bội hàm abs để tính giá trị tuyệt đối của
một số bất kỳ:
int abs(int i);
long abs(long l);
double abs(double d);
Khi đó, xét lời gọi các hàm sau đây:
abs(-10); // gọi hàm int abs(int i);
abs(-100000); // gọi hàm long abs(long l);
abs(-34.12); // gọi hàm double abs(double d);abs('a'); // gọi hàm int abs(int i);
Ví dụ 2: Tải bội hàm tự định nghĩa display để hiển
thị một giá trị có kiểu đơn giản bất kỳ:
void Display(char *string) {puts(string); }
void Display(long value) { printf("%ld",value); }void Display(double value) { printf("%lf",value); }void main(){ Display("\n Hello Mumy");
Display(123456789);
Display(3.1416);
Display(123); // sai }
Trang 2211 Hàm inline
11.1 Nhắc lại macro trong ngôn ngữ C
- Macro (dịch theo nghĩa thông thờng là vĩ mô) hiểu theo nghĩa tin học là một lệnh riêng lẻ viết bằng một ngôn ngữ lập trình nhng kết quả là một chuỗi lệnh bằng ngôn ngữ máy tính
- Trong ngôn ngữ C cho phép tạo ra các macro để
đúng nh ý nghĩa của nó: cho phép tạo ra một hằng hoặc thậm chí tạo ra một hàm và nói chung tạo ra một tên thay thế (tên macro) để ghép macro này nh một đoạn trình mã máy vào chơng trình nguồn lúc thực hiện
- Trớc hết macro đợc định nghĩa bằng chỉ thị #define (và đợc hủy bỏ bằng #undef) Các chỉ thị #define (phép thay thế lệnh), #include (phép chèn tệp), #if (phép lựa chọn các dòng lệnh để biên dịch): đều là các chỉ thị tiền xử lý, không phải là các câu lệnh thông thờng của chơng trình Khi một chơng trình C đợc biên dịch, trớc hết các chỉ thị tiền xử lý sẽ chỉnh lý văn bản chơng trình nguồn, sau đó bản chỉnh lý này mới đợc dịch Các chỉ thị tiền xử lý, do đó làm cho chơng trình ngắn gọn hơn, giúp cho việc tổ chức biên dịch, gỡ rối chơng trình hiệu quả hơn
- Có 2 loại macro: macro thay thế đơn giản và macro thay thế theo đối (nh các hàm)
a)Macro đơn giản
#define tên_macro biểu_thức
có tác dụng thay thế tên_macro bằng biểu_thức
đứng sau nó Khi biểu thức ký tự dài có thể thêm một dấu \ trớc khi xuống dòng Trớc khi dùng lại tên_macro cho một biểu_thức khác thì phải destructor nó bằng chỉ thị
#undef tên_macro
Ví dụ 1:
Trang 23(1) Khi định nghĩa biểu thức chứa phép toán cần đặt
biểu thức trong dấu ngặc đơn
Ví dụ 3: nếu viết
(2) Khi định nghĩa một macro nh một đoạn chơng
trình thì phải viết đoạn chơng trình trong khối lệnh { }
Trang 24Có thể dùng macro có đối để định nghĩa hàm Khi
đó có một số điểm chú ý sau đây:
- Đối của macro có thể không cần khai báo kiểu cụ thể
- Tên hàm (macro) phải viết liền với dấu mở ngoặc bắt đầu khai báo đối
- Tất cả các đối hình thức trong thân hàm (macro) phải viết trong ngoặc đơn
Khi biên dịch, chơng trình sẽ biên dịch sẽ thay
tich(x+y,z) bằng x+y*z nh vậy sẽ không nhận đợc tích (x+y)*z nh mong muốn Vậy phải khai báo lại là:
#define tich(a,b) (a)*(b)
11.2 Hàm inline
Trong ngôn ngữ C++, hàm inline có cách hoạt động giống nh một macro trong ngôn ngữ C Nghĩa là các hàm inline sẽ đợc thay thế trực tiếp thành dãy lệnh mã máy tại chỗ gọi nó trong chơng trình lúc thực hiện
Ưu điểm của macro và inline là ở chỗ nó cho phép trình bầy chơng trình ngắn gọn và quan trọng là cho phép thực hiện nhanh hơn các hàm thông thờng Bởi vì mỗi lần gọi hàm inline (hoặc macro), trình biên dịch sẽ ghép trực
Trang 25tiếp câu lệnh mã máy của nó tại vị trí gọi nó trong chơng trình (mã máy) lúc thực hiện và không đòi hỏi các thủ tục
bổ sung khi gọi hàm và trả giá trị về, nói cách khác không
có cơ chế quản lý lời gọi và trả về (không cần lu ngữ cảnh)
nh đối với các hàm thông thờng
Nhợc điểm của inline (và macro) là khi chúng quá lớn và gọi thờng xuyên thì kích thớc chơng trình sẽ tăng lên rất nhanh Vì mỗi lần gọi inline (hoặc macro) thì các chỉ thị tơng ứng sẽ đợc sinh ra (không có cơ chế lu ngữ cảnh để giải phóng bộ nhớ) do đo chí phí lu trữ tăng lên khi gọi hàm nhiều lần Vậy inline và macro tiết kiệm thời gian nhng không tiết kiệm bộ nhớ, cho nên thân các hàm inline không nên chứa các cấu trúc lặp
Việc sử dụng inline trong C++ tốt hơn macro trong
C ở chỗ hàm inline không cần phải viết các tham số trong dấu ngoặc nh đối với các hàm mà macro mô tả
Ví dụ nếu định nghĩa một macro
#define square(x) { x++*x++;}
thì lời gọi square (a) sẽ sinh ra biểu thức a++*a++ làm thay đổi giá trị của a tới hai lần và hơn nữa lời gọi square(3) sẽ không đợc chấp nhận vì không đợc phép tăng giảm đối với hằng số
Hàm inline đợc định nghĩa và sử dụng nh một hàm bình thờng, điểm khác nhau duy nhất là phải đặt mô tả inline trớc khai báo hàm
Cuối cùng, cần nhớ rằng giống nh macro, đặc tả inline là một yêu cầu chứ không phải là một chỉ thị Nếu vì một lý do nào đó mà trình biên dịch không đáp ứng đợc yêu cầu của inline (chẳng hạn nh bên trong inline có cấu trúc lặp) thì yêu cầu của inline bị bỏ qua và nó đợc biên dịch nh một hàm bình thờng (đây cũng là một điểm tiến bộ của inline so với macro)
Ví dụ:
Trang 2612 Khả năng vào ra mới của C++
12.1 Tổng quan về stream trong C++
Nhìn lại C thấy thấy rằng C sử dụng rất nhiều hàm
để nhập và xuất dữ liệu ví dụ nh: printf(), scanf(), sprintf(), sscanf(), Các hàm này đợc khai báo trong file tiêu đề stdio.h Về một khía cạnh, chúng không nhất quán về thứ
tự và ngữ nghĩa của các tham số
C++ đa ra streams thông qua các lớp và gọi các lớp này là th viện các dòng nhập/xuất Streams tạo ra một khả năng rất mạnh cho phép sửa đổi và mở rộng và vì vậy có thể nhập/xuất đối với các kiểu dữ liệu mới do ngời dùng
định nghĩa
Các dòng nhập (istream) cho phép nhập dữ liệu vào stream, các dòng xuất (ostream) cho phép xuất dữ liệu từ stream Th viện các dòng nhập/xuất là một cấu trúc cây các lớp và chúng ta sẽ nghiên cứu sau
Trang 27Trong tệp tiêu đề iostream.h C++ định nghĩa 2 đối
tợng cin và cout tơng ứng là hai thiết bị chuẩn vào/ra và
đ-ợc sử dụng cùng với hai toán tử tải bội >> (vào) và << (ra)
Thông thờng, ta hiểu cin là bàn phím còn cout là màn hình
12.2 Ghi dữ liệu lên thiết bị ra chuẩn (màn hình) bằng
đó Toán tử << có khả năng tải bội mà trớc hết nó đúng đối với các kiểu sau đây:
1 Kiểu dữ liệu cơ sở: char, int, float, double
Trang 28ợc xem xét trong lần đọc sau.
+ Đối với xâu ký tự, dấu phân cách cũng là SPACE, TAB, CR còn đối với ký tự, dấu phân cách là ký tự CR Trong trờng hợp này không có khái niệm "ký tự không hợp lệ" Mã sinh ra do bấm phím Enter của lần nhập trớc đó vẫn đợc xem xét cho lần nhập xâu ký tự tiếp theo, do đó sẽ
có thể không nhập đợc ký tự mong muốn Giải pháp đặt ra
là trớc mỗi lần nhập xâu ký tự hoặc ký tự ta làm rỗng bộ
đệm bàn phím bằng một trong 2 lệnh sau đây: fflush(stdin); // trong file tiêu đề stdio.h
cin.clear(); // hàm thành phần của lớp định nghĩa
đối tợng cin
+ Trong trờng hợp muốn nhập xâu ký tự có cả dấu cách thì nên dùng lệnh gets(), khai báo trong file tiêu đề stdio.h
Trang 29cout<<"nhap mot xau: "; gets(s)
cout<<"xau vua nhap: "; puts(s);
}
Streams đợc sử dụng hoàn toàn độc lập với stdio
nh-ng việc sử dụnh-ng đồnh-ng thời hai th viện này có thể làm phát sinh ra một số vấn đề, chẳng hạn sẽ không làm xuất ra dữ liệu theo đúng thứ tự mong muốn
Trang 30Bài 3 Một số khái niệm cơ bản của lập trình hớng đối tợng
1 Khái niệm đối tợng (Object)
Đối tợng là mô hình của thực thể, bao gồm:
- Thông tin mô tả đối tợng (dữ liệu), gọi là trạng thái (status) của đối tợng
- Các hàm tác động lên đối tợng làm thay đổi trạng thái của đối tợng, gọi là phơng thức (method) của đối tợng
Dữ liệu và các hàm của đối tợng gắn liền với nhau
- Lớp là kiểu dữ liệu đợc định nghĩa bởi ngời dùng nên nó cũng có các tính chất nh một kiểu dữ liệu cơ sở
Ví dụ nếu A là lớp thì viết A a; cũng giống nh khi viết int x; Tức là a là một biến kiểu A, tuy nhiên ta sẽ gọi
là a là một đối tợng của lớp A và lệnh này gọi là lệnh tạo lập đối tợng
3 Trừu tợng hoá dữ liệu và bao gói thông tin
(Abstraction and Encapsulation)
- Việc đóng gói dữ liệu và các hàm gắn với nó vào một đơn vị cấu trúc (tức là một lớp các đối tợng) đợc xem
nh một nguyên tắc bao gói thông tin Nguyên tắc bao gói dữ liệu ngăn cấm sự truy nhập trực tiếp trong lập trình gọi
là sự che dấu thông tin.
Trang 31- Trừu tợng hoá dữ liệu là cách biểu diễn những đặc tính chung nhất của các đối tợng có cùng một bản chất, bỏ qua những thông tin chi tiết của từng đối tợng riêng lẻ Trong lập trình, lớp đợc sử dụng nh kiểu dữ liệu trừu tợng
và nâng lên mức khuôn mẫu lớp Phơng pháp lập trình ớng đối tợng bao gồm cả trừu tợng hoá dữ liệu và trừu tợng hoá chức năng
h-4 Sự kế thừa (Inheritance)
4.1 Khái niệm sự kế thừa
- Sự kế thừa là quá trình mà các đối tợng của lớp này đợc quyền sử dụng một số tính chất của các đối tợng của các lớp khác Lớp trên gọi là lớp cơ sở (base class), lớp
mới nhận đợc gọi là lớp dẫn xuất (derivative class)
- Nguyên lý kế thừa tạo ra cấu trúc phân cấp các lớp (đồ thị dạng cây)
- Trong lập trình hớng đối tợng, khái niệm kế thừa kéo theo ý tởng sử dụng lại, nghĩa là từ một lớp đã xây dựng, ta có thể bổ sung một số tính chất riêng để tạo ra một lớp mới mà không làm thay đổi những cái đã có
Trang 335 Sự đa hình và sự tải bội (Polymorphism and
Oveloading)
5.1 Khái niệm về sự đa hình (sự tơng ứng bội)
Sự đa hình (Polymorphism) đợc hiểu một cách trừu tợng là khả năng một khái niệm có thể xuất hiện ở nhiều dạng khác nhau
Hàm và toán tử tải bội là một ví dụ của tính đa hình, tuy nhiên, sự tải bội mà chúng ta nhìn thấy ở đây là sự liên kết tĩnh Hàm đợc chọn ở đây là trong thời gian dịch chơng trình và điều này làm giới hạn tính đa hình Trong ngôn ngữ C++, các hàm đợc chọn trong khi chạy chơng trình và
đây là một kỹ thuật trong sự kế thừa ở C++ Sự đa hình
hiểu theo cách trên có lẽ nên dịch là sự tơng ứng bội thì
thích hợp hơn vì nó nhằm trả lời câu hỏi: Ngoài cơ chế cho phép tải bội của các hàm và toán tử trong một lớp, thì một con trỏ đối tợng sẽ liên kết với hàm nào trong các lớp có quan hệ kế thừa mà các hàm này có chung đặc tính (cùng tên và cùng tham số, kiểu tham số và kiểu của hàm) ? Rõ
Tơng ứng bội đóng vai trò quan trọng trong việc tạo
ra các đối tợng có cấu trúc bên trong khác nhau nhng có khả năng cùng dùng chung một giao diện bên ngoài (nh tên gọi) Điều này có nghĩa là một lớp tổng quát các phép toán
Trang 34đợc định nghĩa theo các thuật toán khác nhau nhng có khả năng sử dụng theo cùng một cách giống nhau Tơng ứng bội là mở rộng khái niệm sử dụng lại trong nguyên lý kế thừa
Ví dụ:
Hàm VE() là hàm tơng ứng bội và nó đợc xác định tùy theo ngữ cảnh khi sử dụng.Trong lập trình hớng đối t-ợng với C++, tơng ứng bội có 2 loại:
Loại 1: Tơng ứng bội trong thời gian dịch chơng
trình: Đó là sự cho phép định nghĩa và thực hiện các toán
tử tải bội (operator overloading) và các hàm tải bội (function overloading) Ví dụ một toán tử gốc của ngôn ngữ là phép +, chỉ có thể thực hiện đợc đối với kiểu dữ liệu cơ sở nh int, float, double, char chứ không thể thực hiện
đối với các kiểu dữ liệu do ngời dùng định nghĩa, ví dụ nh
Tương ứng bội
Tương ứng bội trong
thời gian dịch chương Tương ứng bội trong thời gian chạy chương trình
HINH_HOC VE()
HINH_TRON()
VE(TRON) VE(DA_GIAC)DA_GIAC DUONG_THANG
VE(D_THANG)
Trang 35các số phức, các vector, các ma trận cùng kích thớc Nh vậy, lập trình hớng đối tợng cho phép giữ nguyên ký hiệu phép cộng + để định nghĩa bổ sung phép cộng cho các kiểu dữ liệu mới Khi đó ta có định nghĩa chồng toán tử, hay toán tử đó có khả năng tải bội Tơng tự có thể định nghĩa các hàm cùng tên trong một lớp nhng thực hiện các chức năng khác nhau Ví dụ nh các constructor của lớp là một tr-ờng hợp của hàm tải bội; chuyển đổi từ kiểu lớp sang kiểu cơ sở đợc coi là hàm tải bội Nếu hiểu theo nghĩa "phép toán là tác động lên dữ liệu, làm thay đổi dữ liệu" thì toán
tử tải bội và hàm tải bội đều là các phép toán tải bội
Loại 2: tơng ứng bội trong thời gian chạy chơng
trình Đó là sự cho phép định nghĩa và thực hiện các hàm
cùng tên, chung đặc tính trong lớp khác nhau có quan hệ
kế thừa nhau Nếu nh tơng ứng bội trong thời gian dịch
ch-ơng trình quan tâm đến các hàm và toán tử cùng tên trong một lớp thì tơng ứng bội trong thời gian chạy chơng trình quan tâm đến những hàm và phép toán cùng tên và chung
đặc tính giữa các lớp có quan hệ kế thừa và cách ứng xử của các đối tợng tơng ứng nh thế nào khi gọi thực hiện các hàm này
5.2 Liên kết tĩnh và liên kết động
Tơng ứng bội trong thời gian dịch chơng trình và
t-ơng ứng bội trong thời gian chạy cht-ơng trình liên quan
đến khái niệm liên kết tĩnh và liên kết động (có tài liệu
còn gọi liên kết tĩnh là liên kết sớm hay ràng buộc sớm - Early Binding; và gọi liên kết động là liên kết muộn hay ràng buộc muộn - Late Binding)
Trong chơng trình hớng đối tợng với C++, cần phải trả lời câu hỏi: Một con trỏ đối tợng sẽ liên kết với hàm nào trong các lớp có quan hệ kế thừa mà các hàm này có chung đặc tính?
Trang 36- Nếu mối liên kết giữa đối tợng với hàm đó đã có thể xác định đợc ngay từ khi dịch chơng trình và sẽ không thay đổi trong suốt thời gian chạy chơng trình thì đợc gọi
là liên kết tĩnh Nh vậy các phép toán tải bội trong một lớp
và các hàm cùng đặc tính giữa các lớp nếu không có một chỉ định đặc biệt nào đó (ở đây là cài đặt cơ chế hàm ảo) thì đều là các phép toán đợc thực hiện theo sự liên kết tĩnh
- Ngợc lại, nếu mối liên kết này chỉ có thể xác định
đợc tại những thời điểm khác nhau lúc chạy chơng trình thì gọi là liên kết động Liên kết động đợc giải quyết bằng cơ chế hàm ảo (virtual function) Cơ chế hàm ảo cho phép
định nghĩa các hàm trừu tợng ở lớp trên và cài đặt cụ thể ở các lớp dới, sau đó, lúc chạy chơng trình, con trỏ đối tợng
sẽ tùy theo ngữ cảnh mà gọi hàm nào trong các lớp có quan hệ kế thừa
Những nhận xét mở đầu này sẽ đợc trình bầy cụ thể khi nghiên cứu về sự kế thừa
Trang 38- Những thành phần đợc khai trong lớp đợc chia thành 2 nhóm:
+ Những thành phần private chỉ có thể đợc truy nhập bởi các hàm thành phần khác bên trong chính lớp đó, tức là chúng chỉ đợc sử dụng bên trong thân các hàm thành phần của lớp Những thành phần private do đó không thể truy nhập bởi những hàm bên ngoài lớp thậm chí không thể truy nhập thông qua chính đối tợng thuộc lớp đó (thông qua toán tử chấm: ".", là toán tử xác định thành phần của đối t-ợng)
+ Những thành phần public có thể đợc truy nhập bởi các hàm thành phần khác bên trong lớp và các hàm bên ngoài lớp Nếu truy nhập bởi các hàm bên ngoài lớp thì phải sử dụng thông qua đối tợng của lớp Các đối tợng thuộc lớp sẽ truy nhập tới các thành phần public thông qua toán tử xác định "."
- Các hàm bên ngoài lớp tức là các hàm thành phần của lớp khác và các hàm bình thờng trong chơng trình, không thuộc lớp nào cả (gọi là các hàm ngoại lai)
Vùng private
Vùng public
Datafunction
Datafunction
Trang 39{ cout<<"Enter name: "; gets(name);
cout<<"Enter age: "; cin>>age; }
Trang 40{ cout<<"Nhap ho ten sinh vien: "; gets(hoten);
cout<<"Nhap nam sinh: "; cin>>namsinh; }
void SINHVIEN::xem()
{ cout<<"Ho ten: "<<hoten<<endl;
cout<<"Nam sinh: "<<namsinh<<endl;}
Ngoài thành phần private và public, trong lớp còn có thành phần protected đợc dùng trong quan hệ kế thừa giữa các lớp, ta sẽ nghiên cứu sau