زبان برنامه نویسی سی/ساختمان و اجتماع

مقدمه

ویرایش

نوع داده ساختمان Structure و همین طور نوع داده اجتماع Union ، انواع داده‌های ترکیبی هستند که جزء انوع پایه محسوب نمی‌گردند و شما برای اینکه آنها را ایجاد نمائید باید برای هر ساختمان و یا هر اجتماع عضوهایی ایجاد نمائید که هر عضو ، علاوه بر اینکه می‌تواند خود نیز ساختمان یا اجتماع باشد ، اما در نهایت باید از انواع متغیرهای پایه باشد . یعنی حتی اگر چند ساختمان و اجتماع را تو در تو ایجاد نمائید ، در داخلی‌ترین آنها مجبور به استفاده از انواع پایه مثل long ، int یا char خواهید بود

مزیت اصلی ساختمان که همان Structure می‌باشد و با کلیدواژه struct ایجاد می گردد و از سویی دیگر اجتماع که با کلیدواژه union ایجاد می‌گردد ، پشت سر هم ذخیر شدن متغیرهاییست که عضو آن می‌باشند و کار با داده‌هایی را که باید در یک دسته قرار بگیرند راحت‌تر نموده و دسترسی به متغیرهای آنها سریع‌تر صورت خواهد پذیرفت ( نسبت به اینکه متغیرها را جداگانه و بدون ساختمان یا اجتماع ایجاد نمائید )

تفاوت اصلی ساختمان با اجتماع در این است که در یک ساختمان ، حجم اشغال شده توسط آن ، مجموع حجم اشغال توسط متغیرهاییست که در داخل آن ایجاد گردیده اند ، اما حجم اشغال شده توسط یک اجتماع ، حجم بزرگ‌ترین عضو آن اجتماع خواهد بود . قوانین ایجاد و استفاده از ساختمان و اجتماع تا حدود زیادی یکسان است اما نحوه عملکرد آنها از یکدیگر متفاوت می‌باشد

ساختمان

ویرایش

شکل کلی ایجاد یک ساختمان این گونه می‌باشد :

struct tag-name
{

date-type name;
data-type name;
.
..
..

} instance1, instance2, ...;

از هر ساختمان برای استفاده باید دست کم یک نمونه ایجاد کرد . در کد بالا instance1 و instance2 دو نمونه از ساختمان مثال ما می‌باشند که ایجاد گردیده‌اند . در تعریف C محدودیتی برای ایجاد تعداد نمونه‌ها وجود ندارد و شما قادر به ایجاد هر تعداد نمونه از روی هر ساختمان خواهید بود . اما بدون وجود دست کم یک نمونه ، عملاً وجود یک ساختمان بیهوده می‌باشد و با استفاده از نمونه یا نمونه‌هاست که شما قادر به دسترسی به اعضای ساختمان که همان متغیرهای آن ساختمان می‌باشند خواهید بود ( تا از آنها استفاده کنید ) و می‌توانید به آنها در ادامه برنامه مقدار بدهید . اما tag-name که در کد الگوی بالا نوشته گردید ، یک گزینه اختیاری می باشد که در صورت نوشتن آن ، ساختمان دارای نام برچسب خواهد بود و بعد از تعریف ساختمان ( بعد از سمی کالن آخر / بعد از آخرین نمونه ) در ادامه برنامه نیز می توانید از آن ساختمان با استفاده از نام برچسب ، یک یا چند نمونه جدید ایجاد نمائید . در داخل آکولادهای باز و بسته شما می توانید اعضای ساختمان را بنویسید که متغیرهایی از نوع پایه هستند و به هیچ وجه در داخل اعلان ساختمان ، اعضای آن را مقداردهی نکنید ( این کار برخلاف استاندارد می‌باشد و باعث ایجاد خطا می‌شود ) و همان طور که در ابتدای مبحث نیز نوشتیم ، شما می‌توانید ساختمان‌ها یا اجتماع‌ها را تو در توی هم ایجاد نمائید . همچنین دانستن اینکه داده‌های یک ساختمان به صورت رو به جلو در حافظه موقت ذخیره می‌شوند به درک بهتر آن کمک می کند ، یعنی از خانه های قبل تر ( یا به عبارتی با شماره کمتر ، خانه های بالاتر ) آغاز می گردد و به خانه های بعدی می رود ( با شماره بیشتر ، خانه های پائین‌تر ) به هر خانه حافظه موقّت ( RAM ) که می تواند یک بایت را در خود ذخیره نماید یک آفست ( Offset ) از سگمنت ( Segment شیار RAM ) می گویند

