الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

فئة نصوص للتجربه و التحليل

بدأه C++er في 17 أبريل 2011 · 7 رد · 553 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

أثناء عملي بفئات النصوص الخاصه بي احتاج لتطبيق أفكار محدده بعيدا عن الكود الأساسي، أمس احتجت لبناء فئه نصوص بسيطه للتواصل مع STL و بالخصوص iostream لذا قمت بكتابة واحده و بعد أن تأكدت من أن عملية تبادل البيانات تمت بشكل سليم احببت بدلا من حذفها أن أقوم بوضعها هنا كي يستفاد منها اخرون من محبي كتابة مثل هذه الفئات.

الفئه شكلها كالتالي:

#ifndef MY_STRING_H
#define MY_STRING_H

#include <iostream>
using std::istream;
using std::ostream;

namespace MyString
{
	class String
	{
	public:
		static const String Empty;
		static const String NewLine;

	public:
		String();
		String(const char c);
		String(const char* str);
		String(const char* str, unsigned start_index);
		String(const char* str, unsigned start_index, unsigned length);

		String(const String& str);
		String(const String& str, unsigned start_index);
		String(const String& str, unsigned start_index, unsigned length);

		~String();

	public:
		char& charAt(unsigned index);
		char  charAt(unsigned index) const;

		unsigned getLength() const;

		String& append(const String& str);
		String& insert(const String& str, unsigned index);

		bool isEmpty() const { return data == NULL; }

		void clear();

	public:
		char& operator[](unsigned index)       { return charAt(index); }
		char  operator[](unsigned index) const { return charAt(index); }

		bool  operator==(const String& str) const { return  equals(*this, str); }
		bool  operator==(const String& str)       { return  equals(*this, str); }
		bool  operator!=(const String& str) const { return !equals(*this, str); }
		bool  operator!=(const String& str)       { return !equals(*this, str); }

		String& operator= (const char* str);
		String& operator= (const String& str);

	public:
		friend istream& operator>>(istream& is, String& str);
		friend ostream& operator<<(ostream& os, const String& str);

	public:
		static bool compare(const String& s1, const String& s2) { return compare(s1, s2, false); }
		static bool compare(const String& s1, const String& s2, bool ignoreCase);

		static bool equals(const String& s1, const String& s2) { return compare(s1, s2); }
		static bool equals(const String& s1, const String& s2, bool ignoreCase) { return compare(s1, s2, ignoreCase); }

	private:
		struct Data;
		Data* data;
	};
}

#endif // MY_STRING_H

الكود ستجده فى المرفقات، و إليك بعض النقاط التى قد تجدها مثيره للإهتمام:

1 - فى المعتاد عندما تريد الحصول على نص من cin فإما ان تقوم بقراءة النص داخل فئه من نوع string او أن تقوم بحجز مساحه من الذاكره انت تعلم ان النص الذى سيعود لك لن يتخطي هذا الحاجز، ما اقوم بعمله هنا يختلف حيث انى لا استخدم الفئه std::string و لا أحجز مخزن بالذاكره قبل قراءة النص بحجم يكفى القيمه المدخله من المستخدم حيث أنى لا اعرفها و لا استطيع تخمينها، أمر أخر و هو أن istream لا يتم استخدامها حصريا للـ console و إنما تستخدم ايضا مع الملفات و كل شجرة الـ streams داخل مكتبة iostream.

الحل لهذا الأمر هو التعامل المباشر مع المخزن الخاص بالفئه istream لطلب القيمه من المستخدم و معرفة عدد حروفها و بالتالي أستطيع التعامل معها بشكل سليم.

2 - ستجد الدوال append لإضافة النص فى اخر السلسله و insert لاضافة نص فى اى مكان فى السلسله و comapre للمقارنه بين نصين مع امكانية تجاهل حالة الحروف.

3 - لم يتم اضافة عناصر الفئه الـ private داخل الفئه مباشرة و إنما تم عمل encapsulate لها داخل subtype و ذلك من باب اخفاء التفاصيل الداخليه (توجد اساليب اخرى مثل إستخدام الوراثه و انشاء abstract class).

