برای شروع، بیایید یه برنامه طراحی کنیم که هر خطی از ورودی رو که شامل یه «الگو» یا رشتهی خاصی از کاراکترهاست چاپ کنه.
(این در واقع یه حالت خاص از برنامهی معروف UNIX به نام grep هست.)
برای مثال، اگه دنبال الگوی حروف "ould" توی مجموعه خطوط زیر بگردیم:
Ah Love! could you and I with Fate conspire
To grasp this sorry Scheme of Things entire,
Would not we shatter it to bits -- and then
Re-mould it nearer to the Heart's Desire!
خروجی اینطور میشه:
Ah Love! could you and I with Fate conspire
Would not we shatter it to bits -- and then
Re-mould it nearer to the Heart's Desire!
کار بهطور خلاصه به سه بخش تقسیم میشه:
while (یه خط جدید وجود داره)
if (اون خط شامل الگو بود)
چاپش کن
البته ممکنه همهی این کدها رو توی تابع main بنویسیم،
اما راه بهتر اینه که از ساختار تابعی استفاده کنیم و هر بخش رو به یه تابع جدا تقسیم کنیم.
سه قسمت کوچیک خیلی راحتتر از یه بخش بزرگ مدیریت میشن،
چون جزئیات غیرضروری داخل توابع پنهان میشن و احتمال تداخل یا باگ کمتر میشه.
علاوه بر این، ممکنه همین توابع بعداً توی برنامههای دیگه هم قابل استفاده باشن.
عبارت «while یه خط جدید وجود داره» در واقع تابع getline هست که قبلاً تو فصل ۱ نوشتیم،
و «چاپش کن» هم printf هست که از قبل توسط سیستم فراهم شده.
پس تنها کاری که باید بکنیم، نوشتن تابعیه که بررسی کنه آیا یه خط شامل اون الگو هست یا نه.
میتونیم با نوشتن تابع strindex(s, t) این مسئله رو حل کنیم —
این تابع موقعیت یا اندیسی رو برمیگردونه که رشتهی t از اونجا توی s شروع میشه.
اگه s شامل t نباشه، مقدار -1 برمیگردونه.
از اونجایی که اندیسها در آرایههای C از صفر شروع میشن،
یه مقدار منفی مثل -1 برای نشون دادن «پیدا نشدن» خیلی مناسبه.
وقتی بعداً به یه الگوریتم پیچیدهتر برای جستوجوی الگو نیاز پیدا کنیم،
کافیه فقط strindex رو عوض کنیم، بدون اینکه بقیهی برنامه رو دست بزنیم.
(کتابخونهی استاندارد یه تابع به نام strstr داره که شبیه strindex عمل میکنه،
فقط بهجای اندیس، یه اشارهگر (pointer) برمیگردونه.)
تا اینجا طراحی رو انجام دادیم، حالا پیادهسازی خیلی مستقیم و سادهست.
در اینجا کل برنامه رو میبینی تا بهتر متوجه بشی چطور قسمتها با هم جور درمیآن.
فعلاً، الگوی مورد جستوجو یه رشتهی ثابت (literal string) هست که البته خیلی عمومی نیست.
بهزودی در مورد نحوهی مقداردهی اولیهی آرایههای کاراکتری صحبت میکنیم،
و در فصل ۵ یاد میگیریم چطور کاری کنیم که الگو موقع اجرای برنامه بهصورت ورودی تنظیم بشه.
در ضمن، نسخهی getline در اینجا کمی با نسخهی فصل ۱ (مقدمه) فرق داره؛
مقایسهی اون دو میتونه آموزنده باشه.
#include <stdio.h>
#define MAXLINE 1000 /* maximum input line length */
int getline(char line[], int max);
int strindex(char source[], char searchfor[]);
char pattern[] = "ould"; /* pattern to search for */
/* find all lines matching pattern */
main()
{
char line[MAXLINE];
int found = 0;
while (getline(line, MAXLINE) > 0)
if (strindex(line, pattern) >= 0) {
printf("%s", line);
found++;
}
return found;
}
/* getline: get line into s, return length */
int getline(char s[], int lim)
{
int c, i;
i = 0;
while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
s[i++] = c;
if (c == '\n')
s[i++] = c;
s[i] = '\0';
return i;
}
/* strindex: return index of t in s, -1 if none */
int strindex(char s[], char t[])
{
int i, j, k;
for (i = 0; s[i] != '\0'; i++) {
for (j=i, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++)
;
if (k > 0 && t[k] == '\0')
return i;
}
return -1;
}
هر تابع این ساختار رو داره:
نوع_بازگشتی نام_تابع(آرگومانها) {
اعلانها و دستورات
}
بعضی قسمتها میتونن حذف بشن.
حداقلترین حالت یه تابع اینه:
dummy() {}
که هیچ کاری نمیکنه و چیزی هم برنمیگردونه.
گاهی وقتا همچین تابعی موقع توسعهی برنامه مفیده — مثلاً به عنوان یه جایخالی (placeholder).
اگه نوع بازگشتی (return type) رو ننویسیم، بهصورت پیشفرض int در نظر گرفته میشه.
یه برنامه در واقع مجموعهای از تعریف متغیرها و توابعه.
ارتباط بین توابع از طریق آرگومانها، مقادیر بازگشتی و متغیرهای خارجی (external) انجام میشه.
توابع میتونن به هر ترتیبی در فایل منبع قرار بگیرن،
و برنامه میتونه بین چند فایل تقسیم بشه، فقط نباید یه تابع بین دو فایل جدا بشه.
عبارت return راهی برای برگردوندن یه مقدار از تابع فراخوانیشده به تابع فراخوانندهست.
هر عبارت (expression) میتونه بعد از return بیاد:
return expression;
اگه نیاز باشه، اون عبارت به نوع بازگشتی تابع تبدیل میشه.
پرانتز اختیاریه ولی معمولاً برای خوانایی بیشتر استفاده میشه.
تابع فراخواننده میتونه مقدار بازگشتی رو نادیده بگیره.
حتی ممکنه بعد از return هیچ عبارتی نیاد — در اون حالت چیزی برگردونده نمیشه.
اگه اجرای تابع به انتهای بدنه (یعنی } پایانی) برسه،
کنترل بدون برگردوندن مقداری به تابع فراخواننده برمیگرده.
این از نظر سینتکسی مشکلی نداره،
اما معمولاً نشونهی یه اشتباهه اگه تابع از یه مسیر مقدار برگردونه و از مسیر دیگه نه.
در هر صورت، اگه تابعی مقداری برنگردونه ولی انتظار بره که برگردونه،
اون مقدار عملاً «زباله» خواهد بود.
برنامهی جستوجوی الگو مقدار برگشتی main رو به عنوان تعداد خطوط مطابق برمیگردونه،
که سیستم اجراکننده میتونه از اون استفاده کنه.
نحوهی کامپایل و اجرای برنامهای که توی چند فایل منبع نوشته شده،
توی سیستمهای مختلف فرق میکنه.
برای مثال، توی سیستم UNIX، دستور cc که تو فصل ۱ گفتیم این کار رو انجام میده.
فرض کن سه تابع بالا توی سه فایل main.c, getline.c و strindex.c هستن.
اونوقت دستور زیر:
cc main.c getline.c strindex.c
هر سه فایل رو کامپایل میکنه و خروجی هر کدوم (مثل main.o, getline.o, strindex.o)
رو با هم لینک میکنه و فایل اجرایی a.out رو میسازه.
اگه مثلاً فقط توی main.c خطایی وجود داشته باشه،
میتونیم فقط همون فایل رو دوباره کامپایل کنیم و با بقیه لینک کنیم:
cc main.c getline.o strindex.o
دستور cc با استفاده از پسوندهای .c و .o تشخیص میده
کدوم فایل منبعه و کدوم فایل شیء (object file).
تمرین ۱-۴:
تابع strindex(s, t) رو طوری بنویس که آخرین (rightmost) محل وقوع t در s رو برگردونه؛
اگه وجود نداشت، مقدار -1 برگردونه.