مقدار دهی در نمونه ساختمان ، از ۲ راه انجام می‌پذیرد که در صورتی که نمونه را تعریف کنید اما به اعضا مقدار ندهید می‌تواند منجر به داشتن مقدار و موجودی زباله یا آشغال ( garbage value ) در متغیر و عضو شود . اما بیان این نکته نیز خالی از لطف نیست که اگر هر یک از اعضای یک ساختمان ( متغیرها ) از کلاس ذخیره ایستا static تعریف شده باشد ، کامپایلر به صورت خودکار به آن مقدار اولیه 0 خواهد داد و در صورت تعریف عضو متغیر از نوع اشاره‌گر ، مقدار اولیه آن NULL خواهد بود ( جهت مباحث کلاس‌های ذخیره و اشاره‌گر باید به ادامه کتاب مراجعه نمائید ، اما فعلاً ضرورتی ندارد و فقط جهت آگاهی شما به این دو نکته اشاره نمودیم ) . هر نمونه به صورت مجزا ( از نمونه دیگری که ایجاد کرده باشید ) تمام اعضای ساختمان خود را کپی برداری می نماید ، بنابراین اگر مقدار یک عضو در یک نمونه مثل instance1 مقدار 5 باشد ، مقدار همان عضو در نمونه instance2 ممکن است متفاوت باشد ؛ مثلاً 8

جهت مقدار دهی اولیه به صورت یکجا ، طوری که بخواهید ، ساختمان را اعلان کنید و سپس با ایجاد نمونه به اعضای آن مقدار دهید تا تعریف شوند ؛ دو راه در زبان C در نظر گرفته شده است . در روش اول که در استاندارد 89 تعریف شده است و هنوز هم بعد از سالها و استانداردهای جدید ، توسط اکثر کامپایلرها پشتیبانی می‌شود بدین شکل است که در مقابل هر نمونه یک علامت تساوی قرار می‌دهید و یک جفت آکولاد را که باز و بسته هستند می‌نویسید و در داخل آن آکولادها ، شروع به نوشتن مقدار و موجودی مد نظرتان برای هر یک از اعضای ساختمان آن نمونه ، به ترتیب از اولین عضو تا آخرین عضو ، می‌کنید و این مقادیر را به کمک علامت " , " از یکدیگر جدا می‌نمائید . بدیهیست در صورتی که تعداد نوشته شده مقادیر کم‌تر از تعداد اعضای ساختمان باشد ، اعضا تا بدانجا که مقدار دهی شده‌اند ، موجودی خواهند داشت و در صورت اضافه تر بودن مقادیر نوشته شده برای اعضا ، مقادیر اضافه نادیده گرفته می‌شوند و از آنجایی که مقادیر به صورت متوالی به اعضا تخصیص داده می‌شود ، شما مجاز به دادن مقدار نامتناسب به نوع متغیر نخواهید بود ( مثلاً مقدار 'a' را به یک متغیر int بدهید ! )

