> C dilinde okuma yapan bazı fonksiyonların incelenmesi: İş bu fonksiyonlar "getchar", "getc", "scanf", "gets" gibi fonksiyonlardır. Bu fonksiyonlar varsayılan durumda "stdin" dosyasından okuma yapmaktadırlar. Fakat unutmamalıyız ki "gets" fonksiyonu C99 ile "deprecated" hale getirildi, C11 ile de dilden kaldırıldı fakat bir takım derleyiciler hala bünyelerinde bulundurmaktadır. C11 ile dile "gets_s" fonksiyonu da eklendi fakat bu fonksiyonun desteklenmesi derleyicilere bırakıldı. Örneğin, MSVC ve GCC derleyicileri bu fonksiyonu desteklememektedir. Bu dört fonksiyon da aynı tampon üzerinde çalışmaktadır. * Örnek 1, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 120 */ /* # OUTPUT # No: 120 Name: [120 - ] */ int no; char name[15]; /* * Aşağıdaki "scanf" çağrısında klavyeden "120" sayısını girdiğimizi varsayalım. * Dolayısıyla tampona "120\n" karakterleri aktarılacaktır ve "scanf" fonksiyonu * "120" sayısını "no" değişkenine atayacaktır. Artık tamponda '\n' karakteri kaldı. */ printf("No: "); scanf("%d", &no); /* * Tamponda hala bir karakter olduğu için ki '\n' karakteridir, ikinci bir klavyeden * okuma yapılmadı ve iş bu karakter tampondan çekildi. Artık tampon boşaltılmış * durumdadır. */ printf("Name: "); gets(name); printf("[%d - %s]\n", no, name); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 120 Ahmet */ /* # OUTPUT # No: 120 Name: Ahmet [120 - Ahmet] */ int no; char name[15]; /* * Aşağıdaki "scanf" çağrısında klavyeden "120" sayısını girdiğimizi varsayalım. * Dolayısıyla tampona "120\n" karakterleri aktarılacaktır ve "scanf" fonksiyonu * "120" sayısını "no" değişkenine atayacaktır. Artık tamponda '\n' karakteri kaldı. */ printf("No: "); scanf("%d", &no); clear_stdin(); /* * Tampon artık boşaltılmış olduğu için, klavyeden yeniden okuma yapıldı. */ printf("Name: "); gets(name); printf("[%d - %s]\n", no, name); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Şimdi de iş bu fonksiyonların irdelenmesine geçelim: >> "getc" fonksiyonuna daha sonra değinilecektir. >> "getchar" fonksiyonu, "stdin" dosyasından bir karakter okuyan bir fonksiyondur. Eğer tampon tamamiyle boş ise yeni bir satırı tampona çekiyor. "gets" ve "scanf" gibi fonksiyonlar, arka planda "getchar" kullanılarak yazılmış fonksiyonlardır. Yani en temel fonksiyondur diyebiliriz. İş bu fonksiyon, dosyt sonuna geldiğinde ya da "IO" hatası olduğunda "EOF" değeri ile geri dönmektedir. Fonksiyonun prototipi şöyledir; #include int getchar(void); Görüldüğü üzere fonksiyonun geri dönüş değeri "int" türünden. Bunun sebebi tampondaki karakter "0xFF" mi yoksa dosya sonuna mı gelindiğinin bilgisi birbirinden ayırt edilemezdi. Yani kullanıcı "-1" mi girdi yoksa "EOF" konumuna mı gelindi. >> "gets" fonksiyonu, C11 ile dilden kaldırılmış olmasına rağmen derleyiciler bünyelerinde bu fonksiyonu muhafaza etmektedir. Bu fonksiyon, '\n' de dahil olmak üzere okuma yapıyor. Sonrasında parametre olarak geçilen "buffer" alanına '\n' de dahil olmak üzere tampondakileri "buffer" a aktarıyor. Son olarak "buffer" dakilerin son karakterini '\0' ile değiştiriyor. Dolayısıyla tampondaki son karakter olan '\n' karakteri '\0' karakteri ile değiştirilmiş olmaktadır. Tampondaki bütün karakterler okunduğu için, tampon tamamiyle boşaltılmıştır. Eğer tamponda halihazırda karakterler varsa, iş bu fonksiyon klavyeden bir girşi beklemeyecektir. İş bu fonksiyonun prototipi de şu şekildedir; #include char *gets(char *s); Fonksiyon, argüman olarak aldığını geri döndürüyor. Eğer hiç okuma yapmadan direkt olarak "EOF" ile karşılaşırsa, NULL ile geri dönmektedir. "gets" fonksiyonunun dilden kaldırılma sebebi, argüman olarak geçilen "buffer" ın büyüklüğünün yeteri kadar büyük olmaması durumunda, ilgili "buffer" da TAŞMA MEYDANA GELECEĞİDİR. Bu fonksiyon yerine sunulan fakat derleyicilere opsiyonel olarak bıraklın "gets_s" fonksiyonu taşmaya karşı korumalıdır. Bu durumda bizler bi' "gets_s" fonksiyonu da yazabiliriz. "gets_s" fonksiyonunun prototipi de aşağıdaki gibidir; #include char *gets_s( char *str, rsize_t n ); Fonksiyonun birinci parametresi, yazının kaydedileceği "buffer" alanı. İkinci parametresi ise yazının büyüklüğü. "rsize_t" de yine bir tür eş ismi olur, hangi türe ait olduğu derleyicilere opsionel olarak sunulmuştur. Yine "gets_s" gibi çalışan fakat standartlarda olan bir diğer fonksiyon da "fgets" fonksiyonudur. Bu fonksiyonun prototipi de aşağıdaki gibidir; #include char *fgets( char *str, int count, FILE *stream ); İş bu fonksiyonun birinci parametresi, yazının yazılacağı dizinin başlangıç adresi. İkinci parametresi dizinin büyüklüğü. Son parametresi ise kaynak olarak kullanılacak dosyaya ait bilgiler. Bu fonksiyon, tampondaki '\n' karakterini de diziye yerleştirmektedir. Dolayısıyla bizler elle iş bu '\n' karakterini '\0' karakteri ile değiştirmekte fayda vardır. * Örnek 1, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # ankara */ /* # OUTPUT # [97 - nkara] */ /* * Klavyeden "ankara" girdiğimiz zaman, "getchar" sadece 'a' karakterini * bize döndürecektir. Artık tamponda "nkara\n" karakterleri kaldı. */ int ch; ch = getchar(); /* * Tamponda halihazırda karakterler olduğu için, yeniden bir klavyeden * giriş istenmedi. Tampondakiler "name" isimli diziye yerleştirildi. */ char name[15]; gets(name); printf("[%d - %s]\n", ch, name); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # CTRL + D */ /* # OUTPUT # EOF ile karsilasildi. */ char name[15]; if(gets(name) == NULL) printf("EOF ile karsilasildi.\n"); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # *** stack smashing detected ***: terminated */ char name[5]; if(gets(name) == NULL) printf("EOF ile karsilasildi.\n"); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, Temsili "gets" fonksiyonunun yazımı: #include #include #include char* my_gets(char* s); void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Ali Kandemir */ /* # OUTPUT # [Ali] [Kandemir] *** stack smashing detected ***: terminated */ char name[5]; my_gets(name); printf("[%s]\n", name); return 0; } char* my_gets(char* s) { int index = 0; int ch; while((ch = getchar()) != '\n' && ch != EOF) { s[index] = ch; ++index; } if(index == 0 && ch == EOF) return NULL; s[index] = '\0'; return s; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5, Temsili "gets_s" fonksiyonunun yazımı: #include #include #include char* my_gets_s(char* s, size_t n); void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Ahmet Kandemir CTRL + D */ /* # OUTPUT # [Ahme] [Kand] [] */ char name[5]; my_gets_s(name, 5); printf("[%s]\n", name); return 0; } char* my_gets_s(char* s, size_t n) { int ch; size_t i; //for(i = 0; (i < n - 1) && (ch = getchar()) != '\n' && (ch != EOF); ++i) for(i = 0; i < n - 1; ++i) if((ch = getchar()) != '\n' && ch != EOF) s[i] = ch; s[i] = '\0'; if(i == 0 && ch == EOF) return NULL; return s; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 6, #include #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Pehlivanli Ahmet */ /* # OUTPUT # [Pehlivanl] [Ahmet ] */ char name[64]; fgets(name, 10, stdin); printf("[%s]\n", name); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 7, #include #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Pehlivanli Ahmet */ /* # OUTPUT # [Pehlivanl] [Ahmet] */ char name[64]; fgets(name, 10, stdin); char* str; if((str = strchr(name, '\n')) != NULL) *str = '\0'; printf("[%s]\n", name); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> "scanf" fonksiyonu, yine tampondan karakter karakter okuma yapan fakat başarılı yerleştirmelerin adedini geri döndüren bir fonksiyondur. Başarısız ilk yerleştirmede fonksiyon işlevini sonlandırıyor. Eğer hemen "EOF" ile karşılaşırsa, "EOF" ile geri dönüyor. Buradaki önemli olan nokta format karakterine uygun olup olmaması, ilgili karakterin. Prototipi şöyledir; #include int scanf ( const char * format, ... ); İş bu fonksiyon kelimelerin arasındaki boşluk karakterlerini ve kelimelerin arasındaki boşluk karakterini atmakta, kelimenin sonundaki boşluk karakterlerini ki buna '\n' de dahildir, ATMAMAKTADIR. İş bu fonksiyonu kullanırken, format karakterlerinin arasına yazacağımız karakterleri, giriş yaparken de kullanmalıyız. İş bu fonksiyon, başarısız girişimde girişleri tekrardan tampona bıraktığı için, tamponu elle temizlememek gerekebilir. * Örnek 1, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 10 ali */ /* # OUTPUT # result: 1, a: 10, b: 482268176 */ int a, b; int result; /* * Çıktıtan da görüldüğü üzere girişlerin başındaki ve aralardaki * boşluk karakterleri atılmıştır. Başarılı giriş sadece bir * adet olduğu için, "1" ile geri dönmüştür. "ali" yazısı da * değerlendirilmiş fakat tampona geri verilmiştir. */ result = scanf("%d%d", &a, &b); printf("result: %d, a: %d, b: %d\n", result, a, b); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 10/15 */ /* # OUTPUT # result: [2], a: [10], b: [15] */ int a, b; int result; result = scanf("%d/%d", &a, &b); printf("result: [%d], a: [%d], b: [%d]\n", result, a, b); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include #include #include #include char* my_gets_s(char* s, size_t n); void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 10AnkarA */ /* # OUTPUT # result: [1], a: [10], b: [978329392] AnkarA */ int a, b; int result; char buffer[1024]; result = scanf("%d/%d", &a, &b); printf("result: [%d], a: [%d], b: [%d]\n", result, a, b); puts(gets(buffer)); return 0; } char* my_gets_s(char* s, size_t n) { int ch; size_t i; //for(i = 0; (i < n - 1) && (ch = getchar()) != '\n' && (ch != EOF); ++i) for(i = 0; i < n - 1; ++i) if((ch = getchar()) != '\n' && ch != EOF) s[i] = ch; s[i] = '\0'; if(i == 0 && ch == EOF) return NULL; return s; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, #include #include #include #include int disp_menu(void); void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 4 1 2 3 Q */ /* # OUTPUT # Quitting... Adding record... Deleting record... Listing record... -Infinite Loop- */ int option; for(;;) { option = disp_menu(); switch(option) { case 1: printf("Adding record...\n"); break; case 2: printf("Deleting record...\n"); break; case 3: printf("Listing record...\n"); break; case 4: printf("Quitting...\n"); goto EXIT; } } EXIT: return 0; } int disp_menu(void) { printf("1- Add A Record.\n"); printf("2- Delete A Record.\n"); printf("3- List A Record.\n"); printf("4- Quit.\n"); int option; printf("\nChoose an item: "); fflush(stdout); /* * Bizler format karakterine uygun olmayan bir giriş yaptığımız zaman, örneğin 'a' karakteri, * "scanf" fonksiyonu ilgili karakteri tampona geri yerleştiriyor ve fonksiyon sonlanıyor. * "main" içerisindeki "for loop" dolayısıyla tekrardan bu fonksiyon çağrılıyor. Fakat tampon * hala boşaltılmadığı için, klavyeden giriş beklemeden, direkt olarak tampondakini yerleştirmeye * çalışıyor. Yine başarısız oluyor. Az evvelki senaryo tekrar ediyor, ediyor, ediyor... * Bunun önüne geçmek için "scanf" çağrısı sonrasında tamponu boşaltmalıyız. */ scanf("%d", &option); return option; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5, #include #include #include #include int disp_menu(void); void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 4 1 2 3 Q -1 */ /* # OUTPUT # Quitting... Adding record... Deleting record... Listing record... Invalid option!!! Invalid option!!! */ int option; for(;;) { option = disp_menu(); switch(option) { case 1: printf("Adding record...\n"); break; case 2: printf("Deleting record...\n"); break; case 3: printf("Listing record...\n"); break; case 4: printf("Quitting...\n"); goto EXIT; } } EXIT: return 0; } int disp_menu(void) { printf("1- Add A Record.\n"); printf("2- Delete A Record.\n"); printf("3- List A Record.\n"); printf("4- Quit.\n"); int option; printf("\nChoose an item: "); fflush(stdout); if(scanf("%d", &option) != 1 || (option < 0 || option > 4)) { printf("Invalid option!!!\n"); clear_stdin(); return -1; } return option; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } İş bu fonksiyon, başarısız bir giriş okuduğunda ilgili girişi tekrardan tampona bıraktığından bahsetmiştik. İşte bunu yaparken kullandığı fonksiyon ise "ungetc" isimli fonksiyondur. Fonksiyonun prototipi şöyledir; #include int ungetc( int ch, FILE *stream ); Bu fonksiyon da standart bir C fonksiyonudur. Başarı durumunda tampona bırakılan karaktere, başarısızlık durumunda ise "EOF" a geri dönmektedir. >> "getc" fonksiyonu, bir dosyayı "byte byte" okurken işe yaramaktadır. Bilindiği üzere standart C fonksiyonları tamponlu fonksiyonlardır ki gereksiz yere sistem fonksiyonları çağırarak sistemi yavaşlatmasın. Fakat iş bu standart C fonksiyonlarının da çağırmanın bir maliyeti vardır. İşte bu maliyeti azaltmak adına, "fgetc" fonksiyonu yerine, "getc" isimli alternatif bir fonksiyon da C standartlarında bulundurulmuştur. Ek olarak, iş bu fonksiyon bir fonksiyonel makro olarak da yazılabilmektedir. Dolayısıyla diyebiliriz ki "fgetc" ile "getc" arasındaki tek fark, "getc" nin makro olarak da yazılabilir olmasıdır. "getc" fonksiyonunun prototipi de şu şekildedir; #include int getc(FILE *stream); İş bu fonksiyonun özellikleri de "fgetc" ile aynıdır. > Hatırlatıcı Notlar: >> İşimizi standart C fonksiyonları ile halledebiliyorsak, o fonksiyonlar ile halletmeliyiz. Eğer halledemiyorsak ve POSIX fonksiyonlarına ihtiyacımız varsa, her iki standartlardaki fonksiyonları birlikte kullanırken dikkatli davranmalıyız. >> Değişken sayıda argüman alan fonksiyonlar: Bu fonksiyonlar bir şekilde kaç adet argüman almaları gerektiğini bilmeleri gerekiyor. * Örnek 1, #include int main(int argc, char** argv) { /* # OUTPUT # warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=] printf("%d - %d - %d\n", 17, 9); warning: too many arguments for format [-Wformat-extra-args] printf("%d - %d\n", 17, 9, 1993); 17 - 9 - 1336753584 17 - 9 - 1993 17 - 9 */ /* * "printf" fonksiyonu kaç adet geçildiğini hesaplamak için * birinci parametresinde yazılan "%d" formatlayıcılarının * adedine bakıyor. Eğer bunların adedi geçilen argümanın adedinden * fazla ise, fazla olanlar için rastgele değerler çekiyor. */ printf("%d - %d - %d\n", 17, 9); /* * Eğer bunlar aynı ise herhangi bir sorun yok. */ printf("%d - %d - %d\n", 17, 9, 1993); /* * Eğer bunların adedi eksikse, fazla geçilen argüman * ıskartaya çıkartılacaktır. */ printf("%d - %d\n", 17, 9, 1993); return 0; } >> NULL sembolik sabitinin neye karşılık geldiği standartlarca kesin değildir ve aşağıdakilerden birisi olabilir; #define NULL 0 #define NULL (void *) >> C23 standartlarında "NULLptr" dile eklenecektir. Artık C dilinde NULL yerine "NULLptr" kullanmamız gerekebilir. >> C dilinde fonksiyonların imzalarında parantezlerinin içinin boş bırakılması ama tanımlarken parantezlerinin içine "void" yazılması, ilgili fonksiyonu istediğimiz kadar bir argüman ile çağırabiliriz demektir. Öte yandan imzalarında parantezlerin içine "void" yazmamız fakat tanımlarkenki parantezlerinin içinin boş bırakılması, ilgili fonksiyonun argüman almayacağını belirtir. C++ dilinde "void" yazmak ile boş bırakmak aynı şeydir. * Örnek 1, #include void HelloWorld(void); int main(void) { /* # OUTPUT # error: too many arguments to function ‘HelloWorld’ HelloWorld(123); */ /* * İmzasında "void" yazdığı için artık argüman geçemeyiz. */ HelloWorld(123); return 0; } void HelloWorld() { printf("HelloWorld"); } * Örnek 2, #include void HelloWorld(); int main(void) { /* # OUTPUT # HelloWorld */ /* * İmzasında bir şey belirtilmediği için istenildiği kadar * argüman geçilebilir. */ HelloWorld(123, 321, "Ahmet", 'a'); return 0; } void HelloWorld(void) { printf("HelloWorld"); } >> C dilindeki göstericiler için "const" niteleyicisi şu biçimlerde kullanılabilir: ' ' // is a * // pointer to char* x; // x is a pointer to char. const char* y; // y is a pointer to const char. const char*const z; // z is a const pointer to const char. char** n; // n is a pointer to pointer to char. const char** p; // p is a pointer to pointer to const char. const char* const* q; // q is a pointer to const-pointer to const char. const char* const* const i; // i is a const-pointer to const-pointer to const char. Şimdi de örnekleri irdeleyelim: * Örnek 1, int main(void) { /* * Aşağıdaki atama dilin kurallarına göre legaldir. */ char* names[] = { "ali", "veli", "selami" }; char** ppnames; ppnames = names; return 0; } * Örnek 2, int main(void) { /* # OUTPUT # warning: assignment to ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types] ppnames = names; */ /* * Aşağıdaki atama dilin kurallarına göre LEGAL DEĞİLDİR. Çünkü "names" * dizisinin her bir elemanı "char *" türden. Dizinin birinci elemanının adresi * ise "char**" türden. Öte yandan "ppnames" ise "const char**" türden. Burada "const" * özelliğinin düşmesi mevzu bahis değildir. */ char* names[] = { "ali", "veli", "selami" }; const char** ppnames; ppnames = names; return 0; } * Örnek 3, int main(void) { /* # OUTPUT # warning: assignment to ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types] ppnames = names; */ /* * Aşağıdaki atama dilin kurallarına göre legaldir. */ const char* names[] = { "ali", "veli", "selami" }; const char** ppnames; ppnames = names; return 0; } * Örnek 4, int main(void) { /* # OUTPUT # warning: assignment to ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types] ppnames = names; */ /* * Aşağıdaki atama dilin kurallarına göre legaldir. * "names" dizisinin her bir elamanı "const char*" türden. Birinci elemanın adresi ise "const char**" türden. * "ppanesm" ise öyle bir "non-const" göstericidir ki */ const char* names[] = { "ali", "veli", "selami" }; const char* const* ppnames; ppnames = names; return 0; }