Điểm của Đặc điểm Nhân vật STL là gì?


83

Tôi nhận thấy rằng trong bản sao của tài liệu tham khảo SGI STL của tôi, có một trang về Đặc điểm nhân vật nhưng tôi không thể thấy chúng được sử dụng như thế nào? Chúng có thay thế các hàm string.h không? Chúng dường như không được sử dụng bởi std::string, ví dụ như length()phương thức trên std::stringkhông sử dụng length()phương thức Đặc điểm nhân vật . Tại sao các Đặc điểm Tính cách lại tồn tại và chúng có bao giờ được sử dụng trong thực tế không?

Câu trả lời:


171

Đặ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_stringtrô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 CaseInsensitiveStringnhư vậy mà tôi có thể có

CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {  // Always true
    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 comparehà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 strlà chuỗi bạn đang so sánh và rlenlà 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 comparesử dụng trực tiếp comparehà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 eqltở đây, so sánh các ký tự cho bằng và nhỏ hơn, tương ứng, và sau đó được định nghĩa comparetheo 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 CaseInsensitiveStringmộ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_traitsvề kiểu đó và sau đó tạo chuỗi từ kiểu đó. Ví dụ: trong Windows API, có một loại TCHARlà 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ừ TCHARs bằng cách viết

typedef basic_string<TCHAR> tstring;

Và bây giờ bạn có một chuỗi TCHARs.

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_stringtá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::stringkhô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::stringra 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::lengthtrê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 ý.


15
+ ∞
Chris Lutz

14
Có vẻ như bạn đã thực hiện công bằng với tên người dùng của mình :) Có lẽ cũng có liên quan: nhiều thư viện boost sử dụng các khái niệm và nhập các lớp đặc điểm, vì vậy nó không chỉ là thư viện tiêu chuẩn. Hơn nữa, các kỹ thuật tương tự được sử dụng trong các ngôn ngữ khác mà không cần sử dụng mẫu, hãy xem ví dụ bí truyền: ocaml.janestreet.com/?q=node/11 .
phooji

2
cấu trúc đẹp (Cây tìm kiếm bậc ba), tuy nhiên tôi muốn chỉ ra rằng các câu lệnh có thể được "thu gọn" theo nhiều cách khác nhau: 1 / sử dụng các dãy ký tự để trỏ đến một đứa trẻ, thay vì các ký tự đơn lẻ (lợi ích là rõ ràng), 2 / nén đường dẫn (Cây Patricia) và 3 / xô ở cuối các nhánh (tức là, chỉ cần sử dụng một mảng chuỗi đã được sắp xếp miễn là có ít hơn K). Kết hợp chúng (tôi kết hợp 1 và 3) làm giảm đáng kể mức tiêu thụ bộ nhớ mà không ảnh hưởng đến hiệu suất tốc độ nhiều hơn một yếu tố không đổi (và trên thực tế, nhóm làm giảm số lần nhảy).
Matthieu M.

2
@ dan04: Cố gắng lấy bất kỳ lớp / thuật toán tiêu chuẩn nào để sử dụng hàm của bạn.
Xeo

2
Vì vậy, ... tóm lại, đặc điểm chỉ là một số loại giao diện được sử dụng bởi lớp basic_string để thao tác với nhiều loại ký tự khác nhau bất kể chúng là gì, phải không?
Virus721
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.