زبان برنامه نویسی سی/نوع داده پوچ

ویکی‎کتاب، کتابخانهٔ آزاد
پرش به ناوبری پرش به جستجو
Gnome-go-last.svg
Gnome-go-first.svg

پوچ

Void

نوع داده پوچ در زبان C و خانواده آن دو کاربرد و معنا دارد که یکی داده‌ای است که اندازه‌ای ندارد که درمورد اشاره‌گرهای پوچ به کار می‌رود و یکی به معنی توخالی می‌باشد که درمورد تابع‌های برنامه به کار می‌رود . مورد اول یعنی اشاره‌گرهای پوچ که به آن‌ها اشاره‌گرهای همگانی نیز می‌گوئیم ، اشاره‌گرها‌یی هستند که اندازه‌ای ندارند و به عبارت واضح‌تر می‌توانند هر اندازه‌ای را به خود بگیرند و به همین دلیل است که به آن‌ها همگانی می‌گوئیم . یعنی شما به هر داده‌ای که با اشاره‌گر پوچ اشاره کنید ، اندازه اشاره‌گر شما به اندازه داده‌ای که به آن اشاره نموده‌اید در خواهد آمد . شما با اشاره‌گرهای پوچ می‌توانید به هر داده‌ای اشاره کنید . فقط باید دقت کنید که استاندارد C98/99 اشاره دادن اشاره‌گر پوچ را به تابع‌های اشاره‌گر تعریف نکرده بود و از همین روی کامپایلرها به غیر از محیط‌های پازیکس از آن پشتیبانی نمی‌کردند ولی این عمل در استاندارد C11 گنجانده شده است و شما اگر قصد استفاده از چنین کدی را دارید ؛ ابندا اطمینان کسب کنید که کامپایلر شما با استاندارد سال ۲۰۱۱ سازمان ایزو برای زبان سی ، سازگار است . ضمن اینکه سیستم عامل‌هایی که از رابط برنامه‌نویسی و محیط گرافیکی POSIX ( پازیکس ) استفاده می‌کردند ( که عمدتاً شامل سیستم عامل‌های مشابه یونیکس می‌شد و می‌شود ) به علت نحوه تعریف محیط پنجره‌ای خود از آن استفاده کرده و می‌کنند برای همین در محیط این سیستم عامل‌ها برنامه‌نویسان از این ترفند نیز استفاده می‌کردند و محدودیتی نداشتند که شامل سیستم عامل‌های لینوکس Linux ، مک‌اواس macOS ، سولاریس Solaris و سیستم عامل‌های سری بی‌اس‌دی BSD می‌شود . ابتدا مثالی از کاربرد اول داده پوچ می‌زنیم تا به کاربرد بعدی آن برسیم :

char c = 'p';
long g = 15248732251;

void *v = &c;
v = &g;

در مثال بالا اشاره‌گر پوچ v ابتدا به کاراکتر c اشاره می‌کند که یک بایت حجم دارد ، اما در اشاره بعدی به داده بلند g اشاره می‌کند و حجم آن به ۸ بایت ( در سیستم های ۳۲ بیتی ) تغییر می‌کند

دقت کنید : طبق استاندارد C ما مجاز به غیرمستقیم کردن اشاره‌گر پوچ نیستیم . اما با ترفندی که از روش نقش ایفا کردن استفاده می‌کند می‌توانیم به داده‌هایی که اشاره‌گر پوچ به آن‌ها اشاره می‌کند دسترسی پیدا کنیم ( موضوع نقش ایفا کردن داده‌ها در آخرین موضوع از این فصل بررسی خواهد شد ) پس هر جایی از برنامه که کدی بنویسیم که جهت دسترسی به مقدار و موجودی داده اشاره شده توسط اشاره‌گر پوچ ؛ بخواهیم اشاره‌گر پوچ را غیر مستقیم کنیم باید به آن نقش بدهیم . اما نکته‌ای که در مورد نقش دادن به اشاره‌گر پوچ برای غیر مستقیم کردن آن وجود دارد اینست که داده‌ای که قرار است نقش ایفا کند نیز باید عنوان اشاره‌گر تعریف شود ؛ بنابراین از دو علامت استریسک ( * ) باید استفاده کنیم . مثال :

