-
Notifications
You must be signed in to change notification settings - Fork 19
/
Chapter_II-7.tex
1103 lines (906 loc) · 45.6 KB
/
Chapter_II-7.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{قراءة وكتابة الملفات}
المشكل مع استعمال المتغيّرات، هو أنها موجودة فقط في الذاكرة العشوائية
\textenglish{RAM}.
بخروجنا من البرنامج، كلّ المتغيّرات يتم حذفها من الذاكرة ولن يصبح ممكنا استعادة قيمها. كيف يمكننا إذن أن نحتفظ بأحسن العلامات التي تحصّلنا عليها في لعبة؟ كيف يمكننا إنشاء محرر نصوص إذا كان كلّ النصّ المكتوب يختفي بمجرّد إيقاف البرنامج؟
لحسن الحظّ يمكننا القراءة من الملفاّت وكذا الكتابة فيها في لغة
\textenglish{C}.
هذه الملفّات مُخزّنة في القرص الصلب
(\textenglish{Hard disk})
الخاص بالحاسوب: الشيء الإيجابيّ إذن هو أنها تبقى محفوظة، حتّى عند إيقاف البرنامج أو الحاسوب.
للقراءة من الملفات والكتابة فيها، سنحتاج إلى استعمال كلّ ما درسناه حتّى الآن: المؤشرات، الهياكل، السلاسل المحرفيّة، إلخ.
\section{فتح وغلق ملف}
للقراءة والكتابة في الملفّات، سنستعمل دوالًا معرّفة في المكتبة
\InlineCode{stdio}
التي استعملناها سابقًا.\\
نعم، هذه المكتبة تحتوي على الدالتين
\InlineCode{scanf}
و
\InlineCode{printf}
اللتان نعرفهما جيّدا! لكن ليس هذا فحسب: يوجد بها الكثير من الدوال الأخرى، خصوصا التي تعمل على الملفات.
\begin{information}
كل المكتبات التي استعملناها حتّى الآن
(\InlineCode{stdlib.h}، \InlineCode{stdio.h}، \InlineCode{math.h}، \InlineCode{string.h}\dots)
تشكّل ما نسميه بالمكتبات القياسية
(\textenglish{Standard libraries})،
و هي مكتبات تأتي تلقائيا مع البيئة التطويرية التي تستخدمها ولديها الميزة في أنّها تعمل على كل أنظمة التشغيل. بالإمكان استعمالها في أيّ مكان، سواء كنت في
\textenglish{Windows}،
أو
\textenglish{GNU/Linux}
أو
\textenglish{Mac}
أو غير ذلك.
المكتبات القياسيّة ليست كثيرة ولا تمكّننا من القيام بأكثر من بعض الأمور الأساسيّة، كما فعلنا لغاية الآن. للحصول على وظائف أكثر تقدّما، كفتح النوافذ، يجب تنزيل وتثبيت مكتبات جديدة. سنرى ذلك قريبا!
\end{information}
تأكّد إذن، للبدأ، أن تقوم بتضمين المكتبتين
\InlineCode{stdio.h}
و
\InlineCode{stdlib.h}
على الأقل أعلى ملفك
\InlineCode{.c}:
\begin{Csource}
#include <stdlib.h>
#include <stdio.h>
\end{Csource}
هاتان المكتبتان ضروريتان وأساسيّتان لدرجة أنّي أنصحك بتضمينهما في كلّ البرامج التي تكتبها في المستقبل، أيّا كانت.
حسنًا وبعدما قمنا بتضمين المكتبتين، يمكننا أن ننطلق في بالأمور الجدّيّة. إليك الخطوات التي يجب اتّباعها دائمًا حينما تريد العمل على ملف، سواء للقراءة منه أو للكتابة فيه:
\begin{itemize}
\item نقوم باستدعاء دالة
\textbf{فتح الملف}
\InlineCode{fopen}
التي تقوم بإرجاع مؤشّر نحو هذا الملف.
\item \textbf{نتأكّد من نجاح عمليّة الفتح}
(أي إن كان الملفّ موجودا) باختبار قيمة المؤشر الذي أرجعته الدالة. فإن كان المؤشر يساوي
\InlineCode{NULL}،
فهذا يعني أنّ فتح الملف لم ينجح، في هذه الحالة لا يمكننا الإكمال (يجب أن نظهر رسالة خطا).
\item إذا تم الفتح بنجاح (أي أن قيمة المؤشر تختلف عن
\InlineCode{NULL})،
سنستمتع
\textbf{بالكتابة على الملف أو القراءة منه}،
و ذلك باستخدام دوال سنراها لاحقًا.
\item بمجرّد أن
\textbf{ننهي العمل على الملف}،
يجب تذكّر "غلقه" باستعمال الدالة
\InlineCode{fclose}.
\end{itemize}
سنتعلّم كخطوة أولى كيف نستخدم
\InlineCode{fopen}
و
\InlineCode{fclose}،
حينما تتعلّم هذا، سنتعلّم كيف نقرأ محتواه ونكتب نصّا فيه.
\subsection{\texttt{fopen}: فتح ملف}
في فصل السلاسل المحرفيّة، كنا نستعين بنماذج الدوال مثل "دليل استخدام". هذا ما يفعله المبرمجون غالبا: يقرؤون نموذج دالة ويفهمون كيف يستخدمونها. مع ذلك، أعلم أنّنا بحاجة إلى بعض الشروحات البسيطة!
لهذا فلنرى قليلًا نموذج
\InlineCode{fopen}:
\begin{Csource}
FILE* fopen(const char* fileName, const char* openMode);
\end{Csource}
هذه الدالة تنتظر معاملين:
\begin{itemize}
\item اسم الملف الذي نريد فتحه.
\item وضع فتح الملف، أي دلالة تذكر ما الّذي تريد فعله: القراءة من الملف، أو الكتابة فيه، أو كليهما.
\end{itemize}
هذه الدالة ترجع\dots مؤشّرا على
\InlineCode{FILE}!
إنّه مؤشّر على هيكل من نوع
\InlineCode{FILE}.
هذا الهيكل متواجد في المكتبة
\InlineCode{stdio.h}.
يمكنك فتح الملف لترى مما يتكوّن النوع
\InlineCode{FILE}،
لكن هذا ليس ما يهمّنا.
\begin{question}
لكن لِمَا اسم الهيكل كله بحروف كبيرة؟ اعتقدت أن الأسماء بالحروف الكبيرة حجزناها للثوابت ولـ\InlineCode{\#define}؟
\end{question}
هذه "القاعدة"، أنا من قمت بتحديدها (وكثير من المبرمجين يتبعونها)، ولكنّها لم تكن أبدا مفروضة. ويبدو أنّ من برمجوا
\InlineCode{stdio.h}
لا يتبعون نفس القواعد!\\
هذا لا يجب أن يشوّشك كثيرا. سوف ترى أنّ المكتبات الّتي سندرسها لاحقا تتبّع نفس القواعد التي أتّبعها، أي أن اسم الهيكل يبتدئ فقط بحرف واحد كبير.
لنعد إلى دالتنا
\InlineCode{fopen}،
إنها تقوم بإرجاع
\InlineCode{FILE*}.
إنه من المهم جدّا استرجاع هذا المؤشّر كي نتمكّن لاحقًا من القراءة والكتابة في الملف. ولهذا سنقوم بإنشاء مؤشّر على
\InlineCode{FILE}،
في بداية دالتنا
(\InlineCode{main}
مثلا):
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
return 0;
}
\end{Csource}
لقد هيّأنا المؤشّر على
\InlineCode{NULL}
من البداية. أذكّرك بأنّ هذه قاعدة أساسيّة أن تهيّأ كلّ المؤشّرات على
\InlineCode{NULL}
إنّ لم تكن لديك قيمة أخرى لإعطائها. إن لم تفعل ذلك، فأنت تزيد كثيرا خطر وجود أخطاء لاحقا.
\begin{information}
إنه ليس ضروريًا أن تكتب
\InlineCode{struct FILE* file = NULL}،
لأن منشئي
\InlineCode{stdio.h}
قد وضعوا
\InlineCode{typedef}
كما علّمتك منذ مدّة قصيرة.
لاحظ أن شكل الهيكل قد يتغيّر من نظام تشغيل إلى آخر (لا تملك بالضرورة نفس المركّبات في كل الأنظمة). لهذا فلن نعدّل محتوى
\InlineCode{FILE}
مباشرة (لا نقوم بـ\InlineCode{file.element}
مثلا). بل سنكتفي باستدعاء دوال، تتعامل مع
\InlineCode{FILE}
نيابة عنًا.
\end{information}
الآن سنقوم باستدعاء الدالة
\InlineCode{fopen}،
و استرجاع القيمة الّتي تعيدها في المؤشر
\InlineCode{file}.
و لكن قبل هذا يجب أن أشرح لك كيف تستخدم المعامل الثاني
\InlineCode{openMode}.
في الواقع، هناك شفرة تدلّ للحاسوب على أنك تريد أن تفتح الملف بوضع القراءة فقط، الكتابة فقط أو الاثنين معًا.\\
هذه هي أوضاع فتح الملف المختلفة:
\begin{itemize}
\item \textbf{"\textenglish{r}":
قراءة فقط
(\textenglish{Read only})}.
يمكنك قراءة محتوى الملف، ولكن لا يمكنك الكتابة فيه.
\textit{يجب أن يكون الملف موجودًا من قبل}.
\item \textbf{"\textenglish{w}":
كتابة فقط
(\textenglish{Write only})}.
يمكنك الكتابة في الملف، لكن لا يمكنك قراءة محتواه.
\textit{إذا لم يكن الملف موجودًا من قبل، فإنه سيتم إنشاؤه}.
\item \textbf{"\textenglish{a}":
إلحاق
(\textenglish{Append})}.
يمكنك الكتابة في الملف، انطلاقا من نهايته.
\textit{إن لم يكن الملف موجودًا، فسيتم إنشاؤه}.
\item \textbf{"\textenglish{r+}":
قراءة وكتابة
(\textenglish{Read and Write})}.
يمكنك القراءة من الملف والكتابة فيه.
\textit{يجب أن يكون الملف موجودًا من قبل}.
\item \textbf{"\textenglish{w+}":
قراءة وكتابة مع مسح المحتوى أوّلا}.
سيتم تفريغ الملف من محتواه أولًا، ثم بإمكانك الكتابة فيه وقراءة محتواه بعد ذلك.
\textit{إن لم يكن الملف موجودًا من قبل، سيتم إنشاؤه}.
\item \textbf{"\textenglish{a+}"
إلحاق مع القراءة / الكتابة في آخر الملف}.
يمكنك القراءة والكتابة انطلاقا من نهاية الملف.
\textit{إن لم يكن موجودًا، سيتم إنشاؤه}.
\end{itemize}
لمعلوماتك، أنا عرضت لك بعضا من أوضاع فتح ملف. في الحقيقة، يوجد ضعفها!
من أجل كل وضع رأيناه هنا، إن أضفت
\InlineCode{"b"}
بعد المحرف الأول
(\InlineCode{"rb"}، \InlineCode{"wb"}، \InlineCode{"ab"}، \InlineCode{"rb+"}، \InlineCode{"wb+"}، \InlineCode{"ab+"})،
فإن الملف سيتم فتحه بالوضع الثنائي
(\textenglish{Binary}).
هذا وضع خاص قليلًا فلن ندرسه هنا. في الواقع وضع النص يختصّ بتخزين\dots النص، تماما كما يوحي الاسم (فقط المحارف القابلة للعرض). أما الوضع الثنائي، يسمح بتخزين المعلومات
بايتا بايتا
(\textenglish{Byte by byte})
(أعداد بشكل أساسي). هذا مختلف كثيرا. على أي حال فطريقة العمل هي تقريبا نفس الّتي سنراها هنا.
شخصيًا، أستعمل كثيرًا الأوضاع:
\InlineCode{"r"}
(قراءة)،
\InlineCode{"w"}
(كتابة)،
\InlineCode{"r+"}
(قراءة وكتابة في آن واحد). وضع
\InlineCode{"w+"}
خطر قليلًا لأنه يقوم بمسح محتوى الملف مباشرة، بدون أن يطلب التأكيد قبل القيام بذلك. إن هذا الوضع ليس مفيدًا إلا إذا أردنا أن نعيد تهيئة الملف أوّلا.
وضع الإلحاق
(\InlineCode{"a"})
يمكنه أن يفيد في بعض الحالات، إذا كنت تريد إضافة معلومات إلى نهاية الملف.
\begin{information}
إن كنت تريد قراءة ملفّ، فمن المستحسن وضع
\InlineCode{"r"}.
بالطبع، الوضع
\InlineCode{"r+"}
يعمل أيضا، لكن بوضع
\InlineCode{"r"}
فأنت تضمن أنّ الملفّ لا يمكن تعديله، هذا نوع من الحماية.
\end{information}
إن كتبت دالةً
\InlineCode{loadLevel}
(لتحميل مستوى في لعبة مثلا)، الوضع
\InlineCode{"r"}
كافٍ، أما إن أردت أن كتابة دالةٍ
\InlineCode{saveLevel}
(لحفظ المستوى) فستستعمل الوضع
\InlineCode{"w"}.
الشفرة التالية ستفتح الملف
\InlineCode{test.txt}
في وضع
\InlineCode{"r+"}
(قراءة وكتابة):
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
file = fopen("test.txt", "r+");
return 0;
}
\end{Csource}
المؤشّر
\InlineCode{file}
يصبح إذن مؤشرًا على الملف
\InlineCode{test.txt}.
\begin{question}
أين يجب أن يكون الملف
\InlineCode{test.txt}؟
\end{question}
يجب أن يكون في نفس المجلّد الذي يتواجد به الملف التنفيذي
(\InlineCode{.exe}).\\
من أجل متطلّبات هذا الفصل، أطلب منك أن تقوم بإنشاء ملف
\InlineCode{test.txt}
في نفس المسار الذي به
\InlineCode{.exe}،
مثلما أفعل أنا (الشكل الموالي).
\begin{figure}[H]
\centering
\includegraphics[width=0.8\textwidth]{Chapter_II-7_Files}
\end{figure}
كما ترى فأنا أستعمل حاليّا بيئة التطوير
\textenglish{Code::Blocks}
الأمر الذي يفسّر وجود ملف المشروع بصيغة
\InlineCode{.cbp}
(في مكان الصيغة
\InlineCode{.sln}
إن كنت تستعمل
\textenglish{Visual C++}
مثلًا). باختصار، الأمر المهم هو أن برنامجي
(\InlineCode{tests.exe})
موجود في نفس مجلّد الملف الذي نريد قراءته أو كتابته
(\InlineCode{test.txt}).
\begin{question}
هل يجب أن يكون الملف بصيغة
\InlineCode{.txt}؟
\end{question}
لا، الأمر يعود إليك في اختيار صيغة الملف عندما تفتحه. أي أنه بإمكانك أن تخترع صيغتك الخاصّة
\InlineCode{.level}
لحفظ مستويات ألعابك مثلًا.
\begin{question}
هل من الواجب أن يكون الملف الذي نريد فتحه في نفس دليل الملف التنفيذي؟
\end{question}
لا أيضا. يمكنه أن يكون داخل مجلّد بذات الدليل:
\begin{Csource}
file = fopen("directory/test.txt", "r+");
\end{Csource}
هنا، الملف
\InlineCode{test.txt}
في مجلّد داخليّ اسمه
\InlineCode{directory}.
هذه الطريقة التي نسميها
\textit{المسار النسبي}
عمليّة أكثر. هكذا، يمكن للبرنامج أن يعمل أينما كان مثبّتا.
من الممكن أيضا فتح ملفّ أينما كان في القرص الصلب. في هذه الحالة يجب كتابة المسار الكامل (ما نسميه
\textit{المسار المطلق}):
\begin{Csource}
file = fopen("C:\\Program Files\\Notepad++\\readme.txt", "r+");
\end{Csource}
هذه الشفرة تفتح الملف
\InlineCode{readme.txt}
الموجود بـ\InlineCode{C:\textbackslash Program Files\textbackslash Notepad++}.
\begin{warning}
تعمّدت استعمال شرطتين خلفيّتين
\InlineCode{\textbackslash}
كما تلاحظ. في الواقع، إن كتبت إشارة واحدة، سيعتقد الحاسوب أنني أريد أن استخدم رمزا خاصا (مثل \InlineCode{\textbackslash n}
أو \InlineCode{\textbackslash t}).
لكتابة شرطة خلفيّة في سلسلة، يجب كتابتها إذن مرّتين! هكذا يمكن أن يفهم أنّك تريد استخدام الرمز
\InlineCode{\textbackslash}.
\end{warning}
المشكل مع المسارات المطلقة، هو أنها لا تعمل إلا مع نظام معيّن، فهي ليست حلّا محمولا إذن. أي أنه لو كنت تعمل على
\textenglish{GNU/Linux}
لكان عليك كتابة مسار كهذا مثلا:
\begin{Csource}
file = fopen("/home/mateo/directory/readme.txt", "r+");
\end{Csource}
لهذا فأنا أنصحك بكتابة مسارات نسبية. لا تستعمل المسارات المطلقة إلا في حالة كان البرنامج مخصص لنظام تشغيل معيّن، ليعدّل على ملف معيّن في القرص الصلب.
\subsection{اختبار فتح ملف}
المؤشّر
\InlineCode{file}
يجب أن يحوي عنوان الهيكل من نوع
\InlineCode{FILE}،
و الذي نستعمله كواصف
(\textenglish{Descriptor})
للملف. هذا الواصف تم تحميله من أجلك في الذاكرة من طرف الدالة
\InlineCode{fopen}.
بعد هذا، هناك احتمالان:
\begin{itemize}
\item إمّا أن تنجح عملية الفتح، فسنتمكن من المواصلة (أي البدء في القراءة والكتابة في الملف).
\item إمّا ألّا تنجح لأن الملف ليس موجودًا أو أنه مستخدم من طرف برنامج آخر. في هذه الحالة، سنتوقف عن العمل على الملف.
\end{itemize}
مباشرة بعد فتح الملف، يجب التأكد ما إن تمت العملية بنجاح، أم لا. هذا أمر بسيط: إذا كانت قيمة المؤشر تساوي
\InlineCode{NULL}،
فإن الفتح قد فشل. إن كانت قيمته تساوي شيئا غير
\InlineCode{NULL}،
فقد تم الفتح بنجاح.\\
سنتبع إذن هذا المخطط التالي:
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
file = fopen("test.txt", "r+");
if (file != NULL)
{
// We can read or write in the file
}
else
{
// We display an error message if we want
printf("Can't open the file test.txt");
}
return 0;
}
\end{Csource}
افعل هذا دائما عند فتح أي ملف. إن لم تفعل والملف غير موجود، فأنت تخاطر بتوقّف البرنامج بعدها.
\subsection{\texttt{fclose}: غلق الملف}
إذا نجحت عملية فتح الملف، يمكننا القراءة والكتابة فيه (سنرى كيف نفعل هذا لاحقًا).\\
ما إن نكمل العمل على الملف، يجب علينا "غلقه". نستعمل من أجل هذا الدالة
\InlineCode{fclose}
التي تقوم بتحرير الذاكرة. يعني أنّه سيتم حذف الملف المحمّل في الذاكرة العشوائية.
نموذج الدالة:
\begin{Csource}
int fclose(FILE* pointerOnFile);
\end{Csource}
هذه الدالة تأخذ معاملا واحدا: المؤشر نحو الملف.
تقوم بإرجاع
\InlineCode{int}،
و الذي يأخذ القيم:
\begin{itemize}
\item \InlineCode{0}: إذا نجح غلق الملف.
\item \InlineCode{EOF}: إذا فشل الغلق.
\InlineCode{EOF}
هي عبارة عن
\InlineCode{\#define}
موجودة في
\InlineCode{stdio.h}
و هي توافق عددًا خاصًا، يُستعمل للقول أنه حصل خطأ، أو أننا وصلنا إلى نهاية الملف. في حالتنا هذه، هذا يعني حدوث خطأ.
\end{itemize}
في غالب الأحيان، تنجح عملية غلق الملف: هذا ما يدفعني إلى عدم اختبار إن كانت
\InlineCode{fclose}
قد عملت. رغم هذا، يمكنك فعل ذلك إن أردت.
لإغلاق الملف، نكتب إذن:
\begin{Csource}
fclose(file);
\end{Csource}
في النهاية، المخطّط الذي نتّبعه لفتح وغلق ملف سيكون كالتالي:
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
file = fopen("test.txt", "r+");
if (file != NULL)
{
// We read and we write in the file
// ...
fclose(file); // We close the opened file
}
return 0;
}
\end{Csource}
لم أستعمل
\InlineCode{else}
لأظهر رسالة خطأ في حال لم ينجح الفتح، يمكنك فعل ذلك إن أردت.
يجب دائما التفكير في غلق الملف الذي فتحته بمجرّد الانتهاء من العمل عليه. هذا سيسمح بتحرير الذاكرة.\\
إن نسيت تحرير الذاكرة، قد يأخذ برنامجك حجما كبيرًا من الذاكرة بدون أن يستخدمه. في مثال صغير كهذا الأمر غير خطير، لكن مع برنامج كبير، مرحبًا بالمشاكل!
نسيان تحرير الذاكرة أمر يقع. بل سيحدث لك هذا كثيرا. في هذه الحالة نقول أنّه قد حدث
\textit{تسريب للذاكرة} (\textenglish{Memory leak}).
هذا يجعل برنامجك يستخدم قدرا من الذاكرة أكبر من اللازم بدون أن تفهم سبب حصول ذلك. في غالب الأحيان، يكون السبب واحدا أو إثنين من الأمور "الثانوية" مثل نسيان
\InlineCode{fclose}.
\section{طرق مختلفة للقراءة والكتابة في الملفات}
و الآن مادمنا تعلّمنا كيف نفتح ونغلق ملفا، لم يبق سوى أن نضيف الشفرة الّتي تقوم بالقراءة والكتابة عليه.
سنبدأ برؤية كيفيّة الكتابة في ملفّ (الأمر الأبسط قليلا)، ثمّ نمرّ يعدها إلى كيفيّة القراءة من ملفّ.
\subsection{الكتابة في ملف}
توجد الكثير من الدوال التي تسمح بالكتابة في ملف. يبقى عليك أن تختار أيها الأنسب لك لتستخدمها.
هذه الثلاث دوال الّتي سنتعلّمها:
\begin{itemize}
\item \InlineCode{fputc}:
تكتب حرفا في الملفّ
(\underline{حرف واحد}
في المرة).
\item \InlineCode{fputs}:
تكتب سلسلة محرفيّة في الملف.
\item \InlineCode{fprintf}:
تكتب سلسلة "منسّقةً" في الملف، طريقة عملها مطابقة تقريبا للدالة
\InlineCode{printf}.
\end{itemize}
\subsubsection{\texttt{fputc}}
هذه الدالة تكتب حرفا واحدا في المرّة في الملف. نموذجها:
\begin{Csource}
int fputc(int character, FILE* pointerOnFile);
\end{Csource}
و هي تأخذ معاملين:
\begin{itemize}
\item المحرف الذي يجب كتابته (من نوع
\InlineCode{int}،
مثلما قلت فاستعماله يعود تقريبًا إلى استعمال
\InlineCode{char}،
إلا أن عدد المحارف الممكن استعمالها هنا أكبر). يمكنك إذن أن تكتب مباشرة
\InlineCode{'A'}
كمثال.
\item المؤشّر نحو الملف الذي نريد أن نكتب فيه. في مثالنا، المؤشّر اسمه
\InlineCode{file}.
استعمال المؤشّر في كلّ مرة يساعدنا لأنه بإمكاننا أن نفتح العديد من الملفات في آن واحد، ونقرأ ونكتب في كلّ واحد من هذه الملفّات. لست محدّدا بفتح ملفّ واحد في المرّة.
\end{itemize}
الدالة تقوم بإرجاع
\InlineCode{int}،
و هو رمز الخطأ. هذا \InlineCode{int}
يساوي
\InlineCode{EOF}
إذا فشلت الكتابة، وإلّا فسيأخذ قيمة أخرى.\\
بما أنّ الملفّ قد تمّ فتحه بنجاح، فليس من عادتي اختبار إن كانت كلّ واحدة من
\InlineCode{fputc}
قد نجحت، ولكن يمكنك فعل ذلك إن أردت.
الشفرة التالية تسمح بكتابة الحرف
\InlineCode{'A'}
في الملف
\InlineCode{test.txt}
(إذا كان موجودًا من قبل فإنه سيتم استبداله، أمّا إن لم يكن موجودًا سيتم إنشاؤه). الشفرة تحتوي كل الخطوات التي تكلّمنا عنها سابقًا: فتح الملف، اختبار الفتح، الكتابة والغلق:
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
file = fopen("test.txt", "w");
if (file != NULL)
{
fputc('A', file); // Write the character A
fclose(file);
}
return 0;
}
\end{Csource}
افتح بنفسك الملف
\InlineCode{test.txt}.
ماذا ترى؟\\
إن هذا سحريّ، الملف يحتوي الآن على الحرف
\InlineCode{'A'}
كما ترى في الشكل التالي:
\begin{figure}[H]
\centering
\includegraphics[width=0.3\textwidth]{Chapter_II-7_test-A}
\end{figure}
\subsubsection{\texttt{fputs}}
هذه الدالة شبيهة جدًا بالدالة
\InlineCode{fputc}،
إلا أنها تسمح بكتابة سلسلة محرفيّة كاملة، وهذا عادة أحسن من الكتابة حرفًا حرفًا.\\
لكن
\InlineCode{fputc}
تبقى ضروريّة حينما نحتاج إلى الكتابة محرفا بمحرف، وهذا يحدث كثيرا.
نموذج الدالة:
\begin{Csource}
char* fputs(const char* string, FILE* pointerOnFile);
\end{Csource}
المعاملان سهلا الفهم:
\begin{itemize}
\item \InlineCode{string}:
السلسلة الّتي نريد كتابتها. تلاحظ أن النوع هنا هو
\InlineCode{const char*}:
إضافة الكلمة
\InlineCode{const}
في النموذج تشير إلى أن السلسة الّتي سنعطيها للدالة تُفترض ثابتة. أي أنّ الدالة لن تقوم بتغييرها. هذا أمر منطقي عندما نفكّر فيه:
\InlineCode{fputs}
يجب أن تقرأ السلسلة بدون تعديلها. هذه إذن معلومة لك (وحماية) أنّ سلسلتك لن يتمّ إدخال أيّة تعديلات عليها.
\item \InlineCode{pointerOnFile}:
مثل
\InlineCode{fputc}،
تحتاج هذه الدالة إلى مؤشر من نوع
\InlineCode{FILE*}
نحو الملف الّذي فتحته.
\end{itemize}
الدالّة تعيد القيمة
\InlineCode{EOF}،
في حالة وجود خطأ، وإلّا، فهذا يعني أنّها عملت على ما يرام. وهنا أيضا، لن أقوم عادة باختبار القيمة التي ترجعها الدالة.
فلنجرب كتابة سلسلة في ملف:
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
file = fopen("test.txt", "w");
if (file != NULL)
{
fputs("Hello my friends\nHow are you ?", file);
fclose(file);
}
return 0;
}
\end{Csource}
الشكل التالي يظهر الملف بعد التعديل عليه من طرف البرنامج:
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{Chapter_II-7_test-string}
\end{figure}
\subsubsection{\texttt{fprintf}}
إليك نوعًا آخرًا من الدالة
\InlineCode{printf}.
هذه تستخدم للكتابة في ملف. هذه الدالة تستعمل بنفس الطريقة التي نستعمل بها
\InlineCode{printf}،
إلا أنه يجب إعطاءها المؤشر نحو
\InlineCode{FILE}
كمعامل أوّل.
الشفرة التالية تطلب من المستخدم إدخال عمره، ثمّ تقوم بكتابته في الملف:
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
int age = 0;
file = fopen("test.txt", "w");
if (file != NULL)
{
// Request the age
printf("How old are you ? ");
scanf("%d", &age);
// Write the age on the file
fprintf(file , "The mister who uses the PC has %d years", age);
fclose(file);
}
return 0;
}
\end{Csource}
\begin{figure}[H]
\centering
\includegraphics[width=0.6\textwidth]{Chapter_II-7_test-fprintf}
\end{figure}
يمكنك إذا إعادة استعمال ما تعرفه عن
\InlineCode{printf}
للكتابة في ملف! لهذا السبب أنا غالبا ما استعمل
\InlineCode{fprintf}
للكتابة في الملفّات.
\subsection{القراءة من ملف}
لدينا أيضًا ثلاث دوال للقراءة من ملف، اسمها مختلف قليلًا فقط عن دوال الكتابة:
\begin{itemize}
\item \InlineCode{fgetc}:
قراءة محرف.
\item \InlineCode{fgets}:
قراءة سلسلة محرفيّة.
\item \InlineCode{fscanf}:
قراءة سلسلة منسّقة.
\end{itemize}
سأسرع قليلًا في شرح هذه الدوال: إذا كنت قد فهمت ما كتبته من قبل، فلن تجد أي صعوبة مع هذه الدوال.
\subsubsection{\texttt{fgetc}}
أولًا، النموذج:
\begin{Csource}
int fgetc(FILE* pointerOnFile);
\end{Csource}
هذه الدالة تقوم بإرجاع
\InlineCode{int}:
إنه المحرف الذي تمّت قراءته.
إذا لم تقرأ أيّ محرف، فستعيد القيمة
\InlineCode{EOF}.
\begin{question}
لكن كيف لنا أن نعرف المحرف الذي نقرأه؟ ماذا لو أردنا قراءة المحرف الثالث وأيضا العاشر، كيف نفعل هذا؟
\end{question}
في الواقع، في كلّ مرة تقرأ فيها ملفّا، فهناك "مؤشر"
(\textenglish{Cursor})
(مثل المؤشّر الذي يغمز في محرر النصوص) يتحرّك في كلّ مرة. وهذا المؤشّر افتراضي طبعًا، لن تتمكن من رؤيته على الشاشة. وهو يشير إلى أين وصلنا في قراءة الملف.
سنتعلم لاحقًا كيف نعرف الوضعية التي وصل إليها المؤشّر بالضبط وأيضا كيف نحرّكه من مكانه (وذلك لكي نقوم بتحريكه إلى بداية الملف مثلا، أو إلى مكان محرف محدّد، كالمحرف العاشر).
\InlineCode{fgetc}
تقوم بتحريك المؤشر بمحرف واحد في كلّ مرة تقرأ فيها واحدا. أي أنك إن استدعيت
\InlineCode{fgetc}
مرة ثانية، فستقرأ المحرف الثاني، ثم الثالث وهكذا. وبهذا يمكنك استعمال حلقة تكرارية لقراءة محارف الملف واحدا واحدا.
سنقوم بكتابة شفرة تقرأ كلّ محارف الملف واحدا واحدا وفي كلّ مرّة تكتبها على الشاشة. الحلقة ستتوقف حينما تعيد
\InlineCode{fgetc}
القيمة
\InlineCode{EOF}
(والّتي تعني
"\textenglish{End Of File}"
أي "نهاية الملف").
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
int currentCharacter = 0;
file = fopen("test.txt", "r");
if (file != NULL)
{
// A loop to read the characters one by one
do
{
currentCharacter = fgetc(file); // Read the character
printf("%c", currentCharacter); // Display it
} while (currentCharacter != EOF); // Continue while fgets didn't return EOF (End Of File)
fclose(file);
}
return 0;
}
\end{Csource}
الكونسول ستقوم بإظهار محتوى الملف كاملًا، مثلا:
\begin{Console}
Hello, I'm the content of the file test.txt !
\end{Console}
\subsubsection{\texttt{fgets}}
هذه الدالة تقوم بقراءة سلسلة من ملف. هذا يجنبك قراءة كلّ محارف الملف واحدا واحدا. الدالة
\textbf{تقرأ على الأكثر سطرًا واحدًا}
(تتوقف عند ملاقاة أول
\InlineCode{\textbackslash n})،
إن أردت قراءة العديد من الأسطر، فعليك استعمال حلقة.
هذا نموذج الدالة:
\begin{Csource}
char* fgets(char* string, int nbOfCharsToRead, FILE* pointerOnFile);
\end{Csource}
هذه الدالة تتطلب معاملا خاصّا نوعًا ما، ولكنّه سيكون عمليّا جدّا: عدد المحارف التي نريد قراءتها. هذا ما يطلب من الدالة
\InlineCode{fgets}
التوقف عن قراءة السطر إذا كان يحوي أكثر من
\textenglish{X}
من المحارف.
الفائدة: هذا يسمح لنا بضمان عدم حدوث تجاوز في الذاكرة! في الواقع، إذا كان حجم السطر أكبر من أن تسعه السلسلة المحرفيّة، فمن الممكن أن تقرأ عددا من المحارف أكثر ممّا يسمح به المكان المتوفّر، وهذا قد يسبب تعطّل البرنامج.
سنتعلّم كيف نقرأ سطرًا واحدًا باستخدام
\InlineCode{fgets}،
(ثم بعدها سنرى كيفية قراءة ملف كامل).
لهذا فسنقوم بتعريف سلسلة محرفيّة كبيرة كفاية لتخزين السطر المراد قراءته (على الأقل نتمنّى ذلك، لا يمكننا أن نكون متأكّدين
100\%).
سترى فائدة استخدام \InlineCode{\#define}
في تعريف حجم جدول:
\begin{Csource}
#define MAX_SIZE 1000 // A table of size 1000
int main(int argc, char *argv[])
{
FILE* file = NULL;
char string[MAX_SIZE] = ""; // Empty string of size MAX_SIZE
file = fopen("test.txt", "r");
if (file != NULL)
{
fgets(string, MAX_SIZE, file); // Read at maximum MAX_SIZE characters from the file, store them in "string"
printf("%s", string); // Display the string
fclose(file);
}
return 0;
}
\end{Csource}
النتيجة هي نفسها النتيجة السابقة، مع العلم أنّ المحتوى يُكتب في الكونسول:
\begin{Console}
Hello, I'm the content of the file test.txt !
\end{Console}
الفرق هو أننا هنا لم نستعمل حلقة تكرارية. نقوم باسترجاع محتوى الملف كاملا في مرّة.
أنت تلاحظ بكلّ تأكيد الآن فائدة استعمال
\InlineCode{\#define}
في شفرتك لتعريف الحجم الأقصى لجدول مثلًا. في الواقع،
\InlineCode{MAX\_SIZE}
مستعمل في مكانين مختلفين في الشفرة:
\begin{itemize}
\item المرة الأولى لتعريف حجم الجدول الذي نريد إنشاءه.
\item مرّة اخرى في \InlineCode{fgets}
لنقوم بتحديد عدد المحارف التي نقرأها.
\end{itemize}
الفائدة هنا، هي أنّه في حال ما وجدت أن السلسلة المحرفيّة غير كبيرة كفاية لقراءة الملف، فلن يكون عليك سوى تعديل سطر \InlineCode{\#define}
و إعادة الترجمة. هذا سيجنّبك البحث عن كلّ مكان من الشفرة وضعت فيه حجم الجدول. المعالج القبلي سيقوم باستبدال كل تكرار لـ\InlineCode{MAX\_SIZE}
بالقيمة الجديدة.
كما قلت فإن
\InlineCode{fgets}
تقرأ على الأكثر سطرًا واحدا في المرّة. تتوقف عن قراءة السطر عندما تتجاوز عدد المحارف الذي سمحت لها بقراءتها.
نعم ولكن: حاليّا، نحن لا نجيد سوى قراءة سطر واحد باستخدام
\InlineCode{fgets}.
كيف لنا أن نقرأ كل الملف؟ الجواب بسيط: بحلقة تكرارية!
الدالة
\InlineCode{fgets}
تعيد
\InlineCode{NULL}
في حالة لم تستطع قراءة ما طلبته منها.\\
أي أن الحلقة يجب أن تنتهي بمجرّد أن تعيد
\InlineCode{fgets}
القيمة
\InlineCode{NULL}.
ليس علينا سوى استعمال الحلقة
\InlineCode{while}
لكي نقوم بالتكرار ما دامت
\InlineCode{fgets}
لم ترجع
\InlineCode{NULL}:
\begin{Csource}
#define MAX_SIZE 1000
int main(int argc, char *argv[])
{
FILE* file = NULL;
char string[MAX_SIZE] = "";
file = fopen("test.txt", "r");
if (file != NULL)
{
while (fgets(string, MAX_SIZE, file) != NULL) // Read the file while there's no error (NULL)
{
printf("%s", string); // Display the string that we've read
}
fclose(file);
}
return 0;
}
\end{Csource}
هذه الشفرة تقوم بقراءة الملف سطرًا سطرًا وإظهار السطور.
السطر الأكثر لفتًا للانتباه في الشفرة هو:
\begin{Csource}
while (fgets(string, MAX_SIZE, file) != NULL)
\end{Csource}
سطر \InlineCode{while}
يقوم بأمرين: قراءة سطر من الملف والتأكد أن
\InlineCode{fgets}
لم تُعِد
\InlineCode{NULL}.
يمكن ترجمة هذا كالتالي: "اقرأ سطرًا جديدًا ما دمنا لم نصل إلى نهاية الملف".
\subsubsection{\texttt{fscanf}}
مبدأ هذه الدالة مشابه تمامًا لمبدأ نظيرتها
\InlineCode{scanf}،
هنا أيضا.\\
هذه الدالّة تقوم بقراءة ملفّ تمت كتابته بشكل محدّد.
لنفترض أن الملف يحتوي على ثلاثة أعداد مفصولة بفراغ، وهي مثلا أكبر ثلاثة نقاط تم التحصل عليها في لعبتك:
\InlineCode{15 20 30}.
أنت تريد أن تسترجع كلّ واحد من هذه الأعداد في متغير من نوع
\InlineCode{int}.\\
الدالة
\InlineCode{fscanf}
ستسمح لك بالقيام بهذا بشكل سريع.
\begin{Csource}
int main(int argc, char *argv[])
{
FILE* file = NULL;
int score[3] = {0}; // Table of the 3 best scores
file = fopen("test.txt", "r");
if (file != NULL)
{
fscanf(file, "%d %d %d", &score[0], &score[1],&score[2]);
printf("The best scores are : %d, %d and %d", score[0], score[1], score[2]);
fclose(file);
}
return 0;
}
\end{Csource}
\begin{Console}
The best scores are : 15, 20 and 30
\end{Console}
كما ترى، فالدالة
\InlineCode{fscanf}
تنتظر ثلاث أعداد مفصولة بفراغ
(\InlineCode{"\%d \%d \%d"}).
ستقوم بتخزينهم في جدولنا ذو الخانات الثلاث.
نقوم لاحقًا بإظهار كلّ القيم المسترجعة.
\begin{information}
حتّى الآن، لم استعمل سوى رمز
\InlineCode{\%d}
واحدًا في الدالة
\InlineCode{scanf}.
اليوم اكتشفتَ بأنه بإمكانك أن تستعمل العديد منها. إذا كان الملف مكتوبا بطريقة محدّدة جيّدا، فهذا يسمح لك بالإسراع لاسترجاع كلّ واحدة من هذه القيم.
\end{information}
\section{التحرك داخل ملف}
كنت قد كلّمتك عن وجود "مؤشّر" افتراضي
(\textenglish{Virtual cursor})
قبل قليل.
سنقوم الآن بدراسته بشكل أكثر تفصيلًا.
في كلّ مرة تفتح فيها ملفا، فهناك مؤشّر يشير إلى وضعيتك في الملف. ولتتخيّله تماما مثل مؤشر محرر النصوص. يدلّ على المكان الّذي أنت فيه من الملف، أي أين ستقوم بالكتابة.
كتلخيص، نظام المؤشر يسمح لك بالكتابة والقراءة في وضعية محددة من الملف.
توجد ثلاث دوال لتتعرف عليها:
\begin{itemize}
\item \InlineCode{ftell}:
تدلّنا على الوضعية التي نحن بها حاليًا في الملف.
\item \InlineCode{fseek}:
تُموضع المؤشّر في مكان محدد.
\item \InlineCode{rewind}:
تقوم بإرجاع المؤشّر إلى بداية الملف (هذا مكافئ للطلب من الدالة
\InlineCode{fseek}
أن تموضع المؤشّر في البداية).
\end{itemize}
\subsection{\texttt{ftell}: الموضع في الملف}
هذه الدالة بسيطة الاستعمال جدّا. تعيد الموضع الذي يتواجد به المؤشّر حاليا بنوع
\InlineCode{long}:
\begin{Csource}
long ftell(FILE* pointerOnFile);
\end{Csource}
العدد الّذي يتم إرجاعه يدلّ على موضع المؤشر في الملف.
\subsection{\texttt{fseek}: التموضع داخل الملف}
نموذج
\InlineCode{fseek}
هو التالي:
\begin{Csource}
int fseek(FILE* pointerOnFile, long deplacement, int origin);
\end{Csource}
الدالّة
\InlineCode{fseek}
تسمح بتحريك المؤشّر بـعدد من المحارف (يدلّ عليها
\InlineCode{deplacement})
انطلاقا من الموضع الّذي يدلّ عليه
\InlineCode{origin}.
\begin{itemize}
\item العدد
\InlineCode{deplacement}
يمكن له أن يكون عددًا موجبًا (للتقدم إلى الأمام)، معدوما (= 0) أو سالبًا (للرجوع إلى الخلف).
\item أمّا بالنسبة للعدد
\InlineCode{origin}
فهو يأخذ إحدى القيم التالية:
\begin{itemize}
\item \InlineCode{SEEK\_SET}
تعني بداية الملف.
\item \InlineCode{SEEK\_CUR}
تعني الموضع الحالي نفسه.
\item \InlineCode{SEEK\_END}
تعني نهاية الملف.
\end{itemize}
\end{itemize}
إليك بعض الأمثلة لكي تفهم جيّدا كيف تتلاعب بـ\InlineCode{deplacement}
و
\InlineCode{origin}:
\begin{itemize}
\item هذه الشفرة تضع المؤشر محرفين
\textit{بعد}
بداية الملف:
\begin{Csource}
fseek(file, 2, SEEK_SET);
\end{Csource}
\item هذه الشفرة تضع المؤشّر أربع محارف
\textit{قبل}
الوضعية الحالية:
\begin{Csource}