در روش دوم در مقابل نمونه یک تساوی قرار می‌دهیم و یک جفت آکولاد را که باز و بسته هستند می‌نویسیم و در داخل آکولادها با عملگر " . " نام هر عضو را بدون فاصله ( نسبت به عملگر نقطه ) می‌نویسیم و به آن مقدار می‌دهیم و دوباره با کاما " , " آنها را از یکدیگر جدا می‌کنیم . مزیت این روش در این است که هر عضوی را که مد نظرمان باشد مقدار می‌دهیم و هر کدام را که مایل به مقدار دهی آن نبودیم مقدار نمی‌دهیم و یا می‌توانیم به صورت درهم به آنها مقدار بدهیم . عملگر " . " که دات یا نقطه گفته می‌شود جهت دسترسی به اعضای ساختمان توسط نمونه است ، اگر خارج از تعریف ساختمان بخواهید به عضوی دسترسی پیدا نمائید یا مقداری بدهید می توانید با نوشتن نام نمونه و یک نقطه بدون فاصله و دوباره بدون فاصله نوشتن نام عضو به آن دسترسی پیدا نمائید

مثال ۱ :

struct N
{
int a, b, c;
char h;
double M;
};

struct N object = { 5, 26, 32, 'c', 157.8852 };

مثال ۲ :

struct
{
int a , b;
char c;
float d;
} second_object = { .d = 58.1726 };

مثال ۳ :

>
struct fado
{
int a;
char b;
}

struct fado f_object, s_object;

f_object.a = 4;
s_object.a = 6;
s_object.b = 'D';

در مثال های بالا نحوه استفاده از کلیدواژه struct را جهت ایجاد ساختمان و ایجاد عضو برای آن و در نهایت ساختن نمونه از روی آن و دادن مقدار به آنها به روش های مختلف را نمایش دادیم

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

اگر ساختمان‌ها و یا اجتماع‌هایی را تو در تو تعریف نمائید و از ساختمانی و یا اجتماعی درونی‌تر ، نمونه ای نسازید ، نمونه آن ساختمان و یا اجتماع درونی ، اعضای ساختمان یا اجتماع بیرونی‌تر محسوب می گردد و جهت دسترسی به اعضای آن به وسیله نمونه ساختمان یا اجتماع بیرونی‌تر ، دسترسی می یابید . همچنین فراموش نکنید که اگر ساختمان یا اجتماع درونی‌تر دارای نمونه باشد ، به همین صورت عمل می کنید و به وسیله عملگر " . " از نمونه بیرونی‌تر شروع کرده و تا نمونه داخلی پیش می روید و در نهایت عضو مورد نظر را فراخوانی می‌نمائید

مثال ۱ :

struct f_m
	{
	int a, b;
	char d;
		struct s_m
		{
		int g;
		} s_m_object;
	} f_m_object;

	f_m_object.s_m_object.g = 8;

مثال ۲ :

struct outer
	{
	struct inner
	{
	int a, b;
	}Y;
	int c;
	}X;

	X.Y.a = 2;
	X.Y.b = 7;

مثال ۳ :

struct point
	{
	int x, y;
	} first_point;

	struct rectangle
	{
		struct point top_left, bottom_right;
	} my_rectangle;

my_rectangle.top_left.x = 0;
my rectangle.top_left.y = 7;

my_rectangle.bottom_right.x = 10;
my_rectangle.bottom_right.y = 0;


در مثال اول ساختمان s_m داخل ساختمان f_m قرار دارد ؛ از روی ساختمان s_m نمونه s_m_object و از روی ساختمان f_m نمونه f_m_object ساخته شده‌اند . از روی نمونه f_m_object به نمونه s_m_object و در نهایت عضو آن یعنی g دسترسی پیدا کرده و مقدار 8 را به آن اختصاص داده‌ایم . در مثال دوم ، ساختمان درونی تر inner دارای نمونه Y است که داخل ساختمان بیرونی‌تر یعنی outer اعلان شده و ساخته شده است ، پس برای دسترسی به اعضای آن ، یعنی دو متغیر صحیح a و b ، ابتدا نمونه ساختمان بیرونی‌تر Y را نوشته و با عملگر نقطه به نمونه ساختمان درونی‌تر X دسترسی پیدا نموده و به متغیر خود در ساختمان درونی مقدار داده‌ایم . در مثال آخر یک ساختمان با نام point و یک ساختمان با نام rectangle ایجاد نموده‌ایم که داخل ساختمان rectangle دو نمونه از ساختمان point ایجاد کرده‌ایم و در نهایت برای ساختمان rectangle یک نمونه با نام my_rectangle ایجاد کرده و در نهایت به اعضای نمونه‌ها مقدار دهی نموده‌ایم . هر چند این مطلب که در مورد مثال سوم نوشته شد صدق می کند اما تنها در استاندارد C11 و در سال 2011 تعریف شده است اما بسیاری از کامپایلر‌ها از جمله GCC و کامپایلر های مبتنی بر آن و همچنین Open Watcom از دیرباز از آن پشتیبانی نموده و می‌نمایند