int i = 5;
void *v = &i;
printf("%d", *(int *)v);

در مثال بالا نوع داده v که اشاره‌گر پوچ است ، نقش یک صحیح ( int ) را ایفا کرده است . چرا که در متن برنامه خود می‌خواهیم توسط تابع کتابخانه‌ای printf به محتوای متغیز i دسترسی پیدا کنیم . ضمن اینکه باید دقت کنید که در هنگام نقش دادن به داده‌ها ؛ نقشی را بدهید که مقدار داده از دست نرود . یعنی اگر در اینجا اشاره‌گر پوچ به یک داده بلند ( long ) اشاره می کرد و به آن نقش صحیح می‌دادیم ، مقدار خروجی دستخوش تغییر می‌شد و بخشی از مقدار آن از دست می‌رفت ولی اگر نوع نقشی را که قرار بود ایفا کند ، نوع داده خیلی بلند ( long long ) تعریف می‌کردیم مقداری از دست نمی‌رفت اما باعث می‌شد تا فضای بیشتری از حافظه اشغال شود . پس مسلماً نوع داده بلند ( long ) برای آن کاملاً مناسب می‌بود ( نه مقدار آن از دست می‌رفت و نه فضای اضافی اشغال می‌کرد )

اما کاربرد اشاره‌گرهای پوچ چیست ؟ کاربرد عمده اشاره‌گرهای پوچ در ایجاد تابع‌های همگانی است . زمانی که کامپایلر خودش موظف است تا از اشاره‌گر پوچ استفاده کند ؛ نیازی نیست تا ما برایش اشاره‌گر را غیر مستقیم کنیم و بخواهیم به آن نقش بدهیم . اگر پارامتر یا پارامترهای یک تابع از نوع اشاره‌گر پوچ تعریف شوند ، تابع مذکور می‌تواند هر نوع داده‌ای را به عنوان آرگومان دریافت کند ( یعنی تفاوتی نمی‌کند یک کاراکتر باشد یا صحیح یا بلند یا مثبت یا منفی ) برخلاف تابع‌های معمولی که فقط نوع پارامتر یا پارامترهایی که برایشان تعریف شده را ، آن هم به ترتیب ، می‌پذیرند ؛ تابع‌های همگانی که پارامترهای آن‌ها از نوع اشاره‌گر پوچ تعریف می‌شوند می‌توانند هر نوع داده‌ای که به آن‌ها داده شد را بدون در نظر گرفتن نوع آن ، به عنوان آرگومان دریافت کرده و با توجه به کدهای داخل تابع ، آن‌ها را مورد پردازش قرار دهند

استفاده از عملیات منطقی و یا ریاضی بر روی اشاره‌گرهای پوچ در استاندارد C تعریف نشده است ؛ بنابراین پاره‌ای از کامپایلرها از آن پشتیبانی نمی‌کنند و به شما این اجازه را نمی‌دهند . اما برخی از کامپایلرها مثل GCC این اجازه را به شما می‌دهند . اما دقت کنید که در زمان نوشتن کدهای مربوطه ، باید به اشاره‌گر پوچ خود نقش بدهید

استفاده دیگر اشاره‌گرهای پوچ ، ایجاد اشاره‌گرهایی به آدرس خاصی از حافظه موقت می‌باشد که در مبحث پیشین آن را بیان کردیم . اما استفاده از نوع داده پوچ به جای نوع داده دیگری مثل int بهتر است . چرا که هر مقدار کوچک یا بزرگی که بخواهیم می‌توانیم در آن ذخیره کنیم ( بدون از دست رفتن اطلاعات و یا اشغال فضای بیهوده ) . همچنین به کمک اشاره‌گرهای پوچ می‌توان تمام آدرس‌های حافظه موقت را مشاهده نمود و حتی مقدار آنها را تغییر داد ؛ برای بازگردانی آدرس حافظه موقتِ یک داده‌ی دیگر که توسط اشاره‌گر پوچ مورد اشاره قرار گرفته است نیازی به ایفای نقش نیست ، اما باید آرگومان متناسب با آن را با p% تعریف کنید . مثال :

