Let Us C 19th Edition PDF
Document Details
Uploaded by Deleted User
2023
Yashavant Kanetkar
Tags
Summary
Let Us C, 19th Edition, is a comprehensive guide to C programming. The book covers various aspects of the language from basic concepts to more advanced elements. It has been thoroughly revised and updated, with a lean format designed for students familiar with the language. The book includes practical exercises and source code, and also discusses interview questions.
Full Transcript
Let Us C 19th Edition Yashavant Kanetkar www.bpbonline.com NINETEENTH REVISED & UPDATED EDITION 2023 FIRST EDITION 2007 Copyright © BPB Publications, India © Let Us C is a registered trademark of BPB Publications, New Delhi under registration No. 1135514 ISBN: 978-93-5551-276-5 All Rights...
Let Us C 19th Edition Yashavant Kanetkar www.bpbonline.com NINETEENTH REVISED & UPDATED EDITION 2023 FIRST EDITION 2007 Copyright © BPB Publications, India © Let Us C is a registered trademark of BPB Publications, New Delhi under registration No. 1135514 ISBN: 978-93-5551-276-5 All Rights Reserved. No part of this publication can be stored in a retrieval system or reproduced in any form or by any means without the prior written permission of the publishers. LIMITS OF LIABILITY AND DISCLAIMER OF WARRANTY The Author and Publisher of this book have tried their best to ensure that the programmes, procedures and functions described in the book are correct. However, the author and the publishers make no warranty of any kind, expressed or implied, with regard to these programmes or the documentation contained in the book. The author and publisher shall not be liable in any event of any damages, incidental or consequential, in connection with, or arising out of the furnishing, performance or use of these programmes, procedures and functions. Product name mentioned are XVHGIRULGHQWLÀFDWLRQSXUSRVHVRQO\DQGPD\EHWUDGHPDUNVRIWKHLUUHVSHFWLYH companies. All trademarks referred to in the book are acknowledged as properties of their respective owners. www.bpbonline.com Dedicated to baba, who couldn’t be here to see this day... iii About Yashavant Kanetkar Through his books and online Quest Video Courses on C, C++, Data Structures, VC++,.NET, etc. Yashavant Kanetkar has created, molded and groomed lacs of IT careers in the last two and half decades. Yashavant’s books and online courses have made a significant contribution in creating top-notch IT manpower in India and abroad. Yashavant’s books are globally recognized and millions of students / professionals have benefitted from them. His books have been translated into Hindi, Gujarati, Japanese, Korean and Chinese languages. Many of his books are published in India, USA, Japan, Singapore, Korea and China. Yashavant is a much sought-after speaker in the IT field and has conducted seminars/workshops at TedEx, IITs, NITs, IIITs and global software companies. Yashavant has been honored with the prestigious “Distinguished Alumnus Award” by IIT Kanpur for his entrepreneurial, professional and academic excellence. This award was given to top 50 alumni of IIT Kanpur who have made significant contribution towards their profession and betterment of society in the last 50 years. In recognition of his immense contribution to IT education in India, he has been awarded the "Best.NET Technical Contributor" and "Most Valuable Professional" awards by Microsoft for 5 successive years. Yashavant holds a BE from VJTI Mumbai and M. Tech. from IIT Kanpur. His current affiliations include being a Director of KICIT Pvt. Ltd. and an Adjunct Faculty at IIIT, Bangalore. He can be reached at [email protected] or through http://www.ykanetkar.com. iv Acknowledgments Let Us C has become an important part of my life. I have created and nurtured it for last two decades. While doing so, I have received, in addition to the compliments, a lot of suggestions from students, developers, professors, publishers and authors. So much have their inputs helped me in taking this book up to its 19th edition that ideally, I should put their names too on the cover page. In particular, I am indebted to Manish Jain who had a faith in this book idea, believed in my writing ability, whispered the words of encouragement and made helpful suggestions from time to time. I hope every author gets a publisher who is as cooperative, knowledgeable and supportive as Manish. The previous editions of this book saw several changes and facelifts. During this course many people helped in executing programs and spotting bugs. I trust that with their collective acumen, all the programs in this book would run correctly. I associate a lot of value to the work that they did. Any errors, omissions or inconsistencies that remain are, alas, my responsibility. I thank all my family members for enduring the late nights, the clicking keyboard, and mostly for putting up with a marathon book effort. Thinking of a book cover idea is one thing, putting it into action is a different cup of tea. This edition’s cover idea has been implemented by Vinay Indoria. Many thanks to him! And finally, my heartfelt gratitude to the countless students and developers who made me look into every nook and cranny of C. I want to remain in their debt. It is only because of them that Let Us C is now published from India, Singapore, USA, Japan, Dubai, Korea and China in multiple languages. v Preface to 19th Edition Let Us C has been part of learning and teaching material in most Engineering and Science Institutes round the country for years now. From last year or so, I received several suggestions that its size be pruned a bit, as many learners who learn C language in their Engineering or Science curriculum have some familiarity with it. I am happy to fulfill this request. I hope the readers would appreciate the lean look of the current edition. In one of the previous editions, I had realigned the chapters in such a manner that if a C programming course is taught using Let Us C, it can be finished in 22 lectures of one hour each, with one chapter's contents devoted to one lecture. I am happy that many readers liked this idea and reported that this has made their learning path trouble-free. A more rational reorganization of end-of-chapter Exercises in the book has also been well-received. Riding on that feedback I had introduced one more feature in the fifteenth edition—KanNotes. These are hand-crafted notes on C programming. From the reader’s emails I gather that they have turned out to be very useful to help revise their concepts on the day before the examination, viva-voce or interview. Many readers also told me that they have immensely benefitted from the inclusion of the chapter on Interview FAQs. I have improved this chapter further. The rationale behind this chapter is simple—ultimately all the readers of Let Us C sooner or later end up in an interview room where they are required to take questions on C programming. I now have a proof that this chapter has helped to make that journey smooth and fruitful. In this edition I have added a separate section titled 'Programs' in each chapter. It contains interesting programs based on the topics covered in the chapter. All the programs present in the book are available in source code form at www.kicit.com/books/letusc/sourcecode. You are free to download them, improve them, change them, do whatever with them. A new chapter titled 'The Next Level' that I added in the last edition has been further updated to include a few more intricate issues. If you wish to get solutions for the Exercises in the book, they are available in another book titled ‘Let Us C Solutions’. If you want some more problems for practice, they are available in the book titled vi ‘Exploring C’. As usual, new editions of these two books have also been launched along with 19th edition of Let Us C. Every new edition of Let Us C has gives me an opportunity to iron out flaws—some typographical, some related to sequencing of topics to help readers understand them better and some regarding the depth of the content. In all three regards this edition has been no different. Addressing all these issues by keeping the page count under control is a difficult job. However, I have somehow got used to it now. If you like ‘Let Us C’ and want to hear the complete video-recorded lectures created by me on C language (and other subjects like C++, VC++, C#, Java,.NET, Embedded Systems, etc.), then you can visit www.ykanetkar.com for more details. ‘Let Us C’ is as much your book as it is mine. So if you feel that I could have done certain job better than what I have, or you have any suggestions about what you would like to see in the next edition, please drop a line to [email protected] Countless Indians have relentlessly worked for close to three decades to successfully establish “India” as a software brand. At times, I take secret pleasure in seeing that Let Us C has contributed in its own small little way in shaping so many careers that have made the “India” brand acceptable. Recently I was presented with “Distinguished Alumnus Award” by IIT Kanpur. It was great to figure in a list that contained Narayan Murthy, Chief Mentor, Infosys, Dr. D. Subbarao, former Governor, Reserve Bank of India, Dr. Rajeev Motwani of Stanford University, Prof. H. C. Verma, Mr. Som Mittal President of NASSCOM, Prof. Minwalla of Harvard University, Dr. Sanjay Dhande former Director of IIT Kanpur, Prof. Arvind and Prof. Sur of MIT USA and Prof. Ashok Jhunjhunwala of IIT Chennai. I think Let Us C amongst my other books has been primarily responsible for helping me get the “Distinguished Alumnus” award. What was a bit surprising was that almost all who were present knew about the book already and wanted to know from me what it takes to write a book that sells in millions of copies? My reply was—make an honest effort to make the reader understand what you have to say and keep it simple. I don’t know how convincing was this answer, but well, that is what I have been doing with this book in all its previous editions. I have followed the same principle with this edition too. vii All the best and happy programming! Yashavant Kanetkar viii Contents 1. Getting Started 1 What is C? 3 Which C are we Learning? 4 Getting Started with C 4 Alphabets, Digits and Special Symbols 4 Constants, Variables and Keywords 5 Types of Constants 5 Rules for Constructing Integer Constants 5 Rules for Constructing Real Constants 6 Rules for Constructing Character Constants 7 Types of C Variables 7 Rules for Constructing Variable Names 7 C Keywords 8 The First C Program 8 Form of a C Program 9 Comments in a C Program 9 What is main( )? 10 Variables and their Usage 11 printf( ) and its Purpose 12 Compilation and Execution 13 Receiving Input 13 Programs 15 Exercises 17 KanNotes 19 2. C Instructions 21 Types of Instructions 23 Type Declaration Instruction 23 Arithmetic Instruction 24 Integer and Float Conversions 26 Type Conversion in Assignments 27 Hierarchy of Operations 28 Associativity of Operators 30 Control Instructions 31 Programs 31 Exercises 34 KanNotes 37 ix 3. Decision Control Instruction 39 The if - else Statement 41 Multiple Statements within if - else 43 Nested if-elses 45 A Word of Caution 46 Programs 47 Exercises 49 KanNotes 53 4. More Complex Decision Making 55 Use of Logical Operators - Checking Ranges 57 The else if Clause 59 Use of Logical Operators - Yes / No Problem 59 The ! Operator 62 Hierarchy of Operators Revisited 63 The Conditional Operators 63 Programs 64 Exercises 67 KanNotes 73 5. Loop Control Instruction 75 Loops 77 The while Loop 77 Tips and Traps 79 More Operators 81 Programs 83 Exercises 85 KanNotes 87 6. More Complex Repetitions 89 The for Loop 91 Nesting of Loops 95 Multiple Initializations in the for Loop 96 The break Statement 96 The continue Statement 98 The do-while Loop 99 The Odd Loop 100 Programs 101 Exercises 104 KanNotes 107 x 7. Case Control Instruction 109 Decisions using switch 111 The Tips and Traps 113 switch versus if-else Ladder 115 The goto Statement 116 Programs 118 Exercises 121 KanNotes 125 8. Functions 127 What is a Function? 129 Why use Functions? 132 Communication between Functions 133 Order of Passing Arguments 136 Using Library Functions 137 One Dicey Issue 138 Return Type of Function 139 Programs 140 Exercises 142 KanNotes 145 9. Pointers 147 Call by Value and Call by Reference 149 An Introduction to Pointers 149 Pointer Types and their Sizes 154 Back to Function Calls 155 Utility of Call by Reference 156 Conclusions 157 Uses of Pointers 157 Programs 158 Exercises 160 KanNotes 163 10. Recursion 165 Recursion 167 Programs 169 Exercises 172 KanNotes 174 11. Data Types Revisited 175 Integers—short, long, long long, signed, unsigned 177 xi Chars—signed, unsigned 178 Reals—float, double, long double 179 A Few More Issues… 180 Storage Classes in C 181 Automatic Storage Class 182 Register Storage Class 183 Static Storage Class 184 External Storage Class 185 Which to Use When 188 A Few Subtle Issues 188 Exercises 190 KanNotes 193 12. The C Preprocessor 195 Features of C Preprocessor 197 Macro Expansion 197 Macros with Arguments 198 Macros versus Functions 200 File Inclusion 201 Conditional Compilation 202 #if and #elif Directives 204 Miscellaneous Directives 204 #undef Directive 205 #pragma Directive 205 The Build Process 207 Programs 209 Exercises 211 KanNotes 213 13. Arrays 215 What are Arrays? 217 A Simple Program using Array 217 More on Arrays 218 Array Initialization 219 Array Elements in Memory 219 Bounds Checking 219 Passing Array Elements to a Function 220 Pointers and Arrays 221 Accessing Array Elements using Pointers 223 Passing an Array to a Function 225 Flexible Arrays 227 xii Programs 228 Exercises 231 KanNotes 234 14. Multidimensional Arrays 237 Two-Dimensional Arrays 239 Initializing a 2-D Array 240 Memory Map of a 2-D Array 240 Pointers and 2-D Arrays 241 Pointer to an Array 243 Passing 2-D Array to a Function 244 Array of Pointers 245 3-D Array 247 Programs 248 Exercises 250 KanNotes 253 15. Strings 255 What are Strings 257 More about Strings 257 Pointers and Strings 261 Standard Library String Functions 261 strlen( ) 262 strcpy( ) 264 strcat( ) 266 strcmp( ) 266 Programs 267 Exercises 270 KanNotes 273 16. Handling Multiple Strings 275 2-D Array of Characters 277 Array of Pointers to Strings 278 Limitation of Array of Pointers to Strings 281 Programs 282 Exercises 285 KanNotes 286 17. Structures 287 Why use Structures? 289 Array of Structures 291 xiii Intricacies of Structures 292 Structure Declaration 292 Storage of Structure Elements 293 Copying of Structure Elements 294 Nested Structures 295 Passing Structure Elements / Structure Variables 296 Packing Structure Elements 298 Uses of Structures 299 Programs 300 Exercises 303 KanNotes 305 18. Console Input/Output 307 Types of I/O 309 Console I/O Functions 309 Formatted Console I/O Functions 310 sprintf( ) and sscanf( ) Functions 317 Unformatted Console I/O Functions 318 Exercises 320 KanNotes 324 19. File Input/Output 325 File Operations 327 Opening a File 328 Reading from a File 329 Closing the File 329 Counting Characters, Tabs, Spaces, … 330 A File-Copy Program 331 File Opening Modes 333 String (Line) I/O in Files 334 Text Files and Binary Files 335 Record I/O in Files 336 Modifying Records 338 Low-Level File I/O 340 A Low-level File-copy Program 340 Programs 343 Exercises 346 KanNotes 348 20. More Issues In Input/Output 351 Using argc and argv 353 xiv Detecting Errors in Reading/Writing 356 Standard File Pointers 357 I/O Redirection 357 Redirecting the Output 358 Redirecting the Input 360 Both Ways at Once 360 Exercises 361 KanNotes 362 21. Operations On Bits 363 Bit Numbering and Conversion 365 Bit Operations 366 One’s Complement Operator 367 Right Shift and Left Shift Operators 368 A Word of Caution 370 Utility of link = NULL ; if ( rear == NULL ) { rear = q ; front = q ; } else { q -> link = rear ; rear = q ; } } int del_queue( ) { int item ; struct queue *q = rear ; if ( front == NULL ) { printf ( "Queue is empty\n" ) ; return -1; } else { if ( front == rear ) { Chapter 17: Structures 303 item = q -> item ; front = rear = NULL ; free( q ) ; } else { while( q -> link -> link != NULL ) q = q -> link ; item = q -> link -> item ; free( q -> link ) ; front = q ; q -> link = NULL ; } } return item ; } Output Deleted Item = 10 Deleted Item = 20 ____________________________________________________________________ [A] Answer the following questions: (a) Given the statement, maruti.engine.bolts = 25 ; which of the following is True? 1. bolts is a structure variable 2. engine is a structure variable 3. maruti is a structure variable 4. Option 2. and 3. (b) struct time { int hours ; int minutes ; int seconds ; }t; struct time *pt ; pt = &t ; 304 Let Us C With reference to the above declarations which of the following refers to seconds correctly: 1. pt.seconds 2. pt -> seconds 3. time.seconds 4. time->seconds [B] Attempt the following questions: (a) Create a structure called student that can contain data given below: Roll number, Name, Department, Course, Year of joining Assume that there are not more than 450 students in the college. (1) Write a function to print names of all students who joined in a particular year. (2) Write a function to print the data of a student whose roll number is received by the function. (b) Create a structure that can contain data of customers in a bank. The data to be stored is Account number, Name and Balance in account. Assume maximum of 200 customers in the bank. (1) Define a function to print the Account number and name of each customer with balance below Rs. 1000. (2) If a customer requests for withdrawal or deposit, it should receive as input Account number, amount and code (1 for deposit, 0 for withdrawal). Define a function that prints a message, “The balance is insufficient for the specified withdrawal”, if on withdrawal the balance falls below Rs. 1000. (c) An automobile company has serial number for engine parts starting from AA0 to FF9. The other characteristics of parts are year of manufacture, material and quantity manufactured. (1) Create a structure to store information corresponding to a part. (2) Write a program to retrieve information on parts with serial numbers between BB1 and CC6. (d) A record contains name of cricketer, his age, number of test matches that he has played and the average runs that he has Chapter 17: Structures 305 scored. Create an array of structures to hold records of 20 such cricketers and then write a program to read these records and arrange them in ascending order by average runs. Use the qsort( ) standard library function. (e) Suppose there is a structure called employee that holds information like employee code, name and date of joining. Write a program to create an array of structures and enter some data into it. Then ask the user to enter current date. Display the names of those employees whose tenure is greater than equal to 3 years. (f) Create a structure called library to hold accession number, title of the book, author name, price of the book, and flag indicating whether book is issued or not. Write a menu-driven program that implements the working of a library. The menu options should be: 1. Add book information 2. Display book information 3. List all books of given author 4. List the title of book specified by accession number 5. List the count of books in the library 6. List the books in the order of accession number 7. Exit (g) Define a function that compares two given dates. To store a date, use a structure that contains three members namely day, month and year. If the dates are equal the function should return 0, otherwise it should return 1. x Structure is a collection of dissimilar (usually) elements stored in adjacent locations Structure is also known as - User-defined data type / Secondary data type / Aggregate data type / Derived data type x Terminology : struct employee { char name ; int age ; float salary ; } ; struct employee e1, e2, e[ 10 ] ; struct - Keyword employee - Structure name / tag 306 Let Us C name, age, salary - Structure elements / Structure members e1, e2 - Structure variables e[ ] - Array of structures x Structure elements are stored in adjacent memory locations x Size of structure variable = sum of sizes of structure elements x 2 ways to copy structure elements : struct emp e1 = { "Rahul", 23, 4000.50 } ; struct emp e2, e3 ; e2.n = e1.n ; e2.a = e1.a ; e2.s = e1.s ; Æ Piecemeal copying e3 = e1 ; Æ Copying at one shot x Structures can be nested : struct address { char city[ 20 ] ; long int pin ; } ; struct emp { char n[ 20 ] ; int age ; struct address a ; float s ; } ; struct emp e ; To access city and pin we should use e.a.city and e.a.pin x To access structure elements using structure variable, use. operator as in struct emp e ; printf ( “%s %d %f”, e.name, e.age, e.sal ) ; x To access structure elements using structure pointer, use -> operator as in struct emp e ; struct emp *p ; p = &e ; printf ( “%s %d %f”, p->name, p->age, p->sal ) ; x If a structure contains a pointer to itself, it is called a self- referential structure : struct node { int data ; struct node *link ; } ; x Uses of structures : Database Management Displaying characters Printing on printer Mouse Programming Graphics Programming All Disk Operations Console 18 Input / Output “Input from keyboard, Output to screen” I want to print names, ages and salaries of 5 persons on the screen. The names and ages should be left-justified and properly aligned one below the other. The salaries should be aligned with the decimal point and only two digits should be printed after decimal point. To achieve all this, you need formatted console printing functions. This chapter shows you which one of them to use and how… 307 308 Let Us C x Types of I/O x Console I/O Functions Formatted Console I/O Functions sprintf( ) and sscanf( ) Functions Unformatted Console I/O Functions x Exercises x KanNotes Chapter 18: Console Input/Output 309 C language has no provision for receiving data from any of the input devices (like say keyboard, disk, etc.), or for sending data to the output devices (like say monitor, disk, etc.). Then how do we manage Input/Output (I/O)? Well, that is what we intend to explore in this chapter. Types of I/O Though C has no keywords to perform I/O, it has to be dealt with at some point or the other. There is not much use of writing a program that spends all its time telling itself a secret. Each Operating System (OS) has its own way of inputting and outputting data from and to the files and devices. So, functions are defined that can carry out I/O keeping in mind the particular operating system’s I/O facilities. These functions are then compiled and made available to users in the form of libraries. Since I/O facilities are OS dependent, definition of an I/O function for one OS would be different than the one for another OS, even though the behavior of both functions would be same. There are numerous library functions available for I/O. These can be classified into two broad categories: (a) Console I/O functions - Functions to receive input from keyboard and display output to screen. (b) File I/O functions - Functions to perform I/O operations on files on a disk. In this chapter we would be discussing only Console I/O functions. File I/O functions would be discussed in Chapter 19. Console I/O Functions The screen and keyboard together are called a console. Console I/O functions can be further classified into two categories—formatted and unformatted console I/O functions. Formatted functions allow the input read from keyboard or output to screen to be formatted as per our requirements. For example, while displaying values of average marks and percentage marks on the screen, details like where this output would appear on the screen, how many spaces would be present between the two values, the number of places after the decimal points, etc., can be controlled using formatted Chapter 18: Console Input/Output 311 The output of this code snippet would be... Average = 346 Percentage = 69.200000 During execution printf( ) function examines the format string from left to right. Till it doesn’t come across either a % or a \, it continues to display the characters that it encounters, on to the screen. In this example, Average = is displayed on the screen. When it comes across a format specifier, it picks up the first variable in the list of variables and prints its value in the specified format. In this example, when %d is met, the variable avg is picked up and its value is printed. Similarly, when an escape sequence is met, it takes suitable action. In this example, when \n is met, it places the cursor at the beginning of the next line. This process continues till the end of format string is reached. Format Specifications The %d and %f used in the printf( ) are called format specifiers. Figure 18.2 gives a list of format specifiers that can be used with the printf( ) function. Data type Format specifier Integer signed short %hd unsigned short %hu int %d or %i unsigned int %u singed long %ld unsigned long %lu signed long long %lld unsigned long long %llu unsigned hexadecimal %x unsigned octal %o Real float %f double %lf long double %Lf Character signed character %c unsigned character %c String %s Figure 18.2 Different format specifiers. 312 Let Us C We can provide optional specifiers shown in Figure 18.3 in the format specifications. Specifier Description w Digits specifying field width. Decimal point separating field width from precision (precision means number of places after the decimal point) d Digits specifying precision - Minus sign for left justifying output in specified field width Figure 18.3 Optional format specifiers. The field-width specifier tells printf( ) how many columns on screen should be used while printing a value. For example, %10d says, “print the variable as a decimal integer in a field of 10 columns”. If the value to be printed happens not to fill up the entire field, the value is right justified and is padded with blanks on the left. If we include the minus sign in format specifier (as in %-10d), this means left-justification is desired and the value will be padded with blanks on the right. If the field-width used turns out to be less than what is required to print the number, the field-width is ignored and the complete number is printed. Here is an example that illustrates all these features. # include int main( ) { int weight = 63 ; printf ( "weight is %d kg\n", weight ) ; printf ( "weight is %2d kg\n", weight ) ; printf ( "weight is %4d kg\n", weight ) ; printf ( "weight is %6d kg\n", weight ) ; printf ( "weight is %-6d kg\n", weight ) ; printf ( "weight is %1d kg\n", weight ) ; return 0 ; } The output of the program would look like this... Chapter 18: Console Input/Output 313 Columns 0123456789012345678901234567890 weight is 63 kg weight is 63 kg weight is 63 kg weight is 63 kg weight is 63 kg weight is 63 kg Specifying the field width can be useful in creating tables of numeric values with the numbers lined up properly, as the following program demonstrates: # include int main( ) { printf ( "%10.1f %10.1f %10.1f\n", 5.0, 13.5, 133.9 ) ; printf ( "%10.1f %10.1f %10.1f\n", 305.0, 1200.9, 3005.3 ); return 0 ; } This results into a much better output... 01234567890123456789012345678901 5.0 13.5 133.9 305.0 1200.9 3005.3 Note that the specifier %10.1f specifies that a float be printed right- aligned within 10 columns, with one place beyond the decimal point. The format specifiers can be used even while displaying a string of characters. The following program would clarify this point: # include int main( ) { char firstname1[ ] = "Sandy" ; char surname1[ ] = "Malya" ; char firstname2[ ] = "AjayKumar" ; char surname2[ ] = "Gurubaxani" ; printf ( "%20s%20s\n", firstname1, surname1 ) ; printf ( "%20s%20s\n", firstname2, surname2 ) ; return 0 ; } 314 Let Us C And here’s the output... 012345678901234567890123456789012345678901234567890 Sandy Malya AjayKumar Gurubaxani The format specifier %20s prints the string in these 20 columns with right justification. This helps lining up names of different lengths properly. Obviously, the format %-20s would have left-justified the string. Had we used %-20.10s it would have meant left-justify the string in 20 columns and print only first 10 characters of the string. Escape Sequences All escape sequences begin with a \. They are called because the backslash symbol (\) is considered as an ‘escape’ character—it causes an escape from the normal interpretation of a string, so that the next character is recognized as one having a special meaning. Figure 18.4 shows a complete list of these escape sequences. Escape Seq. Purpose Escape Seq. Purpose \n New line \t Tab \v Vertical tab \b Backspace \r Carriage return x \f Form feed \a Alert \’ Single quote \” Double quote \\ Backslash \? Question mark \xhh Char in hex value Figure 18.4 Escape sequences. The following program shows usage of escape sequences \n, \t and \v. # include int main( ) { printf ( "Maths teaches reasoning,\nnot Algebra & geometry\n" ) ; printf ( "Future of learning \r is remote\n" ) ; printf ( "You\tmust\tbe\tcrazy\vto\thate\tthis\tbook\n" ) ; return 0 ; } Chapter 18: Console Input/Output 315 And here’s the output... 01234567890123456789012345678901234567890123456789012345… Maths teaches reasoning, not Algebra & geometry is remotelearning You must be crazy to hate this book The \n takes the cursor to beginning of next line. Hence in the first printf( ) message after the word 'reasoning,' is printed on the next line. In the second printf( ), \r positioned the cursor to the beginning of the line in which it is currently placed. Hence, the message got overwritten. While using \t an 80-column screen is divided into 10 print zones of 8 columns each. Using a \t moves the cursor to the beginning of next print zone. For example, if cursor is positioned in column 5, then printing a \t takes the cursor to column 8. \v moves the cursor in the next line below its current position. \b moves the cursor one position to the left of its current position. \a alerts the user by sounding the speaker inside the computer. Form feed advances the computer stationery attached to the printer to the top of the next page. Characters single quote, double quote, backslash and question mark can be printed by preceding them with the backslash. Thus, the statement, printf ( "He said, \"How\'s life\?\"" ) ; will print... He said, "How's life?" Finally, the character 'A' can be printed using decimal or its octal and hexadecimal representation using the following statement: printf ( "%c %c %c", 65, '\101', '\x41' ) ; Ignoring Characters Sometimes we may wish to ignore some of the characters supplied as input. For example, while receiving a date we may wish to ignore the separator like '.', '/' or '-'. This can be done using %*c. This means if a '.' or '/' or '-' is entered it would be matched and ignored. The * ensures 316 Let Us C that the character entered doesn't get assigned to any variable in the variable list. printf ( "Enter date in dd/mm/yy or dd.mm.yy or dd-mm-yy format\n") ; scanf ( "%d%*c%d%*c%d", &dd, &mm, &yy ) ; printf ( "%d/%d%/%d\n", dd, mm, yy ) ; Mismatch If there is a mismatch in the specifier and the type of value being printed, printf( ) attempts to perform the specified conversion, and does its best to produce a proper result. Sometimes the result is nonsensical, as in case when we ask it to print a string using %d. Sometimes the result is useful, as in the case we ask printf( ) to print ASCII value of a character using %d. Sometimes the result is disastrous and the entire program blows up. The following program shows a few of these conversions, some sensible, some weird: # include int main( ) { char ch = 'z' ; int i = 125 ; float a = 12.55 ; char s[ ] = "hello there !" ; printf ( "%c %d %f\n", ch, ch, ch ) ; printf ( "%s %d %f\n", s, s, s ) ; printf ( "%c %d %f\n",i ,i, i ) ; printf ( "%f %d\n", a, a ) ; return 0 ; } And here’s the output... z 122 -9362831782501783000000000000000000000000000.000000 hello there ! 3280 - 9362831782501783000000000000000000000000000.000000 } 125 -9362831782501783000000000000000000000000000.000000 12.550000 0 I would leave it to you to analyze the results by yourselves. You would find that some of the conversions are quite sensible. Chapter 18: Console Input/Output 317 Let us now turn our attention to scanf( ). The scanf( ) function allows us to enter data from keyboard that will be formatted in a certain way. The general form of scanf( ) statement is as follows: scanf ( "format string", list of addresses of variables ) ; For example: scanf ( "%d %f %c", &c, &a, &ch ) ; Note that we are sending addresses of variables to scanf( ) function. This is necessary because the values received from keyboard must be dropped into variables corresponding to these addresses. The values that are supplied through the keyboard must be separated by either blank(s), Tab(s), or newline(s). Do not include these escape sequences in the format string. All the format specifications that we learnt in printf( ) function are applicable to scanf( ) function as well. sprintf( ) and sscanf( ) Functions The sprintf( ) function works similar to the printf( ) function except for one small difference. Instead of sending the output to the screen as printf( ) does, this function writes the output to a string. The following program illustrates this: # include int main( ) { int i = 10 ; char ch = 'A' ; float a = 3.14 ; char str[ 20 ] ; printf ( "%d %c %f\n", i, ch, a ) ; sprintf ( str, "%d %c %f", i, ch, a ) ; printf ( "%s\n", str ) ; return 0 ; } In this program, the printf( ) prints out the values of i, ch and a on the screen, whereas sprintf( ) stores these values in the string str. Since the string str is present in memory, what is written into str using sprintf( ) doesn’t get displayed on the screen. Once str has been built, its contents 318 Let Us C can be displayed on the screen. In our program this is achieved by the. second printf( ) statement. The counterpart of sprintf( ) is the sscanf( ) function. It allows us to read characters from a string and to convert and store them in C variables according to specified formats. The sscanf( ) function comes in handy for in-memory conversion of characters to values. You may find it convenient to read in strings from a file and then extract values from a string by using sscanf( ). The usage of sscanf( ) is same as scanf( ), except that the first argument is the string from which reading is to take place. Unformatted Console I/O Functions There are several standard library functions available under this category—those that can deal with a single character and those that can deal with a string of characters. For openers, let us look at those which handle one character at a time. fgetchar( ) and fputchar( ) The fgetchar( ) function (or its equivalent getchar( ) macro) lets you read a single character entered from keyboard. The character that is typed has to be followed by Enter key. Its counterpart is fputchar( ) (or its equivalent putchar( ) macro) which displays a character on the screen. Their usage is shown in the following program: # include int main( ) { char ch ; printf ( "\nType any alphabet" ) ; ch = getchar( ) ; printf ( "You typed " ) ; putchar ( ch ) ; return 0 ; } With fgetchar( ) you need to hit the Enter key before the function can digest what you have typed. However, we may want a function that will read a single character the instant it is typed without waiting for the Enter key to be hit. There is no standard function to achieve this and there are different solutions for different OS. Chapter 18: Console Input/Output 319 Compilers like Turbo C and Visual Studio provide function called getch( ) to achieve this. Its prototype is present in the file conio.h. This function reads a single character from keyboard. But it does not use any buffer, so the entered character is immediately returned without waiting for the enter key. In Linux-based systems the same effect can be obtained by doing some terminal settings using stty command. gets( ) and puts( ) gets( ) receives a string from the keyboard. It is terminated when an Enter key is hit. Thus, spaces and tabs are perfectly acceptable as part of the input string. More exactly, gets( ) function gets a newline (\n) terminated string of characters from the keyboard and replaces the \n with a \0. The puts( ) function works exactly opposite to gets( ) function. It outputs a string to the screen. Here is a program which illustrates the usage of these functions. # include int main( ) { char footballer[ 40 ] ; puts ( "Enter name" ) ; gets ( footballer ) ; puts ( "Happy footballing!" ) ; puts ( footballer ) ; return 0 ; } Following is the sample output: Enter name Lionel Messi Happy footballing! Lionel Messi Why did we use two puts( ) functions to print “Happy footballing!” and “Lionel Messi”? Because, unlike printf( ), puts( ) can output only one string at a time. If we attempt to print two strings using puts( ), only the first one gets printed. Similarly, unlike scanf( ), gets( ) can be used to read only one string at a time. A word of caution! While using gets( ) if the length of the input string is bigger than the size of the string passed to gets( ) then we may end up 320 Let Us C exceeding the bounds of the string, which is dangerous. This can be avoided using fgets( ) function as shown below: char str[ 20 ] ; puts ( "Enter a string: " ) ; fgets ( str, 20, stdin ) ; puts ( str ) ; Here is the sample interaction with this code snippet... Enter a string: It is safe to use fgets than gets It is safe to use f Note that only 19 characters were stored in str[ ] followed by a '\0'. So, bounds of the string were not exceeded. Here stdin represents standard input device, i.e., keyboard. [A] What will be the output of the following programs? (a) # include # include int main( ) { char ch ; ch = getchar( ) ; if ( islower ( ch ) ) putchar ( toupper ( ch ) ) ; else putchar ( tolower ( ch ) ) ; return 0 ; } (b) # include int main( ) { int i = 2 ; float f = 2.5367 ; char str[ ] = "Life is like that" ; printf ( "%4d\t%3.3f\t%4s\n", i, f, str ) ; Chapter 18: Console Input/Output 321 return 0 ; } (c) # include int main( ) { printf ( "More often than \b\b not \rthe person who \ wins is the one who thinks he can!\n" ) ; return 0 ; } (d) # include char p[ ] = "The sixth sick sheikh's sixth ship is sick" ; int main( ) { int i = 0 ; while ( p[ i ] != '\0' ) { putchar ( p[ i ] ) ; i++ ; } return 0 ; } [B] Point out the errors, if any, in the following programs: (a) # include int main( ) { int i ; char a[ ] = "Hello" ; while ( a != '\0' ) { printf ( "%c", *a ) ; a++ ; } return 0 ; } (b) # include int main( ) { double dval ; 322 Let Us C scanf ( "%f", &dval ) ; printf ( "Double Value = %lf\n", dval ) ; return 0 ; } (c) # include int main( ) { int ival ; scanf ( "%d\n", &n ) ; printf ( "Integer Value = %d\n", ival ) ; return 0 ; } (d) # include int main( ) { int dd, mm, yy ; printf ( "Enter date in dd/mm/yy or dd-mm-yy format\n" ) ; scanf ( "%d%*c%d%*c%d", &dd, &mm, &yy ) ; printf ( "The date is: %d - %d - %d\n", dd, mm, yy ) ; return 0 ; } (e) # include int main( ) { char text ; sprintf ( text, "%4d\t%2.2f\n%s", 12, 3.452, "Merry Go Round" ) ; printf ( "%s\n", text ) ; return 0 ; } (f) # include int main( ) { char buffer[ 50 ] ; int no = 97; double val = 2.34174 ; char name[ 10 ] = "Shweta" ; sprintf ( buffer, "%d %lf %s", no, val, name ) ; printf ( "%s\n", buffer ) ; sscanf ( buffer, "%4d %2.2lf %s", &no, &val, name ) ; Chapter 18: Console Input/Output 323 printf ( "%s\n", buffer ) ; printf ( "%d %lf %s\n", no, val, name ) ; return 0 ; } [C] Answer the following questions: (a) To receive the string "We have got the guts, you get the glory!!" in an array char str[ 100 ] which of the following functions would you use? 1. scanf ( "%s", str ) ; 2. gets ( str ) ; 3. getchar ( str ) ; 4. fgetchar ( str ) ; (b) If an integer is to be entered through the keyboard, which function would you use? 1. scanf( ) 2. gets( ) 3. getche( ) 4. getchar( ) (c) Which of the following can a format string of a printf( ) function contain: 1. Characters, format specifications and escape sequences 2. Character, integers and floats 3. Strings, integers and escape sequences 4. Inverted commas, percentage sign and backslash character (d) The purpose of the field-width specifier in a printf( ) function is to: 1. Control the margins of the program listing 2. Specify the maximum value of a number 3. Control the size of font used to print numbers 4. Specify how many columns should be used to print the number (e) If we are to display the following output properly aligned which format specifiers would you use? Discovery of India Jawaharlal Nehru 425.50 My Experiments with Truth Mahatma Gandhi 375.50 Sunny Days Sunil Gavaskar 95.50 One More Over Erapalli Prasanna 85.00 324 Let Us C x I/O in C is always done using functions, not using keywords x All I/O functions can be divided into 2 broad categories : 1) Console I/O functions : a) Formatted b) Unformatted 2) Disk I/O functions x The formatted console I/O functions can force the user to receive the input in a fixed format and display the output in a fixed format. x All formatted Console I/O is done using printf( ) and scanf( ) x Examples of formatting : %20s – right-align a string in 20 columns %-10d – left-align an integer in 10 columns %12.4f – right-align a float in 12 columns with 4 places beyond decimal point x Escape sequences : \n - positions cursor on next line \r - positions cursor at beginning of same line When we hit enter \r is generated and is converted into \r\n \t - positions cursor at beginning of next print zone. 1 print zone = 8 columns \v – positions cursor in the next line below current cursor position \’, \”, \?, \\ - produces ‘ “ ? \ in the output \xhh – represents ASCII character in hexadecimal notation x scanf( ) can contain format specifier like %10.2f, but it is too restrictive, hence rarely used x Unformatted console I/O functions : char - fgetchar( ), fputchar( ). fgetchar( ) - Waits for enter int / float - no functions string - gets( ), puts( ), fgets( ), fputs( ) 19 File Input / Output “Save in file, what you will need in future” Once you know how to read / write data from / to file, you have crossed a major hurdle. With this knowledge under your belt, you will be able to write many useful programs. This chapter shows you how… 325 Chapter 19: File Input/Output 327 O ften data is so large that all of it cannot be stored in memory and only a limited amount of it can be displayed on the screen. Also, memory is volatile and its contents would be lost once the program is terminated. At such times, it becomes necessary to store the data in a ‘file’ on disk so that it can be later retrieved, used and displayed either in part or in whole. This chapter discusses how file I/O operations can be performed. File Operations There are different operations that can be carried out on a file. These are: (a) Creation of a new file (b) Opening an existing file (c) Reading from a file (d) Writing to a file (e) Moving to a specific location in a file (seeking) (f) Closing a file Let us write a program to read a file and display its contents on the screen. We will first list the program and show what it does, and then dissect it line-by-line. Here is the listing… # include int main( ) { FILE *fp ; char ch ; fp = fopen ( "PR1.C", "r" ) ; while ( 1 ) { ch = fgetc ( fp ) ; if ( ch == EOF ) break ; printf ( "%c", ch ) ; } printf ( "\n" ) ; fclose ( fp ) ; return 0 ; } 328 Let Us C On execution of this program, it displays the contents of the file ‘PR1.C’ on the screen. Let us now understand how it does the same. Opening a File The basic logic of the program is as follows: (a) Read a character from file. (b) Display the character read on the screen. (c) Repeat steps (a) and (b) for all characters in the file. It would be quite inefficient to access the disk every time we want to read a character from it. It would be more sensible to read the contents of the file into a buffer (a chunk in memory) while opening the file and then read the file character by character from the buffer rather than from the disk. This is shown in Figure 19.1. Memory fp 40 PR1.C 40 Buffer Disk Figure 19.1 File read operation. Same argument also applies to writing information in a file. Instead of writing characters in the file on the disk one character at a time, it would be more efficient to write characters in a buffer and then finally transfer the contents from the buffer to the disk. Before we can read (or write) information from (to) a file on a disk we must open the file. To open the file we have called the function fopen( ). It would open a file “PR1.C” in ‘read’ mode since we intend to read the file contents. In fact fopen( ) performs three important tasks when you open the file in “r” mode: (a) It locates the file to be opened, on the disk. (b) It loads the file from the disk into a buffer. (c) It sets up a character pointer which points to the first character of the buffer. (d) It sets up a FILE structure and returns its address. Chapter 19: File Input/Output 329 Let us understand the purpose of the FILE structure. To be able to successfully read from a file, information like mode of opening, size of file, place in the file from where the next read operation would be performed, etc., has to be maintained. Since all this information is inter- related, all of it is set by fopen( ) in a structure called FILE. fopen( ) returns the address of this structure, which we have collected in the structure pointer fp. We have declared fp as follows: FILE *fp ; The FILE structure has been declared in the header file “stdio.h” (standing for standard input/output header file). Reading from a File A call to fopen( ) sets up a pointer that points to the first character in the buffer. This pointer is one of the elements of the structure to which fp is pointing (refer Figure 19.1). To read the file’s contents from buffer we have called fgetc( ) as under. ch = fgetc ( fp ) ; fgetc( ) reads the character from the current pointer position, advances the pointer position so that it now points to the next character, and returns the character that is read, which we have collected in the variable ch. Note that once the file has been opened, we no longer refer to the file by its name, but through the file pointer fp. We have used the function fgetc( ) in an infinite while loop. We should break out of this loop when all the characters from the file have been read. But how would we know this? Well, fgetc( ) returns a macro EOF (End of File), once all the characters have been read and we attempt to read one more character. The EOF macro is defined in the file “stdio.h”. Closing the File When we have finished reading from the file, we need to close it. This is done using the function fclose( ) through the statement, fclose ( fp ) ; Once we close the file, we can no longer read from it using fgetc( ). Note that to close the file, we don’t use the filename but the file pointer fp. 330 Let Us C On closing the file, the buffer associated with the file is removed from memory. In this program we have opened the file for reading. Suppose we open a file with an intention to write characters into it. This time too, a buffer would get associated with it. When we attempt to write characters into this file using fputc( ) the characters would get written to the buffer. When we close this file using fclose( ) two operations would be performed: (a) The characters in the buffer would be written to the file on the disk. (b) The buffer would be eliminated from memory. You can imagine a possibility when the buffer may become full before we close the file. In such a case the buffer’s contents would be written to the disk the moment it becomes full. This buffer management is done for us by the library functions. Counting Characters, Tabs, Spaces, … Having understood the first file I/O program, let us write a program that will read a file and count how many characters, spaces, tabs and newlines are present in it. Here is the program… # include int main( ) { FILE *fp ; char ch ; int nol = 0, not = 0, nob = 0, noc = 0 ; fp = fopen ( "PR1.C", "r" ) ; while ( 1 ) { ch = fgetc ( fp ) ; if ( ch == EOF ) break ; noc++ ; if ( ch == ' ' ) nob++ ; if ( ch == '\n' ) nol++ ; if ( ch == '\t' ) not++ ; Chapter 19: File Input/Output 331 } fclose ( fp ) ; printf ( "Number of characters = %d\n", noc ) ; printf ( "Number of blanks = %d\n", nob ) ; printf ( "Number of tabs = %d\n", not ) ; printf ( "Number of lines = %d\n", nol ) ; return 0 ; } Here is a sample run... Number of characters = 125 Number of blanks = 25 Number of tabs = 13 Number of lines = 22 The above statistics are true for a file “PR1.C”, which I had on my disk. You may give any other filename and obtain different results. I believe the program is self-explanatory. In this program too, we have opened the file for reading and then read it character-by-character. Let us now try a program that needs to open a file for writing. A File-Copy Program We have already used the function fgetc( ) which reads characters from a file. Its counterpart is a function called fputc( ) which writes characters to a file. As a practical use of these character I/O functions, we can copy the contents of one file into another, as demonstrated in the following program. This program reads the contents of a file and copies them into another file, character-by-character. # include # include int main( ) { FILE *fs, *ft ; char ch ; fs = fopen ( "PR1.C", "r" ) ; if ( fs == NULL ) { puts ( "Cannot open source file" ) ; exit ( 1 ) ; } ft = fopen ( "PR2.C", "w" ) ; 332 Let Us C if ( ft == NULL ) { puts ( "Cannot open target file" ) ; fclose ( fs ) ; exit ( 2 ) ; } while ( 1 ) { ch = fgetc ( fs ) ; if ( ch == EOF ) break ; else fputc ( ch, ft ) ; } fclose ( fs ) ; fclose ( ft ) ; return 0 ; } There is a possibility that when we try to open a file using the function fopen( ), the file may not be opened. While opening the file in “r” mode, this may happen because the file being opened may not be present on the disk at all. And you obviously cannot read a file that doesn’t exist. Similarly, while opening the file for writing, fopen( ) may fail due to a number of reasons, like, disk space may be insufficient to create a new file, or you may not have write permission to the disk or the disk is damaged and so on. If fopen( ) fails to open a file it returns a value NULL (defined in “stdio.h” as #define NULL 0). In this program we have handled this possibility by checking whether fs and ft are set to NULL. If any of them has been set to NULL we have called the exit( ) function to terminate the execution of the program. Usually, a value 0 is passed to exit( ) if the program termination is normal. A non-zero value indicates an abnormal termination of the program. If there are multiple exit points in the program, then the value passed to exit( ) can be used to find out from where the execution of the program got terminated. The fputc( ) function writes a character to a file pointed to by ft. The writing process continues till all characters from the source file have been written to the target file, following which the while loop terminates. Chapter 19: File Input/Output 333 Note that this file-copy program is capable of copying only text files. To copy binary files with extension.EXE or.JPG, we need to open the files in binary mode, which is dealt with in detail in a later section. File Opening Modes Following is a list of modes in which a file can be opened along with the tasks performed by fopen( ) when the file is opened. "r" Searches file. If file is opened successfully fopen( ) loads it into memory and sets up a pointer which points to the first character in it. If the file cannot be opened, fopen( ) returns NULL. Operations possible – reading from the file. "w" Searches file. If the file exists, its contents are overwritten. If the file doesn’t exist, a new file is created. Returns NULL, if unable to open file. Operations possible – writing to the file. "a" Searches file. If file is opened successfully fopen( ) loads it into memory and sets up a pointer that points to the last character in it. If the file doesn’t exist, a new file is created. Returns NULL, if unable to open file. Operations possible - adding new contents at the end of file. "r+" Searches file. If file is opened successfully fopen( ) loads it into memory and sets up a pointer that points to the first character in it. Returns NULL, if unable to open the file. Operations possible - reading existing contents, writing new contents, modifying existing contents of the file. "w+" Searches file. If the file exists, its contents are overwritten. If the file doesn’t exist, a new file is created. Returns NULL, if unable to open file. Operations possible - writing new contents, reading them back and modifying existing contents of the file. "a+" Searches file. If file is opened successfully fopen( ) loads it into memory and sets up a pointer that points to the first character in it. If the file doesn’t exist, a new file is created. 334 Let Us C Returns NULL, if unable to open file. Operations possible - reading existing contents, appending new contents to end of file. Cannot modify existing contents. String (Line) I/O in Files For many purposes, character I/O is just what is needed. However, in some situations, the usage of functions that read or write entire strings might turn out to be more efficient. Here is a program that writes strings to a file using fputs( ) and then reads them back using fgets( ). # include # include # include int main( ) { FILE *fp ; char str[ 80 ] ; fp = fopen ( "POEM.TXT", "w" ) ; if ( fp == NULL ) { puts ( "Cannot open file" ) ; exit ( 1 ) ; } printf ( "\nEnter a few lines of text:\n" ) ; while ( strlen ( gets ( str ) ) > 0 ) { fputs ( str, fp ) ; fputs ( "\n", fp ) ; } fclose ( fp ) ; printf ( "\nFile contents are being read now…\n" , s ) ; fp = fopen ( "POEM.TXT", "r" ) ; if ( fp == NULL ) { puts ( "Cannot open file" ) ; exit ( 2 ) ; } while ( fgets ( str, 79, fp ) != NULL ) printf ( "%s" , str ) ; fclose ( fp ) ; Chapter 19: File Input/Output 335 return 0 ; } And here is a sample run of the program... Enter a few lines of text: Shining and bright, they are forever, so true about diamonds, more so of memories, especially yours! File contents are being read now… Shining and bright, they are forever, so true about diamonds, more so of memories, especially yours! During execution, after entering each string hit Enter. To terminate the execution of the loop, hit Enter at the beginning of a line. This creates a string of zero length, which the program recognizes as the signal to end the loop. We have set up a character array str to receive a string; fputs( ) function writes the contents of this array to the file. Since fputs( ) does not automatically add a newline character to the end of the string, we must do this explicitly to make it easier to read the string back from the file. While reading the file, the function fgets( ) takes three arguments. The first argument is the address where the string is stored. The second argument is the maximum length of the string. This argument prevents fgets( ) from reading in too long a string and overflowing the array. The third argument is the pointer to the structure FILE. On reading a line from the file, the string str would contain the line contents a ‘\n’ followed by a ‘\0’. Thus the string is terminated by fgets( ) and we do not have to terminate it specifically. When all the lines from the file have been read, we attempt to read one more line, in which case fgets( ) returns a NULL. Text Files and Binary Files All the programs that we wrote in this chapter so far worked on text files. A text file contains only textual information like alphabets, digits 336 Let Us C and special symbols. A good example of a text file is any C program, say PR1.C. As against this, a binary file is merely a collection of bytes. This collection might be a compiled version of a C program (say PR1.EXE), or music data stored in a MP4 file or a picture stored in a JPG file. A very easy way to find out whether a file is a text file or a binary file is to open that file in Notepad. If on opening the file you can make out what is displayed then it is a text file, otherwise it is a binary file. From the programming angle there are two main areas where text and binary mode files are different. These are discussed below. Text versus Binary Mode: Newlines In text mode, a newline character is converted into the carriage return- linefeed combination before being written to the file. Likewise, the carriage return-linefeed combination in the file is converted back into a newline when the file is read back. If a file is opened in binary mode, these conversions do not take place. Text versus Binary Mode: Storage of Numbers While using text mode, numbers are stored as character strings. Thus, an integer 12579 occupies 4 bytes in memory, but when written to the file it would occupy 5 bytes, 1 byte per character. Similarly, the floating- point number 1234.56 would occupy 7 bytes in file. Thus, numbers with more digits would require more storage space. Hence if large amount of numerical data is to be stored in a disk file, using text mode may turn out to be inefficient. The solution is to open the file in binary mode and use functions fread( ) and fwrite( ) which work with numbers in binary format. As a result, each number would occupy same number of bytes on disk as it occupies in memory. Record I/O in Files Suppose we wish to perform I/O of data about employees from/to file. For this we would have to create a struct employee and then use the following functions to read/write employee data from/to file. File opened in text mode - fscanf( ) / fprintf( ) File opened in binary mode - fread( ) / fwrite( ) Chapter 19: File Input/Output 337 Given below is the code snippet that shows how to use these functions. You may replace the comments with actual code to make it a fully workable program. # include int main( ) { FILE *fp ; struct emp { char name[ 40 ] ; int age ; float bs ; }; struct emp e ; char ch = 'Y' ; fp = fopen ( "EMPLOYEE.DAT", "w" ) ; while ( ch == 'Y' ) { printf ( "Enter name, age, salary: " ) ; scanf ( "%s %d %f", e.name, &e.age, &e.bs ) ; fprintf ( fp, "%s %d %f\n", e.name, e.age, e.bs ) ; printf ( "Another record: " ) ; ch = fgetchar( ) ; } fclose ( fp ) ; fp = fopen ( "EMPLOYEE.DAT", "r" ) ; while ( fscanf ( fp, "%s %d %f", e.name, &e.age, &e.bs ) != EOF ) printf ( "%s %d %f\n", e.name, e.age, e.bs ) ; fclose ( fp ) ; ch = 'Y' ; fp = fopen ( "EMP.DAT", "wb" ) ; while ( ch == 'Y' ) { printf ( "Enter name, age, salary: " ) ; scanf ( "%s %d %f", e.name, &e.age, &e.bs ) ; fwrite ( &e, sizeof ( e ), 1, fp ) ; printf ( "Another record: " ) ; ch = fgetchar( ) ; } fclose ( fp ) ; 338 Let Us C fp = fopen ( "EMP.DAT", "rb" ) ; while ( fread ( &e, sizeof ( e ), 1, fp ) == 1 ) printf ( "%s %d %f\n", e.name, e.age, e.bs ) ; fclose ( fp ) ; return 0 ; } Note that we have opened the binary file ‘EMP.DAT’ in “rb” and “wb” modes. While opening the file in text mode we can use either “r” or “rt”, but since text mode is the default mode, we usually drop the ‘t’. To read / write a record in a text mode file we have used fscanf( ) and fprintf( ) respectively. They work same as scanf( ) and printf( ) except that they have an additional first argument fp. This argument indicates the file on which they are supposed to work. To read / write a record in a binary mode file we have used fread( ) and fwrite( ) respectively. Let us understand the following call: fwrite ( &e, sizeof ( e ), 1, fp ) ; Suppose the address of e is 400 and size of e is 48 bytes. So the above call means—starting with address 400, write next 48 bytes, once, into a file pointed to by fp. Likewise, the call, fwrite ( &e, sizeof ( e ), 1, fp ) ; would mean—from a file pointed to by fp, read once, 48 bytes and store them at an address starting from 400. The text file-based record I/O has two disadvantages: (a) The numbers would occupy a greater number of bytes, as each number is stored as a character string. (b) If the number of fields in the structure increase (say, by adding address, house rent allowance, etc.), writing structures using fprintf( ), or reading them using fscanf( ), would become tedious. Modifying Records We know how to read or write records from / to a binary mode fle. But what if we are to modify an existing record? Well, when we open a file fopen( ) returns a pointer to a structure. This structure contains a Chapter 19: File Input/Output 339 pointer which points to the first record in the file. fread( ) always reads a record from where the pointer is currently placed. Similarly, fwrite( ) always writes a record where the pointer is currently placed. On using the function fread( ) or fwrite( ), the pointer moves to the beginning of the next record. On closing a file, the pointer is deactivated. The rewind( ) function places the pointer to the beginning of the file, irrespective of where it is present right now. The fseek( ) function lets us move the pointer from one record to another. These functions have been used in the following code to modify an existing record in a file. int recsize ; recsize = sizeof ( struct emp ) ; printf ( "\nEnter name of employee to modify: " ) ; scanf ( "%s", empname ) ; rewind ( fp ) ; while ( fread ( &e, recsize, 1, fp ) == 1 ) { if ( strcmp ( e.name, empname ) == 0 ) { printf ( "\nEnter new name, age & bs " ) ; scanf ( "%s %d %f", e.name, &e.age, &e.bs ) ; fseek ( fp, -recsize, SEEK_CUR ) ; fwrite ( &e, recsize, 1, fp ) ; break ; } } To move the pointer to the previous record from its current position, we have used the function, fseek ( fp, -recsize, SEEK_CUR ) ; -recsize moves the pointer back by recsize bytes from the current position. SEEK_CUR is a macro defined in “stdio.h”. Similarly, if we wish to place the pointer beyond the last record in the file, we can use fseek ( fp, 0, SEEK_END ) ; In fact, -recsize or 0 are just the offsets that tell the compiler by how many bytes should the pointer be moved from a reference position. The 340 Let Us C reference position could be SEEK_END, SEEK_CUR or SEEK_SET. SEEK_END means move the pointer from the end of the file, SEEK_CUR means move the pointer with reference to its current position and SEEK_SET means move the pointer with reference to the beginning of the file. Once the pointer has been properly positioned, we have written a new record that overwrites an existing record. If we wish to know where the pointer is positioned right now, we can use the function ftell( ). It returns this position as a long int which is an offset from the beginning of the file. A sample call to ftell( ) is shown below. long int position ; position = ftell ( fp ) ; Low-Level File I/O In low-level File I/O, data cannot be written as individual characters, or as strings or as formatted data. There is only one way to read/write data in low-level file I/O functions—as a buffer full of bytes. Writing a buffer full of data resembles the fwrite( ) function. However, unlike fwrite( ), the programmer must set up the buffer for the data, place the appropriate values in it before writing, and take them out after writing. Thus, the buffer in the low-level I/O functions is part of the program, rather than being invisible as in high-level file I/O functions. Low-level file I/O functions offer following advantages: (a) Since these functions parallel the methods that the OS uses to write to the disk, they are more efficient than high-level file I/O functions. (b) Since there are fewer layers of routines to go through, low-level I/O functions operate faster than their high-level counterparts. Let us now write a program that uses low-level file input/output functions. A Low-level File-copy Program Earlier we had written a program to copy the contents of one file to another on a character-by-character basis. We can rewrite the same program to read a chunk of bytes from the source file and then write this chunk into the target file. While doing so, the chunk would be read Chapter 19: File Input/Output 341 into the buffer and would be written to the file from the buffer. We would manage the buffer ourselves, rather than relying on the library functions to do so. This is what is low-level about this program. Here is a program which shows how this can be done. # include # include # include # include # include int main( ) { char buffer[ 512 ], source[ 128 ], target[ 128 ] ; int in, out, bytes ; printf ( "\nEnter source file name: " ) ; gets ( source ) ; in = open ( source, O_RDONLY | O_BINARY ) ; if ( in == -1 ) { puts ( "Cannot open file" ) ; exit ( 1 ) ; } printf ( "\nEnter target file name: " ) ; gets ( target ) ; out = open ( target, O_CREAT | O_BINARY | O_WRONLY, S_IWRITE) ; if ( out == -1 ) { puts ( "Cannot open file" ) ; close ( in ) ; exit ( 2 ) ; } while ( ( bytes = read ( in, buffer, 512 ) ) > 0 ) write ( out, buffer, bytes ) ; close ( in ) ; close ( out ) ; return 0 ; } Declaring the Buffer The first difference that you will notice in this program is that we declare a character buffer, char buffer[ 512 ] ; 342 Let Us C This is the buffer in which the data read from the file will be placed. The size of this buffer is important for efficient operation. Depending on the operating system, buffers of certain sizes are handled more efficiently than others. Opening a File We have opened two files in our program, one is the source file from which we read the information, and the other is the target file into which we write the information read from the source file. As in high-level file I/O, the file must be opened before we can access it. This is done using the statement, in = open ( source, O_RDONLY | O_BINARY ) ; As usual, we have to supply to open( ), the filename and the mode in which we want to open the file. The possible file opening modes are given below. O_APPEND - Opens a file for appending O_CREAT - Creates a new file for writing (no effect if file exists) O_RDONLY - Opens a new file for reading only O_RDWR - Creates a file for both reading and writing O_WRONLY - Creates a file for writing only O_BINARY - Opens a file in binary mode O_TEXT - Opens a file in text mode These ‘O-flags’ are defined in the file “fcntl.h”. So, this file must be included in the program while using low-level file I/O. When two or more O-flags are used together, they are combined using the bitwise OR operator ( | ). Chapter 21 discusses bitwise operators in detail. The other statement used in our program to open the file is, out = open ( target, O_CREAT | O_BINARY | O_WRONLY, S_IWRITE ) ; Note that since the target file doesn’t exist when it is being opened, we have used the O_CREAT flag, and since we want to write to the file, we have used O_WRONLY. And finally, since we want to open the file in binary mode, we have used O_BINARY. Chapter 19: File Input/Output 343 Whenever O_CREAT flag is used, another argument must be added to open( ) function to indicate the read/write status of the file to be created. This argument is called ‘permission argument’. Permission arguments could be any of the following: S_IWRITE - Writing to the file permitted S_IREAD - Reading from the file permitted To use these permissions, both the files “types.h” and “stat.h” present in “sys” folder must be #included in the program along with “fcntl.h”. File Handles Instead of returning a FILE pointer as fopen( ) did, in low-level file I/O, open( ) returns an integer value called ‘file handle’. This is a number assigned to a particular file, which is used thereafter to refer to the file. If open( ) returns a value of -1, it means that the file couldn’t be successfully opened. Interaction between Buffer and File The following statement reads the file or as much of it as will fit into the buffer: bytes = read ( in, buffer, 512 ) Here the first argument is file handle, the second is the address of the buffer and the third is the maximum number of bytes we want to read. For copying the file, we must use both the read( ) and the write( ) functions in a while loop. The read( ) function returns the number of bytes actually read. This is assigned to the variable bytes. This variable is used to tell write( ) how many bytes to write from the buffer to the target file. Problem 19.1 Write a program to read a file and display its contents along with line numbers before each line. Program 344 Let Us C # include # include int main( ) { FILE *fp ; char ch, source[ 67 ] ; int count = 1 ; printf ( "\nEnter file name: " ) ; scanf ( "%s", source ) ; fp = fopen ( source, "r" ) ; if ( fp == NULL ) { puts ( "Unable to open the file." ) ; exit ( 0 ) ; } printf ( "\n%3d: ", count ) ; while ( ( ch = getc( fp ) ) != EOF ) { if ( ch == '\n' ) { count++ ; printf ( "\n%3d: ", count ) ; } else printf ( "%c", ch ) ; } fclose ( fp ) ; return 0 ; } Output Enter the file name: Sample.txt 1: What is this life 2: if full of care 3: We have no time 4: to stand and stare! ____________________________________________________________________ Problem 19.2 Write a program to append the contents of one file at the end of another. Chapter 19: File Input/Output 345 Program # include # include # include int main( ) { FILE *fs, *ft ; char source[ 67 ], target[ 67 ], str[ 80 ] ; puts ( "Enter source file name: " ) ; gets ( source ) ; puts ( "Enter target file name: " ) ; gets ( target ) ; fs = fopen ( source, "r" ) ; if ( fs == NULL ) { puts ( "Unable to open source file" ) ; exit ( 0 ) ; } ft = fopen ( target, "a" ) ; if ( ft == NULL ) { fclose ( fs ) ; puts ( "Unable to open target file" ) ; exit ( 0 ) ; } while ( fgets ( str, 79, fs ) != NULL ) fputs ( str, ft ) ; printf ( "Appending file completed!!" ) ; fclose ( fs ) ; fclose ( ft ) ; return 0 ; } Output Enter source file name: Sample.txt Enter target file name: NewSample.txt Appending file completed!! ____________________________________________________________________ 346 Let Us C [A] Answer the following questions: (a) In which file FILE structure is defined? (b) If a file contains the line “I am a boy\r\n” then on reading this line into the array str[ ] using fgets( ) what would str[ ] contain? (c) State True or False: 1. The disadvantage of high-level file I/O functions is that the programmer has to manage the file buffers. 2. If a file is opened for reading, it is necessary that the file must exist. 3. If a file opened for writing already exists, its contents would be overwritten. 4. For opening a file in append mode it is necessary that the file should exist. (d) On opening a file for reading which of the following activities are performed: 1. The disk is searched for existence of the file. 2. The file contents are brought into memory. 3. A pointer is set up which points to the first character in the file. 4. All the above. (e) Is it necessary that a file created in text mode must always be opened in text mode for subsequent operations? [B] Attempt the following questions: (a) Suppose a file contains student records with each record containing name and age of a student. Write a program to read these records and display them in sorted order by name. (b) Write a program to copy contents of one file to another. While doing so replace all lowercase characters to their equivalent uppercase characters. (c) Write a program that merges lines alternately from two files and writes the results to a new file. If one file has a smaller number of lines than the other, the remaining lines from the larger file should be simply copied into the target file. Chapter 19: File Input/Output 347 (d) Write a program to encrypt/decrypt a file using: (1) Offset cipher: In this cipher each character from the source file is offset with a fixed value and then written to the target file. For example, if character read from the source file is ‘A’, then write a character represented by ‘A’ + 128 to the target file. (2) Substitution cipher: In this cipher for each character read from the source file a corresponding predetermined character is written to the target file. For example, if character ‘A’ is read from the source file, then a ‘!’ would be written to the target file. Similarly, every ‘B’ would be substituted by ‘5’ and so on. (e) In the file ‘CUSTOMER.DAT’ there are 10 records with the following structure: struct customer { int accno ; char name[ 30 ] ; float balance ; }; In another file ‘TRANSACTIONS.DAT’ there are several records with the following structure: struct trans { int accno ; char trans_type ; float amount ; }; The element trans_type contains D/W indicating deposit or withdrawal of amount. Write a program to update ‘CUSTOMER.DAT’ file, i.e., if the trans_type is ‘D’ then update the balance of ‘CUSTOMER.DAT’ by adding amount to balance for the corresponding accno. Similarly, if trans_type is ‘W’ then subtract the amount from balance. However, while subtracting the amount ensure that the amount should not get overdrawn, i.e., at least 100 Rs. should remain in the account. (f) There are 10 records present in a file with the following structure: struct date { int d, m, y ; } ; struct employee { int empcode[ 6 ] ; char empname[ 20 ] ; 348 Let Us C struct date join_date ; float salary ; }; Write a program to read these records, arrange them in ascending order by join_date and write them to a target file. (g) A hospital keeps a file of blood donors in which each record has the format: Name: 20 columns Address: 40 columns Age: 2 columns Blood Type: 1 column (Type 1, 2, 3 or 4) Write a program to read the file and print a list of all blood donors whose age is below 25 and whose blood type is 2. (h) Given a list of names of students in a class, write a program to store the names in a file on disk. Make a provision to display the nth name in the list, where n is read from the keyboard. (i) Assume that a Master file contains two fields—roll number and name of the student. At the end of the year, a set of students join the class and another set leaves. A Transaction file contains the roll numbers and an appropriate code to add or delete a student. Write a program to create another file that contains the updated list of names and roll numbers. Assume that the Master file and the Transaction file are arranged in ascending order by roll numbers. The updated file should also be in ascending order by roll numbers. (j) Given a text file, write a program to create another text file deleting the words “a”, “the”, “an” and replacing each one of them with a blank space. x File I/O functions : a) High level : 1) Text mode -(i) Formatted (ii) Unformatted 2) Binary mode b) Low Level x High level text mode formatted file I/O functions: fprintf( ), fscanf( ) x High level text mode, unformatted file I/O functions : char - fgetc( ), fputc( ) Chapter 19: File Input/Output 349 int, float - no functions string - fgets( ), fputs( ) x I/O is always done using a buffer of suitable size High level file I/O functions manage the buffer themselves While using Low level file I/O functions we have to manage the buffer x Functions to open and close a file : High level - fopen( ), fclose( ) Low level - open( ), close( ) x FILE *fp = fopen ( “temp.dat”, “r” ) ; FILE is a structure declared in stdio.h fopen( ) - Creates buffer, Creates structure - Returns address of structure which is assigned to fp x ch = fgetc ( fp ) ; - Reads char, Shifts pointer to next char Returns ASCII value of character read Returns EOF if no character is left for reading x To read a file character by character till we do not reach the end : while ( ( ch = fgetc ( fp ) ) != EOF ) x To read a file line by line till we do not reach the end : char str[ 80 ] ; while ( fgets ( fp, str, 79 ) != NULL ) x EOF and NULL are macros defined in stdio.h #define EOF -1 #define NULL 0 x fopen( ) : To open file for reading in text mode - “rt” or “r” To open file for writing in text mode - “wt” or “w” To open file for reading in binary mode - “rb” To open file for writing in binary mode - “wb” x Difference : fs = fopen ( s,"r" ) ; - Returns NULL if file is absent 350 Let Us C Returns address of FILE structure, if present ft = fopen ( t, "w" ) ; - Creates new file if file is absent Overwrites file, if present fclose ( fs ) ; - Vacates the buffer fclose ( ft ) ; - Writes buffer to disk, vacates the buffer x To read / write record to a file in text mode : struct emp e = { “Ajay”, 24, 4500.50 } ; fprintf ( fp, “%s %d %f\n”, e.name, e.age, e.sal ) ; while ( fscanf ( fp, “%s %d %f\n”, e.name, &e.age, &e.sal ) != EOF ) x To read / write record to a file in binary mode : struct emp e = { “Ajay”, 24, 4500.50 } ; fwrite ( &e, sizeof ( e ), 1, fp ) ; while ( fread ( &e, sizeof ( e ), 1, fp ) !== EOF ) x To move the pointer in a file ; fseek ( fp, 512L, SEEK_SET ) ; Moves the pointer 512 bytes from the beginning of file x Other macros : SEEK_END - from end of file SEEK_CUR - from the current position of the pointer x To read / write a buffer of 512 characters using low level file I/O functions : int in, out ; char buffer[ 512 ] ; out = open ( “trial.dat”, O_WRONLY | O_BINARY | O_CREAT ) ; in = open ( “sample.dat”, O_RDONLY | O_BINARY ) ; write ( out, buffer, 512 ) ; n = read ( in, buffer, 512 ) ; x Include three files while doing low level file I/O : #include #include #include More Issues In 20 Input/Output “More the merrier...” Ever wondered how some programs are able to receive input at command-line itself? And how they are able to redirect their input and output with ease? Well, nothing great, that is, once you have gone through this chapter… 351 352 Let Us C x Using argc and argv x Detecting Errors in Reading/Writing x Standard File Pointers x I/O Redirection Redirecting the Output Redirecting the Input Both Ways at Once x Exercises x KanNotes Chapter 20: More Issues In Input/Output 353 I n Chapters 18 and 19 we saw how Console I/O and File I/O operations are done in C. There are still some more issues related with input/output that remain to be understood. These issues help in making the I/O operations more elegant. Using argc and argv While executing the file-copy program in Chapter 19, we are prompted to enter the source and target filenames. Instead of the program prompting us to enter these filenames, we should be able to supply them at command prompt, in the form: filecopy PR1.C PR2.C where, ‘filecopy’ is the executable form of our C program, ‘PR1.C’ is the source filename and ‘PR2.C’ is the target filename. The command prompt is C:\> if you are using command window, Search box if you are using Windows 10 and $ prompt if you are using Linux. This improvement is possible by passing the source filename and target filename to the function main( ). This is illustrated in the program given below. # include # include int main ( int argc, char *argv[ ] ) { FILE *fs, *ft ; char ch ; if ( argc != 3 ) { puts ( "Improper number of arguments\n" ) ; exit ( 1 ) ; } fs = fopen ( argv[ 1 ], "r" ) ; if ( fs == NULL ) { puts ( "Cannot open source file\n" ) ; exit ( 2 ) ; } ft = fopen ( argv[ 2 ], "w" ) ; if ( ft == NULL ) { 354 Let Us C puts ( "Cannot open target file\n" ) ; fclose ( fs ) ; exit ( 3 ) ; } while ( 1 ) { ch = fgetc ( fs ) ; if ( ch == EOF ) break ; else fputc ( ch, ft ) ; } fclose ( fs ) ; fclose ( ft ) ; return 0 ; } The arguments that we pass to main( ) at the command prompt are called command-line arguments. The function main( ) can have two arguments, traditionally named as argc and argv. Out of these, argv is an array of pointers to strings and argc is an int whose value is equal to the number of strings to which argv points. When the program is executed, the strings on the command-line are passed to main( ). More precisely, the strings at the command-line are stored in memory and address of the first string is stored in argv[ 0 ], address of the second string is stored in argv[ 1 ] and so on. The argument argc is set to the number of strings given at the command- line. For example, in our sample program, if at the command prompt we give, filecopy PR1.C PR2.C then, argc would contain 3 argv[ 0 ] would contain base address of the string ‘filecopy’ argv[ 1 ] would contain base address of the string ‘PR1.C’ argv[ 2 ] would contain base address of the string ‘PR2.C’ Whenever we pass arguments to main( ), it is a good habit to check whether the correct number of arguments have been passed to main( ) or not. In our program this has been done through, Chapter 20: More Issues In Input/Output 355 if ( argc != 3 ) { puts ( "Improper number of arguments\n" ) ; exit ( 1 ) ; } Rest of the program is same as the earlier file-copy program. One final comment... the while loop that we have used in our program can be written in a more compact form, as shown below. while ( ( ch = fgetc ( fs ) ) != EOF ) fputc ( ch, ft ) ; This avoids the usage of an indefinite loop and a break statement to terminate the loop. Here, first fgetc ( fs ) gets the character from the file, assigns it to the variable ch, and then ch is compared against EOF. Remember that it is necessary to put the expression ch = fgetc ( fs ) within a pair of parentheses, so that first the character read is assigned to variable ch and then it is compared with EOF. There is one more way of writing the while loop. It is shown below. while ( !feof ( fs ) ) { ch = fgetc ( fs ) ; fputc ( ch, ft ) ; } Here, feof( ) is a function that returns a 0 if end of file is not reached. Hence we use the ! operator to negate this 0 to a truth value. When the end of file is reached, feof( ) returns a non-zero value, ! makes it 0 and since now the condition evaluates to false, the while loop gets terminated. Note that the following three methods for opening a file are same, since in each one of them, essentially a base address of the string (pointer to a string) is being passed to fopen( ). fs = fopen ( "PR1.C" , "r" ) ; 356 Let Us C fs = fopen ( filename, "r" ) ; fs = fopen ( argv[ 1 ] , "r" ) ; Detecting Errors in Reading/Writing Not at all times when we perform a read or write operation on a file, are we successful in doing so. So, there must be a provision to test whether our attempt to read/write was successful or not. The standard library function ferror( ) reports any error that might have occurred during a read/write operation on a file. It returns a zero if the read/write is successful and a non