Lập trình hướng đối tượng với C++

pdf 396 trang hapham 3140
Bạn đang xem 20 trang mẫu của tài liệu "Lập trình hướng đối tượng với C++", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdflap_trinh_huong_doi_tuong_voi_c.pdf

Nội dung text: Lập trình hướng đối tượng với C++

  1. Lập Trình Hướng Đối Tượng Với C++ Lời nói đầu Lập trình cấu trúc là phương pháp tổ chức, phân chia chương trình thành các hàm, thủ tục, chúng được dùng để xử lý dữ liệu nhưng lại tách rời các cấu trúc dữ liệu. Thông qua các ngôn ngữ Foxpro, Pascal, C đa số những người làm Tin học đã khá quen biết với phương pháp lập trình này. Lập trình hướng đối tượng dựa trên việc tổ chức chương trình thành các lớp. Khác với hàm và thủ tục, lớp là một đơn vị bao gồm cả dữ liệu và các phương thức xử lý. Vì vậy lớp có thể mô tả các thực thể một cách chân thực, đầy đủ cả phần dữ liệu và yêu cầu quản lý. Tư tưởng lập trình hướng đối tượng được áp dụng cho hầu hết các ngôn ngữ mới chạy trên môi trường Windows như Microsoft Access, Visual Basic, Visual C. Vì vậy việc nghiên cứu phương pháp lập trình mới này là rất cần thiết đối với tất cả những người quan tâm, yêu thích Tin học. C ra đời năm 1973 với mục đích ban đầu là để viết hệ điều hành Unix trên máy tính mini PDP. Sau đó C đã được sử dụng rộng rãi trên nhiều loại máy tính khác nhau và đã trở thành một ngôn ngữ lập trình cấu trúc rất được ưa chuộng. Để đưa C vào thế giới hướng hướng đối tượng, năm 1980 nhà khoa học người Mỹ B. Stroustrup đã cho ra đời một ngôn ngữ C mới có tên ban đầu là “C có lớp”, sau đó đến năm 1983 thì gọi là C++. Ngôn ngữ C++ là một sự phát triển mạnh mẽ của C. Trong C++ chẳng những đưa vào tất cả các khái niệm, công cụ của lập trình hướng đối tượng mà còn đưa vào nhiều khả năng mới mẻ cho hàm. Như vậy C++ là một ngôn ngữ lai cho phép tổ chức chương trình theo các lớp và các hàm. Có thể nói C++ đã thúc đẩy ngôn ngữ C vốn đã rất thuyết phục đi vào thế giới lập trình hướng đối tượng và C++ đã trở thành ngôn ngữ hướng đối tượng nổi bật trong những năm 90. Cuốn sách này sẽ trình bầy một cách hệ thống các khái niệm của lập trình hướng đối tượng được cài đặt trong C++ như lớp, đối tượng, sự thừa kế, tính tương ứng bội và các khả năng mới trong xây dựng, sử dụng hàm như: đối tham chiếu, đối mặc định, hàm trùng tên, hàm toán tử. Có một số vấn đề còn ít được biết đến như cách xây dựng hàm với số đối bất định trong C cũng sẽ được giới thiệu. Các chương từ 1 đến 10 với cách giải thích tỉ mỉ và với gần 100 chương trình minh hoạ sẽ cung cấp cho bạn đọc các khái niệm, phương pháp và kinh nghiệm lập trình hướng đối tượng trên C++. Mục lục cuối sách sẽ hệ thống ngắn gọn phương pháp phân tích, thiết kế và lập trình hướng đối tượng trên bình diện chung. Cuốn sách gồm 10 chương và 6 phụ lục Chương 1 hướng dẫn cách làm việc với phần mềm TC++ 3.0 để thử nghiệm các chương trình, trình bầy sơ lược về các phương pháp lập trình và giới thiệu một số mở rộng đơn giản của C++ . Chương 2 trình bầy các khả năng mới trong việc xây dựng và sử dụng hàm trong C++ như biến tham chiếu, đối có kiểu tham chiếu, đối có giá trị mặc định, hàm trực tuyến, hàm trùng tên, hàm toán tử. Chương 3 nói về một khái niệm trung tâm của lập trình hướng đối tượng là lớp gồm: Định nghĩa lớp, khai báo các biến, mảng đối tượng (kiểu lớp), phương thức, dùng con trỏ this trong phương thức, phạm vi truy xuất của các thành phần, các phương thức toán tử. Chương 4 trình bầy các vấn đề tạo dựng, sao chép, huỷ bỏ các đối tượng và các vấn đề khác có liên quan như: Hàm tạo, hàm tạo sao chép, hàm huỷ, toán tử gán, cấp phát bộ nhớ cho đối tượng, hàm bạn, lớp bạn. Chương 5 trình bầy một khái niệm quan trọng tạo nên khả năng mạnh của lập trình hướng đối tượng trong việc phát triển, mở rộng phần mềm, đó là khả năng thừa kế của các lớp. Chương 6 trình bầy một khái niệm quan trọng khác cho phép xử lý các vấn đề khác nhau, các thực thể khác nhau, các thuật toán khác nhau theo cùng một lược đồ thống nhất, đó là tính tương ứng bội và phương thức ảo. Các công cụ này cho phép dễ dàng tổ chức chương trình quản lý nhiều dạng đối tượng khác nhau. GS: Phạm Văn Ất 1 tenshi3003@gmail.com
  2. Lập Trình Hướng Đối Tượng Với C++ Chương 7 nói về việc tổ chức vào - ra trong C++. C++ đưa vào một khái niệm mới gọi là các dòng tin (Stream). Các thao tác vào - ra sẽ thực hiện trao đổi dữ liệu giữa bộ nhớ với dòng tin: Vào là chuyển dữ liệu từ dòng nhập vào bộ nhớ, ra là chuyển dữ liệu từ bộ nhớ lên dòng xuất. Để nhập xuất dữ liệu trên một thiết bị cụ thể nào, ta chỉ cần gắn dòng nhập xuất với thiết bị đó. Việc tổ chức vào ra theo cách như vậy là rất khoa học và tiện lợi vì nó có tính độc lập thiết bị. Chương 8 trình bầy các hàm đồ hoạ sử dụng trong C và C++. Các hàm này được sử dụng rải rác trong toàn bộ cuốn sách để xây dựng các đối tượng đồ hoạ. Chương 9 trình bầy các hàm truy xuất trực tiếp vào bộ nhớ của máy tính, trong đó có bộ nhớ màn hình. Các hàm này sẽ được sử dụng trong chương 10 để xây dựng các lớp menu và cửa sổ . Chương 10 giới thiệu 5 chương trình tương đối hoàn chỉnh nhằm minh hoạ thêm khả năng và kỹ thuật lập trình hướng đối tượng trên C++ Phụ lục 1 trình bầy các phép toán trong C++ và thứ tự ưu của chúng. Phụ lục 2 liệt kê một danh sách các từ khoá của C++. Phụ lục 3 trình bầy bảng mã ASCII và mã quét của các ký tự. Phụ lục 4 trình bầy một vấn đề quan trọng nhưng còn ít được nói đến trong các tài liệu, đó là cách sử dụng con trỏ void để xây dựng các hàm với số đối không cố định giống như các hàm printf và scanf của C. Vì trong C++ vẫn sử dụng các hàm của C, nên trong phụ lục 5 sẽ giới thiệu tóm tắt hơn 200 hàm để bạn đọc tiện việc tra cứu. Cuối cùng, phụ lục 6 trình bầy một cách ngắn gọn phương pháp phân tích, thiết kế và lập trình hướng đối tượng trên bình diện chung. Khi viết chúng tôi đã hết sức cố gắng để cuốn sách được hoàn chỉnh, song chắc chắn không tránh khỏi thiếu sót, vì vậy rất mong nhận được sự góp ý của độc giả. Nhân dịp này chúng tôi xin chân thành cám ơn cử nhân Nguyễn Văn Phác đã tận tình giúp đỡ trong việc hiệu đính và biên tập cuốn sách này. Tác giả GS: Phạm Văn Ất 2 tenshi3003@gmail.com
  3. Lập Trình Hướng Đối Tượng Với C++ Chương 1 C++ và lập trình hướng đối tượng Trong chương này trình bầy các vấn đề sau: - Cách sử dụng phần mềm TC++ 3.0 - Những sửa đổi cần thiết một chương trình C để biến nó thành một chương trình C++ (chạy được trong môi trường C++) - Tóm lược về các phương pháp lập trình cấu trúc và lập trình hướng đối tượng - Những mở rộng của C++ so với C Bài 1. Làm việc với TC++ 3.0 Các ví dụ trong cuốn sách này sẽ viết và thực hiện trên môi trường TC++ 3.0. Bộ cài đặt TC++ 3.0 gồm 5 đĩa. Sau khi cài đặt (giả sử vào thư mục C:\TC) thì trong thư mục TC sẽ gồm các thư mục con sau: C:\TC\BGI chứa các tệp đuôi BGI và CHR C:\TC\BIN chứa các tệp chương trình (đuôi EXE) như TC, TCC, TLIB, TLINK C:\TC\INCLUDE chứa các tệp tiêu đề đuôi H C:\TC\LIB chứa các tệp đuôi LIB, OBJ Để vào môi trường của TC++ chỉ cần thực hiện tệp chương trình TC trong thư mục C:\TC\BIN . Kết quả nhận được hệ menu chính của TC++ với mầu nền xanh gần giống như hệ menu quen thuộc của TC (Turbo C). Hệ menu của TC++ gồm các menu: File, Edit, Search, Run, Compile, Debug, Project, Options, Window, Help. Cách soạn thảo, biên dịch và chạy chương trình trong TC++ cũngg giống như trong TC, ngoại trừ điểm sau: Tệp chương trình trong hệ soạn thảo của TC++ có đuôi mặc định là CPP cũng trong TC thì tệp chương trình luôn có đuôi C. Trong TC++ có thể thực hiện cả chương trình C và C++. Để thực hiện chương trình C cần dựng đuôi C để đặt tên cho tệp chương trình, để thực hiện chương trình C++ cần dựng đuôi CPP để đặt tên cho tệp chương trình. Bài 2. C và C++ - Có thể nói C++ là sự mở rộng (đáng kể) của C. Điều đó có nghĩa là mọi khả năng, mọi khái niệm trong C đều dùng được trong C++. - Vì trong C++ sử dụng gần như toàn bộ các khái niệm, định nghĩa, các kiểu dữ liệu, các cấu trúc lệnh, các hàm và các công cụ khác của C, nên yêu cầu bắt buộc đối với các đọc giả C++ là phải biết sử dụng tương đối thành thạo ngôn ngữ C. - Vì C++ là sự mở rộng của C, nên bản thân một chương trình C đó là chương trình C++ (chỉ cần thay đuôi C bằng đuôi CPP). Tuy nhiên Trình biên dịch TC++ yêu cầu mọi hàm chuẩn dùng trong chương trình đều phải khai báo nguyên mẫu bằng một câu lệnh #include, trong khi điều này không bắt buộc đối với Trình biên dịch của TC. Trong C có thể dùng một hàm chuẩn mà bỏ qua câu lệnh #include để khai báo nguyên mẫu của hàm được dùng. Điều này không báo lỗi khi biên dịch, nhưng có thể dẫn đến kết quả sai khi chạy chương trình. Ví dụ khi biên dịch chương trình sau trong môi trường C sẽ không gặp các dòng cảnh báo (Warning) và thông báo lỗi (error). Nhưng khi chạy sẽ nhận được kết quả sai. #include void main() GS: Phạm Văn Ất 3 tenshi3003@gmail.com
  4. Lập Trình Hướng Đối Tượng Với C++ { float a,b,c,p,s; printf("\nNhap a, b, c "); scanf("%f%f%f",&a,&b,&c); p=(a+b+c)/2; s= sqrt(p*(p-a)*(p-b)*(p-c)); printf("\nDien tich = %0.2f",s); getch(); } Nếu biên dịch chương trình này trong TC++ sẽ nhận được các thông báo lỗi sau: Eror: Funtion ‘sqrt’ should have a prototype Eror: Funtion ‘getch’ should have a prototype Để biến chương trình trên thành một chương trình C++ cần: + Đặt tên chương chường với đuôi CPP + Thêm 2 câu lệnh #include để khai báo nguyên mẫu cho các hàm sqrt, getch: #include #include Bài 3. Lập trình cấu trúc và lập trình hướng đối tượng 3.1. Phương pháp lập trình cấu trúc - Tư tưởng chính của lập trình cấu trúc là tổ chức chương trình thành các chương trình con. Trong PASCAL có 2 kiểu chương trình con là thủ tục và hàm. Trong C chỉ có một loại chương trình con là hàm. Hàm là một đơn vị chương trình độc lập dùng để thực hiện một phần việc nào đó như: Nhập số liệu, in kết quả hay thực hiện một số tính toán. Hàm cần có đối và các biến, mảng cục bộ dùng riêng cho hàm. Việc trao đổi dữ liệu giữa các hàm thực hiện thông qua các đối và các biến toàn bộ. Các ngôn ngữ như C, PASCAL, FOXPRO là các ngôn ngữ cho phép triển khai phương pháp lập trình cấu trúc. Một chương trình cấu trúc gồm các cấu trúc dữ liệu (như biến, mảng, bản ghi) và các hàm, thủ tục. Nhiệm vụ chính của việc tổ chức thiết kế chương trình cấu trúc là tổ chức chương trình thành các hàm, thủ tục: Chương trình sẽ bao gồm các hàm, thủ tục nào. Ví dụ xét yêu cầu sau: Viết chương trình nhập toạ độ (x,y) của một dẫy điểm, sau đó tìm một cặp điểm cách xa nhau nhất. Trên tư tưởng của lập trình cấu trúc có thể tổ chức chương trình như sau: + Sử dụng 2 mảng thực toàn bộ x và y để chứa toạ độ dẫy điẻm + Xây dựng 2 hàm: Hàm nhapsl dùng để nhập toạ độ n điểm, hàm này có một đối là biến nguyên n và được khai báo như sau: void nhapsl(int n); GS: Phạm Văn Ất 4 tenshi3003@gmail.com
  5. Lập Trình Hướng Đối Tượng Với C++ Hàm do_dai dùng để tính độ dài đoạn thẳng đi qua 2 điểm có chỉ số là i và j , nó được khai báo như sau: float do_dai(int i, int j); Chương trình C cho bài toán trên được viết như sau: #include #include #include float x[100],y[100]; float do_dai(int i, int j) { return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); } void nhapsl(int n) { int i; for (i=1;i dmax) { dmax=d; imax=i; jmax=j; } } printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); GS: Phạm Văn Ất 5 tenshi3003@gmail.com
  6. Lập Trình Hướng Đối Tượng Với C++ getch(); } 3.2. Phương pháp lập trình hướng đối tượng + Khỏi niệm trung tõm của lập trình hướng đối tượng là lớp (class). Có thể xem lớp là sự kết hợp các thành phần dữ liệu và các hàm. Cũngg có thể xem lớp là sự mở rộng của cấu trúc trong C (struct) bằng cách đưa thêm vào các phương thức (method) hay cũng gọi là hàm thành viên (member function). Một lớp được định nghĩa như sau: Class Tên_Lớp { // Khai báo các thành phần dữ liệu // Khai báo các phương thức }; + Các phương thức có thể được viết (xây dựng) bên trong hoặc bên ngoài (phía dưới) phần định nghĩa lớp. Cấu trúc (cách viết) phương thức tương tự như hàm ngoại trừ quy tắc sau: Khi xây dựng một phương thức bên ngoài định nghĩa lớp thì trong dòng đầu tiên cần dùng tên lớp và 2 dấu : đặt trước tên phương thức để chỉ rừ phương thức thuộc lớp nào (xem ví dụ bên dưới). + Sử dụng các thành phần dữ liệu trong phương thức: Vì phương thức và các thành phần dữ liệu thuộc cùng một lớp và vì phương thức được lập lên cốt để xử lý các thành phần dữ liệu, nên trong thân của phương thức có quyền truy nhập đến các thành phần dữ liệu (của cùng lớp). + Biến lớp: Sau khi định nghĩa một lớp, có thể dùng tên lớp để khai báo các biến kiểu lớp hay cũng gọi là đối tượng. Mỗi đối tượng sẽ có các thành phần dữ liệu và các phương thức. Lời gọi một phương thức cần chứa tên đối tượng để xác định phương thức thực hiện từ đối tượng nào. + Một chương trình hướng đối tượng sẽ bao gồm các lớp có quan hệ với nhau. + Việc phân tích, thiết kế chương trình theo phương pháp hướng đối tượng nhằm thiết kế, xây dựng các lớp. + Từ khái niệm lớp nẩy sinh hàng loạt khái niệm khác như: Thành phần dữ liệu, phương thức, phạm vi, sự đóng gói, hàm tạo, hàm huỷ, sự thừa kế, lớp cơ sử, lớp dẫn xuất, tương ứng bội, phương thức ảo, + Ưu điểm của việc thiết kế hướng đối tượng là tập trung xác định các lớp để mô tả các thực thể của bài toán. Mỗi lớp đưa vào các thành phần dữ liệu của thực thể và xây dựng luôn các phương thức để xử lý dữ liệu. Như vậy việc thiết kế chương trình xuất phát từ các nội dụng, các vấn đề của bài toán. + Các ngụn ngữ thuần tuý hướng đối tượng (như Smalltalk) chỉ hỗ trợ các khái niệm về lớp, không có các khái niệm hàm. + C++ là ngôn ngữ lai , nó cho phép sử dụng cả các công cụ của lớp và hàm. Để minh hoạ các khái niệm vừa nêu về lập trình hướng đối tượng ta trở lại xét bài toán tìm độ dài lớn nhất đi qua 2 điểm. Trong bài toán này ta gặp một thực thể là dẫy điểm. Các thành phần dữ liệu của lớp dẫy điểm gồm: - Biến nguyên n là số điểm của dẫy - Con trỏ x kiểu thực trỏ đến vùng nhớ chứa dẫy hoành độ - Con trỏ y kiểu thực trỏ đến vùng nhớ chứa dẫy tung độ Các phương thức cần đưa vào theo yêu cầu bài toán gồm: - Nhập toạ độ một điểm GS: Phạm Văn Ất 6 tenshi3003@gmail.com
  7. Lập Trình Hướng Đối Tượng Với C++ - Tính độ dài đoạn thẳng đi qua 2 điểm Dưới đây là chương trình viết theo thiết kế hướng đối tượng. Để thực hiện chương trình này nhớ đặt tên tệp có đuôi CPP. Xem chương trình ta thấy thờm một điều mới trong C++ là: Các khai báo biến, mảng có thể viết bất kỳ chỗ nào trong chương trình (tất nhiên phải trước khi sử dụng biến, mảng). #include #include #include #include class daydiem { public: int n; float *x,*y; float do_dai(int i, int j) { return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); } void nhapsl(void); }; void daydiem::nhapsl(void) { int i; printf("\nSo diem N= "); scanf("%d",&n); x=(float*)malloc((n+1)*sizeof(float)); y=(float*)malloc((n+1)*sizeof(float)); for (i=1;i<=n;++i) { printf("\nNhap toa do x, y cua diem thu %d : ",i); scanf("%f%f",&x[i],&y[i]); } } void main() { daydiem p; p.nhapsl(); int n,i,j,imax,jmax; float d,dmax; n=p.n; dmax=p.do_dai(1,2); imax=1;jmax=2; for (i=1;i<=n-1;++i) GS: Phạm Văn Ất 7 tenshi3003@gmail.com
  8. Lập Trình Hướng Đối Tượng Với C++ for (j=i+1;j dmax) { dmax=d; imax=i; jmax=j; } } printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); getch(); } Bài 4. Một số mở rộng đơn giản của C++ so với C Trong mục này trình bầy một số mở rộng của C++ , tuy đơn giản, ngắn gọn nhưng đem lại rất nhiều tiện lợi. 4.1. Viết các dòng ghi chú Trong C++ vẫn có thể viết các dòng ghi chú trong các dấu /* và */ như trong C. Cách này cho phộp viết các ghi chú trên nhiều dòng hoặc trên một dòng. Ngoài ra trong C++ cũng cho phộp viết ghi chú trên một dòng sau 2 dấu gạch chộo, vớ dụ: int x,y ; // Khai báo 2 biến thực 4.2. Khai báo linh hoạt Trong C tất cả các câu lệnh khai báo biến, mảng cục bộ phải đặt tại đầu khối. Do vậy nhiều khi, vị trí khai báo và vị trí sử dụng của biến khá xa nhau, gây khó khăn trong việc kiểm soát chương trình. C++ đó khắc phục nhược điểm này bằng cách cho phép các lệnh khai báo biến, mảng có thể đặt bất kỳ chỗ nào trong chương trình trước khi các biến, mảng được sử dụng. Ví dụ chương trình nhập một dẫy số thực rồi sắp xếp theo thứ tự tăng dần có thể viết trong C++ như sau: #include #include #include void main() { int n; printf("\n So phan tu cua day N= "); scanf("%d",&n); float *x= (float*)malloc((n+1)*sizeof(float)); for (int i=1;i<=n;++i) { printf("\nX[%d]= ",i); GS: Phạm Văn Ất 8 tenshi3003@gmail.com
  9. Lập Trình Hướng Đối Tượng Với C++ scanf("%f",x+i); } for (i=1;i x[j]) { float tg=x[i]; x[i]=x[j]; x[j]=tg; } printf("\nDay sau khi sap xep\n"); for (i=1;i #include void main() { int n; printf("\n So phan tu cua day N= "); scanf("%d",&n); float s=0.0; for (int i=1;i<=n;++i) s += float(i+1)/float(i) ; // Ep kieu theo C++ printf("S= %0.2f ",s); getch(); } 4.4. Hằng có kiểu Để tạo ra một hằng có kiểu, ta sử dụng từ khoá const đặt trước một khai báo có khởi gán giá trị. Sau đây là một số ví dụ. GS: Phạm Văn Ất 9 tenshi3003@gmail.com
  10. Lập Trình Hướng Đối Tượng Với C++ + Hằng nguyên: const int maxsize = 1000; int a[maxsize] ; + Cấu trúc hằng: typedef struct { int x, y ; // Toạ độ của điểm int mau ; // Mó mầu của điểm } DIEM ; const DIEM d = {320, 240, 15}; Chương trình dưới đây minh hoạ cách dùng hằng có kiểu. Chương trình tạo một cấu trúc hằng (kiểu DIEM) mụ tả điểm giữa màn hình đồ hoạ với mầu trắng. Điểm này được hiển thị trên màn hình đồ hoạ. #include #include #include #include typedef struct { int x,y; int mau; } DIEM; void main() { int mh=0,mode=0; initgraph(&mh,&mode,""); int loi=graphresult(); if (loi) { printf("\nLoi do hoa: %s",grapherrormsg(loi)); getch(); exit(0); } const DIEM gmh = {getmaxx()/2,getmaxy()/2,WHITE}; putpixel(gmh.x,gmh.y,gmh.mau); getch(); closegraph(); } Chú ý: a. Có thể dùng các hàm để gán giá trị cho các hằng có kiểu (trong chương trình trên dựng các hàm getmax và getmaxy). b. Mọi câu lệnh nhằm thay đổi giá trị hằng có kiểu đều bị báo lỗi khi biên dịch chương trình. Vớ dụ nếu trong chương trình đưa vào câu lệnh: GS: Phạm Văn Ất 10 tenshi3003@gmail.com
  11. Lập Trình Hướng Đối Tượng Với C++ gmh.x=200; thì khi dịch chương trình sẽ nhận được thông báo lỗi như sau: Cannot modify a const object 4.5. Các kiểu char và int Trong C một hằng ký tự được xem là nguyên do đó nó có kích thước 2 byte, ví dụ trong C: sizeof(‘A’) = sizeof(int) = 2 Cũng trong C++ một hằng ký tự được xem là giá trị kiểu char và có kích thước một byte. Như vậy trong C++ thì: sizeof(‘A’) = sizeof(char) = 1 4.6. Lấy địa chỉ các phần tử mảng thực 2 chiều Trong Turbo C 2.0 không cho phép dùng phép & để lấy địa chỉ các phần tử mảng thực 2 chiều. Vì vậy khi nhập một ma trân thực (dùng scanf) ta phải nhập qua một biến trung gian sau đó mới gán cho các phần tử mảng. Trong TC ++ 3.0 cho phép lấy địa chỉ các phần tử mảng thực 2 chiều, do đó có thể dùng scanf để nhập trực tiếp vào các phần tử mảng. Chương trình C++ dưới đây sẽ minh hoạ điều này. Chương trình nhập một ma trận thực cấp mxn và xỏc định phần tử có giá trị lớn nhất. #include #include void main() { float a[20][20], smax; int m,n,i,j, imax, jmax; clrscr(); puts( "Cho biet so hang va so cot cua ma tran: ") ; scanf("%d%d",&m,&n) ; for (i=1;i<=m;++i) for (j=1;j<=n;++j) { printf("\na[%d][%d]= ",i,j); scanf("%f",&a[i][j]); // Lấy địa chỉ phần tử mảng thực // 2 chiều } smax = a[1][1]; imax=1; jmax=1; for (i=1;i<=m;++i) for (j=1;j<=n;++j) if (smax<a[i][j]) { smax = a[i][j]; imax=i ; jmax = j; } GS: Phạm Văn Ất 11 tenshi3003@gmail.com
  12. Lập Trình Hướng Đối Tượng Với C++ puts( "\n\n Ma tran") ; for (i=1;i > biến >> >> biến để nhập các giá trị số (nguyên thực) từ bàn phím và gán cho các biến. Để nhập một dẫy không quá n ký tự và chứa vào mảng h (kiểu char) có thể dùng phương thức cin.get như sau: cin.get(h,n); Chú ý 1: Toán tử nhập cin >> sẽ để lại ký tự chuyển dòng ‘\n’ trong bộ đệm, ký tự này có thể làm trôi phương thức cin.get. Để khắc phục tỡnh trạng trên cần dựng phương thức cin.ignore để bỏ qua một ký tự chuyển dòng như sau: cin.ignore(1); Chú ý 2: Để sử dụng các toán tử và phương thức nói trên cần khai báo tệp tiêu đề: #include Chương trình sau minh hoạ việc sử dụng các cụng cụ vào ra mới của C++ để nhập một danh sách n thí sinh. Dữ liệu mỗi thí sinh gồm họ tên, các điểm toán, lý, hoá. Sau đó in danh sách thí sinh theo thứ tự giảm của tổng điểm. #include #include void main() { struct { char ht[25]; GS: Phạm Văn Ất 12 tenshi3003@gmail.com
  13. Lập Trình Hướng Đối Tượng Với C++ float t,l,h,td; } ts[50],tg; int n,i,j; clrscr(); cout > n ; for (i=1;i > ts[i].t >> ts[i].l >> ts[i].h ; ts[i].td = ts[i].t + ts[i].l + ts[i].h ; } for (i=1;i<=n-1;++i) for (j=i+1;j<=n;++j) if (ts[i].td < ts[j].td ) { tg=ts[i]; ts[i]=ts[j]; ts[j]=tg; } cout << "\nDanh sach thi sinh sau khi sap xep " ; for (i=1;i<=n;++i) { cout << "\n Ho ten: " << ts[i].ht; cout << " Tong diem: " << ts[i].td; } getch(); } 5.2. Định dạng khi in ra màn hình + Để quy định số thực (float, double) được in ra có đúng p chữ số sau dấu chấm thập phân, ta sử dụng đồng thời các hàm sau: setiosflags(ios::showpoint); // Bật cờ hiệu showpoint setprecision(p); Các hàm này cần đặt trong toán tử xuất như sau: cout << setiosflags(ios::showpoint) << setprecision(p) ; Câu lệnh trên sẽ có hiệu lực đối với tất cả các toán tử xuất tiếp theo cho đến khi gặp một câu lệnh định dạng mới. GS: Phạm Văn Ất 13 tenshi3003@gmail.com
  14. Lập Trình Hướng Đối Tượng Với C++ + Để quy định độ rộng tối thiểu là w vị trí cho giá trị (nguyên, thực, chuỗi) được in trong các toán tử xuất, ta dùng hàm setw(w) Hàm này cần đặt trong toán tử xuất và nó chỉ có hiệu lực cho một giá trị được in gần nhất. Các giá trị in ra tiếp theo sẽ có độ rộng tối thiểu mặc định là 0. Như vậy câu lệnh: cout Trở lại chương trình trên ta thấy danh sỏch thớ sinh in ra sẽ không thẳng cột. Để khắc phục điều này cần viết lại đoạn chương trình in như sau: cout #include #include void main() { float a[20][20], smax; int m,n,i,j, imax, jmax; clrscr(); cout > m >> n ; for (i=1;i > a[i][j] ; } smax = a[1][1]; imax=1; jmax=1; for (i=1;i<=m;++i) for (j=1;j<=n;++j) if (smax<a[i][j]) GS: Phạm Văn Ất 14 tenshi3003@gmail.com
  15. Lập Trình Hướng Đối Tượng Với C++ { smax = a[i][j]; imax=i ; jmax = j; } cout << "\n\n Ma tran" ; cout << setiosflags(ios::showpoint) << setprecision(1) ; for (i=1;i<=m;++i) for (j=1;j<=n;++j) { if (j==1) cout << '\n' ; cout << setw(6) << a[i][j]; } cout << "\n\n" << "Phan tu max:" << '\n' ; cout << "co gia tri = " << setw(6) << smax; cout << "\nTai hang " << imax << " cot " << jmax ; getch(); } Bài 6. Cấu trúc, hợp và kiểu liệt kê 6.1. Tên sau từ khoá struct được xem như tên kiểu cấu trúc Trong C++ một kiểu cấu trúc cũngg được định nghĩa như C theo mẫu: struct Tên_kiểu_ct { // Khai báo các thành phần của cấu trúc } ; Sau đó để khai báo các biến, mảng cấu trúc, trong C dùng mẫu sau: struct Tên_kiểu_ct danh sách biến, mảng cấu trúc ; Như vậy trong C, tên viết sau từ khoá struct chưa phải là tên kiểu và chưa có thể dùng để khai báo. Trong C++ xem tên viết sau từ khoá struct là tên kiểu cấu trúc và có thể dùng nó để khai báo. Như vậy để khai báo các biến, mảng cấu trúc trong C++ , ta có thể dùng mẫu sau: Tên_kiểu_ct danh sách biến, mảng cấu trúc ; Ví dụ sau sẽ: Định nghĩa kiểu cấu trúc TS (thí sinh) gồm các thành phần : ht (họ tên), sobd (số báo danh), dt (điểm toán), dl (điểm lý), dh (điểm hoá) và td (tổng điểm), sau đó khai báo biến cấu trúc h và mảng cấu trúc ts. struct TS { char ht [25]; long sobd; float dt, dl, dh, td; } ; TS h, ts[1000] ; 6.2. Tên sau từ khoá union được xem như tên kiểu hợp GS: Phạm Văn Ất 15 tenshi3003@gmail.com
  16. Lập Trình Hướng Đối Tượng Với C++ Trong C++ một kiểu hợp (union) cũngg được định nghĩa như C theo mẫu: union Tên_kiểu_hợp { // Khai báo các thành phần của hợp } ; Sau đó để khai báo các biến, mảng kiểu hợp , trong C dùng mẫu sau: union Tên_kiểu_hợp danh sách biến, mảng kiểu hợp ; Như vậy trong C, tên viết sau từ khoá union chưa phải là tên kiểu và chưa có thể dùng để khai báo. Trong C++ xem tên viết sau từ khoá union là tên kiểu hợp và có thể dùng nó để khai báo. Như vậy để khai báo các biến, mảng kiểu hợp, trong C++ có thể dùng mẫu sau: Tên_kiểu_hợp danh sách biến, mảng kiểu hợp ; 6.3. Các union không tên Trong C++ cho phép dùng các union không tên dạng: union { // Khai báo các thành phần } ; Khi đó các thành phần (khai báo trong union) sẽ dùng chung một vùng nhớ. Điều này cho phép tiết kiệm bộ nhớ và cho phép dễ dàng tách các byte của một vùng nhớ. Ví dụ nếu các biến nguyên i , biến ký tự ch và biến thực x không đồng thời sử dụng thì có thể khai báo chúng trong một union không tên như sau: union { int i ; char ch ; float x ; } ; Khi đó các biến i , ch và f sử dụng chung một vùng nhớ 4 byte. Xét ví dụ khác, để tách các byte của một biến unsigned long ta dùng union không tên sau: union { unsigned long u ; unsigned char b[4] ; }; Khí đó nếu gán u = 0xDDCCBBAA; // Số hệ 16 thì : b[0] = 0xAA b[1] = 0xBB b[2] = 0xCC b[3] = 0xDD GS: Phạm Văn Ất 16 tenshi3003@gmail.com
  17. Lập Trình Hướng Đối Tượng Với C++ 6.4. Kiểu liệt kê (enum) + Cũngg giống như cấu trúc và hợp, tên viết sau từ khoá enum được xem là kiểu liệt kê và có thể dùng để khai báo, ví dụ: enum MAU { xanh, do, tim, vang } ; // Định nghĩa kiểu MAU MAU m, dsm[10] ; // Khai báo các biến, mảng kiểu MAU + Các giá trị kiểu liệt kê (enum) là các số nguyên. Do đó có thể thực hiện các phép tính trên các giá trị enum, có thể in các giá trị enum, có thể gán giá trị enum cho biến nguyên, ví dụ: MAU m1 , m2 ; int n1, n2 ; m1 = tim ; m2 = vàng ; n1 = m1 ; // n1 = 2 n2 = m1 + m2 ; // n2 = 5 printf (“\n %d “ , m2 ); // in ra số 3 + Không thể gán trực tiếp một giá trị nguyên cho một biến enum mà phải dùng phép ép kiểu, ví dụ: m1 = 2 ; // lỗi m1 = MAU(2) ; // đúng Bài 7. Cấp phát bộ nhớ 7.1. Trong C++ có thể sử dụng các hàm cấp phát bộ nhớ động của C như: hàm malloc để cấp phát bộ nhớ, hàm free để giải phóng bộ nhớ được cấp phát. 7.2. Ngoài ra trong C++ cũng đưa thêm toán tử new để cấp phát bộ nhớ và toán tử delete để giải phóng bộ nhớ được cấp phát bởi new 7.3. Cách dùng toán tử new để cấp phát bộ nhớ như sau: + Trước hết cần khai báo một con trỏ để chứa địa chỉ vùng nhớ sẽ được cấp phát: Kiểu *p; ở đây Kiểu có thể là: - các kiểu dữ liệu chuẩn của C++ như int , long, float , double, char , - các kiểu do lập trình viên định nghĩa như: mảng, hợp, cấu trúc, lớp, + Sau đó dùng toán tử new theo mẫu: p = new Kiểu ; // Cấp phát bộ nhớ cho một biến (một phần tử) p = new Kiểu[n] ; //Cấp phát bộ nhớ cho n phần tử Ví dụ để cấp phát bộ nhớ cho một biến thực ta dùng câu lệnh sau: float *px = new float ; Để cấp phát bộ nhớ cho 100 phần tử nguyên ta dùng các câu lệnh: int *pn = new int[100] ; for (int i=0 ; i < 100 ; ++i ) GS: Phạm Văn Ất 17 tenshi3003@gmail.com
  18. Lập Trình Hướng Đối Tượng Với C++ pn[i] = 20*i ; // Gán cho phần tử thứ i 7.4. Hai cách kiểm tra sự thành công của new Khi dùng câu lệnh: Kiểu *p = new Kiểu[n] ; hoặc câu lệnh: Kiểu *p = new Kiểu ; để cấp phát bộ nhớ sẽ xuất hiện một trong 2 trường hợp: thành công hoặc không thành công. Nếu thành cụng thì p sẽ chứa địa chỉ đầu vùng nhớ được cấp phát. Nếu không thành cụng thì p = NULL. Đoạn chương trình sau minh hoạ cách kiểm tra lỗi cấp phát bộ nhớ: double *pd ; int n ; cout > n ; pd = new double[n] ; if (pd==NULL) { cout > n ; pd = new double[n] ; // Khi xẩy ra lỗi sẽ gọi hàm kiểm_tra_new GS: Phạm Văn Ất 18 tenshi3003@gmail.com
  19. Lập Trình Hướng Đối Tượng Với C++ Chú ý: Có thể dùng lệnh gán để gán tên hàm xử lý lỗi cho con trỏ _new_handler như trong đoạn chương trình trên, hoặc dựng hàm: set_new_handler(Tên hàm) ; (xem các chương trình minh hoạ bên dưới) 7.5. Toán tử delete dùng để giải phóng vùng nhớ được cấp phát bởi new Cách dùng như sau: delete p ; // p là con trỏ dùng trong new Ví dụ: float *px ; px = new float[2000] ; // Cấp phát bộ nhớ cho 2000 phần tử thực // Sử dụng bộ nhớ được cấp phát delete px ; // giải phóng bộ nhớ 7.6. Hai chương trình minh hoạ Chương trình thứ nhất minh hoạ cách dựng new để cấp phát bộ nhớ chứa n thí sinh. Mỗi thí sinh là một cấu trúc gồm các trường ht (họ tên), sobd (số báo danh) và td (tổng điểm). Chương trình sẽ nhập n, cấp phát bộ nhớ chứa n thớ sinh, kiểm tra lỗi cấp phát bộ nhớ (dựng cách 1), nhập n thớ sinh, sắp xếp thớ sinh theo thứ tự giảm của tổng điểm, in danh sách thí sinh sau khi sắp xếp, và cuối cùng là giải phóng bộ nhớ đó cấp phát. #include #include #include #include struct TS { char ht[20]; long sobd; float td; } ; void main(void) { TS*ts ; int n; cout > n; ts = new TS[n+1]; if(ts==NULL) { cout << "\nLoi cap phat bo nho " ; getch(); exit(0); } GS: Phạm Văn Ất 19 tenshi3003@gmail.com
  20. Lập Trình Hướng Đối Tượng Với C++ for (int i=1;i > ts[i].sobd ; cout > ts[i].td ; } for (i=1;i #include #include #include int k; void loi_bo_nho(void) { cout << "\nLoi bo nho khi cap phat bo nho cho q[" << k << "]"; getch(); exit(0); } void main() GS: Phạm Văn Ất 20 tenshi3003@gmail.com
  21. Lập Trình Hướng Đối Tượng Với C++ { double *q[100] ; long n; clrscr(); set_new_handler(loi_bo_nho) ; // _new_handler=loi_bo_nho; n=10000; for ( k=0;k<100;++k) q[k] = new double[n]; cout << "Khong loi"; getch(); } Bài 8. Các hàm trong C++ Trong C++ có rất nhiều mở rộng, cải tiến về hàm làm cho việc xây dựng và sử dụng hàm rất tiện lợi. Điều này sẽ trình bầy kỹ trong chương sau. Trong mục này chỉ thống kê một số điểm mới về hàm mà C++ đưa vào. 8.1. Đối kiểu tham chiếu Trong C, để nhận kết quả của hàm cần dùng đối con trỏ, làm cho việc xây dựng cũngg như sử dụng hàm khá phiền phức. Trong C++ đưa vào đối kiểu tham chiếu (giống như PASCAL) dùng để chứa kết quả của hàm, khiến cho việc tạo lập cũngg như sử dụng hàm đơn giản hơn. 8.2. Đối tham chiếu const Đối tham chiếu có đặc điểm là các câu lệnh trong thân hàm có thể truy nhập tới và dễ dàng làm cho giá trị của nó thay đổi. Nhiều khi ta muốn dùng đối kiểu tham chiếu chỉ để tăng tốc độ trao đổi dữ liệu giữa các hàm , không muốn dùng nó để chứa kết quả của hàm. Khi đó có thể dùng đối tham chiếu const để bảo toàn giá trị của đối trong thân hàm. 8.3. Đối có giá trị mặc định Trong nhiều trương hợp người dùng viết một lời gọi hàm nhưng cũng chưa biết nên chọn giá trị nào cho các đối . Để khắc phục khó khăn này, C++ đưa ra giải pháp đối có giá trị mặc định. Khi xây dựng hàm, ta gán giá trị mặc định cho một số đối. Người dùng nếu không cung cấp giá trị cho các đối này, thì hàm sẽ dựng giỏ trị mặc định. 8.4. Hàm on line Đối với một đoạn chương trình nhỏ (số lệnh không lớn) thì việc thay các đoạn chương trình này bằng các lời gọi hàm sẽ làm cho chương trình gọn nhẹ đôi chút nhưng làm tăng thời gian máy. Trong các trường hợp này có thể dùng hàm trực tuyến (on line) vừa giảm kích thước chương trình nguồn, vừa không làm tăng thời gian chạy máy. 8.5. Các hàm trùng tên (định nghĩa chồng các hàm) Để lấy giá trị tuyệt đối của một số, trong C cần lập ra nhiều hàm với tên khác nhau, ví dụ abs cho số nguyên, fabs cho số thực, labs cho số nguyên dài, cabs cho số phức. Điều này rừ ràng gõy phiền toỏi cho người sử dụng. Trong C++ cho phép xây dựng các hàm trùng tên nhưng khác nhau về kiểu đối. Như vậy chỉ cần lập một hàm để lấy giá trị tuyệt đối cho nhiều kiểu dữ liệu khác nhau. 8.6. Định nghĩa chồng toán tử GS: Phạm Văn Ất 21 tenshi3003@gmail.com
  22. Lập Trình Hướng Đối Tượng Với C++ Việc dựng các phộp toán thay cho một lời gọi hàm rừ ràng làm cho chương trình ngắn gọn, sáng sủa hơn nhiều. Ví dụ để thực hiện phép cộng 2 ma trận nếu dùng phép cộng và viết: C = A + B ; thì rất gần với toán học. Trong C++ cho phộp dựng các phộp toán chuẩn để đặt tên cho các hàm (gọi là định nghĩa chồng toán tử). Sau đó có thể thay lời gọi hàm bằng các phép toán như nói ở trên. Như vậy một phép toán mang nhiều ý nghĩa, vớ dụ phộp + có thể hiểu là cộng 2 số nguyên, 2 số thực hoặc 2 ma trận. C++ sẽ căn cứ vào kiểu của các số hạng mà quyết định chọn phép cộng cụ thể. GS: Phạm Văn Ất 22 tenshi3003@gmail.com
  23. Lập Trình Hướng Đối Tượng Với C++ chương 2 Hàm trong C++ Chương này trình bầy những khả năng mới của C++ trong việc xây dựng và sử dụng hàm. Đó là: + Kiểu tham chiếu và việc truyền dữ liệu cho hàm bằng tham chiếu. + Đối tham chiếu hằng (const) + Đối có giá trị mặc định + Hàm trực tuyến + Việc định nghĩa chồng các hàm + Việc định nghĩa chồng các toán tử 1. Biến tham chiếu (Reference variable) 1.1. Hai loại biến dùng trong C Trước khi nói đến biến tham chiếu, chúng ta nhắc lại 2 loại biến gặp trong C là: Biến giá trị dùng để chứa dữ liệu (nguyên, thực, ký tự, ) Biến con trỏ dùng để chứa địa chỉ Các biến này đều được cung cấp bộ nhớ và có địa chỉ. Ví dụ câu lệnh khai báo: double x , *px; sẽ tạo ra biến giá trị kiểu double x và biến con trỏ kiểu double px. Biến x có vùng nhớ 8 byte, biến px có vùng nhớ 4 byte (nếu dùng mô hình Large). Biến x dùng để chứa giá trị kiểu double, ví dụ lệnh gán: x = 3.14; sẽ chứa giá trị 3.14 vào biễn x. Biến px dùng để chứa địa chỉ của một biến thực, ví dụ câu lệnh: px = &x ; sẽ lưu trữ địa chỉ của biễn x vào con trỏ px. 1.2. Biến tham chiếu Trong C++ cho phép sử dụng loại biến thứ ba là biến tham chiếu. So với 2 loại biến quen biết nói trên, thì biến này có những đặc điểm sau: + Biến tham chiếu không được cấp phát bộ nhớ, không có địa chỉ riêng. + Nó dùng làm bí danh cho một biến (kiểu giá trị) nào đó và nó sử dụng vùng nhớ của biến này. Ví dụ câu lệnh: float u, v, &r = u ; tạo ra các biến thực u, v và biến tham chiếu thực r. Biến r không được cấp phát bộ nhớ, nó là một tên khác (bí danh) của u và nó dùng chung vùng nhớ của biến u. Thuật ngữ: Khi r là bí danh (alias) của u thì ta nói r tham chiếu đến biến u. Như vậy 2 thuật ngữ trên được hiểu như nhau. ý nghĩa: Khi r là bí danh của u thì r dùng chung vùng nhớ của u, dó đó : + Trong mọi câu lệnh, viết u hay viết r đều có ý nghĩa như nhau, vì đều truy nhập đến cùng một vùng nhớ. + Có thể dùng biến tham chiếu để truy nhập đến một biến kiểu giá trị. Ví dụ: GS: Phạm Văn Ất 23 tenshi3003@gmail.com
  24. Lập Trình Hướng Đối Tượng Với C++ int u, v, &r = u; r = 10 ; // u=10 cout #include struct TS { char ht[25]; float t,l,h,td; } ; void main() GS: Phạm Văn Ất 24 tenshi3003@gmail.com
  25. Lập Trình Hướng Đối Tượng Với C++ { TS ts[10],&h=ts[1]; // h tham chiếu đến ts[1] cout > h.t >> h.l >> h.h ; h.td = h.t + h.l + h.h ; cout << "\n Ho ten: " << ts[1].ht; cout << "\n Tong diem: " << ts[1].td; getch(); } 1.3. Hằng tham chiếu (const) Hằng tham chiếu được khai báo theo mẫu: int n = 10 ; const int &r = n; Cũng giống như biến tham chiếu, hằng tham chiếu có thể tham chiếu đến một biến hoặc một hằng. Ví dụ: int n = 10 ; const int &r = n ; // Hằng tham chiếu r tham chiếu đến biến n const int &s=123 ; //Hằng tham chiếu s tham chiếu đến hằng 123 Sự khác nhau giữa biến và hằng tham chiếu ở chỗ: Không cho phép dùng hằng tham chiếu để làm thay đổi giá trị của vùng nhớ mà nó tham chiếu. Ví dụ: int y = 12, z ; const int &py=y; // Hằng tham chiếu py tham chiếu đến biến y y++; // Đúng z = 2*py ; // Đúng z = 26 cout << y <<" "<< py; // In ra: 13 13 py=py+1; // Sai, Trình biên dịch thông báo lỗi: // Cannot modify a const object Cách dùng: Hằng tham chiếu cho phép sử dụng giá trị chứa trong một vùng nhớ, nhưng không cho phép thay đổi giá trị này. Hằng tham chiếu thường được sử dụng làm đối của hàm để cho phép hàm sử dụng giá trị của các tham số trong lời gọi hàm, nhưng tránh không làm thay đổi giá trị của các tham số. 2. Truyền giá trị cho hàm theo tham chiếu 2.1. Hàm trong C Trong C chỉ có một cách truyền dữ liệu cho hàm theo giá trị : + Cấp phát vùng nhớ cho các đối. + Gán giá trị các tham số trong lời gọi hàm cho các đối sau đó hàm làm việc trên vùng nhớ của các đối chứ không liên quan gì đến các tham số. GS: Phạm Văn Ất 25 tenshi3003@gmail.com
  26. Lập Trình Hướng Đối Tượng Với C++ Như vây chương trình sẽ tạo ra các bản sao (các đối) của các tham số và hàm sẽ thao tác trên các bản sao này, chứ không làm việc trực tiếp với các tham số. Phương pháp này có 2 nhược điểm chính: Tốn kém về thời gian và bộ nhớ vì phải tạo ra các bản sao. Không thao tác trực tiếp trên các tham số, vì vậy không làm thay đổi được giá trị các tham số. 2.2. Truyền giá trị cho hàm theo tham chiếu Trong C++ cung cấp thêm cách truyền dữ liệu cho hàm theo tham chiếu bằng cách dùng đối là biến tham chiếu hoặc đối là hằng tham chiếu. Cách này có ưu điểm: Không cần tạo ra các bản sao của các tham số, do đó tiết kiệm bộ nhớ và thời gian chạy máy. Hàm sẽ thao tác trực tiếp trên vùng nhớ của các tham số, do đó dễ dàng thay đổi giá trị các tham số khi cần. 2.3. Mối quan hệ giữa đối và tham số trong lời gọi hàm Nếu đối là biến hoặc hằng tham chiếu kiểu K thì tham số (trong lời gọi hàm) phải là biến hoặc phần tử mảng kiểu K. Ví dụ: + Đối là biến hoặc hằng tham chiếu kiểu double, thì tham số là biến hoặc phần tử mảng kiểu double + Đối là biến hoặc hằng tham chiếu kiểu cấu trúc, thì tham số là biến hoặc phần tử mảng kiểu cấu trúc 2.4. Các chương trình minh hoạ /* Chương trình sau được tổ chức thành 3 hàm: Nhập dẫy số double Hoán vị 2 biến double Sắp xếp dẫy số double theo thứ tự tăng dần Chương trình sẽ nhập một dẫy số và in dẫy sau khi sắp xếp */ #include #include #include void nhapds(double *a, int n) { for (int i=1; i > a[i] ; } } void hv(double &x, double &y) { double tg=x; x=y; y= tg; } void sapxep(double * a, int n) GS: Phạm Văn Ất 26 tenshi3003@gmail.com
  27. Lập Trình Hướng Đối Tượng Với C++ { for (int i=1; i a[j]) hv(a[i],a[j]); } void main() { double x[100]; int i, n; cout > n; nhapds(x,n); sapxep(x,n); for (i=1;i #include #include struct TS { char ht[20]; float t,l,h,td; } ; void ints(const TS &ts) { cout << setiosflags(ios::showpoint) << setprecision(1) ; cout << "\nHo ten: " << setw(20) << ts.ht << setw(6) << ts.td ; } GS: Phạm Văn Ất 27 tenshi3003@gmail.com
  28. Lập Trình Hướng Đối Tượng Với C++ void nhapsl(TS *ts,int n) { for (int i=1;i > ts[i].t >> ts[i].l >> ts[i].h ; ts[i].td = ts[i].t + ts[i].l + ts[i].h ; } } void hvts(TS &ts1, TS &ts2) { TS tg=ts1; ts1=ts2; ts2=tg; } void sapxep(TS *ts,int n) { for (int i=1;i > n ; nhapsl(ts,n); sapxep(ts,n) ; float dc; cout > dc; cout = dc) GS: Phạm Văn Ất 28 tenshi3003@gmail.com
  29. Lập Trình Hướng Đối Tượng Với C++ ints(ts[i]); else break; getch(); } /* Chương trình sau gồm các hàm: Nhập một ma trận thực cấp mxn In một ma trận thực dưới dạng bảng Tìm phần tử lớn nhất và phần tử nhỏ nhất của dẫy số thưc; Chương trình sẽ nhập một ma trận, in ma trận vừa nhập và in các phần tử lớn nhất và nhỏ nhất trên mỗi hàng của ma trận */ #include #include #include #include void nhapmt(float a[20][20], int m, int n) { for (int i=1 ; i > a[i][j] ; } } void inmt(float a[20][20], int m, int n) { cout << setiosflags(ios::showpoint) << setprecision(1); for (int i=1 ; i<= m ; ++i) for (int j=1; j<= n ; ++j) { if (j==1) cout << "\n" ; cout << setw(6) << a[i][j] ; } } void maxminds(float *x, int n,int &vtmax, int &vtmin) { vtmax = vtmin = 1 ; for (int i=2; i<=n ; ++i) GS: Phạm Văn Ất 29 tenshi3003@gmail.com
  30. Lập Trình Hướng Đối Tượng Với C++ { if (x[i] > x[vtmax]) vtmax = i; if (x[i] > m >> n; nhapmt(a,m,n); clrscr(); inmt(a,m,n); float *p = (float*)a; int vtmax, vtmin; for (int i=1;i #include int z ; int &f() // Hàm trả về một bí danh của biến toàn bộ z { return z; } void main(void) GS: Phạm Văn Ất 30 tenshi3003@gmail.com
  31. Lập Trình Hướng Đối Tượng Với C++ { f()=50; // z = 50 cout #include struct TS { char ht[25]; float t,l,h,td; }; TS ts; TS &f() { return ts; } void main() { TS &h=f(); // h tham chiếu đến biến ts cout > h.t >> h.l >> h.h ; h.td = h.t + h.l + h.h ; cout #include #include struct TS GS: Phạm Văn Ất 31 tenshi3003@gmail.com
  32. Lập Trình Hướng Đối Tượng Với C++ { char ht[25]; float t,l,h,td; }; TS *ts; void cap_phat_bo_nho_nhapsl(int n) { ts = new TS[n+1] ; if (ts==NULL) { cout > h.t >> h.l >> h.h ; h.td = h.t + h.l + h.h ; } } TS &f(int i, int n) // Cho bi danh ts[i] { if (i n) { cout > n; cap_phat_bo_nho_nhapsl(n); while (1) { cout << "\nCan xem thi sinh thu may: " ; GS: Phạm Văn Ất 32 tenshi3003@gmail.com
  33. Lập Trình Hướng Đối Tượng Với C++ cout > i; TS &h=f(i,n); cout << "\n Ho ten: " << h.ht; cout << "\n Tong diem: " << h.td; } } 4. Đối có giá trị mặc định 4.1. Thế nào là đối mặc định Một trong các khả năng mạnh của C++ là nó cho phép xây dựng hàm với các đối có giá trị mặc định. Thông thường số tham số trong lời gọi hàm phải bằng số đối của hàm. Mỗi đối sẽ được khởi gán giá trị theo tham số tương ứng của nó. Trong C++ cho phép tạo giá trị mặc định cho các đối. Các đối này có thể có hoặc không có tham số tương ứng trong lời gọi hàm. Khi không có tham số tương ứng, đối được khởi gán bởi giá trị mặc định. Ví dụ hàm delay với đối số mặc định được viết theo một trong 2 cách sau: Cách 1 (Không khai báo nguyên mẫu): void delay(int n=1000) { for (int i=0 ; i<n ; ++i) ; } Cách 2 (Có khai báo nguyên mẫu): void delay(int n=1000) ; void delay(int n) { for (int i=0 ; i<n ; ++i) ; } Cách dùng: + Cung cấp giá trị cho đối n (Có tham số trong lời gọi hàm) delay(5000) ; // Đối n = 5000 + Sử dụng giá trị mặc định của đối (Không có tham số trong lời gọi) delay() ; // Đối n = 1000 4.2. Quy tắc xây dựng hàm với đối mặc định + Các đối mặc định cần phải là các đối cuối cùng tính từ trái sang phải. Giả sử có 5 đối theo thứ tự từ trái sang phải là d1, d2, d3, d4, d5 Khi đó: nếu một đối mặc định thì phải là d5 GS: Phạm Văn Ất 33 tenshi3003@gmail.com
  34. Lập Trình Hướng Đối Tượng Với C++ nếu hai đối mặc định thì phải là d4, d5 nếu ba đối mặc định thì phải là d3, d4, d5 Các ví dụ sai: d3 và d5 mặc định (khi đó d4 cũng phải mặc định) d3 và d4 mặc định (khi đó d5 cũng phải mặc định) + Khi xây dựng hàm, nếu sử dụng khai báo nguyên mẫu, thì các đối mặc định cần được khởi gán trong nguyên mẫu, ví dụ: // Khởi gán giá trị cho 3 đối mặc định d3, d4 và d5) void f(int d1, float d2, char *d3=”HA NOI”, int d4 = 100, double d5=3.14) ; void f(int d1, float d2, char *d3, int d4, double d5) { // Các câu lệnh trong thân hàm } Không được khởi gán lại cho các đối mặc định trong dòng đầu của định nghĩa hàm. Nếu vi phạm điều này thì Chương trình dịch sẽ thông báo lỗi. + Khi xây dựng hàm, nếu không khai báo nguyên mẫu, thì các đối mặc định được khởi gán trong dòng đầu của định nghĩa hàm, ví dụ: // Khởi gán giá trị cho 3 đối mặc định d3, d4 và d5) void f(int d1, float d2, char *d3=”HA NOI”, int d4 = 100, double d5=3.14) { // Các câu lệnh trong thân hàm } + Giá trị dùng để khởi gán cho đối mặc đinh Có thể dùng các hằng, các biến toàn bộ, các hàm để khởi gán cho đối mặc định, ví dụ: int MAX = 10000; void f(int n, int m = MAX, int xmax = getmaxx(), int ymax = getmaxy() ) ; 4.3. Cách sử dụng hàm có đối mặc định Lời gọi hàm cần viết theo quy định sau: Các tham số thiếu vắng trong lời gọi hàm phải tương ứng với các đối mặc định cuối cùng (tính từ trái sang phải). Nói cách khác: Đã dùng giá trị mặc định cho một đối (tất nhiên phải là đối mặc định) thì cũng phải sử dụng giá trị mặc định cho các đối còn lại. Ví dụ với hàm có 3 đối mặc định: void f(int d1, float d2, char *d3=”HA NOI”, int d4 = 100, double d5=3.14) ; GS: Phạm Văn Ất 34 tenshi3003@gmail.com
  35. Lập Trình Hướng Đối Tượng Với C++ Thì các lời gọi sau là đúng: f(3,3.4,”ABC”,10,1.0) ; // Đầy đủ tham số f(3,3.4,”ABC”) ; // Thiếu 2 tham số cuối f(3,3.4) ; // Thiếu 3 tham số cuối Các lời gọi sau là sai: f(3) ; // Thiếu tham số cho đối không mặc định d2 f(3,3.4, ,10) ; // Đã dùng giá trị mặc định cho d3, thì cũng // phải dùng giá trị mặc định cho d4 và d5 4.4. Các ví dụ Hàm ht (bên dưới) dùng để hiển thị chuỗi ký tự dc trên n dòng màn hình. Các đối dc và n đều có giá trị mặc định. #include #include void ht(char *dc="HA NOI",int n=10) ; void ht(char *dc , int n ) { for (int i=0;i #include void hiendc(char *str, int x=getmaxx()/2, int y = getmaxy()/2, int m=RED); void hiendc(char *str, int x,int y, int m) { int mau_ht = getcolor(); // Luu mau hien tai setcolor(m); outtextxy(x,y,str) ; setcolor(mau_ht); // Khoi phuc mau hien tai GS: Phạm Văn Ất 35 tenshi3003@gmail.com
  36. Lập Trình Hướng Đối Tượng Với C++ } void main() { int mh=0, mode=0; initgraph(&mh,&mode,""); setbkcolor(BLUE); hiendc("HELLO"); // HELLO mầu đỏ giữa màn hình hiendc("CHUC MUNG",1,1); // CHUC MUNG mầu đỏ tại vị // trí (1,1) hiendc("CHAO",1,400,YELLOW); // CHAO mầu vàng tại vị // trí (1,400) getch(); } Ví dụ dưới đây trình bầy hàm tính tích phân xác định gồm 3 đối: f là hàm cần tính tích phân, a và b là các cận dưới và trên (a #include #include #include double bp(double x); double tp( double (*f)(double)=bp,double a=0.0, double b=1.0) ; double bp(double x) { return x*x; } double tp(double (*f)(double), double a, double b ) { int n=1000; double s=0.0, h=(b-a)/n; for (int i=0; i<n ; ++i) s+= f(a+i*h + h) + f(a+i*h ) ; return s*h/2; } void main() { clrscr(); cout << setiosflags(ios::showpoint) << setprecision(2); cout << "\nTich phan tu 0 den 1 cua x*x= " << tp() ; cout << "\nTich phan tu 0 den 1 cua exp(x)= " << tp(exp); GS: Phạm Văn Ất 36 tenshi3003@gmail.com
  37. Lập Trình Hướng Đối Tượng Với C++ cout #include void main() { int s ; s = f(5,6); GS: Phạm Văn Ất 37 tenshi3003@gmail.com
  38. Lập Trình Hướng Đối Tượng Với C++ cout #include inline void dtcvhcn(int a, int b, int &dt, int &cv) { dt=a*b; cv=2*(a+b); GS: Phạm Văn Ất 38 tenshi3003@gmail.com
  39. Lập Trình Hướng Đối Tượng Với C++ } void main() { int a[20],b[20],cv[20],dt[20],n; cout > n; for (int i=1;i > a[i] >> b[i] ; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); for (i=1;i #include inline void dtcvhcn(int a, int b, int &dt, int &cv) ; void main() { int a[20],b[20],cv[20],dt[20],n; cout > n; for (int i=1;i > a[i] >> b[i] ; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); GS: Phạm Văn Ất 39 tenshi3003@gmail.com
  40. Lập Trình Hướng Đối Tượng Với C++ for (i=1;i #include int f(int a); GS: Phạm Văn Ất 40 tenshi3003@gmail.com
  41. Lập Trình Hướng Đối Tượng Với C++ void f(int a); int f(int a) { return a*a; } void f(int a) { cout << "\n " << a ; } void main() { int b=f(5); f(b); getch(); } 6.3. Sử dụng các hàm định nghĩa chồng Khi gặp một lời gọi, Trình biên dịch sẽ căn cứ vào số lượng và kiểu của các tham số để gọi hàm có đúng tên và đúng bộ đối số tương ứng. Ví dụ: abs(123); // Tham số kiểu int, gọi hàm int abs(int i) ; abs(123L); // Tham số kiểu long, gọi hàm long abs(long l); abs(3.14); //Tham số kiểu double, gọi hàm double abs(double d); Khi không có hàm nào có bộ đối cùng kiểu với bộ tham số (trong lời gọi), thì Trình biên dịch sẽ chọn hàm nào có bộ đối gần kiểu nhất (phép chuyển kiểu dễ dàng nhất). Ví dụ: abs(‘A’) ; // Tham số kiểu char, gọi hàm int abs(int i) ; abs(3.14F); // Tham số kiểu float, gọi hàm double abs(double d); 6.4. Nên sử dụng phép định nghĩa chồng các hàm như thế nào Như đã nói ở trên, khi xây dựng cũng như sử dụng các hàm trùng tên, Trình biên dịch C++ đã phải suy đoán và giải quyết nhiều trường hợp khá nhập nhằng. Vì vậy không nên lạm dụng quá đáng khả năng định nghĩa chồng, vì điều đó làm cho chương trình khó kiểm soát và dễ dẫn đến sai sót. Việc định nghĩa chồng sẽ hiệu quả hơn nếu được sử dụng theo các lời khuyên sau: + Chỉ nên định nghĩa chồng các hàm thực hiện những công việc như nhau nhưng trên các đối tượng có kiểu khác nhau. Ví dụ trong chương trình cần xây dựng các hàm: cộng 2 ma trận vuông kiểu double, cộng 2 ma trận vuông kiểu int, cộng 2 ma trân chữ nhật kiểu double, cộng 2 ma trận chữ nhật kiểu int, thì 4 hàm trên nên định nghĩa chồng (đặt cùng tên). + Nên dùng các phép chuyển kiểu (nếu cần) để bộ tham số trong lời gọi hoàn toàn trùng kiểu với bộ đối số của một hàm được định nghĩa chồng. Vì như thế mới tránh được sự nhập nhằng cho Trình biên dịch và Trình biên dịch sẽ chọn đúng hàm cần gọi. 6.5. Lấy địa chỉ các hàm trùng tên Giả sử có 4 hàm đều có tên là tinh_max được khai báo như sau: int tinh_max(int a, int b, int c) ; // Max của 3 số nguyên double tinh_max(double a, double b, double c); // Max của 3 số // thực GS: Phạm Văn Ất 41 tenshi3003@gmail.com
  42. Lập Trình Hướng Đối Tượng Với C++ int tinh_max(int *a, int n) ; // Max của một dẫy số nguyên double tinh_max(double *a, int n) ; //Max của một dẫy số thực Vấn đề đặt ra là làm thế nào lấy được địa chỉ của mỗi hàm. Câu trả lời như sau: Để lấy địa chỉ của một hàm, ta khai báo một con trỏ hàm có kiểu và bộ đối như hàm cần lấy địa chỉ. Sau đó gán tên hàm cho con trỏ hàm. Ví dụ: int (*f1)(int , int, int ); f1 = tinh_max ; // Lấy địa chỉ của hàm thứ nhất double (*f2)(double , double, double); f2 = tinh_max ; // Lấy địa chỉ của hàm thứ hai int (*f3)(int *, int ); f3 = tinh_max ; // Lấy địa chỉ của hàm thứ ba double (*f4)(double *, int ); f4 = tinh_max ; // Lấy địa chỉ của hàm thứ tư 6.6. Các ví dụ Ví dụ 1: Chương trình giải bài toán tìm max của một dẫy số nguyên và max của một dẫy số thực. Trong chươmg trình có 6 hàm. Hai hàm dùng để nhập dẫy số nguyên và dẫy số thực có tên chung là nhapds. Bốn hàm: tính max 2 số nguyên, tính max 2 số thực, tính max của dẫy số nguyên, tính max của dẫy số thực được đặt chung một tên là max. #include #include #include void nhapds(int *x, int n); void nhapds(double *x, int n); int max(int x, int y); double max(double x, double y); int max(int *x, int n); double max(double *x, int n); void nhapds(int *x, int n) { for (int i=1;i > x[i] ; } } void nhapds(double *x, int n) { for (int i=1;i > x[i] ; } } GS: Phạm Văn Ất 42 tenshi3003@gmail.com
  43. Lập Trình Hướng Đối Tượng Với C++ int max(int x, int y) { return x>y?x:y ; } double max(double x, double y) { return x>y?x:y ; } int max(int *x, int n) { int s=x[1]; for (int i=2;i > ni ; cout > nd ; cout << "Nhap day so thuc\n " ; nhapds(x,nd); maxi = max(a,ni); maxd = max(x,nd); cout << "\nMax cua day nguyen = " << maxi ; cout << "\nMax cua day thuc = " << maxd ; getch(); GS: Phạm Văn Ất 43 tenshi3003@gmail.com
  44. Lập Trình Hướng Đối Tượng Với C++ } Ví dụ 2: Chương trình sau thực hiện phép nhân ma trận: D = A*B*C trong đó A, B là các ma trận vuông, C là ma trận chữ nhật. Trong chương trình có 3 cặp hàm trùng tên để thực hiện 3 nhiệm vụ (nhưng trên 2 đối tượng khác nhau là ma trận vuông và chữ nhật): Nhập ma trận, nhân 2 ma trận và in ma trân. #include #include #include typedef int MT[20][20]; void nhapmt(MT a,char *ten, int m, int n); void inmt(MT a,char *ten, int m, int n); void nhanmt(MT a,MT b, MT c, int m, int n, int p); void nhapmt(MT a,char *ten, int n); void inmt(MT a,char *ten, int n); void nhanmt(MT a,MT b, MT c, int n); void nhapmt(MT a, char *ten, int m, int n) { for (int i=1;i > a[i][j]; } } void nhapmt(MT a,char *ten, int n) { nhapmt(a,ten,n,n) ; } void inmt(MT a,char *ten, int m, int n) { cout << "\nMa tran: " << ten; for (int i=1;i<=m;++i) { cout << "\n" ; for (int j=1;j<=n;++j) cout << setw(6) << a[i][j]; } } void inmt(MT a,char *ten, int n) GS: Phạm Văn Ất 44 tenshi3003@gmail.com
  45. Lập Trình Hướng Đối Tượng Với C++ { inmt(a,ten,n,n) ; } void nhanmt(MT a,MT b, MT c, int m, int n, int p) { for (int i=1;i<=m;++i) for (int j=1;j<=p;++j) { c[i][j]=0; for (int k=1;k<=n;++k) c[i][j] += a[i][k] * b[k][j]; } } void nhanmt(MT a,MT b, MT c, int n) { nhanmt(a,b,c,n,n, n) ; } void main() { MT a,b,c,d; // d= abc MT u; clrscr(); nhapmt(a,"A",2); nhapmt(b,"B",2); nhapmt(c,"C",2,3); nhanmt(a,b,u,2); nhanmt(u,c,d,2,2,3); inmt(a,"A",2); inmt(b,"B",2); inmt(u,"U = A*B",2); inmt(c,"C",2,3); inmt(d,"D = U*C",2,3); getch(); } 7. Định nghĩa chồng các toán tử 7.1. Các phép toán trong C và C++ Trong C và C++ có khá nhiều các phép toán dùng để thực hiện các thao tác trên các kiểu dữ liệu chuẩn. Ví dụ các phép số học: + - * / áp dụng cho các kiểu dữ liệu nguyên, thực. Phép lấy phần dư % áp dụng đối với kiểu nguyên. 7.2. Thực hiện các phép toán trên các kiểu dữ liệu không chuẩn trong C Việc thực hiện các phép toán trên các đối tượng tự định nghĩa (như mảng, cấu trúc) là nhu cầu bắt buộc của thực tế. Chẳng hạn cần thực hiện các phép số học trên số phức, trên phân số, trên đa GS: Phạm Văn Ất 45 tenshi3003@gmail.com
  46. Lập Trình Hướng Đối Tượng Với C++ thức, trên véc tơ, trên ma trận. Để đáp ứng yêu cầu này, ta sử dụng các hàm trong C. Ví dụ sau đây là một chương trình C gồm các hàm nhập phân số, in phân số và thực hiện các phép cộng trừ nhân chia phân số. Chương trình sẽ nhập 5 phân số: p, q, z, u, v và tính phân số s theo công thức: s = (p – q*z)/(u + v) #include #include #include typedef struct { int a,b; } PS; void nhap(PS *p); void in(PS p); int uscln(int x, int y); PS rutgon(PS p); PS cong(PS p1, PS p2); PS tru(PS p1, PS p2); PS nhan(PS p1, PS p2); PS chia(PS p1, PS p2); void nhap(PS *p) { int t, m; printf("\nTu va mau: "); scanf("%d%d", &t, &m); p->a = t; p->b = m; } void in(PS p) { printf(" %d/%d",p.a,p.b); } int uscln(int x, int y) { x=abs(x); y=abs(y); if (x*y==0) return 1; while (x!=y) if (x>y) x-=y; else y-=x; return x; } PS rutgon(PS p) { GS: Phạm Văn Ất 46 tenshi3003@gmail.com
  47. Lập Trình Hướng Đối Tượng Với C++ PS q; int x; x=uscln(p.a,p.b); q.a = p.a / x ; q.b = p.b / x ; return q; } PS cong(PS p1, PS p2) { PS q; q.a = p1.a*p2.b + p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS tru(PS p1, PS p2) { PS q; q.a = p1.a*p2.b - p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS nhan(PS p1, PS p2) { PS q; q.a = p1.a * p2.a ; q.b = p1.b * p2.b ; return rutgon(q); } PS chia(PS p1, PS p2) { PS q; q.a = p1.a * p2.b ; q.b = p1.b * p2.a ; return rutgon(q); } void main() { PS p, q, z, u, v ; PS tu,mau, s; GS: Phạm Văn Ất 47 tenshi3003@gmail.com
  48. Lập Trình Hướng Đối Tượng Với C++ printf("\n Nhap phan so p: "); nhap(&p); printf("\n Nhap phan so q: ");nhap(&q); printf("\n Nhap phan so z: ");nhap(&z); printf("\n Nhap phan so u: ");nhap(&u); printf("\n Nhap phan so v: ");nhap(&v); tu = nhan(q,z); tu = tru(p,tu) ; mau = cong(u,v) ; s = chia(tu,mau); printf(“\n Phan so s = “); in(s); getch(); } Nhận xét: Việc sử dụng các hàm để thực hiện các phép tính không được tự nhiên và tỏ ra dài dòng. Ví dụ để thực hiện một công thức s = (p - q*z)/(u + v) phải dùng 2 biến trung gian và 4 lời gọi hàm. Câu hỏi đặt ra là có cách nào để chỉ cần viết đúng công thức toán học, mà vẫn nhận được kết quả mong muốn hay không? Trong C++ có thể đáp ứng được mong muốn này bằng cách sử dụng các phép toán chuẩn của nó cho các kiểu dữ liệu tự định nghĩa (mảng, cấu trúc, ). Nói cách khác C++ cho phép dùng các phép toán để định nghĩa các hàm, mà ta thường gọi là định nghĩa chồng các toán tử (hay còn gọi: Sự tải bội các toán tử). 7.3. Cách định nghĩa chồng các toán tử 7.3.1.Tên hàm toán tử: Gồm từ khoá operator và tên phép toán, ví dụ: operator+ (định nghĩa chồng phép +) operator- (định nghĩa chồng phép -) 7.3.2. Các đối của hàm toán tử: a. Với các phép toán có 2 toán hạng, thì hàm toán tử cần có 2 đối. Đối thứ nhất ứng với toán hạng thứ nhất, đối thứ hai ứng với toán hạng thứ hai. Do vậy, với các phép toán không giao hoán (như phép-) thì thứ tự đối là rất quan trọng. Ví dụ các hàm toán tử cộng , trừ phân số được khai báo như sau: struct PS { int a; // Tử số int b; // Mẫu số } ; PS operator+(PS p1, PS p2); // p1 + p2 PS operator-(PS p1, PS p2); // p1 - p2 PS operator*(PS p1, PS p2); // p1 * p2 PS operator/(PS p1, PS p2); // p1 / p2 b. Với các phép toán có một toán hạng, thì hàm toán tử có một đối. Ví dụ hàm toán tử đổi dấu ma trận (đổi dấu tất cả các phần tử của ma trận) được khai báo như sau: GS: Phạm Văn Ất 48 tenshi3003@gmail.com
  49. Lập Trình Hướng Đối Tượng Với C++ struct MT { double a[20][20] ; // Mảng chứa các phần tử ma trận int m ; // Số hàng ma trận int n ; // Số cột ma trân } ; MT operator-(MT x) ; 7.3.3. Thân của hàm toán tử: Viết như thân của hàm thông thường. Ví dụ hàm đổi dấu ma trận có thể được định nghĩa như sau: struct MT { double a[20][20] ; // Mảng chứa các phần tử ma trận int m ; // Số hàng ma trận int n ; // Số cột ma trân } ; MT operator-(MT x) { MT y; for (int i=1; i<= m ;++i) for (int j=1; j<= n ;++j) y[i][j] = - x[i][j] ; return y; } 7.4. Cách dùng hàm toán tử Có 2 cách dùng: Cách 1: Dùng như một hàm thông thường bằng cách viết lời gọi. Ví dụ: PS p, q, u, v ; u = operator+(p, q) ; // u = p + q v = operator-(p, q) ; // v = p - q Cách 2: Dùng như phép toán của C++ . Ví dụ: PS p, q, u, v ; u = p + q ; // u = p + q v = p - q ; // v = p - q Chú ý: Khi dùng các hàm toán tử như phép toán của C++ ta có thể kết hợp nhiều phép toán để viết các công thức phức tạp. Cũng cho phép dùng dấu ngoặc tròn để quy định thứ tự thực hiện các phép tính. Thứ tự ưu tiên của các phép tính vẫn tuân theo các quy tắc ban đầu của C++ . Chẳng hạn các phép * và / có thứ ưu tiên cao hơn so với các phép + và - Ví dụ: GS: Phạm Văn Ất 49 tenshi3003@gmail.com
  50. Lập Trình Hướng Đối Tượng Với C++ PS p, q, u, v, s1, s2 ; s1 = p*q - u/v ; // s1 = (p*q) s2 = (p - q)/(u + v) ; // s2 = (p - q)/(u + v) § 8. Các ví dụ về định nghĩa chồng toán tử Ví dụ 1: Trong ví dụ này ngoài việc sử dụng các hàm toán tử để thực hiện 4 phép tính trên phân số, còn định nghĩa chồng các phép toán > để xuất và nhập phân số (xem chi tiết trong chương 7). Hàm operator > được khai báo như sau: istream& operator>> (istream& is,PS &p); Dưới đây sẽ chỉ ra cách xây dựng và sử dụng các hàm toán tử. Chúng ta cũng sẽ thấy việc sử dụng các hàm toán tử rất tự nhiên, ngắn gọn và tiện lợi. Chương trình dưới đây có nội dung như chương trình trong §6.2, nhưng thay các hàm bằng các hàm toán tử. #include #include #include typedef struct { int a,b; } PS; ostream& operator > (istream& is,PS &p); int uscln(int x, int y); PS rutgon(PS p); PS operator+(PS p1, PS p2); PS operator-(PS p1, PS p2); PS operator*(PS p1, PS p2); PS operator/(PS p1, PS p2); ostream& operator > (istream& is,PS &p) { cout > p.a >> p.b ; return is; GS: Phạm Văn Ất 50 tenshi3003@gmail.com
  51. Lập Trình Hướng Đối Tượng Với C++ } int uscln(int x, int y) { x=abs(x); y=abs(y); if (x*y==0) return 1; while (x!=y) if (x>y) x-=y; else y-=x; return x; } PS rutgon(PS p) { PS q; int x; x=uscln(p.a,p.b); q.a = p.a / x ; q.b = p.b / x ; return q; } PS operator+(PS p1, PS p2) { PS q; q.a = p1.a*p2.b + p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS operator-(PS p1, PS p2) { PS q; q.a = p1.a*p2.b - p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS operator*(PS p1, PS p2) { PS q; q.a = p1.a * p2.a ; q.b = p1.b * p2.b ; return rutgon(q); } PS operator/(PS p1, PS p2) { PS q; GS: Phạm Văn Ất 51 tenshi3003@gmail.com
  52. Lập Trình Hướng Đối Tượng Với C++ q.a = p1.a * p2.b ; q.b = p1.b * p2.a ; return rutgon(q); } void main() { PS p, q, z, u, v ; PS s; cout > p >> q >> z >> u >> v ; s = (p - q*z) / (u + v) ; cout > có 2 đối dùng để nhập đa thức Chương trình sẽ nhập 4 đa thức: p, q, r, s. Sau đó tính đa thức: f = -(p+q)*(r-s) Cuối cùng tính giá trị f(x), với x là một số thực nhập từ bàn phím. #include #include #include struct DT { double a[20]; // Mang chua cac he so da thuc a0, a1, int n ; // Bac da thuc } ; ostream& operator > (istream& is,DT &d); DT operator-(const DT& d); DT operator+(DT d1, DT d2); DT operator-(DT d1, DT d2); DT operator*(DT d1, DT d2); double operator^(DT d, double x); // Tinh gia tri da thuc GS: Phạm Văn Ất 52 tenshi3003@gmail.com
  53. Lập Trình Hướng Đối Tượng Với C++ ostream& operator > (istream& is, DT &d) { cout > d.n; cout > d.a[i] ; } return is; } DT operator-(const DT& d) { DT p; p.n = d.n; for (int i=0 ; i d2.n ? d1.n : d2.n ; for (i=0; i 0 && d.a[i]==0.0) i; d.n = i; return d ; GS: Phạm Văn Ất 53 tenshi3003@gmail.com
  54. Lập Trình Hướng Đối Tượng Với C++ } DT operator-(DT d1, DT d2) { return (d1 + (-d2)); } DT operator*(DT d1, DT d2) { DT d; int k, i, j; k = d.n = d1.n + d2.n ; for (i=0; i > p; cout > q; cout > r; cout > s; cout > x; f = -(p+q)*(r-s); g = f^x; cout << "\nDa thuc f " << f ; GS: Phạm Văn Ất 54 tenshi3003@gmail.com
  55. Lập Trình Hướng Đối Tượng Với C++ cout > (istream& is,MT& x); // Nhập ma trận istream& operator>> (istream& is, VT &v); // Nhập véc tơ MT operator+(const MT& x1, const MT& x2); // Cộng 2 ma trận MT operator-(const MT& x1, const MT& x2); // Trừ 2 ma trận MT operator*(const MT& x1, const MT& x2); // Nhân 2 ma trận VT operator*(const MT& x, const VT& v); // Nhân ma trận véc tơ MT operator!(MT x); // Nghịch đảo ma trận Thuật toán cho 8 hàm toán tử đầu tương đối quen thuộc không có gì phải bàn. Để nghịch đảo ma trận có nhiều cách, ở đây chúng ta dùng phương pháp Jordance như sau. Giả sử cần nghịch đảo ma trận x cấp n. Ta dùng thêm ma trận đơn vị y. Sau đó thực hiện đồng thời các phép tính trên cả x và y sao cho x trở thành đơn vị. Kết quả y chính là nghịch đảo của x. Thuật toán được tiến hành trên n bước. Nội dung của bước k (k = 1, ,n) như sau: Tìm chỉ số r ( k <= r <= n) sao cho abs(x[r,k]) = max { abs(x[i,k] với i = k, ,n } Nếu abs(x[r,k]) = 0 thì ma trận không có nghịch đảo và thuật toán kết thúc giữa chừng. Hoán vị hàng k với hàng r trong cả 2 ma trận x và y. Chia hàng k của cả x và y cho tg = x[k,k] (mục đích làm cho x[k,k] = 1). Biến đổi để cột k của x trơ thành véc tơ đơn vị bằng cách làm cho các phần tử x[i,k] = 0 (với i khác k). Muốn vậy ta thực hiện các phép tính sau trên cả x và y: (hàng i) = (hàng i) - x[i,k]*(hàng k) , với mọi i khác k GS: Phạm Văn Ất 55 tenshi3003@gmail.com
  56. Lập Trình Hướng Đối Tượng Với C++ Nội dung chương trình là nhập 4 ma trận X, Y, R, S và véc tơ u. Sau đó tính véc tơ v theo công thức: -1 v = ((X + Y)*(R - S)) u Như sẽ thấy trong hàm main() dưới đây, nhờ các hàm toán tử mà câu lệnh tính v được viết gần giống như công thức toán học nêu trên. /* Chương trình */ #include #include #include #include struct MT { double a[20][20]; // Mang chua cac phan tu ma tran int n ; // Cap ma tran } ; struct VT { double b[20]; // Mang chua cac phan tu cua vec to int n ; // Cap vec to } ; ostream& operator > (istream& is,MT& x); istream& operator>> (istream& is, VT &v); MT operator+(const MT& x1, const MT& x2); MT operator-(const MT& x1, const MT& x2); MT operator*(const MT& x1, const MT& x2); VT operator*(const MT& x, const VT& v); MT operator!(MT x); // Tinh ma tran nghich dao ostream& operator<< (ostream& os, const MT& x) { os << setprecision(2) << setiosflags(ios::showpoint); for (int i=1 ; i<= x.n ; ++i) { os << "\n" ; for (int j=1; j<=x.n; ++j) os << setw(6) << x.a[i][j] ; } os << "\n" ; return os; GS: Phạm Văn Ất 56 tenshi3003@gmail.com
  57. Lập Trình Hướng Đối Tượng Với C++ } ostream& operator > (istream& is, MT& x) { cout > x.n; cout > x.a[i][j] ; } return is; } istream& operator>> (istream& is, VT& v) { cout > v.n; cout > v.b[i] ; } return is; } MT operator+(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep cong vi 2 MT khong cung cap"; getch(); return x1; } else GS: Phạm Văn Ất 57 tenshi3003@gmail.com
  58. Lập Trình Hướng Đối Tượng Với C++ { MT x; int i, j, n; n = x.n = x1.n ; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) x.a[i][j] = x1.a[i][j] + x2.a[i][j] ; return x; } } MT operator-(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep tru vi 2 MT khong cung cap"; getch(); return x1; } else { MT x; int i, j, n; n = x.n = x1.n; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) x.a[i][j] = x1.a[i][j] - x2.a[i][j] ; return x; } } MT operator*(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep nhan vi 2 MT khong cung cap"; getch(); return x1; } else { MT x; int n, i, j,k; GS: Phạm Văn Ất 58 tenshi3003@gmail.com
  59. Lập Trình Hướng Đối Tượng Với C++ n = x.n = x1.n; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) { x.a[i][j] = 0.0 ; for (k=1 ; k<=n; ++k) x.a[i][j] += x1.a[i][k]*x2.a[k][j] ; } return x; } } VT operator*(const MT& x, const VT& v) { if (x.n != v.n) { cout << "\n Cap ma tran khac cap vec to, phep nhan vo nghia"; getch(); return v; } else { VT u; int n; n = u.n = v.n ; for (int i=1; i <=n ; ++i) { u.b[i] = 0; for (int j=1; j<=n; ++j) u.b[i] += x.a[i][j]*v.b[j]; } return u; } } MT operator!(MT x) { MT y; int i,j,k,r,n; double tg; n = y.n = x.n ; for (i=1 ; i<=n ; ++i) GS: Phạm Văn Ất 59 tenshi3003@gmail.com
  60. Lập Trình Hướng Đối Tượng Với C++ for (j=1 ; j abs(x.a[r][k]) ) r = i; if (abs(x.a[r][k]) < 1.0E-8) { cout << "\n Ma tran suy bien, khong co nghich dao" ; getch(); return x; } /* Hoan vi hang r va hang k */ for (j=1 ; j<=n ; ++j) { tg = x.a[k][j]; x.a[k][j] = x.a[r][j]; x.a[r][j] = tg; tg = y.a[k][j]; y.a[k][j] = y.a[r][j]; y.a[r][j] = tg; } /* Chia hang k cho a[k,k] */ tg = x.a[k][k] ; for (j=1 ; j<=n ; ++j) { x.a[k][j] /= tg; y.a[k][j] /= tg; } /* Khu cot k : lam cho a[i,k] = 0 voi i != k */ for (int i=1; i<= n ; ++i) if (i != k) { tg = x.a[i][k] ; for (j=1 ; j<=n ; ++j) { x.a[i][j] -= tg*x.a[k][j] ; GS: Phạm Văn Ất 60 tenshi3003@gmail.com
  61. Lập Trình Hướng Đối Tượng Với C++ y.a[i][j] -= tg*y.a[k][j] ; } } } return y; } void main() { MT x,y,r,s; VT u,v; clrscr(); cout > x; cout > y; cout > r; cout > s; cout > u; v = !((x+y)*(r-s))*u ; cout << "\nVec to v = xu " << v ; getch(); } GS: Phạm Văn Ất 61 tenshi3003@gmail.com
  62. Lập Trình Hướng Đối Tượng Với C++ Chương 3 Khái niệm về lớp Như đã nói ở trên, lớp là khái niệm trung tâm của lập trình hướng đối tượng, nó là sự mở rộng của các khái niệm cấu trúc (struct) của C và bản ghi (record) của PASCAL. Ngoài các thành phần dữ liệu (như cấu trúc), lớp còn chứa các thành phần hàm , còn gọi là phương thức (method) hay hàm thành viên (member function). Cũng giống như cấu trúc, lớp có thể xem như một kiểu dữ liệu. Vì vậy lớp còn gọi là kiểu đối tượng và lớp được dùng để khai báo các biến, mảng đối tượng (như thể dùng kiểu int để khai báo các biến mảng nguyên). Như vậy từ một lớp có thể tạo ra (bằng cách khai báo) nhiều đối tượng (biến, mảng) khác nhau. Mỗi đối tượng có vùng nhớ riêng của mình. Vì vậy cũng có thể quan niệm lớp là tập hợp các đối tượng cùng kiểu. Chương này sẽ trình bầy cách định nghĩa lớp, cách xây dựng phương thức, giải thích về phạm vi truy nhập, sư dụng các thành phần của lớp, cách khai báo biến, mảng cấu trúc, lời gọi tới các phương thức. Bài 1. Định nghĩa lớp 1. Lớp được định nghĩa theo mẫu: class tên_lớp { // Khai báo các thành phần dữ liệu (thuộc tính) // Khai báo các phương thức } ; // Định nghĩa (xây dựng) các phương thức Chú ý: Thuộc tính của lớp có thể là các biến, mảng, con trỏ có kiểu chuẩn (int, float, char, char*, long, ) hoặc kiểu ngoài chuẩn đã định nghĩa trước (cấu trúc, hợp, lớp, ) . Thuộc tính của lớp không thể có kiểu của chính lớp đó, nhưng có thể là kiểu con trỏ lớp này, ví dụ: class A { A x ; // Không cho phép, vì x có kiểu lớp A A *p ; // Cho phép , vì p là con trỏ kiểu lớp A } ; 2. Khi báo các thành phần của lớp (thuộc tính và phương thức) có thể dùng các từ khoá private và public để quy định phạm vi sử dụng của các thành phần. Nếu không quy định cụ thể (không dùng các từ khoá private và public) thì C++ hiểu đó là private. Các thành phần private (riêng) chỉ được sử dụng bên trong lớp (trong thân của các phương thức của lớp). Các hàm không phải là phương thức của lớp không được phép sử dụng các thành phần này. Các thành phần public (công cộng) được phép sử dụng ở cả bên trong và bên ngoài lớp. 3. Các thành phần dữ liệu thường (nhưng không bắt buộc) khai báo là private để bảo đảm tính giấu kín, bảo vệ an toàn dữ liệu của lớp, không cho phép các hàm bên ngoài xâm nhập vào dữ liệu của lớp. 4. Các phương thức thường khai báo là public để chúng có thể được gọi tới (sử dụng) từ các hàm khác trong chương trình. GS: Phạm Văn Ất 62 tenshi3003@gmail.com
  63. Lập Trình Hướng Đối Tượng Với C++ 5. Các phương thức có thể được xây dựng bên ngoài hoặc bên trong định nghĩa lớp. Thông thường, các phương thức ngắn được viết bên trong định nghĩa lớp, còn các phương thức dài thì viết bên ngoài định nghĩa lớp. 6. Trong thân phương thức của một lớp (giả sử lớp A) có thể sử dụng: + Các thuộc tính của lớp A + Các phương thức của lớp A + Các hàm tự lập trong chương trình. Vì phạm vi sử dụng của hàm là toàn chương trình. 7. Giá trị trả về của phương thức có thể có kiểu bất kỳ (chuẩn và ngoài chuẩn) Ví dụ sau sẽ minh hoạ các điều nói trên. Chúng ta sẽ định nghĩa lớp để mô tả và xử lý các điểm trên màn hình đồ hoạ. Lớp được đăt tên là DIEM. + Các thuộc tính của lớp gồm: int x ; // hoành độ (cột) int y ; // tung độ (hàng) int m ; // mầu + Các phương thức: Nhập dữ liệu một điểm Hiển thị một điểm ẩn một điểm Lớp điểm được xây dựng như sau: class DIEM { private: int x, y, m ; public: void nhapsl() ; void hien() ; void an() { putpixel(x, y, getbkcolor()); } } ; void DIEM::nhap() { cout > x >> y ; cout > m ; } void DIEM::hien() { int mau_ht ; GS: Phạm Văn Ất 63 tenshi3003@gmail.com
  64. Lập Trình Hướng Đối Tượng Với C++ mau_ht = getcolor(); putpixel(x, y, m); setcolor(mau_ht); } Qua ví dụ trên có thể rút ra một số điều cần nhớ sau: + Trong cả 3 phương thức (dù viết trong hay viết ngoài định nghĩa lớp) đều được phép truy nhập đến các thuộc tính x, y và m của lớp. + Các phương thức viết bên trong định nghĩa lớp (như phương thức an() ) được viết như một hàm thông thường. + Khi xây dựng các phương thức bên ngoài lớp, cần dùng thêm tên lớp và toán tử phạm vi :: đặt ngay trước tên phương phức để quy định rõ đây là phương thức của lớp nào. Bài 2. Biến, mảng đối tượng Như đã nói ở trên, một lớp (sau khi định nghĩa) có thể xem như một kiểu đối tượng và có thể dùng để khai báo các biến, mảng đối tượng. Cách khai báo biến, mảng đối tượng cũng giống như khai báo biến, mảng các kiểu khác (như int, float, cấu trúc, hợp, ), theo mẫu sau: Tên_lớp danh sách đối ; Tên_lớp danh sách mảng ; Ví dụ sử dụng lớp DIEM ở §1, có thể khai báo các biến, mảng DIEM như sau: DIEM d1, d2, d3 ; // Khai báo 3 biến đối tượng d1, d2, d3 DIEM d[20] ; // Khai báo mảng đối tượng d gồm 20 phần tử Mỗi đối tượng sau khi khai báo sẽ được cấp phát một vùng nhớ riêng để chứa các thuộc tính của chúng. Chú ý rằng sẽ không có vùng nhớ riêng để chứa các phương thức cho mỗi đối tượng. Các phương thức sẽ được sử dụng chung cho tất cả các đối tượng cùng lớp. Như vậy về bộ nhớ được cấp phát thì đối tượng giống cấu trúc. Trong trương hợp này: sizeof(d1) = sizeof(d2) = sizeof(d3) = 3*sizeof(int) = 6 sizeof(d) = 20*6 = 120 Thuộc tính của đối tượng: Trong ví dụ trên, mỗi đối tượng d1, d2, d3 và mỗi phần tử d[i] đều có 3 thuộc tính là x, y, m. Chú ý là mỗi thuộc đều thuộc về một đối tượng, vì vậy không thể viết tên thuộc một cách riêng rẽ mà bao giờ cũng phải có tên đối tượng đi kèm, giống như cách viết trong cấu trúc của C hay bản ghi của PASCAL. Nói cách khác, cách viết thuộc tính của đối tượng như sau: tên_đối_tượng.Tên_thuộc_tính Với các đối tượng d1, d2, d3 và mảng d, có thể viết như sau: d1.x // Thuộc tính x của đối tượng d1 d2.x // Thuộc tính x của đối tượng d2 d3.y // Thuộc tính y của đối tượng d3 d[2].m // Thuộc tính m của phần tử d[2] d1.x = 100 ; // Gán 100 cho d1.x d2.y = d1.x; // Gán d1.x cho d2.y Sử dụng các phương thức GS: Phạm Văn Ất 64 tenshi3003@gmail.com
  65. Lập Trình Hướng Đối Tượng Với C++ Cũng giống như hàm, một phương thức được sử dụng thông qua lời gọi. Tuy nhiên trong lời gọi phương thức bao giờ cũng phải có tên đối tượng để chỉ rõ phương thức thực hiện trên các thuộc tính của đối tượng nào. Ví dụ lời gọi: d1.nhapsl(); sẽ thực hiện nhập số liệu vào các thành phần d1.x, d1.y và d1.m Câu lệnh d[3].nhapsl() ; sẽ thực hiện nhập số liệu vào các thành phần d[3].x, d[3].y và d[3].m Chúng ta sẽ minh hoạ các điều nói trên bằng một chương trình đơn giản sử dụng lớp DIEM để nhập 3 điểm, hiện rồi ẩn các điểm vừa nhập. Trong chương trình đưa vào hàm kd_do_hoa() dùng để khởi động hệ đồ hoạ. #include #include #include class DIEM { private: int x, y, m ; public: void nhapsl(); void an() { putpixel(x,y,getbkcolor()); } void hien(); }; void DIEM::nhapsl() { cout > x >> y ; cout > m ; } void DIEM::hien() { int mau_ht; mau_ht = getcolor() ; putpixel(x,y,m); setcolor(mau_ht); } void kd_do_hoa() GS: Phạm Văn Ất 65 tenshi3003@gmail.com
  66. Lập Trình Hướng Đối Tượng Với C++ { int mh, mode ; mh=mode=0; initgraph(&mh, &mode, ""); } void main() { DIEM d1, d2, d3 ; d1.nhapsl(); d2.nhapsl(); d3.nhapsl(); kd_do_hoa(); setbkcolor(BLACK); d1.hien(); d2.hien(); d3.hien(); getch(); d1.an(); d2.an(); d3.an(); getch(); closegraph(); } Bài 3. Con trỏ đối tượng Con trỏ đối tượng dùng để chứa địa chỉ của biến, mảng đối tượng. Nó được khai báo như sau: Tên_lớp *con trỏ ; Ví dụ dùng lớp DIEM có thể khai báo: DIEM *p1 , *p2, *p3 ; // khai báo 3 con trỏ p1, p2, p3 DIEM d1, d2 ; // Khai báo 2 đối tượng d1, d2 DIEM d[20] ; // Khai báo mảng đối tượng và có thể thực hiện các câu lệnh: p1 = &d2 ; // p1 chứa địa chỉ của d2 , hay p1 trỏ tới d2 p2 = d ; // p2 trỏ tới đầu mảng d p3 = new DIEM // Tạo một đối tượng và chứa địa chỉ của nó // vào p3 Để sử dụng thuộc tính của đối tượng thông qua con trỏ, ta viết như sau: Tên_con_trỏ->Tên_thuộc_tính Chú ý: Nếu con trỏ chứa địa chỉ đầu của mảng, có thể dùng con trỏ như tên mảng. GS: Phạm Văn Ất 66 tenshi3003@gmail.com
  67. Lập Trình Hướng Đối Tượng Với C++ Như vậy sau khi thực hiện các câu lệnh trên thì: p1->x và d2.x là như nhau p2[i].y và d[i].y là như nhau Tóm lại ta có quy tắc sau Quy tắc sử dụng thuộc tính: Để sử dụng một thuộc tính của đối tượng ta phải dùng phép . hoặc phép -> . Trong chương trình, không cho phép viết tên thuộc tính một cách đơn độc mà phải đi kèm tên đối tượng hoặc tên con trỏ theo các mẫu sau: Tên_đối_tượng.Tên_thuộc_tính Tên_con_trỏ->Tên_thuộc_tính Tên_mảng_đối_tượng[chỉ_số].Tên_thuộc_tính Tên_con_trỏ[chỉ_số].Tên_thuộc_tính Chương trình dưới đây cũng sử dụng lớp DIEM (trong §1) để nhập một dẫy điểm, hiển thị và ẩn các điểm vừa nhập. Chương trình dùng một con trỏ kiểu DIEM và dùng toán tử new để tạo ra một dẫy đối tượng. #include #include #include class DIEM { private: int x, y, m ; public: void nhapsl(); void an() { putpixel(x,y,getbkcolor()); } void hien(); }; void DIEM::nhapsl() { cout > x >> y ; cout > m ; } void DIEM::hien() { int mau_ht; mau_ht = getcolor() ; putpixel(x,y,m); GS: Phạm Văn Ất 67 tenshi3003@gmail.com
  68. Lập Trình Hướng Đối Tượng Với C++ setcolor(mau_ht); } void kd_do_hoa() { int mh, mode ; mh=mode=0; initgraph(&mh, &mode, ""); } void main() { DIEM *p; int i, n; cout > n; p = new DIEM[n+1]; for (i=1; i > x >> y ; cout > m ; } Rõ ràng trong phương thức này chúng ta sử dụng tên các thuộc tính x, y và m một cách đơn độc. Điều này có vẻ như mâu thuẫn với quy tắc sử dụng thuộc tính nêu trong mục trước. Song sự thể như sau: GS: Phạm Văn Ất 68 tenshi3003@gmail.com
  69. Lập Trình Hướng Đối Tượng Với C++ C++ sử dụng con trỏ đặc biệt this trong các phương thức. Các thuộc tính viết trong phương thức được hiểu là thuộc một đối tượng do con trỏ this trỏ tới. Như vậy phương thức nhapsl() có thể viết một cách tường minh như sau: void DIEM::nhapsl() { cout > this->x >> this->y ; cout > this->m ; } Từ góc độ hàm số có thể kết luận rằng: Phương thức bao giờ cũng có ít nhất một đối là con trỏ this và nó luôn luôn là đối đầu tiên của phương thức. 4.2. Tham số ứng với đối con trỏ this Xét một lời gọi tới phương thức nhapsl() : DIEM d1; d1.nhapsl() ; Trong trường hợp này tham số truyền cho con trỏ this chính là địa chỉ của d1: this = &d1 Do đó: this->x chính là d1.x this->y chính là d1.y this->m chính là d1.m Như vậy câu lệnh d1.nhapsl() ; sẽ nhập dữ liệu cho các thuộc tính của đối tượng d1. Từ đó có thể rút ra kết luận sau: Tham số truyền cho đối con trỏ this chính là địa chỉ của đối tượng đi kèm với phương thức trong lời gọi phương thức. 4.3. Các đối khác của phương thức Ngoài đối đặc biệt this (đối này không xuất hiện một cách tường minh), phương thức còn có các đối khác được khai báo như trong các hàm. Đối của phương thức có thể có kiểu bất kỳ (chuẩn và ngoài chuẩn). Ví dụ để xây dựng phương thức vẽ đường thẳng qua 2 điểm ta cần đưa vào 3 đối: Hai đối là 2 biến kiểu DIEM, đối thứ ba kiểu nguyên xác định mã mầu. Vì đã có đối ngầm định this là đối thứ nhất, nên chỉ cần khai báo thêm 2 đối. Phương thức có thể viết như sau: void DIEM::doan_thang(DIEM d2, int mau) { int mau_ht; mau_ht = getcolor(); setcolor(mau); line(this->x,this->y,d2.x,d2.y); GS: Phạm Văn Ất 69 tenshi3003@gmail.com
  70. Lập Trình Hướng Đối Tượng Với C++ setcolor(mau_ht); } Chương trình sau minh hoạ các phương thức có nhiều đối. Ta vẫn dùng lớp DIEM nhưng có một số thay đổi: + Bỏ thuộc tính m (mầu) + Bỏ các phương thức hien và an +Đưa vào 4 phương thức mới: ve_ doan_thang (Vẽ đoạn thẳng qua 2 điểm) ve_tam_giac (Vẽ tam giác qua 3 điểm) do_dai (Tính độ dài của đoạn thẳng qua 2 điểm) chu_vi (Tính chu vi tam giác qua 3 điểm) Chương trình còn minh hoạ: + Việc phương thức này sử dụng phương thức khác (phương thức ve_tam_giac sử dụng phương thức ve_doan_thang, phương thức chu_vi sử dụng phương thức do_dai) + Sử dụng con trỏ this trong thân các phương thức ve_tam_giac và chu_vi Nội dung chương trình là nhập 3 điểm, vẽ tam giác có đỉnh là 3 điểm vừa nhập sau đó tính chu vi tam giác. #include #include #include #include #include class DIEM { private: int x, y ; public: void nhapsl(); void ve_doan_thang(DIEM d2, int mau) ; void ve_tam_giac(DIEM d2, DIEM d3,int mau) ; double do_dai(DIEM d2) { DIEM d1 = *this ; return sqrt( pow(d1.x - d2.x,2) + pow(d1.y - d2.y,2) ) ; } double chu_vi(DIEM d2, DIEM d3); }; void DIEM::nhapsl() { cout > x >> y ; } void kd_do_hoa() GS: Phạm Văn Ất 70 tenshi3003@gmail.com
  71. Lập Trình Hướng Đối Tượng Với C++ { int mh, mode ; mh=mode=0; initgraph(&mh, &mode, ""); } void DIEM::ve_doan_thang(DIEM d2, int mau) { setcolor(mau); line(this->x,this->y,d2.x,d2.y); } void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) { (*this).ve_doan_thang(d2,mau); d2.ve_doan_thang(d3,mau); d3.ve_doan_thang(*this,mau); } double DIEM::chu_vi(DIEM d2, DIEM d3) { double s; s= (*this).do_dai(d2) + d2.do_dai(d3) + d3.do_dai(*this) ; return s; } void main() { DIEM d1, d2, d3; char tb_cv[20] ; d1.nhapsl(); d2.nhapsl(); d3.nhapsl(); kd_do_hoa(); d1.ve_tam_giac(d2,d3,15); double s = d1.chu_vi(d2,d3); sprintf(tb_cv,"Chu vi = %0.2f", s); outtextxy(10,10,tb_cv); getch(); closegraph(); } Một số nhận xét về đối của phương thức và lời gọi phương thức + Quan sát nguyên mẫu phương thức: void ve_doan_thang(DIEM d2, int mau) ; sẽ thấy phương thức có 3 đối: GS: Phạm Văn Ất 71 tenshi3003@gmail.com
  72. Lập Trình Hướng Đối Tượng Với C++ Đối thứ nhât là một đối tượng DIEM do this trỏ tới Đối thứ hai là đối tượng DIEM d2 Đối thứ ba là biến nguyên mau Nội dung phương thức là vẽ một đoạn thẳng đi qua các điểm *this và d2 theo mã mầu mau. Xem thân của phương sẽ thấy được nội dung này: void DIEM::ve_doan_thang(DIEM d2, int mau) { setcolor(mau); line(this->x,this->y,d2.x,d2.y); } Tuy nhiên trong trương hợp này, vai trò của this không cao lắm, vì nó được đưa vào chỉ cốt làm rõ đối thứ nhất. Trong thân phương thức có thể bỏ từ khoá this vẫn được. + Vai trò của this trở nên quan trọng trong phương thức ve_tam_giac: void ve_tam_giac(DIEM d2, DIEM d3,int mau) ; Phương thức này có 4 đối là: this trỏ tới một đối tượng kiểu DIEM d2 một đối tượng kiểu DIEM d3 một đối tượng kiểu DIEM mau một biến nguyên Nội dung phương thức là vẽ 3 cạnh: cạnh 1 đi qua *this và d2 cạnh 2 đi qua d2 và d3 cạnh 3 đi qua d3 và *this Các cạnh trên được vẽ nhờ sử dụng phương thức ve_doan_thang: Vẽ cạnh 1 dùng lệnh: (*this).ve_doan_thang(d2,mau) ; Vẽ cạnh 2 dùng lệnh: d2.ve_doan_thang(d3,mau); Vẽ cạnh 3 dùng lệnh: d3.ve_doan_thang(*this,mau); Trong trường này rõ ràng vai trò của this rất quan trọng. Nếu không dùng nó thì công việc trơ nên khó khăn, dài dòng và khó hiểu hơn. Chúng ta hãy so sánh 2 phương án: Phương án dùng this trong phương thức ve_tam_giac: void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) { (*this).ve_doan_thang(d2,mau); d2.ve_doan_thang(d3,mau); d3.ve_doan_thang(*this,mau); } Phương án không dùng this trong phương thức ve_tam_giac: void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) { DIEM d1; GS: Phạm Văn Ất 72 tenshi3003@gmail.com