زبان برنامه نویسی سی/تبدیل و جایگزینی دادهها: تفاوت میان نسخهها
محتوای حذفشده محتوای افزودهشده
جزبدون خلاصۀ ویرایش |
بدون خلاصۀ ویرایش |
||
خط ۶:
تبدیل داده (type conversion) در دانش برنامهنویسی که در زبان سی با مفهومی به نام ایفای نقش داده (type casting) وجود دارد ، به رفتار کردن کامپایلر با نوعی از داده به صورت نوع دیگری از داده میگویند که به صورت علنی و غیر علنی میباشد که در ادامه به آنها میپردازیم و در انتهای مبحث به رویدادی که برای دادهها طی تبدیل آنها رخ میدهد مطابق با استاندارد که ناقص است و بیشتر دلبهخواه عرضهکننده کامپایلر میباشد میپردازیم ( استاندارد C تمام تبدیلها را تعریف نکرده است ؛ اما بیشتر نویسندههای کامپایلرهای مطرح از روش یکسانی استفاده میکنند )
'''۱ - غیر علنی :''' در حالت غیر علنی ( implicit ) شما برنامهای را مینویسید که عملوندهای عملگرها ( عملگری مثل عملگر جمع که عملیات جمع بر روی عملوندها را انجام میدهد و عملوند به دادهای میگویند که عملگر بر روی آنها عمل محاسباتی ، منطقی یا ... را انجام میدهد ) از نوع دادههای متفاوتی هستند یا دادهای را از نوع دادهای دیگر جز پارامتر تابع ، به عنوان آرگومان به آن تابع ارجاع میدهید ( پاس میدهید ) . مثلاً یک نوع داده از نوع صحیح را با یک نوع داده از نوع اعشاری ، جمع میبندید و سپس آن را در یک نوع داده کاراکتری ذخیره میکنید یا یک نوع داده صحیح را به عنوان آرگومان به یک تابعی ارجاع میدهید که پارامتر متناظر با آن آرگومان ، در تابع به صورت کاراکتر تعریف شده است ( مثلاً اولین پارامتر تابع به صورت پارامتر کاراکتر تعریف شده است ؛ اما شما موقع احضار تابع ، به تابع یک نوع داده صحیح را
در اینجا کامپایلر به صورت خودکار دادهها را به یک دیگر تبدیل میکند (implicit) که تا جای ممکن با یکدیگر سازگار باشند یا مطابق با دستور شما باشد ( اگر امکان پذیر باشد )
خط ۳۱:
خروجی این برنامه ، عدد 5 خواهد بود . در انتهای همین مبحث ، پیش از بحث typedef به صورت کامل خواهیم نوشت که کامپایلر چگونه انواع دادهها را به یکدیگر تبدیل میکند . اما به صورت ساده در مثال بالا داده اعشاری را که عدد پی ( پای ) را در خود ذخیره کرده بود ، با داده اعشاری با دقت دوبرابر که عدد نپر ( همان اویلر ) را در خود ذخیره کرده بود جمع کردیم و نتیجه را داخل یک نوع داده صحیح گذاشتیم که داده صحیح خروجی تابع است و در خروجی خطدستوری نمایش داده میشود . به زبان ساده ؛ نوع داده صحیح نمیتواند قسمتهای اعشاری در خود ذخیره کند ؛ پس آن را نادیده میگیرد . این یکی از کاربردهای تبدیل دادهها در قسمت انواع صحیح و اعشاری است که قسمت اعشاری را حذف کنیم
'''۱ - علنی :''' در نقش دادن علنی ( explicit ) که از این پس با اصطلاح انگلیسی آن یعنی '''کست کردن''' از آن یاد میکنیم به صورت صریح (explicit) به کامپایلر دستور میدهیم تا با داده ما به عنوان نوع داده دیگری رفتار کند . در این نوع کست کردن که به دستور ما انجام میشود ؛ باید شناسه دادهای که میخواهیم با آن به عنوان نوع دادهای دیگر رفتار شود در مقابل یک جفت پرانتز باز و بسته
{{چپچین}}
خط ۳۹:
در زبان C بسیاری از دادهها توسط کامپایلر قابل تبدیل به یکدیگر نیستند اما برخی را میتوان به یکدیگر تبدیل کرد ؛ مثل مغیرهای پایه ؛ که ما با روش کست کردن علنی به صورت صریح ( explicit ) به کامپایلر دستور میدهیم تا نوع داده مورد نظر ما را به دادهای که طی کست کردن تعیین نمودهایم تبدیل کند . البته در کامپایلرهای جدید که با استاندارد C11 یا C18 سازگار هستند تمام تبدیلها به صورت خودکار قابل انجام است ؛ اما اگر از کامپایلر قدیمی استفاده میکنید یا کامپایلر شما نمیتواند تبدیلهای مجاز را خودکار انجام دهد و به شما خطایی مشابه با casting expected میدهد ؛ داده خود را کست کنید . عواقب کست کردن و تبدیل داده به عهده ماست . بنابراین باید دقت کنید که وقتی میخواهید تبدیلی را انجام بدهید ؛ پس از تبدیل ، دادهای از دست نرود یا زائدهای به وجود نیاید و هر خطایی که در برنامه رخ بدهد از دید بسیاری از کامپایلرها پنهان خواهد ماند ( به شما اخطاری داده نمیشود ) و این خود شما هستید که پس از اجرای برنامه و دیدن نقص در اجرای برنامه خود باید پیدا کنید که کجا کد خطایی نوشتهاید ؛ این مسئله در بخشهای بسیاری از برنامه امکان پذیر است که یکی از آن بخشها همین مبحث کست کردن میباشد . مثلاً یک اشارهگر کاراکتری را با نوع اشارهگر صحیح عوض میکنید و این باعث میشود تا زمان اشاره کردن به فضای بزرگتری از حافظه اشاره شود و دادههای زائد در نتیجه شما لحاظ شوند . در انتهای همین مبحث میپردازیم به اینکه طی تبدیل دادهها چه رخدادی به وقوع میپیوندد تا بهتر تصمیم بگیرید که در کجای برنامه میتوانید داده خود را کست کنید
نتیجه کست کردن علنی و غیر علنی در تبدیل دادهها به یکدیگر در برخی کامپایلرها متفاوت است ؛ برای کسب اطمینان به راهنمای کامپایلر خود مراجعه کنید . برای همین ، زمانی که میخواهید داده شما به دلخواه شما ( و متفاوت با آنچه که کامپایلر میتواند خودکار تبدیل کند و میکند ) تبدیل شود ؛ میتوانید دادههای خود را کست کنید . برای این کار نوع داده خود را داخل یک جفت پرانتز باز و بسته
مثال :
خط ۷۹:
{{پایان چپچین}}
با اعلام هشدار کامپایلر نظیر : warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ رو به رو خواهید شد ( در کامپایلر جیسیسی و کلنگ ) در کامپایلرهای دیگر نیز اخطاری نظیر همین اعلام هشدار مواجه خواهید شد . که در واقع به شما میگوید آرگومانهای تابع printf منتظر یک مقدار اعشاری با دقت دو برابر و یک صحیح است ( مطابق با کد ) اما در متغیرهای نسبت داده شده ، متغیر دوم از نوع اعشاری است که با کست کردن آن مشکل برطرف میشود ؛ دقت کنید که در صورت دادن هشدار نیز برنامه
در مثال بعدی میبینید که کست کردن نابهجا نیز عواقبی در پی دارد :
خط ۱۱۷:
{{پایان چپچین}}
در متغیر صحیح i مقدار
اشارهگرها را تنها میتوان به اشارهگر کست کرد ، طبق استاندارد کست کردن یک غیر اشارهگر به اشارهگر غیر مجاز نیست ، اما این کار علاوه بر بیهوده بودن میتواند باعث ایجاد خطا در برنامه شما شود ( بدین شکل که آدرس حافظه را به جای محتوای آن بازگرداند ) ، کست کردن اشارهگر به عنوان غیر اشارهگر نیز غیر مجاز نیست اما باعث ایجاد خطا میشود و داده کست شده آدرس حافظه را به جای مقدار مورد اشاره باز میگرداند . کست کردن به عنوان اشارهگر با قرار دادن دادهای که قرار است کست شود در داخل جفت پرانتز باز و بسته و یک علامت استریسک ( ستاره ) پس از نوشتن نوع داده انجام میشود و اگر بخواهید آن را غیر مستقیم کنید تا مقدار دادهای را که اشارهگر به آن اشاره نموده ، بازگرداند یک استریسک دیگر ، پیش از جفت پرانتز قرار میدهید
خط ۱۳۷:
{{پایان چپچین}}
در قطعه کد بالا یک متغیر از نوع صحیح بلند با نام ln مقدار
اگر مبحث نوع داده پوچ را به یاد داشته باشید ، گفتیم که اشارهگر پوچ نمیتواند غیر مستقیم شود مگر آنکه کست شود ( نقش نوع داده دیگری که جز نوع پوچ است را ایفا کند ) . اشارهگرهای پوچ درست مثل اشارهگرهای معمولی کست میشوند و قوانینی که در مورد کست کردن اشارهگرها گفته شد در مورد اشارهگرهای پوچ (void pointers) نیز صادق است . سعی کنید همواره اندازه نوع دادهای که اشارهگر پوچ به آن اشارهکرده با اندازه دادهای که قرار است کست شود ، برابر باشد تا نه فضای اضافی اشغال شود و یا خطایی ایجاد شود و نه مقداری از دست برود
خط ۱۵۷:
{{پایان چپچین}}
در مثال بالا متغیری از نوع داده صحیح بلند با نام l با مقدار
'''تابعها'''
هنوز به مبحث تایع نرسیدهایم اما با این حال چند بار در طول همین سه فصل به صورت مختصر نوشتهایم که تابع ، در زبان C دادهای است که دادههای دیگری را میتواند به عنوان پارامتر داخل جفت پرانتزی که در مقابل نام تابع قرار میگیرند تعریف کند تا
با این مقدمه ، میخواهیم به این مطلب نیز اشاره کنیم که تابعها خود و پارامترهایشان میتوانند به عنوان اشارهگرِ تابع تعریف شوند تا تابع اشارهگر به تابع دیگری اشاره کند و یا پارامتر اشارهگر ، به تابع اشارهگر دیگری اشاره کند و موقع احضار تابعمان ، که پارامتر اشارهگری دارد ، یک اشارهگر را با کمک عملگر آدرس به تابع خود نسبت دهیم . اما این کار از طریق عملی مشابه با کست کردن امکانپذیر است ؛ این کار کست کردن تابع نیست ، اما درست مشابه کست کردن انجام میشود
خط ۲۲۰:
یک نکته را پیش از بررسی چگونگی تبدیل دادهها بگوئیم ؛ آن هم اینکه در صورتی که مشخص نکرده باشید که متغیر شما علامتدار است یا بدون علامت ، معمولاً توسط کامپایلر به صورت پیش فرض علامتدار است ( که در بحث متغیرها هم ذکر شده است ) اما اگر مقداری بزرگتر از آنچه که در متغیر علامتدار جا میشود ، ذخیره کنید ، بیشتر کامپایلرهای مطرح به صورت خودکار آن را بدون علامت در نظر میگیرند . گرچه طبق استاندارد ، شما موظف به تعیین کردن علامتدار بودن یا بدون علامت بودن تمام متغیرهای خود هستید ( که در مورد بدون علامتها ، در صورتی است که مقداری بزرگتر از آنچه که در علامتدار جا میشود را میخواهید ذخیره کنید )
اولین نحوه تبدیل که استاندارد است این است که اگر چند متغیر را با نوع دادههای مختلف با یکدیگر در تعامل بگیرید و به وسیله عملگرها
در تبدیل متغیرهای غیر اعشاری ( مثل int و long ) به متغیرهای اعشاری ( مثل float و double ) ، یک بخش اعشاری درست به همان اندازهای که بزرگترین نوع اعشاری دارد ( در قسمت اعشار خود ) ، به همه آنها اضافه خواهد شد . در تبدیل انواع دادههای اعشاری به دادههای صحیح ، کامپایلرهای مدرن بخش اعشاری را نادیده میگیرند اما برخی کامپایلرها بر اساس گرد کردن اعداد اعشاری کار میکنند که معمولاً بر اساس گرد کردن در ریاضی است ( مثلاً کوچکتر از ۰٫۵ به عدد کوچکتر گرد میشود و بزرگتر از ۰٫۵ به عدد بزرگتر )
در پایان باید بدانید که در تبدیل دادهها ، اگر قرار بر از دست رفتن دادهها باشد ، در سیستمهای [https://fa.wikipedia.org/wiki/%D8%A7%D9%86%D8%AF%DB%8C%D8%A7%D9%86 بیگاندیَن] که دادهها از خانههای ابتدایی به سمت خانههای انتهایی حافظه شروع به پر شدن میکنند ، دادهها از قسمت ابتدایی پاک شده و حذف میشوند ؛ اما در سیستمهای لیتلاندیَن دادهها از سمت انتهایی خافظه شروع به پر شدن میکنند و از سمت انتها ، دادهها و مقادیر نادیده گرفته شده و یا حذف میشوند
خط ۲۴۰:
https://en.wikipedia.org/wiki/Single-precision_floating-point_format
'''توضیح :''' این مبحث مربوط به نوشتن کامپایلر میشود که مبتنی بر علم ریاضیات بوده و در علوم برق و الکترونیک نیز مورد استفاده قرار میگیرد ؛ اما به صورت مختصر در اینجا به نحوه ذخیره شدن و پردازش اعداد اعشاری در زبانهای سطح پائینی مثل C و به صورت سختافزاری ، اشاره میکنیم . عدد اعشاری با دقت معمولی ، ۳۲ بیت است . بیت آخر که ذخیره شده ( بیت اول در خانههای حافظه موقت ) علامتِ عدد را مشخص میکند . عدد ۱- به توان عددی که در بیت اول قرار دارد میرسد که این عدد یا ۱ است که ۱- به توان ۱ میشود ۱- و علامت ، منفی است یا میشود ۰ که در نتیجه ۱- به توان ۰ میشود ، ۱ و علامت ، مثبت است ؛ پس ۱ نمایانگر منفی بودن عدد و ۰ نمایانگر مثبت بودن عدد میباشد . هشت بیت بعدی به صورت ۲ به توان آن عدد منهای ۱۲۷ میباشد و در مبنای دهدهی محاسبه میشود ؛ که از ۰۰۰۰۰۰۰۰ شروع میشود که به صورت قرار دادی نمایانگر عدد ۰ میباشد و اگر در قسمت اعشاری همه اعداد ۰ باشند ، عدد ، ۰ منظور میشود . اما اگر در قسمت اعشاری ( مابقی بیتها ) نیز مقداری ذخیره شده باشد ، ۰۰۰۰۰۰۰۰ به عنوان ۱ منظور میشود که منهای ۱۲۷ میشود ۱۲۶- که در نهایت میشود <sup>۱۲۶-</sup>۲ و مقداری که در ۸ بیتِ توان ، ذخیره میشود تا ۲۵۴ ادامه مییابد ( مقدار ۲۵۵ به صورت قرار دادی برای بینهایت ذخیره شده است که با توجه به علامت ، میتواند منفی بینهایت یا مثبت بینهایت باشد ) که ۲۵۴ منهای ۱۲۷ میشود ۱۲۷ و عدد ۲ به توان ۱۲۷ میرسد . مقداری که از ۸ بیت توان با ۲ به توان آن عدد به دست آمد در ۲۳ بیت بعدی که قسمت اعشاری را ذخیره میکنند ضرب میشود . منتها قسمت اعشاری به صورت اعشار دودویی ( باینری ) ذخیره میشوند که در کامپیوتر با یک ۱ ، نیز جمع میشود . در دستگاه دودویی و در کامپیوتر که معمولاً بیگاندین میباشد بیت بیست و سوم که شماره ۲۲ ـم میشود ؛ ۲ به توان ۱- میرسد و ضربدر عدد بیت بیست و سوم میشود و جمع میشود با ۲ به توان ۲- ضربدر عدد بیت بیست و دوم که میشود شماره ۲۱ و همین طور تا بیت اول که میشود شماره ۰ ادامه پیدا میکند . مثلاً ۰٫۰۱۱۱ که در مبنای دودویی میباشد ، در مبنای دهدهی خودمان میشود : ۰٫۴۳۷۵ ؛ ۲ به توان ۱- ضربدر ۰ میشود ۰ که جمع میشود با ۱ ضربدر ۲ به توان ۲- که میشود ۰٫۲۵ و جمع میشود با ۱ ضربدر ۲ به توان ۳- که میشود ۰٫۱۲۵ و جمع میشود با ۱ ضربدر ۲ به توان ۴- که میشود ۰٫۰۶۲۵ که در نهایت عدد ۰٫۴۳۷۵ به دست میآید . ضمن اینکه در استاندارد ۷۵۴ انستیتوی جهانی مهندسین برق و الکترونیک ، استثناهایی هم در نظر گرفته شده است که با مطالعه لینک بالا و یا پیوند فایل پیدیاف استاندارد IEEE 754 میتوانید از تمام مطالب استاندارد آگاه شوید
در تبدیلهای اعشاری بزرگتر به کوجکتر ( مثلاً double به float ) قسمت غیراعشاری از دست نمیرود و ابتدا از قسمت اعشاری و آن هم از قسمت انتهای آن ، مقادیر نادیده گرفته شده و پاک میشوند که البته با گرد شدن نیز همراه است و طی تبدیل ، اگر قرار بر از دست رفتن مقادیری باشد ، هم رقمهای اعشاری کوچکتر حذف میشوند و هم عدد اعشاری گرد میشود
خط ۲۴۶:
==typedef==
کلیدواژه '''typedef''' برای تعیین یک یا چند شناسه
<code>'''typedef''' ''data-type'' ''new-name''</code>
خط ۳۲۱:
ما در زبان C مجاز هستیم تا اشارهگرها را نیز با کمک typedef تعریف و جایگزین نمائیم مثلاً typedef int* iptr شناسه iptr را به عنوان اعلان کننده اشارهگری از نوع صحیح تعریف میکند . بنابراین با الگوی نوشته شده اگر در داخل برنامه خود بنویسیم : iptr *a شناسه a اشارهگر به اشارهگری از نوع صحیح خواهد بود و iptr b[10] شناسه b را به عنوان آرایه ۱۰ عنصری از اشارهگر از نوع صحیح تعریف میکند
از کلیدواژه typedef میتوان برای تعریف ساختمانها و اجتماعها هم استفاده کرد . برای این کار هم میتوانید با استفاده از typedef و نوشتن کلیدواژه struct یا union و با استفاده از نام برچسب ساختمان یا اجتماع ( به ترتیب ) و سپس با نام جایگزینِ شناسه ، نمونه ایجاد کنید و هم میتوانید هنگام تعریف ساختمان یا اجتماع ، پیش از کلیدواژه struct یا union کلیدواژه typedef را بنویسید سپس برچسب ساختمان یا اجتماع را نوشته و سپس بعد از بلوک و پیش از سمیکالن ( نقطه ویرگول ) ِ ساختمان یا اجتماع نام یا نامهای جایگزین را بنویسید و سپس با نام جایگزین ، نمونه یا نمونههایی ایجاد کنید
مثال :
خط ۳۷۲:
مثلاً اگر یک ساختمان با نام node داشته باشید و بخواهید از آن نمونههای اشارهگر زیادی بسازید یک بار مینویسید typedef node* nodeptr و سپس با کمک نام جایگزین nodeptr چندین نمونه اشارهگر با نامهای معمولی startptr و endptr و curptr و prevptr و errptr و refptr با نوشتن در مقابل nodeptr ایجاد میکنید . از این ترفند در مورد ساخت لیستهای پیوندی نیز استفاده میشود
در مورد تابعهای اشارهگر نیز typedef به وفور به کار میرود ، مخصوصاً که بخواهیم تابعهای اشارهگری تعریف کنیم که پارامترهای آنها تابعهای اشارهگر دیگری باشند . مثال :
{{چپچین}}
خط ۴۱۰:
'''دقت کنید :''' در جایگزینی تابع به واسطه typedef نامی که برای تابع انتخاب میکنید ، همان نام جایگزین برای تابع است
'''توضیح :''' در مثال بالا یک تابع با نام add تعریف نمودیم که دو پارامتر a و b را میپذیرد و آنها را با هم جمع میکند و حاصلجمع را به عنوان خروجی عرضه میکند . تابع sub دو پارامتر a و b را میگیرد و b را از a کم میکند و نتیجه را عرضه میکند . تابعی از نوع صحیح اشارهگر با نام function که با همین نام جایگزین نیز شده است ( به خاطر وجود کلیدواژه typedef ) با دو پارامتر a و b اعلان شده و سپس تابع call_function با پارامتر اول که یک function است که خود یک صحیح اشارهگر میباشد تعریف شده و همچنین دو پارامتر دیگر یعنی a و b را میپذیرد و به عنوان خروجی تابعی را از نوع صحیح اشارهگر که روی دو پارامتر a و b پردازش میکند به عنوان خروجی عرضه میکند . در نهایت در تابع main یک متغیر صحیح با نام sum به عنوان حاصل اعلان شده که بار اول call_function را احضار میکند و مقدار آن را ذخیره میکند که به عنوان آرگومان ، تابع add و اعداد
میتوان تابعهای اشارهگری که تابعهای اشارهگری نیز دارند را هم با کمک typedef خواناتر نمود . مثلاً در کرنل FreeBSD نوشته شده بود :
خط ۴۶۵:
'''تفاوت typedef و دستور مستقیم define'''
typedef ، تنها شناسهای را که برای آن معین نمودهایم با نوع داده تعریف شده جایگزین میکند در حالی که دستور مستقیم define هر چیزی را حتی مقدار و موجودیای را که با define تعریف کرده باشیم جایگزین میکند . define میتواند حتی کلیدواژهها و یا تابعهای کتابخانهای را هم جایگزین کند ( که البته کاری ریسکپذیر است و باید از آن اجتناب شود ) typedef مشمول قانون حوزه دید میشود اما دستور مستقیم define# حوزهای ندارد و هر جا که تعریفی برای آن مقرر شده باشد پس از دستور ، هر تعریف شده را ، با مقدارش جایگزین میکند که البته یکی از تفاوتهای عمده typedef و define این است که define نمیتواند نوع دادهای را تعریف کند و فقط هر جا که کاراکتر یا رشته تعریف شده برای خود & در سر راهش قرار بگیرد با مقداری که برای آن تعریف شده جایگزین میکند مثلاً typedef با کد typedef float * fp هر fp را به اشارهگر اعشاری تبدیل میکند و اگر بنویسیم : fp a, b, c معادل خواهد بود با : float *a, *b, *c در حالی که اگر از دستور مستقیم define استفاده کنیم و بنویسیم :
{{چپچین}}
|