Bài giảng Lập trình hướng đối tượng C++ - Chương 7: Các dòng tập tin (Stream)

doc 43 trang hoanguyen 2650
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 7: Các dòng tập tin (Stream)", để 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_7_cac_dong_tap.doc

Nội dung text: Bài giảng Lập trình hướng đối tượng C++ - Chương 7: Các dòng tập tin (Stream)

  1. Chương 7 + Các phương thức: Lớp ios cung cấp một số phương thức phục Các dòng tập tin (Stream) vụ việc định dạng dữ liệu nhập xuất, kiểm tra lỗi (xem bên dưới). C đã cung cấp một thư viện các hàm nhập xuất như printf, scanf, Lớp istream gets, getch(), puts, puch(), fprintf, fscanf, fopen, fwite, fread, . Các Lớp này cung cấp toán tử nhập >> và nhiều phương thức nhập hàm này làm việc khá hiệu quả nhưng không thích ứng với cách tổ khác (xem bên dưới) như các phương thức: get, getline, read, ignore, chức chương trình hướng đối tượng. peek, seekg, tellg, C++ sử dụng khái niệm dòng tin (stream) và đưa ra các lớp dòng Lớp ostream tin để tổ chức việc nhập xuất. Dòng tin có thể xem như một dẫy các byte. Thao tác nhập là lấy (đọc) các byte từ dòng tin (khi đó gọi là Lớp này cung cấp toán tử xuất > và các phương thức nhập của các lớp ios và istream. istream ostream Cách dùng toán tử nhập để đọc dữ liệu từ dòng cin như sau: cin >> Tham_số ; Trong đó Tham_số có thể là: iostream - Biến hoặc phần tử mảng nguyên để nhận một số nguyên Lớp ios - Biến hoặc phần tử mảng thực để nhận một số thực + Thuộc tính của lớp: Trong lớp ios định nghĩa các thuộc tính - Biến hoặc phần tử mảng ký tự để nhận một ký tự được sử dụng làm các cờ định dạng cho việc nhập xuất và các cờ - Con trỏ ký tự để nhận một dẫy các ký tự khác trống kiểm tra lỗi (xem bên dưới). 364 365
  2. 123 3.14 ZHONG HAI PHONG Chú ý: Các toán tử nhập có thể viết nối đuôi để nhập nhiều giá trị (để cho gọn sẽ ký hiệu là ) trên một dòng lệnh như sau: thì kết quả nhập như sau: cin >> Tham_số_1 >> Tham_số_2 >> >> Tham_số_k ; 366 n=123 367 Cách thức nhập như sau: Bỏ qua các ký tự trắng (dấu cách, dấu x=3.14 tab, dấu chuyển dòng) đứng trước nếu có và sau đó đọc vào các ký tự tương ứng với kiểu yêu cầu. Cụ thể đối với từng kiểu như sau: ch=’Z’ Khi nhập số nguyên sẽ bỏ qua các ký tự trắng đứng trước nếu có, ten=”HONG” sau đó bắt đầu nhận các ký tự biểu thị số nguyên. Việc nhập kết thúc que = “HAI” khi gặp một ký tự trắng hoặc một ký tự không thể hiểu là thành phần Con trỏ nhập sẽ dừng tại ký tự trước từ PHONG. Các ký của số nguyên. Ví dụ nếu trên dòng vào (gõ từ bàn phím) chứa các tự còn lại sẽ được nhận trong các câu lệnh nhập tiếp theo. ký tự 123X2 và Tham_số (bên phải cin) là biến nguyên n thì n sẽ nhận giá trị 123. Con trỏ nhập sẽ dừng tại ký tự X. Ví dụ 2: Xét đoạn chương trình: Phép nhập một số thực cũng tiến hành tương tự: Bỏ qua các int m; khoảng trắng đứng trước nếu có, sau đó bắt đầu nhận các ký tự biểu float y; thị số Thực. Việc nhập kết thúc khi gặp một ký tự trắng hoặc một ký cin >> m >> y; tự không thể hiểu là thành phần của số thực. Nếu gõ: Phép nhập một ký tự cũng vậy: Bỏ qua các khoảng trắng đứng trước nếu có, sau đó nhận một ký tự khác ký tự trắng. Ví dụ nếu gõ 456 4.5 XY thì ký tự X được nhận và con trỏ nhập dừng tại thì kết quả nhập là: ký tự Y. m = 456 Phép nhập một dẫy ký tự: Bỏ qua các khoảng trắng đứng trước y = 4.5 nếu có, sau đó bắt đầu nhận từ một ký tự khác ký tự trắng. Việc nhập Ký tự vẫn còn lại trên dòng nhập. kết thúc khi gặp một ký tự trắng. Ví dụ 1: Xét đoạn chương trình: § 3. Nhập ký tự và chuỗi ký tự từ bàn phím char ten[10], que[12]; Chúng ta nhận thấy toán tử nhập >> chỉ tiện lợi khi dùng để nhập char ch; các giá trị số (nguyên, thực). Để nhập ký tự và chuỗi ký tự nên dùng int n; các phương thức sau (định nghĩa trong lớp istream): float x; cin.get cin.getline cin.ignore cin >> n >> x >> ch >> ten >> que ; 3.1. Phương thức get có 3 dạng (thực chất có 3 phương thức cùng Nếu gõ các ký tự: có tên get):
  3. Dạng 1: có thể viết chung trên một câu lệnh sau: int cin.get() ; cin.get(ch1).get(ch2); dùng để đọc một ký tự (kể cả khoảng trắng). Cách thức đọc của Dạng 3: cin.get có thể minh hoạ qua ví dụ sau: Xét các câu lệnh istream& cin.get(char *str, int n, char delim = ‘\n’); char ch; dùng để đọc một dẫy ký tự (kể cả khoảng trắng) và đưa vào vùng ch = cin.get() nhớ368 do str trỏ tới. Quá trình đọc kết thúc khi xẩy ra một trong 2 tình 369 + Nếu gõ huống sau: ABC + Gặp ký tự giới hạn (cho trong delim). Ký tự giới hạn mặc định là ‘\n’ (Enter) thì biến ch nhận mã ký tự A, các ký tự BC còn lại trên dòng vào. + Đã nhận đủ (n-1) ký tự + Nếu gõ Chú ý: A + Ký tự kết thúc chuỗi ‘\0’ được bổ sung vào dẫy ký tự nhận được thì biến ch nhận mã ký tự A, ký tự còn lại trên dòng vào. + 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 + Nếu gõ nhập tiếp theo. Chú ý: thì biến ch nhận mã ký tự (bằng 10) và dòng vào rỗng. + Cũng giống như get() dạng 2, có thể viết các phương thức get() Dạng 2: dạng 3 nối đuôi nhau trên một dòng lệnh. istream& cin.get(char &ch) ; + Ký tự còn lại trên dòng nhập có thể làm trôi phương dùng để đọc một ký tự (kể cả khoảng trắng) và đặt vào một biến kiểu thức get() dạng 3. Ví dụ xét đoạn chương trình: char được tham chiếu bởi ch. char ht[25], qq[20], cq[30]; Chú ý: cout << “\nHọ tên: “ ; + Cách thức đọc của cin.get dạng 2 cũng giống như dạng 1 cin.get(ht,25); + Do cin.get() dạng 2 trả về tham chiếu tới cin, nên có thể sử cout << “\nQuê quán: “ ; dụng các phương thức get() dạng 2 nối đuôi nhau. Ví dụ 2 nếu khai cin.get(qq,20); báo char ch1, ch2; cout << “\nCơ quan: “ ; thì 2 câu lệnh: cin.get(cq,30); cin.get(ch1); cout <<”\n” <<ht<<” “<<qq<<” “<<cq cin.get(ch2);
  4. Đoạn chương trình dùng để nhập họ tên, quê quán và cơ quan. istream& cin.getline(char *str, int n, char delim = ‘\n’); Nếu gõ: Phương thức đầu tiên làm việc như get dạng 3, sau đó nó loại Pham Thu Huong ra khỏi dòng nhập (ký tự không đưa vào dẫy ký tự thì câu lệnh get đầu tiên sẽ nhận được chuỗi “Pham Thu Huong” cất nhận được). Như vậy có thể dùng getline để nhập nhiều chuối ký tự vào mảng ht. Ký tự còn lại sẽ làm trôi 2 câu lệnh get tiếp (mà không lo ngại các câu lệnh nhập tiếp theo bị trôi). theo. Do đó câu lệnh cuối cùng sẽ chỉ in ra Pham Thu Huong. Ví dụ đoạn chương trình nhập họ tên, quê quán và cơ quan bên Để khắc phục tình trạng trên, có thể dùng một trong các cách sau: trên có thể viết như sau (bằng cách dùng getline): + Dùng phương thức get() dạng 1 hoặc dạng 2 để lấy ra ký tự char ht[25], qq[20], cq[30]; trên dòng nhập trước khi dùng get (dạng 3). 370 cout cin.getline(ht,25).getline(qq,20).get(cq,30); cout 3.3. Phương thức ignore cout << “\nCơ quan: “ ; 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 để cin.get(cq,30); không làm ảnh hưởng đến các phép nhập tiếp theo. cout <<”\n” <<ht<<” “<<qq<<” “<<cq Phương thức ignore được mô tả như sau: 3.2. Phương thức getline istream& cin.ignore(int n=1); Tương tự như get dạng 3, có thể dùng getline để nhập một dẫy ký Phương thức sẽ bỏ qua (loại bỏ) n ký tự trên dòng nhập. tự từ bàn phím. Phương thức này được mô tả như sau: 3.4. Nhập đồng thời giá trị số và ký tự
  5. Như đã nói trong §2, toán tử nhập >> bao giờ cũng để lại ký tự ts=NULL; trên dòng nhập. Ký tự này sẽ làm trôi các lệnh sots=0; nhập ký tự hoặc chuỗi ký tự bên dưới. Do vậy cần dùng: } hoặc ignore() TSINH(int n) hoặc get() dạng 1 hoặc get() dạng 2 { để loại bỏ ký tự còn sót lại ra khỏi dòng nhập trước khi thực ts=new TS[n+1]; hiện việc nhập ký tự hoặc chuỗi ký tự. sots=n; } 3.5. Ví dụ: Chương trình dưới đây sử dụng lớp TSINH (Thí sinh) với 2 phương thức xuat và nhap. ~TSINH() //CT7_04.CPP 372 { 373 // Nhập dữ liêu số và ký tự if (sots) #include { #include sots=0; struct TS ts = NULL; { } int sobd; } char ht[25]; void nhap(); float dt,dl,dh,td; void xuat(); } ; } ; class TSINH void TSINH::nhap() { { private: if (sots) TS *ts; for (int i=1; i<=sots; ++i) int sots; { public: cout << "\nThi sinh "<< i << ": " ; TSINH() cout << "\nSo bao danh: " ; {
  6. cin >> ts[i].sobd; t->nhap() ; cin.ignore(); t->xuat(); cout > ts[i].dt >> ts[i].dl >> ts[i].dh; ts[i].td = ts[i].dt + ts[i].dl + ts[i].dh; § 4. Dòng cout và toán tử xuất } 4.1. Dòng cout } Dòng cout là một đối tượng kiểu ostream đã định nghĩa trong void TSINH::xuat() C++. Đó là dòng xuất (output) chuẩn gắn với màn hình (tương tự như stdout của C). Các thao tác xuất trên dòng cout đồng nghĩa với { 374xuất dữ liệu ra màn hình. 375 if (sots) Do cout là một đối tượng của lớp ostream nên với cout chung ta { có thể sử dụng toán tử xuất >n; - con trỏ ký tự - char* (xuất chuỗi ký tự) TSINH *t = new TSINH(n);
  7. Chú ý: Các toán tử xuất có thể viết nối đuôi nhau (để xuất nhiều - Độ rộng quy định giá trị) trên một dòng lệnh như sau: - Độ chính xác cout b?a:b ; float x = -3.1416 ; thì Trình biên dịch sẽ báo lỗi. Để tránh lỗi cần dùng các dấu ngoặc tròn để bao biểu thức điều kiện như sau: char ht[] = “Tran Van Thong” ; int a=5, b=10; thì: cout b?a:b) ; Độ rộng thực tế của n là 4, của m là 3, của x là 7, của ht là 14. Tóm lại: Nên bao các biểu thức trong 2 dấu ngoặc tròn. + Độ rộng quy đinh là số vị trí tối thiểu trên màn hình dành để in 376giá trị. Theo mặc định, độ rộng quy định bằng 0. Chúng ta có thể 377 4.3. Định dạng (tạo khuôn dạng cho) dữ liệu xuất dùng phương thức cout.width() để thiết lập rộng này. Ví dụ câu lệnh: Việc định dạng dữ liệu xuất hay tạo khuôn dạng cho dữ liệu xuất cout.width(8); là một việc cần thiết. Ví dụ cần in các giá trị thực trên 10 vị trí trong sẽ thiết lập độ rộng quy định là 8. đó có 2 vị trí dành cho phần phân. + Mối quan hệ giữa độ rộng thực tế và độ rộng quy định Bản thân toán tử xuất chưa có khả năng định dạng, mà cần sử - Nếu độ rộng thực tế lớn hơn hoặc bằng độ rộng quy định thì số dụng các công cụ sau: vị trí trên màn hình chứa giá trị xuất sẽ bằng độ rộng thực tế. + Các phương thức định dạng - Nếu độ rộng thực tế nhỏ hơn độ rộng quy định thì số vị trí trên + Các các cờ định dạng màn hình chứa giá trị xuất sẽ bằng độ rộng quy định. Khi đó sẽ có + Các hàm và bộ phận định dạng một số vị trí dư thừa. Các vị trí dư thừa sẽ được độn (lấp đầy) bằng khoảng trống. Mục sau sẽ trình bầy cách định dạng giá trị xuất. + Xác định ký tự độn: Ký tự độn mặc định là dấu cách (khoảng trống). Tuy nhiên có thể dùng phương thức cout.fill() để chọn một § 5. Các phương thức định dạng ký tự độn khác. Ví dụ với các câu lệnh sau: 5.1. Nội dung định dạng giá trị xuất int n=123; // Độ rộng thực tế là 3 Nội dung định dạng là xác định các thông số: cout.fill(‘*’); // Ký tự độn là *
  8. cout.width(5); // Độ rộng quy định là 5 cout cout cout.width(6); void main()
  9. { 6.2. Công dụng của các cờ clrscr(); Có thể chia các cờ thành các nhóm: float x=-3.1551, y=-23.45421; Nhóm 1 gồm các cờ định vị (căn lề) : cout.precision(2); ios::left ios::right ios::internal cout.fill('*'); Cờ ios::left: Khi bật cờ ios:left thì giá trị in ra nằm bên trái vùng cout (Trong 6.3 sẽ trình bầy các phương thức dùng để bật, tắt các cờ) #include Các cờ có thể chứa trong một biến kiểu long. Trong tệp void main() đã định nghĩa các cờ sau: { ios::left ios::right ios::internal clrscr(); ios::dec ios::oct ios::hex float x=-87.1551, y=23.45421; ios::fixed ios::scientific ios::showpos cout.precision(2); ios::uppercase ios::showpoint ios::showbase cout.fill('*');
  10. cout.setf(ios::left); // Bật cờ ios::left - 87.16 cout << "\n" ; 23.45 cout.width(8); Nhóm 2 gồm các cờ định dạng số nguyên: cout << x; cout << "\n" ; ios::dec ios::oct ios::hex cout.width(8); + Khi ios::dec bật (mặc định): Số nguyên được in dưới dạng cơ số 10 cout << y; + Khi ios::oct bật : Số nguyên được in dưới dạng cơ số 8 cout.setf(ios::right); // Bật cờ ios::right cout << "\n" ; + Khi ios::hex bật : Số nguyên được in dưới dạng cơ số 16 cout.width(8); Nhóm 3 gồm các cờ định dạng số thực: cout << x; ios::fĩxed ios::scientific ios::showpoint cout << "\n" ; Mặc định: Cờ ios::fixed bật (on) và cờ ios::showpoint tắt (off). cout.width(8); + Khi ios::fixed bật và cờ ios::showpoint tắt thì số thực in ra dưới cout << y; 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. cout.setf(ios::internal); // // Bật cờ ios::internal Ví dụ nếu độ chính xác n = 4 thì: cout << "\n" ; cout.width(8); Số thực -87.1500 được in: -87.15 cout << x; Số thực 23.45425 được in: 23.4543 cout << "\n" ; Số thực 678.0 được in: 678 cout.width(8); + Khi ios::fixed bật và cờ ios::showpoint bật thì số thực in ra cout << y; dưới382 dạng thập phân, số chữ số phần phân (sau dấu chấm) được in ra 383 đúng bằng độ chính xác n. getch(); Ví dụ nếu độ chính xác n = 4 thì: } Số thực -87.1500 được in: -87.1500 Sau khi thực hiện chương trình in ra 6 dòng như sau: Số thực 23.45425 được in: 23.4543 -87.16 Số thực 678.0 được in: 678.0000 23.45 + Khi ios::scientific bật và cờ ios::showpoint tắt thì số thực in ra -87.16 dưới dạng mũ (dạng khoa học). Số chữ số phần phân (sau dấu chấm) 23.45 của phần định trị được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối.
  11. Ví dụ nếu độ chính xác n = 4 thì: Cờ ios::uppercase Số thực -87.1500 được in: -8.715e+01 + Nếu cờ ios::uppercase bật thì các chữ số hệ 16 (như A, B, C, Số thực 23.45425 được in: 2.3454e+01 ) được in dưới dạng chữ hoa. Số thực 678.0 được in: 6.78e+02 + 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. + 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 6.3. Các phương thức bật tắt cờ trị được in đúng bằng độ chính xác n. Các phương thức này định nghĩa trong lớp ios. Ví dụ nếu độ chính xác n = 4 thì: + Phương thức Số thực -87.1500 được in: -8.7150e+01 long cout.setf(long f) ; Số thực 23.45425 được in: 2.3454e+01 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ờ Số thực 678.0 được in: 6.7800e+01 đang bật. Thông thường giá trị f được xác định bằng cách tổ hợp các Nhóm 4 gồm các hiển thị: cờ trình bầy trong mục 6.1. ios::showpos ios::showbase ios::uppercase Ví dụ câu lệnh: Cờ ios::showpos cout.setf(ios::showpoint | ios::scientific) ; + Nếu cờ ios::showpos tắt (mặc định) thì dấu cộng không được in sẽ bật các cờ ios::showpoint và ios::scientific. trước số dương. + Phương thức + Nếu cờ ios::showpos bật thì dấu cộng được in trước số dương. long cout.unsetf(long f) ; Cờ ios::showbase 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. Thông thường giá trị f được xác định bằng cách tổ hợp các + Nếu cờ ios::showbase bật thì số nguyên hệ 8 được in bắt đầu cờ trình bầy trong mục 6.1. bằng ký tự 0 và số nguyên hệ 16 được bắt đầu bằng các ký tự 0x. Ví Ví dụ câu lệnh: dụ nếu a = 40 thì: 384 385 dạng in hệ 8 là: 050 cout.unsetf(ios::showpoint | ios::scientific) ; dạng in hệ 16 là 0x28 sẽ tắt các cờ ios::showpoint và ios::scientific. + Phương thức + Nếu cờ ios::showbase tắt (mặc định) thì không in 0 trước số nguyên hệ 8 và không in 0x trước số nguyên hệ 16. Ví dụ nếu a = 40 long cout.flags(long f) ; thì: có tác dụng giống như cout.setf(long). Ví dụ câu lệnh: dạng in hệ 8 là: 50 cout.flags(ios::showpoint | ios::scientific) ; dạng in hệ 16 là 28 sẽ bật các cờ ios::showpoint và ios::scientific.
  12. + Phương thức ABC long cout.flags() ; 0x28 0x29 sẽ trả về một giá trị long biểu thị các cờ đang bật. 7.2. Các hàm định dạng (định nghĩa trong ) Các hàm định dạng gồm: § 7. Các bộ phận định dạng và các hàm định dạng setw(int n) // như cout.width(int n) 7.1. Các bộ phận định dạng (định nghĩa trong ) setpecision(int n) // như cout.pecision(int n) Các bộ phận định dạng gồm: setfill(char ch) // như cout. fill(char ch) dec // như cờ ios::dec setiosflags(long l) // như cout.setf(long f) oct // như cờ ios::oct resetiosflags(long l) // như cout.unsetf(long f) hex // như cờ ios::hex Các hàm định dạng có tác dụng như các phương thức định dạng endl // xuất ký tự ‘\n’ (chuyển 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. flush // đẩy dữ liệu ra thiết bị xuất Chú ý 1: Các hàm định dạng (cũng như các bộ phận định dạng) Chúng có tác dụng như cờ định dạng nhưng được viết nối đuôi cần viết trong các toán tử xuất. Một hàm định dạng đứng một mình trong toán tử xuất nên tiện sử dụng hơn. như một câu lệnh sẽ không có tác dụng định dạng. Chú ý 2: Muốn sử dụng các hàm định dạng cần bổ sung vào đầu Ví dụ xét chương trình đơn giản sau: chương trình câu lệnh: //CT7_08.CPP #include // Bo phan dinh dang Ví dụ có thể thay phương thức #include cout.setf(ios::showbase) ; #include trong chương trình của mục 7.1 bằng hàm void main() cout getch(); #include } #include Chương trình sẽ in 2 dòng sau ra màn hình: void main() {
  13. clrscr(); 7.3. Ví dụ: Chương trình dưới đây minh hoạ cách dùng các hàm cout // Ham dinh dang #include // Co dinh dang #include #include void main() #include { clrscr(); #include cout << "ABC" << endl << setiosflags(ios::showbase) struct TS << hex << 40 << " " << 41; { getch(); int sobd; } char ht[25]; Dưới đây là ví dụ khác về việc dùng các hàm và bộ phận định float dt,dl,dh,td; dạng. Các câu lệnh: }; int i = 23; class TSINH cout << i << endl << setiosflags(ios::showbase) { << hex << i << dec << setfill(‘*’) private: << endl << setw(4) << i << setfill(‘0’) TS *ts; << endl << setw(5) << i ; int sots; sẽ in ra màn hình như sau: public: 23 TSINH() 0x17 { 388 389 23 ts=NULL; 00023 sots=0; }
  14. TSINH(int n) cin.get(ts[i].ht,25); { cout > ts[i].dt >> ts[i].dl >> ts[i].dh; sots=n; ts[i].td = ts[i].dt + ts[i].dl + ts[i].dh; } } ~TSINH() } { void TSINH::sapxep() { if (sots) int i,j; { for (i=1; i > ts[i].sobd; cout << setiosflags(ios::left); cin.ignore(); cout << "\n" << setw(20) << "Ho ten" << setw(8) 390 << "So BD" << setw(10) << "Tong diem"; 391 cout << "Ho ten: " ;
  15. for (int i=1; i >n; ra màn hình khi chương trình tạm dừng bởi câu lệnh getch(). TSINH *t = new TSINH(n); // Dùng clog và flush t->nhap() ; #include t->sapxep(); #include t->xuat(); void main() getch(); { delete t; clrscr(); } float x=-87.1500, y=23.45425,z=678.0; clog.setf(ios::scientific); § 8. Các dòng tin chuẩn clog.precision(4); Có 4 dòng tin (đố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. Hai trong số đó đã nói ở clog.fill('*'); trên là: clog << "\n"; cin dòng input chuẩn gắn với bàn phím, giống như stdin của clog.width(10); C. clog << x; cout dòng output chuẩn gắn với màn hình, giống như stdout của C. clog << "\n"; clog.width(10); 392 393
  16. clog << y; Ví dụ 1 câu lệnh: clog << "\n"; ofstream prn(4) ; clog.width(10); 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ó clog << z; 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. Để chủ động clog.flush(); yêu cầu đẩy dữ liệu từ bộ đệm ra dòng prn có thể sử dụng phương getch(); 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 § 9. Xuất ra máy in Các câu lệnh sau sẽ xuất dữ liệu ra prn (máy in) và ý nghĩa của Trong số 4 dòng tin chuẩn không dòng nào gắn với máy in. Như chúng như sau: vậy không thể dùng các dòng này để xuất dữ liệu ra máy in. Để xuất prn << “\nTong = “ << (4+9) ; // Đưa một dòng vào bộ đệm 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 prn << “\nTich =“ << (4*9); // Đưa tiếp dòng thứ 2 vào bộ đệm tin mới và cho nó gắn với thiết bị cụ thể. C++ cung cấp 3 lớp stream prn.flush(); // Đẩy dữ liệu từ bộ đệm ra máy in (in 2 dòng) để làm điều này, đó là các lớp: Các câu lệnh dưới đây cũng xuất dữ liệu ra máy in nhưng sẽ in ifstream dùng để tạo dòng nhập từng dòng một: ofstream dùng để tạo dòng xuất prn << “\nTong = “ << (4+9) << flush ; // In một dòng fstream dùng để tạo dòng nhập, dòng xuất hoặc dòng nhập-xuất prn << “\nTich = “ << (4*9) ; << flush // In dòng thứ hai Mỗi lớp có 4 hàm tạo dùng để khai báo các dòng tin (đối tượng Ví dụ 2: Các câu lệnh dòng tin). Trong mục sau sẽ nói thêm về các hàm tạo này. char buf[1000] ; Để 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 các hàm tạo sau: ofstream prn(4,buf,1000) ; ofstream Tên_dòng_tin(int fd) ; 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 1000 byte của mảng buf làm bộ đệm. Các câu lệnh dưới đây ofstream Tên_dòng_tin(int fd, char *buf, int n) ; cũng xuất dữ liệu ra máy in: Trong đó: prn << “\nTong = “ << (4+9) ; // Đưa dữ liệu vào bộ đệm + Tên_dòng_tin là tên biến đối tượng kiểu ofstream hay gọi là tên prn << “\nTich = “ << (4*9) ; // Đưa dữ liệu vào bộ đệm dòng xuất do chúng ta tự đặt. prn.flush() ; // Xuất 2 dòng (ở bộ đệm) ra máy in + fd (file descriptor) 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. 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. + Các tham số buf và n xác định một vùng nhớ n byte do buf trỏ tới. Vùng nhớ sẽ được dùng làm bộ đệm cho dòng xuất. 394 395
  17. Chương trinh minh hoạ: Chương trình dưới đây tương tự như { chương trình trong mục 7.3 (chỉ sửa đổi phương thức xuất) nhưng ts=new TS[n+1]; thay việc xuất ra màn hình bằng xuất ra máy in. sots=n; //CT7_08B.CPP } // Xuat ra may in ~TSINH() // Bo phan dinh dang // Ham dinh dang { #include if (sots) #include { #include sots=0; struct TS ts = NULL; { } int sobd; } char ht[25]; void nhap(); float dt,dl,dh,td; void sapxep(); } ; void xuat(); class TSINH } ; { void TSINH::nhap() private: { TS *ts; if (sots) int sots; for (int i=1; i > ts[i].sobd; sots=0; cin.ignore(); } cout << "Ho ten: " ; TSINH(int n) cin.get(ts[i].ht,25);
  18. cout > ts[i].dt >> ts[i].dl >> ts[i].dh; setw(4) >n; TS tg; TSINH *t = new TSINH(n); tg=ts[i]; t->nhap() ; ts[i]=ts[j]; t->sapxep(); ts[j]=tg; t->xuat(); } getch(); } delete t; void TSINH::xuat() { } ostream prn(4); if (sots) § 10. Làm việc với tệp { 10.1. Các lớp dùng để nhập, xuất dữ liệu lên tệp prn << "\nDanh sach thi sinh:" ; Như đã nói ở trên, C++ cung cấp 4 dòng tin chuẩn để làm việc với prn.precision(1); bàn phím và màn hình. Muốn nhập xuất lên tệp chúng ta cần tạo các prn << setiosflags(ios::left); dòng tin mới (khai báo các đối tượng Stream) và gắn chúng với một prn << "\n" << setw(20) <<"Ho ten" << setw(8) tệp cụ thể. C++ cung cấp 3 lớp stream để làm điều này, đó là các lớp: << "So BD"<< setw(10) << "Tong diem"; ofstream dùng để tạo các dòng xuất (ghi tệp) for (int i=1; i<=sots; ++i) ifstream dùng để tạo các dòng nhập (đọc tệp)
  19. fstream dùng để tạo các dòng nhập, dòng xuất hoặc dòng 1. Dùng lớp fstream để tạo ra một dòng nhập-xuất và gắn nó với nhập-xuất một tệp cụ thể. Sơ đồ dẫn xuất các lớp như sau: 2. Thực hiện nhập dữ liệu từ dòng nhập-xuất vừa tạo như thể nhập dữ liệu từ dòng nhập chuẩn cin. 398 ios 399 3. Thực hiện xuất dữ liệu ra dòng nhập-xuất vừa tạo như thể xuất dữ liệu ra dòng xuất chuẩn cout. Nhận xét: Như vậy: ostream fstreambase istream 1. Việc xuất dữ liệu ra máy in hoặc lên tệp được thực hiện hoàn toàn giống như xuất dữ liệu ra dòng xuất chuẩn cout (màn hình). 2. Việc đọc dữ liệu từ tệp được thực hiện hoàn toàn giống như ofstream ifstream nhập dữ liệu từ dòng nhập chuẩn cin (bàn phím). fstream § 11. Ghi dữ liệu lên tệp 11.1. Lớp ofstream 10.2. Ghi dữ liệu lên tệp Để ghi dữ liệu lên tệp chúng ta sử dụng lớp ofstream. Lớp Thủ tục ghi dữ liệu lên tệp như sau: ofstream thừa kế các phương thức của các lớp ios và ostream. Nó 1. Dùng lớp ofstream để tạo ra một dòng xuất và gắn nó với một cũng thừa kế phương thức: tệp cụ thể. Khi đó việc xuất dữ liệu ra dòng này đồng nghĩa với việc close ghi dữ liệu lên tệp. của lớp fstreambase. Ngoài ra lớp ofstream có thêm các hàm tạo và 2. Thực hiện xuất dữ liệu ra dòng xuất vừa tạo như thể xuất dữ các phương thức sau: liệu ra dòng xuất chuẩn cout. 1. Hàm tạo: 10.3. Đọc dữ liệu từ tệp ofstream() ; // Không đối Thủ tục đọc dữ liệu từ tệp như sau: dùng để tạo một đối tượng ofstream (dòng xuất), chưa gắn với tệp. 1. Dùng lớp ifstream để tạo ra một dòng nhập và gắn nó với một 2. Hàm tạo: tệp cụ thể. Khi đó việc nhập dữ liệu từ dòng này đồng nghĩa với việc ofstream(const char *fn, int mode = ios::out, đọc dữ liệu từ tệp. int prot = filebuf::openprot);dùng để tạo một đối 2. Thực hiện nhập dữ liệu từ dòng nhập vừa tạo như thể nhập dữ tượng ofstream, mở tệp có tên fn để ghi và gắn đối tượng vừa tạo với liệu từ dòng nhập chuẩn cin. tệp được mở. 10.4. Đọc - ghi dữ liệu đồng thời trên tệp + Tham số fn cho biết tên tệp. Thủ tục đọc-ghi dữ liệu đồng thời trên tệp như sau:
  20. + Tham số mode có giá trị mặc định là ios::out (mở để ghi). Tham + Cách 1: Dùng hàm tạo 2 để xây dựng một dòng xuất, mở một số này có thể là một hợp của các giá trị sau: tệp để ghi và gắn tệp với dòng xuất. Sau đó dùng toán tử xuất << và ios::binary ghi theo kiểu nhị phân (mặc định theo kiểu văn các phương thức để xuất dữ liệu ra dòng xuất vừa tạo như thể xuất bản) dữ liệu ra cout (xem các mục trên). ios::out ghi tệp, nếu tệp đã có thì nó bị xoá + Cách 2: Dùng hàm tạo 1 để xây dựng một dòng xuất. Sau đó 400 dùng phương thức open để mở một tệp cụ thể và cho gắn với dòng401 ios::app ghi bổ sung vào cuối tệp xuất vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng ios::ate chuyển con trỏ tệp tới cuối tệp sau khi mở tệp ta có thể dùng phương thức close để chấm dứt mọi ràng buộc giữa ios::trunc xoá nội dung của tệp nếu nó tồn tại dòng xuất và tệp. Sau đó có thể gắn dòng xuất với tệp khác. Theo cách này, có thể dùng một dòng xuất (đối tượng ofstream) để xuất dữ ios::nocreate nếu tệp chưa có thì không làm gì (bỏ qua) liệu lên nhiều tệp khác nhau. ios::noreplace nếu tệp đã có thì không làm gì (bỏ qua) + Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số 11.3. Ví dụ này có thể bỏ qua vì nó đã được gán một giá trị mặc định. Chương trình 1: Chương trình dưới đây sẽ nhập danh sách n thí 3. Hàm tạo: sinh. Thông tin thí sinh gồm: Họ tên, tỉnh hoặc thành phố cư trú, số báo danh, các điểm toán lý hoá. Dữ liệu thí sinh được ghi trên 2 tệp: ofstream(int fd); Tệp DS1.DL ghi thí sinh theo thứ tự nhập từ bàn phím, tệp DS2.DL dùng để tạo một đối tượng ofstream và gắn nó với một tệp có chỉ số ghi thí sinh theo thứ tự giảm của tổng điểm. Cấu trúc của 2 tệp như fd đang mở. sau: (Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem Dòng đầu ghi một số nguyên bằng số thí sinh. cuốn Kỹ thuật Lập trình C của tác giả). Các dòng tiếp theo ghi dữ liệu của thí sinh. Mỗi thí sinh ghi trên 2 4. Hàm tạo: dòng, dòng 1 ghi họ tên trên 24 vị trí và tên tỉnh trên 20 vị trí. Dòng ofstream(int fd, char *buf, int n); 2 ghi số báo danh (6 vị trí), các điểm toán, lý , hoá và tổng điểm dùng để tạo một đối tượng ofstream , gắn nó với một tệp có chỉ số fd (mỗi điểm ghi trên 6 vị trí trong đó một vị trí chứa phần phân). đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm. Chương trình sử dụng lớp TS (Thí sinh) có 3 phương thức: Nhập, sắp xếp và ghi tệp. Cách ghi tệp sử dụng ở đây là cách 1: Dùng hàm 5. Phương thức: tạo dạng 2 của lớp ofstream. void open(const char *fn, int mode = ios::out, Chương trình 2 ngay bên dưới cũng giải quyết cùng bài toán nêu int prot = filebuf::openprot);dùng để mở tệp có trên nhưng sử dụng cách ghi tệp thứ 2 (dùng hàm tạo 1 và phương tên fn để ghi và gắn nó với đối tượng ofstream. Các tham số của thức open) phương thức có cùng ý nghĩa như trong hàm tạo thứ 2. Một điều đáng nói ở đây là việc nhập một chuỗi ký tự (như họ tên 11.2. Các cách ghi tệp và tên tỉnh) bằng các phương thức get hoặc getline chưa được thuận tiện, vì 2 lý do sau: thứ nhất là các phương thức này có thể bị ký tự Có 2 cách chính sau: chuyển dòng (còn sót trên cin) làm trôi. Thứ hai là các phương thức
  21. này có thể để lại một số ký tự trên dòng cin (nếu số ký tự gõ nhiều { hơn so với quy định) và các ký tự này sẽ gây ảnh hưởng đến các cin.ignore(); phép nhập tiếp theo. Để khắc phục các nhược điểm trên, chúng ta đưa vào 2 chương trình trên hàm getstr để nhập chuỗi ký tự từ bàn break; phím. } } //CT7_10.CPP } 402// Ghi Tep 403 #include struct TSINH #include { #include char ht[25]; #include char ttinh[21]; #include int sobd; #include float dt,dl,dh,td; void getstr(char *str,int n) } ; { class TS char tg[21]; { while(1) // Bỏ qua Enter và nhập tối đa n-1 ký tự private: { int sots; cin.get(str,n); TSINH *ts; if (str[0]) public: break; TS() else { cin.ignore(); sots=0; } ts = NULL; while(1) // Loại các ký tự còn lại ra khỏi dòng nhập cin } { void nhap(); cin.get(tg,20); void sapxep(); if (tg[0]==0) void ghitep(char *ttep);
  22. }; TSINH tg = ts[i]; void TS::nhap() ts[i] = ts[j]; { ts[j] = tg; cout > sots ; } int n=sots; void TS::ghitep(char *ttep) ts = new TSINH[n+1]; { for (int i=1; i > ts[i].sobd ; > ts[i].dt >> ts[i].dl >> ts[i].dh ; << setw(6) << ts[i].td ; ts[i].td =ts[i].dt + ts[i].dl + ts[i].dh ; } } f.close(); } } void TS::sapxep() void main() { { int n = sots; clrscr(); for (int i=1; i< n; ++i) TS t; for (int j=i+1; j<= n; ++j) t.nhap(); if (ts[i].td < ts[j].td) t.ghitep("DS1.DL"); {
  23. t.sapxep(); cin.get(tg,20); t.ghitep("DS2.DL"); if (tg[0]==0) cout { #include 406 char ht[25]; 407 #include char ttinh[21]; #include int sobd; #include float dt,dl,dh,td; #include } ; void getstr(char *str,int n) class TS { { char tg[21]; private: while(1) int sots; { TSINH *ts; cin.get(str,n); public: if (str[0]) TS() break; { else sots=0; cin.ignore(); ts = NULL; } } while(1) void nhap(); { void sapxep();
  24. void ghitep(char *ttep); { }; TSINH tg = ts[i]; void TS::nhap() ts[i] = ts[j]; { ts[j] = tg; cout > sots ; } int n=sots; void TS::ghitep(char *ttep) ts = new TSINH[n+1]; { for (int i=1; i > ts[i].sobd ; { cout > ts[i].dt >> ts[i].dl >> ts[i].dh ; f.open(ttep) ; ts[i].td =ts[i].dt + ts[i].dl + ts[i].dh ; } } else } exit(1); void TS::sapxep() } { f << sots ; int n = sots; f << setprecision(1) << setiosflags(ios::showpoint); for (int i=1; i< n; ++i) for (int i=1; i<=sots; ++i) for (int j=i+1; j<= n; ++j) { if (ts[i].td < ts[j].td) f << endl << setw(24) << ts[i].ht << setw(20) << ts[i].ttinh ;
  25. f << endl << setw(6) << ts[i].sobd 1. Hàm tạo: << setw(6) << ts[i].dt ifstream() ; // Không đối << setw(6) << ts[i].dl dùng để tạo một đối tượng ifstream (dòng nhập), chưa gắn với tệp. << setw(6) << ts[i].dh 2. Hàm tạo: << setw(6) << ts[i].td ; ifstream(const char *fn, int mode = ios::in, } int prot = filebuf::openprot); f.close(); dùng để tạo một đối tượng ifstream, mở tệp có tên fn để đọc và gắn } đối tượng vừa tạo với tệp được mở. + Tham số fn cho biết tên tệp. void main() + Tham số mode có giá trị mặc định là ios::in (mở để đọc). Tham { số này có thể là một hợp của các giá trị sau: clrscr(); ios::binary đọc theo kiểu nhị phân (mặc định theo kiểu văn bản) TS t; ios::ate chuyển con trỏ tệp tới cuối tệp sau khi mở tệp t.nhap(); + Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số t.ghitep("DS1.DL"); này có thể bỏ qua vì nó đã được gán một giá trị mặc định. 410 t.sapxep(); 3. Hàm tạo: 411 t.ghitep("DS2.DL"); ifstream(int fd); cout << "\n Hoan thanh"; dùng để tạo một đối tượng ifstream và gắn nó với một tệp có chỉ số fd đang mở. getch(); (Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem } cuốn Kỹ thuật Lập trình C của tác giả) 4. Hàm tạo: § 12. Đọc dữ liệu từ tệp ifstream(int fd, char *buf, int n); 12.1. Lớp ifstream dùng để tạo một đối tượng ifstream , gắn nó với một tệp có chỉ số fd Để đọc dữ liệu từ tệp chúng ta sử dụng lớp ifstream. Lớp ifstream đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm. thừa kế các phương thức của các lớp ios và istream. Nó cũng thừa kế 5. Phương thức: phương thức: void open(const char *fn, int mode = ios::in, close int prot = filebuf::openprot); của lớp fstreambase. Ngoài ra lớp ifstream có thêm các hàm tạo và các phương thức sau:
  26. dùng để mở tệp có tên fn để đọc và gắn nó với đối tượng ifstream. đây dùng phương thức eof để xác định độ dài (số byte) của tệp Các tham số của phương thức có cùng ý nghĩa như trong hàm tạo TC.EXE (chú ý cần dùng kiểu đọc nhị phân): thứ 2. //CT7_14.CPP 12.2. Các cách đọc tệp // Do dai tep Có 2 cách chính sau: #include + Cách 1: Dùng hàm tạo 2 để xây dựng một dòng nhập, mở một #include tệp để đọc và gắn tệp với dòng nhập. Sau đó dùng toán tử nhập >> #include và các phương thức để nhập dữ liệu từ dòng nhập vừa tạo như thể #include nhập dữ liệu từ cin (xem các mục trên) void main() + Cách 2: Dùng hàm tạo 1 để xây dựng một dòng nhập. Sau đó dùng phương thức open để mở một tệp cụ thể và cho gắn với dòng { nhập vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng clrscr(); ta có thể dùng phương thức close để chấm dứt mọi ràng buộc giữa long dd=0; char ch; dòng nhập và tệp. Sau đó có thể gắn dòng nhập với tệp khác. Theo ifstream f("TC.EXE",ios::in | ios::binary); cách này, có thể dùng một dòng nhập (đối tượng ifstream) để nhập dữ liệu từ nhiều tệp khác nhau. if (f.bad()) { 12.3. Kiểm tra sự tồn tại của tệp, kiểm tra cuối tệp cout << "\nTep TC.EXE khong ton tai"; 412 413 + Khi mở một tệp để đọc mà tệp không tồn tại thì sẽ phát sinh lỗi, getch(); khi đó phương thức bad trả về giá trị khác không. Ví dụ để kiểm tra xem tệp DSTS (Danh sách thí sinh) đã tồn tại hay không có thể exit(1); dùng đoạn chương trình: } ifstream fin(“DSTS”); while(f.get(ch),!f.eof()) ++dd; if (fin.bad()) cout << "\n Do dai TC.EXE: " << dd; { getch(); cout << “\nTep DSTS không tồn tai”; } exit(1); 12.4. Ví dụ } Chương trình dưới đây sẽ: + Trong quá trình đọc, con trỏ tệp sẽ chuyển dần về cuối tệp. Khi + Đọc danh sách thí sinh từ tệp DS1.DL do chương trình trong con trỏ tệp đã ở cuối tệp (hết dữ liệu) mà vẫn thực hiện một lệnh đọc muc §11 tạo ra. thì phương thức eof sẽ cho giá trị khác không. Chương trình dưới + In danh sách thí sinh vừa đọc.
  27. + Sắp xếp dẫy thí sinh (vừa nhập từ tệp) theo thứ tự giảm của int sots; tổng điểm. TSINH *ts; + Ghi danh sách thí sinh sau khi sắp xếp lên tệp DS3.DL public: + Đọc danh sách thí sinh từ tệp DS3.DL TS() + In danh sách thí sinh đọc từ tệp DS3.DL { Chương trình sử dụng lớp TS (Thí sinh) có 4 phương thức: sots=0; void xuat(); ts = NULL; void sapxep(); } void ghitep(char *ttep); void xuat(); void doctep(char *ttep); void sapxep(); //CT7_12.CPP void ghitep(char *ttep); // Doc tep void doctep(char *ttep); #include }; #include void TS::xuat() #include { #include cout cout for (int i=1; i<=sots; ++i) 414 415 struct TSINH { { cout << "\nThi sinh thu: " << i ; char ht[25]; cout << "\nHo ten: " << ts[i].ht ; char ttinh[21]; cout << "\nTinh - thanh pho: " << ts[i].ttinh ; int sobd; cout << "\nSo bao danh: " << ts[i].sobd ; float dt,dl,dh,td; cout << "\nCac diem toan, ly, hoa: " } ; << setw(5) << ts[i].dt class TS << setw(5) << ts[i].dl { << setw(5) << ts[i].dh ; private: cout << "\nTong diem: " << ts[i].td ;
  28. } else } exit(1); void TS::sapxep() } { f > sots ; } f.ignore();
  29. if (ts!=NULL) delete ts; Để đọc ghi đồng thời trên tệp, chúng ta sử dụng lớp fstream. Lớp ts = new TSINH[sots+1]; fstream thừa kế các phương thức của các lớp ofstream và ifstream. Ngoài ra lớp fstream có các hàm tạo và phương thức sau: for (int i=1; i > ts[i].sobd >> ts[i].dt >> ts[i].dl dùng để tạo một đối tượng fstream (dòng nhập-xuất), chưa gắn với >> ts[i].dh >> ts[i].td ; tệp. f.ignore(); 2. Hàm tạo: } fstream(const char *fn, int mode, f.close(); int prot = filebuf::openprot); } dùng để tạo một đối tượng fstream, mở tệp có tên fn và gắn đối tượng vừa tạo với tệp được mở. void main() + Tham số fn cho biết tên tệp. { + Tham số mode quy định các kiểu truy nhập và có thể là tổ hợp clrscr(); của các giá trị sau: TS t; ios::binary đọc-ghi theo kiểu nhị phân (mặc định theo kiểu t.doctep("DS1.DL"); văn bản). t.xuat(); ios::out ghi tệp, nếu tệp đã có thì nó bị xoá t.sapxep(); ios::in đọc tệp t.ghitep("DS3.DL"); ios::app ghi bổ sung vào cuối tệp t.doctep("DS3.DL"); ios::ate chuyển con trỏ tệp về cuối sau khi mở ios::trunc xoá nội dung của tệp nếu nó tồn tạI t.xuat(); ios::nocreate nếu tệp chưa có thì không làm gì (bỏ qua) cout << "\n Hoan thanh"; ios::noreplace nếu tệp đã có thì không làm gì (bỏ qua) 418 getch(); 419 Chú ý: } + Tham số mode không có giá trị mặc định. + Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số § 13. Đọc ghi đồng thời trên tệp này có thể bỏ qua vì nó đã được gán một giá trị mặc định. 13.1. Lớp fstream 3. Hàm tạo: fstream(int fd);
  30. dùng để tạo một đối tượng fstream và gắn nó với một tệp có chỉ số fd fstream f; đang mở. f.open(“DU_LIEU”, ios::in | ios::out) ; (Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem cuốn Kỹ thuật Lập trình C của tác giả) 13.3. Di chuyển con trỏ tệp 4. Hàm tạo: 13.3.1. Để di chuyển con trỏ tệp trên dòng xuất, chúng ta sử fstream(int fd, char *buf, int n); dụng các phương thức sau (của lớp ostream) : dùng để tạo một đối tượng fstream , gắn nó với một tệp có chỉ số fd 1. Phương thức đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm. ostream& seekp(long n) ; 5. Phương thức: sẽ chuyển con trỏ tệp tới vị trí (byte) thứ n (số thứ tự các byte tính void open(const char *fn, int mode, từ 0). int prot = filebuf::openprot); 2. Phương thức dùng để mở tệp có tên fn và gắn nó với đối tượng fstream. Các tham ostream& seekp(long offset, seek_dir dir) ; số của phương thức có cùng ý nghĩa như trong hàm tạo thứ 2. sẽ chuyển con trỏ tệp tới vị trí offset kể từ vị trí xuất phát dir. Giá trị Chú ý: Tham số mode không có giá trị mặc định. của offset có thể âm, còn dir có thể nhận một trong các giá trị sau: ios::beg xuất phát từ đầu tệp 13.2. Các cách đọc-ghi đồng thời trên tệp ios::end xuất phát từ cuối tệp Có 2 cách chính sau: ios::cur xuất phát từ vị trí hiện tại của con trỏ tệp + Cách 1: Dùng hàm tạo 2 để xây dựng một dòng nhập-xuất, mở 3. Phương thức một tệp để đọc-ghi và gắn tệp với dòng nhập-xuất. Sau đó dùng toán tử nhập >> , toán tử xuất >> và các phương thức nhập, xuất để long teelp() ; nhập, xuất dữ liệu ra dùng nhập-xuất vừa tạo (như đối với các dòng cho biết vị trí hiện tại của con trỏ tệp. chuẩn cin và cout). Ví dụ: 13.3.2. Để di chuyển con trỏ tệp trên dòng nhập, chúng ta sử fstream f(“DU_LIEU”, ios::in | ios::out) ; dụng các phương thức sau (của lớp istream): + Cách 2: Dùng hàm tạo 1 để xây dựng một dòng nhập-xuất. Sau 4. Phương thức đó dùng phương thức open để mở một tệp cụ thể (để đọc và ghi) và istream& seekg(long n) ; cho gắn với dòng nhập-xuất vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng ta có thể dùng phương thức close để chấm sẽ chuyển con trỏ tệp tới vị trí (byte) thứ n (số thứ tự các byte tính 420 421 dứt mọi ràng buộc giữa dòng nhập-xuất và tệp. Sau đó có thể gắn từ 0) dòng nhập-xuất với tệp khác. Theo cách này, có thể dùng một dòng 5. Phương thức nhập-xuất (đối tượng fstream) để đọc-ghi dữ liệu từ nhiều tệp khác istream& seekg(long offset, seek_dir dir) ; nhau. Ví dụ:
  31. sẽ chuyển con trỏ tệp tới vị trí offset kể từ vị trí xuất phát dir. Giá trị #include của offset có thể âm, còn dir có thể nhận một trong các giá trị sau: void main() ios::beg xuất phát từ đầu tệp { ios::end xuất phát từ cuối tệp char ht[25], ttinh[21], ttep[40]; ios::cur xuất phát vị trí hiện tại của con trỏ tệp int sobd,stt ; 6. Phương thức float dt, dl, dh, td; long teelg() ; fstream f; cho biết vị trí hiện tại của con trỏ tệp. cout > ttep; có thể sử dụng cả 6 phương thức nêu trên. f.open(ttep,ios::out|ios::in|ios::noreplace); 13.4. Ví dụ if (f.bad()) Ví dụ 1. Trong §12 đã viết chương trình xác định độ dài của tệp { TC.EXE. Dưới đây là một phương án khác đơn giản hơn: cout } #include stt=0 ; #include f 422 while(1) 423 #include { #include
  32. ++stt; ++stt; cout > sobd >> dt >> dl >> dh >> td; cout > sobd >> dt>> dl >> dh ; 1) f << endl; f << setw(24) << ht << setw(20) << ttinh ; } f << endl << setw(6) << sobd f.close(); << setw(6) << dt cout << "\n Hoan thanh"; << setw(6) << dl getch(); << setw(6) << dh } << setw(6) << td ; } f.seekg(0); § 14. Xử lý lỗi stt=0; Khi làm việc với tệp không phải mọi việc đều trôi chẩy mà thường xẩy ra nhiều điều trục trặc, chẳng hạn: clrscr(); 1. Mở một tệp để đọc nhưng tệp không tồn tại. cout << "\nDanh sach thi sinh\n"; 2. Đọc dữ liệu nhưng con trỏ tệp đã ở cuối tệp cout << setprecision(1) << 3. Ghi dữ liệu nhưng hết không gian đĩa (đĩa đầy). setiosflags(ios::showpoint); 4. Tạo tệp nhưng đia hỏng, hoặc đĩa cấm ghi hoặc đĩa đầy. while(1) 5. Dùng tên tệp không hợp lệ { 6. Định thực hiện một thao tác nhưng tệp lại không được mở ở f.getline(ht,25).getline(ttinh,21); mode phù hợp để thực hiện thao tác đó. if (f.eof()) break; 424 425
  33. Tóm lại khi làm việc với tệp thường gặp nhiều lỗi khác nhau, nếu cin >> ten_tep ; không biết cách phát hiện xử lý thì chương trình sẽ dẫn đến rối loạn ifstream f(ten_tep); hoặc cho kết quả sai. Trong lớp ios của C++ có nhiều phương thức 426 if (f.bad()) 427 cho phép phát hiện lỗi khi làm việc với tệp. Đó là: { 1. Phương thức cout > ten_tep ; cuối cùng có lỗi, trái lại phương thức có giá trị bằng 0. ofstream f(ten_tep); 3. Phương thức if (f.bad()) int bad() ; { Phương thức bad() trả về giá trị khác không khi một phép nhập cout << “\n Không tạo được tệp << ten_tep ; xuất không hợp lệ hoặc có lỗi mà chưa phát hiện được, trái lại exit(1) ; phương thức có giá trị bằng 0. } 4. Phương thức Ví dụ 3. Để xác định độ dài của tệp, có thể dùng phương thức int good() ; eof() và thuật toán sau: Phương thức good() trả về giá trị khác không nếu mọi việc đều tốt + Đọc một byte (chú ý phải đọc theo kiểu nhị phân) đẹp ( không có lỗi nào xẩy ra). Khi có một lỗi nào đó thì phương thức có giá trị bằng 0. + Nếu việc đọc thành công ( eof()=0 ) thì cộng thêm một vào bộ đếm. Nếu việc đọc không thành ( eof() != 0 ) thì kết thúc vùng lặp. 5. Phương thức Thuật toán trên được thể hiện bằng đoạn chương trình sau: void clear() ; ifstream f(ten_tep, ios::in | ios::binary) ; dùng để tắt tất cả các bit lỗi. long dem = 0 ; char ch; Ví dụ 1. Khi mở tệp để đọc cần kiểm tra xem tệp có tồn tại while (1) không. Để làm điều đó, chúng ta có thể dùng đoạn chương sau: { char ten_tep[40] ; f.get(ch) ; cout << “\n Cho biết tên tệp: “ ; if (!eof()) dem++ ;
  34. else break; //CT7_15.CPP } // Sao tep #include § 15. Nhập xuất nhị phân #include #include 15.1. Chọn kiểu nhập xuất nhị phân 428 429 Kiểu nhập xuất mặc định là văn bản. Để chọn kiểu nhập xuất nhị #include phân, thì trong tham số mode (của hàm tạo dạng 2 và phương thức void main() open) cần chứa giá trị: { ios::binary clrscr(); Ví dụ muốn mở tệp “DSTS.DL” để đọc-ghi theo kiểu nhị phân và char tep_nguon[40], tep_dich[40] ; gắn tệp với dòng nhập-xuất fs , ta dùng câu lệnh sau: char ch; fstream fs(“DSTS.DL” , ios::in | ios::out | ios::binary) ; fstream fnguon, fdich; cout > tep_nguon; 15.2. Đọc, ghi ký tự cout > tep_dich; + Để ghi một ký tự lên tệp có thể dùng phương thức: fnguon.open(tep_nguon,ios::in | ios::binary); ostream & put(char) ; fdich.open(tep_dich,ios::out | ios::binary); + Để đọc một ký tự từ tệp có thể dùng phương thức: if (fnguon.bad() || fdich.bad() ) istream & get(char &) ; { Cần chú ý rằng: Cách đọc ghi ký tự theo kiểu văn bản khác với cout << "\n Loi mo tep nguon hoac dich " ; cách đọc ghi ký tự theo kiểu nhị phân (xem chương 10, cuốn Kỹ thuật lập trình C của tác giả) getch(); Ví dụ để sao tệp có thể dùng thuật toán đơn giản sau: exit(1); + Đọc một ký tự từ tệp nguồn } + Nếu đọc thành công ( phương thức eof() = 0) thì ghi lên tệp while(fnguon.get(ch),!fnguon.eof()) đích và lại tiếp tục đọc ký tự tiếp theo. fdich.put(ch) ; + Nếu đọc không thành công ( phương thức eof() != 0) thì kết fnguon.close(); thúc. fdich.close(); Chú ý là phải dùng kiểu nhập xuất nhị phân thì thuật toán mới cout << "\nHoan thanh" ; cho kết quả chính xác. Chương trình sao tệp dưới đây viết theo thuật getch(); toán trên.
  35. } cout > tep_nguon; cout > tep_dich; 15.3. Đọc, ghi một dẫy ký tự theo kiểu nhị phân fnguon.open(tep_nguon,ios::in | ios::binary); + Phương thức: fdich.open(tep_dich,ios::out | ios::binary); ostream & write(char *buf, int n) ; if (fnguon.bad() || fdich.bad() ) sẽ xuất n ký tự (byte) chứa trong buf ra dòng xuất. { + Phương thức: cout #include § 16. Đọc ghi đồng thời theo kiểu nhị phân #include Chương trình dưới đây minh hoạ cách đọc ghi đồng thời trên tệp #include theo kiểu nhị phân. Chương trình sử dụng các phương thức write, read, các phương thức di chuyển con trỏ tệp và các phương thức void main() kiểm tra lỗi. Chương trình gồm 3 chức năng: { 1. Nhập một danh sách thí sinh mới và ghi vào tệp TS.DL clrscr(); 2. Bổ sung thí sinh vào tệp TS.DL char tep_nguon[40], tep_dich[40] ; 3. Xem sửa thí sinh trên tệp TS.DL char buf[5000]; //CT7_18.CPP int n; // Doc tep fstream fnguon, fdich; #include
  36. #include fstream f; #include f.open(ten_tep,ios::binary|ios::in|ios::ate); #include if (!f.good()) #include sots = 0 ; else #include { #include sots=f.tellg()/size ; #include } struct TSINH } { char ht[25]; void TS::tao_ds() 432 433 int sobd; { float td; fstream f; }; f.open(ten_tep,ios::binary|ios::out|ios::noreplace); class TS if (!f.good()) { { private: cout << "\nDanh sach da ton tai" ; TSINH ts; cout << "\nCo tao lai khong? - C/K" ; char ten_tep[40]; int sots; char ch=getch(); static int size; if (toupper(ch) != 'C') public: return; TS(char *ttep); else void tao_ds(); { void bo_sung(); f.close(); void xem_sua(); f.open(ten_tep,ios::binary|ios::out|ios::trunc); }; } int TS::size = sizeof(TSINH); TS::TS(char *ttep) } { sots=0; strcpy(ten_tep,ttep); while(1)
  37. { f.close(); cout > ts.sobd; cout > ts.td; fflush(stdin); f.write((char*)(&ts),size) ; gets(ts.ht); sots++ ; if (ts.ht[0]==0) break; 434 435 } cout > ts.sobd; } cout > ts.td; { f.write((char*)(&ts),size) ; fstream f; ++stt; f.open(ten_tep,ios::binary|ios::app|ios::nocreate); } if (!f.good()) sots += stt ; { f.close(); cout << "\nDanh sach chua tao" ; } cout << "\nCo tao moi khong? - C/K" ; void TS::xem_sua() char ch=getch(); { if (toupper(ch) != 'C') fstream f; int ch; return; f.open(ten_tep,ios::binary|ios::out|ios::in|ios::nocreate); else if (!f.good()) { {
  38. cout > stt ; TS t("TS.DL"); if (stt sots) break; while(1) f.seekg((stt-1)*size,ios::beg); { f.read((char*)(&ts),size); clrscr(); cout > ts.sobd; clrscr(); cout > ts.td; getch();
  39. } + Nhập 3 thí sinh từ bàn phím và chứa vào 3 biến đối tượng t1, t2, t3. + Ghi nội dung của 3 biến đối tượng t1, t2, t3 lên tệp TS.DL § 17. Xây dựng toán tử nhâp xuất đối tượng trên tệp + Đọc các đối tượng từ tệp TS.DL và chứa vào 3 biến t4, t5, t6 Trong các mục trên đã trình bầy cách dùng các toán tử nhập >> và + In các biến đối tượng t4, t5, t6 ra màn hình xuất class TS #include { #include private: #include // Khai báo các thuộc tính #include public: #include friend fstream& operator >(fstream& fs,TS &t); 438 { 439 private: } ; char ht[25]; Về kiểu ghi: Có thể xây dựng các toán tử để thực hiện các phép float td; đọc ghi theo kiểu văn bản cũng như nhị phân. public: Ví dụ 1: Ghi theo kiểu văn bản friend ostream& operator >(istream& is,TS &t); toán tử nhập xuất đối tượng trên màn hình, bàn phím và tệp. Chương friend fstream& operator >(fstream& fs,TS &t); gồm các nội dung sau: }; + Tạo tệp TS.DL dùng để đọc và ghi theo kiểu văn bản. fstream& operator>>(fstream& fs,TS &t) {
  40. fs.getline(t.ht,25); clrscr(); fs >> t.td; fstream f("TS.DL",ios::out | ios::in | ios::trunc); fs.ignore(); TS t1,t2,t3,t4,t5,t6,t; return fs; cin >> t1 >> t2 >> t3; } f >t4>>t5>>t6; os >t ,!f.eof()) fstream& operator >(istream& is,TS &t) { Ví440 dụ 2 : Ghi theo kiểu nhị phân 441 cout > t.td ; // Kieu nhi phan is.ignore(); #include return is; #include } #include void main() #include { #include
  41. #include } class TS istream& operator>>(istream& is,TS &t) { { private: cout > t.td ; public: is.ignore(); friend ostream& operator >(istream& is,TS &t); } friend fstream& operator >(fstream& fs,TS &t); void main() }; { int TS::size= sizeof(TS); clrscr(); fstream& operator>>(fstream& fs,TS &t) fstream f("THU.DL",ios::binary | ios::out|ios::in|ios::trunc); { TS t1,t2,t3,t4,t5,t6,t; fs.read( (char*)(&t) , t.size); cin >> t1 >> t2 >> t3; return fs; f >t4>>t5>>t6; { 442 cout >t ,!f.eof() ) } cout << t; ostream& operator<<(ostream& os,const TS &t) f.close(); { cout << "\n Xong"; os << t.ht << endl; getch(); os << t.td << endl; } return os;
  42. § 18. Hệ thống các lớp stream 13. long setf(long setbits, long field) Mục này hệ thống lại các lớp stream mà chúng ta đã sử dụng bên 14. long unsetf(long) trên để tổ chức xuất nhập trên màn hình, bàn phím, máy in và tệp 15. int width() 16. int width(int) 18.1. Sơ đồ quan hệ giữa các lớp 18.3. Các phương thức của lớp istream ios 1. operator>> 2. int gcount() 3. int get() istream fstreambase ostream 4. istream& get(char*, int, char = ‘\n’) 5. istream& get(char&) ifstream ofstream 6. istream& getline(char*, int, char = ‘\n’) 7. istream& ignore(int n = 1, int delim = EOF) 8. int peek() fstream 9. istream& putback(char) 18.2. Các phương thức của lớp ios 10. istream& read(char*, int) 1. int bad() 11. istream& seekg(long) 2. void clear(int=0) 12. istream& seekg(long, seek_dir) 3. int eof() 13. long tellg() 4. int fail() 5. int fill() 18.4. Các phương thức của lớp ostream 6. int fill(char) 444 445 1. operator<< 7. long flags() 2. ostream& flush() 8. long flags(long) 3. ostream& put(char) 9. int good() 4. ostream& seekp(long) 10. int precision() 5. ostream& seekp(long, seek_dir) 11. int precision(int) 6. long tellp() 12. long setf(long) 7. ostream& write(char*, int)
  43. 18.5. Các phương thức của lớp fstreambase void close() 18.6. Các phương thức của lớp ifstream 1. ifstream() 2. ifstream(const char*, int = ios::in, int = filebuf::openprot) 3. ifstream(int ) 4. ifstream(int , char*, int) 5. void open(const char*, int = ios::in, int = filebuf::openprot) 18.7. Các phương thức của lớp ofstream 1. ofstream() 2. ofstream(const char*, int = ios::out, int = filebuf::openprot) 3. ofstream(int ) 4. ofstream(int , char*, int) 5. void open(const char*, int = ios::out, int = filebuf::openprot) 18.8. Các phương thức của lớp fstream 1. fstream() 2. fstream(const char*, int, int = filebuf::openprot) 3. fstream(int ) 4. fstream(int , char*, int) 5. void open(const char*, int, int = filebuf::openprot)