اگر تازه وارد دنیای برنامهنویسی به زبان C شدهاید، حتماً با این سوال مواجه شدهاید: “چطور میتوانم از کاربر ورودی بگیرم؟” زبان C برای این کار تابع استانداردی به نام scanf در اختیارمان قرار داده است.
تابع scanf یکی از پرکاربردترین و در عین حال چالشبرانگیزترین توابع زبان C است. بسیاری از برنامهنویسان تازهکار با مشکلاتی مثل نادیده گرفته شدن ورودی، رفتار غیرمنتظره برنامه، یا حتی کرش کردن برنامه مواجه میشوند. اما نگران نباشید! در این مقاله از سی اس فیفتی ارومیه قرار است به صورت جامع و کامل با این تابع آشنا شویم، نکات ریز و درشت آن را یاد بگیریم و در نهایت به یک برنامهنویس حرفهای در زمینه دریافت ورودی تبدیل شویم.
مقدمه
تابع scanf چیست و چه کاربردی دارد؟
ساختار کلی scanf
مشخصکنندههای قالب (Format Specifiers)
گرفتن انواع داده با scanf
گرفتن عدد صحیح (int)
گرفتن عدد اعشاری (float و double)
گرفتن کاراکتر (char)
گرفتن رشته (string)
چرا باید از & استفاده کنیم؟
گرفتن چند ورودی با یک scanf
مشکل بافر ورودی و کاراکتر newline (\n)
علت مشکل
راهحلهای رفع مشکل بافر
محدودیتهای scanf و معایب آن
مشکل سرریز بافر (Buffer Overflow)
عدم خواندن رشته با فاصله
رفتار نامشخص در صورت ورودی نامعتبر
تکنیکهای پیشرفته با scanf
استفاده از scanset برای کنترل ورودی
تعیین عرض (width) برای جلوگیری از سرریز
خواندن یک خط کامل با %[^\n]
بررسی مقدار برگشتی scanf
جایگزینهای بهتر برای scanf
fgets برای خواندن رشته
getchar برای خواندن کاراکتر
نکات ریز و کاربردی (Pro Tips)
جمعبندی و نتیجهگیری
ویدیوی آموزشی
منابع و مراجع
تابع scanf یک تابع استاندارد در زبان C است که در فایل هدر stdio.h تعریف شده است . کار اصلی این تابع دریافت دادههای ورودی از کاربر (معمولاً از طریق صفحهکلید) و ذخیرهسازی آنها در متغیرهای مشخص شده است .
بدون توابع ورودیگیر، برنامههای ما همیشه با دادههای ثابت کار میکنند و عملاً تعاملی با کاربر ندارند. با استفاده از scanf میتوانیم برنامههایی بنویسیم که بر اساس نیاز و ورودی کاربر، رفتار متفاوتی داشته باشند.
ساختار کلی تابع scanf به شکل زیر است :
scanf("format string", &variable1, &variable2, ...);
format string: یک رشته است که شامل مشخصکنندههای قالب (format specifiers) مثل %d، %f، %c و … میباشد. این رشته مشخص میکند که چه نوع دادهای از کاربر انتظار داریم.
variable1, variable2, …: آدرس متغیرهایی هستند که میخواهیم دادههای ورودی در آنها ذخیره شوند. توجه کنید که برای اکثر متغیرها باید از علامت & قبل از نام متغیر استفاده کنیم .
مهمترین مشخصکنندههایی که در scanf استفاده میشوند عبارتند از :
سادهترین کاربرد scanf، گرفتن یک عدد صحیح است :
#include <stdio.h> int main() { int age; printf("لطفاً سن خود را وارد کنید: "); scanf("%d", &age); printf("سن وارد شده: %d\n", age); return 0; }
نکته مهم: اگر کاربر به جای عدد، حرف یا کاراکتر دیگری وارد کند، scanf موفق به خواندن نمیشود و متغیر age مقدار قبلی خود (که مقدار زبالهای از حافظه است) را حفظ میکند .
برای گرفتن اعداد اعشاری از %f برای float و %lf برای double استفاده میکنیم:
#include <stdio.h> int main() { float height; double weight; printf("قد خود را بر حسب متر وارد کنید: "); scanf("%f", &height); printf("وزن خود را بر حسب کیلوگرم وارد کنید: "); scanf("%lf", &weight); printf("قد: %.2f متر، وزن: %.2lf کیلوگرم\n", height, weight); return 0; }
برای گرفتن یک کاراکتر از %c استفاده میکنیم:
#include <stdio.h> int main() { char grade; printf("نمره خود را وارد کنید (A, B, C, ...): "); scanf("%c", &grade); printf("نمره شما: %c\n", grade); return 0; }
برای گرفتن رشته از %s استفاده میکنیم. توجه کنید که برای آرایهها نیازی به & نیست، زیرا نام آرایه خودش آدرس شروع آرایه است :
#include <stdio.h> int main() { char name[50]; printf("نام خود را وارد کنید: "); scanf("%s", name); // بدون & printf("سلام %s!\n", name); return 0; }
هشدار: تابع scanf با %s فقط تا اولین فضای خالی (space, tab, newline) را میخواند. بنابراین اگر نام شما دو کلمهای باشد (مثلاً “محمد رضا”)، فقط قسمت اول در متغیر ذخیره میشود .
یکی از سوالات رایج بین مبتدیان این است: “چرا برای بعضی متغیرها از & استفاده میکنیم و برای بعضی نه؟”
در زبان C، وقتی متغیری را تعریف میکنیم، آن متغیر در حافظه دارای یک آدرس است. تابع scanf برای اینکه بتواند مقدار وارد شده توسط کاربر را مستقیماً در آن مکان حافظه ذخیره کند، نیاز به آدرس متغیر دارد، نه مقدار آن .
علامت & عملگر “آدرس” (address-of) است و آدرس متغیر را برمیگرداند. پس وقتی مینویسیم &age، یعنی آدرس متغیر age را به scanf میدهیم تا بتواند مقدار را در آن آدرس بنویسد.
دلیل عدم استفاده از & برای آرایهها: نام یک آرایه در C، خودش به عنوان آدرس شروع آرایه در نظر گرفته میشود. بنابراین برای آرایهها نیازی به & نیست .
میتوانیم چندین مقدار را در یک خط و با یک scanf دریافت کنیم :
#include <stdio.h> int main() { int id, age; float score; printf("شناسه، سن و نمره خود را وارد کنید (با فاصله جدا کنید): "); scanf("%d %d %f", &id, &age, &score); printf("شناسه: %d، سن: %d، نمره: %.2f\n", id, age, score); return 0; }
همچنین میتوانیم از کاراکترهای جداکننده دیگری مثل کاما استفاده کنیم:
scanf("%d,%d,%f", &id, &age, &score); // کاربر باید بین مقادیر کاما بگذارد: 101,25,18.5
نکته: در این حالت، کاربر باید دقیقاً همان کاراکترهای جداکننده را وارد کند، در غیر این صورت scanf با خطا مواجه میشود.
یکی از رایجترین مشکلاتی که برنامهنویسان C با آن مواجه میشوند، مشکل بافر ورودی و کاراکتر newline است. بیایید با یک مثال این مشکل را بررسی کنیم:
scanf(" %c", &ch); // فاصله قبل از %c باعث میشود whitespaceهای قبلی نادیده گرفته شوند
راهحل ۲: استفاده از fflush (غیراستاندارد)
در برخی کامپایلرها میتوان از fflush(stdin) برای پاک کردن بافر استفاده کرد :
scanf("%d", &number); fflush(stdin); // پاک کردن بافر ورودی (غیراستاندارد) scanf("%c", &ch);
⚠️ توجه: fflush(stdin) رفتاری غیراستاندارد دارد و در همه کامپایلرها کار نمیکند. در کامپایلرهای مبتنی بر لینوکس معمولاً کار نمیکند .
راهحل ۳: استفاده از getchar() برای خالی کردن بافر
یک راه استانداردتر، استفاده از getchar() برای خالی کردن بافر است:
scanf("%d", &number); while (getchar() != '\n'); // خواندن تمام کاراکترها تا newline scanf("%c", &ch);
تابع scanf با وجود کاربرد فراوان، محدودیتها و معایبی دارد که باید از آنها آگاه باشیم .
هنگام استفاده از %s برای خواندن رشته، اگر کاربر رشتهای بلندتر از ظرفیت آرایه وارد کند، scanf بدون کنترل، دادهها را در حافظه مینویسد و باعث سرریز بافر میشود که میتواند منجر به کرش برنامه یا مشکلات امنیتی جدی شود .
همانطور که قبلاً اشاره کردیم، %s فقط تا اولین فضای خالی را میخواند. بنابراین برای گرفتن رشتههای چندبخشی (مثل نام و نام خانوادگی) مناسب نیست .
اگر کاربر دادهای با نوع اشتباه وارد کند (مثلاً به جای عدد، حرف وارد کند)، scanf ناموفق است و متغیر بدون تغییر میماند. بدتر از آن، داده اشتباه در بافر باقی میماند و میتواند باعث ایجاد حلقه بینهایت در برنامه شود .
یکی از قابلیتهای پیشرفته scanf، استفاده از scanset است. با scanset میتوانیم تعیین کنیم که فقط مجموعه خاصی از کاراکترها را بپذیرد :
char str[100]; scanf("%[A-Za-z]", str); // فقط حروف انگلیسی (بزرگ و کوچک) را میپذیرد
برای نفی یک مجموعه (پذیرش همه کاراکترها به جز مجموعه مشخص) از ^ استفاده میکنیم:
scanf("%[^0-9]", str); // هر چیزی به جز اعداد را میپذیرد
برای جلوگیری از سرریز بافر، میتوانیم حداکثر تعداد کاراکترهایی که قرار است خوانده شود را مشخص کنیم :
char name[20]; scanf("%19s", name); // حداکثر 19 کاراکتر + null-terminator
با استفاده از scanset میتوانیم یک خط کامل (شامل فاصلهها) را تا قبل از newline بخوانیم :
char line[100]; scanf("%99[^\n]", line); // خواندن کل خط تا newline
نکته: این روش هم مشکل newline باقیمانده در بافر را دارد و باید مدیریت شود.
تابع scanf تعداد آیتمهایی که با موفقیت خوانده شده را برمیگرداند. این ویژگی بسیار مفید است و میتوانیم از آن برای اعتبارسنجی ورودی استفاده کنیم :
int num; int result = scanf("%d", &num); if (result != 1) { printf("ورودی نامعتبر است!\n"); // پاک کردن بافر و مدیریت خطا while (getchar() != '\n'); }
اگر scanf با خطا مواجه شود، مقدار EOF (معمولاً 1-) را برمیگرداند .
برای خواندن رشته، fgets گزینه بسیار بهتری است، زیرا:
میتواند فاصلهها را هم بخواند
با تعیین طول، از سرریز بافر جلوگیری میکند
کاراکتر newline را هم در رشته ذخیره میکند
char name[100]; printf("نام کامل خود را وارد کنید: "); fgets(name, sizeof(name), stdin); printf("سلام %s", name);
برای خواندن کاراکترهای تکی، میتوانیم از getchar() استفاده کنیم :
int ch = getchar(); // getchar مقدار int برمیگرداند (برای تشخیص EOF)
یک روش حرفهای برای دریافت ورودی، ترکیب fgets و sscanf است:
char buffer[100]; int number; printf("یک عدد وارد کنید: "); fgets(buffer, sizeof(buffer), stdin); if (sscanf(buffer, "%d", &number) == 1) { printf("عدد وارد شده: %d\n", number); } else { printf("ورودی نامعتبر!\n"); }
این روش کنترل کامل روی بافر و اعتبارسنجی را به ما میدهد.
همیشه بعد از scanf، به خصوص وقتی بعد از آن ورودی دیگری میگیرید، بافر را پاک کنید:
scanf("%d", &num); while (getchar() != '\n'); // پاک کردن بافر
در برخی کامپایلرها میتوانید با setbuf(stdin, NULL) بافر را غیرفعال کنید .
در ++C، ترکیب cin و scanf میتواند باعث کاهش سرعت شود. اگر مجبور به استفاده همزمان هستید، از ios_base::sync_with_stdio(false) استفاده کنید .
برای دریافت اعداد بسیار بزرگ (مثل long long)، از %lld استفاده کنید .
همیشه مقدار برگشتی scanf را بررسی کنید و در صورت خطا، بافر را پاک کنید تا از ایجاد حلقه بینهایت جلوگیری شود.
قرار دادن فاصله در ابتدای format string باعث میشود هرگونه whitespace (از جمله newline) نادیده گرفته شود:
scanf(" %c", &ch); // فاصله باعث نادیده گرفتن whitespaceهای قبلی میشود
در این مقاله از سی اس فیفتی ارومیه با تابع scanf در زبان C آشنا شدیم. یاد گرفتیم:
scanf برای دریافت ورودی از کاربر استفاده میشود
ساختار کلی آن شامل رشته قالب و آدرس متغیرهاست
برای هر نوع داده، مشخصکننده مخصوصی وجود دارد
مشکل بافر و کاراکتر newline یکی از رایجترین چالشهاست
راهحلهای مختلفی برای مدیریت بافر وجود دارد
میتوان با scanset و تعیین عرض، کنترل بیشتری روی ورودی داشت
scanf محدودیتهایی دارد که باید به آنها توجه کرد
جایگزینهایی مثل fgets در برخی موارد مناسبتر هستند
نکته کلیدی که باید همیشه به خاطر داشته باشید: اعتبارسنجی ورودی و مدیریت بافر دو اصل مهم در استفاده صحیح از scanf هستند. با رعایت این نکات، میتوانید برنامههای پایدار و بدون باگ بنویسید.
برای یادگیری بهتر و مشاهده مثالهای عملی بیشتر، ویدیوی آموزشی این مقاله را در کانال سی اس فیفتی ارومیه تماشا کنید:
در این ویدیو، تمام مباحث گفته شده را به صورت عملی پیادهسازی کرده و نکات بیشتری را با مثالهای واقعی بررسی میکنیم.
کتاب “زبان برنامهنویسی C” نوشته کرنیگان و ریچی
مستندات استاندارد زبان C (C11 Standard)
مقالات و منابع معتبر آنلاین
نویسنده: بهراد قاسمی
سی اس فیفتی ارومیه – مرجع تخصصی آموزش برنامهنویسی
✨ با ما حرفهای برنامهنویسی کن ✨
📢 نظرات و پیشنهادات خود را با ما در میان بگذارید. اگر سوالی دارید، در بخش نظرات مطرح کنید تا در اسرع وقت پاسخگو باشیم.