در نظر داشته باشید که ساختمان‌ها را با شیوه خاصی می‌توان تعریف مجدد نمود . اگر کلیدواژه struct را به همراه نام برچسب ساختمان مورد نظر ( که تعریف شده است ) را داخل یک بلوک ( بلوک دیگری پس از بلوک ساختمان تعریف شده ) بنویسیم ( همراه با سمی کالن پس از آن ; ) می توانیم دوباره همان ساختمان را تعریف دیگری نمائیم . یعنی ساختمان جدید ، همان نام ساختمان قدیمی را خواهد داشت امّا اعضا و نمونه های دیگری را دارا خواهد شد . در این باز-تعریف ، کامپایلر وقتی وارد بلوکی که ساختمان برای بار دوم تعریف شده است می شود ، تعریف ابتدایی آن را ( ساختمان اوّل را ) نادیده می‌گیرد و تا اتمام بلوک ساختمان دوم ، تعریف ساختمان دوم را لحاظ می‌نماید و به محض پایان بلوک ؛ مجدداً تعریف اوّل آن را لحاظ خواهد نمود

مثال :

struct thing { int a, b; } th1;
/* ... */

{
struct thing;
struct thing { float c, d; } th2;

}

در مثال بالا ساختمان thing یک بار تعریف شده است اما در ادامه پس از چند خط کد ، در داخل یک بلوک با نوشتن ;struct thing امکان تعریف دیگری از آن را فراهم آورده و آن را بازتعریف نموده‌ایم . تعریف دوّم ساختمان thing پس از آکولاد پایانی قطعه کد بالا نادیده گرفته خواهد شد . دقت کنید که این عمل علاوه بر اینکه توسط بسیاری از کامپایلرها به رسمیت شناخته نمی شود ، برای خود شما هم می تواند گیج کننده باشد و در پایان ممکن است دچار اشباه مهلکی ( fatal error ) شوید . پس سعی کنید از استفاده از این قابلیت چشم بپوشید

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

کامپایلرها روش های مختلفی را برای جلوگیری از فضا سازی ( padding یا gaping ) در نظر گرفته‌اند که به آن اصطلاحاً پک کردن می‌گویند ( packing ) و دو روش مستعمل آن ، یکی استفاده از دستور پیش پردازنده :

#pragma pack

در ابتدای برنامه شما و در کنار پیش پردازنده های دیگر است و یا استفاده از ماکروی :

__attribute__((__packed__))

پیش از نوشتن نام برچسب ساختمان و یا اگر برچسبی برای آن در نظر نگرفته اید پس از کلیدواژه struct می‌باشد

مثال ۱ :

struct __attribute__((__packed__)) 
{
    IP_ADDR     MyIPAddr;               // IP address
    IP_ADDR     MyMask;                 // Subnet mask
    IP_ADDR     MyGateway;              // Default Gateway
        // etc...
} APP_CONFIG;

مثال ۲ :

#pragma pack
struct school
{
	double a;
	int b;
	char h;	
	int c;
	char d;	
};

مثال اول کاربرد بیشتری در سیستم عامل‌های لینوکس دارد ( برای کامپایلرهایی مثل GCC ) و مثال دوم در سیستم عامل های ویندوز ( کامپایلرهایی مثل Visual Studio )

