در این مجموعه قصد دارم اصول برنامه نویسی زبان C را در سیستم عامل لینوکس برای شما عزیزان توضیح دهم.اگر فبلا در ویندوز برنامه نویسی کرده باشید میدانید که ما به یک Compiler برای اجرا کردن برنامه های خود نیاز داریم. در ویندوز اغلب از Visual Studio برای زبان های متفاوت استفاده میکنیم.
اما این مقاله جامع هم تنها قادر به آموزش اصول و پایه زبان برنامه نویسی C می باشد و شما برای یادگیری این زبان باید کتاب بخواهید یا در دوره های آموزشی شرکت کنید. اما پیشنهاد می کنم که عجله نکنید ، هر چند یادگیری C سخت است اما پیشنهاد می کنیم کمی هم در مورد جایگزین جدیدتر و با قابلیت تری به نام C++ هم فکر کنید و اگر این آموزش برای یادگیری برنامه نویسی به زبان C کافی نبود پیشنهاد می کنیم به سمت زبان C++ در آموزش کامل و جامع زیر بروید :
در لینوکس برای اجرا کردن دستورات C به یک کامپایلر به نام GCC نیاز داریم. اگر در سیستم خود این package را نصب ندارید، مطابق روش زیر ابتدا به نصب آن اقدام کنید تا وارد قسمت آموزش C بشویم.میتوان هم بصورت دستورات ترمینال و هم بصورت گرافیکی اقدام به نصب انواع بسته های نرم افزاری در لینوکس کرد.
من خودم کار کردن با ترمینال لینوکس را ترجیح میدهم، چون اگر شما روی ترمینال لینوکس تسلط داشته باشید قطعا یک امتیاز بزرگ محسوب میشود، اگرچه همانند ویندوز میتوان بسیاری ار کارها را در لینوکس بصورت گرافیکی انجام داد ولی تسلط رو ترمینال بهتر است، اگر با لینوکس آشنا باشید میفهمید چی میگم !!! برای نصب، در ترمینال دستور yum -y install gcc را وارد کنید. اگر کاربر ارشد (root) نیستید، قبل دستور sudo بگذارید.
برای من چون از قبل نصب هست، دوباره نصب نمیکند.اگر میخواهید بفهمید که چه بسته هایی مرتبط با GCC نصب شده از دستور rpm -qa | grep gcc استفاده کنید. اگر هم میخواهید بدانید مسیر اجرای دستور کجاست از دستور which gcc و اگر میخواهید بفهمید کتابخانه دستور از نصب چه بسته ای ایجاد شده از دستور rpm -qf //usr//bin//gcc استفاده کنید.
برای نوشتن و ویرایش کردن کد ها از یک editor ساده به نام vim استفاده میکنیم. انتخاب text editor وابسته به سلیقه و نیاز شماست. تا اونجایی که من میدونم در بعضی از نسخه های لینوکس، vim بصورت پیش فرض نصب نیست و باید خودتان آن را نصب کنید.
برای شروع یک فایل متنی خالی را باز میکنم. برای این کار در یک مسیر پیش فرض از دستور vim 1.c استفاده میکنیم. با این دستور در مسیری که در حال حاضر در آن هستید، این فایل خالی را ایجاد میکند. برای مشاهده مسیر از دستور pwd استفاده کنید. من روی desktop یک پوشه با نام C درست کرده ام و تمام سورس کد ها را درون آن ایجاد میکنم.
شاید کار کردن با vim کمی برایتان مشکل باشد، میتوانید از gedit به جای آن استفاده کنید. برای استفاده از آن دستور gedit را نوشته و نام فایل مورد نظر را در مقابل آن بنویسید. اگر وجود نداشته باشد، آنرا ایجاد میکند. برنامه خود را در آن نوشته و دکمه save را فشار دهید و خارج شوید.
توجه داشته باشید کهgedit ترمینال را اشغال میکند و مادامی که gedit باز است نمیتوانید دستوری در آن وارد کنید. gedit هم مثل vim در همان مسیری که هستید فایل را ایجاد میکند.همچنین میتوانید پس از درست کردن یک پوشه (راست کلیک و create folder) در مسیر دلخواه برای نگهداری سورس کد ها، درون آن راست کلیک کرده و یک empty file درست کنید. سپس روی آن کلیک کنید تا باز شود.
پس از وارد کردن دستور vim 1.c وارد محیط ویرایشی آن میشویم. با فشردن دکمه i وارد insert mode شده و میتوان درون فایل متنی تایپ کرد. گوشه پایین سمت چپ، mode های vim را میتوان دید. برای شروع دستورات زیر را عینا وارد کنید. خط اول که include کردن کتابخانه اصلی C است. اسکلت برنامه تابع main است. در اینجا فقط یک دستور ساده را بررسی میکنیم.
تابع printf برای چاپ کردن ساده متن و یا محتویات متغیر ها استفاده میشود. مقدار بین " " عینا در خروجی چاپ میشود. از n/ هم برای رفتن به سر خط استفاده میکنیم. در نهایت هم return 0 به معنی اینکه تابع خروجی برنمیگرداند. فراموش نکنید که هر پرانتزی را که باز کردید، حتما در آخر ببندید.
برای save کردن و خارج شدن از محیط ویرایشگر، یکبار دکمه خروج را فشار داده، بعد از آن دکمه شیفت و "ک فارسی" را با هم فشار داده، سپس عبارت wq را وارد کنید. (توضیحات بعضی از دستورات مربوط به اسکریپت نویسی shell است که با عرض پوزش از توضیح آن صرف نظر میکنیم.) پس از آن دوباره به محیط ترمینال باز خواهید گشت.
برای کامپایل کردن آن از دستور gcc -o 1 1.c استفاده میکنیم. الگوی این دستور به این صورت است که ابتدا نام فایل خروجی و بعد از آن نام سورس فایل را میدهیم. توجه به 2 نکته بسیار ضروری است. اول اینکه چون من در همین مسیری که هستم، فایل ها را ایجاد میکنم، فقط کافیست نام آنها را در ترمینال بنویسیم و اگر در مسیری غیر از مسیری که در آن هستیم (خروجی دستور pwd) باشیم، باید آدرس فایل را قبل از اسم فایل بطور کامل ذکر کنیم.
دوم اینکه بر خلاف ویندور، اسم ها نقشی در پسوند و فرمت فایل ها ندارند، یعنی این پسوند c. که من گذاشتم جزء اسم فایل محسوب میشود و فرمت آن نیست. در لینوکس، فرمت یک فایل، چه متن چه عکس چه فیلم و ... در متا دیتای آن دخیره میشود نه در اسم آن. فقط به دلیل اینکه من کاربر متوجه شوم درون این فایل متنی، کد C ذخیره شده، آخر آن یک c. میگذارم. یعنی اگر من آخرش بعد از نقطه هر چیزی بنویسم اصلا مهم نیست. این دو نکته را همیشه به یاد داشته باشید.
دستور gcc خروجی خاصی ندارد ولی با دستور ?$ echo میتوان صحت اجرای آن را بررسی کرد. خروجی این باید صفر باشد. اگر صفر نباشد دستور قبلی یعنی gcc با مشکلی همراه است. دستور gcc یک فایل با نام 1 برای من ایجاد کرد که میتوان بصورت گرافیکی آن را دید و هم میتوان با دستور ls آن را در پوشه ایجاد شده مشاهده کرد. این فایل نتیجه کامپایل است.حال برای اجرای برنامه در ترمینال مینویسیم 1/. و نتیجه در خروجی چاپ میشود.(من میتونستم آخر خط دوم new line کنم که ظاهر بهتری داشته باشد.)
در ادامه مباحث جلسه گذشته این نکته رو اضافه میکنم که هر بار سورس کدتون رو عوض کنید، باید دوباره از دستور gcc استفاده کنید تا کد ها کامپایل شوند. برای edit کردن سورس کد ها میتوانید از ابزار gedit هم استفاده کنید که شبیه notepad در ویندوز است. در این جلسه درمورد data types صحبت خواهیم کرد.
اولین data type که باید یاد بگیریم، int است. این متغیر، اعداد صحیح را ذخیره میکند و 4 بایت فضا اشغال میکند. برای اعداد ممیز دار از متغیر float و برای ذخیره یک کاراکتر از char استفاده میکنیم. که به ترتیب 4 و 1 بایتی هستند. با استفاده از تابع printf میتوان به راحتی محتویات آنها را چاپ کرد.
برای تعریف این متغیر ها، ابتدا نوع متغیر و سپس نام آنرا ذکر میکنیم. به مثال های زیر توجه کنید. برای چاپ int از d% ، برای float از f% و برای char از c% مطابق الگو های زیر استفاده میکنیم. در مسیر دلخواه دستور vim 2.c یا gedit 2.c را در ترمینال وارد کرده تا یک فایل متنی خالی به همین نام ایجاد شود.
(یا بصورت گرافیکی یک فایل خالی درست کنید.).اگر با vim درست کردید، درون آن محتویات زیر را با رفتن به insert mode با استفاده از کلید i وارد کنید. در آخر هم آن را با فشردن دکمه خروج و سپس شیفت و : و وارد کردن wq ذخیره کنید.
با استفاده از دسنور gcc کد را کامپایل کرده و مطابق شکل از کد خروجی بگیرید.
برای مقادیر بزرگ تر میتوان از double استفاده کرد که 8 بایتی است.
چند مثال ساده
در این قسمت به ادامه مبحث Data Types در زبان C میپردازیم. نوع دیگر Data Type ها string است. string (رشته) آرایه ای از کاراکتر ها است. برای بوجود آوردن string، یک آرایه بطول string خود بوجود می آوریم. یک فایل جدید با نام دلخواه با gedit یا vim باز کنید. درون آن محتویات زیر را وارد کنید.
برای تعریف رشته، ابتدا از کلمه کلیدی char استفاده کرده، سپس نام رشته و درون [] اندازه آن را مشخص میکنیم. بعد از آن یک مساوی گذاشته و درون " " عبارت خود را مینویسیم. در C انتهای رشته ها یک فضای Null وجود دارد، پس اندازه آرایه را یکی اضافه تر بگیرید. از دستور printf و برای چاپ کردن رشته از s% استفاده کنید. برای چاپ کردن یک حرف خاص از رشته از c% و شماره آن در رشته استفاده کنید. به یاد داشته باشید شماره گذاری آرایه ها از صفر شروع میشود نه یک.
هماطور که در خط آخر خروجی مشخص است خانه آخر رشته Null است. در قسمت بعد درمورد اشاره گر ها صحبت خواهیم کرد
در این قسمت درمورد اشاره گر ها صحبت خواهیم کرد. به شکل زیر دقت کنید (با paint کشیدم!!! ). فرض کنید خانه های حافظه از شماره 0x000000 تا 0xFFFFFF نامگذاری شده اند. یک متغیر n با مقدار 25 تعریف میکنیم (خط 4). در جایی از حافظه، این متغیر ذخیره میشود.
مثلا در خانه شماره 0x000080 ذخیره میشود. پس محتوای این خانه 25 میشود. حال یک متغیر اشاره گر به نام k تعریف میکنیم و میگوییم که به آدرس خانه ای که n در آن دخیره شده، اشاره کن (خط 5). پس محتوای خانه شماره 0x000100 که به k اختصاص دادیم، به آدرس n اشاره میکند. حالا ابتدا خود متغیر n را چاپ میکنیم (خط 6). سپس آدرس جایی که k اشاره میکند را چاپ میکنیم که آدرس خانه n است (خط 7). و در نهایت محتویات آدرسی که k به آن اشاره میکند که عدد 25 است (خط 8).
در این بخش، درمورد آرایه ها صحبت میکنیم. آرایه ها دسته ای از data type های یکسان به هم چسبیده هستند. وقتی یک آرایه n تایی تعریف میکنیم، تعداد n خانه حافظه پشت سر هم به این آرایه اختصاص داده میشود. به یاد داشته باشید که اگر مثلا یک آرایه 4 تایی تعریف کردید، خانه های آرایه از 0 شروع شده و تا 3 ادامه پیدا میکنند.
یک فایل خالی با vim یا gedit باز کنید و کد های زیر را در آن بنویسید. همانطور که در شکل مشخص است، میتوانید ابتدا آرایه و اندازه آن را تعریف کرده و بعدا به آن مقدار دهید، و یا میتوانید تعریف و مقدار دهی به آرایه را همه در یک خط انجام دهید. هنگام استفاده از تابع printf برای چاپ آرایه، به جنس آرایه دقت کنید.
در این بخش درمورد جمله شرطی IF صحبت خواهیم کرد. تابع if بر اساس یک ورودی تصمیم میگیرد. اگر شرطی که در مقابل آن نوشته شده صحیح باشد، عبارت داخل پرانتز اجرا میشود و برنامه ادامه پیدا میکند. اگر شرط صحیح نباشد جملات داخل پرانتز اجرا نمیشوند و برنامه ادامه پیدا میکند.
اگر برای عدم برقراری شرطی که برای if تعیین کردیم، حالت خاصی داریم، آنرا در else مینویسیم. یعنی اگر شرط برقرار نباشد، else اجرا میشود. میتوان if های تو در تو هم تعریف کرد. میتوان از else if هم برای بررسی یک شرط، در عدم برقراری شرط قبلی استفاده کرد. به مثال های زیر توجه کنید. یک فایل خالی با gedit یا vim باز کرده و عبارات زیر را وارد کنید.
برای بررسی کوچکتر و بزرگتر بودن از < و > ، برای کوچکتر مساوی و بزرگتر مساوی از =< و => ، برای تساوی از == و برای عدم تساوی از =! استفاده کنید.
در این قسمت درمورد حلقه for صحبت خواهیم کرد. شرط یک حلقه for از سه بخش تشکیل میشود و در هر بار تکرار حلقه، کد های درون {} اجرا میشوند. هر بخش شرط با ; از هم جدا میشوند. در بخش اول شرط، شمارنده حلقه تعریف میشود. این شمارنده در بیرون حلقه تعریف میشود.
در مثال زیر i = 0 شمارنده حلقه است و خود i در بیرون حلقه تعریف شده. i = 0 یعنی شمارنده از صفر شروع به شمارش میکند. بخش دوم شرط اتمام حلقه است. یعنی شمارنده حداکثر تا چه عددی بشمارد. مثلا i < 10 یعنی تا موقعی که شمارنده از 10 کوچکتر است حلقه تکرار شود.
(به کوچکتر بودن یا کوچکتر مساوی بودن توجه کنید.) بخش آخر شرط هم، اندازه گام های شمارنده است. یعتی در هر بار تکرار حلقه، شمارنده چند تا زیاد یا کم شود. به کد نمونه زیر دقت کنید. یک فایل خالی با gedit یا vim درست کنید و کد زیر را وارد کنید.
در این مثال شمارنده حلقه از صفر شروع شده و تا موقعی که از 10 کوچکتر باشد، حلقه ادامه پیدا میکند. اندازه گام را 1 در نظر گرفتیم. میتوانستیم به جای i=i+1 از ++i به معنی اینکه i را یکی یکی زیاد کن هم استفاده کنیم. هر بار که شرط چک میشود و داخل حلقه میرود، مقدار متفاوتی برای i وجود دارد که هنگامی که با printf چاپ میشود این تغییر واضح است.
به یک مثال ساده دیگر توجه کنید.
در این مثال شمارنده j در هر بار تکرار حلقه، 2 واحد کم میشود و هنگامی که به 10- میرسد، حلقه متوقف میشود.
کم کم وقت آن است که مثال های پر ملات تری حل کنیم. مثال زیر پیاده سازی یک جدول ضرب است. در این مثال از 2 حلقه تو در تو استفاده شده. سعی کنید با دقت در کد، به الگوریتم ساده ولی پایه ای و مفهومی آن پی ببرید.
یک مثال ساده دیگر.
حال سعی کنید شکل این مثلث را تغییر دهید. مثلا راس آن بالا سمت راست باشد. یا با همین ستاره ها یک لوزی چاپ گنید. (این مثال خیلی کلیشس!!! ترم یک ، مبانی کامپیوتر!!! ) در قسمت بعد در مورد حلقه while توضیح میدهیم.
بریم سر حلقه while. حلقه while به for بسیار شبیه است. در مقابل آن شرط حلقه و در بدنه آن کد های حلقه را مینویسیم. فقط فراموش نکنید شمارنده را اضافه کنید وگرنه حلقه تا بی نهایت پیش میرود. اگر for را یاد گرفته باشید، حرفی برای گفتن درمورد while باقی نمیماند. به دو صورت میتوان ساختار while را پیاده سازی کرد که در شکل میبینید.
حالت دوم استفاده از do است.
در این جلسه درمورد struct صحبت میکنیم. فرض کنید میخواهیم اطلاعات یک شخص یا یک دانشجو مانند نام، معدل، شماره دانشجویی و ... را ذخیره کنیم. اگر از آرایه ها به یاد داشته باشید چون کل آرایه از یک جنس است، مثلا یا int یا float ،نمیتوان انواع مختلفی از داده را درون آن ذخیره کرد.
در اینجاست که از struct استفاده میکنیم. در struct میتوانیم اطلاعات را بصورت کپسوله ذخیره کنیم. برای همان مثال دانشجو نیاز داریم که عناصر متفاوت نام و ... در کنار هم، متصل به هم و با یک نام تعریف شوند تا بتوان مجموعه آنها را با هم صدا زد یا پردازشی روی آنها انجام داد ، به یک تابع فرستاد و ... . با دیدن مثال بهتر متوجه خواهید شد. یک سند خالی با gedit یا vim باز کنید و کد های زیر را وارد کنید.
میتوان struct را قبل از تابع main نیز تعریف کرد. حالا من اول main تعریف کردم. ابتدا کلمه کلیدی struct را نوشته و پس از آن نام struct را وارد میکنیم. درون بدنه struct عناصر آنرا مشخص میکنیم. برای مثال اطلاعات یک دانشجو را میخواهیم ذخیره کنیم. تعدادی عنصر دلخواه با data type های متفاوت تعریف میکنیم.
حال باید اطلاعات دانشجو ها را وارد کنیم. دو متغیر دانشجو از جنس ساختار student_profile میسازیم (خط 15 و 16). برای مقدار دهی، ابتدا نام متغیر ساختاری، سپس نقطه و بعد از آن نام عنصر دلخواه ساختار را مینویسیم و مقدار دلخواه را وارد میکنیم.
برای مقدار دهی به رشته ها از تابع strcpy مطابق الگو استفاده کنید. برای چاپ هم از تابع printf استفاده کردیم. در قسمت مشخص کردن متغیر ورودی تابع printf، ابتدا نام متغیر ساختاری و سپس عنصر مورد نظر را وارد میکنیم.میتوانید مانند آرایه ها هم مقدار دهی کنید. حواستون به ترتیب هم باشه.
حتما تا الان این سوال واستون به وجود آمده که اگه 100 تا دانشجو داشتیم باید 100 بار خط تعریف متغیر ساختاری رو بزنبم ؟؟ البته که نه. برای این کار از آرایه های ساختاری استفاده میکنیم.
بسیار خوب، این هم struct. البته بیشتر با استفاده از اشاره گر ها و توابع با struct کار میکنند. بررسی این موضوعات را اگر امکانش بود در مباحث پیشرفته تر C ادامه میدهیم.
با توجه به اینکه دوستان در کامنت ها اشاره کردن، باید به مبحث "دریافت ورودی از کاربر" پرداخته بشه. میخواستم یکم جلوتر عنوانش کنم ولی دیدم حق با دوستانه و هر چی زودتر توضیح داده بشه بهتره. (یه ذره انتقاد پذیری به جایی بر نمیخوره !!! ) بسیار خوب. برگردیم سر کارمون. تا اینجا با مباحث پایه ای مختلفی آشنا شدیم.
آرایه ها، Data Types، شرط ها، حلقه ها و ... . حال اگر بخواهیم داده های برنامه توسط کاربر مقدار دهی شوند چه کنیم؟ از توابع مختلفی برای اهداف متفاوت میتوان استفاده کرد. در اینجا به یکی از مهم ترین ها و کار راه انداز ترین های آنها یعنی scanf اشاره میکنیم. طبق معمول یک سند خالی با vim یا gedit باز کنید. به کد زیر دقت کنید.
2 آرایه 25 تایی (24 تا قابل استفاده) و یک متغیر int تعریف میکنیم. با تابع scanf مقداری را از صفحه کلید دریافت میکند. همانطور که میدانید برای رشته s%، برای int از d% ، برای کاراکتر از c% و برای float از f% استفاده میکنیم. پس از تعریف نوع متغیر، نام آنرا ذکر میکنیم. در انواع متغیر ها، به جز رشته ها، قبل اسم متغیر & میگذاریم. از این & برای آدرس دهی استفاده میشود. (چرا ؟؟؟) اون 24 هم یعنی حداکثر 24 حرف بگیر. پس از نوشتن کد، آن را save کرده و compile کنید.
همینو میشه یکم تغییر داد.
برای گرفتن یک کاراکتر از کاربر و نمایش آن از getchar و putchar استفاده کنید.
توابع دیگری هم برای خواندن و یا ذخیره وجود دارند مانند خواندن ورودی از یک فایل و نوشتن خروجی در یک فایل که در مبحث فایل آنها را بررسی خواهیم کرد.
در این جلسه درمورد فایل ها صحبت میکنیم. تا اینجای کار دیدید که هر بار یک برنامه را اجرا میکنیم، اطلاعاتی که وارد میکنیم یا اطلاعات خروجی، همگی با اتمام برنامه از بین میروند. اما واضح است که ما به این اطلاعات نیاز داریم و باید ذخیره شوند. روش های متفاوتی برای ذخیره سازی اطلاعات در برنامه ها وجود دارد که یکی از آنها ذخیره در فایل است. بطور کلی توابعی که مربوط به ورود و خروج اطلاعات از فایل ها در C وجود دارند، به 2 دسته تقسیم میشوند.
عملیات روی فایل ها را به 4 دسته کلی میتوان تقسیم کرد.
FILE *ptr ;
باز کردن فایل : برای باز کردن فایل از تابع fopen استفاده میکنیم.
ptr = fopen( "C:\\my_text_file.txt" , "w" );
الگوی کد این تابع به این صورت است که آرگومان اول تابع آدرس فایل و آرگومان دوم حالت (mode) دسترسی به فایل است. انواع mode عبارتند از :
بستن فایل : بعد از اینکه کارمان با فایل تمام شد، مثلا در آن نوشتیم و از آن خواندیم، باید آن را ببندیم. این کار را با استفاده از تابع fclose انجام میدهیم.
fclose( ptr );
بسیار خوب. حالا بیایید چیز هایی را که یاد گرفتیم را پیاده سازی کنیم. طبق روال همیشه یک سند خالی با vim یا gedit باز کنید. به مثال ساده زیر توجه کنید.
#include <stdio.h> #include <stdlib.h> int main() { int n; FILE *fptr; fptr=fopen("/root/Desktop/c/my_text_file" , "w"); if ( fptr == NULL ) { printf("Error"); exit(1); } printf("Enter n: "); scanf( "%d",&n ); fprintf(fptr,"%d \n",n); fclose(fptr); return 0; }
تنها نکته جدیدی که در کد وجود دارد، تابع fprintf است. این تابع همانند printf است با این تفاوت که نتیجه را درون فایل ذخیره میکند. همانطور که واضح است این تابع 3 آرگومان دارد. اولی همان اشاره گرمان به فایل است. دومی فرمت دخیره داده و سومی متغیری که میخواهیم محتویاتش در فایل ذخیره شود. حال به یک مثال ساده دیگر توجه کنید.
#include <stdio.h> #include <stdlib.h> int main() { int n; FILE *fptr; if (( fptr=fopen("/root/Desktop/c/my_text_file" , "r" )) == NULL) { printf("Error"); exit(1); } fscanf( fptr,"%d",&n ); printf( "The Value of n is %d \n",n ); fclose(fptr); return 0; }
در این کد هم مطلب جدیدی وجود ندارد جز تابع fscanf . این تابع همانند scanf است با این تفاوت که ورودی را از فایل میخواند. این تابع 3 آرگومان دارد. اولی اشاره گر به فایل. دومی فرمت داده و سومی هم آدرس متغیری که میخواهیم ورودی در آن دخیره شود. 2 تابع fgetchar و fputchar هم همانند getchar و putchar هستند.بهتر است یک مثال دیگر هم حل کنیم.
مثال: برنامه ای بنویسید که نام و نمرات دانشجویان را بگیرد و آنها را مرتب در یک فایل ذخیره کند.
#include <stdio.h> #imclude <stdlib.h> int main(){ char name[50]; int marks,i,n; FILE *fptr; printf( "Enter the number of Students: " ); scanf( "%d",&n ); FILE *fptr; fptr = ( fopen( "C:\\student.txt" , "a" ) ); if( fptr == NULL) { printf( "Error" ); exit(1); } for( i=0 ; i<n ; i++ ) { printf( "For student %d Enter Name: " ,i+1 ); scanf( "%s",name ); printf( "Enter mark: " ); scanf( "%d" , &mark); fprintf( fptr, "Name: %s\t Mark: %d \n" ,name,mark); } fclose(fptr); return 0; }
فایل های باینری
بر اساس اینکه فایل به چه روشی برای پردازش باز میشود میتواند به دو دسته "باینری" و "متنی" تقسیم شود. در حجم زیاد داده، فایل های متنی مناسب نیستند و معمولا از فایل باینری استفاده میشود. کار کردن با فایل باینری بسیار شبیه به فایل متنی است. فقط کمی تفاوت در کد دارند.
باز کردن فایل باینری
حالت های باز کردن فایل های باینری را با rb+ ، rb+ ، wb ، wb+، ab ، ab نشان میدهند. فقط یک b نسبت به حالت فایل متنی اضافه شده.
خواندن و نوشتن در فایل باینری
از دو تابع fread و fwrite برای خواندن و نوشتن در فایل های باینری استفاده میشود. تابع fwrite چهار آرگومان دارد. آدرس فایل، اندازه داده روی فایل، تعداد داده و اشاره گر. تابع fread هم همینطور.
fwrite ( address_data , size_data , numbers_data to write , pointer_to_file );
به مثال ها توجه کنید.
فایل file4.c
#include <stdio.h> #include <stdlib.h> struct rec { int x, y, z; }; int main() { int counter; FILE *ptr; struct rec myrecord; ptr = fopen( "/root/Desktop/c/file.bin" , "wb" ); if ( !ptr ) { printf( "Unable to open file" ); return 1; } for ( counter=1 ; counter <= 10 ; counter++ ) { myrecord.x = counter; myrecord.y = counter; myrecord.z = counter; fwrite( &myrecord , sizeof(struct rec) , 1 , ptr); } fclose( ptr ); return 0; }
حال برای خواندن اطلاعات از کد زیر استفاده میکنیم.
فایل file5.c
#include <stdio.h> #include <stdlib.h> struct rec { int x ,y ,z ; }; int main() { int counter; FILE *ptr; struct rec myrecord; ptr = fopen( "/root/Desktop/c/file.bin" , "rb" ); if ( !ptr ) { printf( "Unable to open file!" ); return 1; } for ( counter=1 ; counter <= 10 ; counter++ ) { fread( &myrecord , sizeof(struct rec) , 1 , ptr ); printf( "%d \t " , myrecord.x); printf( "%d \t " , myrecord.y); printf( "%d \n " , myrecord.z); } fclose( ptr ); return 0; }
کد ها ساده است و فک نمیکنم احتیاج به توضیح خاصی باشد. اگر فایل file.bin را باز کنیم قابل خواندن توسط انسان نیست. باید با کد مخصوص فایل باینری باز شود.
در این جلسه درمورد توابع صحبت میکنیم. استفاده از توابع در برنامه نویسی بسیار رایج است. تقریبا در تمام برنامه های عملیاتی از توابع استفاده میشود. توابع در برنامه نویسی خاصیت ماژولار بودن را به برنامه اضافه میکنند. اگر قرار است برنامه ما 1000 خط باشد، مسلما این 1000 خط را یک تیکه در تابع main نمینویسیم.
بلکه برنامه را به تعدای تابع کوچک کوچک خورد میکنیم .توابع باعث خوانا تر شدن و قابل فهم تر شدن برنامه میشوند. عیب یابی را آسان تر میکند و درصورت بروز مشکل در برنامه لازم نیست کل اون 1000 خط برنامه را از اول بنویسیم، فقط همان تابع مشکل دار رو دوباره بررسی میکنیم.
قابلیت توسعه و نگهداری سیستم افزایش میابد. اگر قرار باشد سرویس جدیدی در برنامه اضافه شود، توابع لازم را نوشته و به برنامه اضافه میکنیم. جلوتر یاد خواهیم گرفت که توابع را در سورس کد های جدا در فایل های جدا نوشته و در برنامه اصلی آنها را include کنیم. به مثال ساده زیر توجه کنید.
یک تابع برای جمع 2 عدد نوشتیم. تابع را قبل از تابع main تعریف کردیم ( میتونستیم بعد از main هم بنویسیم که بعدا میگم ) کلمه int نشان میدهد که خروجی تابع ما int است. اگر برنامه خروجی ندارد از کلمه void استفاده میکنیم. (مثلا قراره فقط یه چیزی رو بخونه و نشون بده، قرار نیست از خروجیش استفاده بشه).
سپس نام تابع که add-nums است. در داخل پرانتز نوع آرگومان های ورودی به تابع را تعریف میکنیم. هر چی قراره وارد تابع بشه از اینجا وارد میشه. سپس بدنه تابع را مینویسیم. کلمه return هم نشان میدهد خروجی تابع چیست. در اینجا گفته متغیر sum را به عنوان خروجی پاس بده بیرون. تابع main هم تا خط 14 چیز جدیدی نداره.
در خط 14 یک متغیر را برابر تابع قرار دادیم. یعنی خروجی تابع را بریز تو اون متغیر. دو متغیر A و B را هم به عنوان ورودی تابع وارد کردیم. در آخر هم متغیر result را چاپ کردیم. این یک مثال خیلی ساده بود. حالا بیاید همینو با حالت void بودن تابع add-sum بنویسیم.
در تابع main فقط تابع add_nums را صدا کردیم. البته میشد که a و b رو در main از کاربر بگیره و همراه با صدا کردن تابع، به عنوان آرگومان واسش ارسال کنه.یکی از مفاهیم مهم توابع، مفهوم توابع بازگشتی است. در یک تابع بازگشتی، تابع مرتبا خود را صدا میزند. مث یه حلقه بهش نگاه کنید.
برای کارهای محاسباتی تکراری از توابع بازگشتی استفاده میکنیم. بهترین مثال برای تابع بازگشتی، محاسبه فاکتوریل یک عدد با تابع بازگشتی است. به کد زیر توجه کنید. قبلش بگم که اگه خواستید تابع رو بعد از main تعریف کنید باید قبل از main عنوانشو نوشته باشید. مث مثال زیر.
#include <stdio.h> long int Factorial ( int n ); int main() { int n ; printf( "Enter a positive integer : " ); scanf( "%d" , &n ); printf( "Factorial of %d = %ld \n", n, Factorial (n) ); return 0 ; } long int Factorial ( int n ) { if ( n >= 1 ) return n*Factorial ( n-1 ) ; else return 1 ; }
نکته ای که در توابع بازگشتی باید در نظر داشته باشید این است که برای خاتمه حلقه باید یک شرط قید کنید. در اینجا اون if این کار را میکند. کل مفهوم بازگشتی تو خط 14 هستش.حال برای تمرین همین برنامه رو طوری تغییر بدین که عدد هایی که تو هم ضرب میشن رو هم نشون بده، اول خودتون حل کنید بعدش جوابو نگاه کنید.
#include <stdio.h> long int Factorial ( int n ); int main() { int n ; printf( "Enter a positive integer : " ); scanf( "%d" , &n ); printf( "= %ld \n", Factorial (n) ); return 0 ; } long int Factorial ( int n ) { if ( n > 1 ) { printf( "%d * " , n ); return n*Factorial ( n-1 ) ; } if ( n == 1 ) { printf ("%d " , n ) ; return n*Factorial ( n-1 ) ; } else return 1 ; }
در این قسمت در مورد یک مبحث بسیار ساده به نام توابع اشاره گر صحبت میکنیم. توابع اشاره گر به یک تابع دیگر اشاره میکنند و ما برای استفاده از تابع اصلی، به تابعی که به آن شاره میکند رجوع میکنیم و آرگومان ها رو به اشاره گر ارسال میکنیم. به مثال توجه کنید.
#include <stdio.h> int add_num (int a,int b) { int sum = a + b ; return sum ; } int main () { int result; int (*myfunct_pointer)( int , int ); myfunct_pointer = &add_num; result = myfunct_pointer (3 , 4); printf("reslut = %d \n" , result); return 0; }
در خط 10 یک تابع شاره گر تعریف کردیم. فقط جنس آرگومان ها را برای آن تعریف میکنیم، بدون اسم. فقط 2 تا int نوشتم. یعنی ورودی آن 2 تا int است. در خط 11 هم میگوییم که به کجا اشاره کند که این کار را با گداشتن & قبل از نام تابع اصلی انجام میدهیم. یعنی آدرسشو میریزیم توی اشاره گر. حال اگر به اشاره گر آرگومان ارسال کنیم، انگار به تابع اصلی ارسال کردیم. نتیجه تابع اشاره گر رو هم تو یک متغیر ریختیم. خیلی هم ساده و مختصر مفید !!!
در این جلسه درباره آرگومان های تابع main به نام های argc و argv صحبت میکنیم. وقتی برنامه اجرا میشود، این آرگومان ها توسط سیستم عامل مقدار میگیرند. argc تعداد آرگومان ها و argv رشته آرگومان ها هستند. بهتر است در مثال ببینیم.
#include <stdio.h> int main ( int argc , char *argv[]) { printf("argc = %d \n", argc); return 0; }
اگر با اسکریپت نویسی در لینوکس آشنا باشید میدانید که وقتی یک تابع یا برنامه را در ترمینال صدا میزنیم، در مقابل نام آن آرگومان هایی مینویسیم که به تایع، دستور یا برنامه ارسال میشوند. وقتی شما در ترمینال وارد میکنید echo salam کلمه salam به عنوان آرگومان به دستور echo وارد میشود .
یا مثلا با دستور kill 3225 پروسه شماره 3225 را از بین میبرید، عدد 3225 یک آرگومان برای دستور kill است. (مثال بهتر به ذهنم نمیرسه). حال وقتی یک فایل executable را در ترمینال اجرا میکنیم، مهم نیست اینجا به چه زبانی، کلی به قضیه نگاه کنید، کلماتی که بعد از دستور اجرای برنامه وارد میکنیم، حکم آرگومان برای برنامه را دارند.
مثلا وقتی با Bash یک برنامه مینویسیم آرگومان ها را جلوی نام برنامه وارد میکنیم و در برنامه با 1$ 2$ 3$ و .. به آنها دسترسی پیدا میکنیم. در مثال بالا هم تعدای آرگومان به برنامه ارسال کردیم که با space از هم جدا شدند. به خاطر این از صفر شروع نمیشه چون خود دستور اجرا هم یه آرگومان محسوب میشه.argv یک آرایه از اشاره گر ها ست که به آرگومان های دریافتی برنامه اشاره میکند. به مثال توجه کنید.
#include <stdio.h> int main ( int argc , char *argv[]) { int i; printf("argc = %d \n", argc); for ( i = 0 ; i < argc ; i++ ) { printf("argv[%d] = %s \n" , i , argv[i]); } return 0; }
یک حلقه برای چاپ آرگومان های ارسالی به برنامه درست کردیم. با argv میتوانیم به محتوای آرگومان های ارسال شده دسترسی پیدا کنیم. عذر میخوام اگه خوب توضیح نمیدم من استاد این مباحث نیستم.
در این قسمت در مورد Multiple Source Files صحبت میکنیم. این مفهوم بدین معناست که تمام کد ها و توابع برنامه ما در یک سورس فایل نیستند بلکه میتوانند در چندین فایل مجزا باشند. خاصیت این کار این است که برنامه نویسی را راحت تر میکند، برنامه راحت تر و با صرف هزینه کمتر اجرا میشود.
هر وقت که به یک تابع یا سرویس نیاز داشتیم او را در برنامه اصلی صدا میزنیم. در حالت فقط یک سورس فایل اگر هزار تا تابع و سرویس داشته باشیم، موقع اجرای برنامه، هر هزار تا کامپایل شده و فضای حافظه را اشغال میکنند و سرعت اجرای برنامه را پایین می آورند.
ولی وقتی بصورت ماژولار، برنامه را به توابع و فایل های کوچک کوچک تقسیم میکنیم، عیب یابی سیستم راحت میشود، اگر یک فایل یا یک تابع مشکل داشته باشد کل برنامه از کار نمی افتد و هم چنین توسعه و بروز رسانی سیستم راحت تر میشود. با یک مثال ساده این بخش را شروع میکنیم. یک سورس فایل اصلی به نام source.c داریم که تابع main در آن نوشته شده. یک فایل دیگر با نام add-sum داریم که حاوی برنامه ای برای جمع اعداد است.
فایل main-source :
#include <stdio.h> int add_sum ( int a , int b ); int main () { int total; total = add_sum ( 2 , 4 ); printf(" total = %d \n" , total ); return 0; }
فایل add_sum :
#include <stdio.h> int add_sum(int a , int b) { int sum = a + b; return sum; }
برای کامپایل به ترتیب زیر عمل کنید:
روش های دیگری هم برای این کار وجود دارد. میتوان فایل ها را در ابتدای فایل main-source با include معرفی کرد. 3 فایل با نام های main-source.c و header.c و header.h داریم.
فایل main :
#include <stdio.h> #include "header.h" int main () { int total; total = function( 2 , 4 ); printf("total = %d \n",total); return 0; }
فایل header.h :
int function (int , int);
فایل header.c :
#include <stdio.h> #include "header.h" int function(int a , int b) { int sum = a + b; return sum; }
برای اجرا :
امروز درباره دستورات کتابخانه string.h صحبت خواهیم کرد. این کنابخانه توابع متنوعی برای کار با رشته ها را در اختیار شما قرار میدهد. برای دیدن صفحه manual آن در ترمینال دستور man string.h را وارد کنید. خروجی زیر را مشاهده خواهید کرد. این صفحه شامل توضیحات و نحوه استفاده از انواع توابع این کتابخانه است.
<string.h>(0P) POSIX Programmer’s Manual <string.h>(0P) NAME string.h - string operations SYNOPSIS #include <string.h> DESCRIPTION Some of the functionality described on this reference page extends the ISO C standard. Applications shall define the appropriate feature test macro (see the System Interfaces volume of IEEE Std 1003.1-2001, Sec- tion 2.2, The Compilation Environment) to enable the visibility of these symbols in this header. The <string.h> header shall define the following: NULL Null pointer constant. size_t As described in <stddef.h> . The following shall be declared as functions and may also be defined as macros. Function prototypes shall be provided. void *memccpy(void *restrict, const void *restrict, int, size_t); void *memchr(const void *, int, size_t); int memcmp(const void *, const void *, size_t); void *memcpy(void *restrict, const void *restrict, size_t); void *memmove(void *, const void *, size_t); void *memset(void *, int, size_t); char *strcat(char *restrict, const char *restrict); char *strchr(const char *, int); int strcmp(const char *, const char *); int strcoll(const char *, const char *); char *strcpy(char *restrict, const char *restrict); size_t strcspn(const char *, const char *); char *strdup(const char *); char *strerror(int); int *strerror_r(int, char *, size_t); size_t strlen(const char *); char *strncat(char *restrict, const char *restrict, size_t); int strncmp(const char *, const char *, size_t); char *strncpy(char *restrict, const char *restrict, size_t); char *strpbrk(const char *, const char *); char *strrchr(const char *, int); size_t strspn(const char *, const char *); char *strstr(const char *, const char *); char *strtok(char *restrict, const char *restrict); char *strtok_r(char *, const char *, char **); size_t strxfrm(char *restrict, const char *restrict, size_t); Inclusion of the <string.h> header may also make visible all symbols from <stddef.h>. The following sections are informative. APPLICATION USAGE None. RATIONALE None. FUTURE DIRECTIONS None. SEE ALSO <stddef.h>, <sys/types.h>, the System Interfaces volume of IEEE Std 1003.1-2001, memccpy(), memchr(), memcmp(), memcpy(), mem- move(), memset(), strcat(), strchr(), strcmp(), strcoll(), strcpy(), strcspn(), strdup(), strerror(), strlen(), strncat(), strncmp(), strncpy(), strpbrk(), strrchr(), strspn(), strstr(), strtok(), strxfrm() COPYRIGHT Portions of this text are reprinted and reproduced in electronic form from IEEE Std 1003.1, 2003 Edition, Standard for Information Technology -- Portable Operating System Interface (POSIX), The Open Group Base Specifications Issue 6, Copyright (C) 2001-2003 by the Institute of Electrical and Electronics Engineers, Inc and The Open Group. In the event of any discrepancy between this version and the original IEEE and The Open Group Standard, the original IEEE and The Open Group Standard is the referee document. The original Standard can be obtained online at http://www.opengroup.org/unix/online.html . IEEE/The Open Group 2003 <string.h>(0P)
حالا بیایید تعدادی از آنها را امتحان کنیم. یک سند خالی با vim یا gedit باز کنید و دستورات را وارد کنید.
#include <stdio.h> #include <string.h> int main () { char str[24] ; char str2[24] ; int i , n ; /*Example 1*/ sprintf( str , "This is a string function." ); printf( "%s \n" , str ); /*Example 2*/ i = 4; sprintf( str, "Valude of i = %d" , i ); printf( "%s \n" , str ); /*Example 3*/ n = strlen(str); printf("Lenght of str = %d \n" , n ); /*Example 4*/ strcpy( str2 , str ); printf( "str2 is %s \n" , str2 ); /*Example 5*/ memset( str , 0 , 24 ); printf( "str is >%s< \n" , str ); memset( str , 'a' , 10 ); printf( "str is %s \n" , str ); return 0; }
تابع sprintf یک رشته دلخواه را درون یک آرایه از کاراکتر ها میریزد. این تابع را با printf اشتباه نگیرید. sprintf چیزی را در خروجی چاپ نمیکند. فقط برای وارد کردن محتویات است. تابع strlen طول یک رشته را برمیگرداند. نام رشته را به عنوان آرگومان به strlen ارسال کنید.
تابغ strcpy محتویات یک رشته را درون رشته دیکر کپی میکند. در اینجا str را درون str2 کپی کردیم. آرگومان اول نام رشته مبدا و آرگومان دوم نام رشته مبدا است. تابع memset تک تک خانه های یک رشته را تغییر میدهد. 3 آرگومان میگیرد. آرگومان اول نام رشته، آرگومان دوم مقداری را که میخواهیم درون خاته های رشته ریخته شود و آرگومان سوم هم تعداد خانه ها از رشته است که یخواهیم این عمل روی آنها انجام شود. میتواند برابر با طول رشته باشد یا نباشد. ابتدا هر 24 خانه را نال کردیم. بعدش 10 تای اول رو حرف a ریختیم.
در این قسمت درمورد تفاوت بین char pointer و array char صحبت میکنیم. به کد زیر دقت کنید.
#include <stdio.h> #include <string.h> int main () { char str[] = "First String"; char *ptr = "Second String"; printf("str = %s \n",str); printf("ptr = %s \n",ptr); return 0; }
در خروجی خواهید دید
str = First String ptr = Second String
ولی این دو یکی نیستند. بیایید با شکل تفاوت آنها را بررسی کنیم.
وقتی یک آرایه از کاراکتر ها درست میکنید و یک رشته در آن میریزید، چیزی شبیه شکل سمت راست درست میشود. تعدادی از خانه های حافظه به ترتیب برای این آرایه درنظر گرفته میشود. ولی برای char pointers اینطور نیست. شکل سمت چپ.
تعدادی از خانه های حافظه به ترتیب کاراکتر های رشته را ذخیره کرده و آدرس خانه اول در ptr ریخته میشود. (بهترین ابزار ویندوز همین پینته !!!) حال اگر آدرس را افزایش دهیم چه ؟؟ میتوان آدرس را در char pointer افزایش داد ولی در array char نه. به کد زیر دقت کنید.
#include <stdio.h> #include <string.h> int main () { char str[] = "First String"; char *ptr = "Second String"; printf("str = %s \n",str); printf("ptr = %s \n",ptr); ptr = ptr + 1; /* ptr++ */ printf("ptr = %s \n",ptr); return 0; }
حالا خروجی رو ببینید !!!
str = First String ptr = Second String ptr = econd String
دیگر ptr به خانه اول اشاره نمیکند، بلکه به خانه دوم اشاره میکند!!! اگر همین کار رو برای str انجام دهید، هنگام کامپایل خطا میدهد.
در این قسمت در مورد Binary & Unary Operations صحبت میکنیم. به کد زیر دقت کنید.
#include <stdio.h> int main () { int a; printf("Example 1\n"); a = 1; a++; /*a = a + 1*/ printf("a = %d \n",a); printf("Example 2\n"); a = 2; a += 4; /*a = a + 4*/ printf("a = %d \n",a); printf("Example 3\n"); a = 4; --a; /*a = a - 1*/ printf("a = %d \n",a); printf("Example 4\n"); a = 4; a -= 3; /*a = a - 3*/ printf("a = %d \n",a); printf("Example 5\n"); a = 20; a /= 4; /*a = a / 4*/ printf("a = %d \n",a); printf("Example 6\n"); a = 4; a *= 5; /*a = a * 5*/ printf("a = %d \n",a); printf("Example 7\n"); //++a VS a++ a = 4; printf("a = %d \n",a++); printf("a = %d \n\n",a); a = 4; printf("a = %d \n",++a); printf("a = %d \n",a); return 0; }
Example 1 a = 2 Example 2 a = 6 Example 3 a = 3 Example 4 a = 1 Example 5 a = 5 Example 6 a = 20 Example 7 a = 4 a = 5 a = 5 a = 5
مبحث این جلسه بسیار ساده است. عباراتی که کامنت شده اند معادل عملیات قبل خود هستند. یعنی ++a با a=a+1 یک کار را انجام میدهند. به آنهایی که درون کامنت هستند اصطلاحا Binary Operations و به معادل آنها که قبلش نوشته شده Unary Operations گفته میشود.
فقط مثال آخر است که احتیاج به کمی توضیح دارد. این مثال تفاوت بین ++a و a++ را نشان میدهد. ++a یعنی ابتدا متعیر را بگیر، سپس آنرا افزایش بده ولی a++ یعنی ابتدا متغیر را افزایش بده سپس مقدار آنرا بگیر. در مثال 7 اولین printf ابتدا a را نشان میدهد سپس یکی اضافه میکند.
برای همین در printf دوم مقدار a یکی زیاد شده ولی در printf اولی همان 4 است. در printf سوم ابتدا متغیر را یکی زیاد کرده سپس آنرا چاپ کرده. همین متغیر هم در printf بعدی اش چاپ میشود. خیلی ساده است. امیدوارم مطلب را یاد گرفته باشید.
Casting به معنای تبدیل (Convert) یک Data Type به Data Type دیگر است. به کد زیر توجه کنید.
#include <stdio.h> int main () { int i; char c; c = 'a'; i = (int) c; //Casting c to int printf("a = %d , %c \n" , i , i); c = 'A'; i = (int) c; //Casting c to int printf("A = %d , %c \n" , i , i); c = 'z'; i = (int) c; //Casting c to int printf("z = %d , %c \n" , i , i); c = 'Z'; i = (int) c; //Casting c to int printf("Z = %d , %c \n" , i , i); return 0; }
خروجی :
a = 97 , a A = 65 , A z = 122 , z Z = 90 , Z
وقتی در خط 7 بطور مثال متغیر c که از نوع char است را به int تبدیل میکنیم، در واقع "کد اسکی" آن در i ذخیره میشود. با تابع printf آن را چاپ میکنیم. از d% برای چاپ مقدار اسکی یا مقدار int متغیر و از c% برای چاپ کاراکتر آن استفاده کردیم. یک مثال دیگر را ببینید.
#include <stdio.h> int main () { int i; char *ptr; char c = 'a'; ptr = &c; i = (int) ptr; //Casting ptr to int printf("i = %x \n",i); return 0; }
خروجی :
i = bfb893c7
آدرس متغیر c را در ptr ریختیم. سپس آدرسی را که ptr به آن اشاره میکند (نه محتوا) را تبدیل به int کرده و درون i میریزیم.در هنگام cast به سایز Data Type ها هم توجه داشته باشید. تبدیل یک Data Type بزرگ به Data Type کوچک تر بدون خطا و تغییر داده ممکن نخواهد بود. تصویر زیر نحوه رشد اندازه متغیر ها را نشان میدهد.
در این قسمت در مورد دو تابع readdir و opendir صحبت میکنیم. از این 2 تابع برای باز کردن و خواندن محتویات directory ها استفاده میکنیم. به مثال زیر توجه کنید. اضافه کردن header ها فراموش نشود.
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <dirent.h> int main () { DIR *dir; struct dirent *sd; dir = opendir("/root"); if (dir == NULL) { printf("unable to open directory. \n"); exit(1); } while( (sd = readdir(dir)) != NULL ) { printf(">> %s \n", sd-> d_name); } closedir(dir); return 0; }
یک اشاره گر از جنس directory درست میکنیم. یک struct هم درست کردیم. کلمه dirent یک کلمه کلیدی است. برای متوجه شدن man readdir را ببینید. یک مسیر دلخواه را با opendir باز کردیم و درون dir میریزیم. با readdir محتویات dir را میخوانیم و درون sd میریزیم.
توجه کنید که sd یک struct است و تمام عناصر آن مقدار میگیرند. این struct برای همین کار ساخته شده است. در تابع printf هم گفتیم که از sd قسمت dname را چاپ کنید. این که dnmae رو از کجا آوردم توی man readdir نوشته برای دسترسی به نام ها از این استفاده کنید. ابتدا man readdir و سپس خروجی برنامه را در تصویر میبینید. در آخر برنامه فراموش نکنید که مسیر را close کنید.
On Linux, the dirent structure is defined as follows: struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* offset to the next dirent */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all file system types */ char d_name[256]; /* filename */ };
خروجی برنامه :
>> .config >> .dbus >> .lftp >> .bash_logout >> .viminfo >> Music >> anaconda-ks.cfg >> vm >> .lesshst >> .pulse-cookie >> IFS >> .local >> .recently-used.xbel >> .mozilla >> a >> d >> Pictures >> .cache >> .bash_profile >> .icons >> Templates >> Downloads >> .bash_history >> proje.sh >> post-install.log >> test >> .esd_auth >> Public >> VMwareTools-10.0.6-3595377.tar.gz >> Desktop >> .tcshrc >> .cshrc >> .gconfd >> .. >> . >> .spice-vdagent >> .pki >> .thumbnails >> .gnome2_private >> .themes >> b >> .gnome2 >> nohup.out >> c >> .gtk-bookmarks >> .ICEauthority >> Documents >> Videos >> sfk >> post-install >> .gvfs >> temp >> .gconf >> .bashrc >> .pulse >> .nautilus
حالا مثلا inode تک تک فایل ها رو میخوام:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <dirent.h> int main () { DIR *dir; struct dirent *sd; dir = opendir("/root"); if (dir == NULL) { printf("unable to open directory. \n"); exit(1); } while( (sd = readdir(dir)) != NULL ) { printf("%s\t", sd-> d_name); printf("%d\n", sd-> d_ino); } closedir(dir); return 0; }
خروجی:
.config 131249 .dbus 131155 .lftp 134496 .bash_logout 1440 .viminfo 4558 Music 131243 anaconda-ks.cfg 75884 vm 131385 .lesshst 1997 .pulse-cookie 76039 IFS 4555 .local 131348 .recently-used.xbel 70853 .mozilla 131537 a 4556 d 37210 Pictures 131247 .cache 131485 .bash_profile 1441 .icons 131444 Templates 131208 Downloads 131170 .bash_history 76054 proje.sh 35333 post-install.log 22508 test 1747 .esd_auth 76052 Public 131222 VMwareTools-10.0.6-3595377.tar.gz 22501 Desktop 131169 .tcshrc 1444 .cshrc 1443 .gconfd 131088 .. 2 . 8201 .spice-vdagent 131317 .pki 134695 .thumbnails 131445 .gnome2_private 131373 .themes 131443 b 69625 .gnome2 131218 nohup.out 36362 c 4548 .gtk-bookmarks 21545 .ICEauthority 76050 Documents 131231 Videos 131248 sfk 31761 post-install 22505 .gvfs 131334 temp 54246 .gconf 131086 .bashrc 1442 .pulse 131335 .nautilus 131382
مفهوم fork در سیستم عامل های مختلف وجود دارد. هنگامی که در برنامه از دستور fork استفاده میکنیم، ار آن خط به بعد دستورات توسط پروسس های همزمان اجرا میشوند. داده های مورد نیاز فرزند از پدر کپی میشود و از آن به بعد هر پروسس داده مربوط به خود را دارد. به بیان دیگر هنگامی که دستور fork فراخوانی میشود، یک پروسس دیگر در سیستم عامل ایجاد میشود که به این پروسس فرزند و به پروسه قبل از آن پدر گفته میشود.
هنگامی که یک برنامه توسط سیستم اجرا میشود، اجرای بخش های بعدی وابسطه به اجرا شدن قسمت های قبلی است. اما در برخی شرایط شما نیاز دارید تا برنامه ای بنویسید تا چند عمل را به صورت موازی انجام دهد. یک مثال می تواند وب سرور باشد.
هنگامی که یک کاربر یک صفحه را از وب سرور درخواست می کند اگر سرور پاسخ آن کاربر را بدهد و سپس به درخواست کاربر دیگری گوش دهد، در آن فاصله زمانی بسیاری از درخواست ها به سرور بی پاسخ مانده و از بین میروند. بنابراین سرور باید بتواند هر درخواستی که دریافت می کند را به شکل موازی با درخواست های دیگر پاسخ دهد و در یک زمان به تعداد بیشتری کاربر پاسخ دهد.
کاربرد تابع fork اینجا مشخص میشود. هنگامی که برنامه شما این تابع را صدا کند سیستم عامل برنامه شما را که در قالب یک پروسس اجرا می شود به دو پروسس دقیقا همانند هم تبدیل می کند که به طور موازی اجرا می شوند. خروجی تابع یا صفر است و یا یک عدد.
برنامه شما می تواند با بررسی خروجی این تابع متوجه شود که الان کدام پروسس می باشد و با توجه به این موضوع به کاری متفاوت بپردازد. مثلا پروسس سرور وقتی یک درخواست از یک کاربر دریافت می کند تابع fork را صدا می کند. حال برنامه شما در دو پروسس اجرا می شود.
اکنون با بررسی مقدار خروجی تابع fork میتوان فهمید که پروسس جاری پروسس فرزند است یا پدر. اگر پروسس پدر بود که یعنی همان سرور است و دوباره به درخواست های کاربر ها گوش می دهد. اما اگر پروسس فرزند بود، صفحه وب مورد نطر را برای کاربر می فرستد.
به این ترتیب یک برنامه می تواند در تعداد زیادی نسخه به شکل موازی اجرا شود که هرکدام کار متفاوتی را انجام می دهند.گفتیم که اگر عملیات fork موفقیت آمیز باشد، یک پروسس فرزند با id برابر صفر ایجاد میشود که پدر این پروسه id غیر صفر دارد. به الگوریتم زیر دقت کنید:
childPID = fork (); if( childPID >= 0 ) // عملیات ایجاد فرزند موفقیت آمیز بوده یا نه { if( childPID == 0 ) { فرآیند فرزند } else { فرآیند پدر } else // اگر نتواند فرزند درست کند 1- برمیگرداند خطا در ایجاد فرزند }
حالا بر اساس این الگوریتم، یک مثال ببینیم (مثال رو از ویکی پدیا ورداشتم)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid; pid = fork(); if (pid == -1) { printf("can't fork, error\n"); exit(1); } else if (pid == 0) //Child process { int j; for ( j = 0 ; j < 10 ; j++ ) { printf("child: %d\n", j); sleep(1); } exit(0); } else //When fork() returns a positive number, we are in the parent process. { int i; for ( i = 0 ; i < 10 ; i++ ) { printf("parent: %d\n", i); sleep(1); } exit(0); } return 0; }
خروجی به صورت زیر خواهد بود.
parent: 0 child: 0 parent: 1 child: 1 parent: 2 child: 2 parent: 3 child: 3 parent: 4 child: 4 parent: 5 child: 5 parent: 6 child: 6 parent: 7 child: 7 parent: 8 child: 8 parent: 9 child: 9
اگه متوجه نشدید چی شد این حلقه ها، زمان sleep رو متفاوت بدید مثلا parent رو 2 بدید child رو 1 بدید، اونجوری با اختلاف زمانی چاپ، بهتر متوجه میشید چی میشه.
بنا بر تعریف، thread به کوچکترین واحد پردازشی گفته میشود که طبق یک زمان بندی روی آن کار میشود. یک process میتواند شامل چندین thread باشد که بصورت غیر همزمان اجرا میشوند. این اجرای غیر همزمان باعث میشود که هر thread بخشی از یک کار مستقل را به عهده بگیرد و انجام دهد.
بنابراین چندین thread که داخل یک process انجام میشوند، وظیفه ای که به process واگدار شده را انجام میدهند. مجموع این thread ها در حقیقت ظرفیت عملیاتی یک process است. حال چرا باید از thread استفاده کنیم و اصلا به چه دردی میخورد ؟
فرض کنید یک process داریم که که مجموعه ای از ورودی ها را بصورت real time میگیرد و متناظر با هر ورودی، یک خروجی خاص تولید میکند. حال اگر این process شامل thread نباشد، تمام پردازش بصورت همزمان انجام میشود، یعنی یک ورودی میگیرد و یک خروجی میدهد.
مشکل این روش این است که process نمیتواند تا قبل از اینکه کار ورودی قبلی را انجام نداده، ورودی جدیدی بگیرد. حال اگر یک ورودی سنگین تر هم باشد و زمان بیشتری بگیرد، ورودی های بعدی همه باید منتظر بمانند تا کار آن ورودی سنگین انجام شود.
در user space بوجود آوردن و از بین بردن thread ها توسط کتابخانه های thread انجام میشود. این thread ها برای kernel ناشناخته هستند و kernel هیچ نقشی در مدیریت آنها ندارد. در user space خود thread تصمیم میگیرد که چه زمانی حافظه پردازنده را آزاد کند.
نکته مثبت thread در user space این است که switch کردن میان آنها با حداقل overhead انجام میشود و بسیار سریع است. نکته منفی آن هم این است که در این لایه اصطلاحا میان thread ها رابطه co-operative multitasking وجود دارد. یعنی اگر یکی از thread ها block شود، کل process از بین میرود و block میشود.
در kernel space خود kernel وظیفه بوجود آوردن و از بین بردن thread ها را به عهده دارد. در این حالت به ازای هر thread در user space بک thread متناظر هم در kernel space وجود دارد. چون این thread ها توسط kernel مدیریت میشوند، برخلاف thread های user space که با co-operative multitasking کار میکنند، thread های kernel space از preemptive multitasking استفاده میکنند.
در این حالت میتوان یک thread را با حق اولویت پردازش بالاتری اجرا کرد. همچنین اگر یکی از thread ها block شود، مشکلی برای process بوجود نمی آید. اما switching میان thread ها در این لایه کمی کند تر نسبت به user space است.
همانطور که process ها با pid مشخص میشوند، thread ها هم با thread id مشخص میشوند. به نکات زیر در این مورد دقت کنید.
برای thread id یک type به نام pthread_t وجود دارد که توجه داشته باشید structure است. پس یک تابع باید وجود داشته باشد که thread id ها را بتواند مقایسه کند.
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread_t tid2);
تابع بالا 2 تا thread id میگیرد و اگر برابر باشند مقدار غیر صفر و اگر نابرابر باشند صفر برمیگرداند.اگر در شرایطی یک thread بخواهد thread id خود را بداند از تابع زیر استفاده میکنیم.
#include <pthread.h> pthread_t pthread_self(void);
پس از تابع pthread__self برای شناساندن thread id به خود thread استفاده میکنیم.در حالت عادی وقتی یک برنامه اجرا میشود و تبدیل به یک process میشود، با یک thread پیش فرض شروع میشود. در واقع هر process حداقل یک thread را دارد. هر process میتواند با دستور زیر برای خودش thead های بیشتری تولید کند.
#include <pthread.h> int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg)
این تابع نیاز به 4 آرگومان دارد.
حال با توجه به چیزهایی که تا حالا خوندیم، مثال زیر را ببینید:
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); if(pthread_equal(id,tid[0])) { printf("\n First thread processing\n"); } else { printf("\n Second thread processing\n"); } for(i=0; i<(0xFFFFFFFF);i++); return NULL; } int main(void) { int i = 0; int err; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } sleep(5); return 0; }
برای کامپایل کمی دستور gcc را باید تغییر دهید:
gcc -pthread thread.c -o thread
دستورات رو تو فایل thread.c نوشتم و برای اجرا :
[root@CentOS6 c]# ./thread Thread created successfully Thread created successfully Second thread processing First thread processing [root@CentOS6 c]#
حال این کد چه کار میکند ؟
توجه داشته باشید که ترتیب اجرا کردن thread ها ثابت نیست و بستگی به الگوریتم های زمان بندی سیستم عامل دارد. یک بار دیگر به کد دقت کنید.... شاید بپرسید اون sleep چیه دیگه ؟؟ پاکش کنید و دوباره کد رو اجرا کنید... خروجی میشه مث زیر:
[root@CentOS6 c]# ./thread Thread created successfully Thread created successfully [root@CentOS6 c]#
شاید برای شما یک خط processing هم چاپ کند، برای من یکی هم چاپ نکرد !!! حالا چرا این اتفاق افتاد؟؟ دلیل آن این است که قبل از اینکه دومین thread یا حتی اولین thread هم بخواهد کاری انجام دهد، thread پدر کارش تمام شده و به پایان رسیده است.
یادتونه گفتم هر برنامه بصورت پیش فرض حداقل یه دونه thread داره؟؟ تابع main هم یه thread پیش فرض داره و پدر این thread هاییه که ما درست کردیم. پس قبل از اینکه thread های فرزند کاری انجام بدن، کار تابع main تموم میشه و برنامه بسته میشه !!!
پس حداکثر عمر یک thread فرزند در برنامه وابسته به عمر thread پیش فرض ( پدر ) تابع main است. حال اگر بخواهیم این thread پدر را مجبور کنیم تا موقعی که کار thread های فرزند تمام نشده صبر کند، از تابع زیر استفاده میکنیم.
#include <pthread.h> int pthread_join(pthread_t thread, void **rval_ptr);
این تابع مطمئن میشود که تا موقعی که کار thread فرزند تمام نشده، thread پدرش terminate نشود. این تابع در thread پدر تعریف میشود. آرگومان اول thread id اون thread ای است که منتظرش میماند تا تمام شود و آرگومان دوم برابر مقداری است که thread ای که منتظرش بوده برمیگرداند. اگر میخوایم مقداری برنگردد آنرا null میگذاریم. بطور کلی یک thread به 3 روش میتواند terminate شود ( ینی خاتمه پیدا کند ) :
#include <pthread.h> void pthread_exit(void *rval_ptr);
این تابع فقط یک آرگومان میگیرد و این مقدار را به پدر خود پاس میدهد. در واقع مقدار این آرگومان، برابر با چیزی است که یک thread فرزند در نهایت کار خود تولید میکند و آنرا به پدر خود پاس میدهد و thread پدر این مقدار برگشتی را در آرگومان دوم تابع pthread__join دریافت میکند. ( یکم پیچیده شد، ببخشید !!! ). حال اگر بخواهیم مثال بالا را بهبود بدهبم و از توابع جدیدی که یاد گرفتیم استفاده کنیم، کدمان بصورت زیر تغییر پیدا میکند.
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; int ret1,ret2; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); for(i=0; i<(0xFFFFFFFF);i++); if(pthread_equal(id,tid[0])) { printf("\n First thread processing done\n"); ret1 = 100; pthread_exit(&ret1); } else { printf("\n Second thread processing done\n"); ret2 = 200; pthread_exit(&ret2); } return NULL; } int main(void) { int i = 0; int err; int *ptr[2]; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } pthread_join(tid[0], (void**)&(ptr[0])); pthread_join(tid[1], (void**)&(ptr[1])); printf("\n return value from first thread is [%d]\n", *ptr[0]); printf("\n return value from second thread is [%d]\n", *ptr[1]); return 0; }
برای من خروجی رو اینجوری نشون میده :
[root@CentOS6 c]# ./thread Thread created successfully Thread created successfully Second thread processing done First thread processing done return value from first thread is [100] return value from second thread is [200] [root@CentOS6 c]# [root@CentOS6 c]#
حالا اگر دوباره بخوایم کد رو مرور کنیم:
ببخشید این مطلب طولانی و پیچیده شد... میخواستم یک قسمت بشه thread .
در این جلسه درمورد ساختار switch case صحبت میکنیم. به switch case میتوان به عنوان یک if else گسترده تر نگاه کرد. با switch case میتوان یک متغیر را گرفت و برای حالت های مختلف مقدار آن تصمیم گیری های مختلفی انجام داد. اگر بخواهیم همین کار را با if انجام دهیم، به ازای تک تک مقادیری که متغیرمان میگیرد باید یک if بنویسیم. به ساختار زیر دقت کنید.
همانطور که در شکل مشخص است، یک ورودی وارد تابع شده و مقدار آن با هر کدام از موارد ( case ) برابر باشد، کد های همان case اجرا میشود. به مثال ساده زیر دقت کنید.
#include <stdio.h> int main() { printf( "Enter your grade: " ); char Grade ; scanf( "%c" , &Grade ); switch( Grade ) { case 'A' : printf( "Excellent\n" ); break; case 'B' : printf( "Good\n" ); break; case 'C' : printf( "OK\n" ); break; case 'D' : printf( "Mmmmm....\n" ); break; case 'F' : printf( "You must do better than this\n" ); break; default : printf( "What is your grade anyway?\n" ); break; } return 0; }
خروجی کد :
[root@CentOS6 c]# gcc -o switch switch.c [root@CentOS6 c]# ./switch Enter your grade: A Excellent [root@CentOS6 c]# ./switch Enter your grade: B Good [root@CentOS6 c]# ./switch Enter your grade: C OK [root@CentOS6 c]# ./switch Enter your grade: bla blla What is your grade anyway? [root@CentOS6 c]#
کد بسیار ساده است. یک مقدار ورودی از کاربر میگیرد و بر اساس آن تصمیم گیری میکند. سعی کنید مثال را برای حالت ها و انواع متغیر امتحان کنید. حالت پیش فرض برای این است که اگر مقداری که کاربر وارد کرده در هیچ حالت case ها وجود ندارد. حالا اگر بخواهیم در همین مثال بالا برای هر case مقدار 2 شرط همزمان داشته باشد که هر کدام برقرار بود اجرا شود.
#include <stdio.h> int main() { printf("Enter your grade: "); char Grade; scanf("%c",&Grade); switch( Grade ) { case 'A' : case 'a' : printf( "Excellent\n" ); break; case 'B' : case 'b' : printf( "Good\n" ); break; case 'C' : case 'c' : printf( "OK\n" ); break; case 'D' : case 'd' : printf( "Mmmmm....\n" ); break; case 'F' : case 'f' : printf( "You must do better than this\n" ); break; default : printf( "What is your grade anyway?\n" ); break; } return 0; }
مثال : با استفاده از ساختار switch case یک ماشین حساب ساده بسازید.
# include <stdio.h> int main() { char operator; double firstNumber,secondNumber; printf("Enter first operands: "); scanf("%lf",&firstNumber); printf("Enter an operator (+, -, *,): "); scanf("%s", &operator); printf("Enter second operands: "); scanf("%lf",&secondNumber); switch(operator) { case '+': printf("%.1lf + %.1lf = %.1lf\n",firstNumber, secondNumber, firstNumber+secondNumber); break; case '-': printf("%.1lf - %.1lf = %.1lf\n",firstNumber, secondNumber, firstNumber-secondNumber); break; case '*': printf("%.1lf * %.1lf = %.1lf\n",firstNumber, secondNumber, firstNumber*secondNumber); break; case '/': printf("%.1lf / %.1lf = %.1lf\n",firstNumber, secondNumber, firstNumber/firstNumber); break; // operator is doesn't match any case constant (+, -, *, /) default: printf("Error! operator is not correct\n"); } return 0; }
تمرین : با استفاده از while برنامه بالا را طوری تغییر دهید که پس از محاسبه عملیات، از برنامه خارج نشود و بتوان چندین عملیات را با یک بار اجرای برنامه انجام داد. سپس با فشردن دکمه خاصی از برنامه خارج شود.
از تابع qsort برای مرتب کردن آرایه استفاده میشود. هر عنصر آرایه یک مقداری دارد که این تابع با مقایسه کردن آنها ، آرایه را مرتب میکند.
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
توجه داشته باشید که این تابع مقداری را برنمیگرداند. فقط کافی است آرایه را به داخل تابع پاس بدهیم و بعد از صدا کردن تابع، میبینیم که آرایه مرتب شده است. ابتدا به آرگومان های این تابع دقت کنید.
به مثال زیر دقت کنید.
#include <stdio.h> #include <stdlib.h> int values[] = {12, 3, 0, 7, 25, 8, 9, 7, 26, 10 }; int cmpfunc (const void * a, const void * b) { return ( *(int*)a - *(int*)b ); } int main() { int n; printf("Before sorting the list is: \n"); for( n = 0 ; n < 10; n++ ) { printf("%d ", values[n]); } qsort(values, 10, sizeof(int), cmpfunc); printf("\nAfter sorting the list is: \n"); for( n = 0 ; n < 10; n++ ) { printf("%d ", values[n]); } printf("\n"); return(0); }
و خروجی بصورت زیر خواهد بود:
[root@CentOS6 c]# gcc -o qsort qsort.c [root@CentOS6 c]# ./qsort Before sorting the list is: 12 3 0 7 25 8 9 7 26 10 After sorting the list is: 0 3 7 7 8 9 10 12 25 26 [root@CentOS6 c]#
در این جلسه درمورد مبحث مهم socket صحبت خواهیم کرد. معمولا در داخل یک سیستم یا بهتر بگوییم در داخل یک localhost، برای ارتباط میان 2 تا پروسه ( process )، سه تکنیک وجود دارد.
میتوان دسته بندی بیشتری هم کرد ولی موارد بالا، از همه کلاسیک تر و عام تر هستند. اما در شبکه چطور 2 تا پروسه با هم ارتباط برقرار میکنند؟؟ مثلا وقتی شما از کامپیوتر خود به یک وب سایت متصل میشوید، پروسه در حال اجرا درون کامپیوتر شما web browser و پروسه در حال اجرا در سرور مقصد، web server است. این دو پروسه از طریق یک تکنیک به نام Socket با یکدیگر ارتباط برقرار میکنند.
بنا بر تعریف، سوکت نقطه نهایی ارتباط بین ئو سیستم در یک شبکه است. یکم دقیق تر، سوکت به مجموع IP و Port درون یک سیستم گفته میشود. بنابراین در هر طرف ارتباط، یک سوکت وجود دارد که با سوکت دستگاه طرف مقابل ارتباط برقرار میکند.بطور کلی دو دسته ارتباط در بستر شبکه داریم:
احتمالا میدونید که مدل OSI بیشتر جنبه آکادمیک و آموزشی دارد و فقط در کتاب های مرجع زیارتش میکنید و در عمل مدل TCP/IP زیرساخت ارتباطات شبکه ها است. سوکت را میتوان با بسیاری از زبان های برنامه نویسی تا اونجایی که من میدونم نوشت، مثل ++c و java و ... . اما در اینجا ما نوشتن یک سوکت خیلی ساده به زبان c را تمرین میکنیم.
پس اگر تا اینجا مطلب را دنبال کرده باشید میدانید که برای برقراری ارتباط، به دو طرف حداقل نیاز است که یکی سرویس دهنده ( سرور ) و یکی سرویس گیرنده ( کاربر ) باشد. یک فایل خالی جدید به نام server.c ایجاد کرده و کد های زیر را درون آن وارد کنید.
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <time.h> int main(int argc, char *argv[]) { /*Variable*/ int listenfd = 0, connfd = 0; struct sockaddr_in serv_addr; char sendBuff[1025]; time_t ticks; /*Creat Socket*/ listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, '0', sizeof(serv_addr)); memset(sendBuff, '0', sizeof(sendBuff)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(5000); /*Call Bind*/ bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); /*Listen*/ listen(listenfd, 10); /*Accept*/ while(1) { connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); ticks = time(NULL); snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks)); write(connfd, sendBuff, strlen(sendBuff)); close(connfd); sleep(1); } }
کد بالا چکار میکند؟
حال یک فایل خالی دیگر با اسم client.c ایجاد کرده و کد های زیر را درون آن وارد کنید.
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sockfd = 0, n = 0; char recvBuff[1024]; struct sockaddr_in serv_addr; if(argc != 2) { printf("\n Usage: %s <ip of server> \n",argv[0]); return 1; } memset(recvBuff, '0',sizeof(recvBuff)); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Error : Could not create socket \n"); return 1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(5000); if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0) { printf("\n inet_pton error occured\n"); return 1; } if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\n Error : Connect Failed \n"); return 1; } while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) { recvBuff[n] = 0; if(fputs(recvBuff, stdout) == EOF) { printf("\n Error : Fputs error\n"); } } if(n < 0) { printf("\n Read error \n"); } return 0; }
حالا این کد چه میکند ؟
برای اجرای برنامه 2 تا ترمینال باز کنید. در یکی server.c را کامپایل و اجرا کرده و در دیگری client.c را کامپایل و اجرا کنید. به ابنصورت که هر دو کد را با gcc کامپایل کنید. سپس در ترمینالی که کد سرور را کامپایل کردید دستور server. را زده، خواهید دید که ترمینال را اشغال میکند و دستور دیگری نمیتوانید وارد کنید. سپس در ترمینالی که کد کاربر را کامپایل کردید دستور client. را با آرگومان IP سرور وارد کنید.
در ترمینال سرور:
[root@CentOS6 c]# gcc -o server server.c [root@CentOS6 c]# ./server
در ترمینال کاربر:
[root@CentOS6 c]# ./client 127.0.0.1 Sat Aug 13 19:37:52 2016 [root@CentOS6 c]#
لیست پیوندی یکی از مباحث مهم برنامه نویسی است. تو درسی مثل ساختمان داده یادمه خیلی ازش صحبت میشد . در این جلسه میخوایم خیلی ساده و روان لیست پیوندی رو توی C یاد بگیریم. اولین تعریفی که برای لیست پیوندی وجود داره اینه که لیست پیوندی یک " ساختمان داده پویا "ست و میتوان برای هر عنصر فاکتور های مختلفی را ذخیره کرد. حال شاید این سوال بوجود آید که فرق آرایه با لیست پیوندی چیست؟
حالا چه موقع باید تصمیم بگیریم که از لیست پیوندی استفاده کنیم ؟ بیشتر موقعی از لیست پیوندی استفاده میشود که شما نمیدانید قرار است چند عنصر داشته باشید ( چند تا داتشجو مثلا؟؟ )
خانه های لیست پیوندی چطور در حافظه ذخیره میشوند ؟
معمولا اطلاعات هر عنصر لیست پیوندی در بلاک های تصادفی از حافظه دخیره میشوند. پس چطور بهم متصل میشوند؟ با استفاده از اشاره گر ها. ساختر کلی یک لیست پیوندی ساده (پایه) بصورت زیر است.
struct test_struct { int value; struct test_struct *next; };
این structure از یک مقدار value و یک اشاره گر ساخته شده است. نوع value هرچیزی میتواند باشد. اشاره گر next هم به خانه بعدی لیست پیوندی اشاره میکند. پس با این اشاره گر ها میتوان لیست پیوندی را پیمایش کرد و از یکی یه دیگری رفت. به شکل زیر دقت کنید. این یک "لیست پیوندی ساده یک طرفه" است.
چطور یک نود لیست پیوندی ایجاد میشود؟
یک نود لیست پیوندی با اختصاص دادن قطعه ای از حافظه به ساختار لیست پیوندی ایجاد میشود. به کد زیر دقت کنید. با این کد یک لیست پیوندی جدید ایجاد شده و آدرس آن در اشاره گر لیست پیوندی قبلی ذخیره میشود.
struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct));
تابع malloc قطعه ای از حافظه را به مقدار یا ساختاری که به عنوان آرگومان به او دادیم را اختصاص میدهد. در اینجا گفتیم به اندازه ( size of ) ساختار test_struct قطعه ای از حافظه را جدا کن. فقط باید دقت داشته باشید که malloc یک اشاره گر void برمیگرداند.
پس خروجی آن را باید cast کنیم. عبارت داخل پرانتز قبل از malloc این کار را میکند. خروجی malloc را به یک اشاره گر از جنس struct تبدیل میکند که بتوانیم آنرا داخل ptr بریزیم. بعد از اینکه نود بوجود آمد، میتوان مقادیری را به آن اختصاص داد تا نگه دارد. اگر هم آخرین نود ما باشد به اشاره گر خانه بعدی آن مقدار null را اختصاص میدهیم. کد زیر:
ptr->value = my_value; ptr->next = NULL;
چطور یک لیست پیوندی را پیمایش کنیم؟
پیمایش و جستجو در لیست پیوندی یعتی دنبال یک محتوای خاص در لیست پیوندی باشیم. روش کار خیلی ساده است. از خانه اول شروع کرده و دونه دونه جلو میرویم و مقدار مورد نظر را با مقدار ذخیره شده در لیست ها مطابقت میدهیم. این کار را تا موقعی که به انتهای لیست نرسیده ایم ادامه میدهیم. به کد زیر دقت کنید.
while(ptr != NULL) { if(ptr->val == val) { found = true; break; } else { ptr = ptr->next; } }
یک نود چطور در لیست پیوندی پاک میشود؟
وقتی تصمیم گرفته شد که یک نود پاک شود، با فراخوانی تابع free بر روی اشاره گری که به آدرس آن اشاره میکند، آنرا از بین میبریم. روش پاک کردن نود اول و نود آخر با روش پاک کردن نود از وسط لیست متفاوت است. اگر بخواهیم نودی را از وسط لیست پاک کنیم، باید اشاره گر نود قبل از آن را، به نود بعد از نودی که میخوایم پاکش کنیم، وصل کنیم تا لیست پاره نشود. کد زیر یک مثال جامع برای اضافه کردن نود، جستجوی نود و حذف نود است.
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> struct test_struct { int val; struct test_struct *next; }; struct test_struct *head = NULL; struct test_struct *curr = NULL; struct test_struct* create_list(int val) { printf("\n creating list with headnode as [%d]\n",val); struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct)); if(NULL == ptr) { printf("\n Node creation failed \n"); return NULL; } ptr->val = val; ptr->next = NULL; head = curr = ptr; return ptr; } struct test_struct* add_to_list(int val, bool add_to_end) { if(NULL == head) { return (create_list(val)); } if(add_to_end) printf("\n Adding node to end of list with value [%d]\n",val); else printf("\n Adding node to beginning of list with value [%d]\n",val); struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct)); if(NULL == ptr) { printf("\n Node creation failed \n"); return NULL; } ptr->val = val; ptr->next = NULL; if(add_to_end) { curr->next = ptr; curr = ptr; } else { ptr->next = head; head = ptr; } return ptr; } struct test_struct* search_in_list(int val, struct test_struct **prev) { struct test_struct *ptr = head; struct test_struct *tmp = NULL; bool found = false; printf("\n Searching the list for value [%d] \n",val); while(ptr != NULL) { if(ptr->val == val) { found = true; break; } else { tmp = ptr; ptr = ptr->next; } } if(true == found) { if(prev) *prev = tmp; return ptr; } else { return NULL; } } int delete_from_list(int val) { struct test_struct *prev = NULL; struct test_struct *del = NULL; printf("\n Deleting value [%d] from list\n",val); del = search_in_list(val,&prev); if(del == NULL) { return -1; } else { if(prev != NULL) prev->next = del->next; if(del == curr) { curr = prev; } else if(del == head) { head = del->next; } } free(del); del = NULL; return 0; } void print_list(void) { struct test_struct *ptr = head; printf("\n -------Printing list Start------- \n"); while(ptr != NULL) { printf("\n [%d] \n",ptr->val); ptr = ptr->next; } printf("\n -------Printing list End------- \n"); return; } int main(void) { int i = 0, ret = 0; struct test_struct *ptr = NULL; print_list(); for(i = 5; i<10; i++) add_to_list(i,true); print_list(); for(i = 4; i>0; i--) add_to_list(i,false); print_list(); for(i = 1; i<10; i += 4) { ptr = search_in_list(i, NULL); if(NULL == ptr) { printf("\n Search [val = %d] failed, no such element found\n",i); } else { printf("\n Search passed [val = %d]\n",ptr->val); } print_list(); ret = delete_from_list(i); if(ret != 0) { printf("\n delete [val = %d] failed, no such element found\n",i); } else { printf("\n delete [val = %d] passed \n",i); } print_list(); } return 0; }
دقت داشته باشید که :
خروجی :
-------Printing list Start------- -------Printing list End------- creating list with headnode as [5] Adding node to end of list with value [6] Adding node to end of list with value [7] Adding node to end of list with value [8] Adding node to end of list with value [9] -------Printing list Start------- [5] [6] [7] [8] [9] -------Printing list End------- Adding node to beginning of list with value [4] Adding node to beginning of list with value [3] Adding node to beginning of list with value [2] Adding node to beginning of list with value [1] -------Printing list Start------- [1] [2] [3] [4] [5] [6] [7] [8] [9] -------Printing list End------- Searching the list for value [1] Search passed [val = 1] -------Printing list Start------- [1] [2] [3] [4] [5] [6] [7] [8] [9] -------Printing list End------- Deleting value [1] from list Searching the list for value [1] delete [val = 1] passed -------Printing list Start------- [2] [3] [4] [5] [6] [7] [8] [9] -------Printing list End------- Searching the list for value [5] Search passed [val = 5] -------Printing list Start------- [2] [3] [4] [5] [6] [7] [8] [9] -------Printing list End------- Deleting value [5] from list Searching the list for value [5] delete [val = 5] passed -------Printing list Start------- [2] [3] [4] [6] [7] [8] [9] -------Printing list End------- Searching the list for value [9] Search passed [val = 9] -------Printing list Start------- [2] [3] [4] [6] [7] [8] [9] -------Printing list End------- Deleting value [9] from list Searching the list for value [9] delete [val = 9] passed -------Printing list Start------- [2] [3] [4] [6] [7] [8] -------Printing list End-------
کارشناس فناوری اطلاعات - Linux Systems Administrator
زمان پاسخ گویی روز های شنبه الی چهارشنبه ساعت 9 الی 18
فقط به موضوعات مربوط به محصولات آموزشی و فروش پاسخ داده می شود