زبان برنامه نویسی سی/stdio
با این فایل سرآیند تا حدودی آشنایی دارید و در طول کتاب بارها به آن اشاره شده است . stdio.h یک فایل سرآیند ( Header ) استاندارد زبان سی می باشد که مخفف Standard Input/Output یا Standard Input & Output است به معنی ورودیها و خروجیهای استاندارد . ما از تابع printf و scanf به وفور در کتاب استفاده نمودهایم که در فایل سرآیند stdio تعریف شدهاند اما پیش از نوشتن توابع بهتر است با تعیینکنندههای فرمت در این توابع استاندارد آشنا شوید
حتماً به یاد دارید که در کتاب وقتی می نوشتیم ;printf("This is a %s", str) و اگر str یک رشته می بود که داخل آن وارد می کردیم string در خروجی خطدستوری با جمله This is a string مواجه می شدیم . تعیین کنندههای فرمت ، متغیرهایی هستند که داخل دو دابل کوت نوشته می شوند تا بعداً توسط آرگومانهای فرستاده شده ( پاس داده شده ) جایگزین گردند
%[parameter][flags][width][.precision][length]type
ابتدا پارامتر تعیین کننده نوشته می شود ، سپس پرچمها و سپس عرض و پهنا و سپس میزان دقت و بعد طول و در نهایت نوع داده آرگومان
انواع داده آرگومان عبارتند از :
d, i
که اعداد دهدهی هستند با این تفاوت که در تابع scanf تعیین کننده نوع i اگر در ابتدای آرگومان نوشته شود 0x آن را هگزادسیمال ( شانزدهشانزدهی ) دریافت می کند و اگر در ابتدای آرگومان نوشته شود 0 آن را اوکتال ( هشتهشتی ) دریافت خواهد نمود
u
که اعداد دهدهی بدون علامت را تعیین می کند unsigned decimal که همان unsigned int می باشد
ld , li
که اعداد صحیح بلند را تعیین می کند long int
lld , lli
که اعداد صحیح خیلی بلند را تعیین می کند long long int
lu
که اعداد صحیح بلند بدون علامت را تعیین می کند unsigned long
llu
که اعداد صحیح خیلی بلند بدون علامت را تعیین می کند unsigned long long int
f, F
برای اعداد اعشاری با این تفاوت که f برای بینهایت inf یا infininty و برای تعریفنشدهها nan را باز می گرداند و F برای بینهایت INF یا INFINITY و برای تعریفنشدهها NAN را باز می گرداند
lf
که اعداد اعشاری با دقت دو برابر را تعیین می کند double
Lf
برای تعیین اعداد اعشاری با دقت بسیار زیاد به کار می رود long double
e, E
برای اعداد اعشاری به صورت توان برای ۱۰ که به صورت d.ddde±dd مقدار را می فرستد یا دریافت می کند که شامل مقدار صحیح است و یک نقطه و مقادیر اعشاری و علامت E برای E و e برای e و سپس نما و توان که دست کم دو رقم هستند . دقت کنید که در ویندوز ارقام توان به صورت پیش فرض سه تا هستند . مثال : 1.5e03 یعنی ۱٫۵ ضربدر ۱۰ به توان ۳
g, G
برای اعداد که اگر کامل ( صحیح ) باشند شامل نقطه و ممیز نخواهد شد و اگر اعشاری باشند ممکن است آن را به صورت معمولی بنویسد یا به صورت توان ۱۰ که همان e و E می باشند که البته می تواند دریافت نیز بکند ( مثلاً در تابع scanf ) و خود کامپایلر تشخیص می دهد که کدام بهتر است و با این تفاوت که پشت سر نما 0 نمی گذارد . مثال : 21.965 یا 2.5e4
x, X
برای اعداد هگزادسیمال که صحیح بدون علامت هستند که x برای حروف کوچک است مثل x5f و X برای حروف بزرگ مثل X5F
o
برای اعداد اوکتال که صحیح بدون علامت هستند
s
برای رشتههایی که با NULL پایان می یابند ( اگر به یاد داشته باشید ، رشته ، مجموعهای از کاراکترهاست ) مثل رشتههایی که با آرایه می سازیم و ذخیره می نمائیم یعنی رشتهای مثل : monkeys are cute
c
برای یک کاراکتر که به خروجی فرستاده می شود یا از ورودی دریافت می شود
p
برای اشارهگرها که آدرسشان به رابط خطدستوری فرستاده شود یا از آنجا دریافت شود . طبق استاندارد می تواند و باید از نوع پوچ باشد void ولی این مورد یعنی برای p وابسته به نویسنده پیادهساز می باشد و هر کامپایلر به دلخواه خود عمل می کند ( مراجعه کنید به راهنمای کامپایلر خود )
a, A
برای اعداد اعشاری با دقت دو برابر ( double ) که در مبنای شانزده ( هگزادسیمال ) نوشته می شود و 0x و بقیه ارقام و حروف ( کوچک ) برای a و 0X و بقیه ارقام و حروف ( بزرگ ) برای A ( دقت کنید که این استاندارد است ولی هر کامپایلر به دلخواه خود قابلیتهای آن را ممکن است افزایش داده باشد )
n
مقداری را در خروجی خطدستوری چاپ نمی کند اما به آرگومان اشارهگر متناظر خود ، تعداد کاراکترهای داخل تابع خود را باز می گرداند
همچنین با استفاده از ماکروی wchar_t که در فایل سرآیند wchar.h تعریف شده است ما می توانیم کاراکترهای اضافهتر از ازکی را که در UTF-8 و UTF-16 و UTF-32 تعریف شدهاند در تابع printf یا scanf و بقیه توابع این فایل سرآیند به عنوان تعیین کننده نوع آرگومان تعیین کنیم تا کاراکترهای مذکور را در رابط خطدستوری چاپ نمائیم یا دریافت کنیم که این تعیین کنندهها شامل :
lc or C
lc و یا C ( بزرگ ) برای یک کاراکتر UTF و
ls or S
ls یا S ( بزرگ ) برای یک رشته انکدینگ UTF
می شود . اما کاراکترهای UTF-8 را می توان در متن منبع نیز نوشت و آن را در خروجی خطدستوری چاپ نمود . مثال :
#include <stdio.h>
int main(void)
{
printf("ญ");
return 0;
}
که پس از کامپایل و اجرا ، در خروجی کاراکتر ญ از حروف زبان تایلندی را مشاهده خواهید نمود . اما برای کار با حروف یونیکد Unicode مثل UTF-8 باید از توابع و ماکروهای فایل سرآیند wchar.h استفاده نمائید که در مبحث مربوطه نوشته خواهد شد
zu
برای تعیین نوع فرمت داده از نوع size_t که هنوز به آن نرسیدهایم و در ادامه خواهم نوشت اما فقط برای استانداردهای جدید تعریف شده است و در استانداردهای قدیمی مثل C89 همان ul% را می نویسیم
پارامترِ تعیین کننده شماره آرگومان ، جزء استاندارد سی نیست و فقط در پازیکس ( POSIX رابط کاربری قابل حمل برای سیستم عاملهای محیطهای پنجرهای ایکس ) تعریف شده است که یک عدد را می نویسیم و در مقابل آن یک علامت دلار یا همان پسو $ را می گذاریم . با این کار حتی می توانیم کاری کنیم تا یک آرگومان چند بار چاپ یا دریافت شود و البته اگر از آن استفاده کنیم باید در مورد تمام تعیین کنندههای آرگومانهای دیگر نیز استفاده کنیم . مثال :
#include <stdio.h>
int main()
{
int a = 15;
printf("%1$d %1$d", a);
return 0;
}
در اینجا printf دو بار a را چاپ می کند و در مثال زیر :
#include <stdio.h>
int main()
{
int a = 15, b = 17;
printf("%2$d %1$d", a, b);
return 0;
}
با اینکه ابتدا آرگومان a و سپس b فرستاده شدهاند ، اما چون شماره تعیین اول 2 و شماره تعیین دوم 1 می باشد ابتدا b و سپس a چاپ می شود
در صورتی که بخواهید علامت درصد % را در خروجی خط دستوری چاپ کنید باید دو بار آن را بنویسید مثل %%
و پرچمها عبارتند از :
-
این پرچم متن خروجی خطدستوری را در تعیینکننده فرمت خود چپچین می کند . به صورت پیشفرض تمام متون چپچین هستند اما اگر پیش از تعیین کننده نوع آرگومان عددی را بنویسید ( که کمی جلوتر آن را خواهیم نوشت ) به همان اندازه خروجی دارای فضای سفید ( space ) خواهد بود که آرگومان را جلو می برد اما اگر در تعیینکننده فرمت یک علامت منفی ( مایناس - ) بگذارید آن را به سمت چپ انتقال خواهد داد
+
این پرچم ، تضمین می کند که علامت عدد در خروجی خطدستوری نوشته خواهد شد یعنی - برای اعداد منفی ( negative ) و + برای اعداد مثبت ( positive ) که به صورت پیشفرض فقط برای اعداد منفی نوشته می شود و برای اعداد مثبت علامتی گذاشته نمی شود
فضای سپید یا همان اسپیس
این پرچم یعنی space که بازگردان آن می شود فضای سپید یا فضای خالی برای اعداد مثبت جای خالی می گذارد و برای اعداد منفی علامت منفی ( مایناس - ) که اگر همراه با علامت مثبت ( + ) نوشته شود فضای سپید نادیده گرفته می شود
0
عدد صفر به انگلیسی 0 ( زیرو ) هنگامی که برای آرگومان پهنا و عرضی در نظر گرفته شود ( که در ادامه می نویسیم ) که یک عدد است به جای فضای خالی پشت عدد ، صفر می گذارد که البته بستگی به تعداد ارقام عدد نیز دارد ؛ یعنی اگر عدد 352 باشد و ما پهنای آن را ۸ تا تعیین کرده باشیم ۵ تا فضای خالی قرار می گیرد ؛ اگر از این پرچم استفاده کنیم در خروجی خطدستوری چاپ می شود : 00000352
پرچم هاش یا شارپ چندین کار را می تواند انجام بدهد :
۱ − برای تعیینکنندههای فرمت g و G در صورتی که قسمت اعشار نداشته باشند 0 های مقابل آن را ( در قسمت اعشاری ) حذف نمی کند ۲ − برای فرمتکنندههای a و A و e و E و f و F و g و G یک علامت اعشار ( ممیز که در انگلیسی نقطه است ) را حتماً اضافه می کند تا مشخص شود عدد اعشاری است حتی در صورتی که قسمت اعشار 0 باشد ۳ − برای تعیینکنندههای فرمت اوکتال o و هگزادسیمال کوچک x و هگزادسیمال بزرگ X برای هر عددی جز 0 قبل از آن علائم 0 و 0x و 0X را به ترتیب می نویسد و پیش از مقدار چاپ شده در خروجی خطدستوری قرار می دهد ۴ − برای تعیینکنندههای فرمت ls یا S باعث می شود تا سایز متن به تعداد کاراکترهای خروجی باشد و نه به اندازه ( size ) کاراکترهای موجود که ممکن است چند بایتی و عریض باشند که در UFT-8 یا uTF-16 یا UTF-32 تعریف شدهاند ۵ − برای تعیینکنندههای فرمت p که برای چاپ آدرس اشارهگر است آن را به مقدار هگزادسیمال تبدیل می کند که قابل برگشت نیست ( یعنی عکس همین عمل ) مگر اینکه در محیطهای ترا اسپیس ( teraspace ) که شامل برخی سیستم عاملها نظیر لینوکس می شود با دستور مستقیم به کامپایلر آن را مهیا نمائیم
'
آپوستروف با علامت ' ( که همان سینگل کوت می باشد ) باعث می شود تا هر سه رفم با یک ممیز ( علامت نقطه در انگلیسی . ) از یکدیگر جدا شوند که البته برای این کار باید فایل سرآیند locale.h را به برنامه خود ضمیمه نمائید مثل :
#include <stdio.h>
#include <locale.h>
int main()
{
setlocale(LC_NUMERIC, "");
int a = 1564952413;
printf("%'d", a);
return 0;
}
که در اینجا برنامه چاپ می کند : 1.564.952.413
مطالب پرچمها کاملاً روشن هستند و نیازی به مثال ندارد اما به عنوان نمونه یک مثال برای پرچم مایناس ( علامت منفی ) می نویسیم :
#include <stdio.h>
int main()
{
int a = 47;
printf("%-6d", a);
return 0;
}
در این مثال اگر علامت منفی را بر داریم عدد 47 با ۶ فاصله که نوشتهایم 6 چاپ می شود اما چون علامت منفی گذاشتهایم چپچین شده و در ابتدای رابط خطدستوری قرار می گیرد
عرض و پهنا
در عرض و پهنا ، پس از علامت درصد یک شماره می نویسیم که ورودی یا خروجی را به جلو می راند و به هیچ وجه باعث از بین رفتن مقدار نمی شود . این کار باعث می شود تا بتوانیم در رابط خطدستوری شکل و جدول خاصی را ایجاد کنیم . به علاوه اگر پس از علامت درصد علامت استریسک ( ستاره ) را بنویسیم بتوانیم به صورت پویا ، مقداری را به عنوان آرگومان بنویسیم تا به همان اندازه کاراکترهای ما به جلو رانده شوند . مثال عدد را در مثال پیشین نوشتیم ، اما برای مثال استریسک :
#include <stdio.h>
int main()
{
int a = 8, b = 94;
printf("%*d", a, b);
return 0;
}
در مثال بالا مقدار a مقدار ۸ می باشد و b مقدار ۹۴ که مقدار 94 را در خروجی خطدستوری چاپ نمودهایم اما به مقدار a یعنی ۸ تا فاصله و فضای سپید و خالی پیش از آن قرار دادهایم که عدد به جلو رانده می شود
دقت کنید :
اگر در قسمت تعیینکننده فرمت یک نقطه بگذارید و پس از آن یک عدد بنویسید ، برای اعداد اعشاری دقت اعشار همان عدد است و مابقی گرد شده و نادیده گرفته می شوند . برای رشتهها نیز تعداد کاراکترها را معین می کند که اگر از عدد نوشته شده بیشتر باشد ، کاراکترهای بعدی نادیده گرفته می شوند . همچنین می توانید بعد از علامت نقطه ، یک استریسک بگذارید و در قسمت آرگومانها مقدار مورد نظر را به تابع بفرستید تا دقت را معین کنید . مثال :
#include <stdio.h>
int main()
{
char ch[] = "whatthehell";
printf("%.3s", ch);
return 0;
}
مثال :
#include <stdio.h>
int main()
{
printf("%.*s", 3, "abcdef");
return 0;
}
در مثال اول رشته whatthehell فقط سه کاراکتر اولش چاپ می شود و در مثال دوم فقط abc . برای اعداد اعشاری نیز یک مثال می نویسیم :
#include <stdio.h>
int main()
{
float f = 2.564;
printf("%.2f", f);
return 0;
}
در مثال بالا مقدار ۲٫۵۶۴ عدد ۴ ـش نادیده گرفته می شود ( چون کوچکتر از ۵ است اگر گرد شود می شود 0 که فاقد مقداری در ارزش مکانی است و نادیده گرفته می شود )
تعیین کنندههای طول :
hh
برای اعداد صحیح که به صورت کاراکتر تعریف شدهاند و دیگر نیازی به نقش ایفا کردن و کستکردن ندارد . مثال :
#include <stdio.h>
int main()
{
char a = 12;
printf("%hhd", a);
return 0;
}
در مثال بالا a یک کاراکتر است ، اما ما تعیین کننده نوع آرگومان را d و decimal تعریف کردهایم . اما با این حال ( به خاطر استفاده از hh ) برنامه بدون هیچ اشکالی مقدار 12 را در خروجی خطدستوری نمایش می دهد ( دقت کنید که حجم یک کاراکتر ۱ بایت است و تنها می تواند تا مقدار ۱۲۷ و یا بدون علامت تا ۲۵۵ را در خود ذخیره کند )
h
برای اعداد صحیح در مبنای ۱۰ و یا اوکتال و یا هگزادسیمال که به صورت short ( کوتاه ) تعریف شدهاند ( دقت کنید که نوع داده کوتاه یک یا نهایتاً دو بایت است و مقادیر بزرگتر از ظیف خود باعث ایجاد خطا می شود )
مثال :
#include <stdio.h>
int main()
{
unsigned short a = 300;
printf("%hd", a);
return 0;
}
در مثال بالا a یک کوتاه بدون علامت است که با تعیین کننده نوع صحیح و پیشوند h به خروجی خطدستوری ارسال می شود
l
برای نوع اعداد صحیح بلند ؛ البته شما می توانید اعداد اوکتال یا هگزادسیمال را نیز با طول بلند و تعیینکننده فرمت صحیح به مبنای ده برگردانده و در رابط خطدستوری از آن استفاده کنید . مثال :
#include <stdio.h>
int main()
{
long a = 0xff;
printf("%ld", a);
return 0;
}
در مثال بالا نوع داده a بلند است و در آن ff را به شکلی که نوشته شده ( و باید بشود ) ذخیره نمودهایم . در هنگام چاپ با مقدار ۲۵۵ رو به رو خواهیم شد ( دقت کنید که ff در مبنای ۱۰ می شود ۲۵۶ ولی چون ۰ نیز یک مقدار است می شود ۲۵۵ )
ll
برای تعیین طول داده صحیح خیلی بلند
L
برای تعیین طول داده اعشاری خیلی بلند که می شود Lf یا LF یا Lg یا LG
z
برای تعیین طول داده از نوع size_t که در فایل سرآیند stfdef تعریف شده است
j
برای تعیین طول داده از نوع intmax_t که در فایل سرآیند inttypes تعریف شده است
t
برای تعیین طول داده از نوع ptrdiff_t که در قایل سرآیند stddef تعریف شده است
H
به عنوان پیشوند برای A و a و E و e و f و F و G و g که قسمت اعشاری را ۳۲ بیت در نظر می گیرد Decimal32_
D
به عنوان پیشوند برای A و a و E و e و f و F و G و g که قسمت اعشاری را ۶۴ بیت در نظر می گیرد Decimal64_
DD
به عنوان پیشوند برای A و a و E و e و f و F و G و g که قسمت اعشاری را ۱۲۸ بیت در نظر می گیرد Decimal128_
برخی از تعیین کنندههای طول آرگومان در استاندراد باید خارج از دو دابل کوت نوشته شوند و البته باید فایل سرآیند inttypes.h را ضمیمه برنامه خود نمائید
PRId32 PRId64 PRIi32 PRIi64 PRIu32 PRIu64 PRIx32 PRIx64
اولی برای اعداد صحیح ۳۲ بیت و دومی برای صحیح ۶۴ بیت و بعدی برای اعداد صحیح ( منفی و مثبت ) ۳۲ بیت و بعدی مانند قبلی ولی ۶۴ بیت ، بعدی برای اعداد صحیح بدون علامت ۳۲ بیتی و بعدی همان برای ۶۴ بیتی و بعدی برای اعداد هگزادسیمال ۳۲ بیت و بعدی همان برای ۶۴ بیتی . مثال :
#include <stdio.h>
#include <inttypes.h>
int main()
{
int a = 45;
printf("%" PRId64 "\n", a);
return 0;
}
در مثال بالا متغیر از نوع صحیح a با مقدار ۴۵ تعریف شده است و تابع کتاخانهای printf از PRId64 برای تعیینکننده فرمت و تعیین کننده نوع آرگومان استفاده شده و آرگومان a به آن فرستاده شده است که پس از کامپایل و اجرا در خروجی عدد 45 را نمایش می دهد
تابع printf
ویرایشنمای کلی تابع کتابخانهای printf و تابعهای مشابه آن بدین شکل است :
printf(const char *format-string, argument-list);
تابع printf مخفف print formatted و به معنی پرینت فرمت شده می باشد که یک یا چند رشته را دریافت می نماید که داخل آنها تعیینکنندههای فرمت و تعیین کنندههای مختلف می توانند همان طور که تا بدینجا نوشته شد آن را آماده پذیرش آرگومان یا آرگومانها نموده که آرگومان(ها) را به آن می فرستیم ( که حتی می توانند مقدار باشند ) تا جایگزین تعیینکنندههای فرمت شود( شوند )
اگر تعداد آرگومانهای فرستاده شده از تعیینکنندههای فرمت بیشتر باشند ، آنهایی که اضافه هستند نادیده گرفته می شوند اما اگر تعیینکنندههای فرمت بیشتر از آرگومانهای فرستاده شده باشند ، نتیجه تعریف نشده است ( مطابق با استاندارد سی ) . اگر تابع printf با موفقیت بتواند به اتمام برسد یک مقدار مثبت را که تعداد کاراکترهای دریافت شده می باشد را باز می گرداند ( دقت کنید که حتی اعداد و دنبالههای فرار نیز کاراکتر حساب می شوند ) ولی اگر ناموفق باشد یک مقدار منفی را باز می گرداند . مثال :
#include <stdio.h>
int main()
{
printf("%d", printf("This is a string\n"));
return 0;
}
مثال بالا ابتدا printf داخلی را ارزیابی کرده که یک رشته است و آن را چاپ می کند و سپس بیرونی و اولی را که ۱۷ کاراکتر در رشته ما ( به همراه دنباله فرار خط شکسته ) وجود دارد و آن را در رابط خطدستوری نمایش می دهد
دقت کنید :
همان طور که گفته شد اگر تعداد تعیینکنندههای فرمت بیشتر از آرگومانها باشد ، نتیجه تعریفنشده است ( در استاندارد سی ) ولی کامپایلر قدرتمندی همچون GCC به شما هشدار خواهد داد ؛ اما برنامه کامپایل شده و اجرا می شود ولی دارای رخنهپذیری امنیتی خواهد بود که هکرها میتوانند از روش حمله رشته فرمت شده استفاده کرده و به دادههای برنامه شما و سیستم عامل دسترسی پیدا کنند . جهت اطلاعات بیشتر صفحه زیر را مطالعه کنید :
همچنین نوشتن تعیینکننده فرمت n به صورت n% که در همین مبحث نوشته شد می تواند باعث حمله رشته فرمت شده بشود . که باید بسیار مراقب استفاده آن به صورت صحیح باشید . از طرفی این تعیینکننده فرمت باعث ایجاد کامل شدن تورینگ می شود . جهت اطلاعات بیشتر به صفحه زیر مراجعه کنید :
تابع scanf
ویرایشنمای کلی تابع کتابخانهای scanf و تابعهای مشابه آن به شکل زیر است :
scanf(const char *format-string, argument-list);
تابع scanf مخفف scan formatted به معنی اسکن فرمت شده قادر است تا از ورودی استاندارد ( معمولاً صفحهکلید Keyboard ) مقادیری را دریافت نموده و در متغیرها و آرگومانهای خود جای بدهد و آنها را مقداردهی نماید . شکل کلی رشته فرمتشده دریافت کنندههایی مثل scanf و تابعهای مشابه آن به شرح زیر است :
%[parameter]*[width][length]type
پارامتر در scanf همانند printf می باشد . مثال :
#include <stdio.h>
int main()
{
int a, b;
scanf("%2$d%1$d", &a, &b);
printf("%d & %d",a , b);
return 0;
}
همان طور که می بینید با نوشتن شماره و علامت دلار ابتدا b دریافت می شود و سپس a ولی در خروجی ابتدا a چاپ می شود و سپس b . دقت کنید که تابع scanf برای دریافت و وارد کردن مقدار داخل متغیر از علامت آدرسدهی استفاده می کند و علت آن نحوه تعریف آن در فایل سرآیند stdio می باشد که با اشارهگرها نوشته می شود
برای اینکه پهنا و عرض یک آرگومان را جهت دریافت تعیین کنید باید بعد از علامت درصد ( % percent ) یک عدد را بنویسید که به همان تعداد کاراکتر ( چه حرفی و چه عددی ) دریافت خواهد شد و نه بیشتر . دقت کنید که علامت ستاره ( استریسک * ) در تابع scanf پهنا را تعیین نمی کند بلکه باعث می شود تا تابع scanf یک یا چند کاراکتر را نادیده گرفته و در آرگومانهای فرستاده شده ذخیره نکند
پس با نوشتن مقدار پس از علامت درضد تعیین می کنیم که تابع ، چند کاراکتر ( عددی یا حرفی ) را دریافت کند . مثال :
#include <stdio.h>
int main(void)
{
int i;
printf("Enter a number :\n");
scanf("%2d", &i);
printf("Your number is : %d\n", i);
return 0;
}
در مثال بالا شما می توانید به عنوان مثال یک عدد ۵ رقمی را وارد کنید ولی فقط ۲ رقم اول آن دریافت می شود
در تابع scanf و تابعهای مشابه آن می توان از scanset ها استفاده نمود . یک جفت کروشه باز و بسته می توانند تعیین کنند که تابع scanf و تابعهای مشابه آن فقط کاراکترهای خاصی را دریافت نمایند و یا کاراکترهایی را از قلم بیاندازند . مثال :
#include <stdio.h>
int main(void)
{
char str[128];
printf("Enter a string: ");
scanf("%[A-Z]s", str);
printf("You entered: %s\n", str);
return 0;
}
در مثال بالا فقط تا زمانی که تابع scanf به کاراکترهای A تا Z برسد آنها را اسکن می کند و در رشته str ذخیره می کند و اگر به کاراکتر دیگری برسد متوقف می شود . دقت کنید که اگر اولین کاراکتر ورودی شما یک حرف کوچک باشد ( به عنوان مثال ) همان جا عمل اسکن متوقف می شود . دقت کنید که تابع scanf توسط اشارهگرها تعریف شده است و توسط عملگر آدرسدهی مقدار و موجودی را دریافت نموده و در آرگومان متناظر ذخیره می کند بنابراین برای رشته که یک اشارهگر است از عملگر آدرسدهی استفاده نمی کنیم ( حتی اگر از کاراکتر آرایهای استفاده کنیم ؛ چون می دانید که آرایهها توسط اشارهگرها تعریف می شوند )
همچنین باید بدانید که اگر داخل جفت کروشه باز و بسته ، پیش از کاراکتر یا کاراکترها از علامت کرات ^ استفاده کنید تا زمانی که تابع scanf به آن نرسیده باشد دادهها را دریافت کرده و در آرگومان متناظر خود ذخیره می کند . مثال :
#include <stdio.h>
int main(void)
{
char str[100];
printf("Enter a string: \n");
scanf("%[^p]s", str);
printf("You entered: %s\n", str);
return 0;
}
در مثال بالا می توانید متن مقابل را وارد کنید : C is more powerful than python ولی با جمله C is more powerful than مواجه خواهید شد چرا که scanf به کاراکتر p رسیده است
ضمناً دقت کنید که در اسکن کردن متونی که دارای فضای سپید ( مثل اسپیس Space ) هستند استفاده از scanset ها ضروری است . چرا که تابع scanf و تابعهای مشابه آن فضای سفید را به صورت پیشفرض دریافت نمی کنند . مثال :
#include <stdio.h>
int main(void)
{
char a[100];
scanf("%[^\n]s", a);
printf("Your text is : %s\n", a);
return 0;
}
در مثال بالا اگر از scanset ـی که نوشته شده [n\^] استفاده نکنید نمی توانید از اسپیس استفاده کنید و با اولین اسپیس رشته شما شکسته می شود و scanf دیگر اسکن نمی کند
نکته : علامت هایفن یا همان خط تیره ( که به اشتباه به آن دش dash نیز می گویند ) برای بیان یک طیف است اما اگر بخواهید خود آن را برای اسکن شدن یا نشدن تعیین کنید هر کامپایلر از روش خاص خود استفاده می کند و باید به راهنمای کامپایلر خود مراجعه کنید
دقت کنید که فضای سپید مثل اسپیس space یا جدول افقی Tab در تابع scanf و تابعهای مشابه آن باعث می شود تا تابع از ورودی بخواند اما آن را در آرگومانی ذخیره نکند . از این تکنیک می توانید در قسمتهایی از کد خود که خط شکسته دارند استفاده کنید ( چون خط شکسته باعث می شود تا scanf و تابعهای مشابه آن ، خط شکسته را به عنوان یک کاراکتر دریافت کرده و دیگر متن شما را دریافت نکنند )
نکته : علامت استریسک باعث می شود تا کاراکتر یا کاراکترهایی نادیده گرفته شوند و در آرگومانها ذخیره نشوند
مثال :
#include <stdio.h>
int main(void)
{
char a, b;
printf("enter a character\n");
scanf("%c", &a);
printf("enter another character\n");
scanf(" %c%*[^\n]",&b);
printf("Your characters were %c and %c\n", a, b);
return 0;
}
در مثال بالا کاراکتر a بدون هیچ مشکلی دریافت می شود اما چون Enter را می زنید و تابع printf نیز خط را می شکند کاراکتر b باید با ترفند دریافت شود که ابتدا یک اسپیس وارد می کنیم و سپس خط شکسته را نادیده گرفته و کاراکتر b را دریافت می نمائیم تا در خروجی خطدستوری نمایش داده شود
مثال :
#include <stdio.h>
int main(void)
{
char a[100], b[100];
printf("Enter a text :\n");
scanf("%[^\n]s", a);
printf("Your first text is : %s\n", a);
printf("Enter second text :\n");
scanf(" %[^\n]s", b);
printf("Yurt second text is : %s\n", b);
return 0;
}
مثال بالا نیز همانند مثال قبل است با این تفاوت که رشتهها را به جای کاراکترها ذخیره می کنیم . در رشته اول scanf تا زمانی که به خط شکسته نرسیده باشد تابع scanf اسکن می کند و در رشته دوم ابتدا یک اسپیس نوشتهایم تا خط شکسته قبلی ذخیره نگردد و خط شکسته را نادیده گرفتهایم
دقت کنید : که تابع scanf باید به تعداد و اندازه تعریف شده مقدار عددی یا حرفی و رشتهای بپذیرد ؛ در غیر این صورت ذخیره ساز میانی ( بافر Buffer ) سرریز می شود که هم باعث اشغال شدن حافظه می گردد و برنامه شما دچار شکست و توقف می شود و هم اینکه یک رخنه پذیری محسوب می شود . مخصوصاً در مورد رشتهها اگر تعداد تعیین شده کمتر از تعداد کاراکترهای وارد شده باشد بافر سرریز می شود که برای این کار ترفندهای مختلفی وجود دارد که سادهترین آن تعیین پهنا و عرض رشته است که کافی نیست ولی برای تعیین آن باید از تابعهای کتابخانهای دیگری استفاده کرد که در مبحث فعلی موجود نیستند و در مباحث فایلهای سرآیند دیگر موجودند . ضمن اینکه اگر به درستی از تابع scanf استفاده نکنید امکان حمله رشته فرمت شده وجود دارد ( همانند printf که در آنجا آدرسش را گذاشتیم یعنی هکرها با وارد کردن کاراکترهایی خاص به حافظه برنامه یا سیستم عامل و سیستم دسترسی پیدا می کنند )
تابع sprintf
ویرایشتابع sprintf سرنام string print formatted به معنی چاپ رشته فرمتشده همانند تابع printf می باشد به غیر از اینکه متنی را در خروجی خطدستوری نمایش نمی دهد بلکه آن را ذخیره می کند که می توان با تابع printf ( به عنوان مثال ) آن را چاپ نمود . نمای کلی تابع sprintf به شکل زیر می باشد :
sprintf(char *buffer, const char *format, arguments)
ابتدا نام رشتهای که قرار است به عنوان بافر ، دادهها را بگیرد می نویسیم ، سپس رشته خودمان را به همراه تعیینکنندههای فرمت ( در صورت لزوم ) می نویسیم و سپس آرگومانها را که خود می توانند حتی مقدار باشند می نویسیم که در نهایت در بافر ذخیره می شود . مثال :
#include <stdio.h>
int main()
{
char buff[50];
char str[] = "string";
sprintf(buff, "This is a %s", str);
printf("%s", buff);
return 0;
}
ابتدا یک رشته به نام buff مخفف buffer اعلان نمودیم . سپس یک رشته به نام str مخفف string و به معنی رشته با مقدار string تعریف نمودیم . سپس توسط تابع کتابخانهای sprintf رشته This is a را به همراه تعیینکننده فرمت رشته که آرگومان آن str است در buff ذخیره کردیم ( که می شود This is a string یعنی این یک رشته است ) و سپس توسط تابع کتابخانهای printf مقدار buff را در خروجی خطدستوری چاپ نمودیم . از تمام تعیینکنندههای فرمت در printf در sprintf نیز می توانید استفاده کنید
تابع sscanf
ویرایشتابع sscanf سرنام string scan formatted به معنی اسکن رشته فرمتشده ، رشته موجود در متن منبع برنامه ( source code ) را دریافت می نماید و قسمتهای مختلف آن را اسکن می کند و در آرگومانها ذخیره می کند . نمای کلی تابع کتابخانهای sscanf به شکل زیر است :
sscanf(const char *buffer, const char *format, arguments-list);
مثال :
#include <stdio.h>
int main()
{
char str[] = "This is a string with 2 numbers : 5 and 9";
char str2[10], str3[10], str4[10], str5[10], str6[10], str7[10];
char c1, c2;
int i1, i2, i3;
sscanf(str, "%s %s %c %s %s %d %s %c %d %s %d", str2, str3, &c1, str4, str5, &i1, str6, &c2, &i2, str7, &i3);
printf("%s %s %c %s %s %d %s %c %d %s %d", str2, str3, c1, str4, str5, i1, str6, c2, i2, str7, i3);
return 0;
}
در مثال بالا یک رشته به نام str تعریف نمودهایم که یک رشته است که می گوید این یک رشته است با ۲ عدد : ۵ و ۹ سپس ۶ رشته ۱۰ کاراکتری که مطمئن هستیم رشتهها بیشتر از ۱۰ کاراکتر ندارند ، اعلان نمودهایم . سپس ۲ کاراکتر ، اعلان نمودهایم و سپس نیز ۳ عدد داده صحیح را اعلان نمودهایم . تابع کتابخانهای sscanf رشته str را می گیرد و قسمتهای مختلف آن را اسکن می کند و به ترتیب در آرگومانهای تعیین شده ( که متغیرهایی هستند که بالاتر نوشتیم اعلان شدهاند ) ذخیره کردهایم . در نهایت با تابع printf دادههای ذخیره شده را در خروجی خطدستوری چاپ نمودهایم . دقت کنید که scanset ها در sscanf نیز برقرار و کارا هستند
همچنین تابع sscanf در صورت عملکرد موفقیتآمیز مقداری بزرگتر یا مساوی 0 را باز می گرداند و در غیر این صورت مقدار EOF سرنام End Of File به معنی پایان فایل یا پایان سند . مثال :
#include <stdio.h>
int main()
{
char str[] = "This is a string with 2 numbers : 5 and 9";
char str2[10], str3[10], str4[10], str5[10], str6[10], str7[10];
char c1, c2;
int i1, i2, i3;
int itemsread = 0;
itemsread = sscanf(str, "%s %s %c %s %s %d %s %c %d %s %d", str2, str3, &c1, str4, str5, &i1, str6, &c2, &i2, str7, &i3);
printf("%s %s %c %s %s %d %s %c %d %s %d\n", str2, str3, c1, str4, str5, i1, str6, c2, i2, str7, i3);
printf("Items read are : %d\n", itemsread);
return 0;
}
در این مثال تابع کتابخانهای sscanf مقدار ۱۱ را باز می گرداند . ۲ رشته سپس ۱ کاراکتر سپس ۲ رشته و سپس ۱ عدد و دوباره ۱ رشته و سپس ۱ کاراکتر سپس ۱ عدد و بعد ۱ رشته و ۱ عدد دیگر که مجموعاً می شود ۱۱ مورد و آیتم دریافت شده
تابع vprintf
ویرایشتابع vprintf سرنام variadic print formatted به معنی پرینت فرمت شده تعداد متغیر ، تابعی همانند printf را توسط کاربر می سازد . فقط این تابع نیاز به ماکروها و توابعی دارد که در فایل سرآیند stdarg تعریف شدهاند و ما در اینجا به صورت مختصر توضیح می دهیم تا در مبحث مربوطه مفصلاً آنها را تشریح کنیم . va_list یک ماکرو است که فهرستی ( لیستی ) از آرگومانهایی که قرار است ایجاد شوند را در یک متغیر ایجاد می کند . سپس تابع va_start با ۲ آرگومان که اولی همان آرگومانهای va_list است را دریافت می کند تا امکان دسترسی به آن را فراهم کند و دومی آخرین آرگومان ثابت و غیر متغیر تابع را دریافت می کند تا تابع متغیر آغاز به کار کند و va_end آرگومان ساخته شده را می گیرد تا آن را خاتمه دهد ( در تابعی با تعداد پارامترهای متغیر ) . به مثال زیر دقت کنید :
#include <stdio.h>
#include <stdarg.h>
void PrintFormatted(char *string, ...)
{
va_list args;
va_start(args, string);
vprintf(string, args);
va_end(args);
return;
}
int main()
{
PrintFormatted("%d variable argument\n", 1);
PrintFormatted("%d variable %s\n", 2, "arguments");
return 0;
}
در مثال ساده بالا ابتدا stdio را ضمیمه برنامه خود نمودهایم و سپس stdarg را تا تابعی با تعدادِ آرگومانهای پذیرنده متغیر تعریف کنیم . تابع پوچ PrintFormatted یک پارامتر رشته به نام string دارد و سه نقطه ( ellipsis ) که تعداد پارامترهای متغیر را نشان می دهد و تعیین می کند . سپس va_list متغیری برای کار با آرگومانهای دریافت شده با نام args را تعریف کرده است . سپس va_start آرگومانهای ارسالی و آرگومان تابع را پذیرفته است . سپس vprintf هر رشتهای را که به آن فرستاده شود در args ذخیره می کند و پرینت می نماید و در پایان va_end عمر args و تابع با تعداد پارامترهای متغیر را پایان می دهد . حالا به جای printf می توانیم از تابع vprintf استفاده کنیم که ابتدا یک رشته را به آن فرستادهایم که اولین قسمت آن یک متغیر عددی صحیح است که تعیین کننده فرمت آن را نوشته و در پایان به آن ۱ را فرستادهایم . دومی نیز به همین شکل است به استثنای اینکه آخرین آرگومان فرستاده شده به آن ( قبل از خط شکسته ) یک رشته است ( "arguments" ) دقت کنید : تعیین کنندههای فرمت در vprintf همانند printf و sprintf میباشند
تابع vsprintf
ویرایشتابع vsprintf سرنام variadic string print formatted تلفیقی از vprintf و sprintf می باشد . نمای کلی ایجاد تابع vsprintf به شکل زیر است :
vsprintf(char * buffer , const char * format , arguments-list)
ابتدا باید تابعی با تعداد پارامترهای متغیر تعریف نمود که در آن vsprintf یک متغیر اشارهگر را می پذیرد تا متن در آن ذخیره شود ، سپس تعیینکنندههای فرمت و در نهایت فهرست آرگومانهای فرستاده شده را . بدین ترتیب اگر متنی را با تعیین کنندههای فرمت به همراه آرگومانهای متناسب به vsprintf بفرستیم در متغیر اشارهگر ذخیره می شود که می توانیم آن را با یک تابع کتابخانهای مثل printf چاپ کنیم . مثال :
#include <stdio.h>
#include <stdarg.h>
void vspfunc(char * str, char * fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
vsprintf(str, fmt, argptr);
va_end(argptr);
return;
}
int main(void)
{
char buff[50];
int c = 299792458;
double pi = 3.141592;
char string[] = "string";
vspfunc(buff, "%d %f %s", c, pi, string);
printf("%s", buff);
return 0;
}
در مثال بالا ابتدا stdio.h و stdarg.h را ضمیمه برنامه خود نمودهایم تا از ماکروها و تابعهای کتابخانهای آنها در برنامه خود استفاده کنیم . سپس یک تابع پوچ با نام vspfunc مخفف vsprintffunction تعریف نمودهایم که یک متغیر اشارهگر از نوع کاراکتر به نام str مخفف string و یکی هم به نام fmt مخفف format دارد به همراه سه نقطه که نشانگر داشتن تعداد پارامترهای متغیر است . داخل تابع ابتدا به کمک ماکروی va_list متغیری که قرار است آرگومانها را پذیرا شود تعریف نمودهایم ( argptr ) سپس برای کار با آرگومانهای فرستاده شده و آرگومانهای تابع از va_start استفاده کردهایم . سپس تابع vsprintf سه آرگومان را می پذیرد . اولی اشارهگری که در آن دادهها ذخیره شوند ؛ دومی تعیینکنندههای فرمت و سومی فهرست آرگومانهای فرستاده شده . سپس va_end عمر argptr را پایان می دهد . چون تابع ، پوچ است مقداری را باز نمی گرداند . داخل تابع اصلی برنامه main یک رشته به کمک آرایهای از کاراکتر با نام buff مخفف buffer با پنجاه عنصر اعلان نمودهایم . یک متغیر صحیح که سرعت انتشار نور در خلأ را در خود ذخیره کرده یک متغیر اعشاری با دقت دو برابر که عدد پای ( پی ) را در ذخود ذخیره کرده و یک رشته که string را در خود ذخیره کرده . سپس آنها را به کمک تابع کتابخانهای vsprintf در buff ذخیره نمودهایم و با تابع کتابخانهای printf در خروجی خطدستوری چاپ نمودهایم و تابع با موفقیت به پایان می رسد
تابع putchar
ویرایشتابع کتابخانهای putchar سرنام put character به معنی کاراکتر را قرار بده یک کاراکتر را به خروجی خطدستوری می فرستد . نمای کلی ایجاد تابع کتابخانهای putchar به شکل زیر است :
putchar(unsigned char);
برای استفاده از این تابع یا می توانید یک کاراکتر را داخل تابع بنویسید تا چاپ شود یا یک داده کاراکتری تعریف شده را به آن بفرستید . مثال :
#include <stdio.h>
int main(void)
{
putchar('J');
return 0;
}
که در خروجی خطدستوری کاراکتر J را چاپ می کند ؛ همچنین می توانید مقدار و موجودی J را داخل یک داده کاراکتری ذخیره کنید و سپس آن را به putchar بفرستید . مثال :
#include <stdio.h>
int main(void)
{
char c = 'J';
putchar(c);
return 0;
}
یک مثال دیگر برای کامل کردن کاربرد تابع putchar :
#include <stdio.h>
int main(void)
{
char ch = 'A';
for(ch = 'A'; ch <= 'Z'; ch++)
{
putchar(ch);
putchar('\n');
}
return 0;
}
ابتدا یک کاراکتر با مقدار و موجودی کاراکتر A با نام ch تعریف نمودهایم . سپس با حلقه for کاراکتر ch را از A تا زمانی که مقدار کمتر و مساوی Z باشد به همراه یک خط شکسته در خروجی خطدستوری قرار دادهایم که همان حروف الفبای انگلیسی می باشد
تابع getchar
ویرایشتابع کتابخانهای getchar مخفف get character به معنی « کاراکتر را بگیر » برای دریافت یک کاراکتر از ورودی استاندارد ( معمولاً صفحهکلید Keyboard در ورودی خطدستوری ) می باشد . نمای کلی ایجاد تابع getchar بدین شکل می باشد :
getchar(void);
تابع getchar آرگومانی را نمی پذیرد و مقدار آن فقط در دادههای صحیح قابل ذخیره است ( که کامپایلر به صورت خودکار نقش کاراکتر را به آن می دهد )
خروجی getchar باید یک کاراکتر باشد و اگر EOF یعنی End Of File را بازگرداند یعنی یا فایل به پایان رسیده است یا خطایی در وارد کردن کاراکتر وجود داشته است . مثال :
#include<stdio.h>
int main()
{
int ch;
ch = getchar();
printf("The entered character is : %c", ch);
return 0;
}
ابتدا یک داده از نوع صحیح با نام ch اعلان نمودهایم ؛ سپس ch توسط getchar که مقداری از نوع کاراکتر دریافت می کند تعریف می شود و مقدار و موجودی آن ، همان مقدار و موجودی کاراکتری وارد شده است و سپس در خروجی خطدستوری چاپ می شود که کاراکتر وارد شده توسط شما کاراکتر x می باشد که x همان کاراکتر وارد شده توسط کاربر می باشد
تابع puts
ویرایشتابع کتابخانهای puts سرنام put string به معنی رشته را قرار بده ، یک رشته را در خروجی استاندارد که معمولاً محیط خطدستوری سیستم عامل است می نویسد . نمای کلی ایجاد تابع puts به شکل زیر می باشد :
puts(const char * string);
می توانیم یک رشته را داخل آن بنویسیم یا نام یک متغیر از نوع رشته را که تعریف شده است به آن بفرستیم تا در خروجی خطدستوری نمایش دهد .
نکته : تابع puts به خودی خود ، در پایان رشته ، خط را می شکند و وارد خط بعدی می شوید
مثال :
#include<stdio.h>
int main()
{
puts("Hello World!");
return 0;
}
در قطعه کد بالا رشته !Hello World را نمایش دادهایم
مثال :
#include<stdio.h>
int main()
{
char ch[] = "This is a string";
puts(ch);
return 0;
}
در مثال و قطعه کد بالا یک رشته به نام ch را تعریف کردهایم . سپس با تابع کتابخانهای puts مقدار و موجودی ch را نمایش دادهایم
همچنین اگر puts را داخل یک داده صحیح ذخیره کنید ، هم متن نمایش داده می شود و هم تعداد کاراکترهای داخل رشته puts ذخیره می شود . مثال :
#include<stdio.h>
int main()
{
int n = puts("This is a string");
printf("%d", n);
return 0;
}
نکته : تعیینکنندهای فرمت در puts عمل نمی کنند و در صورت استفاده ، کامپایلر از شما خطا می گیرد
تابع gets
ویرایشتابع کتابخانهای gets سرنام get string به معنی رشته را بگیر ، از ورودی استاندارد که معمولاً صفحهکلید است رشتهای را دریافت می کند و سپس آن را در بافر ذخیره می کند ( که یک اشارهگر است ) . نمای کلی ایجاد تابع کتابخانهای gets به صورت زیر است :
gets(char * buffer);
دقت کنید که از تعیینکنندههای فرمت نمی توانید در gets استفاده کنید . تابع gets با یک خط جدید ( شکسته شدن خط ) پایان می یابد که آن را به صورت یک تهی NULL ذخیره می کند و اگر عملیات دریافت با موفقیت انجام شود ، رشته ذخیره شده در خود را باز می گرداند و اگر یک اشارهگر تهی NULL Pointer را باز گرداند یعنی خطایی رخ داده و در صورتی که رشتهای وارد نشده باشد EOF سرنام End Of File را باز می گرداند
نکته : استفاده از تابع gets ریسک پذیر است ؛ چرا که چک نمی کند که رشتهای که شما اعلان کردهاید چند عنصری است و اگر از مقدار تعیین شده بیشتر باشد ، بافر Buffer سرریز می شود و آنچه که باز می گرداند تعریفنشده است
مثال :
#include<stdio.h>
int main()
{
char ch[30];
printf("Enter a string : \n");
gets(ch);
printf("%s", ch);
return 0;
}
در قطعه کد بالا یک رشته ۳۰ کاراکتری با نام ch را اعلان نمودهایم . سپس در رابط خطدستوری چاپ کردهایم یک رشته را وارد کنید و خط را شکستهایم . سپس تابع gets رشته ch را از کاربر دریافت می کند و در نهایت تابع printf آنچه را که وارد کردهاید در خروجی خطدستوری چاپ می کند نکته مهم : تابع gets در استاندار C11 و از آن به بعد ، حذف شده است . اگر کامپایلر شما خطا گرفت یا اگر کامپایلر خود را تنظیم کردید تا از استاندارد جدید استفاده کند و از شما خطا گرفت ، علتش همین است . توصیه شده است که به جای gets از fgets استفاده کنید که در ادامه خواهیم نوشت
ماکروهای stdin و stdout
ویرایشدر قسمت بعدی ماکروی FILE را خواهیم نوشت که به برنامهنویس اجازه میدهد تا جریان فایلی را ایجاد کند تا فایلی ساخته شود و آن را بخوانیم یا درون آن چیزی بنویسیم یا هر دوی اینها . اما ماکروهای stdin سرنام standard iput به معنی ورودی استاندارد و stdout سرنام standard output به معنی خروجی استاندارد برای ما این امکان را فراهم میآورند تا از ورودی و خروجی استاندارد ( در رایانه صفحهکلید و نمایشگر ) به جای جریان فایل در تابعهای کتابخانهای که فایل را میخوانند و در آن مینویسند از صفحهکلید و نمایشگر استفاده کنیم . مثال :
#include <stdio.h>
int main(void)
{
fprintf(stdout, "%s", "string");
return 0;
}
در قطعه کد بالا از تابع کتابخانهای fprintf سر نام file print formatted به معنی چاپ فرمت شده فایل استفاده کردهایم اما به جای اینکه با فایلی سر و کار داشته باشیم و بخواهیم طبق معمول در فایلی چیزی را بنویسیم به جای نام فایل نوشتهایم : stdout و سپس تعیینکننده فرمت تعیین کرده است که یک رشته نوشته شود ( در اینجا نمایش داده شود ) و به جای آن رشته string را به عنوان آرگومان به تابع فرستادهایم . به همین شکل می توان به جای یک تابع کتابخانهای که از فایل ، اطلاعات را می خواند ، بنویسیم : stdin و از صفحهکلید ، اطلاعات را دریافت کنیم
ماکروی نوع داده FILE
ویرایشبه ورود و خروج دادهها از گذرگاه ( مثل برنامه ) جریان داده گفته می شود . همچنین به ورود و خروج دادهها به داخل فایل ، جریان فایل گفته می شود . ماکروی FILE با یک علامت استریسک و یک شناسه برای نام فایلی که می خواهیم بسازیم ، در برنامه ( زبان برنامهنویسی سی ) جریان فایلی را می سازد و آن را آماده استفاده می کند . نمای کلی ایجاد جریان فایل بدین شکل است :
FILE * const char * name-of-file
که const char * name-of-file یک رشته است که نام فایلی را که قصد استفاده از آن را داریم را تعیین می کند . مثل : FILE * fp در اینجا fp سرنام file pointer نام انتخابی ما برای فایلی که قصد استفاده از آن را داریم می باشد
تابعهای fopen و fclose
ویرایشتابع fopen سرنام file open به معنی فایل را باز کن ، فایلی را که ماکروی نوع داده FILE آماده کرده باشد را باز میکند و در دادهی فایل باز شده ( توسط FILE ) ذخیره می کند . تابع کتابخانهای fopen دو پارامتر را از نوع رشته پذیرا می باشد : یکی نام فایل و دیگری نحوه باز کردن فایل file opening mode ( که هر دو طبعاً باید داخل دو دابل کوت " قرار بگیرند )
به عنوان نام فایل اگر فقط نامی را بنویسید ، برنامه فایل مورد نظر شما را در همان آدرس برنامه ایجاد می کند ، اما اگر آدرس معین دیگری را به آن بدهید در همان آدرس ایجاد می کند . تابع fopen در صورت عمل موفق ، یک اشارهگر به فایل ( و به طور کلّیتر شیئای که جریان فایل را در دست دارد ) باز می گرداند و در صورت عدم موفقیت یک اشارهگر تهی NULL باز می گرداند . تابع کتابخانهای fclose نیز سرنام file close فایل باز شده را می بندد تا از حافظه موقت ( RAM ) پاک شود و فضای اشغال شده را آزاد می کند که فقط باید بین جفت پرانتز باز و بسته آن ، نام فایلی را که ایجاد کردهاید بنویسید . تابع fclose در صورت موفقیتآمیز بودن عملیات مقدار 0 را باز می گرداند و در صورت خطا EOF را باز خواهد گرداند
نحوههای باز کردن فایل بدین شرح می باشند :
r
مخفف read که برای خواندن فایل می باشد و به اولین کاراکتر فایل اشاره می کند
w
مخفف write که برای نوشتن در فایل می باشد و فایل را باز نویسی می کند ( یعنی اگر داخل فایل متنی موجود باشد ، آن را جایگزین می کند ) و در صورت عدم وجود فایل ، فایل را ایجاد می کند
a
مخفف append که در ادامه فایل ، متنی را می نویسد ( یعنی اگر متنی داخل آن موجود باشد ، در ادامه آن ، خواهد نوشت ) در صورت عدم وجود فایل آن را می سازد
r+
مخفف + read که هم می خواند و هم می نویسد اما به اولین کاراکتر فایل اشاره می کند
w+
مخفف + write که هم می خواند و هم می نویسد و در صورت نوشتن متن را جایگزین می کند . همچنین در صورت عدم وجود فایل ، آن را می سازد
a+
مخفف + append که هم می خواند و هم در انتهای فایل می نویسد و به آخرین کاراکتر فایل اشاره می کند . در صورت عدم وجود فایل آن را می سازد
rb
مخفف read binary که فایل را به صورت باینری ( دودویی ) و در واقع عددی می خواند . اگر فایل ، موجود نباشد یک اشارهگر NULL باز می گرداند
wb
مخفف write binary که فایل را به صورت باینری ( دودویی ) و در واقع عددی می نویسد و جایگزین می کند . در صورت عدم وجود فایل آن را می سازد
ab
مخفف append binary که فایل را به صورت باینری ( دودویی ) و در واقع عددی اضافه کرده و در انتهای می نویسد . در صورت عدم وجود فایل آن را می سازد
rb+
مخفف + read binary که فایل را جهت خوانده و نوشته شدن در مبنای دو و به صورت عددی باز می کند . اگر فایلی وجود نداشته باشد یک اشارهگر NULL باز می گرداند
wb+
مخفف + write binary که فایل را جهت خوانده و نوشته شدن در مبنای دو و به صورت عددی باز می کند و اگر محتوایی داشته باشد آن را جایگزین می کند . در صورت عدم وجود فایل ، آن را می سازد
ab+
فایل را جهت خواندن و اضافه کردن در مبنای دو و در واقع عددی باز می کند . در صورت عدم وجود فایل آن را می سازد
مثال :
#include <stdio.h>
int main(void)
{
FILE * fp;
fp = fopen("/home/lovelorn/Documents/mytext.txt", "w");
fprintf(fp, "%s", "string");
fclose(fp);
return 0;
}
تشریح : ابتدا فایل سرآیند stdio را ضمیمه برنامه خود نمودهایم تا از ماکروها و تابعهای کتابخانهای استاندارد تعریف شده در آن استفاده کنیم . سپس داخل تابع اصلی برنامه main که پارامتری ندارد یک فایل را با نام fp آماده استفاده نمودهایم . موجودی fp توسط تابع کتابخانهای fopen که در فایل سرآیند stdio تعریف شده است یک فایل متنی به نام mytext می باشد که داخل آدرس home و نام کاربری خودم lovelorn می باشد ( این نام توسط شما در سیستم عامل لینوکس تعریف می شود ) که متعلق به سیستم عامل GNU/Linux می باشد شما در ویندوز می توانید بنویسید :
"D:/myapp/mytext.txt"
که در حالت w یعنی نوشتن قرار دارد و فایلی به نام mytext را با فرمت txt در آدرس ذکر شده ایجاد می کند . سپس توسط تابع fprintf سرنام file print formatted به معنی چاپ فرمت شده فایل که هنوز به آن نرسیدهایم اما همان طور که اشاره شد جهت نوشتن در فایل می باشد ، نام فایل را ذکر کرده و سپس توسط تعیین کننده فرمت تعیین کردهایم که یک رشته را میخواهیم بنویسیم و سپس string به معنی رشته را به آن فرستادهایم که می توانستیم نام یک متغیر تعریف شده را نیز نویسیم که باید یک رشته می بود که داخل متنی موجود وجود داشت . سپس فایل fp را توسط تابع کتابخانهای fclse بستهایم تا فضای اشغال شده توسط فایل fp در حافظه موقت RAM آزاد شود . سپس تابع main با موفقیت به پایان می رسد
تابع fprintf
ویرایشتابع کتابخانهای fprintf سرنام file print formatted به معنی چاپ فرمتشده فایل متنی را از برنامه دریافت نموده و در جریان فایل می نویسد . شکل کلی ایجاد تابع fprintf به صورت زیر است :
fprintf(FILE * stream, const char * format-string, argument list);
ابتدا نام جریان فایل را نوشته ؛ سپس تعیین کنندههای فرمت را به همراه متن دلخواه ( که ضروری نیست ولی می توانید بنویسید ) می نویسیم و سپس آرگومانهایی که به تابع فرستاده می شوند تا جایگزین تعیین کنندههای فرمت شوند را می نویسیم
مثال :
#include <stdio.h>
int main(void)
{
char str[] = "Hello World!\n";
FILE * fp;
fp = fopen("mytext.txt", "w");
fprintf(fp, "%s", str);
fclose(fp);
return 0;
}
در مثال بالا رشته str جمله !Hello World را به همراه یک شکستن خط در خود ذخیره نموده و تعریف شده است . سپس فایل fp را آماده ایجاد نمودهایم . فایل fp توسط تابع کتابخانهای fopen با نام mytext در آدرس برنامه نوشته خواهد شد ( در صورتی که وجود نداشته باشد ساخته خواهد و در صورت وجود ، متن آن بازنویسی خواهد شد و همان طور که در مطلب پیشین گفتیم می توانید آدرس را هم بنویسید . تابع کتابخانهای fprintf که مطلب فعلی می باشد تابع fp را هدف قرار داده و یک رشته را پذیرا می باشد که رسته همان str با موجودی و مقدار !Hello World و یک شکستن خط می باشد . در پایان با تابع کتاخانهای fclose که در مطلب پیشین نوشتیم ، فایل fp زا می بندیم و فضای اشغال شده توسط آن را آزاد می نمائیم و تابع main با موفقیت به اتمام رسیده است . حالا شما می توانید فایل mytext.txt را در جایی که برنامه خود را نوشته و کامپایل نمودهاید بیابید که داخل همان متن نوشته شده به همراه یک خط شکسته در آن موجود می باشد . ضمناً تابع fprintf در صورت عملکرد موفقیتآمیز تعداد بایتهای نوشته شده را باز میگرداند و در صورت عدم موفقیت یک عدد منفی را باز میگرداند
نکته : تعیین کنندههای فرمت در fprintf همانند printf می باشند
تابع fscanf
ویرایشتابع fscanf سرنام file scan formatted به معنی اسکن فرمتشده فایل جهت خواندن دادهها از جریان فایل می باشد که دادهها را داخل متغیرهای آرگومانهای فرستاده شده به خود ذخیره می نماید . شکل کلی ایجاد تابع fscanf به صورت زیر می باشد :
fscanf(FILE * stream, const char * format-string, argument-list);
ابتدا نام جریان فایلی را که قرار است خوانده شود می نویسیم ، سپس تعیین کنندههای فرمت را و سپس در پایان آرگومانهایی که قرار است جایگزین تعیین کنندههای فرمت شوند را به تابع می فرستیم
مثال :
#include <stdio.h>
int main(void)
{
char str1[10], str2[10];
float euler;
FILE * fp;
fp = fopen ("mytext.txt", "r");
fscanf(fp, "%s %s %f", str1, str2, &euler);
printf("The text in file is : %s %s %f\n", str1, str2, euler );
fclose(fp);
return 0;
}
ما سند و فایلی را به نام mytext.txt در آدرس و جایی که برنامه قرار دارد ایجاد نمودهایم و داخل آن نوشتهایم : Hello World! 2.71 سپس در برنامه خود ۲ رشته با نامهای str1 و str2 اعلان نمودهایم ؛ همین طور یک عدد اعشاری با نام اویلر . سپس یک فایل را با نام fp آماده استفاده نمودهایم و سپس با تابع کتابخانهای fopen فایل mytext.txt را جهت خواندن باز نمودهایم . تابع کتابخانهای fscanf که مطلب فعلی ماست فایل fp را اسکن می کند و داخل آن به دنبال ۲ رشته و یک عدد اعشاری می گردد که داخل str1 و str2 و euler ذخیره می کند . دقت کنید که رشتهها جهت دریافت شدن نیازی به عملگر آدرسدهی ( یعنی همان علامت امپرسند & ) ندارند اما جهت دریافت عدد در مثال نیز از آن استفاده نمودهایم که ضروری می باشد . سپس آنچه که در فایل mytext.txt خوانده شده است در خروجی خطدستوری نمایش داده می شود که اولین رشته Hello و دومین رشته !World و عدد اعشاری ما عدد اویلر با دقت ۲ رقم اعشار یعنی ۲٫۷۱ می باشد . در پایان نیز فایل را بسته و تابع را با موفقیت به پایان می رسانیم . مقداری که تابع fscanf باز می گرداند مقدار و تعداد دادههایی است که خوانده است ؛ اگر کمتر از دادههای داخل فایل باشد با مشکلی رو به رو شده است تا جایی که این مقدار می تواند 0 باشد ؛ اما اگر به کلی فایل را نتواند بخواند مقدار EOF را باز خواهد گرداند . دقت کنید که تعیین کنندههای فرمت در fscanf همانند scanf می باشند
تابع getc
ویرایشتابع کتابخانهای getc سرنام get character به معنی کاراکتر را بگیر همانند getchar می ماند اما به جای گرفتن کاراکتر از ورودی استاندارد ( صفحهکلید keyboard ) کاراکتر را از فایل می گیرد . شکل کلی ایجاد تابع getc بدین صورت می باشد :
getc(FILE * Stream)
تابع getc از یک فایل کاراکتری را دریافت کرده و به اولین کاراکتر بعدی می رود . مثال :
#include <stdio.h>
int main(void)
{
FILE * fp;
int ch;
fp = fopen("mytext.txt", "r");
ch = getc(fp);
while(ch != EOF)
{
putchar(ch);
ch = getc(fp);
}
putchar('\n');
fclose(fp);
return 0;
}
ابتدا فایل mytext را برای خواندن باز می کنیم ، داده صحیح ch با تابع کتابخانهای getc فایل را دریافت می کند و اولین کاراکتر آن را در ch ذخیره کرده و به سراغ کاراکتر بعدی می رود . سپس توسط حلقه while تا زمانی که به پایان فایل نرسیده کاراکتر(های) داخل فایل را نمایش داده و بعد دوباره کاراکتر بعدی را می گیرد . در پایان نیز خط را شکستهایم . که فایل تغییر نکرده و خروجی آن همان 2.71 !Hello World می باشد . تابع getc کاراکتری را که خوانده به صورت یک کاراکتر بدون علامت unsigned char که نقش یک صحیح int را ایفا می کند باز می گرداند و به کاراکتر بعدی در جریان فایل اشاره می کند . در صورت رسیدن به پایان فایل EOF را باز می گرداند و یا اگر مشکلی وجود داشته باشد یک خطا را باز می گرداند که می توان با تابعهای کتابخانهای feof و ferror آن را از هم تشخیص داد و خطای بازگردانده شده را دریافت نمود تا خطا را برطرف کرد . فایل را بسته و تابع اصلی برنامه را با موفقیت به پایان می رسانیم
تابع putc
ویرایشتابع putc همانند putchar میباشد و سرنام put character است جز اینکه کاراکتر دریافت شده را اخل یک فایل قرار می دهد و منتظر کاراکتر بعدی می ماند تا وارد شود و آن را داخل فایل قرار دهد . شکل کلی ایجاد تابع putc بدین صورت است :
putc(int ch, FILE * stream);
تابع putc یک مقدار کاراکتری را که می تواند از نوع صحیح و یا کاراکتر باشد دریافت نموده و داخل جریان فایل می نویسد . مثال :
#include <stdio.h>
int main(void)
{
FILE * fp;
char ch;
char brkline = '\n';
fp = fopen("alphabet.txt", "w");
for(ch='A'; ch<='Z'; ch++)
{
putc(ch, fp);
putc(brkline, fp);
}
fclose(fp);
printf("The Operation Has Finished Successfully !\n");
return 0;
}
در مثال بالا فایلی را با نوع داده FILE به نام fp آماده ایجاد شدن می نمائیم . یک کاراکتر با نام ch را اعلان نموده و کاراکتر brkline را با شکستن خط تعریف می کنیم . سپس با تابع کتابخانهای fopen فایل alphabet.txt به معنی الفبا را برای نوشته شدن می سازیم . با حلقه for از جایی که ch مقدار A دارد تا جایی که مقدار آن به Z برسد ch یک واحد یک واحد افزایش می یابد و به سراغ کاراکتر بعدی می رود . تا putc مقدار ch در فایل مد نظر چاپ می کند و سپس دوباره همین تابع مقدار خط شکسته را وارد می کند تا حروف الفبای انگلیسی در خطوط مجزا در فایلی با نام alphabet در دایرکتوری و فولدری که برنامه قرار دارد ایجاد شده و ذخیره گردند . سپس فایل را می بندیم و در پایان با تابع printf در خروجی خطدستوری چاپ نمودهایم عملیات با موفقیت به پایان رسید .
تابع putc در صورت موفقیتآمیز بودن عملیات خود کاراکتری را که نوشته است باز میگرداند و در صورت عدم موفقیت مقدار EOF را باز میگرداند
ماکروی مقدار NULL
ویرایشمقدار تهی با تلفظ نال NULL در فایلهای سرآیند stdio و stdlib و stddef و time و locale و string و wchar تعریف شده است و مقدار اشارهگری است که آن اشارهگر به مکانی غیر قابل دسترسی اشاره می کند تا اگر به اشتباه از آن اشارهگر در برنامه استفاده شد باعث توقف سیستم نشود . حتماً از مبحث اشارهگرها به یاد دارید که اگر اشارهگری را اعلان نمودید و آن را به شکلی تعریف نکردید که به متغیر یا تابع دیگری اشاره کند ، بهتر است آن را با دادن مقدار NULL تعریف کنید . طبق استاندارد عبارت ثابت ;int * ptr = 0 یک اشارهگر NULL است نام آن ( ptr ) دل به خواه است اما اگر یک متغیر صحیح اشارهگر را مقدار 0 به آن بدهید همانند مقدار NULL خواهد بود و غیر قابل دسترسی است و در صورتی که بخواهید آدرس آن را به دست آورید یا در خروجی چاپ کنید و نمایش دهید آدرس آن غیر قابل دسترسی اعلام خواهد شد . مثال :
#include <stdio.h>
int main(void)
{
int * iptr = NULL;
printf("%p\n", iptr);
return 0;
}
در مثال بالا آدرس اشارهگر ptr غیر قابل دسترسی نمایش داده خواهد شد
طبق استاندارد مشخص نشده است که غیر قابل دسترسی بودن مقدار NULL در کامپایلر و پیادهساز باید چگونه باشد اما مقدار آن :
((void *)0)
تعریف شده است . شما علاوه بر دادن مقدار تهی به اشارهگر در برنامههای بزرگ خود میتوانید در دستور شرطی if بررسی کنید که اشارهگر شما به جایی اشاره کرده است یا خیر که می نویسید if (ptr == NULL) بدین ترتیب مشخص می شود که اشارهگر ptr به جایی اشاره کرده و آدرسی دارد یا خیر . همچنین در ساخت دادههای درختی و لیست پیوندی باید آخرین مقدار آن را تهی تعیین کنید تا پایان دادهها را تعیین کنید
تابع fputc
ویرایشتابع fputc سرنام file put character همانند putc میباشد و یک کاراکتر را داخل فایل تعیین شده می نویسد و منتظر کاراکتر بعدی می ماند تا آن را وارد کند ( به محل بعدی اشاره می کند ) جز اینکه putc به نحوی در فایل سرآیند تعریف میشود که در هر بار فراخوانی ، فایل فرستاده شده به خود را دوباره می خواند و این مورد در نوشتن برنامه به صورت غیر حرفهای می تواند باعث ایجاد باگ ، مخصوصاً باگ امنیتی و رخنهپذیری شود در حالی که fputc اینگونه نیست و امن می باشد ؛ ضمن اینکه fputc را می توانید به عنوان آرگومان به یک تابع دیگر بفرستید و همچنین فضای حافظه موقت کمتری اشغال می کند ولی fputc کمی کندتر از putc می باشد . شکل کلی ایجاد تابع fputc بدین صورت می باشد :
fputc(int ch, FILE * fp);
ch نام مقداری است که کاراکتر متناظر آن در ازکی ( ASCII ) توسط شما تعیین می شود تا در فایل نوشته شود . همچنین شما می توانید یک کاراکتر را تعریف کنید و به آن بفرستید تا آن را در فایل بنویسد . تابع fputc در صورت عملکرد موفقیتآمیز مقداری را که نوشته است باز می گرداند و در صورت بروز خطا مقدار EOF را می فرستد ؛ همچنین جهت اشکالزدایی مقادیر دیگری را می فرستد که با تابعهای کتابخانهای دیگر stdio همانند بسیاری از تابعهای کتابخانهای ، خطا را در آن بر طرف کنید . مثال :
#include <stdio.h>
int main ()
{
FILE *fp;
int ch;
int nl = 10;
fp = fopen("alphabet.txt", "w+");
for( ch = 65 ; ch <= 90; ch++ )
{
fputc(ch, fp);
fputc(nl, fp);
}
fclose(fp);
return(0);
}
همین برنامه را به صورت زیر نیز می توانیم بنویسیم :
#include <stdio.h>
int main ()
{
FILE *fp;
char ch;
char nl = '\n';
fp = fopen("alphabet.txt", "w+");
for( ch = 'A' ; ch <= 'Z'; ch++ )
{
fputc(ch, fp);
fputc(nl, fp);
}
fclose(fp);
return(0);
}
تشریح این برنامهها تکرار مکررات در همین صفحه است و واضح است که برنامه چگونه عمل می کند ، فقط در اولین قطعه کد مقدار ۶۵ تا ۹۰ کاراکترهای A تا Z در ازکی می باشند و مقدار ۱۰ کاراکتر خط شکسته که در هر بار اجرای حلقه for یک کاراکتر از حروف الفبای انگلیسی و یک کاراکتر خط شکسته نوشته می شود و در دومین قطعه کد دیگر کاراکتر ها را نوشتهایم و نه مقدار آن را
تابع fgetc
ویرایشتابع fgetc سرنام file get character به معنی کاراکتر را از فایل بگیر همانند getc می باشد و یک کاراکتر را از فایل دریافت می نماید و منتظر کاراکتر بعدی می ماند تا دریافت شود ؛ جز اینکه getc به عنوان یک ماکرو در فایل سرآیند stdio تعریف شده است و با هر بار فراخوانی ، فایل فرستاده شده به خود را دوباره می خواند و اگر کد به درستی نوشته نشود باعث ایجاد باگ مخصوصاً باگ امنیتی و رخنهپذیری می شود ؛ در حالی که fgetc اینگونه نیست و امن می باشد . fgetc را می توانید به عنوان یک آرگومان به تابع دیگری بفرستید و فضای حافظه کمتری را اشغال می کند اما چون fgetc به صورت یک تابع در stdio تعریف شده است کمی کندتر از getc می باشد . شکل کلی ایجاد تابع fgetc بدین صورت می باشد :
fgetc(FILE * stream);
یک فایل را به fgetc می فرستید تا از اولین کاراکتر آن شروع به خواندن کند و منتظر کاراکتر بعدی بماند و به آن اشاره کند تا در ادامه اگر باز هم فراخوانده شد آن را نیز دریافت نماید . تابع fgetc در صورت موفقیتآمیز بودنِ عملکرد خود ، کاراکتری را که خوانده است مقدار عددی صحیح آن را باز می گرداند و در صورت عدم موفقیت EOF را باز می گرداند که با تابعهای feof و ferror که تابعهای تعریف شده در stdio هستید می توان پی برد که فایل به پایان رسیده یا خطایی وجود دارد . مثال :
#include <stdio.h>
int main ()
{
FILE *fp;
int ch;
fp = fopen("alphabet.txt", "r");
ch = fgetc(fp);
while(ch != EOF)
{
putchar(ch);
ch = fgetc(fp);
}
fclose(fp);
return(0);
}
توضیح برنامه : برنامه یک متغیر از نوع صحیح با نام ch دارد که مقدار آن تابع fgetc با دریافت فایل fp می باشد که اولین کاراکتر فایل alphabet.txt را می خواند ( فایلی که در برنامه قبلی ایجاد کردیم که حروف الفبا را نوشت ) و سپس تا زمانی که ch به آخر فایل نرسیده است ch که کاراکتر alphabet.txt است را بر روی خروجی خطدستوری نمایش داده و سپس دوباره fgetc کاراکتر بعدی را می خواند و در خود ذخیره می کند و در پایان فایل را بسته و تابع اصلی را با موفقیت به پایان رساندهایم
تابع fputs
ویرایشتابع کتابخانهای fputs سرنام file put string به معنی رشته را در فایل قرار بده یک رشته از کاراکترها را داخل جریان داده و معمولاً فایل فرستاده شده به آن ، می نویسد . شکل ایجاد تابع fputs بدین صورت می باشد :
fputs(const char * str, FILE * stream);
ابتدا یک فایل را آماده ایجاد می کنیم سپس آن را باز کرده و نامی برای آن انتخاب می کنیم و بعد به کمک fputs متنی را داخل آن می نویسیم و سپس فایل را می بندیم تا فضای اشغال شده آزاد شود .ضمناً تابع fputs در صورت موفقیتآمیز بودن عملکرد خود یک مقدار غیر منفی را می فرستد و در صورت خطا EOF را و البته مقادیر دیگری که می توانید توسط ferror و دیگر تابعهای خطا یاب ، خطای آن را بیابید و بر طرف کنید که در ادامه می نویسیم . مثال :
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("mytext.txt", "w");
fputs("Hello World!\n2.71", fp);
fclose(fp);
return(0);
}
توضیح : همان طور که نوشته شد ، فایلی را آماده کردهایم تا ایجاد شود ( fp ) سپس آن را جهت نوشتن ایجاد نمودهایم ( mytext.txt ) و با تابع کتابخانهای fputs در آن نوشتهایم :
!Hello World
2.71
و البته اگر دقت کنید این بار در ایجاد نوشته و رشته ، خط را شکستهایم . حال اگر با یک پیرایشگر متن ، فایل ایجاد شده را باز کنید ، متن را داخل آن ، مشاهده خواهید نمود
تابع fgets
ویرایشتابع fgets سرنام file get string به معنی رشته کاراکترها را از فایل بگیر ، جهت دریافت یک خط از متن فایل داده شده می باشد . شکل کلی ایجاد تابع fgets بدین صورت می باشد :
fgets(char * str, int limit number, FILE * Stream);
تابع نام یک رشته را دریافت می کند ، سپس تعداد محدودیت تعداد کاراکترهای دریافتی را می نویسیم و سپس ورودی جریان داده که معمولاً جریان فایل است . تابع تا زمانی که به خط شکسته یا تعداد محدود کننده نوشته شده نرسیده باشد ، فایل را می خواند و آن را ذخیره می کند . تابع fgets در صورت داشتن عملکرد موفقیتآمیز ، یک اشارهگر را به رشته خود باز می گرداند و در صورت رسیدن به پایان خط یا خطا در عملکرد یک مقدار نال NULL را می فرستد که با feof می توان فهمید که به پایان رسیده است یا خیر و در صورت خطا می توان با تابع ferror خطای آن را پیدا نمود . مثال :
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("mytext.txt", "r");
char str[20];
fgets(str, 20, fp);
puts(str);
fclose(fp);
return(0);
}
توضیح : در صورت کامپایل قطعه کد بالا و اجرای آن تنها !Hello World را از فایل mytext.txt در خروجی خطدستوری خواهیم دید ، چرا که 2.71 در خط بعدی و بعد از شکسته شدن خط وجود دارد . فایل mytext.txt ابتدا آماده و سپس برای خوانده شدن باز شده . آرایه کاراکتری str با ۲۰ عنصر ، اعلان شده است . تابع fgets فایل mytext.txt را باز نموده و از آن ۲۰ کاراکتر خط اول را می خواند و در str ذخیره می کند و سپس توسط تابع puts این رشته در خروجی خطدستوری نمایش داده می شود و سپس فایل بسته شده و تابع اصلی ، با موفقیت به پایان می رسد
برای اینکه به صورت مطمئن از fgets متنی را از ورودی استاندارد دریافت کنید باید به شکل زیر عمل کنید ( چرا که با زدن دکمه ورود Enter یا همان Return داخل صفحهکلید ) کاراکتر خط شکسته را دریافت نمیکند که باید آن را جایگزین کنید . مثال :
fgets(filename, 200, stdin);
filename[strlen (filename) - 1] = '\0';
توضیح : تابع fgets داده یا فایلی را با نام filename دریافت میکند که تعداد آن نهایتاً ۲۰۰ تا است و از ورودی استاندارد ، یعنی صفحهکلید ، داده را وارد میکنیم . سپس داده طولش سنجیده میشود و شمرده شده ( strlen که در فصل string خواهیم نوشت ) و یکی از آن کم میکنیم تا مقدار آن پوچ باشد که خط شکسته شده را احتساب کند و به درستی عمل کند
ماکروی نوع داده size_t
ویرایشماکروی نوع داده size_t سرنام size type به معنی « اندازه نوع » ، اندازه اشیائی همچون آرایهها ، بلوکهای حافظه ، ساختمانها و رشتهها را به بایت در خود ذخیره می کند . اندازه size_t هرچه که باشد منفی نیست . اندازه size_t در C99 شانزده بیت تعریف شد و در حال حاضر حداکثر مقدار ذخیره شده در کامپایلرها و سیستمهای ۳۲ بیتی قدیمی ۲ بایت و در سیستمهای ۶۴ بیت با کامپایلرها و سیستمهای قدیمیتر ۴ بایت و در سیستمهای جدید ۸ بایت است یعنی به اندازه یک int یا یک long long int بدون علامت . اندازه size_t توسط عملگر sizeof به دست میآید . استفاده از size_t به تخصیص حافظه پویا کمک می کند . ضمناً size_t علاوه بر فایل سرآیند stdio در فایلهای سرآیند stddef و stdlib و string و time و wchar تعریف شده است . نکته : وقتی می خواهید اندازه شیئای را به دست بیاورید بهتر است از size_t استفاده کنید ؛ همچنین برای تعریف اندازههای بزرگ ، جهت قابل حمل بودن برنامه خود بهتر است از size_t استفاده کنید
مثال :
#include <stdio.h>
int main()
{
int array[5] = { 1, 2, 3, 4, 5 };
size_t size = sizeof(array);
printf("The size of the array is: %zu\n", size);
return 0;
}
توضیح : در تابع اصلی برنامه ، یک آرایه صحیح به نام array تعریف نمودهایم که ۵ عنصر دارد و هر صحیح ( int ) در سیستمهای ۶۴ بیتی ۴ بایت است . سپس یک نوع داده size_t با شناسه و نام size تعریف نمودهایم که مقدار آن مقدار array است . سپس در تابع printf در خروجی خطدستوری ، چاپ کردهایم اندازه آرایه مورد نظر به اندازه size می باشد که این کار را با تعیینکننده فرمت z به همراه u که سرنام unsigned می باشد نوشته و آرگومان size را به آن فرستادهایم تا جایگزین شود . در پایان ، تابع اصلی برنامه با موفقیت به پایان می رسد و در صورت کامپایل و اجرای برنامه با اندازه آرایه ۲۰ است ، مواجه خواهیم شد ( ۵ عنصر که هرکدام ۴ بایت را اشغال می کند )
دقت کنید : در تعاریف استانداردهای قدیمیتر مثل C98/C99 ماکروی نوع داده size_t طوری تعریف شده است که تعیینکننده فرمت آن d یا ul یا ull است و نه zu . بنابراین در هنگام نوشتن کد دقت کنید که کامپایلر شما با چه استانداردی ، مطابقت دارد
تابع fread
ویرایشتابع fread سرنام file read به معنی « فایل را بخوان » فایل فرستاده شده به خود را به تعدادی که تعیین کردهایم و تعداد بایتهایی که باید یکجا خوانده شوند ، در یک متغیر اشارهگر که عموماً باید یک آرایه باشد ذخیره می کند . شکل کلی ایجاد تابع fread بدین صورت می باشد :
fread(void * buffer, size_t size, size_t count, FILE * stream);
فایل stream به تعداد count و اندازه هر یک بلوک از بایتهای size در buffer ذخیره می شود . تابع fread در صورت موفقیتآمیز بودنِ عملکرد خود ، تعداد نوشتههای خود ( count ) را باز می گرداند ولی در صورتی که از تعداد count کمتر شود یعنی خطایی رخ داده و یا به پایان فایل رسیدهایم ( EOF ) . اگر size و یا count را 0 بنویسید تابع مقدار 0 را باز میگرداند اما اختلالی در جریان فایل و یا دادههایی که فرستادهایم ایجاد نمی شود و برنامه به ادامه خود می پردازد . مثال :
#include <stdio.h>
int main(void)
{
FILE * fp;
char buf[100];
fp = fopen ("alphabet.txt", "r");
fread(buf, 1, 100, fp);
printf("%s\n", buf);
fclose(fp);
return 0;
}
در مثال بالا ابتدا فایل fp را آماده ایجاد نمودهایم . بعد از آن ، رشته ۱۰۰ کاراکتری buf را اعلان نمودهایم . سپس فایل fp را تعیین کردهایم تا توسط تابع fopen برای خوانده شدن باز شود که همان فایل alphabet.txt می باشد . تابع fread صد بایت را دانه به دانه ( چون برای size نوشتهایم 1 ) از فایل fp می خواند و در buf ذخیره می کند . سپس تابع printf آن را در خروجی خطدستوری چاپ می کند که همان حروف الفبای انگلیسی است . سپس فایل را بسته و تابع با موفقیت به اتمام می رسد
در نظر داشته باشید که fread قابلیت خواندن فایلها را به صورت عددی نیز دارد و یکی از مهمترین تابعهای کتابخانهای زبان سی C برای خواندن فایلها به صورت عددی می باشد . مثال :
#include <stdio.h>
int main()
{
FILE * fp = fopen("/home/lovelorn/Pictures/post-3.jpeg", "rb");
unsigned char n;
for (int i = 0; i < 100; i++)
{
fread(&n, 1, 1, fp);
printf("%d\n", n);
}
fclose(fp);
return 0;
}
در مثال بالا فایل post-3.jpeg از قالب و فرمت JPEG را به صورت عددی باز نمودهایم ( شما میتوانید هر فایلی را که داخل سیستم خود دارید قرار داده و آدرس بدهید ) سپس متغیر کاراکتری بدون علامت n را اعلان نمودهایم تا یک بایت جا را در خود بگیرد . پس از آن با یک حلقه for صد بار تابع fread را فراخواندهایم تا از فایل مورد نظر یک بار یک بایت را بخواند و آن را در n ذخیره کند ؛ دقت کنید که بافر باید یک اشارهگر باشد و چون n اشارهگر نیست ، با عملگر آدرسدهی ، در آن مقادیر را ذخیره کرده و سپس توسط تابع printf در خروجی خطدستوری چاپ نمودهایم . دقت کنید که شما درصورتی که هر واحد را ۴ بایت تعریف کنید هر ۴ بایت یک عدد خوانده می شود و در سیستمهای ۳۲ بیتی قدیمی باید تعیین کننده فرمت lu را بنویسید و در صورتی که داده شما ۸ بایتی باشد که آن را بدون علامت تعیین کنید از فرمت llu و در غیر این صورت با خطای نااندازه بودن حافظه مواجه خواهید شد ( که البته همان طور که در پائین نوشتهایم شما باید دادهها را تبدیل کنید ) . در پایان ، فایل مورد نظر خود را بسته و تابع اصلی برنامه را با موفقیت به پایان می رسانیم . در صورتی که با موفقیت برنامه بالا را کامپایل و اجرا کنید با اعداد ۲۵۵ ، ۲۱۶ ، ۲۵۵ و ... ( وابسته به نسخه استاندارد JPEG ) مواجه خواهید شد ( دقت کنید که اکر تعیینکننده فرمت خود را X بنویسید همانند یک هگزادسیمال ادیتور مقادیر برای شما شانزده شانزدهی و به عبارت دیگر در مبنای ۱۶ نوشته خواهد شد )
دقت کنید : تابع fread برای خواندن فایلها به صورت عددی می باشد و اگر بخواهید یک فایل متنی را عددی بخوانید به مشکلات زیادی بر می خورید و بهتر است از fscanf و sscanf و یا gets استفاده کنید . همچنین تابع fread مقادیر عددی را اگر از نوع کاراکتر نباشند از راست به چپ می خواند و اگر آن را چاپ کنید با عدد برعکس شده ۰ و ۱ آن رو به رو خواهید شد . جهت رفع این مشکل در سیستم عاملهای شبهیونیکس و البته ویندوز تابع ntohs سرنام network to host system به معنی عدد شبکه را به سامانه ( سیستم ) میزبان تبدیل کن ، موجود می باشد ( در شبکه اعداد اندین بزرگ یا همان بیگ اندین Big Endian هستند ) تا قبل از چاپ نمودن عدد آن را بر عکس کنید تا درست خوانده شود ولی تابع استانداردی برای این کار وجود ندارد
در سیستم عاملهای شبهیونیکس و با رابط پازیکس ( POSIX ) می توانید برنامه را همانند برنامه زیر بنویسید :
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
FILE * fp = fopen("/home/lovelorn/Pictures/post-3.jpeg", "rb");
unsigned short n;
unsigned short rev;
for (int i = 0; i < 100; i++)
{
fread(&n, 2, 1, fp);
rev = ntohs(n);
printf("%d\n", rev);
}
fclose(fp);
return 0;
}
دقت کنید : گنجایش ntohs فقط ۲ بایت است و چک کنید که در سیستم شما هم short فقط ۲ بایت جا اشغال می کند . اگر در سیستمهای ۳۲ بیتی قدیمی هستید int هم ۲ بایت است . همچنین این مشکل در سیستمهای اندین کوچک ( لیتل اندین Little Endian ) مثل معماریهای x86 یا بعضاً ARM ( که دو اندینه می باشد ) وجود دارد و در سیستمهای اندین بزرگ ( بیگ اندین Big Endian ) وجود ندارد . از سویی برخی از سیستمها فایل سرآیند netinet/in.h را به جای arpa/inet.h برای استفاده از ntohs نیاز دارند و باید آن را ضمیمه برنامه خود کنید و همچنین در ویندوزهای قدیمی تا نسخه ۸٫۱ فایل سرآیند winsock.h و در ویندوزهای جدید باید Winsock2.h را ضمیمه فایل برنامه خود کنید
توضیح : این برنامه همانند برنامه قبلی نوشته شده است با این تفاوت که نوع داده short را انتخاب نمودهایم و یک متغیر دیگر rev مخفف reversed اعلان نموده و دادههای خود را با ntohs در آن ذخیره نمودهایم و سپس مقدار rev را در خروجی خطدستوری چاپ نمودهایم . اگر مقادیر فایل را ۲ بایت به ۲ بایت به مبنای ۱۰ تبدیل کنید خواهید دید که ارقام و مقادیر صحیح و درست هستند و دیگر برعکس نیستند
تابع fwrite
ویرایشتابع fwrite سرنام file write به معنی « فایل را بنویس » دادههای تحویل داده شده به تابع را داخل یک فایل می نویسد . شکل کلی ایجاد تابع fwrite بدین صورت می باشد :
fwrite(const void * ptr, size_t size, size_t nmemb, FILE * fp);
یک داده اشارهگر به اندازه بلوکهای بایتی size و به تعداد nmemb در داخل فایل fp نوشته می شود . تابع fwrite در صورت موفقیتآمیز بودنِ عملکرد خود تعداد nmemb را باز خواهد گرداند و در غیر این صورت پیام خطایی را خواهد فرستاد که توسط تابع ferror می توانید ایراد را پیدا کرده و رفع کنید . مثال :
#include <stdio.h>
int main(void)
{
FILE * fp;
int num[100];
int number;
fp = fopen("myfile.bin", "wb");
for (number = 0; number < 100; ++number)
{
num[number] = number;
}
fwrite(num, 4, 100, fp);
fclose(fp);
return 0;
}
توضیح : فایل fp آماده ساختن شده است . یک آرایه از نوع متغیر صحیح با ۱۰۰ عنصر و با نام num اعلان شده است . همچنین یک متغیر صحیح با نام number اعلان شده است . تعیین کردهایم که فایلی که می خواهیم به صورت عددی بنویسیم myfile.bin نام دارد و باید ساخته شود و یا بازنویسی شود . با یک حلقه for متغیر number به عنوان اندیس num از مقدار ۰ شروع کرده و تا ۹۹ ادامه می یابد و البته قبل از اجرای حلقه number یک بار افزایش خواهد یافت چون عملگر افزایش پیش از number نوشته شده است . num[0] مقداری نمی گیرد ، بنابراین توسط کامپایلر مقدار 0 را خواهد گرفت . در چهار بایت بعدی num[1] برابر مقدار 1 است و سپس 2 و الی آخر . تابع fwrite متغیر آرایهای num را که ۱۰۰ عنصر ۴ بایتی دارد را هر ۴ بایت به ۴ بایت ، ۱۰۰ بار در فایل fp که همان myfile.bin است می نویسد . سپس فایل را بسته و تابع main را با موفقیت به پایان می رسانیم . برای مشاهده نتیجه ، یک پیرایشگر فایل یا همان پیرایشگر هگزادسیمال ( Hex Editor یا همان Hexadecimal Editor ) نیاز دارید که برای تمام سیستم عاملها موجود است
تابع ungetc
ویرایششکل کلی فراخواندن تابع کتابخانهای ungetc که سرنام unget character می باشد به معنی کاراکتر را نگیر بدین صورت است :
ungetc(int ch, FILE * fp);
تابع ungetc بر خلاف getc عمل می کند و تضمین شده است که حداقل یک کاراکتر را که وارد یا خوانده شده است به جریان داده یا جریان فایل بفرستد تا آماده خواندن شود ( دقت کنید که برخی به کارگیرندهها تعداد بیشتری را می فرستند که به ترتیب عکس ذخیره می شود ، یعنی آخرین کاراکتر فرستاده شده ، اولین کاراکتر برای جریان داده یا جریان فایل است که خوانده شود ) همچنین ungetc تنها ورودی getc را به جریان داده و یا فایل باز نمی گرداند و شامل دیگر گیرندهها نظیر scanf نیز می شود . مهم است بدانید که کامپایلر ، کاراکتر تعریف شده توسط ungetc از نوع صحیح int را به نوع داده unsigned char تبدیل می کند . شما می توانید به جای نوشتن متغیری جهت فرستاده شدن به جریان ، یک کاراکتر نوشتاری را نیز بفرستید . نتیجه هر دو این است که زمانی که یک تابع بخواهد از جریان ورودی داده یا فایل ، کاراکتری را بخواند ، کاراکتر فرستاده شده از ungetc را جهت نوشتن به خروجی می فرستد . تابع ungetc در صورت موفقیتآمیز بودن عملکرد خود ، کاراکتری را که به جریان داده و یا فایل فرستاده است ، باز می گرداند و در غیر این صورت مقدار EOF را . اگر ch مقدار EOF را بگیرد ، تنها EOF را باز می گرداند که البته طبق استاندارد نباید به ungetc مقدار EOF را فرستاد . کاراکتری که شما با ungetc بازگشت می زنید برای جریان ، لازم نیست همان کاراکتری باشد که بار آخر ، توسط جریان خوانده شده است و در واقع ضرورتی ندارد که کاراکترهایی را از جریان داده بخوانید که قبل از آن توسط ungetc بازگشت خورده و خوانده نشدهاند . اما این کار در نوشتن برنامه تقریباً بیهوده و عجیب است . معمولاً از ungetc برای نخواندن یک کاراکتر که از همان جریان داده یا فایل برای خواندن استفاده شده ، استفاده می شود . شما مجاز به چند بار استفاده مکرر از ungetc به صورت پشت سر هم بدون تابعهای خواندن از ورودی استاندارد ( صفحه کلید Keyboard ) یا فایل نیستید . اما برخی به کارگیرندهها این اجازه را می دهند . بازگرداندن کاراکترها توسط ungetc فایل را تغییر نمی دهد . اگر از یک تابع جهت تغییر مکان خواندن فایل مثل fseek یا fseeko یا rewind یا حتی fflush ( که در ادامه خواهند آمد ) استفاده شود ungetc نادیده گرفته می شود . نخواندن یک کاراکتر توسط ungetc در فایلی که به انتهای خود رسیده ، علامت انتهای فایل را پاک می کند چون یک کاراکتر را جهت ورود آماده می کند ولی بعد از آنکه کاراکتر را خواندید اگر دوباره بخواهید بخوانید با انتهای فایل EOF مواجه خواهید شد . مثال :
#include<stdio.h>
int main(void)
{
int ch = 0;
FILE * fr;
fr = fopen("alphabet.txt", "r");
while(ch != EOF)
{
ch = getc(fr);
putchar(ch);
ungetc(ch, fr);
printf("\n%c", ch);
break;
}
return 0;
}
توضیح : یک متغیر که قرار است نقش یک کاراکتر را ایفا کنید با نام ch تعریف کردهایم . فایل fr را آماده ایجاد نمودهایم . سپس توسط تابع fopen فایل alphabet.txt را که در همین صفحه ایجاد کردیم ، چهت خواندن باز می کنیم . سپس حلقه while تا زمانی که به پایان فایل نرسیدهایم ch را توسط getc از فایل fr دریافت می کند و سپس آن را چاپ می کند . تابع ungetc آخرین کاراکتر خوانده شده را به جریان می فرستد . و بعد از آن با تابع printf یک بار خط را شکسته و آخرین مقدار باقیمانده در جریان را که همان A می باشد چاپ می کند . سپس حلقه را می شکنیم چرا که در صورتی که آن را نشکنیم ، مقداری که توسط ungetc به جریان ورودی می فرستد باقی می ماند و تا بی نهایت چاپ می شود A در صورتی که حالا فقط ۲ A در خطهای جداگانه داریم
نکته : اگر یک یا چند بار دیگر تلاش کنید تا ch را چاپ کنید باز هم با مقدار A رو به رو خواهید شد ؛ چرا که در ذخیرهگاه جریان داده یا فایل ، A ذخیره شده است و ungetc آن را به ذخیرهگاه ( buffer ) که در ادامه نوشته خواهد شد فرستاده است
تابع fflush
ویرایششکل کلی فراخواندن تابع fflush سرنام file flush به معنی فایل را خالی کن بدین صورت می باشد :
fflush(FILE * stream)
در کرنل سیستم عامل قسمتی وجود دارد که ذخیرهگاه جریان ورودی/خروجی دادهها و فایلها می باشد که به آن بافر Buffer گفته می شود . با بسته شدن فایل یا خروجی موفقیتآمیز برنامه خود به خود بافر ، تخلیه می شود اما گاهی ممکن است برنامه دچار اشکال شود و متوقف گردیده و بدین صورت فایل نوشته نشود . در این صورت ، اگر شما از تابع کتابخانهای fflush استفاده کرده باشید ، فایل بر روی دیسک و یا خروجی استاندارد نوشته خواهد شد . در استاندارد C تابع fflush برای ورودی ، تعریف نشده است ولی برخی از به کار گیرندهها از آن پشتیبانی می کنند . اما قبل از اینکه برای ورودی از آن استفاده کنید ، راهنمای کامپایلر خود را مطالعه کنید ؛ چرا که ممکن است با یک رفتار تعریفنشده مواجه شوید