آشنایی با توابعی که برای کار با رشتهها و حافظه استفاده میشن.

یسری تابع آماده توی یه کتابخونه به اسم string.h هست که برای کار با رشتهها و حافظه استفاده میشن. برای استفاده از این توابع، باید اول این خط رو بالای برنامت بذاری:
#include <string.h>
توابع موجود تو این فایل/کتابخونه معولا روی char* یا همون آرایهای از char کار میکنن.
معرفی برخی از توابع موجود:
تابع memset: اسم این تابع مخفف Memory Set هست و برای پر کردن یه بخش از حافظه با یه مقدار مشخص استفاده میشه. فرض کن یه جعبه داری با 10 تا خونه. حالا میخوای همه خونههاش رو با حرف A پر کنی. این کار رو با memset میتونی انجام بدی. چرا به همچین چیزی نیاز داریم؟
وقتی یه آرایه تعریف میکنی، ممکنه توش مقدارهای تصادفی باشه (چون حافظه قبلا استفاده شده بوده).
پس قبل از استفاده، باید اون آرایه رو پاک یا مقداردهی اولیه کنی.
اینجاست که memset بهدرد میخوره.
شکل کلی تابع memset (به عنوان ورودی ها دقت کنید) :
memset(Koja Berizam، Chi Berizam، Chand bar Berizam);
مثال واقعی:
#include <stdio.h> #include <string.h> int main() { char myArray[10]; memset(myArray, 'A', 9); // 9 تا خونه اول رو با A پر میکنه myArray[9] = '\0'; // آرایه آخر رو 0\ میزاریم که رشته تموم بشه printf("myArray = %s\n", myArray); return 0; }
تحلیل:
یه آرایه ساختیم به اسم myArray با 10 خونه.
با memset، خونههای 0 تا 8 رو با 'A' پر کردیم.
آخرش (خونه شماره 9) رو گذاشتیم \0 که یعنی اینجا رشته تموم میشه.
printf چاپش میکنه.
خروجی:
myArray = AAAAAAAAA
چند نکته مهم:
- مقدار 'A' در واقع عدد 65عه. ولی مهم نیست، چون حافظه فقط با عدد 1 بایتی پر میشه. یعنی فقط یه عدد بین 0 تا 255 رو قبول میکنه.
- اگه به جای 'A' عدد بدی، مثلا:
memset(myArray, 0, 10);
این یعنی کل آرایه رو با صفر پر کن که معمولا برای پاک کردن اطلاعات استفاده میشه.
- این تابع رشته درست نمیکنه، فقط حافظه رو پر میکنه. اگه بخوای نتیجهش بهعنوان رشته خونده بشه، باید آخرش 0\ رو دستی بذاری.
جمع بندی! به کد زیر دقت کن:
#include <stdio.h> int main() { char myArray[100]; printf("%s", myArray); }
خروجی:
`0∟▲☺
هربار که برنامه رو اجرا کنم، یه مقدار تصادفی بهم نمایش میده! چرا؟ توی کد من اومدم 100 بایت از ram حافظه گرفتم، ممکنه تو حافظهایی که از ram گرفتم، از قبل داده وجود داشته باشه! برای حل این مشکل باید از memset استفاده کنم تا اون حافظه رو تمیز/خالی کنم. مثال:
#include <stdio.h> #include <string.h> int main() { char myArray[100]; memset(myArray, '\0', sizeof(myArray)); }
تابع sizeof تعداد بایت هایی که از حافظه گرفته شده رو برمیگردونه، حالا چون دارم از array استفاده میکنم و گفتم 100 تا خونه یا 100 تا بایت داشته باشه، همون عدد 100 رو برمیگردونه.
تو ورودی اول آرایه رو به memset دادم. تو ورودی دوم کارکتری که میخوام تو تک تک خونهها/بایتها قرار بگیره رو دادم. تو ورودی سوم باید بهش بگم تا کدوم بایت یا خونه رو مقدار دهی کنه که از تابع sizeof استفاده کردم، تعداد بایتها یا همون تعداد خونهها رو به دست میاره و میزاره همونجا و درنتیجه، تمام 100 خونه مقدار 0\ میگیره.
به این کد دقت کن:
char myArray[100] = {0};
تو این مثال هم اومدم تمام خونه های آرایه رو 0\ قرار دادم (کد عددی 0\ میشه 0) ولی اگه بخوام بعدا تابع رو تمیز کنم، از همون memset کمک میگیرم (رایجترین استفاده از memset صفر کردن یک بلوک از حافظه قبل از استفاده از اونه. این کار تضمین میکنه که هیچ داده تصادفی در اون حافظه وجود نداره).
تابع strcpy: این تابع یه string رو از یه جا کپی میکنه به یه جای دیگه. یعنی مقدار رشتهی دوم رو میریزه داخل اولی. ساختار کلی:
strcpy(Jaii ke gharare string dakhelesh rikhte beshe, string asli);
مثال:
#include <stdio.h> #include <string.h> int main() { char src[] = "Hello"; char dest[20]; // مقصد باید فضای کافی داشته باشه memset(dest, '\0', sizeof(dest)); strcpy(dest, src); // string az src copy mishe be dest printf("Source: %s\n", src); printf("Destination: %s\n", dest); }
خروجی:
Source: Hello Destination: Hello
چطوری کار میکنه؟
کاراکتر به کاراکتر، محتویات src رو به dest منتقل میکنه.
وقتی به 0\ میرسه (علامت پایان رشته) کپی رو تموم میکنه.
حتی 0\ هم کپی میشه.
(اگه مقداری از قبل داخل dest وجود داشته باشه، پاک میشه)
یه نکته. من میتونم رشته رو با strcpy هم تعریف کنم که در یکسری مواقع بهتر از روش قبلی هست. اگه رشتت ثابته و فقط همون لحظه لازمش داری این روش بهتره:
char text[] = "Amirhosein"; strcpy(text, "Amirhosein Nazouri"); // خطا میده چون آرایه دیگه جا نداره
اما اگه رشتت قراره بعدا تغییر کنه، بزرگتر بشه، یا بهش چیزی اضافه کنی این روش بهتره:
#include <stdio.h> #include <string.h> int main() { char text[100] = {0}; strcpy(text, "Amirhoein"); printf("%s", text); strcpy(text, "Amirhosein Nazouri"); // این خطا نمیده چون آرایه به اندازه 100 بایت جا داره }
تابع strcat: این تابع برای چسبوندن یه رشته به آخر رشتهی دیگه استفاده میشه. یعنی رشته دوم رو به ته رشته اول اضافه میکنه. strcat مثل + توی پایتونه. ساختار تابع:
strcat(dest, src (stringi ke gharare be dest chasbide beshe));
مثال:
#include <stdio.h> #include <string.h> int main() { char text[50] = "Amir"; strcat(text, "hosein"); printf("%s", text); }
خروجی:
Amirhosein
چطوری کار میکنه؟
از destination شروع میکنه، تا جایی که به 0\ برسه.
از همونجا، رشتهی source رو کاراکتر به کاراکتر اضافه میکنه.
تهش دوباره 0\ میزنه که رشتت تموم شه.
نکات مهم و حساس:
1 - فضای کافی خیلی مهمه:
char name[4] = "Amir"; strcat(name, "hosein"); // اینجا مشکل پیش میاد!
2 - آخر رشته اول باید از قبل 0\ داشته باشه. چون strcat از آخر رشته اول شروع به چسبوندن میکنه. اگه 0\ نباشه، نمیدونه از کجا شروع کنه. مثال اشتباه:
char name[20]; strcat(name, "arta"); // چون ای مقدار اولیه نداره و ممکنه کثیف باشه
مثال درست:
char name[20] = ""; strcat(name, "arta"); // چون ای مقدار اولیه نداره و ممکنه کثیف باشه // یا char name[20]; memset(name, 0, sizeof(name)); strcat(name, "arta"); // چون ای مقدار اولیه نداره و ممکنه کثیف باشه
نکته. تفاوت دو کد زیر چیه؟
char name[20]; char name[20] = "";
تو حالت اول فقط یه آرایه 20تایی از char تعریف کردی، ولی هیچ مقدار اولیه ندادی. یعنی محتوای این آرایه ممکنه:
مقدارهای تصادفی (garbage value) باشه.
تهش 0\ نباشه.
اصلا هیچ معنایی بهعنوان رشته نداشته باشه.
اما، تو حالت دوم به آرایه گفتم مقدار اولیت یه رشته خالی ("") باشه.
رشتهی خالی یعنی فقط یه 0\ آخرش هست و بقیه خونهها صفر نیستن، ولی اون یه 0\ کافیه برای اینکه رشته درستی باشه.
تابع strlen: این تابع تعداد کاراکترهای رشته رو حساب میکنه. البته فقط تعداد کاراکترها قبل از 0\ رو میشمره، نه خود 0\ رو و نه اندازه کل آرایه رو (یعنی طول واقعی رشته رو برمیگردونه). مثال:
char name[] = "Amirhosein"; int len = strlen(name); printf("%d\n", len); // 10 // یا char name2[100] = {0}; strcpy(name2, "Amirhosein"); printf("%d\n", strlen(name2)); // 10 strcat(name2, " Nazouri"); printf("%d\n", strlen(name2)); // 18
تابع strncpy: تابع strncpy مثل strcpyعه ولی با یه فرق مهم! میتونی بگی فقط تا یه تعداد مشخصی از کاراکترها رو کپی کنه. مثال:
#include <stdio.h> #include <string.h> int main() { char source[] = "Amirhossein"; char target[20] = {0}; strncpy(target, source, 5); printf("Result: %s\n", target); }
خروجی:
Result: Amirh
نکته. اگه src کوتاهتر از n باشه، بقیهی dest رو با 0\ پر میکنه. مثال:
char a[10]; strncpy(a, "Hi", 5); // a = {'H', 'i', '\0', '\0', '\0', ...}
به این قسمت از کد دقت کن:
char target[20] = {0};
اگه بهجای {0} = از یه تعریف ساده استفاده میکردی:
char target[20];
اون وقت محتوای حافظه ممکن بود هرچی باشه (garbage memory) و اگه strncpy تهش 0\ نزنه، رشته درست چاپ نمیشه یا حتی برنامه کرش میکنه (پس لازمه چنین مواقعی خودمون دستی 0\ رو قرار بدیم روی کاراکتر آخر)
تابع strncat: تابع strncat برای چسبوندن (append کردن) یه رشته به انتهای یه رشته دیگه استفاده میشه ولی با مشخص کردن تعداد کاراکتری که مجاز هست برای چسبیدن. مثال:
int main() { char name[30] = "Amir"; char name2[] = "hosein Nazouri 21 sale"; strncat(name, name2, 14); printf("%s", name); }
خروجی:
Amirhosein Nazouri
تابع strstr: این تابع میاد توی یه رشته (مثلا یه جمله) دنبال یه کلمه خاص یا تیکهایی از متن میگرده. ساختار تابع:
strstr(Koja Begardam، Donbal chi Begardam);
مثلا:
strstr("salam bar to", "bar");
یعنی بیا توی salam bar to بگرد ببین کلمهی bar هست یا نه. اگه پیدا کنه، اونجایی که پیدا شده رو بهت نشون میده. یه مثال دیگه:
#include <stdio.h> #include <string.h> int main() { char text[] = "salam bar to"; char *natije = strstr(text, "bar"); printf("Natije: %s\n", natije); }
خروجی:
Natije: bar to
اگه بدین شکل بنویسم:
char *natije = strstr(text, "Z");
چون چنین کاراکتر یا جملهایی وجود نداره خروجی زیر برمیگرده:
Natije: (null)
به کد دقت کن، معنی char *netije چیه؟
توی C اشارهگرها (pointers) متغیرهایی هستن که آدرس حافظه یه داده دیگه رو ذخیره میکنن. مثلا:
اگه یه متغیر int x = 10 داشته باشی، x یه عدد ذخیره میکنه.
اما یه اشارهگر مثل int *p = &x آدرس حافظه x رو ذخیره میکنه.
عملگرهای مهم اشارهگرها:
* (ستاره):
تو تعریف متغیر یا تابع: یعنی این متغیر/تابع با اشارهگر کار میکنه.
تو کد: یعنی «محتوای آدرس ذخیرهشده تو اشارهگر».
& (آدرسدهی): آدرس یه متغیر رو میگیره. مثلا x& آدرس x رو میده.
سوال اصلی! چرا تو strstr از * استفاده شده؟
این تابع دنبال زیررشته (bar) تو رشته اصلی میگرده. اگه پیداش کنه، به جای اینکه کل زیررشته رو کپی کنه (که زمان میبره) فقط یه اشارهگر به جایی تو رشته اصلی که زیررشته شروع میشه برمیگردونه. تو C رشتهها، آرایههای char هستن و راحتترین راه برای اشاره به یه قسمت از آرایه، استفاده از اشارهگره. این باعث میشه هم سریع باشه، هم حافظه کمتری مصرف کنه. به این کد دقت کن:
int main() { char text[] = "salam bar to"; char *natije = strstr(text, "bar"); if (natije != NULL) { printf("Natije: %s\n", natije); } else { printf("Zirreshte peyda nashod!\n"); } }
text حالا salam bar to هست. strstr(text, "bar") دنبال bar میگرده و یه اشارهگر به bar برمیگردونه.
natije حالا به bar to اشاره میکنه، چون از bar تا آخر رشته رو نشون میده. خروجی:
Natije: bar to
خروجی strstr معمولا با if بررسی میشه. چرا؟ تابع strstr وقتی زیررشته رو تو رشته اصلی پیدا نمیکنه، یه اشارهگر NULL برمیگردونه. اگه مستقیم این اشارهگر NULL رو به printf با فرمت s% (که انتظار یه رشته معتبر داره) بدی، برنامت ممکنه کرش کنه یا رفتار ناشناخته نشون بده. برای همین، باید همیشه خروجی strstr رو با یه شرط if چک کنی که مطمئن شی NULL نیست. کد:
int main() { char text[] = "salam bar to"; if (strstr(text, "bar") != NULL) { printf("Peyda shod"); } else { printf("Zirreshte peyda nashod!\n"); } }
اینجا چون از strstr مستقیم استفاده کردم و داخل متغیری قرارش ندادم، از pointer استفاده نکردم.
تابع strchr: این تابع مثل تابع قبلی یعنی strstr هست ولی با این تفاوت که فقط دنبال کاراکتر میگرده نه یک رشته کامل. کد:
#include <stdio.h> #include <string.h> int main() { char text[] = "hello amirhossein"; if (strchr(text, 'a') != NULL) printf("Peyda shod\n"); else printf("Peyda nashod.\n"); }
میتونم بدین شکل هم عمل کنم:
#include <stdio.h> #include <string.h> int main() { char text[] = "hello amirhossein"; char *ptr = strchr(text, 'a'); if (ptr != NULL) printf("Peyda shod: %s\n", ptr); else printf("Peyda nashod.\n"); }
خروجی:
Peyda shod: amirhossein
چون اولین کاراکتر a تو رشته hello amirhossein از حرف هشتم شروع میشه و printf از اونجایی که آدرس داده رو نشون میده، از a به بعد رو چاپ میکنه.
نکته. تابع strrchr هم داریم که از سمت راست دنبال رشته میگرده (مثل متد rfind داخل پایتون) کد:
int main() { char text[] = "hello amirhossein nazouri"; char *ptr = strrchr(text, 'a'); if (ptr != NULL) printf("Peyda shod: %s\n", ptr); else printf("Peyda nashod.\n"); }
خروجی:
Peyda shod: azouri
تابع strcmp: این تابع دو رشته رو با هم مقایسه میکنه. خروجی int هست که میگه این دو رشته چطور نسبت به هم قرار میگیرن. چی برمیگردونه؟
0: یعنی دو رشته دقیقا برابر هستن (همه حروف یکی هست).
عدد منفی: یعنی رشتهی اول (str1) از رشتهی دوم (str2) کوچکتره (از نظر ترتیب حرفها)
عدد مثبت: یعنی رشتهی اول بزرگتر از رشتهی دوم هست.
این تابع به ترتیب هر حرف دو رشته رو با هم مقایسه میکنه تا جایی که یا یک حرف متفاوت پیدا کنه یا به انتهای رشتهها برسه. یعنی:
strcmp("apple", "apple") == 0 strcmp("apple", "apricot") < 0 strcmp("banana", "apple") > 0
مثال:
#include <stdio.h> #include <string.h> int main() { char a[] = "amir"; char b[] = "amir"; if (strcmp(a, b) == 0) printf("Strings are equal\n"); else printf("Strings are different\n"); }
خروجی:
Strings are equal
یه مثال دیگه:
char a[] = "Amir"; char b[] = "amir"; int result = strcmp(a, b); printf("%d", result);
خروجی:
-1
تابع strncmp: این تابع دقیقا مثل strcmp هست ولی با یه تفاوت مهم! فقط تا n تا کاراکتر اول از هر رشته رو مقایسه میکنه. چی برمیگردونه؟
0: اگه n کاراکتر اول دو رشته مثل هم باشن.
عدد منفی: اگه تو این n کاراکتر، str1 کوچکتر از str2 باشه.
عدد مثبت: اگه تو این n کاراکتر، str1 بزرگتر از str2 باشه.
چرا ازش استفاده کنیم؟ مثلا وقتی فقط میخوای بررسی کنی که دو رشته تا یه جای خاص شبیه هم هستن. مثلا فقط 3 حرف اول. مثال:
#include <stdio.h> #include <string.h> int main() { char a[] = "Amirhossein"; char b[] = "Amirali"; if (strncmp(a, b, 4) == 0) printf("The first 4 characters are the same!\n"); else printf("The first 4 characters are different!\n"); }
خروجی:
The first 4 characters are the same!
اگه مقدار متغیر b رو به arta تغییر بدم خروجی میشه:
The first 4 characters are different!