ورود و عضویت
0
سبد خرید شما خالی است
ورود و عضویت
0
سبد خرید شما خالی است

آموزش جامع scanf در زبان سی – از صفر تا صد با نکات کاربردی

Comprehensive Guide to scanf in C – From Zero to Hero with Practical Tips
0 دیدگاه

اگر تازه وارد دنیای برنامه‌نویسی به زبان C شده‌اید، حتماً با این سوال مواجه شده‌اید: “چطور می‌توانم از کاربر ورودی بگیرم؟” زبان C برای این کار تابع استانداردی به نام scanf در اختیارمان قرار داده است.

تابع scanf یکی از پرکاربردترین و در عین حال چالش‌برانگیزترین توابع زبان C است. بسیاری از برنامه‌نویسان تازه‌کار با مشکلاتی مثل نادیده گرفته شدن ورودی، رفتار غیرمنتظره برنامه، یا حتی کرش کردن برنامه مواجه می‌شوند. اما نگران نباشید! در این مقاله از سی اس فیفتی ارومیه قرار است به صورت جامع و کامل با این تابع آشنا شویم، نکات ریز و درشت آن را یاد بگیریم و در نهایت به یک برنامه‌نویس حرفه‌ای در زمینه دریافت ورودی تبدیل شویم.


فهرست مطالب

  1. مقدمه

  2. تابع scanf چیست و چه کاربردی دارد؟

  3. ساختار کلی scanf

  4. مشخص‌کننده‌های قالب (Format Specifiers)

  5. گرفتن انواع داده با scanf

    • گرفتن عدد صحیح (int)

    • گرفتن عدد اعشاری (float و double)

    • گرفتن کاراکتر (char)

    • گرفتن رشته (string)

  6. چرا باید از & استفاده کنیم؟

  7. گرفتن چند ورودی با یک scanf

  8. مشکل بافر ورودی و کاراکتر newline (\n)

    • علت مشکل

    • راه‌حل‌های رفع مشکل بافر

  9. محدودیت‌های scanf و معایب آن

    • مشکل سرریز بافر (Buffer Overflow)

    • عدم خواندن رشته با فاصله

    • رفتار نامشخص در صورت ورودی نامعتبر

  10. تکنیک‌های پیشرفته با scanf

    • استفاده از scanset برای کنترل ورودی

    • تعیین عرض (width) برای جلوگیری از سرریز

    • خواندن یک خط کامل با %[^\n]

    • بررسی مقدار برگشتی scanf

  11. جایگزین‌های بهتر برای scanf

    • fgets برای خواندن رشته

    • getchar برای خواندن کاراکتر

  12. نکات ریز و کاربردی (Pro Tips)

  13. جمع‌بندی و نتیجه‌گیری

  14. ویدیوی آموزشی

  15. منابع و مراجع


تابع scanf چیست و چه کاربردی دارد؟

تابع scanf یک تابع استاندارد در زبان C است که در فایل هدر stdio.h تعریف شده است . کار اصلی این تابع دریافت داده‌های ورودی از کاربر (معمولاً از طریق صفحه‌کلید) و ذخیره‌سازی آن‌ها در متغیرهای مشخص شده است .

بدون توابع ورودی‌گیر، برنامه‌های ما همیشه با داده‌های ثابت کار می‌کنند و عملاً تعاملی با کاربر ندارند. با استفاده از scanf می‌توانیم برنامه‌هایی بنویسیم که بر اساس نیاز و ورودی کاربر، رفتار متفاوتی داشته باشند.


ساختار کلی scanf

ساختار کلی تابع scanf به شکل زیر است :

scanf("format string", &variable1, &variable2, ...);
  • format string: یک رشته است که شامل مشخص‌کننده‌های قالب (format specifiers) مثل %d، %f، %c و … می‌باشد. این رشته مشخص می‌کند که چه نوع داده‌ای از کاربر انتظار داریم.

  • variable1, variable2, …: آدرس متغیرهایی هستند که می‌خواهیم داده‌های ورودی در آن‌ها ذخیره شوند. توجه کنید که برای اکثر متغیرها باید از علامت & قبل از نام متغیر استفاده کنیم .

