Microsoft QuickC Programming ════════════════════════════════════════════════════════════════════════════ Microsoft(R) QuickC(TM) Programming The Microsoft(R) Guide to Using the QuickC Compiler By The Waite Group Mitchell Waite, Stephen Prata, Bryan Costales, and Harry Henderson ════════════════════════════════════════════════════════════════════════════ PUBLISHED BY Microsoft Press A Division of Microsoft Corporation 16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717 Copyright (c) 1988 by The Waite Group, Inc. All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher. Library of Congress Cataloging in Publication Data Microsoft QuickC programming. Includes index. 1. C (Computer program language) 2. Microsoft QuickC (Computer program) I. Waite, Mitchell. II. Title: Microsoft Quick C programming. QA76.73.C15M53 1988 005.13'3 88-5203 ISBN 1-55615-048-2 Printed and bound in the United States of America. 1 2 3 4 5 6 7 8 9 MLML 3 2 1 0 9 8 Distributed to the book trade in the United States by Harper & Row. Distributed to the book trade in Canada by General Publishing Company, Ltd. Distributed to the book trade outside the United States and Canada by Penguin Books Ltd. Penguin Books Ltd., Harmondsworth, Middlesex, England Penguin Books Australia Ltd., Ringwood, Victoria, Australia Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand British Cataloging in Publication Data available IBM(R) is a registered trademark, and PC/AT(TM) and PC/XT(TM) are trademarks of International Business Machines Corporation. Microsoft(R) and MS-DOS(R) are registered trademarks, and QuickC(TM) is a trademark of Microsoft Corporation. ──────────────────────────────────────────────────────────────────────────── Acquisitions editor: Claudette Moore Project editor: Eric Stroo Copy editor: Gary Masters Technical reviewer: Doug Henderson ──────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────── Contents Preface Acknowledgments PART 1 INTRODUCTION TO C Chapter 1 Introduction Why Learn C? Why QuickC? Hardware Requirements Knowledge Requirements Conventions and Style Chapter 2 Starting with QuickC Our Book and Their Book Directories and Files Used by QuickC Running the QuickC SETUP Program Setting Up QuickC Starting QuickC Getting Help Fixing Errors Preparing for the Next Chapter PART 2 CORE OF C Chapter 3 C Fundamentals Basic Elements of C Programs Punctuation and Spacing in C Programs Using Comments in C Data Types and Declarations of Variables The Power of printf() Arithmetic Operators Getting Input with scanf() Shortcut Assignments, Increments, and Decrements Relational Operators Logical Operators Chapter 4 Repetition and Looping The for Loop The while Loop The do Loop Debugging and Loops Chapter 5 Decisions and Branching The if Statement The Conditional Assignment Statement ? Multipath Branching The switch Statement The break Statement The continue Statement The goto Statement More Complex Conditions for Branching Chapter 6 Functions and Function Calls Functions and Program Design Declaring and Defining a Function Local and Automatic Variables Static Variables External Variables Register Variables Passing Information to a Function Functions with Many Parameters Functions That Return Information Recursion Noninteger Functions Function Prototypes Putting It All Together: A Larger Program PART 3 ADVANCED C TOPICS Chapter 7 Arrays How Arrays Are Stored in Memory How to Declare Arrays Referencing and Using Array Items Bounds Checking Arrays in Your Code How to Initialize Arrays Arrays and Functions How Array Offsets Advance Multidimensional Arrays Advanced Topics and Tricks The Bitwise Operators, Tiny Arrays Chapter 8 Addresses and Pointers Addresses Reviewed What Is a Pointer? Accessing Variables with Pointers Passing Pointers to Functions Pointers and Arrays Pointer Arithmetic The Interchangeability of *amts and amts[] lvalue vs rvalue Type Casting Pointers and Addresses far Pointers Functions That Return Addresses Dynamic Arrays Advanced Pointer Techniques Chapter 9 Strings Declaring and Initializing Strings The String Pool and String Addresses Pointers and Initialized Strings Formatting Strings with printf() String Input and Output String Manipulation Routines Arrays and Strings The Arguments to main()──argv and argc Character Classification and Transformation Chapter 10 Managing Files Top-level I/O Mid-Level (Unbuffered) File I/O The File System Advanced Error Handling Chapter 11 Advanced Data Types Structure──An Array of Different Types Union──Multiple Types in the Same Space Enumerated Data with enum Bit Fields Advanced typedef Chapter 12 Large Projects Advanced C Preprocessor Using QuickC for Large Projects PART 4 C AND THE HARDWARE Chapter 13 Keyboard and Cursor Control Keyboard Input Functions Reading Non-ASCII Keys Console I/O Functions Keyboard Control with ANSI.SYS Using QuickC to Access the BIOS Cursor and Screen Control with BIOS Calls Chapter 14 Monitors and Text Modes Monitors and Controllers Text Modes and Portability Device-independent Programming Direct Memory Access Paging Ports The EGA and VGA Chapter 15 Graphics and QuickC The Graphics Modes CGA Graphics EGA Graphics VGA Graphics Chapter 16 Debugging Keyboard-Entry Errors Syntax Errors Run-Time Errors Common Run-Time Errors Design Errors Appendix A Some Resources for C Programmers Appendix B Built-in QuickC Functions Index ──────────────────────────────────────────────────────────────────────────── Preface The Waite Group has written books on all aspects of the C language, from primers to advanced texts that mix C with assembly language☼ But when Microsoft Press suggested we write a book on Microsoft's then unreleased QuickC compiler, we were skeptical. Having spent years trying to teach C both in the classroom and through our books, we were painfully aware of how difficult a language it is to learn. Despite its power, the Microsoft C Compiler has confounded many an eager student. Daunted by the cryptic syntax of its command-line interface, they slipped from C's learning curve and disappeared beneath its power curve. Our first look at the QuickC beta version sparked our attention──this was no standard C compiler. QuickC's interface was profoundly different: The editor completely integrated into the compiler, optional mouse compatibility, drop-down menus, full color display, the list went on and on. Finally we had point-and-click compiling. To us, all this meant a radical shift in the way we could teach C──we could take a friendly approach that would make C accessible to a much larger group of people. But was QuickC really a performance program? Were we talking fast object code or code more suited to timing traffic signals at snail races? Well, after creating hundreds of examples for this book, we can report that QuickC lives up to its speedy expectations. A product that can compile more than 10,000 lines per minute should satisfy all but the most jaded of hackers. There was more. QuickC was fully compatible with the libraries of version 5.0 of the Microsoft C Optimizing Compiler. We could develop our C code in QuickC's fast and friendly interface, get the errors out quickly with its integrated debugger and tracer, and then optimize our program for execution time by compiling it under the standard optimizing compiler. With the introduction of QuickC, we felt that C had finally reached the point of rivaling BASIC in ease of use. We saw in QuickC a product that could vitalize the learning of C and reinforce its dominance in professional program development. As educators, we saw that QuickC was an opportunity to build a complete course in the C language, one that could be pursued without an instructor and that would be attractive to beginners and professionals alike. The seductive interface made QuickC a breeze to run──we could focus on the syntax rather than on complicated command-line options and batch files. On top of this, QuickC's large and comprehensive Graphics Library meant that we could make our examples visually appealing and challenging. By assuming an IBM personal computer running MS-DOS or PC-DOS, we could tackle the sensitive issues of the interface between MS-DOS and C while at the same time flexing the keyboard, text, and bitmapped displays. After all these grandiose thoughts, the next thing we did was very natural──we gulped loudly. In the time available, no single author could possibly master a product as rich as QuickC with the aim of writing a book that examines thoroughly its vast repertoire. The solution was to pool the energies of four experienced C authors with over 25 years of combined programming and teaching experience, each writer focusing on a different section of the book. We think you will find that this book goes beyond most C books on the market (including our own). If you have any questions or comments, please address them to: The Waite Group, 3220 Sacramento Street, San Francisco, California 94115. Mitchell Waite Stephen Prata Bryan Costales Harry Henderson ──────────────────────────────────────────────────────────────────────────── Acknowledgments The authors would like to take this opportunity to thank the people at Microsoft Press for helping to make this book a success: Claudette Moore, for acquiring this title and putting up with the authors' numerous requests throughout the project; Gary Masters, for editing and blending the different styles into one clear message; Doug Henderson, for his technical review; Eric Stroo, for coordinating the progress of the final manuscript through the production process and for being so diligent about the final look of the book; Alison Conn, for her review of the book's technical coverage; and Greg Lobdell, for providing a continual flow of alpha and beta copies of the QuickC compiler. Also, thanks to Reed Koch for his many hours of explaining QuickC's internals to the authors. Dedication To Bobbie Lee, who touched me in a way no one ever has Mitchell ──────────────────────────────────────────────────────────────────────────── PART 1 INTRODUCTION TO C ──────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────── Chapter 1 Introduction Why Learn C? If you have experience with C, you are probably familiar with its advantages over alternatives such as BASIC or Pascal, and you may want to skip to the next section, which discusses the specific advantages of QuickC for C programmers. Here we compare C with two other popular languages, BASIC and Pascal. Although Pascal has its enthusiasts, and our old friend BASIC certainly has been improved in many ways (Microsoft's QuickBASIC for example), C has quickly become the premier language for professional programming both on micros, such as the IBM PC family, and on larger machines, such as those running the UNIX/XENIX operating system. Why is C so popular? Portability and Standards One reason is portability. The core of standard C is so designed that the same program runs on an IBM PC, a VAX mini, and an IBM mainframe. Portability comes about from adhering to standards that guarantee common features and functions regardless of the vendor, implementation, or hardware environment. The first, informal C standard was proclaimed by the famous "white book," Brian W. Kernighan and Dennis M. Ritchie's The C Programming Language (New Jersey: Prentice-Hall, 1978). The specifications in this book have been widely adopted in the design of C compilers, but the definitions are not comprehensive and specific enough to provide a true standard. Therefore, the American National Standards Institute (ANSI) has proposed a draft standard for the C language. (At the time of this writing, the standard has not been officially adopted, but most of its features seem stable.) Most current and future C compilers will be written to conform with the ANSI standard. QuickC is compatible with the ANSI standard. It also permits you to verify that your code uses only ANSI-compatible functions and definitions or to identify nonstandard features, such as those needed to support functions specific to MS-DOS and to IBM hardware. Another reason for the popularity of C is its close ties to the UNIX operating system. UNIX was written in C, and a variety of standards support the use of C in the UNIX environment. QuickC is functionally compatible with the UNIX System V standard library specifications. But what does all of this mean to you, the QuickC programmer? A C program written under QuickC on an IBM PC can, if it uses only ANSI-standard features, be moved to an Apple Macintosh, and you can compile it with an ANSI-standard Macintosh C compiler and run it in the new environment. This level of standardization is not common in programming languages. Pascal is only partially standardized: A Turbo Pascal program for the IBM PC, for example, cannot run under standard IBM Pascal without modification. In the IBM PC world, the ubiquitous BASICA program has offered a kind of standard, but other models of computers are provided with quite different dialects of BASIC, and you must do an extensive conversion to get a BASIC program written on one machine to run on another manufacturer's hardware. Notice that this discussion applies specifically to the "core" of C: the control structures, data structures, and basic input/output functions. Outside of this standard core, however, a number of areas of a C implementation are machine-dependent, such as the size of various kinds of numbers, keyboard codes, the video screen, graphics, and features of the operating system that handle files. To be worth its salt, a C compiler that runs on the IBM PC must include functions that give programs access to MS-DOS features, the underlying BIOS, and the hardware. Similarly, a C compiler for the Macintosh must include functions that give a program access to such elements as the machine's system toolbox. These functions are hardware-dependent and implementation-specific──by definition, they are not portable, but they are essential to getting the most out of your machine. C, as you will discover, provides a way to gather the machine-dependent parts in an organized manner, something other languages can't do. BASIC and, to a lesser extent, Pascal approach hardware dependence by customizing the language itself to include commands or functions that take care of the machine-dependent features. For example, a BASIC statement to control the speaker might be called PLAY. Another version of BASIC might call it MUSIC. The problem with this approach is apparent when you try to convert a program to run on a different machine; you cannot easily find the parts of the program that you must change to manipulate proprietary features. Also, such hardware-dependent statements may work differently on computers with different hardware configurations. A Modular Approach The programmer's task is more manageable with C. Each C compiler includes files of definitions, called include files, and collections of precompiled functions, called function libraries, which you can use to supplement the core of C to take full advantage of the features of a given machine. Your QuickC function library includes a rich collection of definitions and functions for MDA, CGA, EGA, MCGA, and VGA graphics (as well as Hercules graphics, starting with version 1.01); the whole set of MS-DOS function calls; and much more. The result is that a C programmer has several choices. If you don't need graphics or machine-specific features, you can write an ANSI-standard text-only C program and easily move it to other machines and operating systems. If you do need machine-dependent features in your program, you can use the "no-frills" version of the program and then add graphics and other hardware-dependent features in easily identified include files and libraries. For a particular hardware environment, you can then merge the appropriate include files and libraries into your program. Figure 1-1 on the following page illustrates the concept of portability. Portability requires many trade-offs. In general, the less portable (in other words, the more hardware-dependent) a program is, the faster it runs, and the more it takes advantage of graphics and other special hardware features. On the other hand, the more portable a program is, the easier it is to maintain, modify, or convert it to work with new hardware. Throughout this book, we point out portability issues and suggest ways to deal with them. For example, we note those features of QuickC that are compatible with ANSI and UNIX System V. We also look at portability versus performance in the MS-DOS world. For example, we discuss alternative ways for dealing with devices such as the keyboard and video display on MS-DOS machines (standard I/O, console I/O, and BIOS) and point out the portability trade-offs involved with each. ┌────────────────────────────┐ │ │ Not │ Customized statements │ portable │ │ │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ ├┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┤ │ │ │ MS-DOS and BIOS │ │ │ ├────────────────────────────┤ │ Hardware │ └────────────────────────────┘ (A) BASIC--A SNUG FIT BUT NOT PORTABLE ANSI/UNIX ┌────────────────────────────┐ Can run on │ Standard functions │ IBM PC, VAX, └─────────┐ ┌─────────┘ Macintosh, │ │ and others. ├────────┤ └────────┘ │ ┌─────────┐ ▼ ┌─────────┐ Implementation │ │ │ │ of C, such as │ │ │ │ Microsoft C or │ └────────┘ │ Quick C. │ Machine-specific libraries │ │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ └┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ │ ▼ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┐ Specific │ │ machine- │ MS-DOS and BIOS │ IBM PC, │ │ for example. ├────────────────────────────┤ │ Hardware │ └────────────────────────────┘ (B) C--A PORTABLE CORE Figure 1-1. Portability in C. C Is Powerful Portability is desirable, but you also want to write code that takes full advantage of the hardware. In this age of drop-down menus, windows, mice, and help screens, users expect a lot more out of software than they did only a few years ago. As a programmer you are often pushing the limits of the hardware, whether in processing speed, I/O, or graphics. When it comes to harnessing the hardware, C really shines. For example, other languages try to hide the fact that you are manipulating the contents of memory when you write code; with C pointers, you can easily manipulate memory directly. With Pascal, you can also directly manipulate memory with pointers, but the syntax is not as simple or as powerful as that of C. And in BASIC, you can use a PEEK and a POKE to access memory, but they lack the flexibility of pointers. Another important indicator of the power of a language is its ability to use machine resources efficiently. All high-level compiled languages translate program statements into machine instructions. With most languages you have little control over the efficiency of the resulting machine instructions. You are at the mercy of the assumptions the compiler or interpreter makes about your program and how it will be used. Suppose, for example, that your program uses one or two variables frequently in a loop that will be executed many times. In C, you declare register variables that are stored, if possible, in internal CPU registers; thus, delays in loading or retrieving their values in memory are avoided. The result is faster execution speed. Another important feature of C is its ability to create a variety of memory models. A memory model describes the way RAM is used during compilation and the way program code and data are shared in RAM. With most older BASICs you can use only 64 KB of memory to hold program code and data. Today, most MS-DOS machines have at least 256 KB (and often 640 KB of memory or more). Thus, newer compilers for BASIC, Pascal, and other languages often allow access to a larger amount of RAM. But C compilers go a step further: You──the programmer──decide how the computer will allocate memory. Depending on the needs of your program, you can choose to use most of the machine's memory for storing compiled instructions, you can use most of the memory to store data (such as arrays, structures, or lists), or you can allocate varying numbers of 64 KB memory segments to both. Figure 1-2 on the following page shows the concepts of register variables, pointers, and memory models. Pointers, register variables, and memory models are only some of the options C gives you for controlling the machine. In addition, most C compilers let you improve, or "optimize," the machine code generated from your program. You can optimize for program size (a smaller .EXE file) or for faster execution or for a combination of these. For example, QuickC performs some optimization for you and lets you choose other features as appropriate. In addition, you can use QuickC in combination with Microsoft C (the professional, industrial-strength C compiler) to provide optimization that is truly state of the art. Pointers ┌──────────────────┐ ┌────────────────┐ for direct │ main () │ ├────────────────┤ access to │ int * ptr; ──────┼──────┐ ├────────────────┤ memory │ ptr ++; ─────────┼───┐ │ ├────────────────┤ │ ... │ │ │ ├────────────────┤ │ │ │ │ ├────────────────┤ │ │ │ │ ├────────────────┤ └──────────────────┘ │ │ ├────────────────┤ Program │ └──────►├────────────────┤ │ ├────────────────┤ └─────────►├────────────────┤ └────────────────┘ Memory (A) POINTERS Fast ┌──────────────────┐ ┌────────────────┐ register │ main () │─────► CPU ├────────────────┤ access │ register int i; │◄───── ├────────────────┤ │ ... │ ├────────────────┤ Regular │ │◄─ ─ ─ ─ ─ ─ ├────────────────┤ memory │ int regular_varn │ ─ ─ ─ ─ ─► ├────────────────┤ access │ ... │ ├────────────────┤ └──────────────────┘ ├────────────────┤ Program ├────────────────┤ ├────────────────┤ ├────────────────┤ └────────────────┘ Memory (B) REGISTER VARIABLES ┌──────┐ │ │ ┌──────┐ │ Code │ ┌──────┐ │ Code │ │ │ │ │ ┌──────┐ ├──────┤ ├──────┤ │ Code │ │ Code │ │ │ │ │ │ │ ├──────┤ │ Data │ │ Data │ ├──────┤ │ Data │ │ │ │ │ │ Data │ └──────┘ └──────┘ └──────┘ └──────┘ (C) MEMORY MODELS Figure 1-2. C gives you control of the machine. C Is Extensible C also lets you customize the contents of include files and libraries so that they contain only the definitions and functions your program needs. These custom files can contain functions for anything from manipulating a database to formatting text. After you write and test these definitions and functions, your main program can use them as easily as it can use the standard include files and libraries provided with your compiler. On large real-world programming projects, teams of programmers can receive specifications for each set of routines needed, and each team can create resources that can be used anywhere in the project. Although most languages offer a version of this building-block methodology, the C approach is the simplest, the most flexible, and the easiest to use. The very popularity of C enhances the value of such language extensions. Hundreds of vendors have created C function libraries for almost every imaginable task. Figure 1-3 shows conceptually how you can use function libraries from both QuickC and other vendors in your programs. You can easily integrate vendor libraries into your own code, and because they are the products of professional C programmers, they are likely to be fast and efficient. You can almost always avoid the age-old problem of reinventing the wheel. ┌──────────┐ │ MS-DOS │ │ ┌──────────┐ │ │ I/O │ │ │ ┌──────────┐ └─────│ │ Graphics │ ┌─────────────┐ │ │ │ │ │ └─────│ │ │ Included ◄─┼──────────────────────│ │ │ definitions │ └──────────┘ │ ─────────── │ │ Your code │ Third- Your │ main () │ Microsoft party custom │ ... │ libraries libraries library └─────────────┘ ┌─┐┌─┐┌─┐ ┌─┐┌─┐ ┌─┐ │ Compile │ ││ ││ │ │ ││ │ │ │ ┌────────▼──────────┐ └─┘└─┘└─┘ └─┘└─┘ └─┘ │ Compiled Modules │───────────┘ │ │ │ ├───────────────────┤ │ │ │ │ Standard Library │──────────────┘ │ │ ├───────────────────┤ │ │ │ Graphics │──────────────────────────────┘ │ ├───────────────────┤ │ │ Database │────────────────────────────────────────────┘ ├───────────────────┤ │ Special functions │ └───────────────────┘ │ Link ┌────────▼──────────┐ │ Ready-to-run │ │ program │ └───────────────────┘ Figure 1-3. Using include files and libraries. C Is Structured The syntax of the C language itself supports structured programming. C provides the control structures of a modern structured language, such as if/then/else, for, while, while...do, and switch. (The last is like Pascal's case statement.) If you are experienced in Pascal or in one of the newer BASICs (such as Microsoft QuickBASIC), you will find these control structures conceptually familiar. However, you will have to learn syntax differences for C, and boxes in the text point these out. If you are used to one of the older BASICs, you will be pleasantly surprised at how these structures enable you to avoid nearly all goto statements that lead to disorganized "spaghetti code." C Is Concise Although C is a well-structured language, it encourages concise rather than verbose statements. For example, it uses braces to begin and end blocks of code, rather than Pascal's begin and end. C provides shorthand operators for assigning values to variables and for incrementing variables. To show the flavor of C, the following table presents a few comparisons of C, Pascal, and BASIC assignment statements: Some Comparisons of BASIC, Pascal, and C BASIC Pascal C ────────────────────────────────────────────────────────────────────────── 1. Set a, b, and c to 0 a = 0 a := 0; a = b = c = 0; b = 0 b := 0; c = 0 c := 0; 2. Set i to i + 1 i = i + 1 i := i + 1; i++; 3. Set a to a + 5 a = a + 5 a := a + 5; a += 5; ────────────────────────────────────────────────────────────────────────── Such conciseness speeds the typing of programs and makes C source files more compact and easier to edit. C functions are more accessible than their Pascal counterparts and much more efficient than the awkward subroutine mechanism of BASIC. With the C preprocessor, you can create your own shorthand, or macro, definitions with which you insert expressions or whole blocks of code in text by typing the name of the definition. This brief overview of the general features of C should suggest why the language is so popular. Let's now look more closely at the product with which this book is concerned, Microsoft QuickC, and see how its particular features and advantages make programming in C even more attractive. Why QuickC? Traditionally, C has had one big drawback compared with interpreted languages such as BASIC──a complex compilation and debugging process. You probably know that C is a compiled language, and MS-DOS─based compiled languages traditionally have required that you go through a lengthy series of steps to produce an executable file. The steps to compiling a traditional C program are the following: 1. Start a text editor or word processor and write a program. 2. Save the program to disk and exit the editor. 3. Run the compiler program by issuing a command line from the DOS prompt, usually with several filenames and options included, that tell it, for example, what memory model to use and whether to generate a listing file. 4. Look at the listing produced by the compiler, and study every error message. 5. Print out this error list for reference. 6. Start the editor again, open your C program file, and for each error try to find the exact line in which the error occurred and correct the program. 7. Go back to step 3 and try again until the program compiles without errors into an object code file. 8. Now run the linker, and tell it what libraries to combine with your object code file to produce an executable program (an MS-DOS .EXE file). If you used an incorrect function name or failed to specify the correct libraries, you will now get a new batch of error messages, this time from the linker. (They may, for example, report an "unresolved external," which probably means the name you used for a function in your code did not match the name of the function defined in the library.) To fix these errors, you may need to look at listings of include files. Or you may have to go back to the editor and correct your program. In any case, you must recompile and then try to link again. 9. When the code links without errors, you can finally run the program. Did it execute as you expected? No? Do you want to make some changes? Well, go back to the editor and try again. Just reading through these steps suggests how tedious a traditional compiled language can be. With interpreted languages, such as BASIC, LOGO, or HyperTalk, you can type a line or two of code, execute it immediately, and see the results. If your line of code contains errors or if you want to add or change something, the interpreter usually provides a simple text editor or line editor you can use immediately. But interpreted languages have one critical drawback──they're slow. Each line in a program in an interpreted language has to be translated into machine-executable instructions each time it is encountered. Therefore, only the simplest interpreted-language applications run fast enough for use in the real world. The philosophy behind QuickC is to provide a programming environment that is as easy to use as an interpreter, but with the execution speed obtainable only through a compiler. With QuickC, writing and testing programs is so easy that C can be a beginning programmer's first language. The QuickC Programming Environment With QuickC, you do all of your program development in and from the same place──the QuickC integrated programming environment. (Figure 1-4 shows the way your screen looks when you start QuickC.) This environment offers many advantages: 1. You can open a file for editing by using the Open command on the File menu, or you can simply start typing a new program. The QuickC full-screen editor is immediately available, with insert/delete, cut/paste, indention──all the features you need to type a program as easily as you type a letter with a word processor. And you never really "leave" this editor. You merely select whatever service you need from the menus. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 1-4 can be found on p.12 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 1-4. The initial QuickC screen. 2. To run the program you can click on the mouse or type a command. When you work with a program that has not yet been compiled, the compiler and linker are called as needed. There are no complex command-line options to type. If your program is error free, the program runs in seconds on the output screen. The main reason your programs compile, link, and run so quickly with QuickC is that, unlike traditional C compilers that compile and link to disk, QuickC by default compiles to memory. Thus, it can compile 10,000 lines a minute on a standard IBM PC/AT. Another reason is that some of the most commonly needed functions are held in memory. You also can create libraries that can be loaded into memory. The result is that QuickC uses available memory very efficiently. 3. As you view an error message, the cursor follows along through your program text; you can instantly correct each error with the built-in editor. No printed listings to pore over; no error numbers to look up! 4. Suppose your program compiles correctly but doesn't work as you expected. Without leaving QuickC, you can turn on the debugging and trace features, rerun your program, and then watch the changing values of selected variables, follow the flow of execution, and check the values being passed to and from functions called by your program. What about multiple-module programs──C programs that have several separately compiled libraries and code files? Traditionally, you had to run a special "make" program and give it a file with a unique syntax that told the compiler how to rebuild such a complex program after any change was made. With QuickC's program list feature, you simply tell QuickC what libraries and source code files you want to use. QuickC keeps track of all the other details, such as the relationship between modules and the date each module was last compiled. 5. Do you need access to MS-DOS? Need to make a new directory or back up some programs? Maybe you want to run some previously compiled C programs from MS-DOS. With a traditional, command-line-driven C compiler, you exit the compiler, work in MS-DOS, and then run the compiler again and figure out where you left off. With QuickC, you never leave the integrated programming environment. Using QuickC's DOS Shell feature, you exit to MS-DOS, take care of your business, and return to QuickC where you left off. You can select many other features from the QuickC programming environment in the same easy way. With a command-line compiler, most features require that you type obscure flags or option switches on the command line or create batch files to simplify complicated compiler commands. With QuickC, you select with a mouse click or keystroke such features as the error warning level, language extensions, and optimization. But don't let the convenience deceive you──underneath the covers, QuickC is constructing the proper list of options so that you can use the same linker. (QuickC also includes a command-line-driven compiler for those times you have a special need, such as compiling under certain memory models, or when you want to work outside the QuickC environment.) QuickC Performs QuickC is faster in almost all cases than its nearest competitors, and it beats them hands down in floating-point operation. QuickC also is fully compatible with its "big brother," the Microsoft C Optimizing Compiler, versions 5.0 and later. Any program that compiles under QuickC compiles under Microsoft C, version 5.0. Therefore, you can develop programs with QuickC and then effortlessly recompile them under Microsoft C for fine tuning, using a variety of optimization techniques. QuickC: Standard and Comprehensive Earlier we discussed ANSI and other official standards for C. There are also unofficial industry standards that are almost as important. When you use QuickC, you have the benefits of using a compiler that has become the industry standard for PCs: Microsoft C. QuickC is fully compatible with that standard. Thus, dozens of third-party C code libraries work with your programs because the programs you write are compatible with the ANSI or UNIX System V standards or with the MS-DOS─specific features of Microsoft C. The extras that come with the QuickC product are also impressive. Each standard-model library (small, medium, compact, large) supports the 8087 coprocessor. There are libraries for every kind of PC graphics from monochrome and CGA to the latest VGA graphics for the IBM PS/2. The QuickC Graphics Library routines feature easy-to-use routines for drawing points and lines and manipulating complete images, including filling and animation, all with impressive speed. QuickC also has libraries that allow your programs complete access to MS-DOS and BIOS calls. And, because of QuickC's UNIX compatibility, you can also use UNIX System V functions for writing programs that can be ported to work in the UNIX environment. Hardware Requirements To run QuickC you need an IBM PC/XT, PC/AT, PS/2, or compatible computer with at least 448 KB of RAM and at least two floppy-disk drives. We suggest, however, that you develop QuickC programs on a hard disk. Compiling or linking to disk with floppy disks is time-consuming compared with hard disks. Also, fitting all the files you need for developing programs onto two disks can be tricky. But because some of you will be using floppy-disk-based systems, we will give you some tips later that should help you make the best of the situation. (And the situation is anything but grim: You can certainly develop programs that run great under QuickC on a floppy-based system.) We also recommend (but don't presuppose) that you use QuickC with a compatible mouse. You can handle all QuickC functions from the keyboard, but why get bogged down learning the keystroke combinations? With a mouse in hand, you simply point at what you want and select it. On the other hand, many people don't have (or choose not to use) a mouse. With QuickC, you can use short keystroke combinations. For example, Alt-r-s selects the Run menu's Start option to compile and run a program. (Even if you have a mouse, typing is sometimes faster.) Graphics capability is optional for most of this book. Chapter 15, which deals with graphics, requires a CGA, of course; for advanced graphics, you need an EGA; and the VGA section requires a PS/2, or a VGA board for older PCs. (If you have the new VGA, you also have CGA and EGA capability.) Even if you have only the basic monochrome adapter, you can create many interesting QuickC programs with the built-in IBM graphics character set. Finally, we recommend that you have a printer (although a printer is not required for this book). Knowledge Requirements Some programming experience──with BASIC or Pascal, for example──will help. But thanks to the ease of use of QuickC, C can be your first programming language, although you may have to work a bit harder than more experienced programmers. Because many of you have programmed in BASIC (such as Microsoft's BASICA or QuickBASIC) or Pascal (Borland's Turbo Pascal, for example), we scatter "asides" throughout the text for BASIC and Pascal programmers. These point out the ways in which C is similar to and different from those languages. Familiarity with another language is a two-edged sword when it comes to learning C. On the one hand, you already know many programming concepts used in C. On the other hand, differences in syntax and usage can trip you up if you aren't careful. If you are a UNIX programmer, you will feel right at home──as soon as you get used to QuickC's much more comfortable living room! The QuickC environment is far easier to use than the UNIX cc compiler and ln linker, and you won't have to write any make scripts. You probably already know the fundamentals of C, but watch out for features that are different in the IBM PC/MS-DOS environment, especially graphics and MS-DOS system calls. But as we noted, with a few minor exceptions, Microsoft C supports the standard I/O and other library functions used on UNIX systems. Occasional boxes point out matters of interest to UNIX programmers. Conventions and Style We have chosen the following typographical conventions for the descriptions of a C program in the text: ■ Names of ordinary (local) variables are lowercase italic. Example: count, sum ■ Names of external or global variables are also italic, but the first letter is capitalized. Example: Model ■ Underscores join the words of multiple-word variable names. Example: Grand_total (an external variable) or line_count (an ordinary variable) ■ Constants created with #define are uppercase italic. Example: PI ■ Macro definitions are uppercase italic. Example: PRINT_ERROR(MSG) ■ Function names are lowercase italic. Underscores join multiple-word function names. Examples: main(), count_lines(), printf() You'll also notice that names of Microsoft library functions that are non-ANSI-standard (such as the graphics functions) are lowercase italic and preceded by an underscore. Example: _getvideoconfig() (a Graphics Library function) ■ In #include statements, names of header files that we create are in double quotation marks. Example: #include "chr_graphics" Names of header files provided by Microsoft are in angle brackets. Example: #include (This convention affects the way in which the compiler searches for header files on disk.) ■ Built-in "keywords," or reserved words, of the C language are lowercase italic. Examples: int, do, while ■ Program names are uppercase roman. Example: HELLO.C ■ Filenames and pathnames are uppercase roman. Examples: \LIB\GRAPHICS.LIB, SCREEN.DAT ■ Names of special keys are spelled as they appear on the standard IBM PC extended keyboard. Examples: Enter (not Return), Ctrl-C, the Esc key Program Listings Program listings are set off from the text in a monospace font. Constants, variables, and function names are capitalized as indicated in the preceding list but are not italicized. In many cases, we provide a sample session that demonstrates how a program interacts with the user. In these listings, user input is italic. guess────────────────────────────────────────────Run the guess.c program What number am I thinking of?───────────────────────────Program response 7─────────────────────────────────────────────────────────────User input Wrong! Try Again? 3 Right! You win! NOTE: The comments at the right in the sample session above are not part of actual program dialogue. Program Style Conventions A clear, consistent typographical style makes programs easier to read. No single style is universally accepted for C program listings. Ultimately, you fashion your own, based on your judgment and the prevailing usage. In some cases, more than one kind of syntax can be used. Although C itself doesn't care about spacing between the elements of a statement or an expression, we use a space between elements unless removing the space is clearer. Also, we use a 4-space indention for nested statements and the braces that enclose them. We always align braces ({ and }) vertically──a major stylistic departure from Kernighan and Ritchie. That is, we put function_name() { } rather than function_name() { } We believe this style enables you to read the listings and identify blocks of code more easily. Be warned, however, that you will find lots of C listings that contain the second style. Finally, because experienced C programmers often make a virtue of saying a lot with a little, we point out concise, idiomatic coding styles that you are likely to see in program listings from various sources, and we sometimes show two or more ways to code a statement. ──────────────────────────────────────────────────────────────────────────── Chapter 2 Starting with QuickC You are now ready to explore the QuickC environment. In this chapter we describe the environment, show how to set up QuickC on your computer system, present an overview of the QuickC menus and dialog boxes, and help you create and run your first QuickC program. We also show how to get help from QuickC and how to fix program errors. When you finish this chapter, you will be comfortable with QuickC and ready to learn the C language itself. Our Book and Their Book QuickC comes with an excellent user manual that details the mechanics of using QuickC. It explains how to configure the system, how to use the menus, the meaning of the options in each menu and dialog box, and how to use the programs that comprise the QuickC programming tools. The QuickC package also includes two reference guides, the Microsoft QuickC Language Reference and the Microsoft QuickC Run-Time Library Reference. The first guide describes the rudiments of the C language; the second provides specific information for using each of the more than 350 C library routines. Our book is designed to complement the QuickC user manual by focusing on teaching C programming with QuickC rather than rehashing operational details from the manual. However, because you need to master QuickC's features to write effective C code, we sometimes present a brief overview of a procedure or subject and then refer you to the manual for a complete discussion. We use this approach particularly when we discuss system setup and configuration, for which the manual provides extensive and detailed guidance. We also do not discuss all the editor commands and keystrokes. You can learn these from the QuickC manual and through one of QuickC's excellent help screens. However, when we know of some useful tricks that aren't covered in the manual, we pass them along immediately. Directories and Files Used by QuickC Programming in C usually involves combining several files to eventually form an executable program. These files include definitions of data structures and functions (header files), libraries of precompiled functions, and your own program code. The QuickC environment uses several directories to organize the files into distinct groups, according to purpose, such as function libraries, include files, and so on. QuickC also uses distinctive filename extensions to identify files that are used or created in the compiling and linking process. Why So Many Files? If you use languages such as BASIC or some versions of Pascal, you might wonder why QuickC needs such an elaborate system of files and directories. With most versions of BASIC, for example, you need only two files: the BASIC interpreter program that creates and runs your programs, and the file that contains your BASIC program. Although the QuickC environment can look quite complicated by comparison, QuickC sets up most of the directories and files for you (especially if you have a hard disk) and makes it easy for you to move among all the files of a programming project. Nevertheless, it is important to understand how QuickC organizes files, especially if you need to modify the default organization to avoid conflicts with existing directories or for some similar reason. To explain the "environment" you work in, we must examine QuickC's directories and the files they contain. We use the QuickC default names in our discussion, because the actual name and location of the directories depend on how you invoke the SETUP program and whether you use a floppy-disk or hard-disk system. Base Directory and Subdirectories QuickC installs directories as subdirectories of a "base" directory. If you use QuickC by itself, the base directory usually is c:\qc; if you use QuickC as part of the Microsoft C 5.0 Optimizing Compiler package, the command is usually c:\c5\qc. Thus the actual pathname for the \BIN, or base, directory probably is C:\QC\BIN or C:\C5\QC\BIN. The most important QuickC directories are \BIN, \INCLUDE, and \LIB. Let's look at these and some optional directories that you might find useful. The \BIN Directory, Compiler, and Linker Programs The \BIN directory contains the program QC.EXE, which runs QuickC, provides the integrated programming environment, and lets you write, compile, link, and execute QuickC programs. (The name "BIN," by the way, is short for "binary." The \BIN directory is usually reserved for "binary files," or files containing executable programs.) The QuickC package actually contains two compiler programs: QC.EXE, which comprises the integrated programming environment with its editor, menus, and so on; and QCL.EXE, a much shorter program, which generates a "command-line" version of the QuickC compiler. (To help you distinguish between the programs, think of QCL as "QuickC Line-oriented.") QCL is much like the traditional C compiler we described in the Introduction. Rather than using menus and dialog boxes, you can compile a program only by going to the MS-DOS prompt and typing a command line with options. Another program in the \BIN directory, called LINK.EXE, combines your compiled programs and stand-alone libraries into a single executable program. QuickC usually performs this linking as an invisible process, although you can specify linker options when necessary. When you use QCL, you control the linker directly with a series of command-line options. QC.EXE, with its integrated programming environment, is more convenient to use, and we assume in most parts of this book that you will use it to compile and run your programs. However, the command-line compiler QCL.EXE is very useful for doing what are called batch compilations, for setting specific combinations of compile options, for compiling with alternate memory models, or for using an alternative program editor with QuickC. QCL also lets you use "make" files created with the Microsoft C Optimizing Compiler, version 4.0 or 5.0. (Make files are files that keep track of the compilation of multiple program modules. QuickC offers an easy-to-use alternative called "program lists," which we discuss in Chapter 6.) The \INCLUDE Directory and Header Files The "core" of C is greatly extended by compiler vendors who develop new sets of predefined constants, macros, data structures, and functions for such areas as graphics, device I/O, and DOS. Some of these are standard (proposed ANSI standard or UNIX System V standard) and are found in virtually all compilers; others are specific to the IBM PC or to Microsoft. The QuickC \INCLUDE directory contains many text files of both types. These are known as "include files" because your program can include definitions from one or more of these files. (They are also known as "header files," because their names must be specified at the beginning, or head, of a program.) This is also where you'll put any third-party libraries you obtain. Include files are not executable files or complete C source programs; they are ordinary text files that contain useful function definitions; they provide an interface between your program and the compiled code in stand-alone libraries. When a program references an include file, the code in the include file is inserted into and compiled with the code you actually typed in. For example, the include file stdio.h contains many of the most commonly used input and output functions, and graphics.h contains definitions for data structures and functions in the Graphics Library. The following table lists the standard QuickC include files. Note that include files have filenames with the .h extension. (Don't worry about understanding this comprehensive list yet; we will discuss many of them in detail as we use them in programs throughout the book.) QuickC Include Files ╓┌─┌──────────┌──────────────────────────────────────────────────────────────╖ File Main Purpose ────────────────────────────────────────────────────────────────────────── assert.h Debugging expressions conio.h PC-specific console (keyboard) and port (device) I/O ctype.h Character testing and conversion direct.h Creating, removing, and changing MS-DOS directories dos.h Setting and reading 8086 registers for MS-DOS calls errno.h System-wide error numbers fcntl.h Opening MS-DOS files with various modes float.h Implementation-dependent values for advanced floating-point File Main Purpose ────────────────────────────────────────────────────────────────────────── float.h Implementation-dependent values for advanced floating-point operations graph.h Microsoft-specific data structures and functions for monochrome (MDA), CGA, EGA, MCGA, and VGA graphics io.h Low-level file-handling and I/O routines limits.h Implementation-dependent values for sizes and ranges for data types, etc. malloc.h Memory allocation functions math.h Definitions used by math library memory.h Memory manipulation routines (buffer setup, etc.) process.h Used with routines that allow a program to "spawn" (run) another program as a "child process" search.h Sorting and searching routines setjmp.h Used for saving and restoring the program state during a "long jump" (jump to a different memory segment) share.h Flags controlling sharing of a file among several users (i.e. on a network) signal.h Values for "signals" that can be sent to interrupt handlers, etc. File Main Purpose ────────────────────────────────────────────────────────────────────────── etc. stdarg.h Allows a function to use a variable number of arguments (ANSI style) stddef.h Miscellaneous constants, types, and variables stdio.h UNIX-compatible standard I/O, such as functions to get and put characters to the console or a file stdlib.h Definitions for miscellaneous library functions string.h Definitions for string manipulation functions time.h Data structures used for accessing system time varargs.h Allows a function to use a variable number of arguments (XENIX-style) The \SYS subdirectory of \INCLUDE contains: locking.h Flags for locking files (for networks) stat.h Defines structure used to return status of an MS-DOS file or directory timeb.h Types used by ftime() (used to get current time) types.h Types used in values returned by functions for time and file status information File Main Purpose ────────────────────────────────────────────────────────────────────────── status information utime.h Used by utime() to update access and modification times for MS-DOS files ────────────────────────────────────────────────────────────────────────── In QuickC, the \INCLUDE directory also contains a subdirectory called \SYS. This subdirectory contains "system specific" include files for IBM personal computers and compatibles. The \LIB Directory and Libraries Much of C programming involves writing code that uses standard C functions to perform such tasks as getting a character from the keyboard or sending a text string to the screen. Microsoft has already compiled these functions for you and has placed them in files called "libraries." The \LIB directory contains these library files, which have either the filename extension .LIB or .QLB. As noted earlier, when QuickC starts, it includes in memory the code for a considerable number of commonly used functions. In addition, Microsoft provides "Quick Library" versions of some libraries, and you can specify that these be loaded as well to provide fast, in-memory access. You can also create your own custom Quick Libraries. Quick Libraries all have the same extension .QLB. If you examine the PACKING.LST file on the QuickC Product disk, you will see many libraries with similar names, such as SLIBC.LIB, SLIBFP.LIB, or MLIBC.LIB. Why are there so many libraries? The architecture of the Intel 8086 and 80286 processors used by the IBM PC family requires that memory be divided into 64 KB segments. As a result, special instructions are needed to access program instructions or data that go beyond a single segment. The designers of C compilers address this problem by providing programmers with multiple memory models, each containing a different allocation of segments for code and data. (QuickC uses compact, small, medium, and large memory models. Microsoft C 5.0 adds a "huge" model.) Additional libraries handle floating-point (decimal) calculations: Some use the 8087 floating-point coprocessor chip, others use software that emulates its functions. Also included is an optional graphics library, GRAPHICS.LIB. Combined Libraries You can use libraries in two ways. When you compile, you can tell the linker to include specified libraries (a memory-model library, a floating-point library, a graphics library, and so on). Although this is most easily done using a "program list," it can involve a bit of bookkeeping. The easier way to use libraries is to use the SETUP program (discussed later in this chapter), to build one or more combined libraries. A combined library is a package that contains one library for the floating-point option, one standard library for the specified memory model, some general purpose "helper" libraries, and possibly the optional GRAPHICS.LIB. The advantage of creating a combined library is that QuickC uses it by default, so you don't have to specify library names when you compile and link. The \LIB directory contains any combined libraries you create with the setup process. Note: If you intend to write graphics programs, use SETUP to combine the Graphics Library with your standard library. That way, QuickC always includes this library in compilations. The \TMP Directory QuickC uses the \TMP directory to store temporary files created during compilation. Normally, QuickC removes these files when it finishes with them. However, if something "hangs" the system during a compile, you might want to check the \TMP library and delete any vestigial files. The \SAMPLE Directory If your computer has a hard disk, the QuickC SETUP program creates a \SAMPLE directory and stores in it several example programs. You can use these to practice loading, editing, compiling, and running QuickC programs. The \PROG or \SOURCE Directory By default, QuickC stores your programs in the current directory when you invoke the compiler. All other files created by the compiling and linking process are also stored there. You also can create directories to store the source code (the actual program text) for the C programs you write and the various files made from your program by QuickC. Although this is entirely optional, it makes for a more orderly directory and helps you organize and find your programs more easily. Whatever your current directory, compiling programs creates the following kinds of files, depending on the compiler and linker options you select: NAME.C──Source code for the C program name NAME.OBJ──Object code produced by the compiler for the C program name NAME.MAP──A "map" file showing the addresses used by the linker when it linked the program name NAME.EXE──The compiled and linked object code for the program name, which can be executed by typing name at the MS-DOS prompt NAME.MAK──A "make" file containing instructions that QuickC uses to recompile or "rebuild" your program if you change it Figure 2-1 summarizes our tour of QuickC directories and files. Without listing all the QuickC files, the chart shows a typical directory structure for QuickC on a hard disk. (The structure of directories on a floppy-disk system has several modifications that we will describe in the section "Setting Up QuickC for Floppy-Disk Systems" on page 32.) C:\ ──┬── │ │ c:\qc ┌─────────────────────┬─────────────────────┬───────────── │ │ │ c:\qc\bin c:\qc\lib c:\qc\include ┌─────────────────┐ ────┬──── ┌────────────────┐ │ │ │ │ │ c:\qc\bin\sample qc.exe mlibce.lib assert.h c:\qc\include ───────┬──────── qcl.exe slibc7.lib bios.h ────────┬──── │ qc.hlp graphics.lib conio.h │ cflow.c link.exe graphics.qlb (etc.) locking.h new-conf.sys lib.exe (etc.) stat.h new-vars.bat (etc.) Figure 2-1. Typical directory structure for QuickC. From Source to Object: An Overview Now that we've surveyed the compiler, linker, include files, and libraries, let's see how they work together when you run a program with QuickC. Let's assume your program uses two include files, stdio.h and graph.h. When you "run" or "start" the QuickC compile/link phase, the compiler starts by "reading" your source code in the editor buffer. First, it sees the instructions to add the include files. The compiler then loads the stdio.h file and compiles the code found there. (The code in an include file is not already compiled.) Next it loads and compiles graph.h. These include files contain, among other things, definitions of functions whose compiled code resides in libraries. (The standard library for each memory model contains the code corresponding to standard header files such as stdio.h; GRAPHICS.LIB contains graph.h.) As it compiles the include file, the compiler notes these references to library code and passes them to the linker. After the compiler generates the object code for the part of the program you wrote yourself, the linker "resolves" all library references: It extracts the "modules" that contain the necessary code from the appropriate libraries and combines them with the rest of the code. The result is a compiled object program. QuickC's default creates an object program that runs from within the QuickC environment. This enables you to run the program immediately after you link it and lets you quickly test programs without leaving the QuickC environment. However, you can also create a .EXE file, or executable MS-DOS file, that you can run from the MS-DOS prompt. Figure 2-2 on the next page summarizes this process graphically. Editor ┌───────────────────┐ Program │ #include stdio.h │ references │ #include graph.h │ include │ ──────────── │ files │ ──────────── │ │ ──────────── │ │ ──────────── │ └───────────────────┘ │ │ Preprocessor ┌──────────┐ ┌─────────▼─────────┐ │stdio.h │ Included ▒ │ ─────────── │ ┌───── │ ───── │ source ▒ │ ─────────── │ │ │ ───── │ code ─── ▒ │ ─────────── │◄────┘ └──────────┘ │ │◄────┐ ┌──────────┐ Your ─── ▒ │ ─────────── │ │ │graph.h │ source ▒ │ ─────────── │ └───── │ ───── │ code └───────────────────┘ │ ───── │ │ └──────────┘ │ Compiler ┌─────────▼─────────┐ Compiled ▒ │ ---------───? │ library ▒ │ ---------───? │ references │ │ │ │ Your ─── ▒ │ ------------ │ compiled ▒ │ ------------ │ code └───────────────────┘ │ │ Linker ┌─────────▼─────────┐ ┌─────────────┐ Final │ ----------- │ │ Libraries │ object │ ----------- │◄─────────│ (combined │ program │ ----------- │ │ or │ (in memory │ ----------- │ │ separate) │ or .EXE) │ ----------- │ └─────────────┘ │ ----------- │ └───────────────────┘ Figure 2-2. Compiling and linking with include files and libraries. Running the QuickC SETUP Program Microsoft distributes QuickC on five floppy disks. These disks and their hundreds of files contain the two compilers (integrated-environment and command-line), a full set of libraries for each memory model with a choice of 8087 hardware or emulation, a rich assortment of more than 30 include files, several utility programs, and many other goodies. The QuickC SETUP program lets you set up a working QuickC environment with directories containing only those files that you need and provides automatic access to directories as you specify. SETUP performs the following operations: ■ Sets up variables and commands in the MS-DOS environment that tell the operating system where to find all QuickC programs and files ■ Sets up a home directory for QuickC, creates the \BIN, \INCLUDE, \LIB, and \TMP subdirectories, and moves files from the floppy disks to these directories ■ Creates one or more combined libraries, depending on the memory model(s) and form of floating-point support you specify Note: SETUP for a floppy-disk system creates only the combined library. You must do the rest partly "by hand." See the section "Setting Up QuickC for Floppy-Disk Systems" on page 32. MS-DOS Variables and QuickC As we mentioned above, QuickC sets up and uses some MS-DOS commands and variables. MS-DOS uses variables (sometimes called MS-DOS "environmental" variables) to specify the location of system resources. When you boot an MS-DOS disk, the operating system calls on two files to configure the system: AUTOEXEC.BAT and CONFIG.SYS. Commands in these files control the environment that QuickC uses when you run it. When you run the SETUP program for a hard-disk system, QuickC sets environmental MS-DOS variables in two files: NEW-VARS.BAT and NEW-CONF.SYS. You can use these files as is or insert their contents into the AUTOEXEC.BAT and CONFIG.SYS files respectively. We recommend the latter procedure unless there are serious conflicts with your existing settings. You can use any editor (such as SideKick or EDLIN) to insert NEW-VARS.BAT in your AUTOEXEC.BAT file. If you have no AUTOEXEC.BAT, use MS-DOS to rename NEW-VARS.BAT as AUTOEXEC.BAT. The resulting file might look like this: setclock────────────────────────────────────────────────Set system clock fastopen c:────────────────────────────────────Install file access cache sk──────────────────────────────────────────────────────────Run SideKick set PATH=c:\;c:\wp;c:\c5\bin;c:\qc\bin;a:\───Combined with your old path set INCLUDE=c:\qc\include set LIB=c:\qc\lib set TMP=c:\qc\tmp After you insert the SET commands found in NEW-VARS.BAT, you will probably have two PATH= commands in your AUTOEXEC.BAT file. Combine the directories in the path provided by SETUP with your existing path, as shown in Figure 2-3. You can usually use the rest of the SET commands without modification. (By default, MS-DOS permits only 128 bytes of space for storing MS-DOS variable values.) If this amount proves insufficient, modify it as described in the sidebar on the next page. AUTOEXEC.BAT NEW-VARS.BAT ┌──────────────────┐ ┌──────────────────────┐ │ ───────── │ │set PATH=c:\qc\bin │ │ ───────── │ │set INCLUDE... │ │ set PATH=c:\ │ │set LIB... │ │ │ │set TMP... │ │ │ │ │ └──────────────────┘ └──────────────────────┘ │ │ └────────────┬─────────────┘ │ ┌─────────────▼──────────────┐ │ ─────────── │ │ ─────────── │ │ set PATH=c:\; c:qc\bin ◄───┼──── Combined paths from │ set INCLUDE=c:\qc\include │▒ AUTOEXEC.BAT and NEW-VARS │ set LIB=c:\qc\lib │▒─── As is, from │ set TMP=c:\qc\tmp │▒ NEW-VARS.BAT └────────────────────────────┘ CONFIG.SYS NEW-CONF.SYS ┌──────────────────┐ ┌──────────────────┐ │ ───────── │ │ FILES=20 ◄───────┼── This is larger, so │ ───────── │ │ BUFFERS=10 │ replace existing FILES │ ───────── │ │ │ command │ FILES=10 │ │ │ │ BUFFERS=10 │ │ │ └──────────────────┘ └──────────────────┘ │ │ └────────────┬─────────────┘ │ ┌─────────────▼──────────────┐ │ ─────────── │ │ ─────────── │ │ ─────────── │ │ ─────────── │ │ FILES=20 │ │ BUFFERS=10 │ └────────────────────────────┘ Figure 2-3. Editing AUTOEXEC.BAT and CONFIG.SYS. Here's what the NEW-VARS.BAT commands do. PATH is an MS-DOS command that specifies the directories that MS-DOS searches to execute a program. Whenever you tell MS-DOS to execute a program on your hard disk (such as the QuickC linker or library manager), it first looks in the root directory of drive C:, and then checks the specified directories in the order they are listed. The next command tells QuickC that include files are in the \INCLUDE subdirectory of the main QuickC directory. Similarly, the other variables show that libraries are found in \QC\LIB and temporary files are in \QC\TMP. Setting Up QuickC Now let's set up the QuickC working environment. The QuickC manual should be your source for detailed information about setup procedures and the various options involved, but here are "quick start" instructions that can simplify the process and probably save you time. The basic steps you should follow are: ■ Check the PACKING.LST file on the first QuickC distribution disk. Be sure you have a complete set of disks and manuals. ■ Back up the QuickC disks to floppy disks (use the MS-DOS DISKCOPY command to ensure you have an exact copy). Then use the backups during the setup process. ■ Run the SETUP program. Before you run SETUP and before you use QuickC to develop programs, be sure that you have at least 448 KB of free memory. QuickC may appear to run fine with somewhat less than 448 KB until you try to compile certain programs. To verify the amount of free memory, type the CHKDSK command at the MS-DOS prompt. To increase the amount of free memory, you might be able to change your AUTOEXEC.BAT file so that some memory-resident programs are not loaded. Then, reboot to free the memory those programs were reserving. ────────────────────────────────────────────────────────────────────────── Out of Environment Space? If your current AUTOEXEC.BAT has many SET commands or a long PATH= statement, you might get an MS-DOS "out of environment space" error when you add the QuickC variables. If this happens, expand the available environment space by putting this command in your CONFIG.SYS file: shell=c:\command.com /e:/p For MS-DOS versions 3.0 and 3.1, size is the number of 16-byte "paragraphs" you want to reserve for the MS-DOS environmental variables; for MS-DOS versions 3.2 and later it is the actual number of bytes. The default size is 10 paragraphs, or 160 bytes. To set the environment to 256 bytes, use: shell=command.com /e:16/p──────────────────────MS-DOS version 3.0 or 3.1 shell=command.com /e:256/p───────────────────MS-DOS version 3.2 or later ────────────────────────────────────────────────────────────────────────── If you normally use memory-resident programs or a RAM disk, we recommend that you reboot without installing them before running SETUP. The SETUP program will fail without at least 385 KB of available RAM. After you set up the QuickC environment, experiment with memory-resident programs or RAM disks if you wish. Setting up a hard-disk system for QuickC differs from setting up a floppy-disk system. Therefore, we have developed separate walkthroughs for hard-disk and floppy-disk users. If you have a floppy-disk system, skip the next section and read "Setting Up QuickC for Floppy-Disk Systems" on page 32. Setting Up QuickC for Hard-Disk Systems First, put Libraries Disk #1 in drive A and type the SETUP command. The following line is a typical SETUP command: C>SETUP H C:\QC M EM GR The H specifies that your system has a hard disk. C:\QC is the pathname of your QuickC "base" directory. By default, QuickC creates the following subdirectories under the base directory: C:\QC\BIN Compiler, linker, and other executable programs C:QC\BIN\SAMPLE Sample C programs C:\QC\INCLUDE Include (header) files C:\QCINCLUDE\SYS System-specific include files C:\QC\LIB Libraries C:\QC\TMP Temporary files ────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────── Are You Using both QuickC and Microsoft C 5.0? If you use both QuickC and the Microsoft C Optimizing Compiler 5.0, you can install both compilers on your hard disk without causing any conflict. Because both compilers use the same library and include files, and because both compilers use the same environment variable names to locate these files, you won't have to create separate directories for each compiler's library and include files. The only planning and organizational work you'll need to do is to organize the compiler files and the source code files. Any program that you can compile with QuickC can be recompiled without change by Microsoft C 5.0. QuickC's fast compiler can save time in program development, and then the sophisticated optimizations of Microsoft C 5.0 can speed the execution of your program. Furthermore, QuickC provides syntax checking for features unique to Microsoft C 5.0. If a program is syntactically correct but uses features of the larger compiler (the huge memory model, for instance), QuickC simply ignores those features when you run the program. ────────────────────────────────────────────────────────────────────────── Note that SETUP overwrites any file with the same name as a QuickC distribution file. If you follow our recommendation to create a new directory for QuickC, \QC, in your root directory, you eliminate this problem. The M option in the SETUP command lets you use the "medium memory model" to compile programs. Note that you can specify other or additional memory models when you run SETUP. (See the manual for details.) Although we will explain all memory models in later chapters, we use the medium model throughout this book because it is the only supported model for programs compiled in the QuickC environment. (If you use the command-line compiler, which assumes a small model, you might want to create a small model combined library that conveniently collects all of the functions you normally use with QCL. You can create new combined libraries without running SETUP again.) The EM in the SETUP command specifies that all floating-point arithmetic be performed by software "emulation" of the 8087 math coprocessor chip. If you have an 8087/80287/80387 chip in your PC, you might prefer to use the 87 option which directs floating-point calculations to the coprocessor. When QuickC builds your core library, it uses this specification to select the appropriate floating-point library. Note, however, that any .EXE file you create with the 87 option will not run on machines without a math coprocessor. If you are concerned about portability, use EM when you set up QuickC. The QuickC environment uses only the emulator; if a coprocessor is present the emulator detects that fact and uses it. Finally, the GR option specifies that QuickC's Graphics Library functions be included in your combined libraries. We recommend that you use this option so that you can run the graphics programs in this book without specifying the GRAPHICS.LIB every time you link. (Of course, if your computer has only a monochrome text display, you should not use this option. This installation will proceed, but programs that you subsequently create that use graphics will not work.) After you enter the initial SETUP command, the program asks you if you want to delete the "library subcomponents," or parts of libraries that are not needed for the configuration you chose. Unless you plan to use memory models or floating-point packages other than those specified in the SETUP command, you can save a lot of disk space by typing y at this prompt. The SETUP program then prompts you to insert the appropriate distribution disks in drive A. Without any further input, SETUP creates the QuickC directories, places header files in the \INCLUDE subdirectory, creates a "combined library" for each specified memory model using the floating-point option you selected, and places the combined libraries in the \LIB subdirectory. This library is called your "standard library" because it contains compiled versions of all the standard C routines (with specified options, such as graphics). If you have already edited your MS-DOS AUTOEXEC.BAT and CONFIG.SYS files, your QuickC environment is now set up and ready to use. Please skip the next section, which is for floppy-disk users. Setting Up QuickC for Floppy-Disk Systems Setting up QuickC for a floppy-disk system differs from hard-disk setup in two principal ways: First, the floppy-disk setup does not create the NEW-VARS.BAT and NEW-CONF.SYS files, so you have to set your own MS-DOS variables; second, because you have only 720 KB of disk space on two floppy drives, you must be more choosy about which files to install. (If you have two 1.4 MB 3.5-inch disk drives, as found in the IBM PS/2 line, you need not be so constrained.) As explained in the Microsoft QuickC Programmer's Guide, you need to format at least two floppy disks: one disk for each memory model and one "scratch" disk to hold temporary files created during the setup process. Insert your copy of the Libraries Disk #1 in drive A and a blank formatted disk in drive B. You are now ready to run SETUP. We recommend that you type the following command: setup f b: m em gr This specifies a floppy-disk setup that places the combined library on drive B. The M option lets you use the "medium memory model" to compile programs. Although we will explain all memory models in later chapters, we use the medium model throughout this book because it is the only model supported for programs compiled in the QuickC environment. The EM in the setup command specifies that all floating-point arithmetic be performed by software "emulation" of the 8087 math coprocessor chip. If you have an 8087/80287/80387 chip in your PC, you might want to use the 87 option instead. When QuickC builds your core library, it uses this specification to select the appropriate floating-point library. Note, however, that any .EXE file you create with the 87 option will not run on machines without a math coprocessor. If you are concerned about portability, use EM when you set up QuickC. Finally, the GR option specifies that QuickC's Graphics Library functions be included in your combined libraries. We recommend that you use this option so that you can run the graphics programs in this book without specifying the library GRAPHICS.LIB every time you link. (Of course, if your computer has only a monochrome text display, you should not use this option. The installation will proceed, but programs that you subsequently create that use graphics will not work.) As it builds the QuickC combined library, SETUP prompts you for the necessary disks. The setup process on floppy disks can take as long as 15 minutes, so don't be alarmed at the seemingly interminable grinding of the disk drives. To create library disks for additional memory models or other floating-point options, run the SETUP program again. Setting Up the MS-DOS Environment Because the floppy-disk setup procedure does not create the NEW-VARS.BAT and NEW-CONFIG.SYS files, you need to set the MS-DOS variables yourself. To do this, add the following two variables to your AUTOEXEC.BAT file: set include=a:\include set lib=b: (If you do not have an AUTOEXEC.BAT, create one and type in the preceding variables.) This tells QuickC to look for include files in A:\INCLUDE and for libraries on drive B. Also, edit your CONFIG.SYS so that it assigns values of at least: files=15 buffers=20 Note that you will have to reboot your system if you are planning on running QuickC right away, so the new setting will take effect. Figure 2-4 summarizes how QuickC is set up and run on floppy disks. Drive A Drive B ┌──────────────────┐ ┌──────────────────┐ │ QC.EXE │ │ MLIBCE.LIB │ ───── Libraries │ │ │ │ │ │ │ QCHELLO.C │ ▒──── Your source │ │ ◄──────► │ CIRCLE.C │ ▒ files │ │ │ │ │ │ │ QCHELLO.EXE │ ▒──── Temporary ┌──│ ◄──┐ │ ──────── │ ▒ and object │ └──────────────────┘ │ └──────────────────┘ files │ │ │ Swapped after startup │ │ ┌──────────────────┐ │ └──► ├──┘ │ \INCLUDE │───── Include files │ │ │ QC.OVL │───── Overlay file │ │ │ QC.HLP │───── Help screens │ │ │ LINK.EXE │───── Linker └──────────────────┘ Figure 2-4. Floppy-disk setup for QuickC. Differences for Floppy-Disk Users The examples in this book assume you have a hard disk with QuickC residing in a directory on drive C. Floppy-disk users can use these examples by substituting references as follows: Hard Disk Floppy Disks ────────────────────────────────────────────────────────────────────────── c:\qc\bin a: c:\qc\include a:\include c:\qc\lib b: ────────────────────────────────────────────────────────────────────────── Starting QuickC Now we're ready to start using QuickC. If you have QuickC on a hard disk and have correctly included \QC\BIN in the PATH variable in the AUTOEXEC.BAT file, run QuickC by typing qc at the C> prompt. (If you haven't changed your PATH variable to include \QC\BIN, you must change to this directory before you can run QuickC.) To use QuickC on a floppy-disk system: 1. Boot your system with an MS-DOS disk that contains the new QuickC AUTOEXEC.BAT and CONFIG.SYS files 2. Put your copy of the Product Disk in drive A 3. Start QuickC by typing qc at the A> prompt 4. When the QuickC screen appears, replace the disk in drive A with a copy of the Work Disk Drive A now contains an "overlay" file (this lets QuickC access files without further disk swapping), the Help menus, the linker, and the \INCLUDE directory. Improving the QuickC Display When you type qc on the MS-DOS command line, QuickC assumes you have a color monitor. If you have a monochrome monitor, this default setting can reduce the contrast of the characters on your screen and make them hard to read. To fix this, exit QuickC by selecting Exit from the File menu, and start QuickC in its "black-and-white" mode by typing qc /b. If you use a computer that refreshes the screen at a faster rate than standard ATs, such as some higher-performance models of COMPAQ computers, you can speed screen displays by using the command qc /g to start QuickC. If your computer has an EGA card, you can set the screen to display 43 lines, instead of the normal 25, by starting QuickC with the qc /h command. Note that unless you have a high-resolution monitor, text can be very hard to read in this mode. You can combine these modes by separating them with a space. For example, qc /b /g starts QuickC in monochrome mode and accelerates the screen refresh rate. You can also put the qc command and options in a batch file so you don't have to type them each time you start. Overview of the QuickC Screen If you've used menu-based integrated programming environments such as Turbo Pascal and Microsoft QuickBASIC before, the QuickC screen should look familiar. (See Figure 2-5.) ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-5 can be found on p.35 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-5. QuickC startup screen. Notice the following screen elements: ■ The menu bar across the top of the screen lists the following options: File, Edit, View, Search, Run, Debug, Calls, and Help. ■ The "title bar" displays the name of the program currently loaded into the editor. (Because we haven't written a program yet, it now reads untitled.c.) ■ The main area of the screen, now blank, is the workspace for your program. ■ Two "scroll bars," a vertical one on the right side of the screen and a horizontal one near the bottom of the screen, let you use an optional mouse to scroll text up and down or side to side. ■ The status line at the bottom of the screen keeps track of the name of the current program, the status of your program, and the current cursor position. Note the Context section of the line. QuickC uses this area to remind you of your current stage of program development. Because no program is loaded, it reads . Making Selections The Microsoft QuickC Programmer's Guide gives exhaustive information on how to select menu items, move among parts of a dialog box, accept or cancel selections, and so on. Following is a brief and convenient summary of this material, explaining both keyboard and mouse commands. The QuickC manual also discusses several alternative selection methods you might want to explore. To save space and time we show only one method each for mouse and keyboard. Keyboard Shortcuts ("Hot Keys") QuickC lets you select certain frequently used menu items without opening the menu first. These "shortcut" or "hot" keys are particularly handy when you use the editor. Here are some of the most useful ones: Key Function ────────────────────────────────────────────────────────────────────────── F2 Open last file used Alt-Backspace Undo last edit Shift-Del Cut marked text Ctrl-Ins Copy marked text Del Clear editor buffer F4 View output screen Ctrl-/ Search for selected text F3 Repeat last search Shift-F3 Find next error Shift-F4 Find previous error Shift-F5 Start program F5 Continue stopped program ────────────────────────────────────────────────────────────────────────── (The Microsoft QuickC Programmer's Guide contains additional combinations.) The Mouse Although you can select all QuickC functions from the keyboard, you might want to try using a mouse if you have one. With a mouse, you need only to point and click to select anything on the screen. Because you don't have to learn all the keystroke combinations for making selections or using the editor, you can concentrate on learning C right away. Further, the mouse makes it easier to select items from a dialog box. You might want to learn both the mouse and keyboard methods and see which one best suits you. Or you can mix them, using the keyboard for making menu selections and the mouse for making selections in dialog boxes, for example. You must use a Microsoft mouse or a compatible mouse (such as the IBM PS/2 mouse or the Logitech serial mouse) with QuickC. Before you can use any mouse with QuickC, however, you must install a "mouse driver," either in your CONFIG.SYS file or as a .COM file in your AUTOEXEC.BAT. (See your mouse documentation for instructions.) The driver is the software that lets QuickC recognize the mouse and respond to its movements as though they were commands. If you currently use a mouse for other programs, your system is probably set up correctly already. Writing a Program Now we're ready to write a simple C program, which we will call QCHELLO.C. First, select the File menu. If you have a mouse, move the mouse until the pointer on the screen is on the File menu, and click the left button. This reveals the menu, as shown in Figure 2-6. Now, move the pointer to the Open option, and click the left button again. To reveal the File menu using the keyboard, press the Alt key and then press the f key. Notice that each menu item has a highlighted letter (often, but not always, the first letter in the word or phrase). Type this letter to select the menu item. Select the Open option by typing o. Note the Exit option in the File menu. Choose this option when you're ready to end your QuickC session. If you select Exit after changing your current program, QuickC first asks if you want to save the changed program. When you exit QuickC, you return to the MS-DOS prompt. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-6 can be found on p.37 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-6. QuickC File menu. Selecting a File When you select the Open option on the File menu, a dialog box appears. (See Figure 2-7.) QuickC uses dialog boxes to obtain the information it needs to carry out your request. You can select a file from a dialog box in two ways. Notice the long rectangle near the top of the dialog box with a cursor blinking in it. Typing a filename in this rectangle is the most straightforward method of selecting a file. Below is a larger rectangle with some names in it. This box lists the contents of the current directory. Names in ALL CAPS are directories; names in lowercase are files. (The contents of the current directory in your system may vary from those in the example.) To make a selection from a dialog box: ■ With a mouse, move the pointer to the item you want. Click the left button to select the item. ■ With the keyboard, use the Tab or back-Tab (shifted tab) key to move from one section of the dialog box to another. Press Enter to select the item. When you select a directory, QuickC lists all files and subdirectories in that directory. Each list you display also has a .. entry. Selecting this entry moves you back to the parent directory of the directory shown. Thus you can easily browse through the file system with only a few keystrokes. With the back-Tab or your mouse, move the cursor to the File Name text box. Type qchello.c and then press the Enter key. Another small dialog box appears to inform you that this file does not exist. Accept the default of Yes to create it. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-7 can be found on p.38 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-7. File "Open" dialog box. Typing in the Program You are now ready to type in a program. QuickC's default mode is in fact "edit mode," and the large area of the screen with the cursor in it is the Edit window. As you type the listing below, use the arrow keys to move the cursor, the Backspace key to make corrections, and press Enter at the end of each line. After you enter the text shown in Listing 2-1, your screen should look like Figure 2-8. ────────────────────────────────────────────────────────────────────────── /* qchello.c -- a simple C program */ main() { printf("Hello, and welcome to QuickC!\n"); } ────────────────────────────────────────────────────────────────────────── Listing 2-1. The QCHELLO.C program. What Does It Do? Although we won't look at the structure and anatomy of C until the next chapter, this program gives you a hint of C style. The first line (enclosed by the characters /* and */) is a comment that briefly describes the program. It is optional but highly recommended. The word main() indicates the beginning of the main function or related group of statements in the program. (Most C programs have many functions in addition to the main one.) As the name suggests, printf() prints the string in the parentheses that follow. The braces, { and }, set off the group of statements (only one in this case) that make up the main function. So, it's easy to see what this program does: It prints Hello, and welcome to QuickC! on the screen. (The \n at the end of the string simply moves the cursor to the beginning of a new line.) ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-8 can be found on p.39 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-8. QCHELLO.C as typed into the edit window. Running QCHELLO.C Running the program is simple. Select the Run menu. As you probably know, before we can run our program we must first compile and link it. The Start option in the Run menu executes all of these steps for you. When you select Start, a dialog box tells you that the program has been "modified" and asks if you want to "rebuild" (compile and link) it. Whenever you change a program, you must recompile. QuickC treats this new program as a changed program, so press Enter or click on Yes to compile it. Before you can blink an eye, QuickC compiles and runs the program. QuickC is fast, as you will see when you write longer programs, and because this little program doesn't use any include files or libraries, it compiles instantaneously. After the program runs, the screen displays the following: Hello, and welcome to QuickC! Program returned (13). Press any key You are now looking at the "output screen." QuickC keeps track of the output screen, which always holds the results of your programs, so you can switch back and forth between it and the QuickC environment screen. Press any key to return to QuickC. For now, don't worry about the return value mentioned in the second output line. Saving the Program To save this program to disk for future reference, open the File menu again. Notice the Save and Save As options. Select Save to write the program to disk. If you want to save the program with a new name, select Save As. When the dialog box appears, type the new name and press Enter. (You might try QCHELLO2.C.) Compiling to a .EXE File QuickC compiles programs to memory by default. Because it is fast, this is often the best way to compile while developing a new program. However, the compiled version of a program compiled to memory disappears when you compile another program or quit QuickC. Eventually you need a compiled version of the program on disk, so you can run it without recompiling. Also, you eventually want to create programs that a user can run directly from MS-DOS without QuickC available. To produce an MS-DOS-executable file, we need to "compile to .EXE." Select the Run menu. Now select the Compile item. The dialog box shown in Figure 2-9 appears. This large dialog box lets you select many options. (We will explain the options later as we use them.) Notice the center column, Output Options. The small black dot in the parentheses next to the word Memory indicates that it is the currently selected output option. We want to change this option to Exe. If you have a mouse, move the pointer between the parentheses next to Exe and click. From the keyboard, you can move the cursor to this position with the Tab and Down Arrow keys and press Enter. But there's an even faster way. Note that the letter x in Exe is highlighted. To select this item, you need only type the letter x. Now you can compile the program. Note the four small rectangles at the bottom of the dialog box. The first one, Build Program, has a double border, which signifies that it is the default. You can select it in one of three ways: tab to it and press Enter, click on it with a mouse, or type b. The Compile box displays the numbers of the program lines being processed as the program is compiled. Because this program is being compiled as a stand-alone .EXE file, it must be linked to various disk files. Very quickly, the program returns you to the familiar QuickC environment screen. Note that the program didn't run and produce output as it did when you compiled it earlier. This compile created a .EXE file, and these executable files cannot be run directly from QuickC. However, QuickC provides an easy way to run it. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-9 can be found on p.41 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-9. Compile dialog box. Escaping to MS-DOS At the File menu, select the item DOS Shell. This option switches the display to the output screen where the MS-DOS sign-on message and prompt appear. You can now run any MS-DOS command, as well as most programs and batch files. To run QCHELLO.EXE, type: C>qchello The screen displays the expected output. (No instruction to press a key appears, of course, because QuickC is not running. We are at the MS-DOS level.) Now type: C>exit to return to QuickC exactly where you left off. Getting Help We will not cover every feature of QuickC in this book so that we can devote more time to C itself. Although we occasionally refer you to the QuickC manual, there's another source of help as near as your keyboard── the QuickC Help facility. In fact, you can select from three levels of help: general, topic, and keyword. General Help Screens Press the F1 key to select the General help option (or use the mouse to make the selection). The first screen you see is shown in Figure 2-10. Notice that it displays a summary of some editor commands as well as some other frequently used commands. The small rectangles at the bottom of the dialog box let you select the Next or Previous help screen. Next, with its double border, is the default. Press Enter or click on the box with the mouse to display the next screen. Don't try to memorize or even understand these screens. Just get an idea of the general information that is available for future reference. Topic Help If you select Topic help, you can page through lists of topics until you find the information you are looking for. (See Figure 2-11.) For example, you could select "preprocessor directives," and then select the particular directive for which you want help. To choose Topic help directly from the editor window, press Shift-F1. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-10 can be found on p.43 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-10. A QuickC help box. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-11 can be found on p.43 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-11. Topic help. Keyword Help Return to the editor window, and move the cursor to the word printf. Suppose you are writing a program and you are not sure how this C function works. By pressing Shift-F1, you can retrieve information about the C keyword or standard function currently marked by the cursor. (See Figure 2-12.) ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-12 can be found on p.44 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-12. Keyword Help screen. Fixing Errors The last section of this chapter discusses how to fix errors in a C program. The QCHELLO.C program should still be in the Edit window. Let's make some changes in the program so we can practice fixing errors. (Normally, we programmers don't have to manufacture errors; we run into enough of them on our own!) Use the arrow keys or the mouse to move the cursor to the word printf. Change it to primtf. Next, go to the end of the line and delete the semicolon. Now select Run and Start to compile and run the program. QuickC soon displays a rectangular error window at the bottom of the screen as shown in Figure 2-13. The error message tells you that a semicolon is missing before the closing }. Notice on your screen that the cursor in the edit window is on the character immediately following the error. This makes it easy to find and correct the error. (In this case, the next character is on the next line, however, so you have to move the cursor to the end of the preceding line to insert the semicolon after the ). ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 2-13 can be found on p.45 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 2-13. Error window. Now run the program again. The next error message, `primtf' : unresolved external, is less clear than the preceding one. Simply stated, it means that primtf is not one of the standard QuickC functions. When you change the m back to an n, the program again runs correctly. Preparing for the Next Chapter In the next chapter we begin our study of the elements of the C language. Although we discuss additional QuickC features as needed, we will not concentrate on using the QuickC environment. So now is a good time to get comfortable with your new QuickC environment. We recommend that you try the following: ■ Save QCHELLO.C under another name, and then use the Open option in the File menu to load it into the editor. ■ Practice compiling and running the program to memory and to a .EXE file. ■ Use the DOS Shell item of the File menu to exit to MS-DOS, run a .EXE program, and then use Exit to return to QuickC. ■ Make some errors in QCHELLO.C and try running the program. Observe the error messages, fix the errors, and run the program again. What happens if the last } is missing? What happens if you change the word "Hello" to "Hi"? ■ Read Chapter 6 in the Microsoft QuickC Programmer's Guide to learn about the advanced features of the editor. We suggest you study them when you want a break from reading this book. None of these editor features are needed for you to use this book, but they make it easier to enter and modify long programs. Remember to use the Help screen to remind you of common editing functions. ──────────────────────────────────────────────────────────────────────────── PART 2 CORE OF C ──────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────── Chapter 3 C Fundamentals Now that you feel comfortable in the QuickC environment, we can turn our attention to the fundamentals of C. First, let's look at the basic elements of a C program. Basic Elements of C Programs The simplest possible C program, which we call TINY.C, is shown in Listing 3-1 on the following page. Type this program into the QuickC editor; then run it with the Start option from the Run menu. (We recommend that you enter and run all sample programs in this book──we believe this will help you better understand and remember the concepts we discuss.) As you probably suspected, this program doesn't actually do anything when you run it. QuickC generated the message Program returned 1. Press any key, but the program produced no output at all. The main() function returns the value 1, in this sample, to the operating system. (The actual value might be different on your machine.) This value is significant only if you control it deliberately, as you might want to do when you call a C program from another program, for example. ────────────────────────────────────────────────────────────────────────── /* tiny.c -- the smallest possible C */ /* program with comments */ main() /* function name and argument list */ { /* function definition in braces */ } ────────────────────────────────────────────────────────────────────────── Listing 3-1. The TINY.C program. A Program Consists of Function Definitions As simple as it is, however, this program illustrates a basic element of C──A C program is essentially a set of function definitions. A function contains statements (instructions) that the program "calls" to perform specific tasks. A function definition must contain at least the following elements: ■ The function name ■ An "argument list" enclosed in parentheses ■ A group of statements that define the function In practice, and especially with programs written in the new ANSI C standard, function definitions can be more complicated than this. But this simplest definition is all we need until we look at functions in more detail in Chapter 6. TINY.C has only one function, main(). The argument list, which follows the function and is enclosed in parentheses, often contains "parameters," or formal descriptions of information, that the function uses when it is called (executed). Although an argument list can also be empty, as it is in main(), the parentheses are still required. Because main() contains no function definition statements, the program does nothing when you run it. The QCHELLO.C program we developed in the last chapter is an even better example of the elements of a C program. Figure 3-1 identifies the parts of QCHELLO.C. Function name Argument list │ │ └──────────┐ ┌──────────┘ main ( ) ┌─ │ { Function definition ───┤ printf("Hello, and welcome to QuickC!\n"); enclosed in braces │ } │ │ └─ └──────────────────┬─────────────────────┘ │ Statement in function definition Figure 3-1. Parts of the QCHELLO.C program. A Function Definition Consists of a Group of Statements In C, a pair of braces ({ and }) encloses a group of statements. Notice the part of the program between the braces in Figure 3-1. The statement here defines the function main(). All stand-alone C programs begin with main(). The statements within braces are sometimes called the "function body," to distinguish them from the function name and argument list, which together form the "function header." The function body can consist of any number of program statements. Note, however, that the braces are still required even if the definition contains no statements. Think of braces as symbols that delimit "paragraphs" of C code. A Statement Is like a Sentence A statement in C consists of keywords, variable and function names, and operators, and, like an English sentence, describes a complete action. Statements always end with a semicolon. Below are some example C statements and their meanings: printf("This is a statement");─────────────────Print This is a statement count = 1;───────────────────────────────────Set the variable count to 1 getche(ch);───────────────────────────Wait for user to type a character, assign it to the variable ch, and echo (display) it on the screen QCHELLO.C has only one statement, printf("Hello, and welcome to QuickC!\n"); this statement translates as "Print the string `Hello, and welcome to QuickC!' and then go to the next line." (The \n specifies a newline character that moves the cursor to the next line.) This statement completely defines the function main() and describes what happens when the program executes the function. A Statement Can Contain Expressions Can an expression, such as count + 2, be a statement? Well, it doesn't end with a semicolon. But more importantly, it is not a complete statement. The word and number merely express a quantity ("two more than the value of the variable count"): They don't do anything with the quantity. Although an expression by itself is not a statement, it can be an important element of a C statement. For example, count = count + 2; is a complete C statement that assigns the quantity of the expression to the variable count. A Statement Can Call Functions Let's look at QCHELLO.C in more detail. (See Figure 3-2 on the following page.) What exactly is the printf() function at the start of the statement that defines the main() function? If you know BASIC, you might say, "It's the command you use to print in C." This isn't really correct, however. In BASIC, PRINT is a built-in BASIC command (or keyword) that prints a string or number. In C, printf doesn't execute a built-in command; it calls a function named printf() and gives ("passes") it an argument (or parameter) that tells it what to print. Function name Argument list │ │ └──────────┐ ┌──────────┘ main ( ) ┌─ │ { Definition of main() ───┤ printf ("Hello, and welcome to QuickC!\n"); function in braces │ } │ │ │ └─ │ └───────────┬─────────────────────┘ │ │ Name of Argument list (string to print) function being called Figure 3-2. Parts of QCHELLO.C revisited. Compare the printf() statement with the line containing main(). Both consist of a name followed by parentheses: that is, a function name and an argument list──the list for main() is empty. (Note that when we show function names in text, we use a trailing set of parentheses to distinguish them from other C elements.) The main() function name with its empty argument list are followed by a pair of braces that enclose the function definition. (You'll notice in QCHELLO.C that no semicolon follows main() because the line isn't a complete statement: It's the header for the function definition that follows.) The line with printf(), however, needs no defining group of statements because we are not defining printf() here; we're merely using, or "calling," the function in a statement. To call a function, simply use its name and argument list in a statement. We refer to statements such as the printf() line as "function calls." Always remember that every function must be defined before you can call it, otherwise QuickC would not know what statements to use when it tries to compile the function name. So where is the definition of the printf() function we called in Figure 3-2? The printf() function is a "core library function." Its definition is built into QuickC so that your program always has access to it. When you link your program, QuickC inserts the appropriate machine code for printing. ────────────────────────────────────────────────────────────────────────── Quick Tip If you know Pascal, you recognize the use of the semicolon to end statements in C. However, there is one important difference between its use in C and in Pascal. In Pascal, the semicolon can be omitted if the statement is the last statement in a group (the statement before the word END). In C, every statement ends with a semicolon. Also notice that the braces in C serve the same function as the Pascal keywords BEGIN and END: They delimit a group of statements. ────────────────────────────────────────────────────────────────────────── We stress the difference between C's library functions and the built-in commands of some other languages to emphasize the all-important role that functions play in C. C makes no distinction in syntax between QuickC library functions, such as printf(); functions that you define yourself, such as main(); and C header files developed by Microsoft or other vendors. The Flow of Execution Starts with main() When you run a C program, execution always begins with the function named main(), which must be present. What QuickC executes next depends on the functions that main() calls in its definition. In QCHELLO.C, execution starts with main(). In the definition of main(), QuickC encounters the name printf() and executes that function. Punctuation and Spacing in C Programs Generally speaking, QuickC lets you break lines of code almost anywhere or insert many spaces (or none) between program elements. For example, you could rewrite the QCHELLO.C program as: main(){printf("Hello, and welcome to QuickC!\n");} or, at the other extreme, you could add line breaks to produce the NARROW.C program shown in Listing 3-2. There are, however, some exceptions to C's tolerance of white space and "free-form" syntax. You can't split a function name across two lines because the compiler reads the newline character at the end of the line as part of the function name. Also, you can't break a quoted string, such as the "Hello, and welcome to QuickC!" in our printf() statement, between two lines because the compiler won't let you use the newline character in a "string constant" (although you can specify a newline with the escape sequence \n, as we have seen). Because C is a somewhat cryptic language, you should use spacing and alignment of code to make it easier for other programmers to read and revise your programs. (Remember, after a few weeks you, too, are "another programmer" when you look at your code.) You'll also find that aligning braces vertically helps you avoid errors: The vertical alignment lets you easily match beginning and ending braces. ────────────────────────────────────────────────────────────────────────── /* narrow.c -- a choppy c program */ main ( ) { printf ("Hello, and welcome to QuickC!\n"); } ────────────────────────────────────────────────────────────────────────── Listing 3-2. The NARROW.C program. Using Comments in C Listing 3-1 on p. 50 contains several lines or parts of lines that begin with /* and end with */; for example: /* tiny.c ── the smallest possible C program */ These lines are comments, or nonexecuting remarks, that explain how a program works. We strongly encourage you to use comments in your programs; they make the program much easier for a reader to understand. Because QuickC ignores comments, they can follow a program statement on the same line or cover many separate lines. The program examples in this book have an introductory comment, and we insert other comments where appropriate. Below are several different styles you can use for comments: /* Comment line one */ /* Comment line two */ or /* Comment line one comment line two */ or /* Comment line one /* Comment line two /* Comment line three */ However, you can't insert a comment within a comment as follows: /* Comment line one /* Nested comment line two */ Comment line three */ The reason you can't "nest" comments is that once the compiler sees the beginning of a comment (the /*), it considers everything that follows (including another /*) to be part of the comment until it sees */. In the nested comment above, the compiler considers the comment ended at the */ after the word two. It then treats the word Comment on the next line as an undefined function or variable name. ────────────────────────────────────────────────────────────────────────── Quick Tip Many versions of Pascal use both /*...*/ and {...} to enclose comments. In C, you can never use braces for comments: They serve only to begin and end groups of statements. ────────────────────────────────────────────────────────────────────────── Data Types and Declarations of Variables Variables are names for memory storage areas used by a program. Variables come in many shapes and sizes. Many BASIC programmers get along reasonably well using only two types of variables: numeric (representing a number) and string (representing a series of characters). A BASIC programmer might write: ITEM$="WIDGET" SERIAL=32767 to define two variables. The $ at the end of ITEM signifies a string variable; its absence in SERIAL specifies a numeric variable. A BASIC interpreter sets up these variables "on the fly" as it analyzes the lines of code, without storing them in a particularly efficient way. With C, the situation is more complicated. In order to use computer memory more efficiently, the C compiler reserves a specific location in memory for each variable. To do this efficiently, it needs to know exactly how many bytes of storage to use and how to store the data in those bytes. Therefore, C uses many "data types" to specify such things as the range of numbers that a variable can hold, whether negative values should be accommodated, whether values can be integers only or include decimal fractions, and so on. If you are a BASIC programmer, this constant attention to data types takes a little getting used to. However, by the end of this chapter, you will know all the available types and when each should be used. Let's begin our survey of data types by considering some different types of data we might store in variables: ■ 30 (the number of students in a class) ■ 557,617,814 (number of seconds since a date in 1970) ■ 22.95 (price of a computer book?) ■ 1,000,000,000,000.00 (future U.S. budget?) ■ a (the letter a) As you probably know, data is stored in a computer as patterns of bits: 1s and 0s, "ons" and "offs." In the IBM PC family of computers, bits are organized in groups of eight (called bytes), or in groups of two bytes (called words), or in groups of four bytes (double words), depending on the operation involved and the processor used. Figure 3-3 on the following page shows how many bytes are needed to store the different sizes and kinds of numbers in the above list. The figure also shows the name of the data type that describes the storage involved. The addresses shown are arbitrary, but they demonstrate how successive items are stored with lower addresses. The QuickC sizeof operator returns the number of bytes that a given data type uses. The program VARSIZE.C (see Listing 3-3 on the following page) uses this operator and a series of printf() statements to print out the sizes (in bytes) of the following data types: char, int, long, float, and double. ADDRESSES DATA TYPE Stored ┌──────────────┐ "downward" │ │ 5003 in memory ├──────────────┤ char │ 1 ─── ▒│ │ 5002 ▒ ──── a │ byte └──────────────┘ │ ┌──────────────┐ │ ▒│ │ 5001 ▒ │ 2 ─── ▒├──────────────┤ ▒ ──── 30 int │ bytes ▒│ │ 5000 ▒ ▼ └──────────────┘ ┌──────────────┐ ▒│ │ 4999 ▒ ▒├──────────────┤ ▒ ▒│ │ 4998 ▒ 4 ─── ▒├──────────────┤ ▒ ──── 557,617,814 long bytes ▒│ │ 4997 ▒ ▒├──────────────┤ ▒ ▒│ │ 4996 ▒ └──────────────┘ ┌──────────────┐ ▒│ │ 4995 ▒ ▒├──────────────┤ ▒ ▒│ │ 4994 ▒ 4 ─── ▒├──────────────┤ ▒ ──── 22.95 float bytes ▒│ │ 4993 ▒ ▒├──────────────┤ ▒ ▒│ │ 4992 ▒ └──────────────┘ ┌──────────────┐ ▒│ │ 4991 ▒ ▒├──────────────┤ ▒ ▒│ │ 4990 ▒ ▒├──────────────┤ ▒ ▒│ │ 4989 ▒ ▒├──────────────┤ ▒ ▒│ │ 4988 ▒ 8 ─── ▒├──────────────┤ ▒ ──── 1,000,000,000,000.00 double bytes ▒│ │ 4987 ▒ ▒├──────────────┤ ▒ ▒│ │ 4986 ▒ ▒├──────────────┤ ▒ ▒│ │ 4985 ▒ ▒├──────────────┤ ▒ ▒│ │ 4984 ▒ └──────────────┘ Figure 3-3. Storing information in memory. ────────────────────────────────────────────────────────────────────────── /* varsize.c -- shows amount of memory */ /* by various types */ main() { printf("Size of a char in bytes is %d\n", sizeof(char)); printf("Size of an int in bytes is %d\n", sizeof(int)); printf("Size of a long in bytes is %d\n", sizeof(long)); printf("Size of a float in bytes is %d\n", sizeof(float)); printf("Size of a double in bytes is %d\n", sizeof(double)); } ────────────────────────────────────────────────────────────────────────── Listing 3-3. The VARSIZE.C program. Here's the output of VARSIZE.C: Size of a char in bytes is 1 Size of an int in bytes is 2 Size of a long in bytes is 4 Size of a float in bytes is 4 Size of a double in bytes is 8 Declaring Variables To declare a variable, specify the data type and then the variable name. Here are some examples: int account_no; float balance; double budget; char acct_type; The first statement declares account_no as an integer (int) variable. The remaining statements declare variables as floating-point decimal (using the keyword float), "jumbo" 8-byte floating-point (double), and 1-byte character (char) data types. When you declare a variable, QuickC sets aside the appropriate number of bytes and notes the variable's starting address. The next program, VARADDRS.C (Listing 3-4), declares several types of variables and then prints out their starting addresses. ────────────────────────────────────────────────────────────────────────── /* varaddrs.c -- uses & operator to get */ /* addresses of variables */ main() { char c1, c2; int i; long l; float f; double d; printf("Address of c1 %d\n", &c1); printf("Address of c2 %d\n", &c2); printf("Address of i %d\n", &i); printf("Address of l %d\n", &l); printf("Address of f %d\n", &f); printf("Address of d %d\n", &d); } ────────────────────────────────────────────────────────────────────────── Listing 3-4. The VARADDRS.C program. Although the output of this program varies with different system configurations, it should look something like this: Address of c1 6146 Address of c2 6144 Address of i 6142 Address of l 6138 Address of f 6134 Address of d 6126 VARADDRS.C obtains the addresses of the variables by using an ampersand (&) prefix with each variable name. The ampersand is the "address operator," and it returns the starting address for each variable specified. Compare the output of VARADDRS.C with Figure 3-3 to see how variables declared with different data types require different amounts of memory. When QuickC allocates the required number of bytes for a declared data type, the last byte allocated (moving downward in memory) is the variable's starting address. For example, the integer variable i has an address of 6142, indicating that it uses two bytes (6144 - 6142 = 2); the double type variable d uses eight bytes (6134 - 6126 = 8). Note that the compiler allocates two bytes for char values c1 and c2, although each value requires only one byte. The extra byte is convenient for manipulating (2-byte) words in memory. Rules for Naming Variables In C, the names of variables and functions are called "identifiers." An identifier can contain any uppercase or lowercase alphabetic characters (A-Z or a-z), digits (0-9), and the underscore character (_). However, the name must begin with a letter or underscore. Below are some examples of legal and illegal names: bignum─────────────────────────────────────────────────────────────Legal BigNum───────────────────────────────────Legal, and distinct from bignum _video───────────────────────────────Legal, can begin with an underscore bal_due─────────────────────────Legal, underscore used to separate words player2───────────────────────────────────Legal, number in variable name 8ball─────────────────────────────────Illegal, can't begin with a number tally-ho!─────────────────Illegal, contains hyphen and exclamation point int───────────────────Illegal, keyword reserved for name of integer type As you can see, you have considerable flexibility in choosing names for your variables. Because QuickC can distinguish the first 31 characters of a variable name, you can use long, descriptive names that help make the program easier to understand and modify. (You might want to use shorter names if your program must run a compiler that does not support long names.) C distinguishes between uppercase and lowercase characters, so that BigNum and bignum are different variables. Note that you can't begin a variable name with a number, use punctuation marks such as ! or $, or use C-language keywords as variable names. (You can embed a keyword in a variable name, however: interest is a legal name even though it contains the keyword int.) Fortunately, C has few keywords compared to languages such as BASIC: Most specify data types (such as int) or control and decision-branching operations (such as while and if). We use specific conventions for naming variables and functions. (See "Conventions and Style" in Chapter 1.) These are not required by QuickC, but are used here to differentiate among types of variables and functions. We also begin our variable names with a character other than an underscore──Microsoft uses the underscore as the initial character for its QuickC library functions. Assignment Statements How do you assign values to variables? In C, the simplest assignment statement consists of a variable name followed by an equal sign (=) and the value to be assigned. Below are some examples: a = 5; b = a + 5; c = a + b; In these assignment statements, the value to the right of the equal sign is assigned to the variable on the left. The value can be a number or an expression involving variables and/or numbers, such as a + 5 or a + b. If the value is an expression, QuickC determines the result and then assigns it to the variable. You can also assign the same value to several variables at once. Usually, you do this to initialize variables by setting them all to 0 or 1: line_count = word_count = 0; line_no = page_no = 1; Initializing Variables Many languages (including most versions of BASIC) automatically initialize numeric variables to 0 and character variables to blank or, perhaps, "null." C does not. For example, if your program has the following two lines: int length; printf("The length is %d\n", length); and you do not initialize length, it might produce the following output: The length is -25480 The default value of a C variable is whatever pattern of bits happens to be in the memory locations of the variable when the compiler assigns them. Therefore, if you want to use a variable called total, for example, in a program that keeps track of some quantity, you should assign that variable an initial value of 0. You might modify the declaration above as follows: int length = 0; Because C is a concise language, it lets you combine the declaration and assignment of a variable. That is, you can declare the data type, the variables, and their values in the same statement: int a = 10, b = 50, c = 100; Type int Now that you know how to declare numeric variables and assign values to them, let's look at the int, or integer, data type more closely. An integer is a whole number, such as 30, -5, or 93,000,000. In QuickC, an int variable can hold numbers in the range of -32,768 through 32,767. This rather odd-looking range is established because the int type uses two bytes (16 bits) of memory. Two bytes can actually hold a range of 0 through 65,535. But, in the regular int type, the high (leftmost) bit of the 2-byte combination stores the integer's sign (positive or negative), leaving only 15 bits for the number. If your variable will never store negative integers, use the unsigned int type. Because the sign bit is not used, you can use the full two bytes to store values from 0 through 65,535. Now let's look at the INTVARS.C program (Listing 3-5), which declares three integer variables, assigns values to them, and then prints out values that describe the World War II German battleship Bismarck. We declare the variables length and beam as int types because the length and beam (width) of the ship are less than 32,767 feet. For the displacement variable (the "weight" of the ship), we use the unsigned int type because we need a larger number (41,676) than 32,767 (the int limit) but a smaller number than 65,535 (the unsigned int type limit). The next three lines assign the values to the variables, and the three printf() statements print the values out. Notice that the printf() statements use two arguments within the parentheses: a string, such as "The battleship Bismarck was %d feet long", followed by a comma and the variable name whose value is to be printed. The %d in the string is a printf() "format specifier," and the value of the variable is printed in its place. (The %d specifier denotes a decimal [base 10] integer. C uses a variety of specifiers for different types and formats of numbers and characters. We'll discuss them when we look at printf().) When you run INTVARS.C, it generates the output that appears below the listing (on the following page). ────────────────────────────────────────────────────────────────────────── Quick Tip ANSI C lets you specify any basic variable type as unsigned. It also lets you specify signed types. Therefore, although QuickC considers the int type to be signed by default, the C language doesn't guarantee that all C compilers do so. To write portable programs, you need to specify all variables as either signed or unsigned types. ────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────── /* intvars.c -- declares, defines, and prints */ /* some integer variables */ main() { /* declare variables */ int length, beam; unsigned int displacement; /* assign values to variables */ length = 824; beam = 118; displacement = 41676; /* print out values */ printf("The battleship Bismarck was %d feet long", length); printf(" with a beam of %d feet,\n", beam); printf("and displaced %u tons.\n", displacement); } ────────────────────────────────────────────────────────────────────────── Listing 3-5. The INTVARS.C program. The battleship Bismarck was 824 feet long with a beam of 118 feet, and displaced 41676 tons. Long Integer Type We've seen that unsigned int variables can hold values to 65,535. But what if you must use larger numbers? Type long uses four bytes (32 bits) of memory (1 bit is reserved for the sign), and can store numbers from -2^31 to +2^31, or -2,147,483,648 to 2,147,483,647 in base 10. Once again, if your variable will contain only positive numbers, you can double the high end of this range by specifying unsigned long. This lets you assign your variable a whole number value in the range 0 through 4,294,967,295. The SCORE.C program (Listing 3-6 on the following page) combines the declaration and assignment of the int variables home, visitors, inning, and attendance. Because total_attendance is a different data type, long, you must declare it in a separate statement. Again, the printf() statements display the values assigned to the variables and produce the following output: The score after 7 innings is Home team 5, Visitors 2. The attendance today is 31300. Attendance this year to date is 1135477. ────────────────────────────────────────────────────────────────────────── /* score.c -- defines and prints */ /* int and long vars */ main() { /* declare some int variables and assign values */ /* to them in the same statement */ int home = 5, visitors = 2, inning = 7, attendance = 31300; long total_attendance = 1135477; /* long int */ /* print out the values */ printf("The score after %d innings is \n", inning); printf("Home team %d, Visitors %d.\n\n", home, visitors); printf("The attendance today is %d.\n", attendance); printf("Attendance this year to date is %ld.", total_attendance); } ────────────────────────────────────────────────────────────────────────── Listing 3-6. The SCORE.C program. Floating-Point Types You should store whole numbers as integers wherever possible──integers use the least amount of memory and integer arithmetic is fast. However, many numbers (such as dollars-and-cents amounts) require decimal fractions. In computers, these types of numbers are stored in "floating-point format." Consider the number 22.95. This number can be stored by dividing it into two parts: the digits themselves and an exponent showing the magnitude of the number in terms of powers of ten. Thus, 22.95 could be represented as 22.95 * 10^0. For uniformity in performing operations, however, C always expresses the digits with only one digit to the left of the decimal point. Therefore, the above number is actually stored as 2.295 * 10^1 (the same as 22.95 * 10^0). C represents this notation with the expression 2.295e+001. The first element, 2.295, is the number's digits (the "mantissa"), and the e+001 represents "exponent 1," or 10^1. Type float The most commonly used floating-point type in C is float. In QuickC, type float uses three bytes to store digits (the mantissa) and one byte to store the exponent. Because exponents can be negative (for example, 1.4e-002 = .014), one bit of the exponent byte stores the sign. Converted into decimal terms, this means you can store a mantissa with seven significant digits and an exponent ranging from -38 to +38. In fact, with QuickC's float type, you can store numbers as large as 3.4e+038, or 34 with 37 zeros after it. The FLOATS.C program (Listing 3-7) displays three float values, each printed in both traditional decimal and exponential notation. ────────────────────────────────────────────────────────────────────────── /* floats.c -- shows floating values in regular */ /* and exponential format */ main() { float f1 = 2500.125, f2 = 0.0033, f3 = -50.99; printf("%f\t %e\n\n", f1, f1); printf("%f\t %e\n\n", f2, f2); printf("%f\t %e\n", f3, f3); } ────────────────────────────────────────────────────────────────────────── Listing 3-7. The FLOATS.C program. The following is the output of the FLOATS.C program: 2500.125000 2.500125e+003 0.003300 3.300000e-003 -50.990002 -5.099000e+001 Notice that because 0.0033 is less than 1, it has a negative exponent (represented by the minus sign after the e). -50.99, on the other hand, is a negative number, but because its absolute (unsigned) magnitude is greater than 1, it has a positive exponent. FLOATS.C prints each variable first in decimal notation and then in exponential notation by varying the format specifier in the printf() statement: The %f produces traditional decimal format, and the %e produces exponential format. Type double For numbers larger than 340,000,000,000,000,000,000,000,000,000,000,000,000 QuickC provides a "jumbo" floating-point type called double (double float). It uses eight bytes of storage and has a range of (plus or minus) 1.7e-308 to 1.7e+308. That's 308 decimal places before or after the decimal point, thus accommodating even the most expansive physicist or astronomer. ────────────────────────────────────────────────────────────────────────── Type Variations on Different Machines The C language doesn't define the number of bytes used by the int and unsigned int types. Instead, the number of bytes is based on the size of number a particular processor can handle in a single operation. This way, C compilers can always take advantage of a machine's architecture. Because the IBM PC uses the Intel 8086, 8088, or 80286 processor, an int uses two bytes, or 16 bits, and this is the implementation QuickC uses. However, on larger personal computers, such as those using the Intel 80386 processor, and on many minicomputers and mainframes, an int uses four bytes, or 32 bits. Even if you write your program in "standard" C, you must be aware of these differences in implementation and machine architecture when you "port" the program to another machine. ────────────────────────────────────────────────────────────────────────── Precision for Floating-Point Numbers You must consider more than size, however, when storing numbers in a computer. We referred to a trillion-dollar budget ($1,000,000,000,000.00) earlier in the chapter. If size were the only consideration, we could use float to store this number. (A float can handle about 10^38, and a trillion is merely 10^12.) However, you also must consider the precision available to each data type in order to choose the right type for a given variable. Precision refers to the number of digits guaranteed to be exactly correct after a calculation. The float type has a precision of seven digits. Consider the following statements and the resulting output: float trillion = 1000000000000.00; printf("%f\n", trillion); 999999995904.000000 We lost $4,096.00 in this operation. Although we might be happy if the government lost only that much of a trillion-dollar budget, we must expect full precision in financial calculations and probably an even higher precision in most scientific calculations. With its seven-digit precision, float can't accurately represent a trillion dollars. We attain the required precision by declaring: double trillion = 1000000000000.00; Because double has 15-digit precision, the result is completely accurate. Type char Let's look at one last data type, char (character). Characters include the uppercase and lowercase letters, numerals, punctuation marks, and nonprinting control characters. Characters on most computers, including the IBM PC, are represented by numbers between 0 and 127, according to the ASCII code. The CHARS.C program (Listing 3-8) shows some examples. Running CHARS.C produces the following output: The character A has ASCII code 65 If you add ten, you get K The character a has ASCII code 97 The first line of the main() function declares two char type variables, ch1 and ch2, and assigns them the values of `A' and `a' respectively. The `A' and `a' are called "character constants" or "character literals," and you assign them to char variables the same way you assign numeric constants. (Note that you must use single quotes around the character constant.) Consider the first printf() statement in the program: printf("The character %c has ASCII code %d\n", ch1, ch1); The variable ch1 is specified twice at the end of the argument list. The first format specifier, %c, prints the value of ch1 as a character. Then the %d specifier prints ch1 as an integer. A character is actually stored as a 1-byte version of int, and unless you specify that QuickC treat it as a character, it is treated as an integer. This enables us to use the expression ch1 + 10 in the second printf() statement. The variable ch1 contains an integer value (the ASCII code for `A', or 65), so adding 10 to it produces 75. When the %c specifier then prints this value, it displays the character with the ASCII value of 75, or `K'. ────────────────────────────────────────────────────────────────────────── /* chars.c -- shows some variables of type char */ /* as both characters and integers */ main() { char ch1 = 'A', ch2 = 'a'; printf("The character %c has ASCII code %d\n", ch1, ch1); printf("If you add ten, you get %c\n", ch1 + 10); printf("The character %c has ASCII code %d\n", ch2, ch2); } ────────────────────────────────────────────────────────────────────────── Listing 3-8. The CHARS.C program. Type unsigned char A char value is a signed, 1-byte value that stores values in the range of -128 to +127. However, the IBM PC's version of ASCII uses the values 0 to 255 as character codes. The first half of extended ASCII contains the regular ASCII character set. Codes from 128 to 255, however, consist of special characters and graphics shapes, which together are called the "extended character set." You can use the extended character set by declaring variables as the unsigned char type. For example: unsigned char box = 178; printf("%c\n", box); displays a rectangular box, or extended ASCII character number 178. (Note that two QuickC general help screens show the complete extended ASCII character set.) Using typedef C lets you rename any data type with the typedef statement. For example, if you use unsigned char type variables to hold characters from the full 256-character extended set, you could define an easily remembered mnemonic: typedef xchar unsigned char; xchar highlight_char, border_char; The typedef statement tells QuickC that the word xchar now represents unsigned char. Next, we declare two variables as type xchar. Note that you can still declare variables as unsigned char at any time. Also note that typedef does not create new data types, it merely provides synonyms for existing ones. The HARDWARE.C program (Listing 3-9) ends our survey of QuickC data types. ────────────────────────────────────────────────────────────────────────── /* hardware.c -- shows a mixture of int, */ /* float, and char types */ main() { int threads = 8; /* threads per inch */ float length = 1.25, /* length in inches */ diameter = 0.425, /* diameter in inches */ price = 0.89; /* price per hundred */ char bin = 'A'; /* kept in bin A */ long quantity = 42300; /* number in bin */ printf("Screws: %d threads/inch\n %f inches long\n", threads, length); printf ("%f diameter\n\n", diameter); printf("Price per 100: %f\n", price); printf("Stored in bin: %c\n Quantity on hand: %ld", bin, quantity); } ────────────────────────────────────────────────────────────────────────── Listing 3-9. The HARDWARE.C program. Be sure you understand why we declared the different types. The printf() statements display the values of the variables and some descriptive text: Screws: eight threads/inch 1.250000 inches long 0.425000 diameter Price per 100: 0.890000 Stored in bin: A Quantity on hand: 42300 Although the program works correctly, it would look better if the output were formatted more neatly. Also, QuickC printed several extra decimal places and filled them with zeros. To gain more control over the appearance of program output, we need to study printf() in more detail. Summary of Data Types You don't need to memorize the precise numbers associated with each data type; one of QuickC's help screens lets you check which data type you should use in a given situation. Display this summary of QuickC data types by pressing the F1 key and then proceeding to the appropriate screen. As you work with various data types in this chapter, you can always consult this chart, shown in Figure 3-4, to refresh your memory. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 3-4 can be found on p.67 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 3-4. Data types help screen. The Power of printf() Thus far we've used printf() statements merely to display values. But printf() is actually quite versatile for formatting numbers and character strings. Using Escape Sequences Let's look at the parts of the printf() statement from our first program, QCHELLO.C: printf ("Hello, and welcome to QuickC!\n"); This is the simplest printf() statement: It merely prints out a string; no variables are involved. Earlier, we briefly discussed the one unusual feature of this printf() statement, the \n at the end of the string. This combination of backslash and following character is called an "escape sequence." Escape sequences tell printf() to print special characters as part of the given string. The \n, for example, adds the newline character, which moves the cursor or printer head to the beginning of the next line. Many languages use two kinds of statements for printing: one to print some information, and one to print some information and then start a new line. With typical conciseness and versatility, C lets you use one function to print any ASCII character, including newline, Tab, and carriage-return characters, giving you complete control of the position of the cursor or printer head. One QuickC help screen, shown in Figure 3-5 on the following page, lists all of the escape sequences. The newline \n and tab \t sequences are the most frequently used. The \a escape sequence causes an "alert," or beep, at the terminal. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 3-5 can be found on p.68 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 3-5. Character escape sequences. The ONELINE.C program (Listing 3-10) shows what happens when you don't use the newline escape sequence. When you run the program, the output is all on one line as follows: All displayed onthe same line, with no space unless specified. Not only do the strings from all three printf() statements end up on the same line, the word "on" at the end of the first string runs into the word "the" at the start of the second string. To print two strings on the same line with a space between them, you must include the space in the string. In the third string of ONELINE.C, we added a space before the word "unless." ────────────────────────────────────────────────────────────────────────── /* oneline.c -- shows how printf() continues */ /* on the same line */ main () { printf("All displayed on"); printf("the same line, with no space"); printf(" unless specified."); /* note added space in line above */ } ────────────────────────────────────────────────────────────────────────── Listing 3-10. The ONELINE.C program. The program STRINGS.C (Listing 3-11) demonstrates the two basic ways to print strings with printf(). The first printf() statement has only one argument, the string to be printed, and the newline escape sequence. The second statement has two arguments, the format specifier %s (for "string") and the string to be printed. It replaces the specifier with the string and prints it. This is the same procedure we used to print numeric variables and literals with specifiers such as %d. The STRINGS.C program produces the following output: This uses a string literal by itself This plugs the literal into %s TABS.C (Listing 3-12) illustrates the use of the tab escape sequence \t. ────────────────────────────────────────────────────────────────────────── /* strings.c -- shows two ways to print */ /* a string with printf() */ main() { printf("This uses a string literal by itself\n"); printf("%s", "This plugs the literal into %s\n"); } ────────────────────────────────────────────────────────────────────────── Listing 3-11. The STRINGS.C program. ────────────────────────────────────────────────────────────────────────── /* tabs.c -- shows formatting with the \t */ /* tab escape sequence */ main() { int q1 = 338, q2 = 57, q3 = 1048, q4 = 778, /* quantity in bin */ t1 = 6, t2 = 8, t3 = 12, t4 = 16; /* threads per inch */ float s1 = 0.250, s2 = 0.500, s3 = 0.750, s4 = 1.0; /* size in inches */ /* print table header */ printf("number\t\t size\t\t threads\n"); printf("in bin\t\t (inches)\t per inch\n\n"); /* print lines of table */ printf("%d\t\t %f\t %d\n", q1, s1, t1); printf("%d\t\t %f\t %d\n", q2, s2, t2); printf("%d\t\t %f\t %d\n", q3, s3, t3); printf("%d\t\t %f\t %d\n", q4, s4, t4); } ────────────────────────────────────────────────────────────────────────── Listing 3-12. The TABS.C program. This program prints four sets of data in a neat table. The program prints the table headers first, using \t to tab to the next field. Using \t to position each item at the next tab stop causes the output to be left- justified in each field. To make the table easier to read, we added a blank line between the header and the data by including an extra \n in the second printf() statement. The program then prints the values of the variables in the same tab fields as the headers. The result of all this is as follows: number size threads in bin (inches) per inch 338 0.250000 6 57 0.500000 8 1048 0.750000 12 778 1.000000 16 Formatting Numbers with printf() The printf() function can also print numbers in a variety of formats. Let's look at a printf() statement from SCORE.C, which is analyzed in Figure 3-6. String to print is enclosed in quotes ┌─────────────────────────────┐ │ │ │ │ ┌─▼──┐ ┌────┐ ┌───▼────┐ printf ("The score after │ %d │ innings is │ \n │ ", │ inning │ ); └────┘ └────┘ └────────┘ │ │ │ │ │ │ Format specifier Newline Variable whose for an int value escape value is to be sequence printed Figure 3-6. The printf() statement from SCORE.C. ────────────────────────────────────────────────────────────────────────── Return and Newline Are Different If you program in other languages on MS-DOS machines, you might expect \r (carriage return) to move the cursor to the start of a new line. Change the \n in TABS.C to \r and run the program again. What happens? Each line prints over the preceding one. Although many languages on MS-DOS machines incorporate a line feed in a carriage return, C treats newline and return as distinct operations. Return moves the cursor to the beginning of the current line but does not advance it to a new line. Newline causes output to start on the next line. It commences with output at the beginning of the next line (rather than directly below the old position) because MS-DOS interprets it as though it contains a carriage return as well. ────────────────────────────────────────────────────────────────────────── Notice the %d in our example, SCORE.C. This, as we have already mentioned, is the format specifier for a decimal integer. The string "The score after %d innings is" is followed by a comma and the variable inning. Thus, when the printf() statement executes, the string is printed with the value of inning. You can also print more than one value in the same string. For example, if you define int apples = 12, oranges = 9, pears = 3;, then execute the following printf() statement: printf("I have %d apples, %d oranges, and %d pears. \n", apples, oranges, you see the following output: I have 12 apples, 9 oranges, and 3 pears. Specifying Formats with printf() Variables are printed according to their type and the format specifiers used. One of the QuickC General help screens (Figure 3-7) shows format specifiers and additional symbols that can specify formats. The program SPECS.C (Listing 3-13 on the following page) prints different types of variables with their appropriate specifiers. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 3-7 can be found on p.71 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 3-7. Format specifiers. ────────────────────────────────────────────────────────────────────────── /* specs.c -- shows printf()format */ /* specifiers for numbers */ main() { int i = 122; /* ASCII code for 'z' */ long l = 93000000; /* distance to Sun (miles) */ float f = 192450.88; /* someone's bottom line */ double d = 2.0e+030; /* mass of Sun (kg.) */ printf("%d\n", i); /* integer as decimal */ printf("%x\n", i); /* integer as hex */ printf("%ld\n", l); /* long */ printf("%f\n", f); /* float as decimal */ printf("%e\n", f); /* float as exponential */ printf("%f\n", d); /* double as decimal */ printf("%e\n", d); /* double as exponential */ } ────────────────────────────────────────────────────────────────────────── Listing 3-13. The SPECS.C program. Compare the following output with the printf() statements in the SPECS.C program: 122 7a 93000000 192450.875000 1.924509e+005 2000000000000000000000000000000.000000 2.000000e+030 The first printf() statement prints the value of the int variable i, 122, as an ordinary decimal integer, using the now familiar %d specifier. The next statement prints the same value with the %x specifier, which prints values in hexadecimal. Next, we print the long integer value 93000000. Notice that this specifier, %ld, combines the %l (long) and %d (integer) specifiers. The SPECS.C program then prints the value of the variable f, 192450.88, using the %f floating-point specifier. In the next statement, we use %e to print the same number in exponential notation. Which is better? If the value represents money, the regular decimal format is more appropriate, but remember that both representations are slightly inaccurate because the original value, 192450.88, has eight places and float has a maximum precision of seven places. (If you want absolute accuracy, use the double specifier.) We print the final value, 2,000,000,000,000,000,000,000,000,000,000, two ways: as a double (note that you can use %f for double as well as for float) and as exponential notation with %e. Clearly, the latter is easier to read and understand. Format Specifiers and Data Types Remember, the format specifier merely controls how a value is displayed. The data type of the value represents how it is actually stored in the computer. The program FORMATS.C (Listing 3-14) displays the comedy of errors that can occur if you carelessly use the wrong format specifier with a data type. The following is the output of the program; compare it with the printf() statements in the program. As integer: 5 As long integer: 8519685 As exponential: 7.084198e-309 As float: 0.000000 The program uses four different specifiers to print the value of the int variable i, which we set to 5. Only the first representation, using %d, is correct. The other results vary widely (even from one run to another). How can the last three methods be so far off the mark? Consider the second printf() statement, in which we told QuickC to print the value of i as a long integer %ld. A long integer uses four bytes of memory, but this variable, as an int type, uses only two. When you specify a long integer, QuickC takes four bytes starting at the address of i and converts them into a long integer. Two of these bytes, however, have nothing to do with the variable i. You can see how similar problems arise when we try to interpret an integer variable as a float. All of this demonstrates that the format specifier must be compatible with the data type being handled. Table 3-1 correlates the most commonly encountered specifiers and data types. ────────────────────────────────────────────────────────────────────────── /* formats.c -- shows what happens when format */ /* doesn't match data type */ main() { int i = 5; printf("As integer: %d\n", i); printf("As long integer: %ld\n", i); printf("As exponential: %e\n", i); printf("As float: %f\n", i); } ────────────────────────────────────────────────────────────────────────── Listing 3-14. The FORMATS.C program. Table 3-1. Compatibility of Specifiers and Data Types Specifier Types ────────────────────────────────────────────────────────────────────────── %d int (signed or unsigned); char (ASCII value) %ld long %f float or double (decimal format) %e float or double (exponential format) %c char (as character) ────────────────────────────────────────────────────────────────────────── Field Width Specifiers We can also improve the appearance of printf() output by controlling how many decimal places are printed and how the number is aligned in the output field. To do this, C lets us precede the format specifier with a "field specifier." The field specifier takes the following form: . The "field width" is the total number of character positions that will be printed, and "decimal places" is the number of places printed after the decimal point. (Use the decimal place specifier only for float and double values.) Following are two examples of field specifiers: "5.2f"────────────────────float; 5 places, 2 of which are decimal places "8d"───────────────────────────────integer; 8 places (no decimal places) The program FIELDS.C (Listing 3-15) shows how field width specifiers work. The program prints a single variable with varying field specifiers: 123.456001────────────────────────────────────12.6f (field specifier) 123.4560────────────────────────────────────────────────────────────8.4f 123.456────────────────────────────────────────────────────────────8.3f 123.46────────────────────────────────────────────────────────────8.2f In the first printf() statement, the field specifier %12.6f sets up a 12-character-wide field, 6 characters of which are decimal places. Because the variable has only 10 characters to be printed (9 digits and a decimal point), printf() indents the number two spaces. By default, numbers are right-justified (printed starting in the rightmost position of the specified field width). To print numbers that start at the left side of the field (left-justified), put a minus sign in front of the field width specifier, "%-4.2f". ────────────────────────────────────────────────────────────────────────── /* fields.c -- shows the same number with different */ /* field widths and number of decimals */ main() { float f = 123.4560; printf("%12.6f\n", f); printf("%8.4f\n", f); printf("%8.3f\n", f); printf("%8.2f\n", f); } ────────────────────────────────────────────────────────────────────────── Listing 3-15. The FIELDS.C program. Note also in the first printf() statement that we asked for six decimal places, even though the variable number contained only the first four places. Although printf() prints these extra places, they add nothing to the precision of the number, and, in fact, give a misleading impression of precision. You should specify decimal places only to the expected precision of the value. For example, if you know that a value will range between 0 and 9999 with decimal places, you might specify %4.3f because the value can have as many as four places to the left of the decimal point, and a float has only seven places of precision. Thus, a total of seven places (4.3) displays an accurate value. Specifying %8.6 for this example would give a false impression of precision. In the second statement, the specifier establishes a field width of 8 (with 4 decimal places). The third statement specifies the same field width of 8, but with only 3 decimal places. Notice that the variable's fourth decimal place, the zero, is dropped, and that the number is indented one space because the variable has only 7 characters. The last statement specifies the same field width of 8, but with only 2 decimal places. The printf() function not only drops the third decimal place, it also rounds up the second decimal place to 6. Also, because the number has one fewer digit to fit in the 8-character field, printf() indents the number another space. Arithmetic Operators Like most languages, C offers a complete set of arithmetic operators: + (addition), - (subtraction), * (multiplication), and / (division). C also provides a fifth operator that is not quite as common in other languages── %, the remainder operator, sometimes called the "modulus" operator. This operator returns only the remainder of a division operation. For example, 5 % 2 is 1 (5 divided by 2 has a remainder of 1), and 9 % 3 is 0 (9 divided by 3 has no remainder). The modulus operator has many uses: You can use it for creating counters that cycle within counters or for resetting variables such as line counts by checking for a remainder of zero (if line_cnt % page_length = 0, then you know that you must start a new page). Operators are used with values to form expressions that yield new values. Below are some examples: 10 * 5─────────────────────────────────────────────Multiply two literals a / 5─────────────────────────────────────────────Divide value of a by 5 count + 1────────────────────────────────────────Add 1 to value of count (a * 80) + b──────────────Multiply value of a by 80, then add value of b In a program, you combine expressions with other elements to form statements. The MATH.C program (Listing 3-16 on the following page) contains statements that use expressions involving arithmetic operators. ────────────────────────────────────────────────────────────────────────── /* math.c -- shows arithmetic and */ /* precedence via expressions */ main() { int a = 10, b = 4, c = 2; /* simple arithmetic expressions */ printf("99 + 2 = %d\n", 99 + 2); /* ints */ printf("5 - 12 = %d\n", 5 - 12); printf("7.25 + 3.5 = %f\n", 7.25 + 3.5); /* floats */ /* compare precedence on these */ printf("20 * 20 + 40 = %d\n", 20 * 20 + 40); printf("20 * (20 + 40) = %d\n", 20 * (20 + 40)); printf("a * a - c + b = %d\n", a * a - c + b); printf("a * (a - (c + b)) = %d\n", a * (a - (c + b))); /* compare integer and float division */ printf("Integers: 5 / 2 = %d\n", 5 / 2); printf("Floats: 5.0 / 2.0 = %f\n", 5.0 / 2.0); } ────────────────────────────────────────────────────────────────────────── Listing 3-16. The MATH.C program. Each printf() statement prints the expression and then its value, as follows: 99 + 2 = 101 5 - 12 = -7 7.25 + 3.5 = 10.750000 20 * 20 + 40 = 440 20 * (20 + 40) = 1200 a * a - c + b = 102 a * (a - (c + b)) = 40 Integers: 5 / 2 = 2 Floats: 5.0 / 2.0 = 2.500000 The first three statements simply add and subtract literal numbers and print the results. Notice in the third statement that when QuickC sees a number with a decimal point, it assumes a float type and prints the answer accordingly (10.750000). Operator Precedence The next set of statements in MATH.C illustrates "precedence," or the rules that determine the order in which operators are applied. Generally, QuickC performs multiplication and division first, then addition and subtraction. When operators have equal precedence (such as division and multiplication), QuickC performs them from left to right. The following QuickC help screen, Figure 3-8, lists all the operators in the language (including many covered in later chapters) and arranges them in groups from highest to lowest precedence. Thus, in Listing 3-16 the first printf() statement in the second group of statements multiplies 20 by 20, then adds 40, resulting in 440. However, you can use parentheses to impose a different order of precedence, as shown in the next statement. To evaluate the expression 20 * (20 + 40), QuickC performs the addition first (resulting in 60) and then multiplies 20 by 60 to produce a value of 1200. The next two statements use combinations of variables. As an exercise, perform the calculations on paper before you run the program. Remember to observe the rules of precedence. Did your answers agree with QuickC's? The final two statements in MATH.C illustrate a common problem for the unwary beginning C programmer. QuickC divides integer and floating-point types differently. When you specify numbers as integers, as in the first statement, integer division is performed. Accordingly, 5 divided by 2 is 2 because this type of division discards any remainder. (A remainder in division is a fraction, and int types cannot represent fractions.) However, when you specify numbers with decimal points, QuickC treats them as float types, resulting in the expected answer of 2.5. Variables of int and float types are handled the same way as the literals above. The RECEIPTS.C program (Listing 3-17 on the following page) performs practical calculations with QuickC's math operators. Notice that we declare the units variable as an int type (you can't sell half a unit!) and the price and tax rates as float types. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 3-8 can be found on p.77 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 3-8. Operator precedence help screen. ────────────────────────────────────────────────────────────────────────── /* receipts.c -- calculates gross and net */ /* receipts on sales */ main() { int units = 38; /* number sold */ float price = 149.99, /* price per item */ rate = 0.06; /* sales tax rate */ /* variables to hold calculated totals */ float gross, tax, net; /* perform calculations */ net = units * price; tax = net * rate; gross = net + tax; /* print results */ printf("\tSales Report\n"); printf("Net sales: \t%6.2f\n", net); printf("Tax:\t\t %5.2f\n", tax); printf("Gross sales:\t%6.2f\n", gross); } ────────────────────────────────────────────────────────────────────────── Listing 3-17. The RECEIPTS.C program. The "calculations" section uses expressions to generate values for the variables net, tax, and gross. The printf() statements combine tab escape sequences \t and field width specifiers to align the output. Specify only two decimal places for money amounts. (Makes cents, doesn't it?) The program produces the following report: Sales Report Net sales: 5699.62 Tax: 341.98 Gross sales: 6041.60 Arithmetic with Mixed Types The accuracy of a number generated by QuickC depends on its data type and the format in which it is printed. An additional problem arises when you perform arithmetic operations on literals (constants) or variables of different data types: For example, what happens when you divide an int by a float? For calculations with mixed data types, C ranks data types roughly according to the number of bytes of storage they require. From highest to lowest, they are: double 8 bytes float 4 bytes long 4 bytes int 2 bytes char 1 byte Generally, QuickC converts the lower-ranking type to the higher-ranking one before it performs the calculation. Thus, when QuickC divides 49 by 12.5, it first converts 49 to 49.0 (a float), then performs the division. (If QuickC chose a lower-ranking type, the calculation would lose precision. The above calculation, for example, would be 49 / 12 = 4 in integer division.) Although the long and float types both use four bytes, a float can contain a fractional part that would be lost when converted "down" to a long: Therefore, float is ranked as the "higher" type. Finally, QuickC converts float types to double types before it calculates the result. Although it's convenient that QuickC performs conversions for you, some real problems can occur if you assign the results of a calculation to a variable of an incorrect data type. The following example illustrates such a mistake: int sales, units = 50; float price = 1.99; sales = units * price; printf("Total sales are %d\n", sales); QuickC calculates price * units correctly by converting units from 50 to 50.0 (to make it a float), and then multiplying it by the float value of price (1.99). The value of the expression is now the float value of 99.50. So far, so good. However, we assigned this value to the variable sales, which we declared as an int type. As a result, the fractional part of the value (.50) is dropped, making the value of sales an incorrect 99.00. The solution to this problem is simple──consider all of the potential values for a variable before you declare it. In this case, you need to declare the variable sales as a float. QuickC can help remind you of potential problems with data type conversions. When you select the Compile option from the Run menu, the left side of the dialog box lists four levels of compiler warning messages (levels 0 through 3). If you select level 2 before you compile programs, QuickC sends a warning message for each program statement that causes a data type conversion. A typical message follows: warning C4051: (1 of 4) data conversion When you see this type of message, note the statement the cursor is on, examine the data types involved, and look up the meaning of warning (4051) in the Microsoft QuickC Programmer's Guide. There you will note that this is an advisory message, and QuickC issues it for perfectly legitimate conversions, such as the int to float conversion in our earlier division example. The MIXED.C program (Listing 3-18 on the following page) shows more examples of operations with mixed data types. ────────────────────────────────────────────────────────────────────────── /* mixed.c -- shows the effects of mixing */ /* data types in an expression */ main() { int i = 50, iresult; long l = 1000000, lresult; float f = 10.5, fresult; double d = 1000.005, dresult; fresult = i + f; /* int + float to float */ printf("%f\n", fresult); fresult = i * f; /* int * float to float */ printf("%f\n", fresult); lresult = l + i; /* long + int to long */ printf("%ld\n", lresult); printf("%f\n", d * f); /* double * float */ /* to double */ fresult = d * f; /* assigned to a float */ printf("%f\n", fresult); /* loses some precision */ /* debugging a division problem */ iresult = i / l; /* int / long to int*/ printf("%d\n", iresult); /* whoops! loses result */ printf("%ld\n", iresult); /* this won't fix it */ fresult = i / l; /* store in float result */ printf("%f\n", fresult); /* doesn't work */ dresult = i / l; /* try a double */ printf("%f\n", dresult); /* doesn't work */ fresult = (float) i / l; /* try type cast */ printf("%f\n", fresult); /* correct result */ } ────────────────────────────────────────────────────────────────────────── Listing 3-18. The MIXED.C program. Compare this output to the program statements: 60.500000 525.000000 1000050 10500.052500 10500.052734 0 8519680 0.000000 0.000000 0.000050 The first set of statements adds an int and a float value and prints the result, which is 60.5, a float value. This shows QuickC's default type conversion at work. The second set of statements shows the same conversion with a multiplication operation. The third pair of statements adds a long to an int. Note that the result is correct (100,000 + 50 = 100,050), and, from its size, you can guess that it must be a long. QuickC converts the value 50 to a long before it does the calculation. Next, the program works with double and float types. When we specify d * f in the printf() statement, QuickC converts the float value f to a double and calculates a double result, which we print. (Remember, you can use the %f format specifier with either float or double types.) Because the answer requires nine places of precision, converting from float to double preserves the accuracy of the value. Next, we perform the same calculation, but we assign the result to a float value, f. Notice that the result, 10,500.052734, becomes inaccurate starting at the fourth decimal place. Converting from double to float can produce both large and subtle errors, depending on the numbers involved. To be safe, use a double variable to hold the result of this type of calculation. The last lengthy set of statements illustrates various approaches for dividing an int value i by a long value l. Only the last method produces the correct result. Assigning the result of the division to an int variable returns a 0, because the result is a very small decimal fraction (50 / 1,000,000), and integer division does not recognize remainders. In the next statement we assume that the result of the division is a decimal fraction and that we can store it in a float. But this doesn't work either. When we add more decimal places by using a double variable for the result, we still get a result of 0. The problem here is that when the two integer variables i and l are divided, the integer portion of the result, 0.000050, is 0. At this point, we can't retrieve the decimal fraction. Assigning it to a float or a double merely gives you a floating-point representation of 0! Type Casting C provides a solution to our division dilemma with a construction called a "type cast." A type cast explicitly converts a value to a specified type before any operations are done on that value. Consider the following example: int i1 = 10, i2 = 3; printf("%d\n", i1 / i2); printf("%f\n", (float) i1 / i2); In the first printf() statement, we divide the two integers and produce the integer result of 3. In the second printf() statement, we add (float) before i1. This is the type cast: It converts the value of i1 to a float. Because a type cast has a higher precedence than the arithmetic operators, it converts i1 to a float before the division operation. Now the division operation contains a float and an int! QuickC's default type conversion then converts i2 to a float as well, and the result is the float value 3.33333. If you look at the last two statements of the MIXED.C program, you can see we used a type cast in the same way, with the correct result of 0.000050. Type casts are useful for handling variables of lower-ranking data types (int, for example) that must occasionally be used in calculations to produce a result of a "higher" type (such as float). It is more efficient in terms of both storage and processing time to declare such variables as the lower type and to use type casts when necessary. Later, you will find type casts valuable when you must convert values to a specific type. Getting Input with scanf() In order to write programs that have real-world utility, we must first understand how a C program gets input from the user. The all-purpose C function for getting input and storing it in a variable is called scanf(). (Like printf(), scanf() is a "built-in" QuickC core library function.) Figure 3-9 shows how it works. Let's assume we have a program with a declared integer variable named acct_no. When the scanf() statement executes, the program waits for input from the user. After the user types the number and a carriage return, the input is stored in the variable acct_no, as if it had been assigned by an assignment statement. Notice that the acct_no variable in the scanf() argument list is preceded by an ampersand (&). Do you remember when we placed ampersands in front of variable names in the VARADDRS.C program (Listing 3-4 on p. 57) to retrieve the storage addresses of the variables? The scanf() function requires as its second argument an address at which it can store the input. The & returns the address of the following variable. If you omit the address operator from the front of the variable name, the value of the variable is interpreted as though it were an address, and the input is stored at that address. This can produce frightful results if it overwrites information that your program needs! Format specifier for type of value wanted │ ┌┴┐ scanf ("%d", &acct_no); │└────┬─┘ │ │ "address of" Variable name └─────────────┬──────────────┘ │ Variable to store input in Figure 3-9. Parts of a scanf() statement. The first argument in the scanf() statement in Figure 3-9 is "%d". This looks and works like the format specifiers we used with printf()──it specifies the type of the value that the program expects. As with printf(), the "%d" specifies an integer. You can also use most of the other specifiers you used with printf(). For example: scanf("%f", &deposit); gets a value for the float variable deposit. Notice that scanf(), by itself, does not print a prompt for the user; it merely presents the user with a blinking cursor. Therefore, you should precede a scanf() statement with a printf() statement that tells the user what information to supply. In the example above, we might precede the scanf() statement with: printf("How much is your deposit?"); The cursor now appears following a space after the prompt. You don't need to include a newline character: The cursor will move to the next line when the user presses Enter after typing the input. You can also use scanf() to get values for more than one variable at a time: printf("What is your age and weight?"); scanf("%d %d", &age &weight); In this example, the user types an age, a space (to separate the values), and then a weight. Note that the user can substitute an Enter or a Tab for the space. The CONVERT.C program (Listing 3-19) uses scanf() to prompt a user for a temperature in Fahrenheit and then converts the temperature to Centigrade. ────────────────────────────────────────────────────────────────────────── /* convert.c -- converts Fahrenheit temperature */ /* to Centigrade; gets value from user */ main() { float ftemp, ctemp; printf("What is the temperature in Fahrenheit? "); scanf("%f", &ftemp); ctemp = (ftemp - 32.0) * 5 / 9.0; printf("The temperature in Centigrade is %5.2f", ctemp); } ────────────────────────────────────────────────────────────────────────── Listing 3-19. The CONVERT.C program. A sample user dialog with CONVERT.C follows: What is the temperature in Fahrenheit? 87 The temperature in Centigrade is 30.56 We print the prompt with a printf() statement, then use a scanf() statement with a floating-point specifier %f to get the input value for the float variable ftemp. The AVGTEMP.C program (Listing 3-20) averages the daily high temperatures for a week. When you run the program, it prompts for the high temperature for each day of the week, beginning with Monday. ────────────────────────────────────────────────────────────────────────── /* avgtemp.c -- finds average temperature */ /* for the week */ main() { int t1, t2, t3, t4, t5, t6, t7; float avg; printf("Enter the high temperature for:\n"); printf("Monday: "); scanf("%d", &t1); printf("Tuesday: "); scanf("%d", &t2); printf("Wednesday: "); scanf("%d", &t3); printf("Thursday: "); scanf("%d", &t4); printf("Friday: "); scanf("%d", &t5); printf("Saturday: "); scanf("%d", &t6); printf("Sunday: "); scanf("%d", &t7); /* calculate and display average */ avg = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7.0; /* divide by 7.0 to ensure float result */ printf("The average high temperature for"); printf(" this week was %5.2f degrees.\n", avg); } ────────────────────────────────────────────────────────────────────────── Listing 3-20. The AVGTEMP.C program. The int variables t1 through t7 store the daily high temperatures, which are obtained by a series of scanf() statements. The program then calculates an average and prints it out. A sample dialog with this program might look as follows: Enter the high temperature for: Monday: 82 Tuesday: 91 Wednesday: 97 Thursday: 104 Friday: 95 Saturday: 88 Sunday: 78 The average high temperature for this week was 90.71 degrees. Note: It is important to note that scanf() does not check to make certain that the input is compatible with the data type of the variable in which it is stored. Shortcut Assignments, Increments, and Decrements Now that you know how to assign a value to a variable with the assignment operator = and how to use arithmetic operators to calculate new values, we can show you a few tricks and shortcuts. In the course of a program, it is often useful to add a value to a variable repeatedly or to subtract a value from a variable repeatedly. For example, a program that counts lines needs to add one to a variable (such as total_lines) each time it counts a new line. We could do this as follows: total_lines = total_lines + 1; That's the way most languages do it. However, because changing the value of a variable is such a common occurrence in programming, C provides special, concise "arithmetic assignment operators" for the purpose. Arithmetic Assignment Operators The arithmetic operators are the +, -, *, /, and %, and the assignment operator is the =. The arithmetic assignment operator, as the name suggests, is a combination of an arithmetic operator and the assignment operator: for example, +=. When a statement executes, QuickC performs the specified arithmetic on the variable's value and then assigns the result of the calculation to the variable as its new value. Using an arithmetic assignment operator, we can write a shorter version of the statement that increases the value of total_lines by one: total_lines += 1; Below are more examples that use arithmetic assignment operators: count -= 1;───────────────────────────Subtract 1 from the value of count fare += 0.75;──────────────────────────────────Add 0.75 to value of fare value *= 10;────────────────────────────────────────Multiply value by 10 You can use any arithmetic operator in an arithmetic assignment operation. Table 3-2 lists the five possible arithmetic assignment operators. The addition and subtraction assignment operators are the most commonly used. The OPEQUAL.C program (Listing 3-21) demonstrates the use of arithmetic assignment statements. The printf() statements print several arithmetic assignment expressions and their results. Be sure that when you read the printf() statements in the program you can correctly predict the following output: Starting values: m = 10 n = 5 m += 2 makes m 12 m -= n makes m 7 m *= 2 makes m 14 m = m + 1 makes m 15 m += 1 makes m 16 Table 3-2. Arithmetic Assignment Operators Operator Meaning ────────────────────────────────────────────────────────────────────────── += Add to value and assign -= Subtract from value and assign *= Multiply by value and assign /= Divide by value and assign %= Get remainder from division and assign ────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────── /* opequal.c -- shows combination math/assignment */ /* operators and increment operators */ main() { int m = 10, n = 5; printf("Starting values: m = %d n = %d\n", m, n); /* combination of arithmetic and assignment */ printf("m += 2 makes m %d\n", m += 2); printf("m -= n makes m %d\n", m -= n); printf("m *= 2 makes m %d\n", m *= 2); /* two ways to increment m */ printf("m = m + 1 makes m %d\n", m = m + 1); printf("m += 1 makes m %d\n", m += 1); } ────────────────────────────────────────────────────────────────────────── Listing 3-21. The OPEQUAL.C program. Increment and Decrement Operators As the last program demonstrated, both m = m + 1 and m += 1 added one to the value of m. If you've done any programming, you know how frequently the value of a variable must be increased or decreased by one. This is especially true when you create a "counter" variable that keeps track of the number of times a statement in a loop executes. C provides an ultra-concise operator, the increment operator ++, to add one to the value of a variable. Similarly, --, the decrement operator, subtracts one from the value of a variable. Consider the following examples: count++ ;────────────────────────────────────────Add 1 to value of count index-- ;─────────────────────────────────Subtract 1 from value of index Note that these increment and decrement operators are really arithmetic assignment statements. They add (or subtract) one and assign the resulting value to the variable. count++;──────────────────is equivalent to───────────────────count += 1; index--;──────────────────is equivalent to───────────────────index -= 1; (Most programmers do not use a space between the increment [or decrement] operator and the variable name. However, in C it is perfectly legal to use intervening spaces, as in count + +.) INCDEC.C (Listing 3-22) shows how the increment and decrement operators change the value of a variable. Compare the program statements to the following output: a is 10 ++a is 11 --a sets a back to 10 ────────────────────────────────────────────────────────────────────────── /* incdec.c -- shows effect of */ /* increments and decrements */ main() { int a = 10; printf("a is %d\n", a); printf("++a is %d\n", ++a); printf("--a sets a back to %d\n", --a); } ────────────────────────────────────────────────────────────────────────── Listing 3-22. The INCDEC.C program. Pre-increment vs Post-increment In the INCDEC.C program we put the increment or decrement operator in front of the variable name. However, you also can use it after the variable name. In either case the variable is incremented or decremented; but there is one important difference. When you use the operator in front of a variable name, the incrementing or decrementing is done immediately. When you use the operator after the variable name, the incrementing or decrementing is not done until the next use of the variable. The PREPOST.C program (Listing 3-23) shows how this works. The output of the program illustrates how incrementing is delayed: b is 100 b++ is still 100 but after it's used, b is incremented to 101 ++b, on the other hand, is immediately 102 Notice what happens to b when we use the increment operator after it, rather than before it. The first printf() statement with the value b++ prints the original value of 100, showing that it has not yet been incremented. The next printf() statement, however, prints 101. As a practical matter, the distinction between pre-increments and post-increments (or decrements) is usually important only when the variable is incremented or decremented while it is being used with other operators in a single expression. For example, suppose you want to increment counter and also assign it to total in the same statement. Assuming counter is currently 10: total = counter++; assigns 10 to total, because counter is assigned to total but not incremented until the next time it is used. On the other hand: total = ++counter; assigns 11 to total, because counter is incremented immediately and then assigned. ────────────────────────────────────────────────────────────────────────── /* prepost.c -- shows effect of pre- */ /* and post-increments */ /* and decrements */ main() { int b = 100; printf("b is %d\n", b); printf("b++ is still %d\n", b++); printf("but after it's used, "); printf("b is incremented to %d\n\n", b); printf("++b, on the other hand, "); printf("is immediately %d\n", ++b); } ────────────────────────────────────────────────────────────────────────── Listing 3-23. The PREPOST.C program. Relational Operators If you have some programming experience, you know that most programs must make decisions based on the values of certain variables. Variables are tested or compared, and the result of the test determines which program statement will execute next. The next two chapters cover the variety of "control structures" that C provides for this purpose. Let's build the foundation for those discussions by looking at the operators that C uses for testing or comparing values. A relational operator compares two values, which can be variables, literal numbers, or whole expressions. A combination of relational operators and values is called a relational expression. An example is count > 10, which translates as "is the value of count greater than 10?" The > in this expression is the "greater than" relational operator. The expression is true or false depending on the current value of the variable count. If count is 8, for example, the expression is false. Table 3-3 illustrates the ways we can compare two values, a and b. In reality, the values can be constants, variables, or expressions──anything that expresses a numeric value. (Remember from our discussion of ASCII that characters, too, are essentially numeric values.) We described the value of a relational expression as being "true" or "false." These terms are useful ways for us to follow the logic of a program, but the actual value of a relational statement, like everything else in the computer, is numeric. When a statement is true, its value is 1; when a statement is false, its value is 0. On the following page, the RELATION.C program (Listing 3-24) uses printf() statements to show the values of some statements that use relational operators. The program generates the following output: a = 5 b = 3 c = 4 Expression a > b has a value of 1 Expression a == c has a value of 0 Expression a > (b + c) has a value of 0 Because a is 5 and b is 3, the expression a > b has a value of 1, or true. Because c is 4, a == c has a value of 0, or false. The third expression combines relational and arithmetic operators: It first calculates the quantity (b + c), and then it compares the value to a. Table 3-3. Relational Operators Expression Meaning ────────────────────────────────────────────────────────────────────────── a < b Is a less than b? a > b Is a greater than b? a == b Is a equal to b? a != b Is a not equal to b? a <= b Is a less than or equal to b? a >= b Is a greater than or equal to b? ────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────── /* relation.c -- shows effect of */ /* relational operators */ main() { int a = 5, b = 3, c = 4; printf("a = %d\t b = %d\t c = %d\n", a, b, c); printf("Expression a > b has a value of %d\n", a > b); printf("Expression a == c has a value of %d\n", a == c); printf("Expression a > (b + c) has a value of %d\n", a > (b + c)); printf("Expression a = b has a value of %d\n", a = b); /* what happened here? */ } ────────────────────────────────────────────────────────────────────────── Listing 3-24. The RELATION.C program. Relational == vs Assignment = Here's a pitfall to watch out for: In C, a single equal sign = is the assignment operator, but a double equal sign == is the relational "equals" operator. In some languages (such as BASIC), a single operator, =, serves both purposes. So, if you are familiar with the BASIC usage, you might make errors with these operators until you get used to the difference. A common symptom of this error is a test that always appears to be either true or false. For example, if you type the assignment count = 10 instead of the relational count == 10 and then use the result in a control structure (such as a loop or if statement), QuickC always sees the result of the test as "true." Why? Because although relational expressions return a value of 1 for "true," QuickC considers any nonzero value to be "true" in this type of test. Because the sample statement with = is actually an assignment, its value is 10 (the number assigned), which QuickC interprets as "true" during a relational test. ────────────────────────────────────────────────────────────────────────── Assignment and "Equals" Relation The following table lets you compare the assignment and relational "equals" operators in C to those in other common languages: Language Assignment Relation ────────────────────────────────────────────────────────────────────── C = == BASIC = = Pascal := = FORTRAN = .EQ. Logo make = COBOL MOVE EQUAL TO ────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────── Precedence of Relational Operators In RELATION.C, we used parentheses in the expression a > (b + c). If you check QuickC's operator precedence help screen (Figure 3-8 on p. 77), you will see that relational operators have a lower precedence than arithmetic operators. Therefore, even if you don't use parentheses, b + c is calculated first, and only then is the result compared to a. Nevertheless, it is a good programming practice to use parentheses to visually clarify an expression. Logical Operators Sometimes it is necessary or useful to test for more than one thing in the same expression or statement. For example, you might want to test to see if either the temperature or pressure in a boiler has exceeded the safety limit. Let's assume the test for temperature is (temp < 900) and the test for pressure is (pressure < 5000). We can combine the two tests as follows: (temp < 900) && (pressure < 5000) The && is called the AND logical operator. It compares the results of two relational values and returns a value of true (1) only if both are true. QuickC first makes the temp test, then it makes the pressure test (testing is from left to right). Then the && operator checks to see if both tests were true. The OR logical operator, ||, works like the AND operator, except that it returns a value of true (1) if either or both of the tests are true. Thus, the statement (ch == 'q') || (turn > last_turn) is true if either the current value of ch is `q' or the current value of turn is greater than that of last_turn, or both. You could use this statement to check if a game is over. Using two relational operators, && and ||, and two possible results of a test (true and false), there are four possible results for a relational statement involving two tests. The TRUTH.C program (Listing 3-25 on the following page) prints these out by making comparisons using ones and zeros that represent the result of already completed relational tests. Recall that QuickC regards a value of 1 to be "true" and a value of 0 to be "false." Thus, 1 AND 1 is 1 means "True and true is true." 1 AND 1 is 1 1 AND 0 is 0 0 AND 0 is 0 1 OR 1 is 1 1 OR 0 is 1 0 OR 0 is 0 ────────────────────────────────────────────────────────────────────────── /* truth.c -- shows logical operators */ main() { printf("1 AND 1 is %d\n", 1 && 1); printf("1 AND 0 is %d\n", 1 && 0); printf("0 AND 0 is %d\n", 0 && 0); printf("1 OR 1 is %d\n", 1 || 1); printf("1 OR 0 is %d\n", 1 || 0); printf("0 OR 0 is %d\n", 0 || 0); } ────────────────────────────────────────────────────────────────────────── Listing 3-25. The TRUTH.C program. Once again, if you check the QuickC operator precedence help screen, you will notice that logical operators, such as && and ||, have a lower precedence than the relational operators, such as < or ==. Therefore, we didn't need parentheses around the relational expressions in our examples because QuickC evaluated them before it checked the logical operators. Again, we used parentheses because they make these complex expressions easier to read. The last operator we need to discuss is the !, or "not" operator. Its function is simple enough──it reverses the truth value of a relational expression. For example, if a is 10, a > 5 is true, but !(a > 5) is false. ──────────────────────────────────────────────────────────────────────────── Chapter 4 Repetition and Looping In all of our programs so far, QuickC has executed the program statements sequentially, from the first statement to the end of the program. However, most of a program's important work involves controlled repetition, in which a group of statements repeatedly does a particular job until the work is done. For example, consider the data-entry routine of a database program. This group of statements (used to receive, validate, and store data) must be repeated as long as the user wants to enter new data records. This set of repeating statements is called a loop because it is executed as though the statements were arranged in a circle. However, when the user wants to stop entering data, the program must be able to recognize a "quit" command and stop repeating the data-entry statements. As you study C, you will find many other examples of the need for controlled repetition. For example, a program that retrieves data from a file must repeatedly read and process data items until it reaches the end of the file. If you program in another language, you probably use loops regularly to initialize and access elements of an array or a set of variables. C uses three types of loops: the for loop, the while loop, and the do loop. Although these loops are fundamentally similar, they let you control the looping action in different ways to suit different needs. This chapter focuses on how to use these three types of loops and some of their common variations. The for Loop The for loop repeats a group of program statements as long as a specified condition is true. Generally, you use it to specify a fixed number of repetitions: for example, processing the accounts for each month of the year. The anatomy of a for loop is as follows: for (start; condition; update) { statements; } In this generalized for loop, start is one or more statements that initialize the variables used by the loop; condition is a relational expression that is tested to see whether the loop should continue to run; and update is one or more statements that change the values of variables in the loop. The group of statements between the braces that follow the for line is called the "body" of the loop. These statements execute as long as the condition in the parentheses is true. (The body can also consist of only one statement, in which case the braces are optional. We tend to use braces for even a single statement, however, because they make the body of the loop easier to distinguish.) The FORLOOP.C program (Listing 4-1) uses a for loop to count from 1 to 10. After we declare the variable i, we begin the loop structure with the keyword for. The parentheses that follow the for contain the control statements for the loop. Note that semicolons separate the control statements. ────────────────────────────────────────────────────────────────────────── /* forloop.c -- a simple for loop that */ /* counts to ten */ main() { int i; for (i = 1; i <= 10; i++) { printf("%d\n", i); /* body of loop */ } printf("All done!\n"); /* executed when i > 10 */ } ────────────────────────────────────────────────────────────────────────── Listing 4-1. The FORLOOP.C program. The start statement establishes the variable i as the loop's control variable. This is the variable whose value is tested to determine when the loop will stop running. (Many people use i, j, or k for loop control variables. This tradition owes its roots to FORTRAN. However, any legal name will do.) The next statement, i <= 10, is the loop's test, or condition. It specifies that the body of the loop execute repeatedly as long as the value of i is less than or equal to 10. The test condition is a relational statement that compares the loop control variable to an assigned value and returns a value of 1 (true) or 0 (false). The last statement in the for loop parentheses is i++. This update statement changes the value of the loop control variable each time the loop body executes. Here we use the ++ increment operator to increase i by one each time it executes, and, in fact, most for loops use update statements that either increment or decrement the control value by one. Using values other than one, however, is almost as easy: The statement value += 10, for example, adds 10 to value each time it executes. You can also use multiplication or division rather than addition or subtraction. Let's step through FORLOOP.C one statement at a time to see how it works: ■ Set i to 1. ■ Check i to see if it is less than or equal to 10. ■ Because the result of this test is true, execute the body of the loop. (The body consists of a printf() statement that prints the value of i.) ■ Execute the update statement, i++. (Set i to i + 1, or 2.) ■ Check the test statement again to see if i is still less than or equal to 10. If it is, execute the body of the loop again. Continue the cycle until the test condition is false (when the value of i increases to 11). Figure 4-1 on the following page shows this program as a flowchart. You can follow the arrows to trace the flow of execution. ────────────────────────────────────────────────────────────────────────── Choosing a Control Variable If you are used to writing loops in BASIC, remember that with C, you must declare the loop control variable before you use it in the loop. Select a data type for the control variable that can accommodate the full range of values the variable will hold when the loop is run. For example, a loop that will run 50,000 times requires a control variable of type unsigned int because a signed int value cannot exceed 32,767. ────────────────────────────────────────────────────────────────────────── ┌────────────────┐ │ Initialize │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ i = 1 │ ┌─────────▒───────────────── └───────┼────────┘ │° for (i = 1; i <= 10; i++° │ │° ▒ ▒ ° ▼ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒{▒▒▒▒▒▒▒▒▒▒ ▒ ° /\ ▒ ▬ ▬ ▬ │° printf("%d\n", i);▒ ° / \ ◄▒▒▒▒▒▒▒ ▬ ▬ │° } ▒ ▒ ° / TEST \ No ▬ ▬ │° ▒ ▒ ° ┌───►/ i <= 10 \ ──────► ▬ END ▬ │° ▒ ▒ ° │ \ ▬ / ▬ ▬ │° ▒ ▒ ° │ \ ▬ ? ▬ / ▬ ▬ │° ▒ ▒ ° │ \ ▬ / ▬ ▬ ▬ │° ▒ ▒ ° │ \/ │° ▒ ▒ ° │ │ Yes └───────────────▒────────▒── │ ┌───────▼────────┐ ▒ ▒ │Do body of loop │ ▒ ▒ │ │ print f... │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒ │ └───────┬────────┘ ▒ │ │ ▒ │ ┌───────▼────────┐ ▒ │ │ Add 1 to i │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ └───────┬────────┘ │ │ └───◄──────┘ Figure 4-1. The for loop. Why does the loop stop running? Let's look at the situation when i = 10: The printf() statement in the body of the loop prints the number 10. The update statement then increments the loop by 1 and the test statement executes. Because the value of i is now 11, the test fails (returns a value of "false"). This causes the program to skip the loop body and execute the next statement, which prints the message All done! for Loop Style As with other C statements, the statements within the parentheses of a for loop can extend to more than one line if necessary. As noted in our discussion of conventions, we align the braces vertically for the loop body, as shown in FORLOOP.C. An older style aligns the braces as follows: for (i = 1; i <= 10; i++) { printf("%d\n",i); } With this style, the braces can get lost in a long listing, making it difficult to find where the body of the loop begins and ends. Aligning the braces vertically makes them easier to spot and highlights the body of the loop. Also note that we indent the body of a C loop to the right of the line that specifies it. To indent text in the QuickC editor, simply press the Tab key. The default indention in QuickC is eight characters, but you can change this value at the Options box of the View menu. We use a tab of four characters in our listings. Pitfalls to Avoid in for Loops An easy mistake to make when writing for loops is to put a semicolon after the closing parenthesis: for (i = 1; i <= 10; i++);───────────────────────────────Semicolon added This does not cause a compiler error: In C, a semicolon by itself is a "null statement." Such a statement does nothing, but it counts as a legal statement. Using a semicolon after the parenthesis makes the null statement the body of the loop. Adding the semicolon to FORLOOP.C causes the loop to do "nothing" 10 times; the program then prints the value of i (which is 11 after the loop exits) and All Done! Also, always remember to put braces before and after a loop body that consists of more than one statement. If you do not use braces, only the first statement following the parentheses executes as the body of the loop. The remaining statements will execute only once, after the loop terminates. (This is another reason for adopting the practice of always putting braces around the statements in a loop body, even when the body has only one statement.) Multistatement for Loops FORLOOP.C has only one statement in the body of the loop, but most programs are much more complex. Let's develop a program that will print a table of square roots, squares, and cubes for the integers from 1 through 9. Because this program must calculate and print three values for each number, it needs several statements in the body of the for loop. Using QuickC Library Functions To write such a program, we need a means of producing the square root of a number. Although C does not have operators for calculating squares or cubes directly, we can get these values simply by multiplying a variable by itself two and three times respectively. To get the square root, however, we must call on QuickC's sqrt() function. This function returns the square root of any value you pass to it. For example, if i = 4, then sqrt(i) = 2. The square root function, sqrt(), is an example of a QuickC library function (sometimes called a "library routine"). We've already used several QuickC "core" functions, such as printf() and scanf(). Because these functions are part of the QuickC environment, you can use them without any special commands. (Appendix B lists all the built-in core functions.) ────────────────────────────────────────────────────────────────────────── Quick Tip Sometimes it is convenient to break out of a loop during its execution. Perhaps you recognize a problem with its output, or perhaps you find yourself in a runaway loop──one whose test will not or cannot fail. To break out of a loop, press Ctrl-Break. ────────────────────────────────────────────────────────────────────────── However, sqrt() is not on this list. It is one of many library routines that are defined in the header files (often called "include files") of the \INCLUDE directory. One of the early tasks in learning QuickC is becoming familiar with its external library functions. Fortunately, QuickC makes it easy to explore the function library. QuickC's extensive on-line help screens let you call up a summary of any function to find out whether it is a core function or an external library function. To find out about sqrt(), select Topic from the Help menu. Next, select the appropriate topic, math; this produces a list of library functions that include the sqrt() function. When you select this function, a small help window appears at the top of the QuickC screen. (See Figure 4-2.) The first entry in this window informs you that sqrt() resides in both the float.h and math.h include files. QuickC also lets you browse through include files while you are working on a program. Simply select Include from the View menu, select the \INCLUDE directory from the window (if necessary), and then select the include file you want to view. When you finish, select Open Last File from the File menu, and QuickC returns you to the program you were working on. Of course, the preferred reference for all QuickC library functions is the Microsoft QuickC Run-Time Library Reference, one of the manuals that come with QuickC. It introduces the library functions by category. ┌────────────────────────────────────────────────────────────────────────┐ │ Figure 4-2 can be found on p.98 of the printed version of the book. │ └────────────────────────────────────────────────────────────────────────┘ Figure 4-2. Library function help window. Using an Include File in a Program To use functions or other definitions from an include file in your program, you must specify the name of the file you want to call before the start of main(). For example: #include includes the file that contains graphics functions and definitions in your program. (The angle brackets that enclose the filename tell QuickC to look for the file in the default \INCLUDE directory, whose pathname the setup procedure stored in the environmental variable INCLUDE.) This statement is actually a "directive" to the QuickC preprocessor, a program that examines your C program code and looks for special commands that tell it to make changes in the program text before compilation begins. In this case, the #include preprocessor directive reads the contents of the specified include file into the program and compiles it as though you had typed it in. Only after it reads and compiles all the include files does QuickC compile your program statements. Note that preprocessor statements such as #include are not actually C language statements and do not end with a semicolon. Creating a Program List We have seen that we need to use a #include statement if we want to refer to the sqrt() function in the program we want to run, TABLE.C (Listing 4-2).