4 - تم انشاء ثوابت من نوع الفئه و هم NewLine و Empty و ذلك للتسهيل فى استخدام الفئه، يمكنك استخدام الثابت الأخير للمقارنه، فمثلا الأكواد التاليه كلها ستطبع نفس النتيجه:

String test;

boolalpha(cout);

cout << test.isEmpty();                       // true
cout << String::Empty.isEmpty();              // true
cout << String::compare(test, String::Empty); // true
cout << String::equals(test, String::Empty);  // true
cout << (String::Empty == test);              // true

5 - المعاملين operator<< و operator>> عند تضمين الكود لهم تمت اضافة scope resolution قبل إسميهم، سأترك لك نقطة البحث لتعلم لماذا؟

6 - هذه الفئه لم يتم عمل test على كامل تصميمها حيث الهدف منها هو تجربة أفكار محدده و بالتالي لا تعتمد عليها إلا فى قراءة اكوادها و تجربتهم.

قمت بكتابة test unit بسيطه لإختبار التعامل مع iostream و بعض دوال الفئه، الهدف من الكود التالي هو نسخ ملف الكود الذى يحتوى على الداله main و وضعه داخل ملف اخر و ايضا طباعة مسار الملف التنفيذى و اختبار الداله insert و append

#include "my_string.h"
#include <fstream>
using MyString::String;
using std::ifstream;
using std::ofstream;
using std::cout;

int main(int argv, const char** args)
{
    String path = args[0];

    cout << "executable file path: " << path << String::NewLine;

    cout << String().append("Arab").append("2000").insert(" Team ", 4) << String::NewLine;

    String content;

    ofstream("mainp2.cc") << (ifstream("mainp.cc") >> content, content);
}

simple_string.zip

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 17 أبريل 2011 في 14:01

2

مدونتي: C++ Tips and Tricks

#2

رائع اخى محمد

احاول تجربتها الليلة ان شاء الله

تحياتى


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#3

توجد نقطه نسيت ذكرها و هي تخص تضمين دالة operator>>، إذا كان نوع الـ stream المدخله هي ifstream و حجم الملف المطلوب منه اكبر من حجم الـ internal buffer حينها in_avail ستعود بحجم الـ buffer و ليس عدد البايت الموجوده بالملف، حينها ستحتاج لإستخدام seekg للوصول لأخر الـ stream و بعدها استخدام tellg للحصول على عدد الحروف.

و الله ولي التوفيق

مدونتي: C++ Tips and Tricks

#4

اخى محمد عندى استفسار بسيط

لماذا لم تجعل الدوال compare , equals تعمل على الكائن من الفئة نفسها بدون static

مثلا

