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
────────────────────────────────────────────────────────────────────────────
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
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
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
────────────────────────────────────────────────────────────────────────────
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.
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
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.
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 <graphics.h> (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()
{
<body of function>
}
rather than
function_name() {
<body of function>
}
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.)
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.
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
└────────────────────────────┘
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:<size>/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.
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 <Program not compiled>.
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.
──────────────────────────────────────────────────────────────────────────
/* 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:
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 */
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.
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:
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:
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 */
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:
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. │
└────────────────────────────────────────────────────────────────────────┘
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:
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:
<field width>.<decimal places>
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:
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 */
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 */
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;
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 */
}
──────────────────────────────────────────────────────────────────────────
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:
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 */
/* 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.
(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?
──────────────────────────────────────────────────────────────────────────
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
──────────────────────────────────────────────────────────────────────
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.
──────────────────────────────────────────────────────────────────────────
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 <graph.h>
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).