Các khai báo này mô tả một cấu trúc dữ liệu đơn giản Mang | ine lưu đỏng đữ liệu nhập: mảng s:¡ae được tạo ra bằng cách sao chép các ký tự từ mảng 1ine và đánh dâu kết thúc mỗi truéng.
Trang 1Phân còn lại của phân này bao gồm một bán cài đặt mới của hàm :evgetl.ne phù hợp với đặc tả Thư viện dược chia làm hai tập tín một tập tin csv.» chứa các khai báo hàm mô tả phương thức giao tiếp, và một tập tin cai dat ;.c chứa mã nguồn cài đặt phương thức giao tiếp
Sau đây là tap tin header:
Zr ogv.h: header file của thu việ ~/
extern char *tesvgetline(FILE *f); /* đọc dòng
Các biến bên trong nội dung để lưu trữ văn bản và các hàm bên
trong như hàm sol:t được khai báo tĩnh nên chúng chỉ được truy xuất trong phạm vi của tập tín chứa chúng, Đây là cách đơn giản nhất để che giấu
enum { NOMEM = -2}
static char *line = NULL; /* 'đòng dữ liệu nhập */ static char *sline = NULL; /* dang duoc Lao ra để
tách */
static int maxiine = 0; /* sẽ dòng tôi da ~/
static char **field = NULL; /* con tro đến trường */ static int maxfield = 0; /* kích thước mảng
trường */
static int nfield = 0; /* sô trường có trong
157
Trang 2fieldsap|} = “,"; ¢* ky ty phan € trưởng
Các biến tĩnh cũng được khởi tạo Các giá trị khởi tạo được dùng để kiếm tra xem nên tạo mới háy mở rộng thêm các mảng
Các khai báo này mô tả một cấu trúc dữ liệu đơn giản Mang | ine lưu đỏng đữ liệu nhập: mảng s:¡ae được tạo ra bằng cách sao chép các ký
tự từ mảng 1ine và đánh dâu kết thúc mỗi truéng Mang cac con tro field trỏ tới các bắt đầu của trường trong mảng s1ine Sơ đồ sau đây cho thay trạng thái của ba máng này với dòng nhập liệu ab, “ca”, “e"" E", ,"g,h” được xử lý Các phần tử mờ (shaded) trong máng s1:r:c không phải là
Trang 3*/
159
Trang 4line = mews;
sline - news;
return NULL; /* oul of memory */
return (c == EOF && i Ô} ? NULL : Line;
Các dòng đọc được từ tập tin được đưa vào mảng | ines nd sé duge
mở rộng thêm khí cần thiết bang cdch goi ham realioc; kích thước tăng
gấp đôi sau mỗi lần mở rộng thêm, như trong phần 2.6 Kích thước của mảng s1ine được duy trì bằng với kích thước mảng Line; ham esvgetline goi ham split dé tạo ra các con trỏ trường trong một mang field riéng biệt; mảng này cũng có thể được mở rộng khi cần thiết
Để thử nghiệm, ta sẽ khởi tạo các mảng với kích thước rất nhỏ và phát triển chúng theo nhu cầu, nhằm bảo đảm rằng mã nguồn mở rộng mảng được thi hành nhiều lần Nếu cấp phát không thành công, ta goi ham reset
để phục hồi các biến toàn cục về trạng thái khởi đầu, nhờ đó các lời gọi hàm esvget line tiép theo có cơ hội thành công:
Trang 5static void resec (void}
static int endofline(FTLE *fin, int e}
Trang 6Các xử lý được tách ra là cần thiết, bởi vì các hàm nhập chuẩn không
xử lý cho rất nhiều định đạng không hợp lý trong các dữ liệu nhập thực tế
Prototype cia chang ta dùng hàm s: ctok để xác định :oken kế tiếp bằng cách tìm kiếm một ký tự ngăn cách, thường là dấu phẩy, tuy nhiên việc
xử lý đấu phẩy được đóng trong dau nháy là không kha thi Cần phải có một thay đổi lớn trong cải đặt hàm split, mac đù phương thức giao tiếp của nó không cần thay đối Hãy xem các đồng nhập liệu sau:
Mỗi dòng có ba trường rỗng Nhưng hàm sp1it phân tích chúng và
các dữ liệu nhập lạ khác một cách đúng đắn và rất phức tạp; sau đây là một
ví dụ cho thấy các trường hợp đặc biệt và các điều kiện biên có thể chiếm
một phần quan trọng trong chương trình như thế nào
/ * Ham split : chuyến dòng thành trường */
static int split(void) , {
Trang 7else sepp = p + strespn(p, fieldsep);
Vòng lặp sẽ mở rộng mảng con trỏ các trường khi cần thi4t, tiếp đến gọi một trong hai hàm khác để định vị và xử lý trường kế tiếp Nếu trường bat đầu bằng một đấu nháy, thì hàm advqueted sé Um trường và trả về một
163
Trang 8con tro tới dấu ngăn cách ở cuối trường Ngược lai, ding ham reospn ip, s} wong ther vien dé tim dấu phẩy kế tiếp; nó tìm trong chuỗi ø một xuất hiện kế tiếp của bat ky ký tự nao trong chuỗi s: nó trả về số ký tự
đã đi qua
Các đấu nhảy giữa một trường được thé hiện bằng hai dấu nháy liên tiếp do do ham advquote ép chúng lại thành một; nó cũng loại bỏ các đấu nháy bao quanh trường Một vài phức tạp được thêm vào nhằm giải quyết các dữ liệu nhập gần giống với định nghĩa, chẳng hạn như *absc“def Trong các trường hợp nảy, ta mở rộng trường bằng cách thêm tủy ý vào sau dau nháy thứ hai cho đến khi gặp dau ngăn cách kế tiếp Microsoft Excel dùng thuật toán tương tự như thể
164
Trang 9
véield va
Vi dong di liệu nhập d& duge cat ra nén ham
esvnfield duoc cai dat bình thường:
/* Hàm csvfieid: trả về con Lro của hrường than */
thu viện C8V */
/* Ham main: kiêm tra ‹
int main (void)
165
Trang 10while {({line = csavgetlineistdin:) NUT)
ane = ‘s'\n", lines;
for (i = 0; i < csvntield{}; itti printf (“field[<d] - ‘es'\n", a, csvfieldli));
return 0;
} Đến đây chúng ta đã hoàn thành phiên bản viết bằng ngôn ngữ C Nó
xử lý các dữ liệu nhập lớn tùy ý và thậm chí có một vài xử lý hợp lý với đữ liệu dạng lạ Giá phải trả là nó đài hơn gấp bốn lần so với phiên bản đầu tiên
và một vải chỗ của mã nguồn được làm chỉ tiết ra Các mở rộng về kích thước và độ phức tạp như thế là một kết quả thù cho việ chuyển một
phiên bản mẫu thánh sản phẩm sử dụng được
Bài tập 4-1 Có nhiều cấp độ trong việc chia trường: một trong các cấp độ đó là chia ngay khi một số trường được yêu cầu nhằm chỉ cắt ra các trường được yêu cầu, hay là chia cho đến trường được yêu cầu Hãy liệt kê các khả năng, ước lượng những khó khăn và lợi ích của chúng, tiếp đến viết
chương trình và tính các tốc độ tương ứng của chúng
Bài tập 4-2 Hãy mở rộng thêm một số tiện ích để các ký tự phân cách có thể đổi thành:
Trang 11Một phương thức giao tiếp nên được tô chức như thế nào?
Bài tập 4-3, Chúng ta chọn cách khởi tạo tĩnh dược hễ trợ bởi ngôn ngữ C như là cơ sở cho một lần chuyên đổi: nễu một con trỏ là NULL thi việc khởi tạo sẽ được thực hiện Một khả năng khác là yêu cầu người dùng gọi một hàm khởi tao một cách rõ ràng, điều này có thể bao gồm các kích thước khởi đầu cho các mảng Hãy cài đặt một phiên bản kết hợp các điểm mạnh của hai cach, Vai tro cua ham reset trong cai dat ctia ban la gi?
Bài tập 4-4 Hãy thiết kế và cài đặt một thư viện dùng để tạo dữ liệu dang CSV Phién bản đơn gián nhất có thế lấy một máng các chuỗi và in chúng ra với các đầu nháy và đấu phẩy Phiên bản tốt hơn có thể sử dụng một chuỗi có định dạng tương tự như trong ham printf Hay xem một số gợi ý và hệ thống các ký hiệu trong Chương 9
4.4 Bản cài đặt bằng C++
Trong phần này ta sẽ cài đặt một phiên bản của thư viện CSV bằng C++ để giải quyết những hạn chế của phiên ban trên ngôn ngữ C Ở đây ta cần một vài thay đổi trong phần đặc ta; quan trọng nhất là các hàm sẽ thao tác lên các chuỗi trong C++ thay vi thao tác trên các mảng ký tự như trong
€ Việc dùng các chuỗi cla C++ sẽ tự động giải quyết các vấn dé về quản lý
vùng lưu trỡ, vì các hàm trong thư viện sẽ quản lý bộ nhớ giúp chúng ta Đặc biệt, các hàm thao tác lên trường sẽ trả về các chuỗi cho phép hàm gọi thực hiện có thể sửa đổi, nhờ đó ta có được một thiết kế linh hoạt hơn phiên bán trước
Lớp csv định nghĩa phan giao tiếp chung, trong khi vẫn che giấu cần thận các biến và các hàm cà đặt Do một đối tượng lớp bao gồm tất cả các trạng thái của một thực thé, nên ta có thé tao ra nhiều thực thể là các biến kiểu csv; do các phiên bản là độc lập nhau nên các luồng nhập liệu CS có thể hoạt động đồng thời
class Csv {[//đọc và phân tích với dẫu phẩy là giá trị
phân cách
167
Trang 13Các tham số mặc dinh cla =enstrae= or được định nghĩa nên một đối tượng csv mặc định, nó sẽ đọc từ luồng nhập liệu chuân và dùng dấu ngăn cách trường bình thường: cũng có thê được thay thé bởi các giá trị cụ thể nào đó
Đề quản lý các chuỗi, lớp esv đùng các lớp vee:or Vi ing trong C++ chuẩn, chứ không dùng các kiểu chuỗi lä một máng các ký tự của C Không có trạng thái không tôn tại của một chuỗi: "rộng" chỉ có nghĩa là
chiều dài bằng không, và không có dạng tương duong voi NUL do dé ta
không thé sir dyng nun nhu là tín hiệu kết thúc tap tin Vi va
Csv:i:gerline tra về đồng dữ liệu nhập thông qua một đi số tham chiếu, ta
dùng giá trị trả về của hàm để cho biết kết thúc tập tin và thông báo lỗi
// Hàm get:ine: đọc vào một dòng, và được mở rộng khỉ
Trang 14Cần phải có vai thay đối nhỏ trong hàm endo£+ine Một lần nữa, ta phải đọc mỗi lúc một ký tự từ đữ liệu nhập, vì không một hàm nhập chuẩn nào có thê xử lý nhiều loại dữ liệu nhập khác nhau
ee TIỀN, oflin kiếm tra công dĩ liệu nhập được kết
civic bang \r, \n, \r\n, hodc BOF */
Sau đây là phiên bản mới của hàm split:
// Ham split: chuyén déng thành các trường int Csv::split()
Trang 15thay đôi cả hai hàm split va advquoted Phién ban moi cha sds dùng hàm chuẩn của C++ : ind £:rst e£ để định vị một xuất hiện kế tiếp của một ký tự phân cách Lời gọi s.find first oftfie:dsep,j} sẽ tìm trong chuỗi s phần đâu tiên của bat kỳ ký tự nào có trong £ieldsep và xuất hiện tại hoặc sau vị trí + Nếu tìm thất bại, nó sẽ trả về một chỉ số ngoài phạm vi của chuỗi, do đó ta phải đưa nó vào trong phạm vi Các lệnh tiếp theo bên trong vòng lặp thêm các ký tự cho tới ký tự phan cach dé mang
£ie1d chứa các chuỗi £1a
// Hàm advduoted: trả về chỉ số cua kí tự phân cách kế tiếp
171
Trang 16dé liệu hoàn toàn khác,
172
Trang 17/* Hàm a#plairn: trả về chỉ số của kí sự phân
¿a trưởng <héng 2am trong dâu nháy */
return 4;
} Cũng giếng như phần trước hàm c eLfietd được viết bình thường, trong khi €sz::getaZiels thì ngắn đến nỗi nó được cài đặt trong
định nghĩa lớp
// Ham getfield: tra vé truéng tha n
return field[n];
Trang 18Chương trình kiểm chứng của chúng ta không khác hơn trước lắm:
Cách dùng thì khác với phiên bản C, mặc dù không nhiều Tùy thuộc
vào trình biên địch phiên bản C++ chạy chậm hơn phiên bán C từ 40% đến
4 lần với các tập tin dữ liệu nhập lớn khoảng 30000 đòng và 25 trường mỗi đồng Như chúng ta đã thấy khi so sánh với các phiên ban cia Markov, su khác biệt này phản ánh mức độ hoàn thiện của thư viện Chương trình nguồn của C++ ngắn hơn khoảng 20%,
Bài tập 4-5 Hãy cái tiến cai dat C++ bang cach định nghĩa chồng toán tứ [: để các trường có thể được truy xuất theo kiểu c 1
Bài tập 4-6 Ilây viết một phiên bản của thư viện CS bang Java, rồi
so sánh ba cách cài đặt trên phương điện trồng sáng, mạnh mẽ, và tốc độ
174
Trang 19Bài tập 4-7 !lãy đóng gói phiên bản C++ của các mã neudn CSE như một STZ quen thudéc
4.5 Nguyên tắc thiết kế phương thức giao tiếp
Trong các phần trước chúng ta đã nêu ra các phần chỉ tiết của một
phương thức giao tiếp Nó định nghĩa các công việc mà mã nguồn thực hiện cho đối tượng sử dụng nó, và cách thức của các hàm và các thành phan dtr liệu có thể được sử dụng bởi những phẩn còn lại của chương trình Phương thức giao tiếp CSV của chúng ta hỗ trợ ba hàm: đọc một dòng, lây một
trường, và trả về số trường, và nó cũng là ba diều khiển duy nhất mà nó có thể thực hiện
Một phương thức giao tiếp tốt phải phù hợp tối với tác vụ của nó —
đơn giản tổng quát, đễ dùng, và tiện lợi - và nó phải đễ thích ứng khi đối tượng sử dụng nó thay đổi và cách thức cải đặt nó thay đối Các phương thức giao tiếp tốt phải tuân theo một số nguyên tắc nhất định Các nguyên tắc này có thể phụ thuộc nhau hoặc thậm chí phải nhất quán, nhưng chúng giúp ta mô tả các vấn đề/xảy trong phần giao tiếp giữa các thành phần trong,
Trang 20Các thư viện cơ sở của hầu hết các ngôn ngữ lập trình chính là các ví
dụ tương tự, mặc đủ không phai lúc nào chúng cũng là các thiết kế tốt Thư viện nhập xuất chuẩn của € là mội trong số nỗi tiếng nhất nó có hàng tá hàm như: mở, đóng, đọc, viết, hay nói cách khác là các hàm thao tác trên tập tin Cách thức thao tác tập tin được che giấu phía sau kiểu đữ liệu rrLE*; ta có thể nhìn thấy các thuộc tính của nó (bởi vì chúng thường được liệt kê trong thư vién <stdio.h>), tuy nhiên không nên dùng chủng
Tránh dùng biến toàn cục: cế găng truyền các tham chiếu tới tất cả các đữ liệu thông qua các đối số hàm nếu có thể được
Chúng tôi phán đối mạnh mẽ việc dùng chung đữ liệu dưới mọi hình thức; rất khó đế bảo đảm tính nhất quản của các giá trị nếu người dùng có thể thay đổi các biến theo ý họ Các giao tiếp hàm cho phép tăng cường các quy định truy cập dé dang hơn, tuy nhiên nguyên tắc này thường không nhất quán Các luồng nhập xuất được định nghĩa san nhu scdin va stdout gan như luôn được định nghĩa như là các thành phần của một mảng toàn cục các cấu trúc F11:
fdeFine stdour (4+ ioa[l]i
#define stderr (&_ iob[2]}
Tiên riêng _ iob theo quy ước chuẩn ANSI €, hai gạch dưới đặt trước là để quy ước cho các tên riêng được nhìn thấy, điều này nhằm giám khả năng xung đột với các tên trong chương trình
Các cơ chế lớp trong C++ và Java tốt hơn trong việc che giấu thông tin; đó là điểm mạnh của hai ngôn neữ này Các lớp container trong thư viện mau chuan ctia C++ (C++ Standard Template Library) ma ching ta ding trong Chương 3 thậm chí còn mạnh hơn; không có một thông tỉn gì về cách thức cài đặt ngoại trừ một số bảo đảm cho việc thực hiện, và các người tạo thư viện có thể sử dụng bất kỳ cơ chế nào họ thích
176