String test = "test";
if(test.equal("TEST"))
{
cout << "identical";
}else{
cout << "different";

وتستخدم هذه الطريقة المعروفة فى الجافا مثلا

وتضيف دالة اخرى بالاسم

equalsIgnoreCase

تتغاضى عن حالة الحروف فى عملية المقارنة

؟

واولا واخيرا شكرا لك مشاركتنا مشاريعك ووحداتك البرمجية

حياك الله


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#5
اقتباس
لماذا لم تجعل الدوال compare , equals تعمل على الكائن من الفئة نفسها بدون static

الداله equals تستدعي الداله compare لذا سنركز على الداله comapre.

فلنفترض ان تريد المقارنه بين نصين "ABC" و "XYZ" فى الوضع الحالي للفئه String ستكتب الكود التالي:

cout << (String::compare("ABC", "XYZ")? "equals": "not equals");

حيث انه يوجد المشيد الذى يأخذ const char* و لم تم تعريفه بـ explicit سيقوم المترجم بإجراء implicit cast و انشاء نسختين مؤقتتين لكل من النصين و من ثم يقارن بينهم و يعيد لك النتيجه.

أما لو جعلنا الداله compare تتبع الكائن و ليس الفئه في هذه الحاله اصبح لزاما عليك انشاء النسخه بنفسك حتى لو كانت مؤقته، و سيصبح المثال السابق كالتالي:

cout << (String("ABC").compare("XYZ")? "equals": "not equals");

بالنسبه لنقطة تجاهل حالة الأحرف، أنظر معي المثال التالي:

	String s[2];

	cout << "compare two strings example\n\n";
	cout << "enter 1st string? ";
	cin >> s[0];
	cout << "\nenter 2nd string ? ";
	cin >> s[1];

CHECK:
	char state;

	cout << "ignore character case([y]es - [n]o)? ";
	cin >> state;

	state |= 0x20;

	if ( !(state == 'y' || state == 'n') )
	{
		cout << "\nwrong choice, try again!\n";
		goto CHECK;
	}

	bool result = String::compare( s[0], s[1], state == 'y' );

	cout << "input strings are " << (result? "": "not") << "equal.\n";

على افتراض ان لديك دالتين أحداهما تتجاهل حالة الأخرى و الأخرى لا تتجاهلها فسيتم تعديل السطر قبل الأخير ليصبح كالتالي:

bool result;

if (state == 'y')
   result = String::compare( s[0], s[1] );
else
   result = String::compareIgnoreCase( s[0], s[1] );

قد يبدو من النظره الأولى أن الفرق بينهم بسيط و لكنه فعليا غير ذلك، و إليك الفرق:

1 - سيكون لديك دالتين تقوما بنفس العمليه (المقارنه) و حيث ان الإختلاف بينهم ليس كبير فإذا حدث و وجدت مشكله ما بأحدهم سيتوجب عليك التحقق من الاخرى.

2 - حجم الكود الناتج لكلاهما أكبر من وجودهم مجتمعين و نفس الكلام لكود الإستدعاء.

3 - تطبيق الـ optimization عليهما كداله واحده سيكون افضل منهما منفصلين.

4 - إسلوب الإستدعاء أوضح لداله واحده عنه بدالتين.

النصوص فى الجافا هي Unicode و فئة String تتعامل فقط مع ASCII (أى فى المدى من 0 إلى 127).

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 20 أبريل 2011 في 19:55

مدونتي: C++ Tips and Tricks

#6
اقتباس

أما لو جعلنا الداله compare تتبع الكائن و ليس الفئه في هذه الحاله اصبح لزاما عليك انشاء النسخه بنفسك حتى لو كانت مؤقتة

اظن انه سواء بال implicit converter او manually يتولد لدينا كائنين اذا لافرق بين الطريقتين من ناحية الاداء performance

وبالنسبة لى ارى ان الطريقة الثانية اكثر تالفا friendly مع مبرمجى oop (مجرد رأى ربما أُخطئ)

@ حالة الاحرف

لماذا توجد الدالة compare من الاساس ؟ أليس للمقارنة ؟

فلو انشأنا الدالة equalsIgnoreCase يمكننا نسخ الكود داخل الدالة compare فى الدالتين equals و equalsIgnoreCase

والاستغناء عن الدالة compare نهائيا

اى نعم سيؤدى ذلك الى زيادة حجم البرنامج قليلا ولكن سيزيد سرعة الاداء بالفعل

لاننا سنتخلى عن عمليات المقارنة التى تتم داخل الدالة compare عند مقارنة الحروف

ويصبح المبرمج صاحب قرار اكثر فعليه ان يختار equal or equalIgnoreCase

ولك ان تتخيل لو استغنينا عن المقارنات فى الدالة compare كيف يكون الاداء فى الدالة equals

اقتباس

قد يبدو من النظره الأولى أن الفرق بينهم بسيط و لكنه فعليا غير ذلك، و إليك الفرق:

1 - سيكون لديك دالتين تقوما بنفس العمليه (المقارنه) و حيث ان الإختلاف بينهم ليس كبير فإذا حدث و وجدت مشكله ما بأحدهم سيتوجب عليك التحقق من الاخرى.

2 - حجم الكود الناتج لكلاهما أكبر من وجودهم مجتمعين و نفس الكلام لكود الإستدعاء.

3 - تطبيق الـ optimization عليهما كداله واحده سيكون افضل منهما منفصلين.

4 - إسلوب الإستدعاء أوضح لداله واحده عنه بدالتين.

1- اظن الكود كله صغير وليس صعبا فى الصيانة فما بالك بدالتين لا تتعدى الواحدة 20 سطرا على الاكثر

2- بالنسبة للحجم فاظن الفرق سيكون بسيط جدا ولكن مقابل اكتساب سرعة اكتر فى الاداء تذكر الدالة equals

3- ربما ولكن مع الاستغناء عن compare فسيكون فرق طفيف ان وجد

4- اظن الاستدعاء للدالة equalsIgnoreCase اوضح من compare وارسال boolean parameter ربما أخطئ فيه سهوا (كمبرمج)

هذا غير ان اسماء الدوال واضحة جدا ومُعبرة

ملحوظة :

انا لا املك من الخبرة ما يُعطينى الحق ان اناقشك فان كان رأيى قريب للصحة فلك حرية الرد

والا فتجاهله واغفر لى فانا اريد ان اتعلم منك فقط لا اكثر ولك تحياتى

اقتباس

و الله ولي التوفيق


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#7
اقتباس
انا لا املك من الخبرة ما يُعطينى الحق ان اناقشك فان كان رأيى قريب للصحة فلك حرية الرد

والا فتجاهله واغفر لى فانا اريد ان اتعلم منك فقط لا اكثر ولك تحياتى

أخي كلنا نعلم بعضنا، و احيانا نخطئ و احيانا نصيب و فى النهايه نتعلم شيئا و نصحح معلوماتنا و هو الهدف النهائي لأي نقاش.

حتى يكون النقاش عملي دعنى أوجز لك بعض الأمور:

1 - يوجد نوعين من البحث داخل النصوص هما Exact Match و Pattern Match و مقارنة نصين هي عمليه ضمنيه من ايهما.

2 - الإسلوب المستخدم فى الداله comapre غير عملي على الإطلاق و السبب فى ذلك يرجع إلى انها تقارن حرف حرف و في اتجاه واحد لذا على افتراض ان النصين مساحة كل واحد منهما 10KB و اخر حرف هو المختلف فلك ان تتخيل كم من الوقت ستأخذ هذه الداله حتى تنتهي.

3 - الداله compare لضمان أقصي سرعه يتم تضمينها بإستدعاء دالة البحث مثل IndexOf للنص الثاني داخل النص الأول فإن عادت بـ -1 (مثلا) فهذا يعنى عدم وجود النص الثاني داخل النص الأول و غير ذلك فإنه يكون موجود.

4 - الداله IndexOf تضمينها للبحث عن نص داخل نص أخر مع مراعاة التنفيذ بأقصي سرعه (مما يعنى مقارنة أول و أخر حرف من كلا النصين مره واحده) قد يأخذ منك 100 سطر (بتحديد حالة الحروف)، الإسلوب المستخدم هنا يتبع Exact Match و إذا اردنا تطبيقها بإستخدام pattern Match سنحتاج لبناء معالج Regular Expression ليقوم بالمقارنه لنا.

الكود التالي هو كود الداله indexOf الموجوده بالإصدار 0.5 من فئة النصوص StringA الخاصه بي:

/*************************************************
 *		IndexOf
 *
 * DESCRIPTION
 *  get index of string
 *
 * PARAMETERS
 *  find         string to find.
 *  start_index  index to start search
 *  length       number of character to search
 *  ignoreCase   true to ignore character case, false otherwise.
 *
 * RETURNS
 *  index of find string or StringA::InvalidIndex
 *
 *****************************************/
size_t StringA::IndexOf(IN const StringA& find, IN size_t start_index,
						IN size_t length, IN bool ignoreCase) const
{
	// check for nulls
	if (data == NULL_PTR || find.data == NULL_PTR)
		return InvalidIndex; // nothing to look in

	// if input parameters are invalid
	if (start_index >= data->mLen || length > data->mLen)
		return InvalidIndex; // invalid parameters

	// if input length exceed string length
	if (find.data->mLen > data->mLen || start_index + length > data->mLen)
		return InvalidIndex; // logically nothing will be found

	// set start index to search in
	astr str_front  = data->mStr + start_index,
		 find_front = find.data->mStr;

	// temporary store character for search
	achar tmp1 = 0, tmp2 = 0;

	// result index
	size_t result_idx = InvalidIndex;

	// if one character search
	if (find.data->mLen == 1)
	{
		// set character case
		tmp1 = ignoreCase? *find_front | 0x20: *find_front;

		// loop as long as there is a place to search in
		while(length)
		{
			tmp2 = ignoreCase? *str_front++ | 0x20: *str_front++;
			// if there is a match
			if (tmp1 == tmp2)
			{
				// set the index
				result_idx = str_front - data->mStr - 1;
				// get out
				break;
			}
			// decrement the length
			--length;
		}

		// return the index
		return result_idx;
	}

	// start multi-character search

	// source string last possible index
	size_t str_last_index = start_index + length - find.data->mLen;

	// find string last character index
	size_t find_last_index = find.data->mLen - 1;

	// last character in source string and find string
	astr str_end  = str_front + find_last_index,
		 find_end = find_front + find_last_index;

	// a counter for loop
	size_t counter = 0;

	// temporary store character for search
	achar tmp3 = 0, tmp4 = 0;

	// this constant will be used soon
	// its description founded before it is used
	const size_t LOOP_SIZE = find.data->mLen - 2; // the -2 is because we match 2 characters from each string at a time

	// loop as long as there is a place to search in
	while(start_index <= str_last_index)
	{
		// set current character to compare
		tmp1 = *str_front;
		tmp2 = *find_front;
		// get last character from both strings
		tmp3 = *str_end;
		tmp4 = *find_end;

		// if not case sensitive
		if (ignoreCase)
		{
			tmp1 |= 0x20;   tmp2 |= 0x20;
			tmp3 |= 0x20;   tmp4 |= 0x20;
		}

		// if no match
		if (tmp1 != tmp2 || tmp3 != tmp4)
		{
			// increment the base string
			str_front++;
			str_end++;
			// and the start index
			start_index++;
			// jump to next loop
			continue;
		}

		// skip first and last character because
		// we know they are match
		str_front++;
		str_end--;
		find_front++;
		find_end--;

		// set counter to equal to LOOP_SIZE /2
		// LOOP_SIZE: is the number of character to search in
		//            decremented by 2 which they are the first
		//            and last characters we already matched
		// /2 : this division used because we'll match 4 characters
		//      at a time.

		// store loop size to save calculation time because
		// equation won't change.
		counter = LOOP_SIZE / 2;

		// if number of characters is not even number
		if (find.data->mLen & 1)
			// increment counter to check the remain characters
			counter++;

		// loop as long as the counter is not zero
		while (counter)
		{
			tmp1 = *str_front++;
			tmp2 = *find_front++;
			tmp3 = *str_end--;
			tmp4 = *find_end--;

			// if not case sensitive
			if (ignoreCase)
			{
				tmp1 |= 0x20;   tmp2 |= 0x20;
				tmp3 |= 0x20;   tmp4 |= 0x20;
			}

			// if no match
			if (tmp1 != tmp2 || tmp3 != tmp4)
				break;
			else // otherwise
				counter--; // decrement the counter
		}

		// if the counter is 0
		if (counter == 0)
		{
			// we got a match
			// store current index in result_idx
			result_idx = start_index;
			// and go out the main loop
			break;
		}
		else // otherwise
		{
			// set the start_index beyond the string that searched
			start_index++;
			// reset the pointers
			str_front  = data->mStr + start_index;
			find_front = find.data->mStr;
			str_end    = str_front + find_last_index;
			find_end   = find_front + find_last_index;
		}
	}

	// return the result
	return result_idx;
}

اقتباس
اظن انه سواء بال implicit converter او manually يتولد لدينا كائنين اذا لافرق بين الطريقتين من ناحية الاداء performance

الفرق يكمن فى وضوح الكود، فلماذا تنشئ termoparay variable إن كنت لن تستخدمه؟ و إن كان الأمر كذلك لماذا لا تدع المترجم يقوم بهذا الأمر عوضا عنك!

موضوع وجود دالتين أو داله واحده تعتمد بشكل أساسي على تصميم واجهة الفئه و من وجهة نظرى ان تجاهل حالة الأحرف هو من خصائص المقارنه و بالتالي يمكن جعله جزء من الداله نفسها و لجعل واجهة الفئه سهله تم انشاء overloads بمعاملات اقل لتفي بالغرض.

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 21 أبريل 2011 في 21:28

مدونتي: C++ Tips and Tricks

#8

تمام اخى محمد

وشكرا لاهتمامك

بالتوفيق ان شاء الله


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

مواضيع مشابهة