चरित्र लक्षण धाराओं और तारों के पुस्तकालयों का एक अत्यंत महत्वपूर्ण घटक है क्योंकि वे धारा / स्ट्रिंग वर्गों को उन वर्णों के तर्क को अलग करने की अनुमति देते हैं जो उन वर्णों के तर्क से संग्रहीत किए जा रहे हैं कि उन वर्णों पर क्या हेरफेर किया जाना चाहिए।
के साथ शुरू करने के लिए, डिफ़ॉल्ट चरित्र लक्षण वर्ग, char_traits<T>
C ++ मानक में बड़े पैमाने पर उपयोग किया जाता है। उदाहरण के लिए, कोई वर्ग नहीं कहा जाता है std::string
। बल्कि, एक वर्ग टेम्पलेट std::basic_string
है जो इस तरह दिखता है:
template <typename charT, typename traits = char_traits<charT> >
class basic_string;
फिर, std::string
के रूप में परिभाषित किया गया है
typedef basic_string<char> string;
इसी तरह, मानक धाराओं को परिभाषित किया गया है
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
typedef basic_istream<char> istream;
तो क्यों इन वर्गों को संरचित किया जाता है जैसे वे हैं? हमें टेम्पलेट तर्क के रूप में एक अजीब लक्षण वर्ग का उपयोग क्यों करना चाहिए?
इसका कारण यह है कि कुछ मामलों में हम एक स्ट्रिंग की तरह चाहते हो सकते हैं std::string
, लेकिन कुछ अलग गुणों के साथ। इसका एक क्लासिक उदाहरण यह है कि यदि आप केस को अनदेखा करना चाहते हैं तो स्ट्रिंग्स को स्टोर करना चाहते हैं। उदाहरण के लिए, मैं एक स्ट्रिंग बनाना चाहता हूं, जिसे CaseInsensitiveString
मैं कह सकता हूं
CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {
cout << "Strings are equal." << endl;
}
यही है, मेरे पास एक स्ट्रिंग हो सकती है जहां केवल उनके मामले की संवेदनशीलता में भिन्न होने वाले दो तारों की तुलना बराबर होती है।
अब, मान लीजिए कि मानक पुस्तकालय लेखकों ने लक्षणों का उपयोग किए बिना तार डिजाइन किए हैं। इसका मतलब यह होगा कि मुझे मानक पुस्तकालय में एक बेहद शक्तिशाली स्ट्रिंग क्लास होगी जो मेरी स्थिति में पूरी तरह से बेकार थी। मैं इस स्ट्रिंग क्लास के लिए ज्यादा कोड का पुन: उपयोग नहीं कर सकता था, क्योंकि तुलना करने वाले हमेशा काम करेंगे कि मैं उन्हें कैसे काम करना चाहता था। लेकिन लक्षणों का उपयोग करके, वास्तव में उस कोड का पुन: उपयोग करना संभव है std::string
जो केस-असंवेदनशील स्ट्रिंग प्राप्त करने के लिए ड्राइव करता है।
यदि आप C ++ ISO मानक की एक प्रति खींचते हैं और स्ट्रिंग के तुलना संचालकों के काम करने की परिभाषा पर गौर करते हैं, तो आप देखेंगे कि वे सभी compare
फ़ंक्शन के संदर्भ में परिभाषित हैं । यह फ़ंक्शन कॉल करके परिभाषित किया गया है
traits::compare(this->data(), str.data(), rlen)
str
वह स्ट्रिंग कहां है जिसकी आप तुलना कर रहे हैं और rlen
दो स्ट्रिंग लंबाई से छोटी है। यह वास्तव में काफी दिलचस्प है, क्योंकि इसका मतलब है कि टेम्पलेट पैरामीटर के रूप में निर्दिष्ट लक्षण प्रकार द्वारा निर्यात किए गए फ़ंक्शन compare
का उपयोग सीधे करता है compare
! नतीजतन, अगर हम एक नए लक्षण वर्ग को परिभाषित करते हैं, तो compare
इसे परिभाषित करते हैं ताकि यह पात्रों के मामले की तुलना में असंवेदनशील हो, हम एक स्ट्रिंग वर्ग का निर्माण कर सकते हैं जो व्यवहार की तरह है std::string
, लेकिन चीजों को असंवेदनशील व्यवहार करता है!
यहाँ एक उदाहरण है। हम std::char_traits<char>
उन सभी कार्यों के लिए डिफ़ॉल्ट व्यवहार प्राप्त करते हैं जो हम नहीं लिखते हैं:
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;
}
};
(नोटिस मैंने भी परिभाषित किया है eq
और lt
यहाँ है, जो क्रमशः समानता और कम-से-कम के लिए वर्णों की तुलना करते हैं, और फिर compare
इस फ़ंक्शन के संदर्भ में परिभाषित किए जाते हैं)।
अब जब हमारे पास यह विशेषता वर्ग है, तो हम CaseInsensitiveString
तुच्छ रूप से परिभाषित कर सकते हैं
typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;
और वोइला! अब हमारे पास एक स्ट्रिंग है जो हर मामले को असंवेदनशील तरीके से व्यवहार करता है!
बेशक, लक्षण का उपयोग करने के लिए इसके अलावा अन्य कारण भी हैं। उदाहरण के लिए, यदि आप एक स्ट्रिंग को परिभाषित करना चाहते हैं जो निश्चित आकार के कुछ अंतर्निहित चरित्र प्रकार का उपयोग करता है, तो आप char_traits
उस प्रकार पर विशेषज्ञ कर सकते हैं और फिर उस प्रकार से तार बना सकते हैं। उदाहरण के लिए, Windows API में, एक प्रकार TCHAR
है जो या तो एक संकीर्ण या विस्तृत वर्ण है जो इस बात पर निर्भर करता है कि आपने मैक्रोप्रोसेसिंग के दौरान क्या सेट किया है। फिर आप TCHAR
लेखन से तार को बाहर कर सकते हैं
typedef basic_string<TCHAR> tstring;
और अब आपके पास एक तार है TCHAR
।
इन सभी उदाहरणों में, ध्यान दें कि हमने बस कुछ टेम्प्लेट क्लास को परिभाषित किया था (या पहले से मौजूद एक का उपयोग किया था) उस प्रकार के लिए एक स्ट्रिंग प्राप्त करने के लिए कुछ टेम्पलेट प्रकार के पैरामीटर के रूप में। इसका पूरा बिंदु यह है कि basic_string
लेखक को केवल यह निर्दिष्ट करने की आवश्यकता है कि कैसे लक्षणों का उपयोग किया जाए और हम जादुई तरीके से उन्हें तार पाने के लिए डिफ़ॉल्ट के बजाय हमारे लक्षणों का उपयोग कर सकते हैं, जिनमें कुछ अति सूक्ष्म अंतर या क्वर्की डिफ़ॉल्ट स्ट्रिंग प्रकार का हिस्सा नहीं है।
उम्मीद है की यह मदद करेगा!
EDIT : जैसा कि @phooji ने बताया, लक्षण की यह धारणा न केवल STL द्वारा उपयोग की जाती है, न ही यह C ++ के लिए विशिष्ट है। पूरी तरह से बेशर्म आत्म-प्रचार के रूप में, कुछ समय पहले मैंने एक टर्नरी खोज ट्री ( यहां वर्णित एक प्रकार का मूलांक वृक्ष ) का कार्यान्वयन लिखा था, जो किसी भी प्रकार के तारों को संग्रहीत करने के लिए लक्षणों का उपयोग करता है और ग्राहक जो भी तुलना करना चाहता है, उनका उपयोग करता है। यह एक दिलचस्प रीड हो सकता है यदि आप एक उदाहरण देखना चाहते हैं कि यह व्यवहार में कहां उपयोग किया जाता है।
संपादित करें : आपके दावे के जवाब में , जो std::string
उपयोग नहीं करता है traits::length
, यह पता चलता है कि यह कुछ स्थानों पर करता है। सबसे विशेष रूप से, जब आप सी-स्टाइल स्ट्रिंग से std::string
बाहर का निर्माण करते हैं, तो char*
स्ट्रिंग की नई लंबाई traits::length
उस स्ट्रिंग पर कॉल करके प्राप्त की जाती है । ऐसा लगता है कि traits::length
इसका उपयोग ज्यादातर वर्णों के सी-स्टाइल अनुक्रमों से निपटने के लिए किया जाता है, जो कि सी ++ में स्ट्रिंग्स के "कम से कम सामान्य भाजक" हैं, जबकि std::string
इसका उपयोग मनमाना सामग्री के तार के साथ काम करने के लिए किया जाता है।