زبان برنامه نویسی سی/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 می باشد
f, F
برای اعداد اعشاری با این تفاوت که f برای بینهایت inf یا infininty و برای تعریفنشدهها nan را به خروجی می فرستد و F برای بینهایت INF یا INFINITY و برای تعریفنشدهها NAN را می فرستد
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 استفاده نمائید که در مبحث مربوطه نوشته خواهد شد
پارامترِ تعیین کننده شماره آرگومان ، جزء استاندارد سی نیست و فقط در پازیکس ( 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 باعث می شود تا سایز متن به تعداد کاراکترهای خروجی باشد و نه به اندازه کاراکترهای موجود که ممکن است چند بایتی و عریض باشند که در 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;
}
مطالب پرچمها کاملاً روشن هستند و نیازی به مثال ندارد اما به عنوان نمونه یک مثال برای پرچم مایناس ( علامت منفی ) می نویسیم :
#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% که در همین مبحث نوشته شد می تواند باعث حمله رشته فرمت شده بشود . که باید بسیار مراقب استفاده آن به صورت صحیح باشید . از طرفی این فرمت کننده باعث ایجاد کامل شدن تورینگ می شود . جهت اطلاعات بیشتر به صفحه زیر مراجعه کنید :