#include<stdio.h>

int main(void)
{
	long l = 5684997;
	void* vptr = &l;
	printf("%p\n", vptr);
	return 0;
}

نکته : مقدار اشاره‌گرهای پوچ در مبنای شانزده و به صورت هگزادسیمال ( شانزده‌شانزدهی ) باز گردانده می‌شوند

کاربرد دیگر نوع داده پوچ در مبحث تابع‌ها می‌باشد . از آنجایی که هنوز به مبحث تابع نرسیده‌ایم ( اما با این حال تا بدینجا چند بار به آن اشاره نموده‌ایم ) نمی‌توانیم مفصلاً راجع به تابع بحث کنیم . اما تابع‌ها در C داده‌هایی هستند از نوع متغیرها که پارامترهایی برای آن‌ها می‌توان تعریف نمود تا با توجه به بلوک داخلی تابع ؛ پس از ارجاع داده‌هایی به آن تابع ؛ تابع تعریف شده ما روی داده‌های ارسال شده پردازش‌های تعریف شده توسط ما را انجام داده و مقداری را به عنوان خروجی ارائه دهد . برخی تابع‌ها پارامتر ندارند و تنها مجموعه‌ای از عملیات را داخل خود انجام می‌دهند ( طبق تعریف ما ) و برای همین برای سهولت در اجرای چند عمل از تابع خود استفاده می‌کنیم . ضمن اینکه تعداد زیادی تابع در فایل‌های سرآیند استاندارد C موجود هستند که ارتباط برنامه‌نویس با سخت افزار ، سیستم عامل ، فایل‌ها را فراهم کرده و بسیاری از کارهای دیگر را تسهیل می‌کنند ( یعنی به جای اینکه ما تابعی بنویسیم تا کار متدوال در برنامه‌نویسی را انجام دهد ؛ این تابع از قبل توسط عرضه کننده کامپایلر نوشته شده و در داخل فایل سرآیند قرار دارد که با ضمیمه کردن فایل سرآیند مربوطه - دستور مستقیم include - می‌توانیم از آن‌ها استفاده کنیم )

تابعی که با نوع داده void تعریف می‌شود نمی‌تواند خروجی‌ای داشته باشد . یعنی نوع داده تابع ما پوچ است . از همین روی استفاده از دستور return در تابع پوچ به غیر از حالت استاندارد ;return غیر مجاز است . یعنی اگر بنویسیم ;return a * b کامپایلر از ما خطا خواهد گرفت . از آنجایی که نوشتن استاندارد کد همیشه توصیه می‌شود در پایان تابع پوچ خود بنویسید : ;return ؛ اما تابع پوچ به چه دردی می‌خورد ؟ تابعی که با یک نوع داده پایه مثل int تعریف شده باشد پس از کامپایل و اجرای برنامه همواره مقداری از فضای حافظه را اشغال خواهد نمود و تعریف شده است تا هر زمان که لازم شد احضار شود . اما تابع پوچ زمانی که نوبتش فرا می‌رسد و در برنامه احضار می‌شود اجرا شده و سپس پایان یافته و از بین می‌رود ( یعنی فضایی که برای آن تخصیص یافته بود پاک می‌شود ) و روال اجرای برنامه باز می‌گردد به ادامه برنامه‌ای که ما نوشته ایم . برای مثال تابع main جزء اصلی برنامه ما حساب می‌شود و طبق استاندارد هر برنامه‌ای که به زبان C نوشته می‌شود باید یک تابع داشته باشد با نام main که کنترل تمام برنامه را بر عهده دارد . حال اگر شما بخواهید برنامه شما کاری را انجام دهد و سپس بسته شده و کاملاً حافظه اشغال شده را باز گرداند می‌توانید از نوع داده پوچ برای تعریف آن استفاده کنید ؛ در غیر این صورت ( یعنی اگر برنامه شما باید بافی بماند و طبق دستور خاصی خارج شود ) هرگز آن را پوچ تعریف نکنید