مشخص‌کننده‌های قالب (Format Specifiers)

مهم‌ترین مشخص‌کننده‌هایی که در scanf استفاده می‌شوند عبارتند از :

گرفتن انواع داده با scanf

گرفتن عدد صحیح (int)

ساده‌ترین کاربرد scanf، گرفتن یک عدد صحیح است :

#include <stdio.h>

int main() {
    int age;
    printf("لطفاً سن خود را وارد کنید: ");
    scanf("%d", &age);
    printf("سن وارد شده: %d\n", age);
    return 0;
}

نکته مهم: اگر کاربر به جای عدد، حرف یا کاراکتر دیگری وارد کند، scanf موفق به خواندن نمی‌شود و متغیر age مقدار قبلی خود (که مقدار زباله‌ای از حافظه است) را حفظ می‌کند .

گرفتن عدد اعشاری (float و double)

برای گرفتن اعداد اعشاری از %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;
}

گرفتن کاراکتر (char)

برای گرفتن یک کاراکتر از %c استفاده می‌کنیم:

#include <stdio.h>

int main() {
    char grade;
    printf("نمره خود را وارد کنید (A, B, C, ...): ");
    scanf("%c", &grade);
    printf("نمره شما: %c\n", grade);
    return 0;
}

گرفتن رشته (string)

برای گرفتن رشته از %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

می‌توانیم چندین مقدار را در یک خط و با یک 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 با خطا مواجه می‌شود.

مشکل بافر ورودی و کاراکتر newline (\n)

یکی از رایج‌ترین مشکلاتی که برنامه‌نویسان C با آن مواجه می‌شوند، مشکل بافر ورودی و کاراکتر newline است. بیایید با یک مثال این مشکل را بررسی کنیم:

#include <stdio.h>

int main() {
    int number;
    char ch;
    
    printf("یک عدد وارد کنید: ");
    scanf("%d", &number);
    
    printf("یک کاراکتر وارد کنید: ");
    scanf("%c", &ch);  // این scanf کار نمی‌کند!
    
    printf("عدد: %d، کاراکتر: %c\n", number, ch);
    return 0;
}

وقتی این برنامه اجرا می‌شود، به نظر می‌رسد که scanf دوم نادیده گرفته شده و برنامه بدون منتظر ماندن برای ورودی کاراکتر، ادامه می‌یابد .

علت مشکل

وقتی کاربر عدد را وارد کرده و دکمه Enter را می‌زند، دو چیز در بافر ورودی قرار می‌گیرد:

  1. عدد وارد شده

  2. کاراکتر newline (\n) که مربوط به کلید Enter است

تابع scanf با %d عدد را می‌خواند، اما کاراکتر \n را در بافر باقی می‌گذارد. سپس scanf بعدی با %c بلافاصله آن کاراکتر \n باقی‌مانده را به عنوان ورودی می‌خواند و ادامه می‌دهد .

راه‌حل‌های رفع مشکل بافر

راه‌حل ۱: قرار دادن فاصله قبل از %c
ساده‌ترین راه، قرار دادن یک فاصله قبل از %c است :

scanf(" %c", &ch);  // فاصله قبل از %c باعث می‌شود whitespaceهای قبلی نادیده گرفته شوند

راه‌حل ۲: استفاده از fflush (غیراستاندارد)
در برخی کامپایلرها می‌توان از fflush(stdin) برای پاک کردن بافر استفاده کرد :

c
scanf("%d", &number);
fflush(stdin);  // پاک کردن بافر ورودی (غیراستاندارد)
scanf("%c", &ch);

⚠️ توجهfflush(stdin) رفتاری غیراستاندارد دارد و در همه کامپایلرها کار نمی‌کند. در کامپایلرهای مبتنی بر لینوکس معمولاً کار نمی‌کند .

راه‌حل ۳: استفاده از getchar() برای خالی کردن بافر
یک راه استانداردتر، استفاده از getchar() برای خالی کردن بافر است:

c
scanf("%d", &number);
while (getchar() != '\n');  // خواندن تمام کاراکترها تا newline
scanf("%c", &ch);

محدودیت‌های scanf و معایب آن

