با این فایل سرآیند تا حدودی آشنایی دارید و در طول کتاب بارها به آن اشاره شده است . 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 استفاده نمائید که در مبحث مربوطه نوشته خواهد شد

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 به شما هشدار خواهد داد ؛ اما برنامه کامپایل شده و اجرا می شود ولی دارای رخنه‌پذیری امنیتی خواهد بود که هکرها می‌توانند از روش حمله رشته فرمت شده استفاده کرده و به داده‌های برنامه شما و سیستم عامل دسترسی پیدا کنند . جهت اطلاعات بیشتر صفحه زیر را مطالعه کنید :

Format string attack

همچنین نوشتن تعیین‌کننده فرمت n به صورت n% که در همین مبحث نوشته شد می تواند باعث حمله رشته فرمت شده بشود . که باید بسیار مراقب استفاده آن به صورت صحیح باشید . از طرفی این تعیین‌کننده فرمت باعث ایجاد کامل شدن تورینگ می شود . جهت اطلاعات بیشتر به صفحه زیر مراجعه کنید :

Turing-complete

تابع 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 آنچه را که وارد کرده‌اید در خروجی خط‌دستوری چاپ می کند

ماکروهای 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 کاراکتر بعدی را می خواند و در خود ذخیره می کند و در پایان فایل را بسته و تابع اصلی را با موفقیت به پایان رسانده‌ایم