Bài giảng Lập trình hướng đối tượng C++ - Chương 2: Hàm trong C++

doc 30 trang hoanguyen 3310
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình hướng đối tượng C++ - Chương 2: Hàm trong 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:

  • docbai_giang_lap_trinh_huong_doi_tuong_c_chuong_2_ham_trong_c.doc

Nội dung text: Bài giảng Lập trình hướng đối tượng C++ - Chương 2: Hàm trong C++

  1. 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 Trong C++ cho phép sử dụng loại biến thứ ba là biến tham chiếu. xây dựng và sử dụng hàm. Đó là: 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ó sử dụng vùng nhớ của biến này. Ví dụ câu lệnh: + Hàm trực tuyến float u, v, &r = u ; + Việc định nghĩa chồng các hàm 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 + Việc định nghĩa chồng các toán tử đượ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. § 1. Biến tham chiếu (Reference variable) 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. 1.1. Hai loại biến dùng trong C ý nghĩa: Khi r là bí danh của u thì r dùng chung vùng nhớ của u, Trước khi nói đến biến tham chiếu, chúng ta nhắc lại 2 loại biến dó đó : gặp trong C là: + Trong mọi câu lệnh, viết u hay viết r đều có ý nghĩa như nhau, Biến giá trị dùng để chứa dữ liệu (nguyên, thực, ký tự, ) vì đều truy nhập đến cùng một vùng nhớ. Biến con trỏ dùng để chứa địa chỉ + Có thể dùng biến tham chiếu để truy nhập đến một biến kiểu giá Các biến này đều được cung cấp bộ nhớ và có địa chỉ. Ví dụ câu trị. lệnh khai báo: double x , *px; Ví dụ: sẽ tạo ra biến giá trị kiểu double x và biến con trỏ kiểu double px. int u, v, &r = u; Biến x có vùng nhớ 8 byte, biến px có vùng nhớ 4 byte (nếu dùng r = 10 ; // u=10 mô hình Large). Biến x dùng để chứa giá trị kiểu double, ví dụ lệnh cout << u ; // in ra số 10 gán: r++ ; // u = 11 x = 3.14; ++ u ; // r = 12 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: cout << r ; // in ra số 12 px = &x ; v = r ; // v=12 36 37
  2. & r ; // Cho địa chỉ của u Chương trình dưới đây minh hoạ cách dùng biến tham chiếu đến một phần tử mảng cấu trúc để nhập dữ liệu và thực hiện các phép tính trên các trường của phần tử mảng cấu trúc. Công dụng: Biến tham chiếu thường được sử dụng làm đối của hàm để cho phép hàm truy nhập đến các tham số biến trong lời gọi #include hàm. #include struct TS Vài chú ý về biến tham chiếu: 38 { 39 a. Vì biến tham chiếu không có địa chỉ riêng, nó chỉ là bí danh của một biến kiểu giá trị nên trong khai báo phải chỉ rõ nó tham chiếu char ht[25]; đến biến nào. Ví dụ nếu khai báo: float t,l,h,td; double &x ; } ; thì Trình biên dịch sẽ báo lỗi: void main() Reference variable ‘x’ must be initialized { b. Biến tham chiếu có thể tham chiếu đến một phần tử mảng, ví TS ts[10],&h=ts[1]; // h tham chiếu đến ts[1] dụ: cout > h.t >> h.l >> h.h ; d. Biến tham chiếu có thể tham chiếu đến một hằng. Khi đó nó sẽ h.td = h.t + h.l + h.h ; sử dụng vùng nhớ của hằng và nó có thể làm thay đổi giá trị chứa trong vùng nhớ này. cout << "\n Ho ten: " << ts[1].ht; Ví dụ nếu khai báo: cout << "\n Tong diem: " << ts[1].td; int &s = 23 ; getch(); thì Trình biên dịch đưa ra cảnh báo (warning): } Temporary used to initialize 's' 1.3. Hằng tham chiếu (const) Tuy nhiên chương trình vẫn làm việc. Các câu lệnh dưới đây vẫn Hằng tham chiếu được khai báo theo mẫu: thực hiện và cho kết quả như sau: int n = 10 ; s++; const int &r = n; cout << "\ns= " << s; // In ra s=24 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ụ:
  3. 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 giá trị các tham số. 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 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 chiếu. Cách này có ưu điểm: Ví dụ: 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ộ 40nhớ và thời gian chạy máy. 41 const int &py=y; // Hằng tham chiếu py tham chiếu đến biến y y++; // Đúng 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. z = 2*py ; // Đúng z = 26 cout 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 #include tiếp với các tham số. Phương pháp này có 2 nhược điểm chính: #include
  4. void nhapds(double *a, int n) printf("\n%0.1lf",x[i]); { getch(); for (int i=1; i > a[i] ; Chương trình sau gồm các hàm: } - 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 void hv(double &x, double &y) - Sắp xếp dẫy thí sinh theo thứ tự giảm của tổng điểm { - In một cấu trúc (in họ tên và tổng điểm) 42 43 double tg=x; x=y; y= tg; Chương trình sẽ nhập dữ liệu một danh sách thí sinh, nhập điểm chuẩn và in danh sách thí sinh trúng tuyển } */ void sapxep(double * a, int n) #include { #include for (int i=1; i for (int j=i+1 ; j a[j]) { hv(a[i],a[j]); char ht[20]; } float t,l,h,td; void main() } ; { void ints(const TS &ts) double x[100]; { int i, n; cout > n; } nhapds(x,n); void nhapsl(TS *ts,int n) sapxep(x,n); { for (i=1;i<=n;++i) for (int i=1;i<=n;++i)
  5. { cout > 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 = dc) void hvts(TS &ts1, TS &ts2) ints(ts[i]); { else TS tg=ts1; 44 45 break; ts1=ts2; getch(); ts2=tg; } } void sapxep(TS *ts,int n) /* { Chương trình sau gồm các hàm: for (int i=1;i TS ts[100]; #include int n,i; #include clrscr(); #include void nhapmt(float a[20][20], int m, int n)
  6. { { 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; { int vtmax, vtmin; cout x[vtmax]) vtmax = i; đó có thể dùng hàm để truy nhập đến một biến hoặc một phần tử if (x[i]
  7. #include return ts; int z ; } int &f() // Hàm trả về một bí danh của biến toàn bộ z void main() { { return z; TS &h=f(); // h tham chiếu đến biến ts } cout > h.t >> h.l >> h.h ; cout cấu trúc. #include #include struct TS #include { #include char ht[25]; struct TS float t,l,h,td; { }; char ht[25]; TS ts; float t,l,h,td; TS &f() }; { TS *ts;
  8. void cap_phat_bo_nho_nhapsl(int n) void main() { { ts = new TS[n+1] ; int n, i ; if (ts==NULL) cout > n; { cap_phat_bo_nho_nhapsl(n); cout > i; cout > h.t >> h.l >> h.h ; h.td = h.t + h.l + h.h ; 50 51 } § 4. Đối có giá trị mặc định } 4.1. Thế nào là đối mặc định TS &f(int i, int n) // Cho bi danh ts[i] 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 if (i n) 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ị cout << "Chi so mang khong hop le " ; mặc định cho các đối. Các đối này có thể có hoặc không có tham số exit(1); 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. return ts[i]; Ví dụ hàm delay với đối số mặc định được viết theo một trong 2 } cách sau:
  9. 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) void f(int d1, float d2, char *d3, int d4, double d5) { { for (int i=0 ; i<n ; ++i) ; // Các câu lệnh trong thân hàm } } Cách dùng: 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ẽ + Cung cấp giá trị cho đối n (Có tham số trong lời gọi hàm) thông báo lỗi. delay(5000) ; // Đối n = 5000 + Khi xây dựng hàm, nếu không khai báo nguyên mẫu, thì các đối + Sử dụng giá trị mặc định của đối (Không có tham số trong lời mặc định được khởi gán trong dòng đầu của định nghĩa hàm, ví dụ: gọi) // Khởi gán giá trị cho 3 đối mặc định d3, d4 và d5) delay() ; // Đối n = 1000 void f(int d1, float d2, char *d3=”HA NOI”, 4.2. Quy tắc xây dựng hàm với đối mặc định int d4 = 100, double d5=3.14) + Các đối mặc định cần phải là các đối cuối cùng tính từ trái sang { 52 53 phải. Giả sử có 5 đối theo thứ tự từ trái sang phải là // Các câu lệnh trong thân hàm d1, d2, d3, d4, d5 } Khi đó: + Giá trị dùng để khởi gán cho đối mặc đinh nếu một đối mặc định thì phải là d5 Có thể dùng các hằng, các biến toàn bộ, các hàm để khởi gán cho nếu hai đối mặc định thì phải là d4, d5 đối mặc định, ví dụ: nếu ba đối mặc định thì phải là d3, d4, d5 int MAX = 10000; void f(int n, int m = MAX, int xmax = getmaxx(),
  10. int ymax = getmaxy() ) ; for (int i=0;i f(3,3.4) ; // Thiếu 3 tham số cuối #include Các lời gọi sau là sai: void hiendc(char *str, int x=getmaxx()/2, f(3) ; // Thiếu tham số cho đối không mặc định d2 int y = getmaxy()/2, int m=RED); f(3,3.4, ,10) ; // Đã dùng giá trị mặc định cho d3, thì cũng void hiendc(char *str, int x,int y, int m) // phải dùng giá trị mặc định cho d4 và d5 { int mau_ht = getcolor(); // Luu mau hien tai 4.4. Các ví dụ setcolor(m); Hàm ht (bên dưới) dùng để hiển thị chuỗi ký tự dc trên n dòng 54 outtextxy(x,y,str) ; 55 màn hình. Các đối dc và n đều có giá trị mặc định. setcolor(mau_ht); // Khoi phuc mau hien tai #include } #include void main() void ht(char *dc="HA NOI",int n=10) ; { void ht(char *dc , int n ) int mh=0, mode=0; {
  11. initgraph(&mh,&mode,""); return s*h/2; 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 #include § 5. Các hàm trực tuyến (inline) #include #include 5.1. Ưu, nhược điểm của hàm double bp(double x); Việc tổ chức chương trình thành các hàm có 2 ưu điểm rõ rệt : Thứ nhất là chia chương trình thành các đơn vị độc lập, làm cho double tp( double (*f)(double)=bp,double a=0.0, double b=1.0) ; chương trình được tổ chức một cách khoa học dễ kiểm soát dễ phát double bp(double x) hiện lỗi, dễ phát triển, mở rộng. { Thứ hai là giảm được kích thước chương trình, vì mỗi đoạn return x*x; chương trình thực hiện nhiệm vụ của hàm được thay bằng một lời } gọi hàm. double tp(double (*f)(double), double a, double b ) Tuy nhiên hàm cũng có nhược điểm là làm chậm tốc độ chương 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ữ int n=1000; liệu của các tham số cho các đối, giải phóng vùng nhớ trước khi double s=0.0, h=(b-a)/n; thoát khỏi hàm. for (int i=0; i<n ; ++i) Các hàm trực tuyến trong C++ cho khả năng khắc phục được nhược điểm nói trên. s+= f(a+i*h + h) + f(a+i*h ) ;
  12. 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ụ trên, Trình biên dịch C++ sẽ bắt lỗi vì thiếu khai báo nguyên mẫu float f(int n, float x) hàm f . { // Các câu lệnh trong thân hàm 5.3. Cách biên dịch hàm trực tuyến } Chương trình dịch xử lý các hàm inline như các macro (được định nghĩa trong lệnh #define), nghĩa là nó sẽ thay mỗi lời gọi hàm bằng hoặc 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 inline float f(int n, float x) cho chương trình dài ra, nhưng tốc độ chương trình tăng lên do { không phải thực hiện các thao tác có tính thủ tục khi gọi hàm. // Các câu lệnh trong thân hàm 5.4. So sánh macro và hàm trực tuyến } Dùng macro và hàm trực tuyến đều dẫn đến hiệu quả tương tự, Chú ý: Trong mọi trường hợp, từ khoá inline phải xuất hiện tuy nhiên người ta thích dùng hàm trực tuyến hơn, vì cách này đảm trước các lời gọi hàm thì Trình biên dịch mới biết cần xử lý hàm theo 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 kiểu inline. sót lặt vặt thường gặp khi dùng #define (như thiếu các dấu ngoặc, Ví dụ hàm f trong chương trình sau sẽ không phải là hàm trực dấu chấm phẩy) tuyến vì từ khoá inline viết sau lời gọi hàm: 5.5. Khi nào thì nên dùng hàm trực tuyến #include Phương án dùng hàm trực tuyến rút ngắn được thời gian chạy #include máy nhưng lại làm tăng khối lượng bộ nhớ chương trình (nhất là đối void main() 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 { phương án trực tuyến đối với các hàm nhỏ. int s ; 585.6. Sự hạn chế của Trình biên dịch 59 s = f(5,6); Không phải khi gặp từ khoá inline là Trình biên dịch nhất thiết cout << s ; phải xử lý hàm theo kiểu trực tuyến. getch(); Chú ý rằng từ khoá inline chỉ là một sự gợi ý cho Trình biên dịch } chứ không phải là một mệnh lệnh bắt buộc.
  13. Có một số hàm mà các Trình biên dịch thường không xử lý theo clrscr(); cách inline như các hàm chứa biến static, hàm chứa các lệnh chu for (i=1;i Phương án 2: Sử dụng khai báo nguyên mẫu. Khi đó từ khoá inline đặt trước nguyên mẫu. #include Chú ý: Không được đặt inline trước định nghĩa hàm. Trong inline void dtcvhcn(int a, int b, int &dt, int &cv) chương trình dưới đây nếu đặt inline trước định nghĩa hàm thì hậu { quả như sau: Chương trình vẫn dịch thông, nhưng khi chạy thì dt=a*b; chương trình bị quẩn, không thoát được. cv=2*(a+b); #include } #include void main() 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; cout > n; { for (int i=1;i > a[i] >> b[i] ; cout > a[i] >> b[i] ; } dtcvhcn(a[i],b[i],dt[i],cv[i]);
  14. } 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 § 6. Định nghĩa chồng các hàm (Overloading) #include 6.1. Khái niệm về định nghĩa chồng int f(int a); Định nghĩa chồng (hay còn gọi sự tải bội) các hàm là dùng cùng void f(int a); 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 f(int a) ý nghĩa của C++. { Như đã biết, trong C và các ngôn ngữ khác (như PASCAL, FOXPRO, ) mỗi hàm đều phải có một tên phân biệt. Đôi khi đây là return a*a; một sự hạn chế lớn, vì phải dùng nhiều hàm khác nhau để thực hiện } cùng một công việc. Ví dụ để lấy giá trị tuyệt đối trong C cần dùng void f(int a) tới 3 hàm khác nhau: { int abs(int i); // Lấy giá trị tuyệt đối giá trị kiểu int cout << "\n " << a ; longt labs(longt l); // Lấy giá trị tuyệt đối giá trị kiểu long 62 } 63 double fabs(double d); // Lấy giá trị tuyệt đối giá trị kiểu double
  15. 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). int b=f(5); + 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 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. } 6.5. Lấy địa chỉ các hàm trùng tên 6.3. Sử dụng các hàm định nghĩa chồng Giả sử có 4 hàm đều có tên là tinh_max được khai báo như sau: 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à int tinh_max(int a, int b, int c) ; // Max của 3 số nguyên kiểu của các tham số để gọi hàm có đúng tên và đúng bộ đối số double tinh_max(double a, double b, double c); // Max của 3 tương ứng. Ví dụ: số // thực abs(123); // Tham số kiểu int, gọi hàm int abs(int i) ; int tinh_max(int *a, int n) ; // Max của một dẫy số nguyên abs(123L); // Tham số kiểu long, gọi hàm long abs(long l); double tinh_max(double *a, int n) ; //Max của một dẫy số thực abs(3.14); //Tham số kiểu double, gọi hàm double abs(double d); Vấn đề đặt ra là làm thế nào lấy được địa chỉ của mỗi hàm. Câu Khi không có hàm nào có bộ đối cùng kiểu với bộ tham số (trong trả lời như sau: 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 Để lấy địa chỉ của một hàm, ta khai báo một con trỏ hàm có kiểu (phép chuyển kiểu dễ dàng nhất). Ví dụ: và bộ đối như hàm cần lấy địa chỉ. Sau đó gán tên hàm cho con trỏ abs(‘A’) ; // Tham số kiểu char, gọi hàm int abs(int i) ; hàm. Ví dụ: abs(3.14F); // Tham số kiểu float, gọi hàm double abs(double d); int (*f1)(int , int, int ); f1 = tinh_max ; // Lấy địa chỉ của hàm thứ nhất 6.4. Nên sử dụng phép định nghĩa chồng các hàm như thế nào double (*f2)(double , double, double); Như đã nói ở trên, khi xây dựng cũng như sử dụng các hàm trùng f2 = tinh_max ; // Lấy địa chỉ của hàm thứ hai 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 int (*f3)(int *, int ); định nghĩa chồng, vì điều đó làm cho chương trình khó kiểm soát và f3 = tinh_max ; // Lấy địa chỉ của hàm thứ ba dễ dẫn đến sai sót. Việc định nghĩa chồng sẽ hiệu quả hơn nếu được double (*f4)(double *, int ); sử dụng theo các lời khuyên sau: f4 = tinh_max ; // Lấy địa chỉ của hàm thứ tư + 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 6.6. Các ví dụ chương trình cần xây dựng các hàm: cộng 2 ma trận vuông kiểu Ví dụ 1: Chương trình giải bài toán tìm max của một dẫy số double, cộng 2 ma trận vuông kiểu int, cộng 2 ma trân chữ nhật kiểu 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à 64 65
  16. 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 double max(double x, double y) một tên là max. { #include return x>y?x:y ; #include #include } void nhapds(int *x, int n); int max(int *x, int n) void nhapds(double *x, int n); { int max(int x, int y); int s=x[1]; double max(double x, double y); for (int i=2;i > x[i] ; for (int i=2;i > x[i] ; double x[20] , maxd ; } clrscr(); } cout > ni ; { cout y?x:y ; nhapds(a,ni);
  17. cout > nd ; for (int i=1;i > a[i][j]; cout for (int j=1;j cout } typedef int MT[20][20]; } void nhapmt(MT a,char *ten, int m, int n); void inmt(MT a,char *ten, 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); inmt(a,ten,n,n) ; 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 m, int n, int p) 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<=m;++i)
  18. for (int j=1;j nhapmt(a,"A",2); #include nhapmt(b,"B",2); #include nhapmt(c,"C",2,3); typedef struct nhanmt(a,b,u,2); nhanmt(u,c,d,2,2,3); { inmt(a,"A",2); int a,b; inmt(b,"B",2); } PS; inmt(u,"U = A*B",2); void nhap(PS *p); inmt(c,"C",2,3); void in(PS p); inmt(d,"D = U*C",2,3); int uscln(int x, int y); getch(); PS rutgon(PS p); } PS cong(PS p1, PS p2); PS tru(PS p1, PS p2); § 7. Định nghĩa chồng các toán tử PS nhan(PS p1, PS p2);
  19. PS chia(PS p1, PS p2); return q; void nhap(PS *p) } { PS cong(PS p1, PS p2) int t, m; { printf("\nTu va mau: "); PS q; scanf("%d%d", &t, &m); q.a = p1.a*p2.b + p2.a*p1.b; 70 p->a = t; p->b = m; q.b = p1.b * p2.b ; 71 } return rutgon(q); void in(PS p) } { PS tru(PS p1, PS p2) printf(" %d/%d",p.a,p.b); { } PS q; int uscln(int x, int y) q.a = p1.a*p2.b - p2.a*p1.b; { q.b = p1.b * p2.b ; x=abs(x); y=abs(y); return rutgon(q); if (x*y==0) return 1; } while (x!=y) PS nhan(PS p1, PS p2) if (x>y) x-=y; { else y-=x; PS q; return x; q.a = p1.a * p2.a ; } q.b = p1.b * p2.b ; PS rutgon(PS p) return rutgon(q); { } PS q; PS chia(PS p1, PS p2) int x; { x=uscln(p.a,p.b); PS q; q.a = p.a / x ; q.a = p1.a * p2.b ; q.b = p.b / x ; q.b = p1.b * p2.a ;
  20. 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ử { 7.3.1.Tên hàm toán tử: Gồm từ khoá operator và tên phép toán, ví dụ: PS p, q, z, u, v ; operator+ (định nghĩa chồng phép +) PS tu,mau, s; operator- (định nghĩa chồng phép -) printf("\n Nhap phan so p: "); nhap(&p); 72 7.3.2. Các đối của hàm toán tử: 73 printf("\n Nhap phan so q: ");nhap(&q); printf("\n Nhap phan so z: ");nhap(&z); 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 printf("\n Nhap phan so u: ");nhap(&u); toán hạng thứ hai. Do vậy, với các phép toán không giao hoán (như printf("\n Nhap phan so v: ");nhap(&v); phép-) thì thứ tự đối là rất quan trọng. tu = nhan(q,z); Ví dụ các hàm toán tử cộng , trừ phân số được khai báo như sau: tu = tru(p,tu) ; struct PS mau = cong(u,v) ; { s = chia(tu,mau); int a; // Tử số int b; // Mẫu số printf(“\n Phan so s = “); in(s); } ; getch(); PS operator+(PS p1, PS p2); // p1 + p2 } PS operator-(PS p1, PS p2); // p1 - p2 Nhận xét: Việc sử dụng các hàm để thực hiện các phép tính PS operator*(PS p1, PS p2); // p1 * p2 không được tự nhiên và tỏ ra dài dòng. Ví dụ để thực hiện một công PS operator/(PS p1, PS p2); // p1 / p2 thức 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 s = (p - q*z)/(u + v) đố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ử 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ủa ma trận) được khai báo như sau: cách nào để chỉ cần viết đúng công thức toán học, mà vẫn nhận được struct MT 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 double a[20][20] ; // Mảng chứa các phần tử ma trận (mảng, cấu trúc, ). Nói cách khác C++ cho phép dùng các phép int m ; // Số hàng ma trận
  21. 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 7.3.3. Thân của hàm toán tử: Viết như thân của hàm thông v = p - q ; // v = p - q thường. Ví dụ hàm đổi dấu ma trận có thể được định nghĩa như sau: Chú ý: Khi dùng các hàm toán tử như phép toán của C++ ta có struct MT 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 double a[20][20] ; // Mảng chứa các phần tử ma trận đầu của C++ . Chẳng hạn các phép * và / có thứ ưu tiên cao hơn so int m ; // Số hàng ma trận với các phép + và - 74 int n ; // Số cột ma trân Ví dụ: 75 } ; PS p, q, u, v, s1, s2 ; MT operator-(MT x) s1 = p*q - u/v ; // s1 = (p*q) { s2 = (p - q)/(u + v) ; // s2 = (p - q)/(u + v) MT y; for (int i=1; i > để 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: Ví dụ: istream& operator>> (istream& is,PS &p); PS p, q, u, v ; 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, u = operator+(p, q) ; // u = p + q ngắn gọn và tiện lợi. v = operator-(p, q) ; // v = p - q Chương trình dưới đây có nội dung như chương trình trong §6.2, Cách 2: Dùng như phép toán của C++ . nhưng thay các hàm bằng các hàm toán tử.
  22. #include if (x*y==0) return 1; #include while (x!=y) if (x>y) x-=y; #include else y-=x; typedef struct return x; { } int a,b; PS rutgon(PS p) } PS; { ostream& operator > (istream& is,PS &p); int x; int uscln(int x, int y); x=uscln(p.a,p.b); PS rutgon(PS p); q.a = p.a / x ; 76 77 PS operator+(PS p1, PS p2); q.b = p.b / x ; PS operator-(PS p1, PS p2); return q; PS operator*(PS p1, PS p2); } PS operator/(PS p1, PS p2); PS operator+(PS p1, PS p2) ostream& operator > (istream& is,PS &p) } { PS operator-(PS p1, PS p2) cout > p.a >> p.b ; PS q; return is; } q.a = p1.a*p2.b - p2.a*p1.b; q.b = p1.b * p2.b ; int uscln(int x, int y) return rutgon(q); { } x=abs(x); y=abs(y); PS operator*(PS p1, PS p2)
  23. { operator^ có 2 đối dùng để tính giá đa thức tại x PS q; operator > có 2 đối dùng để nhập đa thức q.b = p1.b * p2.b ; Chương trình sẽ nhập 4 đa thức: p, q, r, s. Sau đó tính đa thức: return rutgon(q); 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. PS operator/(PS p1, PS p2) #include { #include PS q; q.a = p1.a * p2.b ; #include q.b = p1.b * p2.a ; struct DT return rutgon(q); { } double a[20]; // Mang chua cac he so da thuc a0, a1, int n ; // Bac da thuc 78 void main() 79 { } ; PS p, q, z, u, v ; ostream& operator > (istream& is,DT &d); cout > p >> q >> z >> u >> v ; DT operator+(DT d1, DT d2); s = (p - q*z) / (u + v) ; DT operator-(DT d1, DT d2); cout << "\n Phan so s = " << s; DT operator*(DT d1, DT d2); getch(); double operator^(DT d, double x); // Tinh gia tri da thuc } ostream& operator<< (ostream& os, DT d) Ví dụ 2: Chương trình đưa vào các hàm toán tử: { operator- có một đối dùng để đảo dấu một đa thức os << " - Cac he so (tu ao): " ; operator+ có 2 đối dùng để cộng 2 đa thức for (int i=0 ; i<= d.n ; ++i) operator- có 2 đối dùng để trừ 2 đa thức os << d.a[i] <<" " ; operator* có 2 đối dùng để nhân 2 đa thức return os; }
  24. istream& operator>> (istream& is, DT &d) else { d.a[i] = d2.a[i]; cout > d.n; while (i>0 && d.a[i]==0.0) i; d.n = i; cout > d.a[i] ; { } return (d1 + (-d2)); return is; } } DT operator*(DT d1, DT d2) DT operator-(const DT& d) { { DT p; DT d; p.n = d.n; int k, i, j; for (int i=0 ; i d2.n ? d1.n : d2.n ; double operator^(DT d, double x) for (i=0; i<=k ; ++i) { if (i<=d1.n && i<=d2.n) double s=0.0 , t=1.0; d.a[i] = d1.a[i] + d2.a[i]; for (int i=0 ; i<= d.n ; ++i) else if (i<=d1.n) { d.a[i] = d1.a[i]; s += d.a[i]*t;
  25. t *= x; double a[20][20] ; // Mang a chứa các phần tử ma trận } int n ; // Cấp ma trận return s; } ; } struct VT { void main() double b[20]; // Mang chua cac phan tu cua vec to { int n ; // Cap vec to DT p,q,r,s,f; } ; double x,g; Để xử lý ma trận và véc tơ, chúng ta xây dựng 9 hàm toán tử: clrscr(); ostream& operator > p; ostream& operator > q; istream& operator>> (istream& is,MT& x); // Nhập ma trận cout > r; istream& operator>> (istream& is, VT &v); // Nhập véc tơ cout > s; MT operator+(const MT& x1, const MT& x2); // Cộng 2 ma cout > x; trận f = -(p+q)*(r-s); MT operator-(const MT& x1, const MT& x2); // Trừ 2 ma trận g = f^x; MT operator*(const MT& x1, const MT& x2); // Nhân 2 ma trận cout << "\nDa thuc f " << f ; VT operator*(const MT& x, const VT& v); // Nhân ma trận véc tơ 82 cout << "\n x = " << x; 83 MT operator!(MT x); // Nghịch đảo ma trận cout << "\nf(x) = " << g; getch(); 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 § 9. Các bài toán về ma trận và véc tơ 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. 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ơ Nội dung của bước k (k = 1, ,n) như sau: 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: Tìm chỉ số r ( k <= r <= n) sao cho struct MT abs(x[r,k]) = max { abs(x[i,k] với i = k, ,n } {
  26. Nếu abs(x[r,k]) = 0 thì ma trận không có nghịch đảo và thuật toán ostream& operator > (istream& is,MT& x); Chia hàng k của cả x và y cho tg = x[k,k] (mục đích làm cho istream& operator>> (istream& is, VT &v); x[k,k] = 1). MT operator+(const MT& x1, const MT& x2); 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 MT operator-(const MT& x1, const MT& x2); phép tính sau trên cả x và y: MT operator*(const MT& x1, const MT& x2); (hàng i) = (hàng i) - x[i,k]*(hàng k) , với mọi i khác k VT operator*(const MT& x, const VT& v); Nội dung chương trình là nhập 4 ma trận X, Y, R, S và véc tơ u. MT operator!(MT x); // Tinh ma tran nghich dao Sau đó tính véc tơ v theo công thức: ostream& operator os for (int j=1; j os } struct MT os << "\n" ; { return os; double a[20][20]; // Mang chua cac phan tu ma tran } int n ; // Cap ma tran ostream& operator<< (ostream& os, const VT& v) 84 85 } ; { struct VT os << setprecision(2) << setiosflags(ios::showpoint); { for (int i=1 ; i<= v.n ; ++i) double b[20]; // Mang chua cac phan tu cua vec to os << setw(6) << v.b[i] ; int n ; // Cap vec to os << "\n" ; } ; return os;
  27. } cout > (istream& is, MT& x) cung cap"; getch(); { return x1; cout > x.n; else cout > x.a[i][j] ; for (j=1; j > (istream& is, VT& v) { MT operator-(const MT& x1, const MT& x2) cout > v.n; if (x1.n!=x2.n) cout > v.b[i] ; return x1; } } return is; 86 } else 87 { MT operator+(const MT& x1, const MT& x2) { MT x; if (x1.n!=x2.n) int i, j, n; { n = x.n = x1.n;
  28. for (i=1; i<=n; ++i) } for (j=1; j<=n ;++j) } x.a[i][j] = x1.a[i][j] - x2.a[i][j] ; VT operator*(const MT& x, const VT& v) return x; { } if (x.n != v.n) } { MT operator*(const MT& x1, const MT& x2) cout << "\n Cap ma tran khac cap vec to, phep nhan vo { nghia"; if (x1.n!=x2.n) getch(); { return v; cout << "\nKhong thuc hien duoc phep nhan vi 2 MT khong } cung cap"; else getch(); { return x1; VT u; int n; } n = u.n = v.n ; else for (int i=1; i <=n ; ++i) { { MT x; u.b[i] = 0; int n, i, j,k; for (int j=1; j<=n; ++j) n = x.n = x1.n; u.b[i] += x.a[i][j]*v.b[j]; for (i=1; i<=n; ++i) } for (j=1; j<=n ;++j) return u; { } x.a[i][j] = 0.0 ; } for (k=1 ; k<=n; ++k) 88 MT operator!(MT x) 89 x.a[i][j] += x1.a[i][k]*x2.a[k][j] ; { } MT y; return x;
  29. int i,j,k,r,n; y.a[r][j] = tg; double tg; } n = y.n = x.n ; /* Chia hang k cho a[k,k] */ for (i=1 ; i abs(x.a[r][k]) ) r = i; { if (abs(x.a[r][k]) < 1.0E-8) tg = x.a[i][k] ; { for (j=1 ; j<=n ; ++j) cout << "\n Ma tran suy bien, khong co nghich dao" ; { getch(); x.a[i][j] -= tg*x.a[k][j] ; return x; y.a[i][j] -= tg*y.a[k][j] ; } } /* Hoan vi hang r va hang k */ } for (j=1 ; j<=n ; ++j) } { return y; tg = x.a[k][j]; } x.a[k][j] = x.a[r][j]; void main() x.a[r][j] = tg; { MT x,y,r,s; tg = y.a[k][j]; VT u,v; 90 y.a[k][j] = y.a[r][j]; 91 clrscr();
  30. cout > x; cout > y; cout > r; cout > s; cout > u; v = !((x+y)*(r-s))*u ; cout << "\nVec to v = xu " << v ; getch(); } 92