9/5/15

Tại sao cần xóa bộ đệm bàn phím trước khi nhập chuỗi?

Thông thường, khi nhập một chuỗi trong màn hình console, ta phải có thao tác xóa bộ nhớ đệm bàn phím. Nếu không có thể thấy rằng kết quả nhập chuỗi bị sai hoặc trôi đi mất.

Trong quá trình chạy chương trình ta sẽ phải nhập bằng bàn phím, mọi ký tự bạn gõ vào bàn phím (kể cả ký tự Enter \n) đều được đẩy vào bộ nhớ đệm trước khi được gán vào biến. Nếu trước đó bạn có nhập số bằng scanf hoặc cin, chúng chỉ nhận số chứ không nhận được ký tự Enter, và ký tự Enter vẫn còn trong bộ nhớ đệm. Đến khi nhập chuỗi, các hàm nhập chuỗi nhận được ký tự Enter thì dừng nhập luôn và chương trình vẫn chạy tiếp. Điều này khiến kết quả bị sai.

Bạn có thể sử dụng các hàm sau để thực hiện xóa bộ nhớ đệm.

flushall()
Hàm này được định nghĩa trong stdio.h. Nó có tác dụng xóa bộ nhớ đệm tất cả các dòng nhập, xuất chuẩn và nhập xuất file.

fflush(stdin)
Hàm fflush() trong thư viện stdio.h cũng có tác dụng tương tự flushall(). Tuy nhiên nó cho phép lựa chọn xóa bộ nhớ đệm cho stream nào. Ở đây ta truyền vào stdin để xóa bộ đệm cho dòng nhập chuẩn, tức là bàn phím.

cin.ignore()
Đây là một phương thức của đối tượng cin trong C++. Phương thức này còn có những tham số khác nghĩa là bỏ qua hay loại bỏ một số lượng ký tự trong bộ đệm hoặc bỏ qua đến khi gặp ký tự nào đó. Nếu không có tham số thì mặc định là bỏ 1 ký tự trong bộ đệm. Dùng cách này hữu ích khi nhập dữ liệu chuyển từ số sang chữ.

Vậy nên dùng hàm nào? Hàm nào cũng được, flushallfflush(stdin) có vẻ đơn giản hơn trong khi dùng cin.ignore() bạn phải cẩn thận, không lạm dụng để tránh sai ý muốn.

Cách viết một hàm đọc file cơ bản trong C/C++

Có nhiều cách viết hàm đọc file khác nhau, tùy theo yêu cầu bài toán và ý đồ người lập trình. Tuy nhiên, mình sẽ giới thiệu cách viết đơn giản nhất mà đa số trường hợp người ta hay dùng trong đọc file văn bản dạng text.

Bạn có thể sử dụng con trỏ FILE* hoặc trong C++ bạn còn có thể sử dụng đối tượng fstream để đọc ghi file (nhớ #include<fstream> trước khi khai báo đối tượng fstream). Các ví dụ bên dưới mình sử dụng fstream trong C++, về con trỏ FILE* thì tương tự.

Nguyên tắc chính là kiểm tra xem file đã được mở chưa. Sau đó tiến hành đọc file để ghi vào mảng, danh sách liên kết hoặc in ra màn hình, v.v... Cuối cùng là đóng file để kết thúc việc đọc file.

Tùy vào cấu trúc file văn bản mà ta xử lý khác nhau trong phần đọc file.

Dưới đây là ví dụ:
Mình có file D:\Test.txt có nội dung:
1 2 5 7 3 5 7 8 9
Yêu cầu đọc và xuất các số trên ra màn hình.
#include<fstream>
#include<iostream>
using namespace std;
void ReadFile(char* path)
{
   int n;
   fstream file(path, ios::in); //vừa khai báo vừa mở file
   if (!file.is_open()) //kiểm tra file đã được mở chưa
      cout<<"Could not open file!"<<endl;
   else
   {
      //xử lý đọc file ở đây
      while(!file.eof())
      {
         file>>n; //đọc file vào biến n
         cout<<n<<" ";
      }
      file.close(); //đóng file khi hoàn tất
   }
}

Ví dụ khác:
File D:\Test.txt có dòng đầu tiên là số lượng phần tử mảng, dòng thứ 2 là các phần tử
5
1 2 3 4 5
Yêu cầu đọc và lưu vào mảng.
#include<fstream>
#include<iostream>
using namespace std;
void ReadFile(char* path, int *a, int &n)
{
   fstream file(path, ios::in); //vừa khai báo vừa mở file
   if (!file.is_open()) //kiểm tra file đã được mở chưa
      cout<<"Could not open file!"<<endl;
   else
   {
      //xử lý đọc file ở đây
      file>>n; //đọc số đầu tiên
      for(int i = 0; i< n;i++)
         file>>a[i]; //đọc các số kế tiếp
      file.close(); //đóng file khi hoàn tất
   }
}

7/5/15

Vấn đề cấp phát động trong hàm

Ý tưởng của việc này là bạn có 1 con trỏ, bạn muốn cấp phát tài nguyên cho nó thông qua một hàm.

Ở đây ta có ví dụ:
int *a;
Ta đã có con trỏ a, làm sao để giữ được giá trị của con trỏ sau khi cấp phát?
Có thể thấy ta không thể truyền tham trị vào hàm, vì sau khi kết thúc hàm giá trị con trỏ không được lưu giữ.

Có 2 cách giải quyết cho trường hợp này

Dùng tham chiếu
Ta sẽ truyền một biến tham chiếu của con trỏ vào hàm. Sau khi cấp phát, giá trị con trỏ vẫn được lưu giữ. Lưu ý biến tham chiếu đến con trỏ cấp 1 là *&a, con trỏ cấp 2 là **&a, tức dấu & luôn ở gần tên biến.

Ví dụ:
void Alloc(int *&a, int n)
{
   a = new int[n];
}

Dùng con trỏ cấp cao hơn
Trong ví dụ này ta sẽ truyền vào con trỏ cấp 2. Con trỏ cấp 2 trỏ đến con trỏ cấp 1. Vì vậy, nếu bạn muốn có mảng n chiều thì có thể truyền vào con trỏ cấp n+1.

Ví dụ:
void Alloc(int **a, int n)
{
   *a = new int[n];
}
Ở đây *a là dữ liệu mà con trỏ cấp 2 đang trỏ tới, giá trị của *a bị thay đổi vẫn được lưu lại.