بیت فیلدها

ویرایش

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

طبق استاندارد زبان C داده‌های نوع صحیح ( int ) به صورت بدون علامت یا با علامت می توانند به صورت بیت فیلد در بیایند که برای این عمل طبق معمول ، متغیّر را تعریف می‌نمائیم ؛ منتها پیش از سمی کالن ( ; ) یک کالن ( : ) می گذاریم ( با فاصله از نام متغیر ) و با یک فاصله دیگر عددی را که نشان می‌دهد متغیِر چند بیت Bit باید اشغال نماید را می‌نویسیم ( دقت کنید که بیت با بایت مفاوت است ؛ هر 8 بیت ، 1 بایت است )

مثال :

struct atoosa
{
int a : 5;
int b : 2;
};

در برخی کامپایلر ها نظیر Open Watcom و GCC استفاده از تبدیل متغیر به بیت فیلد در مورد متغیّر های پایه دیگر نیز به رسمیت شناخته می شود ( یعنی می توانید حجم long ، char یا float را هم توسط ترفند بیت فیلد بکاهید ) . تبدیل متغیِرها به بیت فیلد هیچ گونه خللی در اجرا یا کامپایل برنامه ایجاد نمی‌کند و هیچ لطمه‌ای به سرعت آن نیز نمی‌زند . قابلیت بیت فیلد کردن متغیّرها در زبان C باعث می‌شود تا زبان سی به زبان های سطح پائین برنامه نویسی نزدیک شود ( شما می‌توانید بیت‌ها را هم علاوه بر بایت‌ها کنترل کنید )

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

-(2^N)/2

تا

((2^N)/2) - 1

می باشد در حالی که اگر بدون علامت باشد از 0 تا

(2^N) - 1

خواهد بود

در اینجا عدد N همان عددی است که شما به عنوان سقف مقداری که متغیر می تواند در خود جای دهد و فضایی که قرار است بیت‌فیلد اشغال کند تعیین کرده‌اید که این رقم نمی‌تواند از حجمی که نوع متغیر به صورت استاندارد اشغال می‌کند ، بیشتر باشد ( علامت ^ نیز به مفهوم توان است ) . بدیهیست اگر برای بیت-فیلد خود ، عدد پنج را تعیین کرده باشید ، متغیر شما 5 بیت می تواند اشغال نماید که بزرگ ترین عددی که شما می‌توانید به آن بدهید در مبنای 2 که خانه‌های حافظه را پر نماید 11111 می‌باشد ( هر بیت فقط توانایی ذخیره یک 0 یا یک 1 را دارد ) که در مبنای دهدهی خودمان می شود 32 و چون عدد صفر نیز مثبت حساب می شود بزرگترین عددی که می‌توانید برای مقدار متغیر بدون علامت خود وارد کنید 31 می باشد و در صورتی که متغیر خود را علامت‌دار تعریف کرده باشید ( اگر به صورت int نوشته باشد و مشخص نکرده باشید که علامت‌دار است یا نه ، علامت‌دار در نظر گرفته می‌شود ) از 15- تا 14 می‌توانید عددی را به عنوان مقدار متغیر تعیین نمائید . همان طور که پیشتر نیز گفتیم اگر در همین مثال ، شما عدد بیت‌فیلد را 5 تعیین کنید که متغیر صحیح شما مثلاً بدون علامت باشد و شما عدد 38 را به عنوان مقدار و موجودی متغیر متناظر آن تعیین کنید ، از سقف مُجازی که خودتان تعیین کرده اید گذشته‌اید که برخی کامپایلرها بزرگ ترین عدد را که متناسب متغیر شما باشد در نظر گرفته و ذخیره می نمایند که در اینجا می شود 19 ( شما عدد 38 را وارد کرده‌اید که می‌شود 100110 که ۶ بیت دارد که از ۵ بیت تعیین شده بیشتر است بنابراین بیت آخر آن یعنی 0 آخر نادیده گرفته می‌شود که می‌شود 10011 که در مبنای دهدهی می‌شود 19 ) اما کامپایلرهای استاندارد در صورت دادن مقداری بزرگ‌تر از آنچه که بیت‌فیلد تعیین شده توسط شما می‌تواند ذخیره کند ، از شما خطا خواهند گرفت

