انجمنهای فارسی اوبونتو
کمک و پشتیبانی => برنامهسازی => نویسنده: 🇬🇧بریتانیای کبیر🇬🇧 در 08 فروردین 1404، 10:19 قظ
-
صفحه زیر دقیقا داره چی رو توظیح میده؟ متوجه نمیشم.
https://sourceware.org/glibc/manual/2.40/html_node/How-Unread.html
-
توی زبان سی یه تابع هست به اسم getc که باهاش یه کاراکتر رو از یه استریم میخونه. به فرض اگر برنامه تو در حال اجرا است و یکی یه کلید رو روی کیبرد می زنه، تو می تونی با این دستور بخونیش. اما یه وقتی هست که به هر دلیلی از این خوندن پشیمون شدی ((: می تونی با ungetc کاراکتر رو به استریم برگردونی. مثالی که زده اینه که کاراکترها رو می خونی و وقتی یه چیزی به جز اسپیس بود بر می گردونیش تا بعدا دوباره خونده بشه.
-
برای فهم بهتر این مثالها میتونید یک برنامه ساده براش بنویسید.
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
Run
$ gcc -Wall run.c -o run; ./run
*/
void skip_whitespace(FILE *stream) {
int c;
do {
/* No need to check for EOF because it is not
isspace, and ungetc ignores EOF. */
c = getc(stream);
printf("read: %c, %d\n", c, c);
//} while (!isspace(c));
} while (isspace(c));
int b = ungetc(c, stream);
printf("put: %c, %d\n", b, b);
}
int main(void) {
// printf("Hell world!\n");
// char str[] = " simple text";
// printf("%s\n", str);
FILE *fptr = fopen("test.txt", "r");
char string[100] = "";
fgets(string, 100, fptr);
// print the file content
printf("stream:\n%slen: %ld\n", string, strlen(string));
// move pointer to start position
rewind(fptr);
skip_whitespace(fptr);
char string2[100] = "";
fgets(string2, 100, fptr);
printf("stream:\n%slen: %ld\n", string2, strlen(string2));
// close the file
fclose(fptr);
return 0;
}
این یک نمونه از اجرای برنامه روی فایلی با محتویات متن هست. همانطور که میبینید کاراکترهای فاصله را که در ابتدا قرار دارند از جریان داده fptr حذف کرده.
$ gcc -Wall run.c -o run; ./run
stream:
simple
len: 9
read: , 32
read: , 32
read: s, 115
put: s, 115
stream:
simple
len: 7
-
فقط من نفهمیدم چطوری ungetc بدون ویرایش پرونده یک حرفی که درون پرونده نیستو میتونه به جای یک حرف دیگه که درون پرونده هست بذاره تا اون حرف اصلیه خوانده نشه. اون حرف جدیده کجا ذخیره میشه؟
حله
تو همون بافر ذخیره میشه. بعد از rewine یا fseek پاک میشه.
-
سعی میکنم روال برنامه را توضیح بدم.
ما برای کار با فایل یک اشارهگر میسازیم. بعد از باز کردن فایل fptr به ابتدای اون فایل اشاره میکنه.
این اشاره از نوع آدرس باینری است.
(البته بهتر بود در برنامه صحت باز کردن فایل را هم چک میکردیم که اشاره گر از نوع NULL نباشد)
در ادامه کار تابع fgets به میزان حداکثر صد کاراکتر از جایی که fptr به آن اشاره میکنه میخونه و داخل یک آرایه از جنس کاراکتر ذخیره میکنه. که عملا میشه خوندن تمام محتویات فایل تا انتها.
(با خود فایل روی دیسک کاری نداریم فقط ازش خوندیم)
و بعد این آرایه کارکتری را چاپ کردیم تا داخلش را ببینیم.
(در C یک آرایه از کاراکترها که به کاراکتر 0 ختم بشه رشته یا string نامیده میشه.)
چون fgets اشاره گر فایل را به انتها حرکت داده rewind را اجرا کردیم تا برگرده سرجای اول خودش یعنی ابتدای فایل.
و در ادامه fptr به تابع skip_whitespace پاس داده شده تا روی آن عملیات انجام بده.
در حلقهٔ درونی این تابع از محل اشاره گر فایل یک کاراکتر یک کاراکتر توسط تابع getc خوانده شد.
کاراکتر خونده شده به همراه کد ASCII معادل اون را چاپ کردیم تا کار واضحتر باشه.
حلقه تا زمانی که کاراکتر خوانده شده space باشه ادامه پیدا میکنه. به عبارت دیگه وقتی دیگه کاراکتر space نباشه کار حلقه تموم شده است.
حالا آخرین کاراکتر (غیر space) داخل متغیر c قرار دارد.
در اینجا ungetc اجرا میشه. و متغیر c را به درون stream یا همان چیزی که اشارهگر fptr به آن اشاره میکنه بر میگردونه.
و در ادامه اون کاراکتر را هم چاپ میکنه.
اجرای تابع به اتمام رسیده و کار تمام است.
حالا چون ارجاع متغیر fptr به درون تابع از نوع reference بوده(البته احتمالا این اصطلاح در اینجا دقیق نیست) و تغییراتی که داخل تابع روی fptr انجام دادیم حفظ خواهد شد.
عملا با این کار باعث شدیم که تمام کاراکترهای space را از ابتدای stream حذف کنیم.
ما با ساخت یک اشارهگر فایل یعنی fptr از محتویات فایل test.txt یک جریان یا stream ساختیم.
توجه کنید که تمام این حرکات هیچ اثری روی خود فایل اصلی که روی دیسک قرار دارد، ندارد و محتویات آن دست نخورده باقی مانده است. چون صرفا محتوای فایل را روی حافظه بارگذاری کردیم و یکسری عملیات روی آن انجام دادیم.
این دو صفحه را ببینید.
man ascii
man fgetc
-
فقط من نفهمیدم چطوری ungetc بدون ویرایش پرونده یک حرفی که درون پرونده نیستو میتونه به جای یک حرف دیگه که درون پرونده هست بذاره تا اون حرف اصلیه خوانده نشه. اون حرف جدیده کجا ذخیره میشه؟
حله
تو همون بافر ذخیره میشه. بعد از rewine یا fseek پاک میشه.
قبل از اینکه skip_whitespace اجرا بشه rewind را اجرا کردیم. این به خاطر این است که اشاره گر برگرده به ابتدای فایل چون با fgets رفته آخر.
میتونید این دستور rewind را حذف کنید و همزمان fgets قبلی را هم حذف کنید. باز هم برنامه درست کار میکنه.
-
حالا چون ارجاع متغیر fptr به درون تابع از نوع reference بوده(البته احتمالا این اصطلاح در اینجا دقیق نیست) و تغییراتی که داخل تابع روی fptr انجام دادیم حفظ خواهد شد.
در زبان C تمام ارجاعها به داخل تابع با مقدار است. یعنی فقط مقدار متغیر به داخل تابع منتقل میشود و تغییرات روی آن متغیر اعمال نخواهند شد یا به عبارت دیگر یک کپی از مقدار متغیر به تابع منتقل میشود و از این پس این مقدار برای این تابع محلی است.
در این مثال درواقع امکان call by reference توسط اشارهگر به نوعی دور زده میشود.
یا اینکه با بهرهگیری از امکانات اشارهگر این امکان پیاده سازی میشود به طوری که شما تصور کنید در اینجا call by reference انجام گرفته است تا در نتیجه تغییراتی که درون تابع، روی fptr انجام شده حفظ شوند.
حالا در این مورد شما دو راه دارید.
۱. مثالی که زده شده را به عنوان call by reference بپذیرید. به صورت حفظی و بدون بحث این نکته را به خاطر بسپارید.
عموما همین روش در آموزش هم پیش گرفته میشه (حداقل تا جایی که من دیدم) یا اینکه به روش درست رفتار زبان C به مسئله نگاه کنید که نیازمند کمی آشنایی با مبحث اشارهگرها است.
۱. میدانیم تمام ارجاعها با مقدار خواهند بود. call by value
یعنی پاس دادن متغیر به داخل تابع، تنها منجر به کپی شدن مقدار از تابع صدا کننده به تابع مقصد میشود.
۲. ارجاع متغیر از نوع اشارهگر fptr هم مقداری است.
۳. چون مقدار ذخیره شده داخل متغیر fptr از جنس آدرس است پس همین آدرس به داخل تابع پاس داده شده است.
۴. چون در تابع روی این متغییر (آدرس حافظه) عملیات انجام میشود در نتیجه حاصل کار روی آدرس حافظه نوشته خواهد شد.
۵. پس محتویات آدرسی از حافظه که متغیر fptr به آن اشاره میکند تغییر خواهد کرد.
۶. در نتیجه اینجا اینطور به نظر میرسد که ارجاع متغیر به تابع از نوع reference بوده است در صورتی که در واقع چنین نیست.
یک نمونه ساده
#include <stdio.h>
void foo(int *x) {
// *x = 12;
//*x = (*x)++;// UB
*x = (*x) + 1;
}
int main(void) {
printf("pointers in C\n");
int a = 10;
int c = 42;
int *b;
b = &c;
printf("a before: %d \n", a);
printf("b before: %d \n", *b);
foo(&a);
foo(b);
printf("a after: %d \n", a);
printf("b after: %d \n", *b);
return 0;
}
اجرا
$ gcc -Wall point.c -o point; ./point
pointers in C
a before: 10
b before: 42
a after: 11
b after: 43