تابع‌های پوچ مقداری را باز نمی‌گردانند . این جزء خاصیت تابع void است . و اگر در قسمتی تابع پوچی تعریف کنید که احضار شود ( در تابع mian یا در تابعی که در تابع mian احضار شده ) پس از پایان آن اجرای برنامه را به مابفی کدها می‌سپارد . بنابراین مثلاً اگر بخواهید یک تابع برای ماشین حساب تعریف کنید هرگز نباید از نوع داده void برای تعریف تابع خود استفاده کنید . چرا که قرار است ماشین حساب شما عددی را داده‌ای را به عنوان خروجی صادر کند ؛ که اگر پوچ باشد امکان پذیر نیست

طبق استاندارد C ، تابعی که پارامتر ندارد و ما نمی‌خواهیم پارامتری برای آن در نظر بگیریم تعریف کنیم باید در پرانتزهای تابع مورد نظر کلیدواژه void را ذکر کنیم . یعنی مثلاً می‌نویسیم : int hello(void) سپس ما مجاز نخواهیم بود تا داده‌ای را به عنوان آرگومان به تابع تسبت دهیم تا مورد پردازش قرار بگیرد ؛ چرا که تابع بدون پارامتر تعریف شده و نمی‌تواند آرگومانی را پذیرا باشد . گرچه اکثر کامپایلرها در صورت ننوشتن کلیدواژه void و جا انداختن آن منظور برنامه‌نویس را تابع بدون پارامتر در نظر می‌گیرند ( یعنی از شما خطا نخواهند گرفت که پارامتری تعریف نکرده‌اید ) اما بهتر است شما همواره به صورت استاندارد کد خود را بنویسید یعنی پرانتز باز و بسته خود را () خالی نگذارید ( همان طور که هم‌اینک نوشتیم ) و حتماً کلیدواژه void را ذکر کنید مثل :


#include<stdio.h>
int hello(void)
{
printf("hello world!\n");
return 0;
}

می‌پذیریم که در صورت آشنایی تازه شما با زبان C بخش‌هایی از کد بالا نامفهوم است ؛ اما اگر کتاب را تا انتها دنبال کنید به خوبی تمام مفاهیم را درک خواهید کرد اما به صورت خلاصه توضیح می‌دهیم که یک تابع از نوع داده صحیح تعریف کردیم که پارامتری نمی پذیرد و در بلوک آن ( که قسمت ضروری تابع است ) از تابع کتابخانه‌ای printf استفاده نمودیم که در فایل سرآیند stdio.h قرار دارد و وظیفه آن چاپ فرمت شده متن است print formatted و به عنوان آرگومان ، می‌توان هر متنی را به آن نسب داد و همچنین از کاراکتر‌های خروج می‌توان در آن استفاده نمود که ما در اینجا از n\ استفاده کردیم . بعدها خواهید دید که اگر بخواهید در متن خود بخش‌هایی را که معلوم نیست چه چیز خواهند بود در تابع printf بگنجانید ، می‌توانید متغیر‌هایی را تعریف کنید و به تابع نسبت دهید که در این راه باید از کاراکترهای خروج کمک بگیرید ( اگر به یاد داشته باشید کاراکتر نوشته شده توسط ما در قطعه کد بالا خط جدیدی را در برنامه ایجاد می‌کند ) ضمن اینکه برنامه بالا فقط در محیط خط دستوری قابل اجراست و زبان C به خودی خود رابط کاربری گرافیکی ندارد ( که البته ما در این کتاب رابط‌های موجود برای زبان C را نیز گنجانده‌ایم ) در پایان ، دستور return می‌گوید که تابع چه چیزی باید باز گرداند که ما نوشته ایم 0 ! یعنی چیزی باز نمی‌گرداند که اگر یک تابع غیراصلی بود برنامه دنبال می‌شد تا کدهای بعدی اجرا شوند اما چون تابع main است که جزء زبان سی بوده و اجرا و کنترل تمام برنامه توسط تابع main انجام می‌شود و حتی تابع‌های دیگر را باید از طریق تابع main فراخوانی کنیم ، با دستور ;return 0 برنامه به اتمام می‌رسد و فضایی که برای اجرای برنامه در حافظه موقت اشغال شده بود ، آزاد می‌گردد