Giáo trình Lập trình hướng đối tượng (Phần 2)
Chương này trình bày những vấn đề sau đây:
Định nghĩa lớp
Tạo lập đối tượng
Truy nhập đến các thành phần của lớp
Con trỏ đối tượng
Con trỏ this
Hàm bạn
Dữ liệu thành phần tĩnh, hàm thành phần tĩnh
Hàm tạo, hàm hủy
Hàm tạo sao chép
Lớp là khái niệm trung tâm của lập trình hướng đối
tượng, nó là sự mở rộng của các khái niệm cấu trúc (struct)
của C. Ngoài các thành phần dữ liệu, lớp còn chứa các
thành phần hàm, còn gọi là phương thức (method) hoặc
hàm thành viên (member function). Lớp có thể xem như
một kiểu dữ liệu các biến, mảng đối tượng. Từ một lớp đ•
định nghĩa, có thể tạo ra nhiều đối tượng khác nhau, mỗi
đối tượng có vùng nhớ riêng.
Chương này sẽ trình bày cách định nghĩa lớp, cách xây
dựng phương thức, giải thích về phạm vi truy nhập, sử
dụng các thành phần của lớp, cách khai báo biến, mảng cấu
trúc, lời gọi tới các phương thức .
3.1. Định nghĩa lớp
Cú pháp: Lớp được định nghĩa theo mẫu :class tên_lớp
{
private: [Khai báo các thuộc tính]
[Định nghĩa các hàm thành phần (phương
thức)]
public : [Khai báo các thuộc tính]
[Định nghĩa các hàm thành phần
(phương thức)]
} ;
Thuộc tính của lớp được gọi là dữ liệu thành phần và
hàm được gọi là phương thức hoặc hàm thành viên. Thuộc
tính và hàm được gọi chung là các thành phần của lớp. Các
thành phần của lớp được tổ chức thành hai vùng: vùng sở
hữu riêng (private) và vùng dùng chung (public) để quy
định phạm vi sử dụng của các thành phần. Nếu không quy
định cụ thể (không dùng các từ khóa private và public) thì
C++ hiểu đó là private. Các thành phần private chỉ được sử
dụng bên trong lớp (trong thân của các hàm thành phần).
Các thành phần public được phép sử dụng ở cả bên trong
và bên ngoài lớp. Các hàm không phải là hàm thành phần
của lớp thì không được phép sử dụng các thành phần này.
Khai báo các thuộc tính của lớp: được thực hiện y như
việc khai báo biến. Thuộc tính của lớp không thể có kiểu
chính của lớp đó, nhưng có thể là kiểu con trỏ của lớp này,
Tóm tắt nội dung tài liệu: Giáo trình Lập trình hướng đối tượng (Phần 2)
Chương 3: Lớp Chương này trình bày những vấn đề sau đây: Định nghĩa lớp Tạo lập đối tượng Truy nhập đến các thành phần của lớp Con trỏ đối tượng Con trỏ this Hàm bạn Dữ liệu thành phần tĩnh, hàm thành phần tĩnh Hàm tạo, hàm hủy Hàm tạo sao chép Lớp là khái niệm trung tâm của lập trình hướng đối tượng, nó là sự mở rộng của các khái niệm cấu trúc (struct) của C. Ngoài các thành phần dữ liệu, lớp còn chứa các thành phần hàm, còn gọi là phương thức (method) hoặc hàm thành viên (member function). Lớp có thể xem như một kiểu dữ liệu các biến, mảng đối tượng. Từ một lớp đ• định nghĩa, có thể tạo ra nhiều đối tượng khác nhau, mỗi đối tượng có vùng nhớ riêng. Chương này sẽ trình bày cách định nghĩa lớp, cách xây dựng phương thức, giải thích về phạm vi truy nhập, sử dụng các thành phần của lớp, cách khai báo biến, mảng cấu trúc, lời gọi tới các phương thức . 3.1. Định nghĩa lớp Cú pháp: Lớp được định nghĩa theo mẫu : class tên_lớp { private: [Khai báo các thuộc tính] [Định nghĩa các hàm thành phần (phương thức)] public : [Khai báo các thuộc tính] [Định nghĩa các hàm thành phần (phương thức)] } ; Thuộc tính của lớp được gọi là dữ liệu thành phần và hàm được gọi là phương thức hoặc hàm thành viên. Thuộc tính và hàm được gọi chung là các thành phần của lớp. Các thành phần của lớp được tổ chức thành hai vùng: vùng sở hữu riêng (private) và vùng dùng chung (public) để quy định phạm vi sử dụng của các thành phần. Nếu không quy định cụ thể (không dùng các từ khóa private và public) thì C++ hiểu đó là private. Các thành phần private chỉ được sử dụng bên trong lớp (trong thân của các hàm thành phần). Các thành phần public được phép sử dụng ở cả bên trong và bên ngoài lớp. Các hàm không phải là hàm thành phần của lớp thì không được phép sử dụng các thành phần này. Khai báo các thuộc tính của lớp: được thực hiện y như việc khai báo biến. Thuộc tính của lớp không thể có kiểu chính của lớp đó, nhưng có thể là kiểu con trỏ của lớp này, Ví dụ: class A { A x; //Không cho phép, vì x có kiểu lớp A A *p ; // Cho phép, vì p là con trỏ kiểu lớp A } ; Định nghĩa các hàm thành phần: Các hàm thành phần có thể được xây dựng bên ngoài hoặc bên trong định nghĩa lớp. Thông thường, các hàm thành phần đơn giản, có ít dòng lệnh sẽ được viết bên trong định nghĩa lớp, còn các hàm thành phần dài thì viết bên ngoài định nghĩa lớp. Các hàm thành phần viết bên trong định nghĩa lớp được viết như hàm thông thường. Khi định nghĩa hàm thành phần ở bên ngoài lớp, ta dùng cú pháp sau đây: Kiểu_trả_về_của_hàm Tên_lớp::Tên_hàm(khai báo các tham số) { [nội dung hàm] } Toán tử :: được gọi là toán tử phân giải miền xác định, được dùng để chỉ ra lớp mà hàm đó thuộc vào. Trong thân hàm thành phần, có thể sử dụng các thuộc tính của lớp, các hàm thành phần khác và các hàm tự do trong chương trình. Chú ý : • Các thành phần dữ liệu khai báo là private nhằm bảo đảm nguyên lý che dấu thông tin, bảo vệ an toàn dữ liệu của lớp, không cho phép các hàm bên ngoài xâm nhập vào dữ liệu của lớp . • Các hàm thành phần khai báo là public có thể được gọi tới từ các hàm thành phần public khác trong chương trình . 3.2. Tạo lập đối tượng Sau khi định nghĩa lớp, ta có thể khai báo các biến thuộc kiểu lớp. Các biến này được gọi là các đối tượng. Cú pháp khai báo biến đối tượng như sau: Tên_lớp Danh_sách_biến ; Đối tượng cũng có thể khai báo khi định nghĩa lớp theo cú pháp sau: class tên_lớp { ... } ; Mỗi đối tượng sau khi khai báo sẽ được cấp phát một vùng nhớ riêng để chứa các thuộc tính của chúng. Không có vùng nhớ riêng để chứa các hàm thành phần cho mỗi đối tượng. Các hàm thành phần sẽ được sử dụng chung cho tất cả các đối tượng cùng lớp. 3.3. Truy nhập tới các thành phần của lớp • Để truy nhập đến dữ liệu thành phần của lớp, ta dùng cú pháp: Tên_đối_tượng. Tên_thuộc_tính Cần chú ý rằng dữ liệu thành phần riêng chỉ có thể được truy nhập bởi những hàm thành phần của cùng một lớp, đối tượng của lớp cũng không thể truy nhập. • Để sử dụng các hàm thành phần của lớp, ta dùng cú pháp: Tên_đối_tượng. Tên_hàm (Các_khai_báo_tham_số_thực_sự) Ví dụ 3.1 #include #include class DIEM { private : int x,y ; public : void nhapsl( ) { cout << "\n Nhap hoanh do va tung do cua diem:"; cin >>x>>y ; } void hienthi( ) { cout<<"\n x = " <<x<<" y = "<<y<<endl;} } ; void main() { clrscr(); DIEM d1; d1.nhapsl(); d1.hienthi(); getch(); } Ví dụ 3.2 #include #include class A { int m,n; public : void nhap( ) { cout << "\n Nhap hai so nguyen : " ; cin>>m>>n ; } int max() { return m>n?m:n; } void hienthi( ) { cout<<"\n Thanh phan du lieu lon nhat x = " <<max()<<endl;} }; void main () { clrscr(); A ob; ob.nhap(); ob.hienthi(); getch(); } Chú ý: Các hàm tự do có thể có các đối là đối tượng nhưng trong thân hàm không thể truy nhập đến các thuộc tính của lớp. Ví dụ giả sử đ• định nghĩa lớp : class DIEM { private : double x,y ; // toa do cua diem public : void nhapsl() { cout << “ Toa do x,y : “ ; cin >> x>>y ; } void in() { cout << “x =””<<x<<”y=”<<y ; } } ; Dùng lớp DIEM, ta xây dựng hàm tự do tính độ đài của đoạn thẳng đi qua hai điểm như sau : double do_dai ( DIEM d1, DIEM d2 ) { return sqrt(pow(d1.x-d2.x,2) + pow(d1.y-d2.y,2)); } Chương trình dịch sẽ báo báo lỗi đối với hàm này. Bởi vì trong thân hàm không cho phép sử dụng các thuộc tính d1.x,d2.x,d1.y của các đối tượng d1 và d2 thuộc lớp DIEM . Ví dụ 3.3 Ví dụ sau minh họa việc sử dụng hàm thành phần với tham số mặc định: #include #include class Box { private: int dai; int rong; int cao; public: int get_thetich(int lth,int wdth = 2,int ht = 3); }; int Box::get_thetich(int l, int w, int h) { dai = l; rong = w; cao = h; cout<< dai<<'\t'<< rong<<'\t'<<cao<<'\t'; return dai * rong * cao; } void main() { Box ob; int x = 10, y = 12, z = 15; cout <<"Dai Rong Cao Thetich\n"; cout << ob.get_thetich(x, y, z) << "\n"; cout << ob.get_thetich(x, y) << "\n"; cout << ob.get_thetich(x) << "\n"; cout << ob.get_thetich(x, 7) << "\n"; cout << ob.get_thetich(5, 5, 5) << "\n"; getch(); } Kết quả chương trình như sau: Dai Rong Cao Thetich 10 12 15 1800 10 12 3 360 10 2 3 60 10 7 3 210 5 5 5 125 Ví dụ 3.4 Ví dụ sau minh họa việc sử dụng hàm inline trong lớp: #include #include #include class phrase { private: char dongtu[10]; char danhtu[10]; char cumtu[25]; public: phrase(); inline void set_danhtu(char* in_danhtu); inline void set_dongtu(char* in_dongtu); inline char* get_phrase(void); }; void phrase::phrase() { strcpy(danhtu,""); strcpy(dongtu,""); strcpy(cumtu,""); } inline void phrase::set_danhtu(char* in_danhtu) { strcpy(danhtu, in_danhtu); } inline void phrase::set_dongtu(char* in_dongtu) { strcpy(dongtu, in_dongtu); } inline char* phrase::get_phrase(void) { strcpy(cumtu,dongtu); strcat(cumtu," the "); strcat(cumtu,danhtu); return cumtu; } void main() { phrase text; cout " << text.get_phrase() << "\n"; text.set_danhtu("file"); cout " << text.get_phrase()<<"\n"; text.set_dongtu("Save"); cout " << text.get_phrase()<<"\n"; text.set_danhtu("program"); cout " << text.get_phrase()<<"\n"; } Kết quả chương trình như sau: Cum tu la : -> the Cum tu la : -> the file Cum tu la : -> Save the file Cum tu la : -> Save the program Ví dụ 3.5 Ví dụ sau minh họa việc sử dụng từ khóa const trong lớp: #include #include class constants { private: int number; public: void print_it(const int data_value); }; void constants::print_it(const int data_value) { number = data_value; cout << number << "\n"; } void main() { constants num; const int START = 3; const int STOP = 6; int index; for (index=START; index<=STOP; index++) { cout<< "index = " ; num.print_it(index); cout<< "START = " ; num.print_it(START); } getch(); } Kết quả chương trình như sau: index = 3 START = 3 index = 4 START = 3 index = 5 START = 3 index = 6 START = 3 3.4. Con trỏ đối tượng Con trỏ đối tượng dùng để chứa địa chỉ của biến đối tượng, được khai báo như sau : Tên_lớp * Tên_con_ trỏ ; Ví dụ : Dùng lớp DIEM, ta có thể khai báo: DIEM *p1, *p2, *p3 ; // Khai báo 3 con trỏ p1, p2, p3 DIEM d1, d2 ; //Khai báo hai đối tượng d1, d2 DIEM d [20] ; // Khai báo mảng đối tượng Có thể thực hiện câu lệnh : p1 = &d2 ; //p1 chứa địa chỉ của d2, p1 trỏ tới d2 p2 =d ; // p2 trỏ tới đầu mảng d p3 =new DIEM //tạo một đối tượng và chứa địa chỉ của nó vào p3 Để truy xuất các thành phần của lớp từ con trỏ đối tượng, ta viết như sau : Tên_con_trỏ -> Tên_thuộc_tính Tên_con_trỏ -> Tên_hàm(các tham số thực sự) Nếu con trỏ chứa đầu địa chỉ của mảng, có thể dùng con trỏ như tên mảng. Ví dụ 3.6 #include #include class mhang { int maso; float gia; public: void getdata(int a, float b) {maso= a; gia= b;} void show() { cout << "maso" << maso<< endl; cout << "gia" << gia<< endl; } }; const int k=5; void main() { clrscr(); mhang *p = new mhang[k]; mhang *d = p; int x,i; float y; cout<<"\nNhap vao du lieu 5 mat hang :"; for (i = 0; i <k; i++) { cout <<"\nNhap ma hang va don gia cho mat hang thu " <<i+1; cin>>x>>y; p -> getdata(x,y); p++;} for (i = 0; i <k; i++) { cout <<"\nMat hang thu : " << i + 1 << endl; d -> show(); d++; } getch(); } 3.5. Con trỏ this Mỗi hàm thành phần của lớp có một tham số ẩn, đó là con trỏ this. Con trỏ this trỏ đến từng đối tượng cụ thể. Ta h•y xem lại hàm nhapsl() của lớp DIEM trong ví dụ ở trên: void nhapsl( ) { cout << "\n Nhap hoanh do va tung do cua diem : "; cin >>x>>y; } Trong hàm này ta sử dụng tên các thuộc tính x,y một cách đơn độc. Điều này dường như mâu thuẩn với quy tắc sử dụng thuộc tính. Tuy nhiên nó được lý giải như sau: C++ sử dụng một con trỏ đặc biệt trong các hàm thành phần. Các thuộc tính viết trong hàm thành phần được hiểu là thuộc một đối tượng do con trỏ this trỏ tới. Như vậy hàm nhapsl() có thể viết một cách tường minh như sau: void nhapsl( ) { cout << "\n Nhap hoanh do va tung do cua diem:" ; cin >>this->x>>this->y ; } Con trỏ this là đối thứ nhất của hàm thành phần. Khi một lời gọi hàm thành phần được phát ra bởi một đối tượng thì tham số truyền cho con trỏ this chính là địa chỉ của đối tượng đó. Ví dụ: Xét một lời gọi tới hàm nhapsl() : DIEM d1 ; d1.nhapsl(); Trong trường hợp này của d1 thì this =&d1. Do đó this - > x chính là d1.x và this-> y chính là d1.y Chú ý: Ngoài tham số đặc biệt this không xuất hiện một cách tường minh, hàm thành phần lớp có thể có các thamô1 khác được khai báo như trong các hàm thông thường. Ví dụ 3.7 #include #include class time { int h,m; public : void nhap(int h1, int m1) { h= h1; m = m1;} void hienthi(void) { cout <<h << " gio "<<m << " phut"<<endl;} void tong(time, time); }; void time::tong(time t1, time t2) { m= t1.m+ t2.m; //this->m = t1.m+ t2.m; h= m/60; //this->h = this->m/60; m= m%60; //this->m = this->m%60; h = h+t1.h+t2.h; //this->h = this->h + t1.h+t2.h; } void main() { clrscr(); time ob1, ob2,ob3; ob1.nhap(2,45); ob2.nhap(5,40); ob3.tong(ob1,ob2); cout <<"object 1 = "; ob1.hienthi(); cout <<"object 2 = "; ob2. hienthi(); cout <<"object 3 = "; ob3. hienthi(); getch(); } Chương trình cho kết quả như sau : object 1 = 2 gio 45 phut object 2 = 5 gio 40 phut object 3 = 8 gio 25 phut 3.6. Hàm bạn Trong thực tế thường x•y ra trường hợp có một số lớp cần sử dụng chung một hàm. C++ giải quyết vấn đề này bằng cách dùng hàm bạn. Để một hàm trở thành bạn của một lớp, có 2 cách viết: Cách 1: Dùng từ khóa friend để khai báo hàm trong lớp và xây dựng hàm bên ngoài như các hàm thông thường (không dùng từ khóa friend). Mẫu viết như sau : class A { private : // Khai báo các thuộc tính public : ... // Khai báo các hàm bạn của lớp A friend void f1 (...) ; friend double f2 (...) ; ... } ; // Xây dựng các hàm f1,f2,f3 void f1 (...) { ... } double f2 (...) { ... } Cách 2: Dùng từ khóa friend để xây dựng hàm trong định nghĩa lớp . Mẫu viết như sau : class A { private : // Khai báo các thuộc tính public : ... // Khai báo các hàm bạn của lớp A void f1 (...) { ... } double f2 (...) { ... } } ; Hàm bạn có những tính chất sau: - Hàm bạn không phải là hàm thành phần của lớp. - Việc truy nhập tới hàm bạn được thực hiện như hàm thông thường. - Trong thân hàm bạn của một lớp có thể truy nhập tới các thuộc tính của đối tượng thuộc lớp này. Đây là sự khác nhau duy nhất giữa hàm bạn và hàm thông thường. - Một hàm có thể là bạn của nhiều lớp. Lúc đó nó có quyền truy nhập tới tất cả các thuộc tính của các đối tượng trong các lớp này. Để làm cho hàm f trở thành bạn của các lớp A, B và C ta sử dụng mẩu viết sau : class B ; //Khai báo trước lớp A class B ; // Khai báo trước lớp B class C ; // Khai báo trước lớp C // Định nghĩa lớp A class A { // Khai báo f là bạn của A friend void f(... ) } ; // Định nghĩa lớp B class B { // Khai báo f là bạn của B friend void f(...) } ; // Định nghĩa lớp C class C { // Khai báo f là bạn của C friend void f(...) } ; // Xây dựng hàm f void f(...) { ... } ; Ví dụ 3.8 #include #include class sophuc {float a,b; public : sophuc() {} sophuc(float x, float y) {a=x; b=y;} friend sophuc tong(sophuc,sophuc); friend void hienthi(sophuc); }; sophuc tong(sophuc c1,sophuc c2) {sophuc c3; c3.a=c1.a + c2.a ; c3.b=c1.b + c2.b ; return (c3); } void hienthi(sophuc c) {cout<<c.a<<" + "<<c.b<<"i"<<endl; } void main() { clrscr(); sophuc d1 (2.1,3.4); sophuc d2 (1.2,2.3) ; sophuc d3 ; d3 = tong(d1,d2); cout<<"d1= ";hienthi(d1); cout<<"d2= ";hienthi(d2); cout<<"d3= ";hienthi(d3); getch(); } Chương trình cho kết quả như sau : d1= 2.1 + 3.4i d2= 1.2 + 2.3i d3= 3.3 + 5.7i Ví dụ 3.9 #include #include class LOP1; class LOP2 { int v2; public: void nhap(int a) { v2=a;} void hienthi(void) { cout<<v2<<"\n";} friend void traodoi(LOP1 &, LOP2 &); }; class LOP1 { int v1; public: void nhap(int a) { v1= ... mặc định là ‘\n’. + Đ• nhận đủ (n-1) ký tự. Chú ý: + Ký tự kết thúc chuỗi ‘\0’ được bổ sung vào cuối chuối nhận được. + Ký tự giới hạn vẫn còn lại trên dòng nhập để dành cho các lệnh nhận tiếp theo. + Ký tự còn lại trên dòng nhập có thể làm trôi phương thức get() dạng 3. Ví dụ: Xét đoạn chương trình: char hoten[25], diachi[50], quequan[30] ; cout<<”\nHọ tên:”; cin.get(ht,25); cout<<”\nĐịa chỉ : ”; cin.get(diachi,50); cout<<”\nQuê quán : ”; cin.get(quequan,30); cout <<”\n” <<hoten<<” ”<<diachi<<” ”<<quequan; Đoạn chương trình dùng để nhập họ tên, dịa chỉ và quê quán. Nếu gõ vào Nguyen van X thì câu lệnh get đầu tiên sẽ nhận được chuỗi “Nguyen van X” cất vào mảng hoten. Ký tự còn lại sẽ làm trôi 2 câu lệnh get tiếp theo. Do đó câu lệnh cuối cùng sẽ chỉ in ra Nguyen van X. Để khắc phục tình trạng trên, có thể dùng một trong các cách sau: + Dùng phương thức get() dạng 1 hoặc dạng 2 để lấy ra ký tự trên dòng nhập trước khi dùng get (dạng 3). + Dùng phương thức ignore để lấy ra một số ký tự không cần thiết trên dòng nhập trước khi dùng get dạng 3. Phương thức này viết như sau: cin.ignore(n) ; // Lấy ra (loại ra hay loại bỏ) n ký tự trên dòng nhập. Như vậy để có thể nhập được cả quê quán và cơ quan, cần sửa lại đoạn chương trình trên như sau: char hoten[25], diachi[50], quequan[30] ; cout<<”\nHọ tên : ”; cin.get(hoten,25); cin.get(); // Nhấn cout<<”\nĐịa chỉ : ”; cin.get(diachi,50); ignore(1); // Bỏ qua cout<<”\nQuê quán : ”; cin.get(quequan,30); cout <<”\n” <<hoten<<” ”<<diachi<<” ”<<quequan; 1.3.2. Phương thức getline() Phương thức getline để nhập một d•y ký tự từ bàn phím, được mô tả như sau: istream& cin.getline(char *str, int n, char d = ‘\n’); Ví dụ: char hoten[25], diachi[50]; cout<<”\nHọ tên:”; cin.getline(hoten,25); cout<<”\nĐịa chỉ”; cin.getline(diachi,50); cout <<”\n” <<hoten<<” ”<<diachi; 1.3.3. Phương thức ignore Phương thức ignore dùng để bỏ qua (loại bỏ) một số ký tự trên dòng nhập. Trong nhiều trường hợp, đây là việc làm cần thiết để không làm ảnh hưởng đến các phép nhập tiếp theo. Phương thức ignore được mô tả như sau: istream& cin.ignore(int n=1); Phương thức sẽ bỏ qua (loại bỏ) n ký tự trên dòng nhập. 1.4. Dòng cout và toán tử xuất << 1.4.1. Dòng cout cout là một đối tượng kiểu ostream và được nói là “bị ràng buộc tới”thiết bị chuẩn, thông thường là màn hình. Các thao tác xuất trên dòng cout đồng nghĩa với xuất dữ liệu ra màn hình. 1.4.2. Toán tử xuất << C++ định nghĩa chồng toán tử dịch trái << để gửi các ký tự sang dòng xuất . Cách dùng toán tử xuất để xuất dữ liệu từ bộ nhớ ra dòng cout như sau: cout<<biểu thức; Trong đó biểu thức biểu thị một giá trị cần xuất ra màn hình. Giá trị sẽ được biến đổi thành một d•y ký tự trước khi đưa ra dòng xuất. Chú ý: Để xuất nhiều giá trị trên một dòng lệnh, có thể viết như sau: cout<<biểu thức 1<<biểu thức 2 <<...<<biểu thức n; 1.4.3. Các phương thức định dạng 1. Phương thức int cout.width(); cho biết độ rộng quy định hiện tại. 2. Phương thức int cout.width(int n); thiết lập độ rộng quy định mới là n và trả về độ rộng quy định trước đó. Chú ý: Độ rộng quy định n chỉ có tác dụng cho một giá trị xuất. Sau đó C++ lại áp dụng độ rộng quy định bằng 0. Ví dụ với các câu lệnh: int m=1234, n=56; cout<<”\nAB”; cout.width(6); cout<<m; cout<<n; thì kết quả là: AB 123456 (giữa B và số 1 có 2 dấu cách). 3. Phương thức int cout.precision(); cho biết độ chính xác hiện tại (đang áp dụng để xuất các giá trị thức). 4. Phương thức int cout.precision(int n); thiết lập dộ chính xác sẽ áp dụng là n và cho biết độ chính xác trước đó. Độ chính xác được thiết lập sẽ có hiệu lực cho tới khi gặp một câu lệnh thiết lập độ chính xác mới. 5. Phương thức char cout.fill(); cho biết ký tự độn hiện tại đang được áp dụng. 6. Phương thức char cout.fill( char ch); quy định ký tự độn mới sẽ được dùng là ch và cho biết ký tự độn đang dùng trước đó. Ký tự độn được thiết lập sẽ có hiệu lực cho tới khi gặp một câu lệnh chọn ký tự độn mới. Ví dụ: #include #include void main() { clrscr(); float x=-3.1551, y=-23.45421; cout.precision(2); cout.fill(‘*’); cout<<”\n”; cout.width(8); cout<<x; cout<<”\n”; cout.width(8); cout<<y; getch(); } Sau khi thực hiện, chương trình in ra màn hình 2 dòng sau: ***-3.16 **-23.45 1.4.4. Cờ định dạng Mỗi cờ định dạng chứa trong một bit. Cờ có 2 trạng thái: Bật (on) – có giá trị 1, Tắt (off) – có giá trị 0. Các cờ có thể chứa trong một biến kiểu long. Trong tập tin iostream.h đ• định nghĩa các cờ sau: ios::left ios::right ios::internal ios::dec ios::oct ios::hex ios::fixed ios::scientific ios::showpos ios::uppercase ios::showpoint ios::showbase Có thể chia cờ định dạng thành các nhóm: Nhóm 1 gồm các cờ căn lề: ios::left ios::right ios::internal Cờ ios::left: khi bật cờ ios::left thì giá trị in ra nằm bên trái vùng quy định, các ký tự độn nằm sau. Cờ ios::right: khi bật cờ ios::right thì giá trị in ra nằm bên phải vùng quy định, các ký tự độn nằm trước. Chú: ý mặc định cờ ios::right bật. Cờ ios::internal: cờ ios::internal có tác dụng giống như cờ ios::right chỉ khác là dấu (nếu có) in đầu tiên. Chương trình sau minh hoạ cách dùng các cờ căn lề: Ví dụ #include #include void main() { clrscr(); float x=-87.1551, y=23.45421; cout.precision(2); cout.fill('*'); cout.setf(ios::left); //bat co ios::left cout<<"\n"; cout.width(8); cout<<x; cout<<"\n"; cout.width(8); cout<<y; cout.setf(ios::right); //bat co ios::right cout<<"\n"; cout.width(8); cout<<x; cout<<"\n"; cout.width(8); cout<<y; cout.setf(ios::internal); //bat co ios::internal cout<<"\n"; cout.width(8); cout<<x; cout<<"\n"; cout.width(8); cout<<y; getch(); } Sau khi thực hiện chương trình in ra 6 dòng như sau: -87.16** 23.45** **-87.16 ***23.45 -**87.16 ***23.45 Nhóm 2 gồm các cờ định dạng số nguyên: ios::dec ios::oct ios::hex + Khi ios::dec bật (mặc định): số nguyên được in dưới dạng cơ số 10 + Khi ios::oct bật: số nguyên được in dưới dạng cơ số 8 + khi ios::hex bật: số nguyên được in dưới dạng cơ số 16 Nhóm 3 gồm các cờ định dạng số thực: ios::fixed ios::scientific ios::showpoint Mặc định : cờ ios::fixed bật (on) và cờ ios::showpoint tắt (off). + Khi ios::fixed bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối. Ví dụ nếu độ chính xác n = 4 thì Số thực -87.1500 được in: -87.15 Số thực 23.45425 được in: 23.4543 Số thực 678.0 được in: 678 + Khi ios::fixed bật và cờ ios::showpoint bật thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được in ra đúng bằng độ chính xác n. Ví dụ nếu độ chính xác n = 4 thì Số thực -87.1500 được in: -87.1500 Số thực 23.45425 được in: 23.4543 Số thực 678.0 được in: 6780000 + Khi ios::scientific bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng khoa học. Số chữ số phần phân (sau dấu chấm) được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối. Ví dụ nếu độ chính xác n=4 thì Số thực -87.1500 được in: -87.15e+01 Số thực 23.45425 được in: 23.4543e+01 Số thực 678.0 được in: 678e+02 + Khi ios::scientific bật và cờ ios::showpoint bật thì số thực in ra dưới dạng mũ . Số chữ số phần phân (sau dấu chấm) của phần định trị được in đúng bằng độ chính xác n. Ví dụ nếu độ chính xác n=4 thì Số thực -87.1500 được in: -87.150e+01 Số thực 23.45425 được in: 23.4543e+01 Số thực 678.0 được in: 67800e+01 Nhóm 4 gồm các hiển thị: ios::uppercase ios::showpos ios::showbase Cờ ios::showpos + Nếu cờ ios::showpos tắt (mặc định) thì dấu cộng không được in trước số dương. + Nếu cờ ios::showpos tắt thì dấu cộng được in trước số dương. Cờ ios::showbase bật thì số nguyên hệ 8 được in bắt đầu bằng ký tự 0 và số nguyên hệ 16 được bắt đầu bằng các ký tự 0x. Ví dụ nếu a = 40 thì: Dạng in hệ 8 là: 050 Dạng in hệ 16 là 0x28 Cờ ios::showbase tắt (mặc định) thì không in 0 trước số nguyên hệ 8 và không 0x trước số nguyên hệ 16. Ví dụ nếu a = 40 thì: Dạng in hệ 8 là: 50 Dạng in hệ 16 là 28 Cờ ios::uppercase + Nếu cờ ios::uppercase bật thì các chữ số hệ 16 (như A, B, C,...) được in dưới dạng chữ hoa. + Nếu cờ ios::uppercase tắt (mặc định) thì các chữ số hệ 16 (như A, B, C,...) được in dưới dạng chữ thường. 1.4.5. Các phương thức bật tắt cờ Các phương thức này định nghĩa trong lớp ios. 1. Phương thức long cout.setf(long f) ; sẽ bật các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. 2. Phương thức long cout.unsetf(long f) ; sẽ tắt các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. 3. Phương thức long cout.flags(long f) ; có tác dụng giống như cout.setf(long). 4. Phương thức long cout.flags() ; sẽ trả về một giá trị long biểu thị các cờ đang bật. 1.4.6. Các bộ phận định dạng Các bộ phận định dạng (định nghĩa trong tập tin iostream.h) bao gồm: dec // như cờ ios::dec oct // như cờ ios::oct hex // như cờ ios::hex endl // xuất ký tự ‘\n’ (chuyển dòng) flush // đẩy dữ liệu ra thiết bị xuất Ví dụ Xét chương trình sau: #include #include #include void main() { clrscr(); cout.setf(ios::showbase); cout<<”ABC”<<endl<<hex<<40<<” ”<<41; getch(); } 1.4.7. Các hàm định dạng Các hàm định dạng (định nghĩa trong ) bao gồm: set(int n) // như cout.width(int n) setpecision(int n) // như cout.setpecision(int n) setfill( char ch) // như cout.setfill( char ch) setiosflags( long l) // như cout. setiosflags( long f) resetiosflags( long l) // như cout. setiosflags( long f) Các hàm định dạng có tác dụng như các phương thức định dạng nhưng được viết nối đuôi trong toán tử xuất nên tiện sử dụng hơn. Chú ý Các hàm định dạng ( cũng như các bộ phận định dạng ) cần viết trong toán tử xuất. Một hàm định dạng đứng một mình như một câu lệnh sẽ không có tác dụng dịnh dạng. Muốn sử dụng các hàm định dạng cần bổ sung vào đầu chương trình câu lệnh: #include Chương trình trong ví dụ 1.2. có thể viết lại theo các phương án sau: Phương án 1: #include #include void main() { clrscr(); cout <<setiosflags(ios::showbase); cout<<”ABC”<<endl<<hex<<40<<” “<<41; getch(); } Phương án 2: #include #include void main() { clrscr(); cout <<”ABC”<<endl<< setiosflags(ios::showbase) << hex<<40<<” “<<41; getch(); } 1.5. Các dòng chuẩn Có 4 dòng (đối tượng của các lớp stream) đ• định nghĩa trước, được cài đặt khi chương trình khởi động là: - cin dòng input chuẩn gắn với bàn phím, giống như stdin của C. - cout dòng output chuẩn gắn với màn hình, giống như stdout của C. - cerr dòng output lỗichuẩn gắn với màn hình, giống như stderr của C. - clog giống cerr nhưng có thêm bộ đệm. Chú ý Có thể dùng các dòng cerr và clog để xuất ra màn hình như đ• dùng đối với cout. Vì clog có thêm bộ đệm, nên dữ liệu được đưa vào bộ đệm. Khi đầy bộ đệm thì đưa dữ liệu bộ đệm ra dòng clog. Vì vậy trước khi kết thúc xuất cần dùng phương thức: clog.flush(); để đẩy dữ liệu từ bộ đệm ra clog. Chương trình sau minh họa cách dùng dòng clog. Chúng ta nhận thấy, nếu bỏ câu lệnh clog.flush() thì sẽ không nhìn thấy kết quả xuất ra màn hình khi chương trình tạm dừng bởi câu lệnh getch(). Ví dụ #include #include void main() { clrscr(); float x=-87.1500, y=23.45425,z=678.0; clog.setf(ios::scientific); clog.precision(4); clog.fill('*'); clog<<"\n"; clog.width(10); clog<<x; clog<<"\n"; clog.width(10); clog<<y; clog<<"\n"; clog.width(10); clog<<z; clog.flush(); getch(); } 1.6. Xuất ra máy in Bốn dòng chuẩn không gắn với máy in. Như vậy không thể dùng các dòng này để xuất dữ liệu ra máy in. Để xuất dữ liệu ra máy in (cũng như nhập, xuất trên tệp) cần tạo ra các dòng tin mới và cho nó gắn với thiết bị cụ thể. C++ cung cấp 3 lớp stream để làm điều này, đó là các lớp: ofstream dùng để tạo các dòng xuất (ghi tệp) ifstream dùng để tạo các dòng nhập (độc tệp) fstream dùng để tạo các dòng nhập, dòng xuất hoặc dòng nhập-xuất Mỗi lớp có 4 hàm tạo dùng để khai báo các dòng (đối tượng dòng tin). Để tạo một dòng xuất và gắn nó với máy in ta có thể dùng một trong những hàm tạo sau đây: ofstream Tên_dòng(int fd); ofstream Tên_dòng(int fd, chả *buf, int n); Trong đó: - Tên_dòng là tên biến đối tượng kiểu ofstream chúng ta tự đặt. - fd(file disciptor) là chỉ số tập tin. Chỉ số tập tin định sẵn đối với stdprn (máy in chuẩn) là 4. - Các tham số buf và n xác định một vùng nhớ n byte do buff trỏ tới. Vùng nhớ sẽ được làm bộ đệm cho dòng xuất. Ví dụ: Câu lệnh ofstream prn(4); sẽ tạo dòng tin xuất prn và gắn nó với máy in chuẩn. Dòng prn sẽ có bộ đệm mặc định. Dữ liệu trước hết chuyển vào bộ đệm, khi đầy bộ đêm thì dữ liệu sẽ được đẩy từ bộ đệm ra dòng prn và có thể sử dụng phương thức flush hoặc bộ phận định dạng flush. Cách viết như sau: prn.flush ;// Phương thức prn<<flush; //Bộ phận định dạng Các câu lệnh sau sẽ xuất dữ liệu ra prn (máy in) và ý nghĩa của chúng như sau: prn<<”\n Tong=”<<(4+9); //Đưa một dòng vào bộ đệm prn<<”\n Tich=”<<(4*9); // Đưa dòng tiếp theo vào bộ đệm prn.flush(); //Đẩy dữ liệu từ bộ đệm ra máy in (in 2 dòng) Các câu lệnh dưới đây sẽ xuất dữ liệu ra máy in nhưng xuất từng dòng một: prn<<”\n Tong=”<<(4+9)<<flush; // In một dòng prn<<”\n Tích=”<<(4*9)<<flush; //In dòng tiếp theo Ví dụ: Các câu lệnh char buf [512]; ofstream prn(4,buf,512); sẽ tạo dòng tin xuất prn và gắn nó với máy in chuẩn. Dòng xuất prn sử dụng 512 byte của mảng buf làm bộ đệm. Các câu lệnh dưới đây cũng xuất ra máy in: prn<<”\n Tong=”<<(4+9); //Đưa dữ liệu vào bộ đêm prn<<”\n Tich=”<<(4*9) ; //Đưa dữ liệu vào bộ đêm prn.flush(); // Xuất 2 dòng (ở bộ đệm) ra máy in Chú ý: Trước khi kết thúc chương trình, dữ liệu từ bộ đệm sẽ được tự động đẩy ra máy in. Tài liệu tham khảo 1. Ivar Jacobson, Object - Oriented Software Engineering, Addison-Wesley Publishing Company, 1992. 2. Michael Blaha, William Premerlani, Object - Oriented Modeling and Design for Database Applications, Prentice Hall, 1998. 2. Phạm Văn ất, C++ và Lập trình hướng đối tượng, NXB Khoa học và Kỹ thuật, 1999. 3. Đoàn Văn Ban, Phân tích và thiết kế hướng đối tượng, NXB Khoa học và Kỹ thuật, 1997. 4. Nguyễn Thanh Thủy, Lập trình hướng đối tượng với C++, NXB Khoa học và Kỹ thuật, 1999.
File đính kèm:
- giao_trinh_lap_trinh_huong_doi_tuong_phan_2.pdf