تابع scanf با وجود کاربرد فراوان، محدودیت‌ها و معایبی دارد که باید از آن‌ها آگاه باشیم .

مشکل سرریز بافر (Buffer Overflow)

هنگام استفاده از %s برای خواندن رشته، اگر کاربر رشته‌ای بلندتر از ظرفیت آرایه وارد کند، scanf بدون کنترل، داده‌ها را در حافظه می‌نویسد و باعث سرریز بافر می‌شود که می‌تواند منجر به کرش برنامه یا مشکلات امنیتی جدی شود .

عدم خواندن رشته با فاصله

همانطور که قبلاً اشاره کردیم، %s فقط تا اولین فضای خالی را می‌خواند. بنابراین برای گرفتن رشته‌های چندبخشی (مثل نام و نام خانوادگی) مناسب نیست .

رفتار نامشخص در صورت ورودی نامعتبر

اگر کاربر داده‌ای با نوع اشتباه وارد کند (مثلاً به جای عدد، حرف وارد کند)، scanf ناموفق است و متغیر بدون تغییر می‌ماند. بدتر از آن، داده اشتباه در بافر باقی می‌ماند و می‌تواند باعث ایجاد حلقه بی‌نهایت در برنامه شود .


تکنیک‌های پیشرفته با scanf

استفاده از scanset برای کنترل ورودی

یکی از قابلیت‌های پیشرفته scanf، استفاده از scanset است. با scanset می‌توانیم تعیین کنیم که فقط مجموعه خاصی از کاراکترها را بپذیرد :

c
char str[100];
scanf("%[A-Za-z]", str);  // فقط حروف انگلیسی (بزرگ و کوچک) را می‌پذیرد

برای نفی یک مجموعه (پذیرش همه کاراکترها به جز مجموعه مشخص) از ^ استفاده می‌کنیم:

c
scanf("%[^0-9]", str);  // هر چیزی به جز اعداد را می‌پذیرد

تعیین عرض (width) برای جلوگیری از سرریز

برای جلوگیری از سرریز بافر، می‌توانیم حداکثر تعداد کاراکترهایی که قرار است خوانده شود را مشخص کنیم :

c
char name[20];
scanf("%19s", name);  // حداکثر 19 کاراکتر + null-terminator

خواندن یک خط کامل با %[^\n]

با استفاده از scanset می‌توانیم یک خط کامل (شامل فاصله‌ها) را تا قبل از newline بخوانیم :

c
char line[100];
scanf("%99[^\n]", line);  // خواندن کل خط تا newline

نکته: این روش هم مشکل newline باقی‌مانده در بافر را دارد و باید مدیریت شود.

بررسی مقدار برگشتی scanf

تابع scanf تعداد آیتم‌هایی که با موفقیت خوانده شده را برمی‌گرداند. این ویژگی بسیار مفید است و می‌توانیم از آن برای اعتبارسنجی ورودی استفاده کنیم :

c
int num;
int result = scanf("%d", &num);

if (result != 1) {
    printf("ورودی نامعتبر است!\n");
    // پاک کردن بافر و مدیریت خطا
    while (getchar() != '\n');
}

اگر scanf با خطا مواجه شود، مقدار EOF (معمولاً 1-) را برمی‌گرداند .


جایگزین‌های بهتر برای scanf

fgets برای خواندن رشته

برای خواندن رشته، fgets گزینه بسیار بهتری است، زیرا:

  • می‌تواند فاصله‌ها را هم بخواند

  • با تعیین طول، از سرریز بافر جلوگیری می‌کند

  • کاراکتر newline را هم در رشته ذخیره می‌کند

c
char name[100];
printf("نام کامل خود را وارد کنید: ");
fgets(name, sizeof(name), stdin);
printf("سلام %s", name);

getchar برای خواندن کاراکتر

برای خواندن کاراکترهای تکی، می‌توانیم از getchar() استفاده کنیم :

c
int ch = getchar();  // getchar مقدار int برمی‌گرداند (برای تشخیص EOF)

ترکیب fgets و sscanf

یک روش حرفه‌ای برای دریافت ورودی، ترکیب fgets و sscanf است:

