النوع long في C++

النوع long في C++

بالإضافة إلي بيان استخدام متحكمات الخرج , يبين لنا البرنامجين السابقين نوعا جديدا من المتغيرات هو النوع long الذي استخدم لوجود عدد أكثر من طاقة المتغير int , وهو العدد 2,425,785 فالنوع long يمكن أن يحتوي مدي من -2,147,483,648 إلي 2,147,483,647 بينما الحد الأعلي لمدي النوع int هو 32,767 . والنوع long يمكن أن يعرف أيضاً علي أنه long int وهو ما يعني نفس الشئ . والمتغير long يحتل أربعة بايت في الذاكرة , أي ضعف ما يحتله المتغير int , كما هو مبين في الشكل ] أي 32 بتة , لذا فإن المدي هو 2 31 [

Variable of type float in memory

شكل المتغير long مختزن في الذاكرة

تعريف الثوابت من النوع long

من الممكن بالنسبة للثوابت أن تعرف علي أنها من هذا النوع باستخدام حرف l بعد قيمتها عند تخصيصها , علي النحو التالي :

Long var = 76781; // assign long constant 7678 to variable long var

ويجب ملاحظة أن الثوابت التي قيمتها أكثر من 65,535 تعتبر من هذا النوع سواء أضيف لها حرف l أم لا .

التعريف المتعدد

لقد استهللنا المتغيرات pop1, pop2, pop3 في نفس تعريفها كما فعلنا مع المتغير char في البرنامج char var , إلا أننا هنا قمنا بتعريف المتغيرات الثلاثة في نفس السطر , وبنفس لفظ التعريف long , مع الفصل بينها بفاصلة عادية . وفي هذا اقتصاد للمساحة في حالة كون مجموعة من المتغيرات من نفس النوع .

ملخص أنواع المتغيرات

تضمنت أمثلتنا إلي الأن أربعة من أنواع من المتغيرات ؛ char, int, float, long , ولا يزال هناك اثنين من المتغيرات , هما double, long double لم نتعرض لهما بعد , فلنتوقف الأن قليلا لكي توجز هذه الأنواع من المتغيرات . ويبين الجدول الكلمات الحاكمة key words المستخدمة لتعريف كل نوع , ومداه , ودرجة دقته (للمتغيرات الكسرية ) , والقدر الذي يشغله من الذاكرة في بيئة الدوس .

جدول المتغيرات الأساسية في لغة السي ++

النوع

الكلمة الحاكمة

المدي

الدقة ( للأعداد الكسرية )

عدد البتات

 

من

إلي

محرفي

Char

-128

127

-

1

عدد صحيح

Int

-23769

23767

-

2

طويل

Long

-

21474836

-

4

21474836

47

48

كسري

Float

38-10x3,4

3810x3,4

7

4

مضاعف

Double

308-10x1,7

30810x1,7

15

8

مضاعف

Long

-10x1,1

0 1x1,1

19

10

يوجد لدينا متغير للمحارف , وهو char , ومتغيران للأعداد الصحيحة , int, long(int) , وثلاثة للأعداد الكسرية , float, double, long double . ونظريا , يوجد متغير رابع يسمي short , ولكنه في بيئة الدوس يطابق المتغير int ولذا فنادرا ما يذكر . على أنه double من الذاكرة ضعف ما يحتله float , ولذلك فهو أكثر مدي ودقة بقدر كبير , وهو يشير إلي أنه " عدد كسري مضاعف الدقة " , وهو يستخدم في حالة كون مدي المتغير float أقل من العدد المطلوب التعامل معه , أو لا يعطي الدقة المطلوبة . ويشغل النوع long double عشر بايتات , وبذلك فهو أوسع مدي وأكثر دقة من النوع double .

ولتتذكر دائما أنه ليس من الكياسة استخدام نوع يشغل من الذاكرة أكبر مما هو مطلوب لمتغيرات البرنامج , حيث إنه كلما زادت المساحة المشغولة من الذاكرة بالمتغيرات , كان تنفيذ البرنامج أبطأ .

الأنواع وحيدة الإشارة

إذا أزلنا الإشارة من نوعي المحارف والأعداد الصحيحة , يمكنك أن تبدأ مداها ابتداء من الصفر , لتشتمل فقط الأعداد الموجبة , وتسمي هذه الأنواع " الأنواع وحيدة الإشارة unsigned data types " . ويسمح لها ذلك أن تشمل أعدادا ضعف ما يسمح به المدي العادي ] لاحظ أن استخدام البتة المستخدمة لتحديد الإشارة لتوسيع المدي يعني ضرب المدي في 2 , مثلا 72 سوف تصبح 82 [ ويبين الجدول التالي مدي هذه الأنواع

جدول الأنواع وحيدة الإشارة

الكلمة الحاكمة

المدي الرقمي

Unisigned

Char

Unsigned Int

Long

unsigned

أقل

0

0

0

أعلي

255

65.535

295,4,294,967

 

 

وتجاوز المدي بالنسبة للمتغيرات ذات الإشارة يمكن أن يؤدي إلي أخطاء غامضة , وهي أخطاء قد يمكن إزالتها لو استخدمنا النوع وحيد الإشارة . ففي المثال التالي , يخزن العدد 25000 مرة كعدد صحيح وحديد الإشارة unsigned , ومرة كعدد صحيح معتاد int , وستجري علي العدد بعض العمليات الحسابية وهو علي الصورتين , ولنلاحظ النتيجة .

Singtest.cpp

 

signtest.cpp


 


// signtest.cpp


// tests signed and unsigned integers


#include <iostream.h>


 


void main()


   {


   int signedVar = 25000;            // signed: -32768 to 32767


   unsigned int unsignVar = 25000;   // unsigned: 0 to 65535


 


   signedVar = (signedVar * 2) / 3;  // calculation exceeds range


   unsignVar = (unsignVar * 2) / 3;  // calculation within range


 


   cout << "signedVar = " << signedVar << endl;  // wrong: -5178


   cout << "unsignVar = " << unsignVar << endl;  // ok: 16666


   }




سوف تفاجأ أن يكون خرج البرنامج قد ظهر خطأ مع المتغير int (-5178) وصحيحا في حالة المتغير unsigned int (16666) . فقد أجري علي نفس العدد عمليتي ضرب في 2 ثم قسمة علي 3 , ولكن في حالة المتغير int سوف يكون ناتج عملية الضرب 50000 أي أكبر من المدي المتاح للمتغير (32767) . ولذا فليس مستغربا أن تكون النتيجة النهائية خاطئة في هذه الحالة .



والدرس المستفاد من هذا المثال هو أنه يجب علي الدوام ملاحظة تناسب المساحة المطلوبة للمتغيرات في الذاكرة مع القيم التي تتخذها خلال تشغيل البرنامج . ( قد تكون النتيجة المبينة في المثال مختلفة لأنواع الأجهزة التي تعمل علي بيئة خلاف الدوس , والتي تعطي مساحة تخزين أكبر للنوع int) .



تحويل النوع



تتسامح لغة السي ++ وكذا السي التقليدية , مع العبارات التي تحتوي علي أكثر من نوع , كما يتبين لك من البرنامج التالي :



Mixed.cpp




mixed.cpp


 


// mixed.cpp


// shows mixed expressions


#include <iostream.h>


 


void main()


   {


   int count = 7;


   float avgWeight = 155.5;


 


   double totalWeight = count * avgWeight;


   cout << "totalWeight=" << totalWeight << endl;


   }




في هذا البرنامج ضرب عدد من نوع int مع أخر من نوع float ليكون حاصل الضرب من نوع double , وتسير ترجمة البرنامج دون مشاكل , فالمترجم يعلم أنه من الطبيعي أن تجري بعض العمليات الرياضية , ومنها عملية الضرب علي أنواع مختلفة من الأعداد .



وكثير من اللغات لا تسمح بذلك , فعندما يحتوي التعبير علي أكثر من نوع , يفترض المترجم أنك قد أرتكبت خطأ , ويعطي بالتالي إشارة بذلك , ليحميك من نفسك . أما السي ++ فتفترض فيك الانتباه لمل تفعل , وتقوم بتنفيذ مطلبك , وهذا هو أحد الأسباب في شعبية لغة السي , فهي تعطيك قدر أكبر من الحرية في العمل , ومن المسئولية في نفس الوقت .



التحويل التلقائي للنوع



لنتبع ما يفعله المترجم حينما يواجه تعبيرا يحتوي علي أنواع مختلفة من المتغيرات . إنه يرتب المتغيرات ترتيبا تصاعديا وفقا للشكل التالي :












































النوع



الرتبة



Long double



الأعلي



double





float



long



int



char



الأدني





وحينما يصادف المترجم نوعين من المتغيرات في تعبير واحد فإنه يحول المتغير ذا الرتبة الأدني إلي نوع زميله ذي الرتبة الأعلي . وعلي ذلك فإن المتغير count في المثال المعطي يرفع من رتبة النوع int إلي رتبة النوع float , ويخزن في مكان مؤقت لحين إجراء عملية الضرب . ولما كان حاصل الضرب يستخرج من نفس النوع float , فإنه يرفع بالتالي إلي النوع double حتي يمكن تخزينه في المتغير total weight .



ويبين الشكل هذه العملية .



Object-Oriented Programming in C   _Page_0084_Image_0001























شكل التحويل التلقائي للنوع



هذه التحويلات تتم دون أن يلاحظها أحد , وفي العادة لا يفترض أن تشغل نفسك بها , فلغة السي ++ تغنيك عن ذلك . علي أنه حينما نبدأ في دراسة الكائنات , فإننا في الواقع سوف نضع أنواع البيانات الخاصة بنا , و حتي نتمتع بإمكانية خلط الأنواع المختلفة في نفس التعبير علينا أ نضع روتينيات تتيح عملية التحويل , حيث لن يقوم المترجم بهذه العملية من تلقاء نفسه كما يفعل مع أنواع البيانات الخاصة باللغة .



التحويل القسري للنوع



يقصد بمصطلح التحويل القسري casting " أن يقوم المترجم بنفسه بتحويل النوع , فيعتبر بذلك قد حول النوع "قسرا" بدلا من أن يقوم المترجم بذلك تلقائيا , ففي أي الأحوال يريد المبرمج أن يلجا لهذه الإمكانية ؟



هل تذكر الخطأ الذي حدث في البرنامج signtest.cpp ؟ لقد حدث لكون عملية الضرب , وهي عملية بينية في البرنامج , قد أنتجت قيمة أكبر مما يحتمله النوع int . هذا الخطأ صححناه بتعريف المتغير علي أنه من النوع unsigned int أيضا , في مثل هذه المواقف يمكنك الخروج من المأزق بإستخدام إمكانية التحويل القسري , وإليك مثالا يوضح الفكرة .



Cast.cpp




cast.cpp


 


// cast.cpp


// tests signed and unsigned integers


#include <iostream.h>


 


void main()


   {


   int intVar = 25000;              // signed: -32768 to 32767


   intVar = (intVar * 10) / 10;             // result too large


   cout << "intVar = " << intVar << endl;   // wrong answer


 


   intVar = 25000;


   intVar = ( long(intVar) * 10) / 10;      // cast to long


   cout << "intVar = " << intVar << endl;   // right answer


   }




فتري أن حاصل ضرب المتغير intVar في 10 ينتج قيمة 250,000 , وهي قيمة أكبر بكثير مما يمكن للنوع unsigned int من أن يحتويها , ولذلك فقد كان ناتج العملية في النصف الأول من البرنامج خطأ .



وتصحيحا لهذا الوضع , يلزم أن تكون النتيجة من النوع long , وهو نوع له مدي يستغرق تلك القيمة . ولكن لنفرض أن لا تريد أن تغير من تعريف المتغيرات , حفاظا علي حجم البرنامج مثلا , تتيح لك لغة السي حلا أخر , هو أن تغير نوع المتغير int var "قسريا" قبل إجراء عملية الضرب , وذلك بالعبارة :



Long (int var)



التي تراها في صلب البرنامج , أمه تتسبب في إنشاء متغير مؤقت , من النوع long . تخزن فيه نتيجة الضرب , قبل الإنتقال إلي الخطوة التالية , وهي عملية القسمة . وكما تري من خرج البرنامج , فإن نتيجة العملية التي تمت دون تغيير قسري للنوع جاءت خاطئة , بينما الأخري صحيحة .



هذا وتقبل السي ++ الصيغة التالية لمتغير القسري :



(long) int var



بمعني أن يكون القوسان محيطين بالكلمة الحاكمة لتعريف النوع بدلا من اسم المتغير , وهي الصيغة الوحيدة التي تقبلها السي التقليدية , ولكننا في السي ++ نفضل الصيغة الأولي ( والذي يطلق عليها " التعبير الدالي functional notion " لكونه يجعل الكلمة الحاكمة علي صورة الدالة ) .



ولسوف يعطيك المترجم رسالة ''conversion may lose significant'' ومعناها أن التحويل قد يؤدي إلي فقدان أرقام مهمة , وهو يحدث حين يحول المترجم المتغير المحول قسريا إلي صورته الأولي , ] حيث يكون التحول من الرتبة الأعلى إلي الرتبة الأدنى [ , وهو تحذير يتم تجاهله .

التسميات: