سی (زبان برنامه‌نویسی)

پیشینه

در سال ۱۹۶۷ مارتین ریچاردز زبان BCPL را برای نوشتن نرم‌افزارهای سیستم‌عامل و کامپایلر در دانشگاه کمبریج ابداع کرد. سپس در سال ۱۹۷۰ کن تامسون زبان B را بر مبنای ویژگی‌های زبان BCPL نوشت و از آن برای ایجاد اولین نسخه‌های سیستم‌عامل یونیکس در آزمایشگاه‌های بل استفاده کرد. زبان C در سال ۱۹۷۲ توسط دنیس ریچی از روی زبان B و BCPL در آزمایشگاه بل ساخته شد و ویژگی‌های جدیدی همچون نظارت بر نوع داده‌ها نیز به آن اضافه شد. ریچی از این زبان برای ایجاد سیستم‌عامل یونیکس استفاده کرد اما بعدها اکثر سیستم‌عامل‌های دیگر نیز با همین زبان نوشته شدند. این زبان با سرعت بسیاری گسترش یافت و چاپ کتاب "The C Programming Language" در سال ۱۹۷۸ توسط برایان کرنیگان و ریچی باعث رشد روزافزون این زبان در جهان شد.
متأسفانه استفاده گسترده این زبان در انواع کامپیوترها و سخت‌افزارهای مختلف باعث شد که نسخه‌های مختلفی از این زبان به وجود آید که با یکدیگر ناسازگار بودند. در سال ۱۹۸۳ مؤسسه استانداردهای ملی آمریکا (ANSI) کمیته‌ای موسوم به X3J11 را را مأمور کرد تا یک تعریف فاقد ابهام و مستقل از ماشین را از این زبان تدوین نماید. در سال ۱۹۸۹ این استاندارد تحت عنوان ANSI C به تصویب رسید و سپس در سال ۱۹۹۰، سازمان بین‌المللی استانداردسازی (ISO) نیز این استاندارد را پذیرفت و مستندات مشترک آن‌ها تحت عنوان ANSI/ISO C منتشر گردید.
در سال‌های بعد و با ظهور روش‌های برنامه‌نویسی شئ‌گرا نسخه جدیدی از زبان C بنام ++C توسط بی‌یارنه استراس‌تروپ در اوایل ۱۹۸۰ در آزمایشگاه‌های بل توسعه یافت. در ++C علاوه بر امکانات جدید، خاصیت شئ‌گرایی نیز به C اضافه شده‌است.
با گسترش شبکه و اینترنت، نیاز به زبانی احساس شد که برنامه‌های آن بتوانند بر روی هر ماشین و هر سیستم‌عامل دلخواهی اجرا گردد. شرکت سان مایکروسیستمز در سال ۱۹۹۵ میلادی زبان جاوا را برمبنای C و ++C ایجاد کرد که هم‌اکنون از آن در سطح وسیعی استفاده می‌شود و برنامه‌های نوشته شده به آن بر روی هر کامپیوتری که از جاوا پشتیبانی کند (تقریباً تمام سیستم‌های شناخته شده) قابل اجرا می‌باشد.

برنامه‌نویسی ساختار یافته

در دهه ۱۹۶۰ میلادی توسعه نرم‌افزار دچار مشکلات عدیده‌ای شد. در آن زمان سبک خاصی برای برنامه‌نویسی وجود نداشت و برنامه‌ها بدون هیچگونه ساختار خاصی نوشته می‌شدند. وجود دستور پرش (goto) نیز مشکلات بسیاری را برای فهم و درک برنامه توسط افراد دیگر ایجاد می‌کرد، چرا که جریان اجرای برنامه مرتباً دچار تغییر جهت شده و دنبال کردن آن دشوار می‌گردید؛ لذا نوشتن برنامه‌ها عملی بسیار زمان بر و پرهزینه شده بود و معمولاً اشکال زدایی، اعمال تغییرات و گسترش برنامه‌ها بسیار مشکل بود. فعالیت‌های پژوهشی در این دهه باعث به وجود آمدن سبک جدیدی از برنامه‌نویسی بنام برنامه‌نویسی ساخت‌یافته گردید؛ روش منظمی که باعث ایجاد برنامه‌هایی کاملاً واضح و خوانا گردید که اشکال‌زدایی و خطایابی آن‌ها نیز بسیار ساده‌تر بود.
اصلی‌ترین نکته در این روش عدم استفاده از دستور پرش (goto) است. تحقیقات بوهم و ژاکوپینی نشان داد که می‌توان هر برنامه‌ای را بدون دستور پرش و فقط با استفاده از ۳ ساختار کنترلی ترتیب، انتخاب و تکرار نوشت.
ساختار ترتیب، همان اجرای دستورات به صورت متوالی (یکی پس از دیگری) است که کلیه زبان‌های برنامه‌نویسی در حالت عادی بهمان صورت عمل می‌کنند.
ساختار انتخاب به برنامه‌نویس اجازه می‌دهد که براساس درستی یا نادرستی یک شرط، تصمیم بگیرد کدام مجموعه از دستورات اجرا شود.
ساختار تکرار نیز به برنامه نویسان اجازه می‌دهد مجموعه خاصی از دستورات را تا زمانی‌که شرط خاصی برقرار باشد، تکرار نماید.
هر برنامه ساخت‌یافته از تعدادی بلوک تشکیل می‌شود که این بلوک‌ها به ترتیب اجرا می‌شوند تا برنامه خاتمه یابد (ساختار ترتیب). هر بلوک می‌تواند یک دستور ساده مانند خواندن، نوشتن یا تخصیص مقدار به یک متغیر باشد یا اینکه شامل دستورهایی باشد که یکی از ۳ ساختار فوق را پیاده‌سازی کنند. نکته مهم اینجاست که در مورد دستورات داخل هر بلوک نیز همین قوانین برقرار است و این دستورات می‌توانند از تعدادی بلوک به شرح فوق ایجاد شوند و تشکیل ساختارهایی مانند حلقه‌های تودرتو را دهند.
نکته مهم اینجاست که طبق قوانین فوق یک حلقه تکرار یا به‌طور کامل داخل حلقه تکرار دیگر است یا به‌طور کامل خارج آن قرار می‌گیرد و هیچگاه حلقه‌های روی هم افتاده نخواهیم داشت.
از جمله اولین تلاش‌ها در زمینه ساخت زبان‌های برنامه‌نویسی ساخت‌یافته، زبان پاسکال بود که توسط پروفسور نیکلاوس ویرت در سال ۱۹۷۱ برای آموزش برنامه‌نویسی ساخت یافته در محیط‌های آموزشی ساخته شد و بسرعت در دانشگاه‌ها رواج یافت اما به دلیل نداشتن بسیاری از ویژگی‌های مورد نیاز مراکز صنعتی و تجاری در بیرون دانشگاه‌ها موفقیتی نیافت.
کمی‌بعد زبان C ارائه گردید که علاوه بر دارا بودن ویژگی‌های برنامه‌نویسی ساخت یافته به دلیل سرعت و کارایی بالا مقبولیتی همه گیر یافت و هم‌اکنون سال‌هاست که به‌عنوان بزرگ‌ترین زبان برنامه‌نویسی دنیا شناخته شده‌است.

مراحل اجرای یک برنامه C

برنامه‌های نوشته شده به زبان C در فایل متنی ساده ای با پسوند c. و به نام کد منبع (source code) ذخیره می‌شوند. فایل کد منبع برای اجرا باید به زبان ماشین ترجمه شود که به اینکار ترجمه (compile) و به برنامه‌ای که عمل ترجمه به زبان ماشین یا زبان دیگر را انجام می‌دهد مترجم (compiler) می‌گویند. کامپایلرهای متعددی برای زبان C و برای سیستم‌عامل‌های مختلف نوشته شده‌است، مانند Clangو gcc.
معمولاً برنامه نویسان C در برنامه‌های خود از یک سری از کدهای از پیش آماده شده برای انجام عملیات متداول (مانند محاسبه جذر یا سینوس) استفاده می‌کنند که برنامه آن‌ها قبلاً نوشته و ترجمه شده‌است. این برنامه‌ها یا در قالب کتابخانه‌های استاندارد توسط شرکت‌های ارائه‌کننده نرم‌افزار یا توسط دیگر برنامه‌نویسان نوشته و در اختیار شخص یا بر روی اینترنت قرار داده شده‌است که در چنین وضعیتی باید کد منبع با کدهای مربوط به برنامه‌های کمکی متصل (Link) شود.
برای اینکار نیاز به یک پیوند دهنده (Linker) داریم و نتیجه این عمل یک فایل اجرایی خواهد بود. مرحله بعدی اجرای برنامه و دادن ورودی‌های لازم به آن و به دست آوردن خروجی‌ها می‌باشد. در شکل زیر این مراحل نشان داده شده‌اند.
انجام مراحل بالا برای اجرای هر برنامه زمان‌بر می‌باشد، به همین دلیل اکثر توسعه‌دهندگان کامپایلرها، محیط‌هایی را برای برنامه‌نویسی ارائه کرده‌اند که کلیه مراحل بالا را به‌طور خودکار انجام می‌دهند.
به این محیط‌ها Integrated Development Environment (سرواژه:IDE) (محیط توسعه مجتمع) گفته می‌شود. این محیط‌ها دارای یک ویرایشگر متن می‌باشند که معمولاً دارای ویژگی‌هایی همچون استفاده از رنگ‌های مختلف برای نشان دادن اجزای مختلف برنامه مانند کلمات کلیدی و قابلیت تکمیل اتوماتیک قسمت‌های مختلف برنامه می‌باشد. پس از نوشتن برنامه و Run کردن آن، کلیه عملیات فوق به‌طور اتوماتیک انجام شده و برنامه اجرا می‌گردد. علاوه براین، این محیط‌ها معمولاً دارای امکانات اشکال‌زدایی برنامه (Debug) نیز می‌باشند که شامل مواردی همچون اجرای خط به خط برنامه یا دیدن محتویات متغیرها در زمان اجرا است.

دستورهای خروجی

برای چاپ یک متن ساده بر روی خروجی از دستور زیر استفاده می‌گردد:
printf(" Hello world! ")

کلمات کلیدی

auto
, double
, int
, struct
, break
, else
, long , switch , case , enum , register , typedef , char , extern , return , union , const , float , short , unsigned , continue , for , signed , void , default , goto , sizeof , volatile , do , if , static , while, restrict

تابع

برنامه‌ای است که برای حل تکه‌های کوچک مسئله نوشته می‌شود.
ابتدا مسألهٔ بزرگ به چند زیر مسئله از نظر منطقی تقسیم می‌شود و برای هر کدام یک تابع نوشته می‌شود.
با استفاده از توابع می‌توان برنامه‌های ساخت یافته نوشت که وظایف این نوع برنامه‌ها توسط بخش‌های مستقلی که تشکیل دهنده برنامه‌اند انجام می‌شود. این بخش‌های مستقل همان توابع هستند.

توابع کتابخانه‌ای

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

نوشتن تابع

برای نوشتن تابع باید اهداف مشخص باشد.
هر تابع سه بخش دارد: فراخوانی، الگوی تابع، تعریف تابع
تعریف تابع مجموعه‌ای از دستورهاست که عملکرد تابع را مشخص می‌کند.
فراخوانی تابع دستوری است که تابع را فراخوانی می‌کند که با نام تابع انجام می‌شود.
<نوع تابع>یکی از چیزهایی است که توسط کاربر تعریف می‌شود.

متغیرهای محلی (local)

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

متغیرهای سراسری (global)

این متغیرها را می‌توان خارج از توابع مثلاً ابتدای برنامه قبل از تعریف تابع Main یا قبل از تعریف هر تابع دیگر معرفی کرد. متغیرهایی که خارج از توابع معرفی شوند، توسط کلیه توابعی که از آن به بعد تعریف شده‌اند، قابل استفاده‌اند.
پس از هر فراخوانی همین که اجرای تابع خاتمه یافت، متغیرهای محلی آن از بین می‌روند؛ ولی متغیرهای سراسری تا پایان اجرای تابع mainفعال و قابل استفاده‌اند.

حوزه دسترسی به متغیر

محدوده‌ای که هر متغیر در آن قابل دسترسی است حوزه دسترسی نامیده می‌شود. متغیرهایی که به صورت محلی در ابتدای تابع معرفی شده‌اند دارای حوزه دسترسی تابع هستند. متغیرهایی که در یک بلوک به‌طور محلی تعریف شده‌اند دارای حوزه دسترسی بلوک هستند. متغیرهایی که خارج از توابع معرفی شده‌اند دارای حوزه دسترسی فایل می‌باشند.
کلاس‌های حافظه دارای چهار نوع اتوماتیک، ثبات، خارجی، ایستا هستند که به ترتیب توسط کلمات auto, register, extern و static مشخص می‌شوند.

کلاس حافظه اتوماتیک

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

کلاس حافظه ثبات

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

کلاس حافظه خارجی

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

کلاس حافظه ایستا

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

استفاده از آرایه‌ها در زبان C