شما نمی‌توانید بیت‌فیلدها را داخل ساختمان ، مقدار دهی کنید . بلکه فقط از طریق نمونه یا نمونه‌های ساختمانتان می‌توانید بیرون از ساختمان به آنها مقدار بدهید و بعداً مقدار آنها را تغییر دهید

نکته دیگری در مورد ذخیره شدن بیت‌فیلدها وجود دارد که جهت درک بهتر برنامه‌نویسی و مفهوم بیت‌فیلد بهتر است آن را بدانید . اگر در ساختمان شما دو یا چند بیت‌فیلد پشت سر هم تعریف شوند ( یعنی دو یا چند متغیر خود را پشت سر هم به صورت بیت فیلد نوشته‌اید ) آنگاه کامپایلر در صورتی که بیت فیلدها کوچک‌تر از یک بایت باشند ، آنها را پشت سر هم در داخل خانه‌های مشترک ذخیره می‌نماید . مثلاً اگر شما یک بیت فیلد تعریف نموده‌اید که 2 بیت است ، سپس یک بیت فیلد درست بعد از آن تعریف می‌کنید که 3 بیت است ، جون از 8 بیت تجاوز نکرده و امکان ذخیره هر دوی متغیرها در داخل یک خانه حافظه وجود دارد ، پس آنها را پشت سر هم و در داخل یک خانه می‌نویسد . اما گاهی پیش می آید که شما یک بیت‌فیلد 7 بیتی تعریف کرده و یک بیت‌فیلد دیگر را پشت سر آن 6 بیتی تعریف می‌نمائید ، اینکه این دو بیت‌فیلد ، پشت سر هم ذخیره شوند و یا در خانه های مجزا ، توسط استاندارد C تعریف نشده است و هر کامپایلر روال کار خاص خودش را دارد که برای دانستن آن باید به راهنمای کامپایلر خود مراجعه نمائید

ذکر سه نکته برای تسلط بیشتر شما در زبان C مفید واقع خواهد گردید . اول اینکه برخی کامپایلرها به شما اجازه تعیین بیت‌فیلد برای نوع صحیح int و یا احیاناً انواع دیگر را بالاتر از سقف ظرفیت استاندارد آن نوع می‌دهند . مثلاً اگر استاندارد ظرفیت int دو بایت یعنی ۱۶ بیت است ، اجازه تعیین سقفی بالاتر از آن را در بیت‌فیلد برای شما لحاظ می‌نمایند . نکته دیگر اینکه شما می توانید بیت‌فیلدهایی بدون نام ایجاد نمائید که به آنها بیت‌فیلد های بدون نام می گویند ( نوع متغیر را می‌نویسید ولی شناسه و نامی برای آن تعیین نمی‌کنید ) شما نمی توانید آنها را فراخوانی کرده و بنابراین در ادامه برنامه خود مقدار آن را تغییر دهید ، پس می‌توانیم بگوئیم شما نمی‌توانید از آنها استفاده نمائید اما کاربرد این بیت‌فیلدها ایجاد فاصله بین خانه‌های حافظه موقت یا همان آفست‌های شیارهای Segments حافظه موقت RAM می‌باشد . با این کار شما می توانید چیدمان مورد نظر خود را به ساختمان تعریف شده خود تحمیل نمائید و هر متغیر معمولی یا بیت فیلد را در هر خانه‌ای که می‌خواهید ، قرار دهید و ساختمان خود را آنگونه که تمایل دارید مرتب نمائید . نکته آخر اینکه در صورتی که بیت‌فیلدی با ظرفیت 0 ایجاد نمائید ( یعنی بعد از علامت کالن « : » عدد 0 را بنویسید ) آنگاه مطابق با تعریف سی ، کامپایلر تفهیم می‌شود که باید بیت‌فیلد بعدی را در آفستِ سگمنت یا همان خانه حافظه بعدی بنویسد و حتی اگر ظرفیت آفست آخر ( قبل از بیت فیلد 0 ) تکمیل نشده و همچنان فضا برای نوشتن داده‌ها وجود دارد ، کامپایلر دیگر داده‌ها را در ادامه آن نخواهد نوشت و به سراغ خانه بعدی می‌رود ( برای این کار نوع متغیر را هم تعیین نمی‌کنید و فقط می‌نویسید 0 : )