c
char buffer[100];
int number;

printf("یک عدد وارد کنید: ");
fgets(buffer, sizeof(buffer), stdin);

if (sscanf(buffer, "%d", &number) == 1) {
    printf("عدد وارد شده: %d\n", number);
} else {
    printf("ورودی نامعتبر!\n");
}

این روش کنترل کامل روی بافر و اعتبارسنجی را به ما می‌دهد.


نکات ریز و کاربردی (Pro Tips)

۱. پاک کردن بافر بعد از scanf

همیشه بعد از scanf، به خصوص وقتی بعد از آن ورودی دیگری می‌گیرید، بافر را پاک کنید:

c
scanf("%d", &num);
while (getchar() != '\n');  // پاک کردن بافر

۲. استفاده از setbuf برای غیرفعال کردن بافر

در برخی کامپایلرها می‌توانید با setbuf(stdin, NULL) بافر را غیرفعال کنید .

۳. تفاوت بین scanf و cin در ++C

در ++C، ترکیب cin و scanf می‌تواند باعث کاهش سرعت شود. اگر مجبور به استفاده همزمان هستید، از ios_base::sync_with_stdio(false) استفاده کنید .

۴. دریافت اعداد بزرگ

برای دریافت اعداد بسیار بزرگ (مثل long long)، از %lld استفاده کنید .

۵. مدیریت خطاهای ورودی

همیشه مقدار برگشتی scanf را بررسی کنید و در صورت خطا، بافر را پاک کنید تا از ایجاد حلقه بی‌نهایت جلوگیری شود.

۶. استفاده از فاصله در format string

قرار دادن فاصله در ابتدای format string باعث می‌شود هرگونه whitespace (از جمله newline) نادیده گرفته شود:

c
scanf(" %c", &ch);  // فاصله باعث نادیده گرفتن whitespaceهای قبلی می‌شود

جمع‌بندی و نتیجه‌گیری

در این مقاله از سی اس فیفتی ارومیه با تابع scanf در زبان C آشنا شدیم. یاد گرفتیم:

  • scanf برای دریافت ورودی از کاربر استفاده می‌شود

  • ساختار کلی آن شامل رشته قالب و آدرس متغیرهاست

  • برای هر نوع داده، مشخص‌کننده مخصوصی وجود دارد

  • مشکل بافر و کاراکتر newline یکی از رایج‌ترین چالش‌هاست

  • راه‌حل‌های مختلفی برای مدیریت بافر وجود دارد

  • می‌توان با scanset و تعیین عرض، کنترل بیشتری روی ورودی داشت

  • scanf محدودیت‌هایی دارد که باید به آن‌ها توجه کرد

  • جایگزین‌هایی مثل fgets در برخی موارد مناسب‌تر هستند

نکته کلیدی که باید همیشه به خاطر داشته باشید: اعتبارسنجی ورودی و مدیریت بافر دو اصل مهم در استفاده صحیح از scanf هستند. با رعایت این نکات، می‌توانید برنامه‌های پایدار و بدون باگ بنویسید.


ویدیوی آموزشی 🎥

برای یادگیری بهتر و مشاهده مثال‌های عملی بیشتر، ویدیوی آموزشی این مقاله را در کانال سی اس فیفتی ارومیه تماشا کنید:

در این ویدیو، تمام مباحث گفته شده را به صورت عملی پیاده‌سازی کرده و نکات بیشتری را با مثال‌های واقعی بررسی می‌کنیم.


منابع و مراجع

  • کتاب “زبان برنامه‌نویسی C” نوشته کرنیگان و ریچی

  • مستندات استاندارد زبان C (C11 Standard)

  • مقالات و منابع معتبر آنلاین


نویسنده: بهراد قاسمی
سی اس فیفتی ارومیه – مرجع تخصصی آموزش برنامه‌نویسی
✨ با ما حرفه‌ای برنامه‌نویسی کن ✨


📢 نظرات و پیشنهادات خود را با ما در میان بگذارید. اگر سوالی دارید، در بخش نظرات مطرح کنید تا در اسرع وقت پاسخگو باشیم.



 

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Avatar
Behrad
27 بهمن 1404