بسیاری از موارد پیش می‌آید که برنامه‌نویس نیاز به استفاده از تعداد زیادی متغیر پیدا می‌کند. مثلاً اگر بخواهیم جملات ۱ تا ۱۰۰ سری فیبوناچی را برای استفاده ذخیره کنیم، به صد متغیر نیاز داریم. تعریف این صد متغیر به صورت مستقل و با نام‌های جداگانه کاری سخت و طاقت فرساست و البته معقولانه نیست. به همین خاطر در زبان C قابلیتی پیش‌بینی شده تا بتواند تعداد دلخواهی متغیر از یک نوع را به راحتی ایجاد کرد.
به این متغیرها که در حافظه پشت سر هم قرار می‌گیرند و همگی از یک نوع هستند آرایه (Array) می‌گویند. آرایه‌ها کاربردهای بسیار زیادی دارند و همانند دنباله‌ها در ریاضی عمل می‌کنند. همان‌طور که در ریاضی برای مشخص کردن یک جمله از دنباله از اندیس استفاده می‌کنیم، در C نیز برای مشخص کردن یک متغیر خاص از اندیس استفاده می‌شود.
همان‌طور که مشاهده می‌کنید خانهٔ پنجم شمارهٔ ۴ دارد و این بدین خاطر است که خانه‌ها از شمارهٔ ۰ تا n-1 (که n تعداد خانه هاست) شماره‌گذاری می‌شود. پس باید به این مورد دقت کرد. مورد دیگر این که شمارهٔ خانه در [] قرار می‌گیرد. درون علامت آکلاد می‌توان عبارت نیز قرار داد.
همان‌طور که گفته شد در حافظهٔ رایانه خانه‌های آرایه پشت سر هم قرار می‌گیرند. اندازهٔ هر خانه به اندازهٔ نوع تعریف شده‌است. مثلاً در مثال قبل اگر int دو بایت اشغال کند آرایهٔ array، در کل ۲۰ بایت از حافظه اشغال خواهد کرد. این که خانه‌ها پشت سر هم قرار می‌گیرند ویژگی کارایی است که در بحث اشاره گرها به کار می‌آید.

اشاره گرها در C

یکی از مهم‌ترین قابلیت‌های زبان C، امکان استفاده از اشاره گر هااست. اشاره گرها کارایی، قدرت و انعطاف‌پذیری برنامه را بیشتر می‌نمایند. علاوه بر فراهم آوردن امکان نوشتن کدهای برنامهٔ کوتاهتر، غنی تر و کاراتر، گاهی مواردی پیش می‌آید که انجام محاسبات مورد نیاز برنامه تنها توسط اشاره گرها امکان‌پذیر می‌باشد. به همین دلیل، از این توانایی در برنامه‌های C زیاد استفاده می‌گردد. اگر چه در ظاهر کار با اشاره گرها مشکل است و درک برنامه‌ای که از آن‌ها استفاده شده‌است ساده نیست، اما در حقیقت اگر از آن‌ها به درستی استفاده گردد، وضوح و سادگی برنامه افزایش می‌یابد.

اشاره گر چیست؟

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

انواع متغیرها و داده‌ها در C

زبان C قابلیت‌های زیادی برای کار با داده‌ها دارد. تمام انواع عددی و غیر عددی به صورت کارا در سی یافت می‌شوند. در زیر به اختصار به بعضی اشاره می‌شود:
int
این نوع داده از نوع عددی صحیح (integer) است. در حافظه ۲ بایت اشغال می‌کند ولی برخی کامپایلرها برای آن ۴ بایت در نظر می‌گیرند.
float
این نوع داده از نوع اعشاری است. معمولاً در حافظه ۴ بایت اشغال می‌کند و اعداد اعشاری مثبت و منفی را در خود جای می‌دهد.
double
این نوع داده برای استفاده در اعداد اعشاری است که مقدار بیشتری فضا باید اشغال کنند. این نوع داده نیز ۸ بایت اشغال می‌کند.
char
این نوع داده برای ذخیرهٔ کاراکتر استفاده می‌شود و ۱ بایت اشغال می‌کند.

long int

برای داده‌های صحیحی که در حافظه فضای بیشتری اشغال می‌کنند و ۴ بایت اشغال می‌کند.

short int

برای داده‌های صحیح که نیاز به حافظهٔ زیادی ندارند قابل استفاده است.
unsigned int
این نوع داده از نوع عددی صحیح بدون علامت است.

ساختارها در C

نوع داده‌هایی که تا به حال به آن‌ها برخورد داشته‌ایم مانند int,float,char،... در زبان C به صورت استاندارد تعریف شده‌اند و برنامه‌نویس مستقیماً از آن‌ها برای تعیین نوع استفاده می‌نماید. ساختارها نوع دیگری از انواع داده‌ها می‌باشند با این تفاوت که توسط برنامه‌نویس تعریف می‌شوند.

ساختار چیست؟

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

خود فراخوانی

به فرایندی گفته می‌شود که طی آن از درون یک تابع همان تابع فراخوانی گردد.