اجتماع

ویرایش

تمامی مباحث بیان شده در مورد ساختمان‌ها در مورد اجتماع‌ها نیز صدق می کند ، اعم از نحوه نوشتن برای ایجاد آن تا ایجاد نمونه از روی ساختمان و استفاده از نمونه‌ها برای دسترسی به اعضای آن که در مورد اجتماع نیز همین گونه می‌باشد ؛ جز اینکه برای ایجاد اجتماع باید از کلیدواژه unuion استفاده نمائید و از طرفی در ساختمان ، اعضا به ترتیبی که نوشته می شوند ، هر عضو جدیدتر در آفستی با شماره بالاتر ذخیره می گردد و هر چه تعداد اعضا بیشتر باشد ، ساختمان فضای بیشتری را اشغال خواهد نمود و به عبارت ساده فضای اشعال شده توسط ساختمان به اندازه مجموع فضای اشعال شده توسط اعضای آن ساختمان می باشد ؛ این در حالیست که فضای اشغال شده توسط یک اجتماع به اندازه فضای اشغال شده توسط بزرگ ترین عضو آن است . یعنی اگر شما در داخل یک اجتماع سه عضو ایجاد نمائید مثلاً یک char یک double و یک int ظرفیت آن اجتماع 8 بایت است ( یعنی به اندازه double ) و شما می‌توانید در داخل کاراکتر یا نوع صحیح خود ( int ) نیز به اندازه 8 بایت مقدار و موجودی تعیین نمائید . از سویی دیگر در ساختمان هر عضو مقدار اختصاصی خود را دارد ، اما در اجتماع ، آخرین مقدار و موجودی که به هر کدام از اعضا بدهید ، بقیه اعضا نیز همان مقدار و موجودی را خواهند داشت

مثال :

union afra
{
int a;
char H;
double dec;
float f; 
} Uni;

در کد بالا مقدار اشغال شده توسط هر یک از چهار عضو که متغیر هستند 8 بایت است و اگر ما به متغیر صحیح a مقدار 52 بدهیم این مقدار در تمامی اعضا نوشته می شود و اگر پس از آن به متغیر شناور ( اعشاری ) f مقدار 10.05 را بدهیم این مقدار در تمامی اعضا نوشته می شود ( آخرین مقدار برای هر عضو ، مقدار حال حاضر برای تمام اعضا می‌باشد )

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

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

مثال :

union {
struct {
int rectype;
int v1, v2, v3;
char text;
} val1;
struct {
int rectype;
short int flags : 8;
enum {red,blue,green} hue;
} val2;
} record;

در کد بالا می توانیم بنویسیم :

record.val1.rectype = 33;

و در ادامه می توانیم همین مقدار را در rectype ساختمان دوم بیابیم و عمل خود را بر روی آن انجام دهیم ، مثلاً :

DoSomething(record.val2.rectype);

دقت کنید که علاوه بر این شکل ما می‌توانیم از ساختمان‌هایی ( با عضو اول هم‌نام ) که خارج از union ما ایجاد شده‌اند نیز داخل union خود ، نمونه ایجاد کنیم و سپس همین ترفند را به کار بگیریم ( یعنی ساختمان در جای دیگری تعریف شده است اما نمونه آن داخل اجتماع ایجاد شده و با ساختمان دیگری که داخل اجتماع است ، عضو اول همنام دارند )