Đặc điểm ký tự là một thành phần cực kỳ quan trọng của thư viện luồng và chuỗi vì chúng cho phép các lớp luồng / chuỗi tách biệt logic của những ký tự đang được lưu trữ khỏi logic của những thao tác cần được thực hiện trên các ký tự đó.
Để bắt đầu, lớp đặc điểm ký tự mặc định char_traits<T>
, được sử dụng rộng rãi trong tiêu chuẩn C ++. Ví dụ, không có lớp nào được gọi std::string
. Thay vào đó, có một mẫu lớp std::basic_string
trông như thế này:
template <typename charT, typename traits = char_traits<charT> >
class basic_string;
Sau đó, std::string
được định nghĩa là
typedef basic_string<char> string;
Tương tự, các luồng tiêu chuẩn được định nghĩa là
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
typedef basic_istream<char> istream;
Vậy tại sao các lớp này lại có cấu trúc như vậy? Tại sao chúng ta nên sử dụng một lớp đặc điểm kỳ lạ làm đối số mẫu?
Lý do là trong một số trường hợp, chúng ta có thể muốn có một chuỗi giống như vậy std::string
, nhưng với một số thuộc tính hơi khác. Một ví dụ cổ điển về điều này là nếu bạn muốn lưu trữ chuỗi theo cách bỏ qua chữ hoa và chữ thường. Ví dụ: tôi có thể muốn tạo một chuỗi có tên CaseInsensitiveString
như vậy mà tôi có thể có
CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {
cout << "Strings are equal." << endl;
}
Đó là, tôi có thể có một chuỗi mà hai chuỗi chỉ khác nhau về độ nhạy chữ hoa chữ thường được so sánh bằng nhau.
Bây giờ, giả sử rằng các tác giả thư viện chuẩn đã thiết kế các chuỗi mà không sử dụng các đặc điểm. Điều này có nghĩa là tôi sẽ có trong thư viện tiêu chuẩn một lớp chuỗi cực kỳ mạnh mẽ mà hoàn toàn vô dụng trong tình huống của tôi. Tôi không thể sử dụng lại nhiều mã cho lớp chuỗi này, vì các phép so sánh sẽ luôn hoạt động theo cách tôi muốn chúng hoạt động. Nhưng bằng cách sử dụng các đặc điểm, bạn thực sự có thể sử dụng lại mã điều khiển std::string
để có được một chuỗi không phân biệt chữ hoa chữ thường.
Nếu bạn kéo lên một bản sao của tiêu chuẩn ISO C ++ và xem định nghĩa về cách hoạt động của các toán tử so sánh của chuỗi, bạn sẽ thấy rằng tất cả chúng đều được định nghĩa theo compare
hàm. Hàm này lần lượt được xác định bằng cách gọi
traits::compare(this->data(), str.data(), rlen)
đâu str
là chuỗi bạn đang so sánh và rlen
là chuỗi nhỏ hơn trong hai độ dài chuỗi. Điều này thực sự khá thú vị, vì nó có nghĩa là định nghĩa compare
sử dụng trực tiếp compare
hàm được xuất bởi loại đặc điểm được chỉ định làm tham số mẫu! Do đó, nếu chúng ta xác định một lớp đặc điểm mới, sau đó xác định compare
để nó so sánh các ký tự không phân biệt chữ hoa chữ thường, chúng ta có thể xây dựng một lớp chuỗi hoạt động giống như vậy std::string
, nhưng xử lý mọi thứ phân biệt chữ hoa chữ thường!
Đây là một ví dụ. Chúng tôi kế thừa std::char_traits<char>
để có được hành vi mặc định cho tất cả các hàm mà chúng tôi không viết:
class CaseInsensitiveTraits: public std::char_traits<char> {
public:
static bool lt (char one, char two) {
return std::tolower(one) < std::tolower(two);
}
static bool eq (char one, char two) {
return std::tolower(one) == std::tolower(two);
}
static int compare (const char* one, const char* two, size_t length) {
for (size_t i = 0; i < length; ++i) {
if (lt(one[i], two[i])) return -1;
if (lt(two[i], one[i])) return +1;
}
return 0;
}
};
(Lưu ý rằng tôi cũng đã xác định eq
và lt
ở đây, so sánh các ký tự cho bằng và nhỏ hơn, tương ứng, và sau đó được định nghĩa compare
theo hàm này).
Bây giờ chúng ta có lớp đặc điểm này, chúng ta có thể định nghĩa CaseInsensitiveString
một cách tầm thường là
typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;
Và Voila! Bây giờ chúng ta có một chuỗi xử lý mọi thứ phân biệt chữ hoa chữ thường!
Tất nhiên, có những lý do khác ngoài lý do này để sử dụng các đặc điểm. Ví dụ: nếu bạn muốn xác định một chuỗi sử dụng một số kiểu ký tự cơ bản có kích thước cố định, thì bạn có thể chuyên char_traits
về kiểu đó và sau đó tạo chuỗi từ kiểu đó. Ví dụ: trong Windows API, có một loại TCHAR
là ký tự hẹp hoặc rộng tùy thuộc vào macro bạn đặt trong quá trình tiền xử lý. Sau đó, bạn có thể tạo chuỗi từ TCHAR
s bằng cách viết
typedef basic_string<TCHAR> tstring;
Và bây giờ bạn có một chuỗi TCHAR
s.
Trong tất cả các ví dụ này, hãy lưu ý rằng chúng tôi chỉ định nghĩa một số lớp đặc điểm (hoặc sử dụng một lớp đã tồn tại) làm tham số cho một số kiểu mẫu để lấy một chuỗi cho kiểu đó. Toàn bộ điểm của điều này là basic_string
tác giả chỉ cần chỉ định cách sử dụng các đặc điểm và chúng ta có thể làm cho chúng sử dụng các đặc điểm của chúng tôi một cách kỳ diệu thay vì mặc định để lấy các chuỗi có một số sắc thái hoặc không phải là một phần của loại chuỗi mặc định.
Hi vọng điêu nay co ich!
CHỈNH SỬA : Như @phooji đã chỉ ra, khái niệm đặc điểm này không chỉ được sử dụng bởi STL, cũng không phải dành riêng cho C ++. Như một cách tự quảng cáo hoàn toàn không biết xấu hổ, một thời gian trước, tôi đã viết một bản triển khai cây tìm kiếm bậc ba (một loại cây cơ số được mô tả ở đây ) sử dụng các đặc điểm để lưu trữ các chuỗi thuộc bất kỳ loại nào và sử dụng bất kỳ loại so sánh nào mà khách hàng muốn họ lưu trữ. Nó có thể là một bài đọc thú vị nếu bạn muốn xem một ví dụ về nơi điều này được sử dụng trong thực tế.
CHỈNH SỬA : Đáp lại yêu cầu của bạn rằng std::string
không sử dụng traits::length
, hóa ra là nó có ở một số nơi. Đáng chú ý nhất, khi bạn xây dựng một std::string
ra khỏi một char*
chuỗi C-phong cách, chiều dài mới của chuỗi được lấy bằng cách gọi traits::length
trên chuỗi đó. Dường như nó traits::length
được sử dụng chủ yếu để xử lý các chuỗi ký tự kiểu C, là "mẫu số chung nhỏ nhất" của các chuỗi trong C ++, trong khi std::string
được sử dụng để làm việc với các chuỗi có nội dung tùy ý.