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

doc 284 trang hoanguyen 5690
Bạn đang xem 20 trang mẫu của tài liệu "Lập trình hướng đối tượng C++ với TC++3.0", để 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:

  • doclap_trinh_huong_doi_tuong_c_voi_tc3_0.doc
  • docphuc_luc_3_0071_70988.doc
  • docphuc_luc_5_7646_70989.doc
  • docphuc_luc_6_5744_70990.doc
  • docphuluc4_0646_70991.doc

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

  1. Chương 1 trỡnh, để thực hiện chương trỡnh C++ cần dựng đuôi CPP để đặt tên C++ và lập trỡnh hướng đối tượng cho tệp chương trỡnh. 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 2. C và C++ - Những sửa đổi cần thiết một chương trỡnh C để biến nó thành - Có thể nói C++ là sự mở rộng (đáng kể) của C. Điều đó có nghĩa một chương trỡnh C++ (chạy được trong môi trường C++) là mọi khả năng, mọi khái niệm trong C đều dùng được trong 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 - Vỡ trong C++ sử dụng gần như toàn bộ các khái niệm, định hướng đối tượng 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 - Những mở rộng của C++ so với C 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 § 1. Làm việc với TC++ 3.0 đó là chương trỡnh C++ (chỉ cần thay đuôi C bằng đuôi CPP). Tuy Các ví dụ trong cuốn sách này sẽ viết và thực hiện trên môi nhiên Trỡnh biờn dịch TC++ yờu cầu mọi hàm chuẩn dùng trong trường TC++ 3.0. Bộ cài đặt TC++ 3.0 gồm 5 đĩa. Sau khi cài đặt chương trỡnh đều phải khai báo nguyên mẫu bằng một câu lệnh (giả sử vào thư mục C:\TC) thỡ trong thư mục TC sẽ gồm các thư #include, trong khi điều này không bắt buộc đối với Trỡnh biờn dịch mục con sau: của TC. C:\TC\BGI chứa các tệp đuôi BGI và CHR Trong C có thể dùng một hàm chuẩn mà bỏ qua câu lệnh #include C:\TC\BIN chứa các tệp chương trỡnh (đuôi EXE) như TC, để khai báo nguyên mẫu của hàm được dùng. Điều này không báo TCC, TLIB, TLINK lỗi khi biên dịch, nhưng có thể dẫn đến kết quả sai khi chạy chương C:\TC\INCLUDE chứa các tệp tiêu đề đuôi H trỡnh. C:\TC\LIB chứa các tệp đuôi LIB, OBJ Ví dụ khi biên dịch chương trỡnh sau trong mụi trường C sẽ Để vào môi trường của TC++ chỉ cần thực hiện tệp chương trỡnh không gặp các dũng cảnh bỏo (Warning) và thụng bỏo lỗi (error). TC trong thư mục C:\TC\BIN . Kết quả nhận được hệ menu chính Nhưng khi chạy sẽ nhận được kết quả sai. của TC++ với mầu nền xanh gần giống như hệ menu quen thuộc của #include TC (Turbo C). Hệ menu của TC++ gồm các menu: File, Edit, void main() 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ũng giống như trong TC, ngoại trừ điểm sau: Tệp chương trỡnh trong hệ float a,b,c,p,s; soạn thảo của TC++ cú đuôi mặc định là CPP cũn trong TC thỡ tệp printf("\nNhap a, b, c "); chương trỡnh luụn có đuôi C. scanf("%f%f%f",&a,&b,&c); Trong TC++ có thể thực hiện cả chương trỡnh C và C++. Để thực p=(a+b+c)/2; hiện chương trỡnh C cần dựng đuôi C để đặt tên cho tệp chương s= sqrt(p*(p-a)*(p-b)*(p-c)); 6 7
  2. printf("\nDien tich = %0.2f",s); Nhiệm vụ chính của việc tổ chức thiết kế chương trỡnh cấu trỳc là getch(); 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 Nếu biên dịch chương trỡnh này trong TC++ sẽ nhận được các 8một dẫy điểm, sau đó tỡm một cặp điểm cách xa nhau nhất. 9 thông báo lỗi sau: Trên tư tưởng của lập trỡnh cấu trỳc cú thể tổ chức chương trỡnh Eror: Funtion ‘sqrt’ should have a prototype như sau: Eror: Funtion ‘getch’ should have a prototype + Sử dụng 2 mảng thực toàn bộ x và y để chứa toạ độ dẫy điẻm Để biến chương trỡnh trờn thành một chương trỡnh C++ cần: + Xây dựng 2 hàm: + Đặt tên chương chường với đuôi CPP Hàm nhapsl dùng để nhập toạ độ n điểm, hàm này có một đối là + Thêm 2 câu lệnh #include để khai báo nguyên mẫu cho các hàm biến nguyên n và được khai báo như sau: sqrt, getch: void nhapsl(int n); #include Hàm do_dai dùng để tính độ dài đoạn thẳng đi qua 2 điểm có chỉ #include số là i và j , nó được khai báo như sau: float do_dai(int i, int j); § 3. Lập trình cấu trúc và lập trình hướng đối tượng Chương trỡnh C cho bài toỏn trờn được viết như sau: 3.1. Phương pháp lập trỡnh cấu trỳc #include - Tư tưởng chính của lập trỡnh cấu trỳc là tổ chức chương trỡnh #include thành cỏc chương trỡnh con. Trong PASCAL cú 2 kiểu chương #include trỡnh con là thủ tục và hàm. Trong C chỉ cú một loại chương trỡnh float x[100],y[100]; con là hàm. float do_dai(int i, int j) 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 return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); 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à void nhapsl(int n) các biến toàn bộ. { Các ngôn ngữ như C, PASCAL, FOXPRO là các ngôn ngữ cho int i; phép triển khai phương pháp lập trỡnh cấu trỳc. for (i=1;i<=n;++i) 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. {
  3. printf("\nNhap toa do x, y cua diem thu %d : ",i); + Khỏi niệm trung tõm của lập trỡnh hướng đối tượng là lớp scanf("%f%f",&x[i],&y[i]); (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ũng 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ũn } gọi là hàm thành viờn (member function). Một lớp được định nghĩa void main() như sau: { int n,i,j,imax,jmax; 10 class Tên_Lớp 11 float d,dmax; { printf("\nSo diem N= "); // Khai báo các thành phần dữ liệu scanf("%d",&n); // Khai báo các phương thức nhapsl(n); }; dmax=do_dai(1,2); imax=1;jmax=2; + Các phương thức có thể được viết (xây dựng) bên trong hoặc for (i=1;i dmax) + 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 dmax=d; 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 imax=i; của phương thức có quyền truy nhập đến các thành phần dữ liệu (của jmax=j; 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ũn 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 printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); phương thức cần chứa tên đối tượng để xác định phương thức thực printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); hiện từ đối tượng nào. getch(); + 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 3.2. Phương pháp lập trỡnh hướng đối tượng đối tượng nhằm thiết kế, xây dựng các lớp.
  4. + Từ khái niệm lớp nẩy sinh hàng loạt khái niệm khác như: #include Thành phần dữ liệu, phương thức, phạm vi, sự đóng gói, hàm tạo, class daydiem 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, public: + Ưu điểm của việc thiết kế hướng đối tượng là tập trung xác định int n; 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 float *x,*y; 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ừ float do_dai(int i, int j) 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ỉ return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); 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 void nhapsl(void); 12 13 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 void daydiem::nhapsl(void) 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: int i; - Biến nguyên n là số điểm của dẫy printf("\nSo diem N= "); - Con trỏ x kiểu thực trỏ đến vùng nhớ chứa dẫy hoành độ scanf("%d",&n); - Con trỏ y kiểu thực trỏ đến vùng nhớ chứa dẫy tung độ x=(float*)malloc((n+1)*sizeof(float)); Các phương thức cần đưa vào theo yêu cầu bài toán gồm: y=(float*)malloc((n+1)*sizeof(float)); - Nhập toạ độ một điểm for (i=1;i #include { #include daydiem p; p.nhapsl();
  5. int n,i,j,imax,jmax; int x,y ; // Khai báo 2 biến thực float d,dmax; 4.2. Khai báo linh hoạt n=p.n; Trong C tất cả các câu lệnh khai báo biến, mảng cục bộ phải đặt dmax=p.do_dai(1,2); imax=1;jmax=2; tại đầu khối. Do vậy nhiều khi, vị trí khai báo và vị trí sử dụng của for (i=1;i dmax) C++ như sau: { #include dmax=d; #include imax=i; #include 14 15 jmax=j; void main() } { } int n; printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); printf("\n So phan tu cua day N= "); printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); scanf("%d",&n); getch(); float *x= (float*)malloc((n+1)*sizeof(float)); } for (int i=1;i x[j]) hoặc trờn một dũng. Ngoài ra trong C++ cũn cho phộp viết ghi chỳ { trờn một dũng sau 2 dấu gạch chộo, vớ dụ: float tg=x[i];
  6. x[i]=x[j]; getch(); x[j]=tg; } } 4.4. Hằng có kiểu printf("\nDay sau khi sap xep\n"); Để tạo ra một hằng có kiểu, ta sử dụng từ khoá const đặt trước for (i=1;i hỡnh đồ hoạ. #include #include void main() #include { #include int n; #include printf("\n So phan tu cua day N= "); typedef struct scanf("%d",&n); { float s=0.0; int x,y; int mau; for (int i=1;i<=n;++i) } DIEM; s += float(i+1)/float(i) ; // Ep kieu theo C++ void main() printf("S= %0.2f ",s);
  7. { Cũn trong C++ một hằng ký tự được xem là giá trị kiểu char và có int mh=0,mode=0; kích thước một byte. Như vậy trong C++ thỡ: initgraph(&mh,&mode,""); sizeof(‘A’) = sizeof(char) = 1 int loi=graphresult(); if (loi) 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ỉ printf("\nLoi do hoa: %s",grapherrormsg(loi)); 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 getch(); exit(0); 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 const DIEM gmh = {getmaxx()/2,getmaxy()/2,WHITE}; chiều, do đó có thể dùng scanf để nhập trực tiếp vào các phần tử putpixel(gmh.x,gmh.y,gmh.mau); mảng. getch(); 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 closegraph(); nhất. } #include #include Chỳ ý: void main() 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 18 float a[20][20], smax; 19 khi biên dịch chương trỡnh. Vớ dụ nếu trong chương trỡnh đưa vào int m,n,i,j, imax, jmax; câu lệnh: clrscr(); gmh.x=200; puts( "Cho biet so hang va so cot cua ma tran: ") ; thỡ khi dịch chương trỡnh sẽ nhận được thông báo lỗi như sau: scanf("%d%d",&m,&n) ; Cannot modify a const object for (i=1;i<=m;++i) 4.5. Các kiểu char và int for (j=1;j<=n;++j) 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: printf("\na[%d][%d]= ",i,j); sizeof(‘A’) = sizeof(int) = 2 scanf("%f",&a[i][j]); // Lấy địa chỉ phần tử mảng thực // 2 chiều
  8. } để đưa giá trị các biểu thức ra màn hỡnh, dựng toỏn tử nhập: smax = a[1][1]; imax=1; jmax=1; cin >> biến >> >> biến for (i=1;i > 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 puts( "\n\n Ma tran") ; 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: for (i=1;i } Chương trỡnh sau minh hoạ việc sử dụng cỏc cụng cụ vào ra mới puts( "\n\nPhan tu max:" ); 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ứ printf("\nco gia tri = %6.1f", smax); tự giảm của tổng điểm. printf("\nTai hang %d cot %d " ,imax, jmax) ; getch(); #include } 20 #include 21 void main() § 5. Vào ra trong C++ { 5.1. Các toán tử và phương thức xuất nhập struct Để in dữ liệu ra màn hỡnh và nhập dữ liệu từ bàn phớm , trong { C++ vẫn cú thể dựng cỏc hàm printf và scanf (như chỉ ra trong các chương trỡnh C++ ở cỏc mục trờn). char ht[25]; Ngoài ra trong C++ cũn dựng toỏn tử xuất: float t,l,h,td; cout << biểu thức << << biểu thức ; } ts[50],tg;
  9. int n,i,j; } clrscr(); 5.2. Định dạng khi in ra màn hỡnh cout > n ; số sau dấu chấm thập phân, ta sử dụng đồng thời các hàm sau: for (i=1;i > ts[i].t >> ts[i].l >> ts[i].h ; thực, chuỗi) được in trong các toán tử xuất, ta dùng hàm ts[i].td = ts[i].t + ts[i].l + ts[i].h ; 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 for (i=1;i ts[j]=tg; Trở lại chương trỡnh trờn ta thấy danh sỏch thớ sinh in ra sẽ } 22khụng thẳng cột. Để khắc phục điều này cần viết lại đoạn chương 23 cout << "\nDanh sach thi sinh sau khi sap xep " ; trỡnh in như sau: for (i=1;i<=n;++i) cout << "\nDanh sach thi sinh sau khi sap xep " ; { cout << setiosflags(ios::showpoint) << setprecision(1) ; cout << "\n Ho ten: " << ts[i].ht; for(i=1;i<=n;++i) cout << " Tong diem: " << ts[i].td; } { getch(); cout << "\n Ho ten: " << setw(25) << ts[i].ht;
  10. cout cout } #include cout > m >> n ; for (i=1;i > a[i][j] ; // Khai báo các thành phần của cấu trúc } } ; smax = a[1][1]; imax=1; jmax=1; Sau đó để khai báo các biến, mảng cấu trúc, trong C dùng mẫu for (i=1;i<=m;++i) 24sau: 25 for (j=1;j<=n;++j) struct Tên_kiểu_ct danh sách biến, mảng cấu trúc ; if (smax<a[i][j]) 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. smax = a[i][j]; imax=i ; jmax = j;
  11. Trong C++ xem tên viết sau từ khoá struct là tên kiểu cấu trúc và 6.3. Các union không tên có thể dùng nó để khai báo. Như vậy để khai báo các biến, mảng cấu Trong C++ cho phép dùng các union không tên dạng: trúc trong C++ , ta có thể dùng mẫu sau: union 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 // Khai báo các thành phần 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. Khi đó các thành phần (khai báo trong union) sẽ dùng chung một struct TS 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ớ. char ht [25]; Ví dụ nếu cỏc biến nguyờn i , biến ký tự ch và biến thực x khụng long sobd; đồng thời sử dụng thỡ cú thể khai bỏo chỳng trong một union khụng float dt, dl, dh, td; tờn như sau: } ; union TS h, ts[1000] ; { int i ; 6.2. Tên sau từ khoá union được xem như tên kiểu hợp char ch ; Trong C++ một kiểu hợp (union) cũng được định nghĩa như C float x ; theo mẫu: } ; union Tên_kiểu_hợp { Khi đó các biến i , ch và f sử dụng chung một vùng nhớ 4 byte. // Khai báo các thành phần của hợp 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 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 ; unsigned long u ; Như vậy trong C, tên viết sau từ khoá union chưa phải là tên kiểu unsigned char b[4] ; 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ó Khí đó nếu gán thể dùng nó để khai báo. Như vậy để khai báo các biến, mảng kiểu 26 u = 0xDDCCBBAA; // Số hệ 16 27 hợp, trong C++ có thể dùng mẫu sau: thỡ : Tên_kiểu_hợp danh sách biến, mảng kiểu hợp ;
  12. b[0] = 0xAA 7.1. Trong C++ có thể sử dụng các hàm cấp phát bộ nhớ động của C b[1] = 0xBB như: hàm malloc để cấp phát bộ nhớ, hàm free để giải phóng bộ nhớ được cấp phát. b[2] = 0xCC b[3] = 0xDD 7.2. Ngoài ra trong C++ cũn đư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 6.4. Kiểu liệt kê (enum) 7.3. Cách dùng toán tử new để cấp phát bộ nhớ như sau: + Cũng giống như cấu trúc và hợp, tên viết sau từ khoá enum + Trước hết cần khai báo một con trỏ để chứa địa chỉ vùng nhớ sẽ được xem là kiểu liệt kê và có thể dùng để khai báo, ví dụ: được cấp phát: enum MAU { xanh, do, tim, vang } ; // Định nghĩa kiểu MAU Kiểu *p; MAU m, dsm[10] ; // Khai báo các biến, mảng kiểu MAU ở đây Kiểu có thể là: + 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ị - các kiểu dữ liệu chuẩn của C++ như int , long, float , double, enum, có thể gán giá trị enum cho biến nguyên, ví dụ: char , MAU m1 , m2 ; - 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, int n1, n2 ; + Sau đó dùng toán tử new theo mẫu: m1 = tim ; p = new Kiểu ; // Cấp phát bộ nhớ cho một biến (một phần tử) m2 = vàng ; p = new Kiểu[n] ; //Cấp phát bộ nhớ cho n phần tử n1 = m1 ; // n1 = 2 Ví dụ để cấp phát bộ nhớ cho một biến thực ta dùng câu lệnh sau: n2 = m1 + m2 ; // n2 = 5 float *px = new float ; printf (“\n %d “ , m2 ); // in ra số 3 Để cấp phát bộ nhớ cho 100 phần tử nguyên ta dùng các câu lệnh: + Không thể gán trực tiếp một giá trị nguyên cho một biến enum int *pn = new int[100] ; mà phải dùng phép ép kiểu, ví dụ: for (int i=0 ; i < 100 ; ++i ) m1 = 2 ; // lỗi pn[i] = 20*i ; // Gán cho phần tử thứ i m1 = MAU(2) ; // đúng 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: § 7. Cấp phát bộ nhớ Kiểu *p = new Kiểu ; 28 29
  13. để cấp phát bộ nhớ sẽ xuất hiện một trong 2 trường hợp: thành công Đoạn chương trỡnh kiểm tra theo cỏch thứ nhất cú thể viết theo hoặc không thành công. cỏch thứ hai như sau: Nếu thành cụng thỡ p sẽ chứa địa chỉ đầu vùng nhớ được cấp void kiem_tra_new(void) // Lập hàm kiểm tra phát. { Nếu khụng thành cụng thỡ p = NULL. cout > n ; cin >> n ; pd = new double[n] ; pd = new double[n] ; // Khi xẩy ra lỗi sẽ gọi hàm kiểm_tra_new if (pd==NULL) 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) ; cout << “ Lỗi cấp phát bộ nhớ “ (xem các chương trỡnh minh hoạ bờn dưới) exit (0) ; } 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 thứ 2 để kiểm tra sự thành công của toán tử new là dùng con Cách dùng như sau: trỏ hàm: delete p ; // p là con trỏ dùng trong new _new_handler Ví dụ: được định nghĩa trong tệp “new.h”. Khi gặp lỗi trong toán tử new (cấp phát không thành công) thỡ chương trỡnh sữ thực hiện một hàm float *px ; nào đó do con trỏ _new_handler trỏ tới. Cách dùng con trỏ này như px = new float[2000] ; // Cấp phát bộ nhớ cho 2000 phần tử sau: thực + Xây dựng một hàm dùng để kiểm tra sự thành công của new // Sử dụng bộ nhớ được cấp phát + Gán tên hàm này cho con trỏ _new_handler delete px ; // giải phóng bộ nhớ Như vậy hàm kiểm tra sẽ được gọi mỗi khi có lỗi xẩy ra trong 7.6. Hai chương trỡnh minh hoạ toán tử new.
  14. 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 for (int i=1;i cin.get(ts[i].ht,20); #include cout cin >> ts[i].sobd ; #include cout > ts[i].td ; { } char ht[20]; for (i=1;i > n; cout << "\n" << setw(20) << ts[i].ht << ts = new TS[n+1]; setw(6)<< ts[i].sobd <<setw(6)<< ts[i].td; if(ts==NULL) delete ts; { cout << "\nLoi cap phat bo nho " ; getch(); getch(); } exit(0);
  15. Chương trỡnh thứ hai minh hoạ cỏch dựng con trỏ _new_handler Trong C++ có rất nhiều mở rộng, cải tiến về hàm làm cho việc để kiểm tra sự thành công của toán tử new. Chương trỡnh sẽ cấp 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 phỏt bộ nhớ cho một mảng con trỏ và sẽ theo rừi khi nào thỡ không chương sau. Trong mục này chỉ thống kê một số điểm mới về hàm đủ bộ nhớ để cấp phát. mà C++ đưa vào. #include 8.1. Đối kiểu tham chiếu #include Trong C, để nhận kết quả của hàm cần dùng đối con trỏ, làm cho #include việc xây dựng cũng như sử dụng hàm khá phiền phức. Trong C++ #include đưa vào đối kiểu tham chiếu (giống như PASCAL) dùng để chứa kết int k; quả của hàm, khiến cho việc tạo lập cũng như sử dụng hàm đơn giản 32 33 void loi_bo_nho(void) hơn. { 8.2. Đối tham chiếu const cout << "\nLoi bo nho khi cap phat bo nho cho q[" << k << Đố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 getch(); muốn dùng đối kiểu tham chiếu chỉ để tăng tốc độ trao đổi dữ liệu exit(0); 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. void main() { 8.3. Đối có giá trị mặc định double *q[100] ; long n; Trong nhiều trương hợp người dùng viết một lời gọi hàm nhưng clrscr(); cũn chưa biết nên chọn giá trị nào cho các đối . Để khắc phục khó set_new_handler(loi_bo_nho) ; 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 // _new_handler=loi_bo_nho; không cung cấp giá trị cho các đối này, thỡ hàm sẽ dựng giỏ trị mặc n=10000; định. for ( k=0;k<100;++k) q[k] = new double[n]; 8.4. Hàm on line cout << "Khong loi"; Đối với một đoạn chương trỡnh nhỏ (số lệnh khụng lớn) thỡ việc getch(); 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 § 8. Các hàm trong C++ máy.
  16. 8.5. Các hàm trùng tên (định nghĩa chồng các hàm) + Việc đị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 + Việc định nghĩa chồng các toán tử 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 § 1. Biến tham chiếu (Reference variable) 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 1.1. Hai loại biến dùng trong C để lấy giá trị tuyệt đối cho nhiều kiểu dữ liệu khác nhau. Trước khi nói đến biến tham chiếu, chúng ta nhắc lại 2 loại biến 8.6. Định nghĩa chồng toán tử gặp trong C là: 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 Biến giá trị dùng để chứa dữ liệu (nguyên, thực, ký tự, ) cho chương trỡnh ngắn gọn, sáng sủa hơn nhiều. Ví dụ để thực hiện Biến con trỏ dùng để chứa địa chỉ phép cộng 2 ma trận nếu dùng phép cộng và viết: Các biến này đều được cung cấp bộ nhớ và có địa chỉ. Ví dụ câu C = A + B ; lệnh khai báo: 34 35 thỡ rất gần với toỏn học. Trong C++ cho phộp dựng cỏc phộp toỏn double x , *px; chuẩn để đặt tên cho các hàm (gọi là định nghĩa chồng toán tử). Sau sẽ tạo ra biến giá trị kiểu double x và biến con trỏ kiểu double px. đó có thể thay lời gọi hàm bằng các phép toán như nói ở trên. Như Biến x có vùng nhớ 8 byte, biến px có vùng nhớ 4 byte (nếu dùng vậy một phép toán mang nhiều ý nghĩa, vớ dụ phộp + cú thể hiểu là mô hình Large). Biến x dùng để chứa giá trị kiểu double, ví dụ lệnh 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 gán: của các số hạng mà quyết định chọn phép cộng cụ thể. 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 ; chương 2 sẽ lưu trữ địa chỉ của biễn x vào con trỏ px. Hàm trong C++ 1.2. Biến tham chiếu 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à: 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 + Kiểu tham chiếu và việc truyền dữ liệu cho hàm bằng tham sau: chiếu. + Biến tham chiếu không được cấp phát bộ nhớ, không có địa chỉ + Đối tham chiếu hằng (const) riêng. + Đối có giá trị mặc định + Nó dùng làm bí danh cho một biến (kiểu giá trị) nào đó và nó + Hàm trực tuyến sử dụng vùng nhớ của biến này. Ví dụ câu lệnh: 36 37
  17. float u, v, &r = u ; double &x ; 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 thì Trình biên dịch sẽ báo lỗi: được cấp phát bộ nhớ, nó là một tên khác (bí danh) của u và nó dùng Reference variable ‘x’ must be initialized chung vùng nhớ của biến u. b. Biến tham chiếu có thể tham chiếu đến một phần tử mảng, ví Thuật ngữ: Khi r là bí danh (alias) của u thì ta nói r tham chiếu dụ: đến biến u. Như vậy 2 thuật ngữ trên được hiểu như nhau. int a[10] , &r = a[5]; ý nghĩa: Khi r là bí danh của u thì r dùng chung vùng nhớ của u, dó đó : r = 25 ; // a[5] = 25 + Trong mọi câu lệnh, viết u hay viết r đều có ý nghĩa như nhau, c. Không cho phép khai báo mảng tham chiếu vì đều truy nhập đến cùng một vùng nhớ. d. Biến tham chiếu có thể tham chiếu đến một hằng. Khi đó nó sẽ + Có thể dùng biến tham chiếu để truy nhập đến một biến kiểu giá sử dụng vùng nhớ của hằng và nó có thể làm thay đổi giá trị chứa trị. trong vùng nhớ này. Ví dụ nếu khai báo: Ví dụ: int &s = 23 ; int u, v, &r = u; thì Trình biên dịch đưa ra cảnh báo (warning): r = 10 ; // u=10 Temporary used to initialize 's' cout Công dụng: Biến tham chiếu thường được sử dụng làm đối của #include hàm để cho phép hàm truy nhập đến các tham số biến trong lời gọi hàm. struct TS 38 { 39 Vài chú ý về biến tham chiếu: char ht[25]; a. Vì biến tham chiếu không có địa chỉ riêng, nó chỉ là bí danh của float t,l,h,td; một biến kiểu giá trị nên trong khai báo phải chỉ rõ nó tham chiếu đến biến nào. Ví dụ nếu khai báo: } ;
  18. void main() z = 2*py ; // Đúng z = 26 { cout > h.t >> h.l >> h.h ; phép hàm sử dụng giá trị của các tham số trong lời gọi hàm, nhưng h.td = h.t + h.l + h.h ; tránh không làm thay đổi giá trị của các tham số. cout << "\n Ho ten: " << ts[1].ht; cout << "\n Tong diem: " << ts[1].td; § 2. Truyền giá trị cho hàm theo tham chiếu getch(); 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ị : 1.3. Hằng tham chiếu (const) + Cấp phát vùng nhớ cho các đối. Hằng tham chiếu được khai báo theo mẫu: + 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 int n = 10 ; các tham số. const int &r = n; Như vây chương trình sẽ tạo ra các bản sao (các đối) của các tham Cũng giống như biến tham chiếu, hằng tham chiếu có thể 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 chiếu đến một biến hoặc một hằng. Ví dụ: tiếp với các tham số. Phương pháp này có 2 nhược điểm chính: int n = 10 ; Tốn kém về thời gian và bộ nhớ vì phải tạo ra các bản sao. Không const int &r = n ; // Hằng tham chiếu r tham chiếu đến biến n thao tác trực tiếp trên các tham số, vì vậy không làm thay đổi được const int &s=123 ; //Hằng tham chiếu s tham chiếu đến hằng 123 giá trị các tham số. Sự khác nhau giữa biến và hằng tham chiếu ở chỗ: Không cho 2.2. Truyền giá trị cho hàm theo tham chiếu phép dùng hằng tham chiếu để làm thay đổi giá trị của vùng nhớ mà Trong C++ cung cấp thêm cách truyền dữ liệu cho hàm theo tham nó tham chiếu. chiếu bằng cách dùng đối là biến tham chiếu hoặc đối là hằng tham Ví dụ: chiếu. Cách này có ưu điểm: int y = 12, z ; Không cần tạo ra các bản sao của các tham số, do đó tiết kiệm bộ 40 41 const int &py=y; // Hằng tham chiếu py tham chiếu đến biến y nhớ và thời gian chạy máy. y++; // Đúng
  19. 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. double tg=x; x=y; y= tg; 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 void sapxep(double * a, int n) 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à for (int i=1; i a[j]) biến hoặc phần tử mảng kiểu cấu trúc hv(a[i],a[j]); 2.4. Các chương trình minh hoạ } /* void main() Chương trình sau được tổ chức thành 3 hàm: { Nhập dẫy số double double x[100]; Hoán vị 2 biến double int i, n; Sắp xếp dẫy số double theo thứ tự tăng dần cout > n; */ nhapds(x,n); #include sapxep(x,n); #include for (i=1;i printf("\n%0.1lf",x[i]); void nhapds(double *a, int n) getch(); { for (int i=1; i > a[i] ; - Nhập dẫy cấu trúc (mỗi cấu trúc chứa dữ liệu một thí sinh) } - Hoán vị 2 biến cấu trúc } - Sắp xếp dẫy thí sinh theo thứ tự giảm của tổng điểm void hv(double &x, double &y) 42 43
  20. - In một cấu trúc (in họ tên và tổng điểm) } Chương trình sẽ nhập dữ liệu một danh sách thí sinh, nhập điểm void hvts(TS &ts1, TS &ts2) chuẩn và in danh sách thí sinh trúng tuyển { */ TS tg=ts1; #include ts1=ts2; #include ts2=tg; #include } struct TS void sapxep(TS *ts,int n) { { char ht[20]; for (int i=1;i > n ; cout > ts[i].t >> ts[i].l >> ts[i].h ; cin >> dc; ts[i].td = ts[i].t + ts[i].l + ts[i].h ; cout << "\n\nDanh sach trung tuyen\n" ; } for (i=1;i<=n;++i)
  21. if (ts[i].td >= dc) { ints(ts[i]); cout if (x[i] > x[vtmax]) vtmax = i; #include if (x[i] } #include } void nhapmt(float a[20][20], int m, int n) void main() { { for (int i=1 ; i > m >> n; cin >> a[i][j] ; nhapmt(a,m,n); } clrscr(); } inmt(a,m,n); void inmt(float a[20][20], int m, int n) float *p = (float*)a;
  22. int vtmax, vtmin; cout getch(); #include } struct TS { § 3. Hàm trả về các tham chiếu char ht[25]; Hàm có thể có kiểu tham chiếu và trả về giá trị tham chiếu. Khi float t,l,h,td; đó có thể dùng hàm để truy nhập đến một biến hoặc một phần tử }; mảng nào đó. Dưới đây là một số ví dụ. TS ts; Ví dụ 1 trình bầy một hàm trả về một tham chiếu đến một biến TS &f() toàn bộ. Do đó có thể dùng hàm để truy nhập đến biến này. { #include return ts; #include } int z ; void main() int &f() // Hàm trả về một bí danh của biến toàn bộ z { { TS &h=f(); // h tham chiếu đến biến ts return z; cout > h.t >> h.l >> h.h ; f()=50; // z = 50
  23. h.td = h.t + h.l + h.h ; { cout > h.t >> h.l >> h.h ; hay không. Sau đó dùng hàm này để truy nhập đến các phần tử mảng h.td = h.t + h.l + h.h ; cấu trúc. } #include } #include TS &f(int i, int n) // Cho bi danh ts[i] #include { struct TS if (i n) { { char ht[25]; cout > n; { cap_phat_bo_nho_nhapsl(n); cout << "Loi cap phat bo nho " ; while (1) exit(1); { } cout << "\nCan xem thi sinh thu may: " ; for (int i=1;i<=n;++i)
  24. cout > i; ; TS &h=f(i,n); } cout << "\n Ho ten: " << h.ht; Cách dùng: cout << "\n Tong diem: " << h.td; + 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) 50 delay() ; // Đối n = 1000 51 § 4. Đối có giá trị mặc định 4.2. Quy tắc xây dựng hàm với đối mặc định 4.1. Thế nào là đố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 Một trong các khả năng mạnh của C++ là nó cho phép xây dựng phải. Giả sử có 5 đối theo thứ tự từ trái sang phải là hàm với các đối có giá trị mặc định. Thông thường số tham số trong d1, d2, d3, d4, d5 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ị Khi đó: mặc định cho các đối. Các đối này có thể có hoặc không có tham số nếu một đối mặc định thì phải là d5 tương ứng trong lời gọi hàm. Khi không có tham số tương ứng, đối nếu hai đối mặc định thì phải là d4, d5 được khởi gán bởi giá trị mặc định. nếu ba đối mặc định thì phải là d3, d4, d5 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): Các ví dụ sai: void delay(int n=1000) 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) for (int i=0 ; i<n ; ++i) + 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) Cách 2 (Có khai báo nguyên mẫu): void f(int d1, float d2, char *d3=”HA NOI”, void delay(int n=1000) ; int d4 = 100, double d5=3.14) ; void delay(int n)
  25. void f(int d1, float d2, char *d3, int d4, double d5) Ví dụ với hàm có 3 đối mặc định: { void f(int d1, float d2, char *d3=”HA NOI”, // Các câu lệnh trong thân hàm int d4 = 100, double d5=3.14) ; } Thì các lời gọi sau là đúng: Không được khởi gán lại cho các đối mặc định trong dòng đầu f(3,3.4,”ABC”,10,1.0) ; // Đầy đủ tham số của định nghĩa hàm. Nếu vi phạm điều này thì Chương trình dịch sẽ f(3,3.4,”ABC”) ; // Thiếu 2 tham số cuối thông báo lỗi. f(3,3.4) ; // Thiếu 3 tham số cuố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ụ: Các lời gọi sau là sai: // Khởi gán giá trị cho 3 đối mặc định d3, d4 và d5) f(3) ; // Thiếu tham số cho đối không mặc định d2 void f(int d1, float d2, char *d3=”HA NOI”, f(3,3.4, ,10) ; // Đã dùng giá trị mặc định cho d3, thì cũng int d4 = 100, double d5=3.14) // phải dùng giá trị mặc định cho d4 và d5 { 52 4.4. Các ví dụ 53 // Các câu lệnh trong thân hàm 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. + Giá trị dùng để khởi gán cho đối mặc đinh #include Có thể dùng các hằng, các biến toàn bộ, các hàm để khởi gán cho #include đối mặc định, ví dụ: void ht(char *dc="HA NOI",int n=10) ; int MAX = 10000; void ht(char *dc , int n ) void f(int n, int m = MAX, int xmax = getmaxx(), { int ymax = getmaxy() ) ; for (int i=0;i<n;++i) cout << "\n" << dc; 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: void main() 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). ht(); // In dòng chữ “HA NOI” trên 10 dòng 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 ht("ABC",3); // In dòng chữ “ABC” trên 3 dòng đối còn lại. ht("DEF"); // In dòng chữ “DEF” trên 10 dòng
  26. 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 Dùng các hàm getmaxx() và getmaxy() để khởi gán cho x, y. Dùng hằng RED gán cho m. #include #include #include #include #include double bp(double x); void hiendc(char *str, int x=getmaxx()/2, double tp( double (*f)(double)=bp,double a=0.0, double b=1.0) ; int y = getmaxy()/2, int m=RED); double bp(double x) void hiendc(char *str, int x,int y, int m) { { return x*x; int mau_ht = getcolor(); // Luu mau hien tai } setcolor(m); double tp(double (*f)(double), double a, double b ) 54 outtextxy(x,y,str) ; { 55 setcolor(mau_ht); // Khoi phuc mau hien tai int n=1000; } double s=0.0, h=(b-a)/n; void main() for (int i=0; i<n ; ++i) { s+= f(a+i*h + h) + f(a+i*h ) ; int mh=0, mode=0; return s*h/2; initgraph(&mh,&mode,""); setbkcolor(BLUE); } hiendc("HELLO"); // HELLO mầu đỏ giữa màn hình void main() hiendc("CHUC MUNG",1,1); // CHUC MUNG mầu đỏ tại vị { // trí (1,1) clrscr(); hiendc("CHAO",1,400,YELLOW); // CHAO mầu vàng tại vị cout << setiosflags(ios::showpoint) << setprecision(2); // trí (1,400) cout << "\nTich phan tu 0 den 1 cua x*x= " << tp() ; getch(); cout << "\nTich phan tu 0 den 1 cua exp(x)= " << tp(exp); }
  27. cout gọi hàm. #include Tuy nhiên hàm cũng có nhược điểm là làm chậm tốc độ chương void main() 56trình do phải thực hiện một số thao tác có tính thủ tục mỗi khi gọi 57 { hàm như: Cấp phát vùng nhớ cho các đối và biến cục bộ, truyền dữ liệu của các tham số cho các đối, giải phóng vùng nhớ trước khi int s ; thoát khỏi hàm. s = f(5,6); Các hàm trực tuyến trong C++ cho khả năng khắc phục được cout << s ; nhược điểm nói trên. getch(); } 5.2. Các hàm trực tuyến inline int f(int a, int b) Để biến một hàm thành trực tuyến ta viết thêm từ khoá { inline return a*b; vào trước khai báo nguyên mẫu hàm. Nếu không dùng nguyên mẫu } thì viết từ khoá này trước dòng đầu tiên của định nghĩa hàm. Ví dụ: Chú ý: Trong C++ , nếu hàm được xây dựng sau lời gọi hàm thì inline float f(int n, float x); bắt buộc phải khai báo nguyên mẫu hàm trước lời gọi. Trong ví dụ float f(int n, float x) trên, Trình biên dịch C++ sẽ bắt lỗi vì thiếu khai báo nguyên mẫu { hàm f .
  28. 5.3. Cách biên dịch hàm trực tuyến Ví dụ: Chương trình sau sử dụng hàm inline tính chu vi và diện Chương trình dịch xử lý các hàm inline như các macro (được định tích của hình chữ nhật: nghĩa trong lệnh #define), nghĩa là nó sẽ thay mỗi lời gọi hàm bằng Phương án 1: Không khai báo nguyên mẫu. Khi đó hàm dtcvhcn một đoạn chương trình thực hiện nhiệm vụ của hàm. Cách này làm phải đặt trên hàm main. cho chương trình dài ra, nhưng tốc độ chương trình tăng lên do #include không phải thực hiện các thao tác có tính thủ tục khi gọi hàm. #include 5.4. So sánh macro và hàm trực tuyến inline void dtcvhcn(int a, int b, int &dt, int &cv) Dùng macro và hàm trực tuyến đều dẫn đến hiệu quả tương tự, { tuy nhiên người ta thích dùng hàm trực tuyến hơn, vì cách này đảm dt=a*b; bảo tính cấu trúc của chương trình, dễ sử dụng và tránh được các sai sót lặt vặt thường gặp khi dùng #define (như thiếu các dấu ngoặc, cv=2*(a+b); dấu chấm phẩy) } void main() 5.5. Khi nào thì nên dùng hàm trực tuyến { Phương án dùng hàm trực tuyến rút ngắn được thời gian chạy máy nhưng lại làm tăng khối lượng bộ nhớ chương trình (nhất là đối int a[20],b[20],cv[20],dt[20],n; với các hàm trực tuyến có nhiều câu lệnh). Vì vậy chỉ nên dùng cout > n; for (int i=1;i > a[i] >> b[i] ; chứ không phải là một mệnh lệnh bắt buộc. dtcvhcn(a[i],b[i],dt[i],cv[i]); Có một số hàm mà các Trình biên dịch thường không xử lý theo } cách inline như các hàm chứa biến static, hàm chứa các lệnh chu clrscr(); trình hoặc lệnh goto hoặc lệnh switch, hàm đệ quy. Trong trường hợp này từ khoá inline lẽ dĩ nhiên bị bỏ qua. for (i=1;i<=n;++i) Thậm chí từ khoá inline vẫn bị bỏ qua ngay cả đối với các hàm { không có những hạn chế nêu trên nếu như Trình biên dịch thấy cần cout << "\n Hinh chu nhat thu " << i << " : "; thiết (ví dụ đã có quá nhiều hàm inline làm cho bộ nhớ chương trình cout << "\nDo dai 2 canh= " << a[i] << " va " << b[i] ; quá lớn) cout << "\nDien tich= " << dt[i] ;
  29. cout #include § 6. Định nghĩa chồng các hàm (Overloading) inline void dtcvhcn(int a, int b, int &dt, int &cv) ; 6.1. Khái niệm về định nghĩa chồng void main() Định nghĩa chồng (hay còn gọi sự tải bội) các hàm là dùng cùng { một tên để định nghĩa các hàm khác nhau. Đây là một mở rộng rất có int a[20],b[20],cv[20],dt[20],n; ý nghĩa của C++. cout > n; FOXPRO, ) mỗi hàm đều phải có một tên phân biệt. Đôi khi đây là for (int i=1;i > a[i] >> b[i] ; longt labs(longt l); // Lấy giá trị tuyệt đối giá trị kiểu long dtcvhcn(a[i],b[i],dt[i],cv[i]); double fabs(double d); // Lấy giá trị tuyệt đối giá trị kiểu double } Nhờ khả năng định nghĩa chồng, trong C++ có thể dùng chung clrscr(); một tên cho cả 3 hàm trên như sau: for (i=1;i<=n;++i) int abs(int i) ; // Lấy giá trị tuyệt đối giá trị kiểu int { longt abs(longt l) ; // Lấy giá trị tuyệt đối giá trị kiểu long cout << "\n Hinh chu nhat thu " << i << " : "; double abs(double d) ; // Lấy giá trị tuyệt đối giá trị kiểu double cout << "\nDo dai 2 canh= " << a[i] << " va " << b[i] ;
  30. 6.2. Yêu cầu về các hàm định nghĩa chồng } Khi dùng cùng một tên để định nghĩa nhiều hàm, Trình biên dịch C++ sẽ dựa vào sự khác nhau về tập đối của các hàm này để đổi tên 6.3. Sử dụng các hàm định nghĩa chồng các hàm. Như vậy, sau khi biên dịch mỗi hàm sẽ có một tên khác 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à nhau. kiểu của các tham số để gọi hàm có đúng tên và đúng bộ đối số Từ đó cho thấy: các hàm được định nghĩa trùng tên phải có tập tương ứng. Ví dụ: đối khác nhau (về số lượng hoặc kiểu). Nếu 2 hàm hoàn toàn trùng abs(123); // Tham số kiểu int, gọi hàm int abs(int i) ; tên và trùng đối thì Trình biên dịch sẽ không có cách nào phân biệt abs(123L); // Tham số kiểu long, gọi hàm long abs(long l); được. Ngay cả khi 2 hàm này có kiểu khác nhau thì Trình biên dịch abs(3.14); //Tham số kiểu double, gọi hàm double abs(double d); vẫn báo lỗi. Ví dụ sau xây dựng 2 hàm cùng có tên là f và cùng có một đối nguyên a, nhưng kiểu hàm khác nhau. Hàm thứ nhất kiểu Khi không có hàm nào có bộ đối cùng kiểu với bộ tham số (trong nguyên (trả về a*a), hàm thứ hai kiểu void (in giá trị a). Chương 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 trình sẽ bị thông báo lỗi khi biên dịch (bạn hãy thử xem sao) (phép chuyển kiểu dễ dàng nhất). Ví dụ: #include abs(‘A’) ; // Tham số kiểu char, gọi hàm int abs(int i) ; #include abs(3.14F); // Tham số kiểu float, gọi hàm double abs(double d); int f(int a); 6.4. Nên sử dụng phép định nghĩa chồng các hàm như thế nào void f(int a); int f(int a) 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 return a*a; đị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: void f(int a) + 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 cout << "\n " << a ; chương trình cần xây dựng các hàm: cộng 2 ma trận vuông kiểu 62 } double, cộng 2 ma trận vuông kiểu int, cộng 2 ma trân chữ nhật kiểu63 void main() 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 int b=f(5); 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 f(b); nghĩa chồng. Vì như thế mới tránh được sự nhập nhằng cho Trình getch(); biên dịch và Trình biên dịch sẽ chọn đúng hàm cần gọi.
  31. 6.5. Lấy địa chỉ các hàm trùng tên void nhapds(int *x, int n); Giả sử có 4 hàm đều có tên là tinh_max được khai báo như sau: void nhapds(double *x, int n); int tinh_max(int a, int b, int c) ; // Max của 3 số nguyên int max(int x, int y); double tinh_max(double a, double b, double c); // Max của 3 double max(double x, double y); số // thực int max(int *x, int n); int tinh_max(int *a, int n) ; // Max của một dẫy số nguyên double max(double *x, int n); double tinh_max(double *a, int n) ; //Max của một dẫy số thực void nhapds(int *x, int n) { 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: for (int i=1;i > x[i] ; int (*f1)(int , int, int ); } f1 = tinh_max ; // Lấy địa chỉ của hàm thứ nhất } void nhapds(double *x, int n) double (*f2)(double , double, double); { f2 = tinh_max ; // Lấy địa chỉ của hàm thứ hai for (int i=1;i > x[i] ; 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ố int max(int x, int y) 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à return x>y?x:y ; nhapds. Bốn hàm: tính max 2 số nguyên, tính max 2 số thực, tính } 64max của dẫy số nguyên, tính max của dẫy số thực được đặt chung 65 một tên là max. double max(double x, double y) #include { #include return x>y?x:y ; #include }
  32. int max(int *x, int n) maxd = max(x,nd); { cout } #include void main() #include { typedef int MT[20][20]; int a[20] , n , ni, nd, maxi ; void nhapmt(MT a,char *ten, int m, int n); double x[20] , maxd ; void inmt(MT a,char *ten, int m, int n); clrscr(); void nhanmt(MT a,MT b, MT c, int m, int n, int p); cout > ni ; void inmt(MT a,char *ten, int n); cout > nd ; for (int i=1;i<=m;++i) cout << "Nhap day so thuc\n " ; for (int j=1;j<=n;++j) 66 67 nhapds(x,nd); { maxi = max(a,ni); cout << "\n" << ten <<"[" << i << "," << j << "]= " ;
  33. cin >> a[i][j]; } } } } void nhanmt(MT a,MT b, MT c, int n) void nhapmt(MT a,char *ten, int n) { { nhanmt(a,b,c,n,n, n) ; nhapmt(a,ten,n,n) ; } } void main() void inmt(MT a,char *ten, int m, int n) { { MT a,b,c,d; // d= abc cout << "\nMa tran: " << ten; MT u; for (int i=1;i<=m;++i) clrscr(); { nhapmt(a,"A",2); cout << "\n" ; nhapmt(b,"B",2); for (int j=1;j<=n;++j) nhapmt(c,"C",2,3); nhanmt(a,b,u,2); cout << setw(6) << a[i][j]; nhanmt(u,c,d,2,2,3); } inmt(a,"A",2); } inmt(b,"B",2); void inmt(MT a,char *ten, int n) inmt(u,"U = A*B",2); { inmt(c,"C",2,3); inmt(a,ten,n,n) ; inmt(d,"D = U*C",2,3); } getch(); void nhanmt(MT a,MT b, MT c, int m, int n, int p) } { for (int i=1;i<=m;++i) § 7. Định nghĩa chồng các toán tử for (int j=1;j<=p;++j) { 7.1. Các phép toán trong C và C++ c[i][j]=0; 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: + - for (int k=1;k<=n;++k) * / áp dụng cho các kiểu dữ liệu nguyên, thực. Phép lấy phần dư % c[i][j] += a[i][k] * b[k][j]; áp dụng đối với kiểu nguyên. 68 69
  34. 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 printf("\nTu va mau: "); trong C scanf("%d%d", &t, &m); Việc thực hiện các phép toán trên các đối tượng tự định nghĩa 70 p->a = t; p->b = m; 71 (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 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 void in(PS p) 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 printf(" %d/%d",p.a,p.b); 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) int uscln(int x, int y) #include { #include x=abs(x); y=abs(y); #include if (x*y==0) return 1; typedef struct while (x!=y) { if (x>y) x-=y; int a,b; else y-=x; } PS; return x; void nhap(PS *p); } void in(PS p); PS rutgon(PS p) int uscln(int x, int y); { PS rutgon(PS p); PS q; PS cong(PS p1, PS p2); int x; PS tru(PS p1, PS p2); x=uscln(p.a,p.b); PS nhan(PS p1, PS p2); q.a = p.a / x ; PS chia(PS p1, PS p2); q.b = p.b / x ; void nhap(PS *p) return q; { } int t, m; PS cong(PS p1, PS p2) {
  35. PS q; { q.a = p1.a*p2.b + p2.a*p1.b; PS p, q, z, u, v ; q.b = p1.b * p2.b ; PS tu,mau, s; return rutgon(q); printf("\n Nhap phan so p: "); nhap(&p); 72 73 } printf("\n Nhap phan so q: ");nhap(&q); PS tru(PS p1, PS p2) printf("\n Nhap phan so z: ");nhap(&z); { printf("\n Nhap phan so u: ");nhap(&u); PS q; printf("\n Nhap phan so v: ");nhap(&v); q.a = p1.a*p2.b - p2.a*p1.b; tu = nhan(q,z); q.b = p1.b * p2.b ; tu = tru(p,tu) ; return rutgon(q); mau = cong(u,v) ; } s = chia(tu,mau); PS nhan(PS p1, PS p2) printf(“\n Phan so s = “); in(s); { getch(); PS q; } q.a = p1.a * p2.a ; Nhận xét: Việc sử dụng các hàm để thực hiện các phép tính q.b = p1.b * p2.b ; không được tự nhiên và tỏ ra dài dòng. Ví dụ để thực hiện một công return rutgon(q); thức } s = (p - q*z)/(u + v) PS chia(PS p1, PS p2) 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 PS q; kết quả mong muốn hay không? q.a = p1.a * p2.b ; 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 q.b = p1.b * p2.a ; (mảng, cấu trúc, ). Nói cách khác C++ cho phép dùng các phép return rutgon(q); 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ử). void main() 7.3. Cách định nghĩa chồng các toán tử
  36. 7.3.1.Tên hàm toán tử: Gồm từ khoá operator và tên phép toán, 7.3.3. Thân của hàm toán tử: Viết như thân của hàm thông ví dụ: thường. Ví dụ hàm đổi dấu ma trận có thể được định nghĩa như sau: operator+ (định nghĩa chồng phép +) struct MT operator- (định nghĩa chồng phép -) { 7.3.2. Các đối của hàm toán tử: double a[20][20] ; // Mảng chứa các phần tử ma trận 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 int m ; // Số hàng ma trận đối. Đối thứ nhất ứng với toán hạng thứ nhất, đối thứ hai ứng với 74 int n ; // Số cột ma trân 75 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: MT operator-(MT x) struct PS { { MT y; int a; // Tử số for (int i=1; i<= m ;++i) int b; // Mẫu số for (int j=1; j<= n ;++j) } ; y[i][j] = - x[i][j] ; PS operator+(PS p1, PS p2); // p1 + p2 return y; PS operator-(PS p1, PS p2); // p1 - p2 } PS operator*(PS p1, PS p2); // p1 * p2 PS operator/(PS p1, PS p2); // p1 / p2 7.4. Cách dùng hàm toán tử 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 Có 2 cách dùng: đố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ách 1: Dùng như một hàm thông thường bằng cách viết lời gọi. của ma trận) được khai báo như sau: Ví dụ: struct MT PS p, q, u, v ; { u = operator+(p, q) ; // u = p + q double a[20][20] ; // Mảng chứa các phần tử ma trận v = operator-(p, q) ; // v = p - q int m ; // Số hàng ma trận Cách 2: Dùng như phép toán của C++ . int n ; // Số cột ma trân Ví dụ: } ; PS p, q, u, v ; MT operator-(MT x) ; u = p + q ; // u = p + q
  37. v = p - q ; // v = p - q #include Chú ý: Khi dùng các hàm toán tử như phép toán của C++ ta có typedef struct 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 int a,b; đầu của C++ . Chẳng hạn các phép * và / có thứ ưu tiên cao hơn so } PS; với các phép + và - ostream& operator > (istream& is,PS &p); PS p, q, u, v, s1, s2 ; int uscln(int x, int y); PS rutgon(PS p); s1 = p*q - u/v ; // s1 = (p*q) 76 PS operator+(PS p1, PS p2); 77 s2 = (p - q)/(u + v) ; // s2 = (p - q)/(u + v) PS operator-(PS p1, PS p2); PS operator*(PS p1, PS p2); § 8. Các ví dụ về định nghĩa chồng toán tử PS operator/(PS p1, PS p2); Ví dụ 1: Trong ví dụ này ngoài việc sử dụng các hàm toán tử để ostream& operator > để xuất và nhập phân số (xem chi tiết trong chương os > (istream& is,PS &p) ostream& operator > được khai báo như sau: cout > (istream& is,PS &p); is >> p.a >> p.b ; Dưới đây sẽ chỉ ra cách xây dựng và sử dụng các hàm toán tử. return is; 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. int uscln(int x, int y) 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ử. x=abs(x); y=abs(y); #include if (x*y==0) return 1; #include while (x!=y) if (x>y) x-=y;
  38. else y-=x; q.a = p1.a * p2.a ; return x; q.b = p1.b * p2.b ; } return rutgon(q); PS rutgon(PS p) } { PS operator/(PS p1, PS p2) PS q; { int x; PS q; x=uscln(p.a,p.b); q.a = p1.a * p2.b ; q.a = p.a / x ; q.b = p1.b * p2.a ; q.b = p.b / x ; return rutgon(q); return q; } } 78 void main() 79 PS operator+(PS p1, PS p2) { { PS p, q, z, u, v ; PS q; PS s; q.a = p1.a*p2.b + p2.a*p1.b; cout > p >> q >> z >> u >> v ; return rutgon(q); s = (p - q*z) / (u + v) ; } cout << "\n Phan so s = " << s; PS operator-(PS p1, PS p2) getch(); { } PS q; q.a = p1.a*p2.b - p2.a*p1.b; Ví dụ 2: Chương trình đưa vào các hàm toán tử: q.b = p1.b * p2.b ; operator- có một đối dùng để đảo dấu một đa thức return rutgon(q); operator+ có 2 đối dùng để cộng 2 đa thức } operator- có 2 đối dùng để trừ 2 đa thức PS operator*(PS p1, PS p2) operator* có 2 đối dùng để nhân 2 đa thức { operator^ có 2 đối dùng để tính giá đa thức tại x PS q; operator<< có 2 đối dùng để in đa thức
  39. operator>> có 2 đối dùng để nhập đa thức cout > d.n; f = -(p+q)*(r-s) cout { #include cout is >> d.a[i] ; } struct DT return is; { } double a[20]; // Mang chua cac he so da thuc a0, a1, DT operator-(const DT& d) int n ; // Bac da thuc { } ; DT p; ostream& operator > (istream& is,DT &d); for (int i=0 ; i d2.n ? d1.n : d2.n ; os > (istream& is, DT &d) d.a[i] = d2.a[i]; {
  40. i=k; } while (i>0 && d.a[i]==0.0) i; return s; d.n = i; } return d ; void main() } { DT operator-(DT d1, DT d2) DT p,q,r,s,f; { double x,g; return (d1 + (-d2)); clrscr(); } cout > p; DT operator*(DT d1, DT d2) cout > q; { cout > r; DT d; cout > s; int k, i, j; cout > x; k = d.n = d1.n + d2.n ; f = -(p+q)*(r-s); for (i=0; i<=k; ++i) d.a[i] = 0; g = f^x; for (i=0 ; i<= d1.n ; ++i) cout << "\nDa thuc f " << f ; for (j=0 ; j<= d2.n ; ++j) 82 cout << "\n x = " << x; 83 d.a[i+j] += d1.a[i]*d2.a[j] ; cout << "\nf(x) = " << g; return d; getch(); } } double operator^(DT d, double x) { § 9. Các bài toán về ma trận và véc tơ double s=0.0 , t=1.0; Trong mục này sẽ xét các ma trận thực vuông cấp n và các véc tơ for (int i=0 ; i<= d.n ; ++i) thực cấp n. Chúng được biểu diễn thông qua các kiểu cấu trúc MT và VT: { struct MT s += d.a[i]*t; { t *= x; double a[20][20] ; // Mang a chứa các phần tử ma trận
  41. int n ; // Cấp ma trận 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 struct VT 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 double b[20]; // Mang chua cac phan tu cua vec to 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: int n ; // Cap vec to (hàng i) = (hàng i) - x[i,k]*(hàng k) , với mọi i khác k } ; Nội dung chương trình là nhập 4 ma trận X, Y, R, S và véc tơ u. Để xử lý ma trận và véc tơ, chúng ta xây dựng 9 hàm toán tử: Sau đó tính véc tơ v theo công thức: ostream& operator > (istream& is,MT& x); // Nhập ma trận 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. istream& operator>> (istream& is, VT &v); // Nhập véc tơ /* Chương trình */ MT operator+(const MT& x1, const MT& x2); // Cộng 2 ma trận #include MT operator-(const MT& x1, const MT& x2); // Trừ 2 ma trận #include MT operator*(const MT& x1, const MT& x2); // Nhân 2 ma trận #include VT operator*(const MT& x, const VT& v); // Nhân ma trận véc #include tơ struct MT 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ó double a[20][20]; // Mang chua cac phan tu ma tran gì phải bàn. Để nghịch đảo ma trận có nhiều cách, ở đây chúng ta int n ; // Cap ma tran dùng phương pháp Jordance như sau. Giả sử cần nghịch đảo ma trận 84 85 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 struct VT 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: double b[20]; // Mang chua cac phan tu cua vec to Tìm chỉ số r ( k <= r <= n) sao cho int n ; // Cap vec to 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 ostream& operator<< (ostream& os, const MT& x); kết thúc giữa chừng. ostream& operator<< (ostream& os, const VT& v);
  42. istream& operator>> (istream& is,MT& x); { istream& operator>> (istream& is, VT &v); cout > x.n; MT operator-(const MT& x1, const MT& x2); cout > x.a[i][j] ; { } os > (istream& is, VT& v) os > v.n; } cout > v.b[i] ; ostream& operator > (istream& is, MT& x) cung cap";
  43. getch(); for (j=1; j<=n ;++j) return x1; x.a[i][j] = x1.a[i][j] - x2.a[i][j] ; } return x; else } { } MT x; int i, j, n; MT operator*(const MT& x1, const MT& x2) n = x.n = x1.n ; { for (i=1; i<=n; ++i) if (x1.n!=x2.n) for (j=1; j<=n ;++j) { x.a[i][j] = x1.a[i][j] + x2.a[i][j] ; cout << "\nKhong thuc hien duoc phep nhan vi 2 MT khong return x; cung cap"; } getch(); } return x1; MT operator-(const MT& x1, const MT& x2) } { else if (x1.n!=x2.n) { { MT x; cout << "\nKhong thuc hien duoc phep tru vi 2 MT khong cung cap"; int n, i, j,k; getch(); n = x.n = x1.n; return x1; for (i=1; i<=n; ++i) } for (j=1; j<=n ;++j) else { { x.a[i][j] = 0.0 ; for (k=1 ; k<=n; ++k) MT x; 88 89 int i, j, n; x.a[i][j] += x1.a[i][k]*x2.a[k][j] ; n = x.n = x1.n; } for (i=1; i<=n; ++i) return x; }
  44. } double tg; VT operator*(const MT& x, const VT& v) n = y.n = x.n ; { for (i=1 ; i abs(x.a[r][k]) ) r = i; { if (abs(x.a[r][k]) < 1.0E-8) VT u; int n; { n = u.n = v.n ; for (int i=1; i <=n ; ++i) cout << "\n Ma tran suy bien, khong co nghich dao" ; { getch(); u.b[i] = 0; return x; for (int j=1; j<=n; ++j) } u.b[i] += x.a[i][j]*v.b[j]; /* Hoan vi hang r va hang k */ } for (j=1 ; j<=n ; ++j) return u; { } tg = x.a[k][j]; } x.a[k][j] = x.a[r][j]; MT operator!(MT x) x.a[r][j] = tg; { tg = y.a[k][j]; MT y; 90 y.a[k][j] = y.a[r][j]; 91 int i,j,k,r,n; y.a[r][j] = tg;
  45. } cout > y; /* Chia hang k cho a[k,k] */ cout > r; tg = x.a[k][k] ; cout > s; for (j=1 ; j > u; { v = !((x+y)*(r-s))*u ; x.a[k][j] /= tg; cout > x; 92
  46. 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ú ý: Chương 3 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 Khái niệm về lớp trước (cấu trúc, hợp, lớp, ) . Thuộc tính của lớp không thể có kiểu Như đã nói ở trên, lớp là khái niệm trung tâm của lập trình hướng của chính lớp đó, nhưng có thể là kiểu con trỏ lớp này, ví dụ: đố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 class A 93 94 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 A x ; // Không cho phép, vì x có kiểu lớp A (method) hay hàm thành viên (member function). Cũng giống như A *p ; // Cho phép , vì p là con trỏ kiểu lớp A 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 2. Khi báo các thành phần của lớp (thuộc tính và phương thức) có tượng (biến, mảng) khác nhau. Mỗi đối tượng có vùng nhớ riêng của thể dùng các từ khoá private và public để quy định phạm vi sử dụng 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ủa các thành phần. Nếu không quy định cụ thể (không dùng các từ cùng kiểu. khoá private và public) thì C++ hiểu đó là private. Chương này sẽ trình bầy cách định nghĩa lớp, cách xây dựng Các thành phần private (riêng) chỉ được sử dụng bên trong lớp phương thức, giải thích về phạm vi truy nhập, sư dụng các thành (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ầ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 của lớp không được phép sử dụng các thành phần này. phương thức. 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 § 1. Định nghĩa lớp lớp.
  47. 4. Các phương thức thường khai báo là public để chúng có thể public: được gọi tới (sử dụng) từ các hàm khác trong chương trình. void nhapsl() ; 5. Các phương thức có thể được xây dựng bên ngoài hoặc bên void hien() ; 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 void an() đị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ử putpixel(x, y, getbkcolor()); dụng: } + Các thuộc tính của lớp A } ; + Các phương thức của lớp A void DIEM::nhap() + 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. cout > x >> y ; ngoài chuẩn) cout > m ; tên là DIEM. } + Các thuộc tính của lớp gồm: void DIEM::hien() int x ; // hoành độ (cột) { int y ; // tung độ (hàng) int mau_ht ; int m ; // mầu mau_ht = getcolor(); + Các phương thức: putpixel(x, y, m); Nhập dữ liệu một điểm setcolor(mau_ht); Hiển thị một điểm } ẩn một điểm Qua ví dụ trên có thể rút ra một số điều cần nhớ sau: Lớp điểm được xây dựng như sau: + Trong cả 3 phương thức (dù viết trong hay viết ngoài định class DIEM 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. private: + Các phương thức viết bên trong định nghĩa lớp (như phương int x, y, m ; thức an() ) được viết như một hàm thông thường.
  48. + 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_đối_tượng.Tên_thuộc_tính tên lớp và toán tử phạm vi :: đặt ngay trước tên phương phức để quy Với các đối tượng d1, d2, d3 và mảng d, có thể viết như sau: định rõ đây là phương thức của lớp nào. 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 § 2. Biến, mảng đối tượng d3.y // Thuộc tính y của đối tượng d3 Như đã nói ở trên, một lớp (sau khi định nghĩa) có thể xem như d[2].m // Thuộc tính m của phần tử d[2] 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 d1.x = 100 ; // Gán 100 cho d1.x biến, mảng các kiểu khác (như int, float, cấu trúc, hợp, ), theo mẫu d2.y = d1.x; // Gán d1.x cho d2.y sau: Sử dụng các phương thức Tên_lớp danh sách đối ; Cũng giống như hàm, một phương thức được sử dụng thông qua Tên_lớp danh sách mảng ; lời gọi. Tuy nhiên trong lời gọi phương thức bao giờ cũng phải có Ví dụ sử dụng lớp DIEM ở §1, có thể khai báo các biến, mảng tên đối tượng để chỉ rõ phương thức thực hiện trên các thuộc tính DIEM như sau: của đối tượng nào. Ví dụ lời gọi: DIEM d1, d2, d3 ; // Khai báo 3 biến đối tượng d1, d2, d3 d1.nhapsl(); DIEM d[20] ; // Khai báo mảng đối tượng d gồm 20 phần tử 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 Mỗi đối tượng sau khi khai báo sẽ được cấp phát một vùng nhớ Câu lệnh 97 98 riêng để chứa các thuộc tính của chúng. Chú ý rằng sẽ không có d[3].nhapsl() ; 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 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 lớp. Như vậy về bộ nhớ được cấp phát thì đối tượng giống cấu trúc. Chúng ta sẽ minh hoạ các điều nói trên bằng một chương trình Trong trương hợp này: đơn giản sử dụng lớp DIEM để nhập 3 điểm, hiện rồi ẩn các điểm sizeof(d1) = sizeof(d2) = sizeof(d3) = 3*sizeof(int) = 6 vừa nhập. Trong chương trình đưa vào hàm kd_do_hoa() dùng để khởi động hệ đồ hoạ. sizeof(d) = 20*6 = 120 #include Thuộc tính của đối tượng: #include 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 #include đối tượng, vì vậy không thể viết tên thuộc một cách riêng rẽ mà bao class DIEM 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: private: int x, y, m ;
  49. public: } void nhapsl(); void main() void an() { { DIEM d1, d2, d3 ; putpixel(x,y,getbkcolor()); d1.nhapsl(); } void hien(); d2.nhapsl(); }; d3.nhapsl(); void DIEM::nhapsl() kd_do_hoa(); { setbkcolor(BLACK); cout > x >> y ; d2.hien(); cout > m ; getch(); } d1.an(); void DIEM::hien() d2.an(); { d3.an(); 99 100 int mau_ht; getch(); mau_ht = getcolor() ; closegraph(); putpixel(x,y,m); } setcolor(mau_ht); } § 3. Con trỏ đối tượng void kd_do_hoa() 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ỏ ; int mh, mode ; Ví dụ dùng lớp DIEM có thể khai báo: mh=mode=0; DIEM *p1 , *p2, *p3 ; // khai báo 3 con trỏ p1, p2, p3 initgraph(&mh, &mode, "");
  50. DIEM d1, d2 ; // Khai báo 2 đối tượng d1, d2 #include DIEM d[20] ; // Khai báo mảng đối tượng #include và có thể thực hiện các câu lệnh: class DIEM 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 private: p3 = new DIEM // Tạo một đối tượng và chứa địa chỉ của nó int x, y, m ; // vào p3 public: void nhapsl(); Để sử dụng thuộc tính của đối tượng thông qua con trỏ, ta viết như sau: void an() 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 putpixel(x,y,getbkcolor()); trỏ như tên mảng. } Như vậy sau khi thực hiện các câu lệnh trên thì: void hien(); p1->x và d2.x là như nhau }; p2[i].y và d[i].y là như nhau void DIEM::nhapsl() { Tóm lại ta có quy tắc sau cout > x >> y ; tượng ta phải dùng phép . hoặc phép -> . Trong chương trình, không cout > m ; Tên_đối_tượng.Tên_thuộc_tính } Tên_con_trỏ->Tên_thuộc_tính void DIEM::hien() 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 int mau_ht; Chương trình dưới đây cũng sử dụng lớp DIEM (trong §1) để mau_ht = getcolor() ; 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 putpixel(x,y,m); 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. setcolor(mau_ht); #include }
  51. void kd_do_hoa() void DIEM::nhapsl() { { int mh, mode ; cout > x >> y ; initgraph(&mh, &mode, ""); cout > m ; void main() } { DIEM *p; 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 int i, n; quy tắc sử dụng thuộc tính nêu trong mục trước. Song sự thể như cout > n; C++ sử dụng con trỏ đặc biệt this trong các phương thức. Các p = new DIEM[n+1]; thuộc tính viết trong phương thức được hiểu là thuộc một đối tượng for (i=1; i > this->x >> this->y ; for (i=1; i > this->m ; p[i].an(); } 103 104 getch(); closegraph(); 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 § 4. Đối của phương thức, con trỏ this Xét một lời gọi tới phương thức nhapsl() : 4.1. Con trỏ this là đối thứ nhất của phương thức DIEM d1; Chúng ta hãy xem lại phương thức nhapsl của lớp DIEM
  52. d1.nhapsl() ; line(this->x,this->y,d2.x,d2.y); Trong trường hợp này tham số truyền cho con trỏ this chính là setcolor(mau_ht); địa chỉ của d1: } this = &d1 Chương trình sau minh hoạ các phương thức có nhiều đối. Ta vẫn Do đó: dùng lớp DIEM nhưng có một số thay đổi: this->x chính là d1.x + Bỏ thuộc tính m (mầu) this->y chính là d1.y + Bỏ các phương thức hien và an this->m chính là d1.m +Đưa vào 4 phương thức mới: Như vậy câu lệnh ve_ doan_thang (Vẽ đoạn thẳng qua 2 điểm) d1.nhapsl() ; ve_tam_giac (Vẽ tam giác qua 3 điểm) sẽ nhập dữ liệu cho các thuộc tính của đối tượng d1. Từ đó có thể rút do_dai (Tính độ dài của đoạn thẳng qua 2 điểm) ra kết luận sau: chu_vi (Tính chu vi tam giác qua 3 điểm) 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. 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 4.3. Các đối khác của phương thức ve_tam_giac sử dụng phương thức ve_doan_thang, 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 chu_vi sử dụng phương thức do_dai) minh), phương thức còn có các đối khác được khai báo như trong + Sử dụng con trỏ this trong thân các phương thức ve_tam_giac các hàm. Đối của phương thức có thể có kiểu bất kỳ (chuẩn và ngoài và chu_vi chuẩn). Nội dung chương trình là nhập 3 điểm, vẽ tam giác có đỉnh là 3 Ví dụ để xây dựng phương thức vẽ đường thẳng qua 2 điểm ta điểm vừa nhập sau đó tính chu vi tam giác. cần đưa vào 3 đối: Hai đối là 2 biến kiểu DIEM, đối thứ ba kiểu #include 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ư #include sau: #include 105 106 void DIEM::doan_thang(DIEM d2, int mau) #include #include { class DIEM int mau_ht; { mau_ht = getcolor(); private: setcolor(mau); int x, y ;
  53. public: (*this).ve_doan_thang(d2,mau); void nhapsl(); d2.ve_doan_thang(d3,mau); void ve_doan_thang(DIEM d2, int mau) ; d3.ve_doan_thang(*this,mau); void ve_tam_giac(DIEM d2, DIEM d3,int mau) ; } double do_dai(DIEM d2) double DIEM::chu_vi(DIEM d2, DIEM d3) { { DIEM d1 = *this ; return sqrt( pow(d1.x - d2.x,2) + double s; pow(d1.y - d2.y,2) ) ; s= (*this).do_dai(d2) + d2.do_dai(d3) + d3.do_dai(*this) ; } return s; double chu_vi(DIEM d2, DIEM d3); } }; void DIEM::nhapsl() void main() { { cout > x >> y ; char tb_cv[20] ; } d1.nhapsl(); void kd_do_hoa() d2.nhapsl(); { d3.nhapsl(); int mh, mode ; kd_do_hoa(); mh=mode=0; d1.ve_tam_giac(d2,d3,15); initgraph(&mh, &mode, ""); double s = d1.chu_vi(d2,d3); sprintf(tb_cv,"Chu vi = %0.2f", s); } outtextxy(10,10,tb_cv); void DIEM::ve_doan_thang(DIEM d2, int mau) getch(); { closegraph(); setcolor(mau); } line(this->x,this->y,d2.x,d2.y); Một số nhận xét về đối của phương thức và lời gọi phương107 108 } thức void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) + Quan sát nguyên mẫu phương thức: { void ve_doan_thang(DIEM d2, int mau) ;
  54. sẽ thấy phương thức có 3 đối: Vẽ cạnh 1 dùng lệnh: (*this).ve_doan_thang(d2,mau) ; Đối thứ nhât là một đối tượng DIEM do this trỏ tới Vẽ cạnh 2 dùng lệnh: d2.ve_doan_thang(d3,mau); Đối thứ hai là đối tượng DIEM d2 Vẽ cạnh 3 dùng lệnh: d3.ve_doan_thang(*this,mau); Đối thứ ba là biến nguyên mau Trong trường này rõ ràng vai trò của this rất quan trọng. Nếu Nội dung phương thức là vẽ một đoạn thẳng đi qua các điểm *this không dùng nó thì công việc trơ nên khó khăn, dài dòng và khó hiểu và d2 theo mã mầu mau. Xem thân của phương sẽ thấy được nội hơn. Chúng ta hãy so sánh 2 phương án: dung này: Phương án dùng this trong phương thức ve_tam_giac: void DIEM::ve_doan_thang(DIEM d2, int mau) void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) { { setcolor(mau); (*this).ve_doan_thang(d2,mau); line(this->x,this->y,d2.x,d2.y); d2.ve_doan_thang(d3,mau); } d3.ve_doan_thang(*this,mau); 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 Phương án không dùng this trong phương thức ve_tam_giac: thức có thể bỏ từ khoá this vẫn được. void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) + 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) ; DIEM d1; Phương thức này có 4 đối là: d1.x = x; this trỏ tới một đối tượng kiểu DIEM d1.y = y; d2 một đối tượng kiểu DIEM d1.ve_doan_thang(d2,mau); d3 một đối tượng kiểu DIEM d2.ve_doan_thang(d3,mau); mau một biến nguyên d3.ve_doan_thang(d1,mau); 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 5. Nói thêm về kiểu phương thức và kiểu đối của phương thức cạnh 3 đi qua d3 và *this 5.1. Kiểu phương thức 109 110 Các cạnh trên được vẽ nhờ sử dụng phương thức ve_doan_thang:
  55. Phương thức có thể không có giá trị trả về (kiểu void) hoặc có thể + Lớp DAY_HINH_CN gồm trả về một giá trị có kiểu bất kỳ, kể cả giá trị kiểu đối tượng, con trỏ - Các thuộc tính: đối tượng, tham chiếu đối tượng. int n ; //số hình chữ nhật của dẫy 5.2. Đối của phương thức HINH_CN *h; //Con trỏ tới dẫy đối tượng của lớp Đối của phương thức (cũng giống như đối của hàm) có thể có HINH_CN kiểu bất kỳ: - Các phương thức + Kiểu dữ liệu chuẩn như int, float, char, . Con trỏ hoặc tham void nhapsl(); // Nhập một dẫy hình chữ nhật chiếu đến kiểu dữ liệu chuẩn như int*, float*, char*, int&, float&, HINH_CN hinh_dt_max() ; //Trả về hình chữ nhật có char&, // diện tích max + Các kiểu ngoài chuẩn đã định nghĩa trước như đối tượng, cấu trúc, hợp, enum, . Con trỏ hoặc tham chiếu đến các kiểu ngoài HINH_CN *hinh_cv_max() ; // Trả về con trỏ tới HCN có chuẩn này. // chu vi max + Kiểu đối tượng của chính phương thức, con trỏ hoặc tham #include chiếu đến kiểu đối tượng này. #include 5.3. Các ví dụ class HINH_CN Ví dụ 1 minh hoạ: { + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng private: của lớp khác đã định nghĩa bên trên. int d, r; // chieu dai va chieu rong + Phương thức có giá trị trả về kiểu đối tượng và con trỏ đối public: tượng. void nhapsl() Nội dung chương trình là nhập một dẫy hình chữ nhật, sau đó tìm { hình chữ nhật có max diện tích và hình chữ nhật có max chu vi. cout > d >> r ; + Lớp HINH_CN gồm: } - Các thuộc tính: d và r (chiều dài và chiều rộng) void in() - Các phương thức { void nhapsl() ; // Nhập chiều dài, rộng cout << "\nchieu dai = " << d ; int dien_tich(); // Tính diện tích cout << " chieu rong= " << r; int chu_vi() ; // Tính chu vi } 111 112
  56. int dien_tich() HINH_CN hdtmax; { hdtmax = h[1]; return d*r; for (int i=2; i hdtmax.dien_tich() ) int chu_vi() hdtmax = h[i]; { return hdtmax; return 2*(d+r); } } HINH_CN *DAY_HINH_CN::hinh_cv_max() } ; { class DAY_HINH_CN int imax = 1; { for (int i=2; i h[imax].chu_vi() ) int n; // So hinh ch nhat imax = i ; HINH_CN *h; return (h+imax); public: } void nhapsl(); HINH_CN hinh_dt_max() ; void main() HINH_CN *hinh_cv_max() ; { } ; DAY_HINH_CN d; void DAY_HINH_CN::nhapsl() HINH_CN hdtmax; { d.nhapsl(); cout > n; hdtmax.in() ; h = new HINH_CN[n+1]; HINH_CN *hcvmax=d.hinh_cv_max(); for (int i=1;i in() ; h[i].nhapsl(); getch(); } } HINH_CN DAY_HINH_CN::hinh_dt_max() { Ví dụ 2 minh hoạ:
  57. + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng + Các vấn đề đáng chú ý trong chương trình là: của lớp khác đã định nghĩa bên trên. - Phương thưc tĩnh tao_tg (sẽ giải thích bên dưới) + Phương thức có giá trị trả về kiểu đối tượng - Phương thưc maxdt 113 114 + Vai trò của con trỏ this (xem phương thức maxdt của lớp + Thuật toán là: TAM_GIAC) - Duyệt qua các tổ hợp 3 điểm. + Phương thức tĩnh (xem phương thức tao_tg của lớp TAM_GIAC) - Dùng phương thức tao_tg để lập tam giác từ 3 điểm Nội dung chương trình là nhập một dẫy các điểm, sau đó tìm tam - Dùng phương thức maxdt để chọn tam giác có diện tích lớn giác lớn nhất (về diện tích) có đỉnh là các điểm vừa nhập. hơn trong 2 tam giác: tam giác vừa tạo và tam giác có diện tích max (trong số các tam giác đã tạo) Chương trình được tổ chức thành 2 lớp: #include + Lớp DIEM gồm: #include - Các thuộc tính: x và y (toạ độ của điểm) #include - Các phương thức class DIEM void nhapsl() ; // Nhập x, y void in() ; // In toạ độ { double do_dai(DIEM d2) ; // Tính độ dài đoạn thẳng qua private: // 2 điểm (điểm ẩn xác định bởi this và điểm d2) double x,y; // Toa do cua diem + Lớp TAM_GIAC gồm: public: - Các thuộc tính: void nhapsl() DIEM d1,d2,d3; // 3 đỉnh của tam giác { - Các phương thức: cout > x >> y ; void in(); // In toạ độ 3 đỉnh } // Tạo một đối tượng TAM_GIAC từ 3 đối tượng DIEM void in() static TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) { double dien_tich() ; // Tính diện tích cout << " x = " << x << " y = " << y; // Tìm tam giác có diện tích max trong 2 tam giác *this và } t2 double do_dai(DIEM d2) TAM_GIAC maxdt(TAM_GIAC t2);
  58. { d3.nhapsl(); return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) ); } } void TAM_GIAC::in() } ; { class TAM_GIAC cout dien_tich() > t2.dien_tich()) } ; return *this ; void TAM_GIAC::nhapsl() else { return t2; cout << "\nDinh 1 - " ; } d1.nhapsl(); void main() cout << "\nDinh 2 - " ; { d2.nhapsl(); DIEM d[50]; cout << "\nDinh 3 - " ; int n, i ;
  59. clrscr(); return t; cout > n; Phương thức tĩnh (sẽ nói thêm trong các mục bên dưới) có các for (i=1; i { #include #include TAM_GIAC t; class DIEM t.d1=e1; t.d2 = e2; t.d3=e3;
  60. { t.d1=e1; t.d2 = e2; t.d3=e3; private: return t; double x,y; // Toa do cua diem } public: void nhapsl() double dien_tich() ; { TAM_GIAC maxdt(TAM_GIAC t2); cout > x >> y ; void TAM_GIAC::nhapsl() } { void in() 119 120 cout << "\nDinh 1 - " ; { d1.nhapsl(); cout << " x = " << x << " y = " << y; cout << "\nDinh 2 - " ; } d2.nhapsl(); double do_dai(DIEM d2) cout << "\nDinh 3 - " ; { d3.nhapsl(); return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) ); } } void TAM_GIAC::in() } ; { class TAM_GIAC cout << "\nDinh 1: " ; d1.in(); { cout << "\nDinh 2: " ; d2.in(); private: cout << "\nDinh 3: " ; d3.in(); DIEM d1,d2,d3; // 3 dinh tam giac } public: double TAM_GIAC::dien_tich() void nhapsl(); { void in(); double a,b,c,p,s; friend TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) a=d1.do_dai(d2); { b=d2.do_dai(d3); TAM_GIAC t; c=d3.do_dai(d1);
  61. p=(a+b+c)/2; for (k=j+1;k dien_tich() > t2.dien_tich()) cout > n; DIEM d1,d2,d3; // 3 dinh tam giac for (i=1; i<=n; ++i) public: { void nhapsl(); cout << "\nNhap diem " << i << " - " ; void in(); d[i].nhapsl(); friend TAM_GIAC tao_tg(DIEM e1,DIEM e2,DIEM e3); } double dien_tich() ; int j, k ; TAM_GIAC maxdt(TAM_GIAC t2); TAM_GIAC tmax, t; } ; tmax = tao_tg(d[1],d[2],d[3]); TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) for (i=1;i<=n-2;++i) { for (j=i+1;j<=n-1;++j) TAM_GIAC t;
  62. t.d1=e1; t.d2 = e2; t.d3=e3; }; return t; Dùng lớp DIEM, ta xây dựng hàm tính độ dài của đoạn thẳng đi } qua 2 điểm như sau: Nhận xét: Không cho phép dùng từ khoá friend khi xây dựng double do_dai(DIEM d1, DIEM d2) hàm (bên ngoài lớp) { return sqrt(pow(d1.x-d2.x,2) + pow(d1.y-d2.y,2)); § 6. Hàm, hàm bạn } Hàm này sẽ bị báo lỗi khi dịch, vì trong thân hàm không cho phép 6.1. Hàm có các tính chất sau: sử dụng các thuộc tính d1.x, d1.y, d2.x, d2.y của các đối tượng d1 và + Phạm vi của hàm là toàn bộ chương trình, vì vậy hàm có thể d2 thuộc lớp DIEM. được gọi tới từ bất kỳ chỗ nào. Như vây trong các phương thức có + Phạm vi sử dụng của các phương thức (public) là toàn chương thể sử dụng hàm. trình, vì vậy trong thân hàm có thể gọi tới các phương thức. Ví dụ + Đối của hàm có thể là các đối tượng, tuy nhiên có một hạn chế giả sử đã định nghĩa lớp: là trong thân hàm không cho phép truy nhập tới thuộc tính của các123 124 đối này. Ví dụ giả sử đã định nghĩa lớp: class DIEM class DIEM { { private: private: double x,y; // Toa do cua diem double x,y; // Toa do cua diem public: public: void nhapsl() void nhapsl() { { cout > x >> y ; cin >> x >> y ; } } void in() void in() { { cout << " x = " << x << " y = " << y; cout << " x = " << x << " y = " << y; } } double do_dai(DIEM d2)