home *** CD-ROM | disk | FTP | other *** search
Text File | 2013-11-08 | 1.2 MB | 30,854 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
- Microsoft QuickC Programming
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Microsoft(R) QuickC(TM) Programming
-
- The Microsoft(R) Guide to Using the QuickC Compiler
-
- By The Waite Group
- Mitchell Waite, Stephen Prata, Bryan Costales, and Harry Henderson
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- PUBLISHED BY
- Microsoft Press
- A Division of Microsoft Corporation
- 16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717
-
- Copyright (c) 1988 by The Waite Group, Inc.
- All rights reserved. No part of the contents of this book may be reproduced
- or transmitted in any form or by any means without the written permission of
- the publisher.
-
- Library of Congress Cataloging in Publication Data
-
- Microsoft QuickC programming.
- Includes index.
- 1. C (Computer program language) 2. Microsoft QuickC (Computer program)
- I. Waite, Mitchell. II. Title: Microsoft Quick C programming.
- QA76.73.C15M53 1988 005.13'3 88-5203
- ISBN 1-55615-048-2
-
- Printed and bound in the United States of America.
-
- 1 2 3 4 5 6 7 8 9 MLML 3 2 1 0 9 8
-
- Distributed to the book trade in the United States by Harper & Row.
-
- Distributed to the book trade in Canada by General Publishing Company, Ltd.
-
- Distributed to the book trade outside the United States and Canada by
- Penguin Books Ltd.
-
- Penguin Books Ltd., Harmondsworth, Middlesex, England
- Penguin Books Australia Ltd., Ringwood, Victoria, Australia
- Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand
-
- British Cataloging in Publication Data available
-
- IBM(R) is a registered trademark, and PC/AT(TM) and PC/XT(TM) are trademarks
- of International Business Machines Corporation.
-
- Microsoft(R) and MS-DOS(R) are registered trademarks, and QuickC(TM) is a
- trademark of Microsoft Corporation.
-
- ────────────────────────────────────────────────────────────────────────────
- Acquisitions editor: Claudette Moore
- Project editor: Eric Stroo
- Copy editor: Gary Masters
- Technical reviewer: Doug Henderson
- ────────────────────────────────────────────────────────────────────────────
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Contents
-
-
- Preface
-
- Acknowledgments
-
- PART 1 INTRODUCTION TO C
-
- Chapter 1 Introduction
- Why Learn C?
- Why QuickC?
- Hardware Requirements
- Knowledge Requirements
- Conventions and Style
-
- Chapter 2 Starting with QuickC
- Our Book and Their Book
- Directories and Files Used by QuickC
- Running the QuickC SETUP Program
- Setting Up QuickC
- Starting QuickC
- Getting Help
- Fixing Errors
- Preparing for the Next Chapter
-
- PART 2 CORE OF C
-
- Chapter 3 C Fundamentals
- Basic Elements of C Programs
- Punctuation and Spacing in C Programs
- Using Comments in C
- Data Types and Declarations of Variables
- The Power of printf()
- Arithmetic Operators
- Getting Input with scanf()
- Shortcut Assignments, Increments, and Decrements
- Relational Operators
- Logical Operators
-
- Chapter 4 Repetition and Looping
- The for Loop
- The while Loop
- The do Loop
- Debugging and Loops
-
- Chapter 5 Decisions and Branching
- The if Statement
- The Conditional Assignment Statement ?
- Multipath Branching
- The switch Statement
- The break Statement
- The continue Statement
- The goto Statement
- More Complex Conditions for Branching
-
- Chapter 6 Functions and Function Calls
- Functions and Program Design
- Declaring and Defining a Function
- Local and Automatic Variables
- Static Variables
- External Variables
- Register Variables
- Passing Information to a Function
- Functions with Many Parameters
- Functions That Return Information
- Recursion
- Noninteger Functions
- Function Prototypes
- Putting It All Together: A Larger Program
-
- PART 3 ADVANCED C TOPICS
-
- Chapter 7 Arrays
- How Arrays Are Stored in Memory
- How to Declare Arrays
- Referencing and Using Array Items
- Bounds Checking Arrays in Your Code
- How to Initialize Arrays
- Arrays and Functions
- How Array Offsets Advance
- Multidimensional Arrays
- Advanced Topics and Tricks
- The Bitwise Operators, Tiny Arrays
-
- Chapter 8 Addresses and Pointers
- Addresses Reviewed
- What Is a Pointer?
- Accessing Variables with Pointers
- Passing Pointers to Functions
- Pointers and Arrays
- Pointer Arithmetic
- The Interchangeability of *amts and amts[]
- lvalue vs rvalue
- Type Casting Pointers and Addresses
- far Pointers
- Functions That Return Addresses
- Dynamic Arrays
- Advanced Pointer Techniques
-
- Chapter 9 Strings
- Declaring and Initializing Strings
- The String Pool and String Addresses
- Pointers and Initialized Strings
- Formatting Strings with printf()
- String Input and Output
- String Manipulation Routines
- Arrays and Strings
- The Arguments to main()──argv and argc
- Character Classification and Transformation
-
- Chapter 10 Managing Files
- Top-level I/O
- Mid-Level (Unbuffered) File I/O
- The File System
- Advanced Error Handling
-
- Chapter 11 Advanced Data Types
- Structure──An Array of Different Types
- Union──Multiple Types in the Same Space
- Enumerated Data with enum
- Bit Fields
- Advanced typedef
-
- Chapter 12 Large Projects
- Advanced C Preprocessor
- Using QuickC for Large Projects
-
- PART 4 C AND THE HARDWARE
-
- Chapter 13 Keyboard and Cursor Control
- Keyboard Input Functions
- Reading Non-ASCII Keys
- Console I/O Functions
- Keyboard Control with ANSI.SYS
- Using QuickC to Access the BIOS
- Cursor and Screen Control with BIOS Calls
-
- Chapter 14 Monitors and Text Modes
- Monitors and Controllers
- Text Modes and Portability
- Device-independent Programming
- Direct Memory Access
- Paging
- Ports
- The EGA and VGA
-
- Chapter 15 Graphics and QuickC
- The Graphics Modes
- CGA Graphics
- EGA Graphics
- VGA Graphics
-
- Chapter 16 Debugging
- Keyboard-Entry Errors
- Syntax Errors
- Run-Time Errors
- Common Run-Time Errors
- Design Errors
-
- Appendix A Some Resources for C Programmers
-
- Appendix B Built-in QuickC Functions
-
- Index
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Preface
-
- The Waite Group has written books on all aspects of the C language, from
- primers to advanced texts that mix C with assembly language But when
- Microsoft Press suggested we write a book on Microsoft's then unreleased
- QuickC compiler, we were skeptical. Having spent years trying to teach C
- both in the classroom and through our books, we were painfully aware of
- how difficult a language it is to learn. Despite its power, the Microsoft
- C Compiler has confounded many an eager student. Daunted by the cryptic
- syntax of its command-line interface, they slipped from C's learning curve
- and disappeared beneath its power curve.
-
- Our first look at the QuickC beta version sparked our attention──this was
- no standard C compiler. QuickC's interface was profoundly different: The
- editor completely integrated into the compiler, optional mouse
- compatibility, drop-down menus, full color display, the list went on and
- on. Finally we had point-and-click compiling. To us, all this meant a
- radical shift in the way we could teach C──we could take a friendly
- approach that would make C accessible to a much larger group of people.
-
- But was QuickC really a performance program? Were we talking fast object
- code or code more suited to timing traffic signals at snail races? Well,
- after creating hundreds of examples for this book, we can report that
- QuickC lives up to its speedy expectations. A product that can compile
- more than 10,000 lines per minute should satisfy all but the most jaded of
- hackers.
-
- There was more. QuickC was fully compatible with the libraries of version
- 5.0 of the Microsoft C Optimizing Compiler. We could develop our C code in
- QuickC's fast and friendly interface, get the errors out quickly with its
- integrated debugger and tracer, and then optimize our program for
- execution time by compiling it under the standard optimizing compiler.
- With the introduction of QuickC, we felt that C had finally reached the
- point of rivaling BASIC in ease of use. We saw in QuickC a product that
- could vitalize the learning of C and reinforce its dominance in
- professional program development.
-
- As educators, we saw that QuickC was an opportunity to build a complete
- course in the C language, one that could be pursued without an instructor
- and that would be attractive to beginners and professionals alike. The
- seductive interface made QuickC a breeze to run──we could focus on the
- syntax rather than on complicated command-line options and batch files. On
- top of this, QuickC's large and comprehensive Graphics Library meant that
- we could make our examples visually appealing and challenging. By assuming
- an IBM personal computer running MS-DOS or PC-DOS, we could tackle the
- sensitive issues of the interface between MS-DOS and C while at the same
- time flexing the keyboard, text, and bitmapped displays.
-
- After all these grandiose thoughts, the next thing we did was very
- natural──we gulped loudly. In the time available, no single author could
- possibly master a product as rich as QuickC with the aim of writing a book
- that examines thoroughly its vast repertoire. The solution was to pool the
- energies of four experienced C authors with over 25 years of combined
- programming and teaching experience, each writer focusing on a different
- section of the book. We think you will find that this book goes beyond
- most C books on the market (including our own). If you have any questions
- or comments, please address them to: The Waite Group, 3220 Sacramento
- Street, San Francisco, California 94115.
-
- Mitchell Waite
- Stephen Prata
- Bryan Costales
- Harry Henderson
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Acknowledgments
-
- The authors would like to take this opportunity to thank the people at
- Microsoft Press for helping to make this book a success: Claudette Moore,
- for acquiring this title and putting up with the authors' numerous
- requests throughout the project; Gary Masters, for editing and blending
- the different styles into one clear message; Doug Henderson, for his
- technical review; Eric Stroo, for coordinating the progress of the final
- manuscript through the production process and for being so diligent about
- the final look of the book; Alison Conn, for her review of the book's
- technical coverage; and Greg Lobdell, for providing a continual flow of
- alpha and beta copies of the QuickC compiler. Also, thanks to Reed Koch
- for his many hours of explaining QuickC's internals to the authors.
-
-
-
-
-
- Dedication
- To Bobbie Lee,
- who touched me in a way no one ever has
- Mitchell
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 1 INTRODUCTION TO C
- ────────────────────────────────────────────────────────────────────────────
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 1 Introduction
-
-
- Why Learn C?
-
- If you have experience with C, you are probably familiar with its
- advantages over alternatives such as BASIC or Pascal, and you may want to
- skip to the next section, which discusses the specific advantages of
- QuickC for C programmers. Here we compare C with two other popular
- languages, BASIC and Pascal.
-
- Although Pascal has its enthusiasts, and our old friend BASIC certainly
- has been improved in many ways (Microsoft's QuickBASIC for example), C has
- quickly become the premier language for professional programming both on
- micros, such as the IBM PC family, and on larger machines, such as those
- running the UNIX/XENIX operating system. Why is C so popular?
-
- Portability and Standards
-
- One reason is portability. The core of standard C is so designed that the
- same program runs on an IBM PC, a VAX mini, and an IBM mainframe.
- Portability comes about from adhering to standards that guarantee common
- features and functions regardless of the vendor, implementation, or
- hardware environment. The first, informal C standard was proclaimed by the
- famous "white book," Brian W. Kernighan and Dennis M. Ritchie's The C
- Programming Language (New Jersey: Prentice-Hall, 1978). The specifications
- in this book have been widely adopted in the design of C compilers, but
- the definitions are not comprehensive and specific enough to provide a
- true standard. Therefore, the American National Standards Institute (ANSI)
- has proposed a draft standard for the C language. (At the time of this
- writing, the standard has not been officially adopted, but most of its
- features seem stable.) Most current and future C compilers will be written
- to conform with the ANSI standard. QuickC is compatible with the ANSI
- standard. It also permits you to verify that your code uses only
- ANSI-compatible functions and definitions or to identify nonstandard
- features, such as those needed to support functions specific to MS-DOS and
- to IBM hardware.
-
- Another reason for the popularity of C is its close ties to the UNIX
- operating system. UNIX was written in C, and a variety of standards
- support the use of C in the UNIX environment. QuickC is functionally
- compatible with the UNIX System V standard library specifications.
-
- But what does all of this mean to you, the QuickC programmer?
-
- A C program written under QuickC on an IBM PC can, if it uses only
- ANSI-standard features, be moved to an Apple Macintosh, and you can
- compile it with an ANSI-standard Macintosh C compiler and run it in the
- new environment.
-
- This level of standardization is not common in programming languages.
- Pascal is only partially standardized: A Turbo Pascal program for the IBM
- PC, for example, cannot run under standard IBM Pascal without
- modification. In the IBM PC world, the ubiquitous BASICA program has
- offered a kind of standard, but other models of computers are provided
- with quite different dialects of BASIC, and you must do an extensive
- conversion to get a BASIC program written on one machine to run on another
- manufacturer's hardware.
-
- Notice that this discussion applies specifically to the "core" of C: the
- control structures, data structures, and basic input/output functions.
- Outside of this standard core, however, a number of areas of a C
- implementation are machine-dependent, such as the size of various kinds of
- numbers, keyboard codes, the video screen, graphics, and features of the
- operating system that handle files. To be worth its salt, a C compiler
- that runs on the IBM PC must include functions that give programs access
- to MS-DOS features, the underlying BIOS, and the hardware. Similarly, a C
- compiler for the Macintosh must include functions that give a program
- access to such elements as the machine's system toolbox. These functions
- are hardware-dependent and implementation-specific──by definition, they
- are not portable, but they are essential to getting the most out of your
- machine. C, as you will discover, provides a way to gather the
- machine-dependent parts in an organized manner, something other languages
- can't do.
-
- BASIC and, to a lesser extent, Pascal approach hardware dependence by
- customizing the language itself to include commands or functions that take
- care of the machine-dependent features. For example, a BASIC statement to
- control the speaker might be called PLAY. Another version of BASIC might
- call it MUSIC. The problem with this approach is apparent when you try to
- convert a program to run on a different machine; you cannot easily find
- the parts of the program that you must change to manipulate proprietary
- features. Also, such hardware-dependent statements may work differently on
- computers with different hardware configurations.
-
- A Modular Approach
-
- The programmer's task is more manageable with C. Each C compiler includes
- files of definitions, called include files, and collections of precompiled
- functions, called function libraries, which you can use to supplement the
- core of C to take full advantage of the features of a given machine. Your
- QuickC function library includes a rich collection of definitions and
- functions for MDA, CGA, EGA, MCGA, and VGA graphics (as well as Hercules
- graphics, starting with version 1.01); the whole set of MS-DOS function
- calls; and much more.
-
- The result is that a C programmer has several choices. If you don't need
- graphics or machine-specific features, you can write an ANSI-standard
- text-only C program and easily move it to other machines and operating
- systems. If you do need machine-dependent features in your program, you
- can use the "no-frills" version of the program and then add graphics and
- other hardware-dependent features in easily identified include files and
- libraries. For a particular hardware environment, you can then merge the
- appropriate include files and libraries into your program. Figure 1-1 on
- the following page illustrates the concept of portability.
-
- Portability requires many trade-offs. In general, the less portable (in
- other words, the more hardware-dependent) a program is, the faster it
- runs, and the more it takes advantage of graphics and other special
- hardware features. On the other hand, the more portable a program is, the
- easier it is to maintain, modify, or convert it to work with new hardware.
-
- Throughout this book, we point out portability issues and suggest ways to
- deal with them. For example, we note those features of QuickC that are
- compatible with ANSI and UNIX System V. We also look at portability versus
- performance in the MS-DOS world. For example, we discuss alternative ways
- for dealing with devices such as the keyboard and video display on MS-DOS
- machines (standard I/O, console I/O, and BIOS) and point out the
- portability trade-offs involved with each.
-
- ┌────────────────────────────┐
- │ │ Not
- │ Customized statements │ portable
- │ │
- │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
- ├┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┤
- │ │
- │ MS-DOS and BIOS │
- │ │
- ├────────────────────────────┤
- │ Hardware │
- └────────────────────────────┘
-
- (A) BASIC--A SNUG FIT BUT NOT PORTABLE
-
-
- ANSI/UNIX
- ┌────────────────────────────┐ Can run on
- │ Standard functions │ IBM PC, VAX,
- └─────────┐ ┌─────────┘ Macintosh,
- │ │ and others.
- ├────────┤
- └────────┘
- │
- ┌─────────┐ ┌─────────┐ Implementation
- │ │ │ │ of C, such as
- │ │ │ │ Microsoft C or
- │ └────────┘ │ Quick C.
- │ Machine-specific libraries │
- │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
- └┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
- │
-
- ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
- ┌┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┐ Specific
- │ │ machine-
- │ MS-DOS and BIOS │ IBM PC,
- │ │ for example.
- ├────────────────────────────┤
- │ Hardware │
- └────────────────────────────┘
-
- (B) C--A PORTABLE CORE
-
- Figure 1-1. Portability in C.
-
- C Is Powerful
-
- Portability is desirable, but you also want to write code that takes full
- advantage of the hardware. In this age of drop-down menus, windows, mice,
- and help screens, users expect a lot more out of software than they did
- only a few years ago. As a programmer you are often pushing the limits of
- the hardware, whether in processing speed, I/O, or graphics.
-
- When it comes to harnessing the hardware, C really shines. For example,
- other languages try to hide the fact that you are manipulating the
- contents of memory when you write code; with C pointers, you can easily
- manipulate memory directly. With Pascal, you can also directly manipulate
- memory with pointers, but the syntax is not as simple or as powerful as
- that of C. And in BASIC, you can use a PEEK and a POKE to access memory,
- but they lack the flexibility of pointers.
-
- Another important indicator of the power of a language is its ability to
- use machine resources efficiently. All high-level compiled languages
- translate program statements into machine instructions. With most
- languages you have little control over the efficiency of the resulting
- machine instructions. You are at the mercy of the assumptions the compiler
- or interpreter makes about your program and how it will be used. Suppose,
- for example, that your program uses one or two variables frequently in a
- loop that will be executed many times. In C, you declare register
- variables that are stored, if possible, in internal CPU registers; thus,
- delays in loading or retrieving their values in memory are avoided. The
- result is faster execution speed.
-
- Another important feature of C is its ability to create a variety of
- memory models. A memory model describes the way RAM is used during
- compilation and the way program code and data are shared in RAM. With most
- older BASICs you can use only 64 KB of memory to hold program code and
- data. Today, most MS-DOS machines have at least 256 KB (and often 640 KB
- of memory or more). Thus, newer compilers for BASIC, Pascal, and other
- languages often allow access to a larger amount of RAM. But C compilers go
- a step further: You──the programmer──decide how the computer will allocate
- memory. Depending on the needs of your program, you can choose to use most
- of the machine's memory for storing compiled instructions, you can use
- most of the memory to store data (such as arrays, structures, or lists),
- or you can allocate varying numbers of 64 KB memory segments to both.
- Figure 1-2 on the following page shows the concepts of register
- variables, pointers, and memory models.
-
- Pointers, register variables, and memory models are only some of the
- options C gives you for controlling the machine. In addition, most C
- compilers let you improve, or "optimize," the machine code generated from
- your program. You can optimize for program size (a smaller .EXE file) or
- for faster execution or for a combination of these. For example, QuickC
- performs some optimization for you and lets you choose other features as
- appropriate. In addition, you can use QuickC in combination with Microsoft
- C (the professional, industrial-strength C compiler) to provide
- optimization that is truly state of the art.
-
- Pointers ┌──────────────────┐ ┌────────────────┐
- for direct │ main () │ ├────────────────┤
- access to │ int * ptr; ──────┼──────┐ ├────────────────┤
- memory │ ptr ++; ─────────┼───┐ │ ├────────────────┤
- │ ... │ │ │ ├────────────────┤
- │ │ │ │ ├────────────────┤
- │ │ │ │ ├────────────────┤
- └──────────────────┘ │ │ ├────────────────┤
- Program │ └──────├────────────────┤
- │ ├────────────────┤
- └─────────├────────────────┤
- └────────────────┘
- Memory
-
- (A) POINTERS
-
-
- Fast ┌──────────────────┐ ┌────────────────┐
- register │ main () │───── CPU ├────────────────┤
- access │ register int i; │───── ├────────────────┤
- │ ... │ ├────────────────┤
- Regular │ │─ ─ ─ ─ ─ ─ ├────────────────┤
- memory │ int regular_varn │ ─ ─ ─ ─ ─ ├────────────────┤
- access │ ... │ ├────────────────┤
- └──────────────────┘ ├────────────────┤
- Program ├────────────────┤
- ├────────────────┤
- ├────────────────┤
- └────────────────┘
- Memory
-
- (B) REGISTER VARIABLES
-
-
- ┌──────┐
- │ │
- ┌──────┐ │ Code │ ┌──────┐
- │ Code │ │ │ │ │
- ┌──────┐ ├──────┤ ├──────┤ │ Code │
- │ Code │ │ │ │ │ │ │
- ├──────┤ │ Data │ │ Data │ ├──────┤
- │ Data │ │ │ │ │ │ Data │
- └──────┘ └──────┘ └──────┘ └──────┘
-
- (C) MEMORY MODELS
-
- Figure 1-2. C gives you control of the machine.
-
- C Is Extensible
-
- C also lets you customize the contents of include files and libraries so
- that they contain only the definitions and functions your program needs.
- These custom files can contain functions for anything from manipulating a
- database to formatting text. After you write and test these definitions
- and functions, your main program can use them as easily as it can use the
- standard include files and libraries provided with your compiler. On large
- real-world programming projects, teams of programmers can receive
- specifications for each set of routines needed, and each team can create
- resources that can be used anywhere in the project. Although most
- languages offer a version of this building-block methodology, the C
- approach is the simplest, the most flexible, and the easiest to use.
-
- The very popularity of C enhances the value of such language extensions.
- Hundreds of vendors have created C function libraries for almost every
- imaginable task. Figure 1-3 shows conceptually how you can use function
- libraries from both QuickC and other vendors in your programs. You can
- easily integrate vendor libraries into your own code, and because they are
- the products of professional C programmers, they are likely to be fast and
- efficient. You can almost always avoid the age-old problem of reinventing
- the wheel.
-
- ┌──────────┐
- │ MS-DOS │
- │ ┌──────────┐
- │ │ I/O │
- │ │ ┌──────────┐
- └─────│ │ Graphics │
- ┌─────────────┐ │ │ │
- │ │ └─────│ │
- │ Included ─┼──────────────────────│ │
- │ definitions │ └──────────┘
- │ ─────────── │
- │ Your code │ Third- Your
- │ main () │ Microsoft party custom
- │ ... │ libraries libraries library
- └─────────────┘ ┌─┐┌─┐┌─┐ ┌─┐┌─┐ ┌─┐
- │ Compile │ ││ ││ │ │ ││ │ │ │
- ┌──────────────────┐ └─┘└─┘└─┘ └─┘└─┘ └─┘
- │ Compiled Modules │───────────┘ │ │ │
- ├───────────────────┤ │ │ │
- │ Standard Library │──────────────┘ │ │
- ├───────────────────┤ │ │
- │ Graphics │──────────────────────────────┘ │
- ├───────────────────┤ │
- │ Database │────────────────────────────────────────────┘
- ├───────────────────┤
- │ Special functions │
- └───────────────────┘
- │ Link
- ┌──────────────────┐
- │ Ready-to-run │
- │ program │
- └───────────────────┘
-
- Figure 1-3. Using include files and libraries.
-
- C Is Structured
-
- The syntax of the C language itself supports structured programming. C
- provides the control structures of a modern structured language, such as
- if/then/else, for, while, while...do, and switch. (The last is like
- Pascal's case statement.) If you are experienced in Pascal or in one of
- the newer BASICs (such as Microsoft QuickBASIC), you will find these
- control structures conceptually familiar. However, you will have to learn
- syntax differences for C, and boxes in the text point these out. If you
- are used to one of the older BASICs, you will be pleasantly surprised at
- how these structures enable you to avoid nearly all goto statements that
- lead to disorganized "spaghetti code."
-
- C Is Concise
-
- Although C is a well-structured language, it encourages concise rather
- than verbose statements. For example, it uses braces to begin and end
- blocks of code, rather than Pascal's begin and end. C provides shorthand
- operators for assigning values to variables and for incrementing
- variables. To show the flavor of C, the following table presents a few
- comparisons of C, Pascal, and BASIC assignment statements:
-
- Some Comparisons of BASIC, Pascal, and C
- BASIC Pascal C
- ──────────────────────────────────────────────────────────────────────────
- 1. Set a, b, and c to 0 a = 0 a := 0; a = b = c = 0;
- b = 0 b := 0;
- c = 0 c := 0;
- 2. Set i to i + 1 i = i + 1 i := i + 1; i++;
- 3. Set a to a + 5 a = a + 5 a := a + 5; a += 5;
- ──────────────────────────────────────────────────────────────────────────
-
- Such conciseness speeds the typing of programs and makes C source files
- more compact and easier to edit. C functions are more accessible than
- their Pascal counterparts and much more efficient than the awkward
- subroutine mechanism of BASIC. With the C preprocessor, you can create
- your own shorthand, or macro, definitions with which you insert
- expressions or whole blocks of code in text by typing the name of the
- definition.
-
- This brief overview of the general features of C should suggest why the
- language is so popular. Let's now look more closely at the product with
- which this book is concerned, Microsoft QuickC, and see how its particular
- features and advantages make programming in C even more attractive.
-
-
- Why QuickC?
-
- Traditionally, C has had one big drawback compared with interpreted
- languages such as BASIC──a complex compilation and debugging process. You
- probably know that C is a compiled language, and MS-DOS─based compiled
- languages traditionally have required that you go through a lengthy series
- of steps to produce an executable file.
-
- The steps to compiling a traditional C program are the following:
-
- 1. Start a text editor or word processor and write a program.
-
- 2. Save the program to disk and exit the editor.
-
- 3. Run the compiler program by issuing a command line from the DOS
- prompt, usually with several filenames and options included, that tell
- it, for example, what memory model to use and whether to generate a
- listing file.
-
- 4. Look at the listing produced by the compiler, and study every error
- message.
-
- 5. Print out this error list for reference.
-
- 6. Start the editor again, open your C program file, and for each error
- try to find the exact line in which the error occurred and correct the
- program.
-
- 7. Go back to step 3 and try again until the program compiles without
- errors into an object code file.
-
- 8. Now run the linker, and tell it what libraries to combine with your
- object code file to produce an executable program (an MS-DOS .EXE
- file). If you used an incorrect function name or failed to specify the
- correct libraries, you will now get a new batch of error messages,
- this time from the linker. (They may, for example, report an
- "unresolved external," which probably means the name you used for a
- function in your code did not match the name of the function defined
- in the library.) To fix these errors, you may need to look at listings
- of include files. Or you may have to go back to the editor and correct
- your program. In any case, you must recompile and then try to link
- again.
-
- 9. When the code links without errors, you can finally run the program.
- Did it execute as you expected? No? Do you want to make some changes?
- Well, go back to the editor and try again.
-
- Just reading through these steps suggests how tedious a traditional
- compiled language can be. With interpreted languages, such as BASIC, LOGO,
- or HyperTalk, you can type a line or two of code, execute it immediately,
- and see the results. If your line of code contains errors or if you want
- to add or change something, the interpreter usually provides a simple text
- editor or line editor you can use immediately.
-
- But interpreted languages have one critical drawback──they're slow. Each
- line in a program in an interpreted language has to be translated into
- machine-executable instructions each time it is encountered. Therefore,
- only the simplest interpreted-language applications run fast enough for
- use in the real world.
-
- The philosophy behind QuickC is to provide a programming environment that
- is as easy to use as an interpreter, but with the execution speed
- obtainable only through a compiler. With QuickC, writing and testing
- programs is so easy that C can be a beginning programmer's first language.
-
- The QuickC Programming Environment
-
- With QuickC, you do all of your program development in and from the same
- place──the QuickC integrated programming environment. (Figure 1-4 shows
- the way your screen looks when you start QuickC.) This environment offers
- many advantages:
-
- 1. You can open a file for editing by using the Open command on the File
- menu, or you can simply start typing a new program. The QuickC
- full-screen editor is immediately available, with insert/delete,
- cut/paste, indention──all the features you need to type a program as
- easily as you type a letter with a word processor. And you never
- really "leave" this editor. You merely select whatever service you
- need from the menus.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 1-4 can be found on p.12 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 1-4. The initial QuickC screen.
-
- 2. To run the program you can click on the mouse or type a command. When
- you work with a program that has not yet been compiled, the compiler
- and linker are called as needed. There are no complex command-line
- options to type. If your program is error free, the program runs in
- seconds on the output screen.
-
- The main reason your programs compile, link, and run so quickly with
- QuickC is that, unlike traditional C compilers that compile and link
- to disk, QuickC by default compiles to memory. Thus, it can compile
- 10,000 lines a minute on a standard IBM PC/AT. Another reason is that
- some of the most commonly needed functions are held in memory. You
- also can create libraries that can be loaded into memory. The result
- is that QuickC uses available memory very efficiently.
-
- 3. As you view an error message, the cursor follows along through your
- program text; you can instantly correct each error with the built-in
- editor. No printed listings to pore over; no error numbers to look up!
-
- 4. Suppose your program compiles correctly but doesn't work as you
- expected. Without leaving QuickC, you can turn on the debugging and
- trace features, rerun your program, and then watch the changing values
- of selected variables, follow the flow of execution, and check the
- values being passed to and from functions called by your program.
-
- What about multiple-module programs──C programs that have several
- separately compiled libraries and code files? Traditionally, you had
- to run a special "make" program and give it a file with a unique
- syntax that told the compiler how to rebuild such a complex program
- after any change was made. With QuickC's program list feature, you
- simply tell QuickC what libraries and source code files you want to
- use. QuickC keeps track of all the other details, such as the
- relationship between modules and the date each module was last
- compiled.
-
- 5. Do you need access to MS-DOS? Need to make a new directory or back up
- some programs? Maybe you want to run some previously compiled C
- programs from MS-DOS. With a traditional, command-line-driven C
- compiler, you exit the compiler, work in MS-DOS, and then run the
- compiler again and figure out where you left off. With QuickC, you
- never leave the integrated programming environment. Using QuickC's DOS
- Shell feature, you exit to MS-DOS, take care of your business, and
- return to QuickC where you left off.
-
- You can select many other features from the QuickC programming environment
- in the same easy way. With a command-line compiler, most features require
- that you type obscure flags or option switches on the command line or
- create batch files to simplify complicated compiler commands. With QuickC,
- you select with a mouse click or keystroke such features as the error
- warning level, language extensions, and optimization. But don't let the
- convenience deceive you──underneath the covers, QuickC is constructing the
- proper list of options so that you can use the same linker. (QuickC also
- includes a command-line-driven compiler for those times you have a special
- need, such as compiling under certain memory models, or when you want to
- work outside the QuickC environment.)
-
- QuickC Performs
-
- QuickC is faster in almost all cases than its nearest competitors, and it
- beats them hands down in floating-point operation.
-
- QuickC also is fully compatible with its "big brother," the Microsoft C
- Optimizing Compiler, versions 5.0 and later. Any program that compiles
- under QuickC compiles under Microsoft C, version 5.0. Therefore, you can
- develop programs with QuickC and then effortlessly recompile them under
- Microsoft C for fine tuning, using a variety of optimization techniques.
-
- QuickC: Standard and Comprehensive
-
- Earlier we discussed ANSI and other official standards for C. There are
- also unofficial industry standards that are almost as important. When you
- use QuickC, you have the benefits of using a compiler that has become the
- industry standard for PCs: Microsoft C. QuickC is fully compatible with
- that standard. Thus, dozens of third-party C code libraries work with your
- programs because the programs you write are compatible with the ANSI or
- UNIX System V standards or with the MS-DOS─specific features of
- Microsoft C.
-
- The extras that come with the QuickC product are also impressive. Each
- standard-model library (small, medium, compact, large) supports the 8087
- coprocessor. There are libraries for every kind of PC graphics from
- monochrome and CGA to the latest VGA graphics for the IBM PS/2. The QuickC
- Graphics Library routines feature easy-to-use routines for drawing points
- and lines and manipulating complete images, including filling and
- animation, all with impressive speed. QuickC also has libraries that allow
- your programs complete access to MS-DOS and BIOS calls. And, because of
- QuickC's UNIX compatibility, you can also use UNIX System V functions for
- writing programs that can be ported to work in the UNIX environment.
-
-
- Hardware Requirements
-
- To run QuickC you need an IBM PC/XT, PC/AT, PS/2, or compatible computer
- with at least 448 KB of RAM and at least two floppy-disk drives. We
- suggest, however, that you develop QuickC programs on a hard disk.
- Compiling or linking to disk with floppy disks is time-consuming compared
- with hard disks. Also, fitting all the files you need for developing
- programs onto two disks can be tricky. But because some of you will be
- using floppy-disk-based systems, we will give you some tips later that
- should help you make the best of the situation. (And the situation is
- anything but grim: You can certainly develop programs that run great under
- QuickC on a floppy-based system.)
-
- We also recommend (but don't presuppose) that you use QuickC with a
- compatible mouse. You can handle all QuickC functions from the keyboard,
- but why get bogged down learning the keystroke combinations? With a mouse
- in hand, you simply point at what you want and select it.
-
- On the other hand, many people don't have (or choose not to use) a mouse.
- With QuickC, you can use short keystroke combinations. For example,
- Alt-r-s selects the Run menu's Start option to compile and run a program.
- (Even if you have a mouse, typing is sometimes faster.)
-
- Graphics capability is optional for most of this book. Chapter 15, which
- deals with graphics, requires a CGA, of course; for advanced graphics, you
- need an EGA; and the VGA section requires a PS/2, or a VGA board for older
- PCs. (If you have the new VGA, you also have CGA and EGA capability.) Even
- if you have only the basic monochrome adapter, you can create many
- interesting QuickC programs with the built-in IBM graphics character set.
-
- Finally, we recommend that you have a printer (although a printer is not
- required for this book).
-
-
- Knowledge Requirements
-
- Some programming experience──with BASIC or Pascal, for example──will help.
- But thanks to the ease of use of QuickC, C can be your first programming
- language, although you may have to work a bit harder than more experienced
- programmers.
-
- Because many of you have programmed in BASIC (such as Microsoft's BASICA
- or QuickBASIC) or Pascal (Borland's Turbo Pascal, for example), we scatter
- "asides" throughout the text for BASIC and Pascal programmers. These point
- out the ways in which C is similar to and different from those languages.
- Familiarity with another language is a two-edged sword when it comes to
- learning C. On the one hand, you already know many programming concepts
- used in C. On the other hand, differences in syntax and usage can trip you
- up if you aren't careful.
-
- If you are a UNIX programmer, you will feel right at home──as soon as you
- get used to QuickC's much more comfortable living room! The QuickC
- environment is far easier to use than the UNIX cc compiler and ln linker,
- and you won't have to write any make scripts. You probably already know
- the fundamentals of C, but watch out for features that are different in
- the IBM PC/MS-DOS environment, especially graphics and MS-DOS system
- calls. But as we noted, with a few minor exceptions, Microsoft C supports
- the standard I/O and other library functions used on UNIX systems.
- Occasional boxes point out matters of interest to UNIX programmers.
-
-
- Conventions and Style
-
- We have chosen the following typographical conventions for the
- descriptions of a C program in the text:
-
- ■ Names of ordinary (local) variables are lowercase italic. Example:
- count, sum
-
- ■ Names of external or global variables are also italic, but the first
- letter is capitalized. Example: Model
-
- ■ Underscores join the words of multiple-word variable names. Example:
- Grand_total (an external variable) or line_count (an ordinary variable)
-
- ■ Constants created with #define are uppercase italic. Example: PI
-
- ■ Macro definitions are uppercase italic. Example: PRINT_ERROR(MSG)
-
- ■ Function names are lowercase italic. Underscores join multiple-word
- function names. Examples: main(), count_lines(), printf()
-
- You'll also notice that names of Microsoft library functions that are
- non-ANSI-standard (such as the graphics functions) are lowercase italic
- and preceded by an underscore. Example: _getvideoconfig() (a Graphics
- Library function)
-
- ■ In #include statements, names of header files that we create are in
- double quotation marks. Example: #include "chr_graphics"
-
- Names of header files provided by Microsoft are in angle brackets.
- Example: #include <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.)
-
- C:\
- ──┬──
- │
- │
- c:\qc
- ┌─────────────────────┬─────────────────────┬─────────────
- │ │ │
- c:\qc\bin c:\qc\lib c:\qc\include
- ┌─────────────────┐ ────┬──── ┌────────────────┐
- │ │ │ │ │
- c:\qc\bin\sample qc.exe mlibce.lib assert.h c:\qc\include
- ───────┬──────── qcl.exe slibc7.lib bios.h ────────┬────
- │ qc.hlp graphics.lib conio.h │
- cflow.c link.exe graphics.qlb (etc.) locking.h
- new-conf.sys lib.exe (etc.) stat.h
- new-vars.bat (etc.)
-
- Figure 2-1. Typical directory structure for QuickC.
-
- From Source to Object: An Overview
-
- Now that we've surveyed the compiler, linker, include files, and
- libraries, let's see how they work together when you run a program with
- QuickC. Let's assume your program uses two include files, stdio.h and
- graph.h. When you "run" or "start" the QuickC compile/link phase, the
- compiler starts by "reading" your source code in the editor buffer. First,
- it sees the instructions to add the include files. The compiler then loads
- the stdio.h file and compiles the code found there. (The code in an
- include file is not already compiled.) Next it loads and compiles graph.h.
- These include files contain, among other things, definitions of functions
- whose compiled code resides in libraries. (The standard library for each
- memory model contains the code corresponding to standard header files such
- as stdio.h; GRAPHICS.LIB contains graph.h.) As it compiles the include
- file, the compiler notes these references to library code and passes them
- to the linker.
-
- After the compiler generates the object code for the part of the program
- you wrote yourself, the linker "resolves" all library references: It
- extracts the "modules" that contain the necessary code from the
- appropriate libraries and combines them with the rest of the code. The
- result is a compiled object program. QuickC's default creates an object
- program that runs from within the QuickC environment. This enables you to
- run the program immediately after you link it and lets you quickly test
- programs without leaving the QuickC environment. However, you can also
- create a .EXE file, or executable MS-DOS file, that you can run from the
- MS-DOS prompt. Figure 2-2 on the next page summarizes this process
- graphically.
-
- Editor
- ┌───────────────────┐
- Program │ #include stdio.h │
- references │ #include graph.h │
- include │ ──────────── │
- files │ ──────────── │
- │ ──────────── │
- │ ──────────── │
- └───────────────────┘
- │
- │ Preprocessor ┌──────────┐
- ┌──────────────────┐ │stdio.h │
- Included ▒ │ ─────────── │ ┌───── │ ───── │
- source ▒ │ ─────────── │ │ │ ───── │
- code ─── ▒ │ ─────────── │────┘ └──────────┘
- │ │────┐ ┌──────────┐
- Your ─── ▒ │ ─────────── │ │ │graph.h │
- source ▒ │ ─────────── │ └───── │ ───── │
- code └───────────────────┘ │ ───── │
- │ └──────────┘
- │ Compiler
- ┌──────────────────┐
- Compiled ▒ │ ---------───? │
- library ▒ │ ---------───? │
- references │ │
- │ │
- Your ─── ▒ │ ------------ │
- compiled ▒ │ ------------ │
- code └───────────────────┘
- │
- │ Linker
- ┌──────────────────┐ ┌─────────────┐
- Final │ ----------- │ │ Libraries │
- object │ ----------- │─────────│ (combined │
- program │ ----------- │ │ or │
- (in memory │ ----------- │ │ separate) │
- or .EXE) │ ----------- │ └─────────────┘
- │ ----------- │
- └───────────────────┘
-
- Figure 2-2. Compiling and linking with include files and libraries.
-
-
- Running the QuickC SETUP Program
-
- Microsoft distributes QuickC on five floppy disks. These disks and their
- hundreds of files contain the two compilers (integrated-environment and
- command-line), a full set of libraries for each memory model with a choice
- of 8087 hardware or emulation, a rich assortment of more than 30 include
- files, several utility programs, and many other goodies. The QuickC SETUP
- program lets you set up a working QuickC environment with directories
- containing only those files that you need and provides automatic access to
- directories as you specify.
-
- SETUP performs the following operations:
-
- ■ Sets up variables and commands in the MS-DOS environment that tell the
- operating system where to find all QuickC programs and files
-
- ■ Sets up a home directory for QuickC, creates the \BIN, \INCLUDE, \LIB,
- and \TMP subdirectories, and moves files from the floppy disks to these
- directories
-
- ■ Creates one or more combined libraries, depending on the memory
- model(s) and form of floating-point support you specify
-
- Note: SETUP for a floppy-disk system creates only the combined library.
- You must do the rest partly "by hand." See the section "Setting Up QuickC
- for Floppy-Disk Systems" on page 32.
-
- MS-DOS Variables and QuickC
-
- As we mentioned above, QuickC sets up and uses some MS-DOS commands and
- variables. MS-DOS uses variables (sometimes called MS-DOS "environmental"
- variables) to specify the location of system resources. When you boot an
- MS-DOS disk, the operating system calls on two files to configure the
- system: AUTOEXEC.BAT and CONFIG.SYS. Commands in these files control the
- environment that QuickC uses when you run it.
-
- When you run the SETUP program for a hard-disk system, QuickC sets
- environmental MS-DOS variables in two files: NEW-VARS.BAT and
- NEW-CONF.SYS. You can use these files as is or insert their contents into
- the AUTOEXEC.BAT and CONFIG.SYS files respectively. We recommend the
- latter procedure unless there are serious conflicts with your existing
- settings.
-
- You can use any editor (such as SideKick or EDLIN) to insert NEW-VARS.BAT
- in your AUTOEXEC.BAT file. If you have no AUTOEXEC.BAT, use MS-DOS to
- rename NEW-VARS.BAT as AUTOEXEC.BAT. The resulting file might look like
- this:
-
- setclock────────────────────────────────────────────────Set system clock
- fastopen c:────────────────────────────────────Install file access cache
- sk──────────────────────────────────────────────────────────Run SideKick
- set PATH=c:\;c:\wp;c:\c5\bin;c:\qc\bin;a:\───Combined with your old path
- set INCLUDE=c:\qc\include
- set LIB=c:\qc\lib
- set TMP=c:\qc\tmp
-
- After you insert the SET commands found in NEW-VARS.BAT, you will probably
- have two PATH= commands in your AUTOEXEC.BAT file. Combine the directories
- in the path provided by SETUP with your existing path, as shown in Figure
- 2-3. You can usually use the rest of the SET commands without
- modification. (By default, MS-DOS permits only 128 bytes of space for
- storing MS-DOS variable values.) If this amount proves insufficient,
- modify it as described in the sidebar on the next page.
-
- AUTOEXEC.BAT NEW-VARS.BAT
- ┌──────────────────┐ ┌──────────────────────┐
- │ ───────── │ │set PATH=c:\qc\bin │
- │ ───────── │ │set INCLUDE... │
- │ set PATH=c:\ │ │set LIB... │
- │ │ │set TMP... │
- │ │ │ │
- └──────────────────┘ └──────────────────────┘
- │ │
- └────────────┬─────────────┘
- │
- ┌───────────────────────────┐
- │ ─────────── │
- │ ─────────── │
- │ set PATH=c:\; c:qc\bin ───┼──── Combined paths from
- │ set INCLUDE=c:\qc\include │▒ AUTOEXEC.BAT and NEW-VARS
- │ set LIB=c:\qc\lib │▒─── As is, from
- │ set TMP=c:\qc\tmp │▒ NEW-VARS.BAT
- └────────────────────────────┘
-
-
- CONFIG.SYS NEW-CONF.SYS
- ┌──────────────────┐ ┌──────────────────┐
- │ ───────── │ │ FILES=20 ───────┼── This is larger, so
- │ ───────── │ │ BUFFERS=10 │ replace existing FILES
- │ ───────── │ │ │ command
- │ FILES=10 │ │ │
- │ BUFFERS=10 │ │ │
- └──────────────────┘ └──────────────────┘
- │ │
- └────────────┬─────────────┘
- │
- ┌───────────────────────────┐
- │ ─────────── │
- │ ─────────── │
- │ ─────────── │
- │ ─────────── │
- │ FILES=20 │
- │ BUFFERS=10 │
- └────────────────────────────┘
-
- Figure 2-3. Editing AUTOEXEC.BAT and CONFIG.SYS.
-
- Here's what the NEW-VARS.BAT commands do. PATH is an MS-DOS command that
- specifies the directories that MS-DOS searches to execute a program.
- Whenever you tell MS-DOS to execute a program on your hard disk (such as
- the QuickC linker or library manager), it first looks in the root
- directory of drive C:, and then checks the specified directories in the
- order they are listed. The next command tells QuickC that include files
- are in the \INCLUDE subdirectory of the main QuickC directory. Similarly,
- the other variables show that libraries are found in \QC\LIB and temporary
- files are in \QC\TMP.
-
-
- Setting Up QuickC
-
- Now let's set up the QuickC working environment. The QuickC manual should
- be your source for detailed information about setup procedures and the
- various options involved, but here are "quick start" instructions that can
- simplify the process and probably save you time.
-
- The basic steps you should follow are:
-
- ■ Check the PACKING.LST file on the first QuickC distribution disk. Be
- sure you have a complete set of disks and manuals.
-
- ■ Back up the QuickC disks to floppy disks (use the MS-DOS DISKCOPY
- command to ensure you have an exact copy). Then use the backups during
- the setup process.
-
- ■ Run the SETUP program.
-
- Before you run SETUP and before you use QuickC to develop programs, be
- sure that you have at least 448 KB of free memory. QuickC may appear to
- run fine with somewhat less than 448 KB until you try to compile certain
- programs.
-
- To verify the amount of free memory, type the CHKDSK command at the MS-DOS
- prompt. To increase the amount of free memory, you might be able to change
- your AUTOEXEC.BAT file so that some memory-resident programs are not
- loaded. Then, reboot to free the memory those programs were reserving.
-
- ──────────────────────────────────────────────────────────────────────────
- Out of Environment Space?
- If your current AUTOEXEC.BAT has many SET commands or a long PATH=
- statement, you might get an MS-DOS "out of environment space" error when
- you add the QuickC variables. If this happens, expand the available
- environment space by putting this command in your CONFIG.SYS file:
-
- shell=c:\command.com /e:<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.
-
- Drive A Drive B
- ┌──────────────────┐ ┌──────────────────┐
- │ QC.EXE │ │ MLIBCE.LIB │ ───── Libraries
- │ │ │ │
- │ │ │ QCHELLO.C │ ▒──── Your source
- │ │ ────── │ CIRCLE.C │ ▒ files
- │ │ │ │
- │ │ │ QCHELLO.EXE │ ▒──── Temporary
- ┌──│ ──┐ │ ──────── │ ▒ and object
- │ └──────────────────┘ │ └──────────────────┘ files
- │ │
- │ Swapped after startup │
- │ ┌──────────────────┐ │
- └── ├──┘
- │ \INCLUDE │───── Include files
- │ │
- │ QC.OVL │───── Overlay file
- │ │
- │ QC.HLP │───── Help screens
- │ │
- │ LINK.EXE │───── Linker
- └──────────────────┘
-
- Figure 2-4. Floppy-disk setup for QuickC.
-
- Differences for Floppy-Disk Users
-
- The examples in this book assume you have a hard disk with QuickC residing
- in a directory on drive C. Floppy-disk users can use these examples by
- substituting references as follows:
-
- Hard Disk Floppy Disks
- ──────────────────────────────────────────────────────────────────────────
- c:\qc\bin a:
- c:\qc\include a:\include
- c:\qc\lib b:
- ──────────────────────────────────────────────────────────────────────────
-
-
- Starting QuickC
-
- Now we're ready to start using QuickC.
-
- If you have QuickC on a hard disk and have correctly included \QC\BIN in
- the PATH variable in the AUTOEXEC.BAT file, run QuickC by typing
-
- qc
-
- at the C> prompt. (If you haven't changed your PATH variable to include
- \QC\BIN, you must change to this directory before you can run QuickC.)
-
- To use QuickC on a floppy-disk system:
-
- 1. Boot your system with an MS-DOS disk that contains the new QuickC
- AUTOEXEC.BAT and CONFIG.SYS files
-
- 2. Put your copy of the Product Disk in drive A
-
- 3. Start QuickC by typing qc at the A> prompt
-
- 4. When the QuickC screen appears, replace the disk in drive A with a
- copy of the Work Disk
-
- Drive A now contains an "overlay" file (this lets QuickC access files
- without further disk swapping), the Help menus, the linker, and the
- \INCLUDE directory.
-
- Improving the QuickC Display
-
- When you type qc on the MS-DOS command line, QuickC assumes you have a
- color monitor. If you have a monochrome monitor, this default setting can
- reduce the contrast of the characters on your screen and make them hard to
- read. To fix this, exit QuickC by selecting Exit from the File menu, and
- start QuickC in its "black-and-white" mode by typing qc /b.
-
- If you use a computer that refreshes the screen at a faster rate than
- standard ATs, such as some higher-performance models of COMPAQ computers,
- you can speed screen displays by using the command qc /g to start QuickC.
-
- If your computer has an EGA card, you can set the screen to display 43
- lines, instead of the normal 25, by starting QuickC with the qc /h
- command. Note that unless you have a high-resolution monitor, text can be
- very hard to read in this mode.
-
- You can combine these modes by separating them with a space. For example,
- qc /b /g starts QuickC in monochrome mode and accelerates the screen
- refresh rate. You can also put the qc command and options in a batch file
- so you don't have to type them each time you start.
-
- Overview of the QuickC Screen
-
- If you've used menu-based integrated programming environments such as
- Turbo Pascal and Microsoft QuickBASIC before, the QuickC screen should
- look familiar. (See Figure 2-5.)
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 2-5 can be found on p.35 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 2-5. QuickC startup screen.
-
- Notice the following screen elements:
-
- ■ The menu bar across the top of the screen lists the following options:
- File, Edit, View, Search, Run, Debug, Calls, and Help.
-
- ■ The "title bar" displays the name of the program currently loaded into
- the editor. (Because we haven't written a program yet, it now reads
- untitled.c.)
-
- ■ The main area of the screen, now blank, is the workspace for your
- program.
-
- ■ Two "scroll bars," a vertical one on the right side of the screen and a
- horizontal one near the bottom of the screen, let you use an optional
- mouse to scroll text up and down or side to side.
-
- ■ The status line at the bottom of the screen keeps track of the name of
- the current program, the status of your program, and the current cursor
- position. Note the Context section of the line. QuickC uses this area
- to remind you of your current stage of program development. Because no
- program is loaded, it reads <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.
-
- ADDRESSES DATA TYPE
- Stored ┌──────────────┐
- "downward" │ │ 5003
- in memory ├──────────────┤ char
- │ 1 ─── ▒│ │ 5002 ▒ ──── a
- │ byte └──────────────┘
- │ ┌──────────────┐
- │ ▒│ │ 5001 ▒
- │ 2 ─── ▒├──────────────┤ ▒ ──── 30 int
- │ bytes ▒│ │ 5000 ▒
- └──────────────┘
- ┌──────────────┐
- ▒│ │ 4999 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4998 ▒
- 4 ─── ▒├──────────────┤ ▒ ──── 557,617,814 long
- bytes ▒│ │ 4997 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4996 ▒
- └──────────────┘
- ┌──────────────┐
- ▒│ │ 4995 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4994 ▒
- 4 ─── ▒├──────────────┤ ▒ ──── 22.95 float
- bytes ▒│ │ 4993 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4992 ▒
- └──────────────┘
- ┌──────────────┐
- ▒│ │ 4991 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4990 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4989 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4988 ▒
- 8 ─── ▒├──────────────┤ ▒ ──── 1,000,000,000,000.00 double
- bytes ▒│ │ 4987 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4986 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4985 ▒
- ▒├──────────────┤ ▒
- ▒│ │ 4984 ▒
- └──────────────┘
-
- Figure 3-3. Storing information in memory.
-
- ──────────────────────────────────────────────────────────────────────────
- /* varsize.c -- shows amount of memory */
- /* by various types */
-
- main()
- {
- printf("Size of a char in bytes is %d\n", sizeof(char));
- printf("Size of an int in bytes is %d\n", sizeof(int));
- printf("Size of a long in bytes is %d\n", sizeof(long));
- printf("Size of a float in bytes is %d\n", sizeof(float));
- printf("Size of a double in bytes is %d\n", sizeof(double));
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-3. The VARSIZE.C program.
-
- Here's the output of VARSIZE.C:
-
- Size of a char in bytes is 1
- Size of an int in bytes is 2
- Size of a long in bytes is 4
- Size of a float in bytes is 4
- Size of a double in bytes is 8
-
- Declaring Variables
-
- To declare a variable, specify the data type and then the variable name.
- Here are some examples:
-
- int account_no;
- float balance;
- double budget;
- char acct_type;
-
- The first statement declares account_no as an integer (int) variable. The
- remaining statements declare variables as floating-point decimal (using
- the keyword float), "jumbo" 8-byte floating-point (double), and 1-byte
- character (char) data types.
-
- When you declare a variable, QuickC sets aside the appropriate number of
- bytes and notes the variable's starting address. The next program,
- VARADDRS.C (Listing 3-4), declares several types of variables and then
- prints out their starting addresses.
-
- ──────────────────────────────────────────────────────────────────────────
- /* varaddrs.c -- uses & operator to get */
- /* addresses of variables */
-
- main()
- {
- char c1, c2;
- int i;
- long l;
- float f;
- double d;
-
- printf("Address of c1 %d\n", &c1);
- printf("Address of c2 %d\n", &c2);
- printf("Address of i %d\n", &i);
- printf("Address of l %d\n", &l);
- printf("Address of f %d\n", &f);
- printf("Address of d %d\n", &d);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-4. The VARADDRS.C program.
-
- Although the output of this program varies with different system
- configurations, it should look something like this:
-
- Address of c1 6146
- Address of c2 6144
- Address of i 6142
- Address of l 6138
- Address of f 6134
- Address of d 6126
-
- VARADDRS.C obtains the addresses of the variables by using an ampersand
- (&) prefix with each variable name. The ampersand is the "address
- operator," and it returns the starting address for each variable
- specified. Compare the output of VARADDRS.C with Figure 3-3 to see how
- variables declared with different data types require different amounts of
- memory. When QuickC allocates the required number of bytes for a declared
- data type, the last byte allocated (moving downward in memory) is the
- variable's starting address. For example, the integer variable i has an
- address of 6142, indicating that it uses two bytes (6144 - 6142 = 2); the
- double type variable d uses eight bytes (6134 - 6126 = 8). Note that the
- compiler allocates two bytes for char values c1 and c2, although each
- value requires only one byte. The extra byte is convenient for
- manipulating (2-byte) words in memory.
-
- Rules for Naming Variables
-
- In C, the names of variables and functions are called "identifiers." An
- identifier can contain any uppercase or lowercase alphabetic characters
- (A-Z or a-z), digits (0-9), and the underscore character (_). However, the
- name must begin with a letter or underscore. Below are some examples of
- legal and illegal names:
-
- bignum─────────────────────────────────────────────────────────────Legal
- BigNum───────────────────────────────────Legal, and distinct from bignum
- _video───────────────────────────────Legal, can begin with an underscore
- bal_due─────────────────────────Legal, underscore used to separate words
- player2───────────────────────────────────Legal, number in variable name
- 8ball─────────────────────────────────Illegal, can't begin with a number
- tally-ho!─────────────────Illegal, contains hyphen and exclamation point
- int───────────────────Illegal, keyword reserved for name of integer type
-
- As you can see, you have considerable flexibility in choosing names for
- your variables. Because QuickC can distinguish the first 31 characters of
- a variable name, you can use long, descriptive names that help make the
- program easier to understand and modify. (You might want to use shorter
- names if your program must run a compiler that does not support long
- names.) C distinguishes between uppercase and lowercase characters, so
- that BigNum and bignum are different variables. Note that you can't begin
- a variable name with a number, use punctuation marks such as ! or $, or
- use C-language keywords as variable names. (You can embed a keyword in a
- variable name, however: interest is a legal name even though it contains
- the keyword int.) Fortunately, C has few keywords compared to languages
- such as BASIC: Most specify data types (such as int) or control and
- decision-branching operations (such as while and if).
-
- We use specific conventions for naming variables and functions. (See
- "Conventions and Style" in Chapter 1.) These are not required by QuickC,
- but are used here to differentiate among types of variables and functions.
- We also begin our variable names with a character other than an
- underscore──Microsoft uses the underscore as the initial character for its
- QuickC library functions.
-
- Assignment Statements
-
- How do you assign values to variables? In C, the simplest assignment
- statement consists of a variable name followed by an equal sign (=) and
- the value to be assigned. Below are some examples:
-
- a = 5;
- b = a + 5;
- c = a + b;
-
- In these assignment statements, the value to the right of the equal sign
- is assigned to the variable on the left. The value can be a number or an
- expression involving variables and/or numbers, such as a + 5 or a + b. If
- the value is an expression, QuickC determines the result and then assigns
- it to the variable.
-
- You can also assign the same value to several variables at once. Usually,
- you do this to initialize variables by setting them all to 0 or 1:
-
- line_count = word_count = 0;
- line_no = page_no = 1;
-
- Initializing Variables
-
- Many languages (including most versions of BASIC) automatically initialize
- numeric variables to 0 and character variables to blank or, perhaps,
- "null." C does not. For example, if your program has the following two
- lines:
-
- int length;
- printf("The length is %d\n", length);
-
- and you do not initialize length, it might produce the following output:
-
- The length is -25480
-
- The default value of a C variable is whatever pattern of bits happens to
- be in the memory locations of the variable when the compiler assigns them.
- Therefore, if you want to use a variable called total, for example, in a
- program that keeps track of some quantity, you should assign that variable
- an initial value of 0. You might modify the declaration above as follows:
-
- int length = 0;
-
- Because C is a concise language, it lets you combine the declaration and
- assignment of a variable. That is, you can declare the data type, the
- variables, and their values in the same statement:
-
- int a = 10, b = 50, c = 100;
-
- Type int
-
- Now that you know how to declare numeric variables and assign values to
- them, let's look at the int, or integer, data type more closely. An
- integer is a whole number, such as 30, -5, or 93,000,000. In QuickC, an
- int variable can hold numbers in the range of -32,768 through 32,767. This
- rather odd-looking range is established because the int type uses two
- bytes (16 bits) of memory. Two bytes can actually hold a range of 0
- through 65,535. But, in the regular int type, the high (leftmost) bit of
- the 2-byte combination stores the integer's sign (positive or negative),
- leaving only 15 bits for the number.
-
- If your variable will never store negative integers, use the unsigned int
- type. Because the sign bit is not used, you can use the full two bytes to
- store values from 0 through 65,535.
-
- Now let's look at the INTVARS.C program (Listing 3-5), which declares
- three integer variables, assigns values to them, and then prints out
- values that describe the World War II German battleship Bismarck.
-
- We declare the variables length and beam as int types because the length
- and beam (width) of the ship are less than 32,767 feet. For the
- displacement variable (the "weight" of the ship), we use the unsigned int
- type because we need a larger number (41,676) than 32,767 (the int limit)
- but a smaller number than 65,535 (the unsigned int type limit).
-
- The next three lines assign the values to the variables, and the three
- printf() statements print the values out. Notice that the printf()
- statements use two arguments within the parentheses: a string, such as
- "The battleship Bismarck was %d feet long", followed by a comma and the
- variable name whose value is to be printed. The %d in the string is a
- printf() "format specifier," and the value of the variable is printed in
- its place. (The %d specifier denotes a decimal [base 10] integer. C uses a
- variety of specifiers for different types and formats of numbers and
- characters. We'll discuss them when we look at printf().) When you run
- INTVARS.C, it generates the output that appears below the listing (on the
- following page).
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- ANSI C lets you specify any basic variable type as unsigned. It also lets
- you specify signed types. Therefore, although QuickC considers the int
- type to be signed by default, the C language doesn't guarantee that all C
- compilers do so. To write portable programs, you need to specify all
- variables as either signed or unsigned types.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* intvars.c -- declares, defines, and prints */
- /* some integer variables */
-
- main()
- {
- /* declare variables */
- int length, beam;
- unsigned int displacement;
- /* assign values to variables */
- length = 824;
- beam = 118;
- displacement = 41676;
-
- /* print out values */
- printf("The battleship Bismarck was %d feet long",
- length);
- printf(" with a beam of %d feet,\n", beam);
- printf("and displaced %u tons.\n", displacement);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-5. The INTVARS.C program.
-
- The battleship Bismarck was 824 feet long with a beam of 118 feet,
- and displaced 41676 tons.
-
- Long Integer Type
-
- We've seen that unsigned int variables can hold values to 65,535. But what
- if you must use larger numbers? Type long uses four bytes (32 bits) of
- memory (1 bit is reserved for the sign), and can store numbers from -2^31
- to +2^31, or -2,147,483,648 to 2,147,483,647 in base 10. Once again, if
- your variable will contain only positive numbers, you can double the high
- end of this range by specifying unsigned long. This lets you assign your
- variable a whole number value in the range 0 through 4,294,967,295.
-
- The SCORE.C program (Listing 3-6 on the following page) combines the
- declaration and assignment of the int variables home, visitors, inning,
- and attendance. Because total_attendance is a different data type, long,
- you must declare it in a separate statement. Again, the printf()
- statements display the values assigned to the variables and produce the
- following output:
-
- The score after 7 innings is
- Home team 5, Visitors 2.
-
- The attendance today is 31300.
- Attendance this year to date is 1135477.
-
- ──────────────────────────────────────────────────────────────────────────
- /* score.c -- defines and prints */
- /* int and long vars */
- main()
- {
- /* declare some int variables and assign values */
- /* to them in the same statement */
-
- int home = 5, visitors = 2, inning = 7, attendance = 31300;
- long total_attendance = 1135477; /* long int */
-
- /* print out the values */
-
- printf("The score after %d innings is \n", inning);
- printf("Home team %d, Visitors %d.\n\n", home, visitors);
- printf("The attendance today is %d.\n", attendance);
- printf("Attendance this year to date is %ld.",
- total_attendance);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-6. The SCORE.C program.
-
- Floating-Point Types
-
- You should store whole numbers as integers wherever possible──integers use
- the least amount of memory and integer arithmetic is fast. However, many
- numbers (such as dollars-and-cents amounts) require decimal fractions. In
- computers, these types of numbers are stored in "floating-point format."
-
- Consider the number 22.95. This number can be stored by dividing it into
- two parts: the digits themselves and an exponent showing the magnitude of
- the number in terms of powers of ten. Thus, 22.95 could be represented as
- 22.95 * 10^0. For uniformity in performing operations, however, C always
- expresses the digits with only one digit to the left of the decimal point.
- Therefore, the above number is actually stored as 2.295 * 10^1 (the same
- as 22.95 * 10^0). C represents this notation with the expression
- 2.295e+001. The first element, 2.295, is the number's digits (the
- "mantissa"), and the e+001 represents "exponent 1," or 10^1.
-
- Type float
-
- The most commonly used floating-point type in C is float. In QuickC, type
- float uses three bytes to store digits (the mantissa) and one byte to
- store the exponent. Because exponents can be negative (for example,
- 1.4e-002 = .014), one bit of the exponent byte stores the sign. Converted
- into decimal terms, this means you can store a mantissa with seven
- significant digits and an exponent ranging from -38 to +38. In fact, with
- QuickC's float type, you can store numbers as large as 3.4e+038, or 34
- with 37 zeros after it.
-
- The FLOATS.C program (Listing 3-7) displays three float values, each
- printed in both traditional decimal and exponential notation.
-
- ──────────────────────────────────────────────────────────────────────────
- /* floats.c -- shows floating values in regular */
- /* and exponential format */
-
- main()
- {
- float f1 = 2500.125, f2 = 0.0033, f3 = -50.99;
-
- printf("%f\t %e\n\n", f1, f1);
- printf("%f\t %e\n\n", f2, f2);
- printf("%f\t %e\n", f3, f3);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-7. The FLOATS.C program.
-
- The following is the output of the FLOATS.C program:
-
- 2500.125000 2.500125e+003
- 0.003300 3.300000e-003
- -50.990002 -5.099000e+001
-
- Notice that because 0.0033 is less than 1, it has a negative exponent
- (represented by the minus sign after the e). -50.99, on the other hand, is
- a negative number, but because its absolute (unsigned) magnitude is
- greater than 1, it has a positive exponent. FLOATS.C prints each variable
- first in decimal notation and then in exponential notation by varying the
- format specifier in the printf() statement: The %f produces traditional
- decimal format, and the %e produces exponential format.
-
- Type double
-
- For numbers larger than
-
- 340,000,000,000,000,000,000,000,000,000,000,000,000
-
- QuickC provides a "jumbo" floating-point type called double (double
- float). It uses eight bytes of storage and has a range of (plus or minus)
- 1.7e-308 to 1.7e+308. That's 308 decimal places before or after the
- decimal point, thus accommodating even the most expansive physicist or
- astronomer.
-
- ──────────────────────────────────────────────────────────────────────────
- Type Variations on Different Machines
- The C language doesn't define the number of bytes used by the int and
- unsigned int types. Instead, the number of bytes is based on the size of
- number a particular processor can handle in a single operation. This way,
- C compilers can always take advantage of a machine's architecture. Because
- the IBM PC uses the Intel 8086, 8088, or 80286 processor, an int uses two
- bytes, or 16 bits, and this is the implementation QuickC uses. However, on
- larger personal computers, such as those using the Intel 80386 processor,
- and on many minicomputers and mainframes, an int uses four bytes, or 32
- bits. Even if you write your program in "standard" C, you must be aware of
- these differences in implementation and machine architecture when you
- "port" the program to another machine.
- ──────────────────────────────────────────────────────────────────────────
-
- Precision for Floating-Point Numbers
-
- You must consider more than size, however, when storing numbers in a
- computer. We referred to a trillion-dollar budget ($1,000,000,000,000.00)
- earlier in the chapter. If size were the only consideration, we could use
- float to store this number. (A float can handle about 10^38, and a
- trillion is merely 10^12.)
-
- However, you also must consider the precision available to each data type
- in order to choose the right type for a given variable. Precision refers
- to the number of digits guaranteed to be exactly correct after a
- calculation. The float type has a precision of seven digits. Consider the
- following statements and the resulting output:
-
- float trillion = 1000000000000.00;
- printf("%f\n", trillion);
-
- 999999995904.000000
-
- We lost $4,096.00 in this operation. Although we might be happy if the
- government lost only that much of a trillion-dollar budget, we must expect
- full precision in financial calculations and probably an even higher
- precision in most scientific calculations. With its seven-digit precision,
- float can't accurately represent a trillion dollars. We attain the
- required precision by declaring:
-
- double trillion = 1000000000000.00;
-
- Because double has 15-digit precision, the result is completely accurate.
-
- Type char
-
- Let's look at one last data type, char (character). Characters include the
- uppercase and lowercase letters, numerals, punctuation marks, and
- nonprinting control characters. Characters on most computers, including
- the IBM PC, are represented by numbers between 0 and 127, according to the
- ASCII code. The CHARS.C program (Listing 3-8) shows some examples.
-
- Running CHARS.C produces the following output:
-
- The character A has ASCII code 65
- If you add ten, you get K
- The character a has ASCII code 97
-
- The first line of the main() function declares two char type variables,
- ch1 and ch2, and assigns them the values of `A' and `a' respectively. The
- `A' and `a' are called "character constants" or "character literals," and
- you assign them to char variables the same way you assign numeric
- constants. (Note that you must use single quotes around the character
- constant.)
-
- Consider the first printf() statement in the program:
-
- printf("The character %c has ASCII code %d\n", ch1, ch1);
-
- The variable ch1 is specified twice at the end of the argument list. The
- first format specifier, %c, prints the value of ch1 as a character. Then
- the %d specifier prints ch1 as an integer. A character is actually stored
- as a 1-byte version of int, and unless you specify that QuickC treat it as
- a character, it is treated as an integer. This enables us to use the
- expression ch1 + 10 in the second printf() statement. The variable ch1
- contains an integer value (the ASCII code for `A', or 65), so adding 10 to
- it produces 75. When the %c specifier then prints this value, it displays
- the character with the ASCII value of 75, or `K'.
-
- ──────────────────────────────────────────────────────────────────────────
- /* chars.c -- shows some variables of type char */
- /* as both characters and integers */
-
- main()
- {
- char ch1 = 'A', ch2 = 'a';
-
- printf("The character %c has ASCII code %d\n", ch1, ch1);
- printf("If you add ten, you get %c\n", ch1 + 10);
- printf("The character %c has ASCII code %d\n", ch2, ch2);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-8. The CHARS.C program.
-
- Type unsigned char
-
- A char value is a signed, 1-byte value that stores values in the range of
- -128 to +127. However, the IBM PC's version of ASCII uses the values 0 to
- 255 as character codes. The first half of extended ASCII contains the
- regular ASCII character set. Codes from 128 to 255, however, consist of
- special characters and graphics shapes, which together are called the
- "extended character set." You can use the extended character set by
- declaring variables as the unsigned char type. For example:
-
- unsigned char box = 178;
- printf("%c\n", box);
-
- displays a rectangular box, or extended ASCII character number 178. (Note
- that two QuickC general help screens show the complete extended ASCII
- character set.)
-
- Using typedef
-
- C lets you rename any data type with the typedef statement. For example,
- if you use unsigned char type variables to hold characters from the full
- 256-character extended set, you could define an easily remembered
- mnemonic:
-
- typedef xchar unsigned char;
- xchar highlight_char, border_char;
-
- The typedef statement tells QuickC that the word xchar now represents
- unsigned char. Next, we declare two variables as type xchar. Note that you
- can still declare variables as unsigned char at any time. Also note that
- typedef does not create new data types, it merely provides synonyms for
- existing ones.
-
- The HARDWARE.C program (Listing 3-9) ends our survey of QuickC data
- types.
-
- ──────────────────────────────────────────────────────────────────────────
- /* hardware.c -- shows a mixture of int, */
- /* float, and char types */
-
- main()
- {
- int threads = 8; /* threads per inch */
- float length = 1.25, /* length in inches */
- diameter = 0.425, /* diameter in inches */
- price = 0.89; /* price per hundred */
- char bin = 'A'; /* kept in bin A */
- long quantity = 42300; /* number in bin */
-
- printf("Screws: %d threads/inch\n %f inches long\n",
- threads, length);
- printf ("%f diameter\n\n", diameter);
- printf("Price per 100: %f\n", price);
- printf("Stored in bin: %c\n Quantity on hand: %ld",
- bin, quantity);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-9. The HARDWARE.C program.
-
- Be sure you understand why we declared the different types. The printf()
- statements display the values of the variables and some descriptive text:
-
- Screws: eight threads/inch
- 1.250000 inches long
- 0.425000 diameter
-
- Price per 100: 0.890000
- Stored in bin: A
- Quantity on hand: 42300
-
- Although the program works correctly, it would look better if the output
- were formatted more neatly. Also, QuickC printed several extra decimal
- places and filled them with zeros. To gain more control over the
- appearance of program output, we need to study printf() in more detail.
-
- Summary of Data Types
-
- You don't need to memorize the precise numbers associated with each data
- type; one of QuickC's help screens lets you check which data type you
- should use in a given situation. Display this summary of QuickC data types
- by pressing the F1 key and then proceeding to the appropriate screen. As
- you work with various data types in this chapter, you can always consult
- this chart, shown in Figure 3-4, to refresh your memory.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 3-4 can be found on p.67 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 3-4. Data types help screen.
-
-
- The Power of printf()
-
- Thus far we've used printf() statements merely to display values. But
- printf() is actually quite versatile for formatting numbers and character
- strings.
-
- Using Escape Sequences
-
- Let's look at the parts of the printf() statement from our first program,
- QCHELLO.C:
-
- printf ("Hello, and welcome to QuickC!\n");
-
- This is the simplest printf() statement: It merely prints out a string; no
- variables are involved. Earlier, we briefly discussed the one unusual
- feature of this printf() statement, the \n at the end of the string. This
- combination of backslash and following character is called an "escape
- sequence." Escape sequences tell printf() to print special characters as
- part of the given string. The \n, for example, adds the newline character,
- which moves the cursor or printer head to the beginning of the next line.
- Many languages use two kinds of statements for printing: one to print some
- information, and one to print some information and then start a new line.
- With typical conciseness and versatility, C lets you use one function to
- print any ASCII character, including newline, Tab, and carriage-return
- characters, giving you complete control of the position of the cursor or
- printer head.
-
- One QuickC help screen, shown in Figure 3-5 on the following page, lists
- all of the escape sequences. The newline \n and tab \t sequences are the
- most frequently used. The \a escape sequence causes an "alert," or beep,
- at the terminal.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 3-5 can be found on p.68 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 3-5. Character escape sequences.
-
- The ONELINE.C program (Listing 3-10) shows what happens when you don't
- use the newline escape sequence. When you run the program, the output is
- all on one line as follows:
-
- All displayed onthe same line, with no space unless specified.
-
- Not only do the strings from all three printf() statements end up on the
- same line, the word "on" at the end of the first string runs into the word
- "the" at the start of the second string. To print two strings on the same
- line with a space between them, you must include the space in the string.
- In the third string of ONELINE.C, we added a space before the word
- "unless."
-
- ──────────────────────────────────────────────────────────────────────────
- /* oneline.c -- shows how printf() continues */
- /* on the same line */
-
- main ()
- {
- printf("All displayed on");
- printf("the same line, with no space");
- printf(" unless specified.");
- /* note added space in line above */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-10. The ONELINE.C program.
-
- The program STRINGS.C (Listing 3-11) demonstrates the two basic ways to
- print strings with printf().
-
- The first printf() statement has only one argument, the string to be
- printed, and the newline escape sequence. The second statement has two
- arguments, the format specifier %s (for "string") and the string to be
- printed. It replaces the specifier with the string and prints it. This is
- the same procedure we used to print numeric variables and literals with
- specifiers such as %d. The STRINGS.C program produces the following
- output:
-
- This uses a string literal by itself
- This plugs the literal into %s
-
- TABS.C (Listing 3-12) illustrates the use of the tab escape sequence \t.
-
- ──────────────────────────────────────────────────────────────────────────
- /* strings.c -- shows two ways to print */
- /* a string with printf() */
-
- main()
- {
- printf("This uses a string literal by itself\n");
- printf("%s", "This plugs the literal into %s\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-11. The STRINGS.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* tabs.c -- shows formatting with the \t */
- /* tab escape sequence */
-
- main()
- {
- int q1 = 338, q2 = 57, q3 = 1048, q4 = 778,
- /* quantity in bin */
- t1 = 6, t2 = 8, t3 = 12, t4 = 16;
- /* threads per inch */
-
- float s1 = 0.250, s2 = 0.500, s3 = 0.750, s4 = 1.0;
- /* size in inches */
-
- /* print table header */
- printf("number\t\t size\t\t threads\n");
- printf("in bin\t\t (inches)\t per inch\n\n");
-
- /* print lines of table */
- printf("%d\t\t %f\t %d\n", q1, s1, t1);
- printf("%d\t\t %f\t %d\n", q2, s2, t2);
- printf("%d\t\t %f\t %d\n", q3, s3, t3);
- printf("%d\t\t %f\t %d\n", q4, s4, t4);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-12. The TABS.C program.
-
- This program prints four sets of data in a neat table. The program prints
- the table headers first, using \t to tab to the next field. Using \t to
- position each item at the next tab stop causes the output to be left-
- justified in each field. To make the table easier to read, we added a
- blank line between the header and the data by including an extra \n in the
- second printf() statement. The program then prints the values of the
- variables in the same tab fields as the headers. The result of all this is
- as follows:
-
- number size threads
- in bin (inches) per inch
-
- 338 0.250000 6
- 57 0.500000 8
- 1048 0.750000 12
- 778 1.000000 16
-
- Formatting Numbers with printf()
-
- The printf() function can also print numbers in a variety of formats.
- Let's look at a printf() statement from SCORE.C, which is analyzed in
- Figure 3-6.
-
- String to print is
- enclosed in quotes ┌─────────────────────────────┐
- │ │ │
- │ ┌───┐ ┌────┐ ┌───────┐
- printf ("The score after │ %d │ innings is │ \n │ ", │ inning │ );
- └────┘ └────┘ └────────┘
- │ │ │
- │ │ │
- Format specifier Newline Variable whose
- for an int value escape value is to be
- sequence printed
-
- Figure 3-6. The printf() statement from SCORE.C.
-
- ──────────────────────────────────────────────────────────────────────────
- Return and Newline Are Different
- If you program in other languages on MS-DOS machines, you might expect \r
- (carriage return) to move the cursor to the start of a new line. Change
- the \n in TABS.C to \r and run the program again. What happens? Each line
- prints over the preceding one. Although many languages on MS-DOS machines
- incorporate a line feed in a carriage return, C treats newline and return
- as distinct operations. Return moves the cursor to the beginning of the
- current line but does not advance it to a new line. Newline causes output
- to start on the next line. It commences with output at the beginning of
- the next line (rather than directly below the old position) because MS-DOS
- interprets it as though it contains a carriage return as well.
- ──────────────────────────────────────────────────────────────────────────
-
- Notice the %d in our example, SCORE.C. This, as we have already mentioned,
- is the format specifier for a decimal integer. The string "The score after
- %d innings is" is followed by a comma and the variable inning. Thus, when
- the printf() statement executes, the string is printed with the value of
- inning. You can also print more than one value in the same string. For
- example, if you define int apples = 12, oranges = 9, pears = 3;, then
- execute the following printf() statement:
-
- printf("I have %d apples, %d oranges, and %d pears. \n", apples, oranges,
-
- you see the following output:
-
- I have 12 apples, 9 oranges, and 3 pears.
-
- Specifying Formats with printf()
-
- Variables are printed according to their type and the format specifiers
- used. One of the QuickC General help screens (Figure 3-7) shows format
- specifiers and additional symbols that can specify formats.
-
- The program SPECS.C (Listing 3-13 on the following page) prints different
- types of variables with their appropriate specifiers.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 3-7 can be found on p.71 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 3-7. Format specifiers.
-
- ──────────────────────────────────────────────────────────────────────────
- /* specs.c -- shows printf()format */
- /* specifiers for numbers */
-
- main()
- {
- int i = 122; /* ASCII code for 'z' */
- long l = 93000000; /* distance to Sun (miles) */
- float f = 192450.88; /* someone's bottom line */
- double d = 2.0e+030; /* mass of Sun (kg.) */
-
- printf("%d\n", i); /* integer as decimal */
- printf("%x\n", i); /* integer as hex */
- printf("%ld\n", l); /* long */
- printf("%f\n", f); /* float as decimal */
- printf("%e\n", f); /* float as exponential */
- printf("%f\n", d); /* double as decimal */
- printf("%e\n", d); /* double as exponential */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-13. The SPECS.C program.
-
- Compare the following output with the printf() statements in the SPECS.C
- program:
-
- 122
- 7a
- 93000000
- 192450.875000
- 1.924509e+005
- 2000000000000000000000000000000.000000
- 2.000000e+030
-
- The first printf() statement prints the value of the int variable i, 122,
- as an ordinary decimal integer, using the now familiar %d specifier. The
- next statement prints the same value with the %x specifier, which prints
- values in hexadecimal. Next, we print the long integer value 93000000.
- Notice that this specifier, %ld, combines the %l (long) and %d (integer)
- specifiers.
-
- The SPECS.C program then prints the value of the variable f, 192450.88,
- using the %f floating-point specifier. In the next statement, we use %e to
- print the same number in exponential notation. Which is better? If the
- value represents money, the regular decimal format is more appropriate,
- but remember that both representations are slightly inaccurate because the
- original value, 192450.88, has eight places and float has a maximum
- precision of seven places. (If you want absolute accuracy, use the double
- specifier.)
-
- We print the final value, 2,000,000,000,000,000,000,000,000,000,000, two
- ways: as a double (note that you can use %f for double as well as for
- float) and as exponential notation with %e. Clearly, the latter is easier
- to read and understand.
-
- Format Specifiers and Data Types
-
- Remember, the format specifier merely controls how a value is displayed.
- The data type of the value represents how it is actually stored in the
- computer. The program FORMATS.C (Listing 3-14) displays the comedy of
- errors that can occur if you carelessly use the wrong format specifier
- with a data type. The following is the output of the program; compare it
- with the printf() statements in the program.
-
- As integer: 5
- As long integer: 8519685
- As exponential: 7.084198e-309
- As float: 0.000000
-
- The program uses four different specifiers to print the value of the int
- variable i, which we set to 5. Only the first representation, using %d, is
- correct. The other results vary widely (even from one run to another). How
- can the last three methods be so far off the mark? Consider the second
- printf() statement, in which we told QuickC to print the value of i as a
- long integer %ld. A long integer uses four bytes of memory, but this
- variable, as an int type, uses only two. When you specify a long integer,
- QuickC takes four bytes starting at the address of i and converts them
- into a long integer. Two of these bytes, however, have nothing to do with
- the variable i. You can see how similar problems arise when we try to
- interpret an integer variable as a float. All of this demonstrates that
- the format specifier must be compatible with the data type being handled.
- Table 3-1 correlates the most commonly encountered specifiers and data
- types.
-
- ──────────────────────────────────────────────────────────────────────────
- /* formats.c -- shows what happens when format */
- /* doesn't match data type */
-
- main()
- {
- int i = 5;
- printf("As integer: %d\n", i);
- printf("As long integer: %ld\n", i);
- printf("As exponential: %e\n", i);
- printf("As float: %f\n", i);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-14. The FORMATS.C program.
-
- Table 3-1. Compatibility of Specifiers and Data Types
- Specifier Types
- ──────────────────────────────────────────────────────────────────────────
- %d int (signed or unsigned); char (ASCII value)
- %ld long
- %f float or double (decimal format)
- %e float or double (exponential format)
- %c char (as character)
- ──────────────────────────────────────────────────────────────────────────
-
- Field Width Specifiers
-
- We can also improve the appearance of printf() output by controlling how
- many decimal places are printed and how the number is aligned in the
- output field. To do this, C lets us precede the format specifier with a
- "field specifier." The field specifier takes the following form:
-
- <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:
-
- 123.456001────────────────────────────────────12.6f (field specifier)
- 123.4560────────────────────────────────────────────────────────────8.4f
- 123.456────────────────────────────────────────────────────────────8.3f
- 123.46────────────────────────────────────────────────────────────8.2f
-
- In the first printf() statement, the field specifier %12.6f sets up a
- 12-character-wide field, 6 characters of which are decimal places. Because
- the variable has only 10 characters to be printed (9 digits and a decimal
- point), printf() indents the number two spaces. By default, numbers are
- right-justified (printed starting in the rightmost position of the
- specified field width). To print numbers that start at the left side of
- the field (left-justified), put a minus sign in front of the field width
- specifier, "%-4.2f".
-
- ──────────────────────────────────────────────────────────────────────────
- /* fields.c -- shows the same number with different */
- /* field widths and number of decimals */
-
- main()
- {
- float f = 123.4560;
-
- printf("%12.6f\n", f);
- printf("%8.4f\n", f);
- printf("%8.3f\n", f);
- printf("%8.2f\n", f);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-15. The FIELDS.C program.
-
- Note also in the first printf() statement that we asked for six decimal
- places, even though the variable number contained only the first four
- places. Although printf() prints these extra places, they add nothing to
- the precision of the number, and, in fact, give a misleading impression of
- precision. You should specify decimal places only to the expected
- precision of the value. For example, if you know that a value will range
- between 0 and 9999 with decimal places, you might specify %4.3f because
- the value can have as many as four places to the left of the decimal
- point, and a float has only seven places of precision. Thus, a total of
- seven places (4.3) displays an accurate value. Specifying %8.6 for this
- example would give a false impression of precision.
-
- In the second statement, the specifier establishes a field width of 8
- (with 4 decimal places). The third statement specifies the same field
- width of 8, but with only 3 decimal places. Notice that the variable's
- fourth decimal place, the zero, is dropped, and that the number is
- indented one space because the variable has only 7 characters. The last
- statement specifies the same field width of 8, but with only 2 decimal
- places. The printf() function not only drops the third decimal place, it
- also rounds up the second decimal place to 6. Also, because the number has
- one fewer digit to fit in the 8-character field, printf() indents the
- number another space.
-
-
- Arithmetic Operators
-
- Like most languages, C offers a complete set of arithmetic operators: +
- (addition), - (subtraction), * (multiplication), and / (division). C also
- provides a fifth operator that is not quite as common in other languages──
- %, the remainder operator, sometimes called the "modulus" operator. This
- operator returns only the remainder of a division operation. For example,
- 5 % 2 is 1 (5 divided by 2 has a remainder of 1), and 9 % 3 is 0 (9
- divided by 3 has no remainder).
-
- The modulus operator has many uses: You can use it for creating counters
- that cycle within counters or for resetting variables such as line counts
- by checking for a remainder of zero (if line_cnt % page_length = 0, then
- you know that you must start a new page).
-
- Operators are used with values to form expressions that yield new values.
- Below are some examples:
-
- 10 * 5─────────────────────────────────────────────Multiply two literals
- a / 5─────────────────────────────────────────────Divide value of a by 5
- count + 1────────────────────────────────────────Add 1 to value of count
- (a * 80) + b──────────────Multiply value of a by 80, then add value of b
-
- In a program, you combine expressions with other elements to form
- statements. The MATH.C program (Listing 3-16 on the following page)
- contains statements that use expressions involving arithmetic operators.
-
- ──────────────────────────────────────────────────────────────────────────
- /* math.c -- shows arithmetic and */
- /* precedence via expressions */
-
- main()
- {
- int a = 10, b = 4, c = 2;
-
- /* simple arithmetic expressions */
- printf("99 + 2 = %d\n", 99 + 2); /* ints */
- printf("5 - 12 = %d\n", 5 - 12);
- printf("7.25 + 3.5 = %f\n", 7.25 + 3.5);
- /* floats */
-
- /* compare precedence on these */
- printf("20 * 20 + 40 = %d\n", 20 * 20 + 40);
- printf("20 * (20 + 40) = %d\n", 20 * (20 + 40));
- printf("a * a - c + b = %d\n", a * a - c + b);
- printf("a * (a - (c + b)) = %d\n",
- a * (a - (c + b)));
-
- /* compare integer and float division */
-
- printf("Integers: 5 / 2 = %d\n", 5 / 2);
- printf("Floats: 5.0 / 2.0 = %f\n", 5.0 / 2.0);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-16. The MATH.C program.
-
- Each printf() statement prints the expression and then its value, as
- follows:
-
- 99 + 2 = 101
- 5 - 12 = -7
- 7.25 + 3.5 = 10.750000
- 20 * 20 + 40 = 440
- 20 * (20 + 40) = 1200
- a * a - c + b = 102
- a * (a - (c + b)) = 40
- Integers: 5 / 2 = 2
- Floats: 5.0 / 2.0 = 2.500000
-
- The first three statements simply add and subtract literal numbers and
- print the results. Notice in the third statement that when QuickC sees a
- number with a decimal point, it assumes a float type and prints the answer
- accordingly (10.750000).
-
- Operator Precedence
-
- The next set of statements in MATH.C illustrates "precedence," or the
- rules that determine the order in which operators are applied. Generally,
- QuickC performs multiplication and division first, then addition and
- subtraction. When operators have equal precedence (such as division and
- multiplication), QuickC performs them from left to right. The following
- QuickC help screen, Figure 3-8, lists all the operators in the language
- (including many covered in later chapters) and arranges them in groups
- from highest to lowest precedence.
-
- Thus, in Listing 3-16 the first printf() statement in the second group of
- statements multiplies 20 by 20, then adds 40, resulting in 440. However,
- you can use parentheses to impose a different order of precedence, as
- shown in the next statement. To evaluate the expression 20 * (20 + 40),
- QuickC performs the addition first (resulting in 60) and then multiplies
- 20 by 60 to produce a value of 1200.
-
- The next two statements use combinations of variables. As an exercise,
- perform the calculations on paper before you run the program. Remember to
- observe the rules of precedence. Did your answers agree with QuickC's?
-
- The final two statements in MATH.C illustrate a common problem for the
- unwary beginning C programmer. QuickC divides integer and floating-point
- types differently. When you specify numbers as integers, as in the first
- statement, integer division is performed. Accordingly, 5 divided by 2 is 2
- because this type of division discards any remainder. (A remainder in
- division is a fraction, and int types cannot represent fractions.)
- However, when you specify numbers with decimal points, QuickC treats them
- as float types, resulting in the expected answer of 2.5. Variables of int
- and float types are handled the same way as the literals above.
-
- The RECEIPTS.C program (Listing 3-17 on the following page) performs
- practical calculations with QuickC's math operators. Notice that we
- declare the units variable as an int type (you can't sell half a unit!)
- and the price and tax rates as float types.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 3-8 can be found on p.77 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 3-8. Operator precedence help screen.
-
- ──────────────────────────────────────────────────────────────────────────
- /* receipts.c -- calculates gross and net */
- /* receipts on sales */
-
- main()
- {
- int units = 38; /* number sold */
- float price = 149.99, /* price per item */
- rate = 0.06; /* sales tax rate */
-
- /* variables to hold calculated totals */
- float gross, tax, net;
-
- /* perform calculations */
- net = units * price;
- tax = net * rate;
- gross = net + tax;
-
- /* print results */
- printf("\tSales Report\n");
- printf("Net sales: \t%6.2f\n", net);
- printf("Tax:\t\t %5.2f\n", tax);
- printf("Gross sales:\t%6.2f\n", gross);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-17. The RECEIPTS.C program.
-
- The "calculations" section uses expressions to generate values for the
- variables net, tax, and gross. The printf() statements combine tab escape
- sequences \t and field width specifiers to align the output. Specify only
- two decimal places for money amounts. (Makes cents, doesn't it?) The
- program produces the following report:
-
- Sales Report
- Net sales: 5699.62
- Tax: 341.98
- Gross sales: 6041.60
-
- Arithmetic with Mixed Types
-
- The accuracy of a number generated by QuickC depends on its data type and
- the format in which it is printed. An additional problem arises when you
- perform arithmetic operations on literals (constants) or variables of
- different data types: For example, what happens when you divide an int by
- a float?
-
- For calculations with mixed data types, C ranks data types roughly
- according to the number of bytes of storage they require. From highest to
- lowest, they are:
-
- double 8 bytes
- float 4 bytes
- long 4 bytes
- int 2 bytes
- char 1 byte
-
- Generally, QuickC converts the lower-ranking type to the higher-ranking
- one before it performs the calculation. Thus, when QuickC divides 49 by
- 12.5, it first converts 49 to 49.0 (a float), then performs the division.
- (If QuickC chose a lower-ranking type, the calculation would lose
- precision. The above calculation, for example, would be 49 / 12 = 4 in
- integer division.) Although the long and float types both use four bytes,
- a float can contain a fractional part that would be lost when converted
- "down" to a long: Therefore, float is ranked as the "higher" type.
- Finally, QuickC converts float types to double types before it calculates
- the result.
-
- Although it's convenient that QuickC performs conversions for you, some
- real problems can occur if you assign the results of a calculation to a
- variable of an incorrect data type. The following example illustrates such
- a mistake:
-
- int sales, units = 50;
- float price = 1.99;
- sales = units * price;
- printf("Total sales are %d\n", sales);
-
- QuickC calculates price * units correctly by converting units from 50 to
- 50.0 (to make it a float), and then multiplying it by the float value of
- price (1.99). The value of the expression is now the float value of 99.50.
- So far, so good. However, we assigned this value to the variable sales,
- which we declared as an int type. As a result, the fractional part of the
- value (.50) is dropped, making the value of sales an incorrect 99.00. The
- solution to this problem is simple──consider all of the potential values
- for a variable before you declare it. In this case, you need to declare
- the variable sales as a float.
-
- QuickC can help remind you of potential problems with data type
- conversions. When you select the Compile option from the Run menu, the
- left side of the dialog box lists four levels of compiler warning messages
- (levels 0 through 3). If you select level 2 before you compile programs,
- QuickC sends a warning message for each program statement that causes a
- data type conversion. A typical message follows:
-
- warning C4051: (1 of 4)
- data conversion
-
- When you see this type of message, note the statement the cursor is on,
- examine the data types involved, and look up the meaning of warning (4051)
- in the Microsoft QuickC Programmer's Guide. There you will note that this
- is an advisory message, and QuickC issues it for perfectly legitimate
- conversions, such as the int to float conversion in our earlier division
- example.
-
- The MIXED.C program (Listing 3-18 on the following page) shows more
- examples of operations with mixed data types.
-
- ──────────────────────────────────────────────────────────────────────────
- /* mixed.c -- shows the effects of mixing */
- /* data types in an expression */
-
- main()
- {
- int i = 50, iresult;
- long l = 1000000, lresult;
- float f = 10.5, fresult;
- double d = 1000.005, dresult;
-
- fresult = i + f; /* int + float to float */
- printf("%f\n", fresult);
-
- fresult = i * f; /* int * float to float */
- printf("%f\n", fresult);
-
- lresult = l + i; /* long + int to long */
- printf("%ld\n", lresult);
-
- printf("%f\n", d * f); /* double * float */
- /* to double */
- fresult = d * f; /* assigned to a float */
- printf("%f\n", fresult); /* loses some precision */
-
- /* debugging a division problem */
-
- iresult = i / l; /* int / long to int*/
- printf("%d\n", iresult); /* whoops! loses result */
- printf("%ld\n", iresult); /* this won't fix it */
- fresult = i / l; /* store in float result */
- printf("%f\n", fresult); /* doesn't work */
- dresult = i / l; /* try a double */
- printf("%f\n", dresult); /* doesn't work */
- fresult = (float) i / l; /* try type cast */
- printf("%f\n", fresult); /* correct result */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-18. The MIXED.C program.
-
- Compare this output to the program statements:
-
- 60.500000
- 525.000000
- 1000050
- 10500.052500
- 10500.052734
- 0
- 8519680
- 0.000000
- 0.000000
- 0.000050
-
- The first set of statements adds an int and a float value and prints the
- result, which is 60.5, a float value. This shows QuickC's default type
- conversion at work. The second set of statements shows the same conversion
- with a multiplication operation. The third pair of statements adds a long
- to an int. Note that the result is correct (100,000 + 50 = 100,050), and,
- from its size, you can guess that it must be a long. QuickC converts the
- value 50 to a long before it does the calculation.
-
- Next, the program works with double and float types. When we specify d * f
- in the printf() statement, QuickC converts the float value f to a double
- and calculates a double result, which we print. (Remember, you can use the
- %f format specifier with either float or double types.) Because the answer
- requires nine places of precision, converting from float to double
- preserves the accuracy of the value.
-
- Next, we perform the same calculation, but we assign the result to a float
- value, f. Notice that the result, 10,500.052734, becomes inaccurate
- starting at the fourth decimal place. Converting from double to float can
- produce both large and subtle errors, depending on the numbers involved.
- To be safe, use a double variable to hold the result of this type of
- calculation.
-
- The last lengthy set of statements illustrates various approaches for
- dividing an int value i by a long value l. Only the last method produces
- the correct result.
-
- Assigning the result of the division to an int variable returns a 0,
- because the result is a very small decimal fraction (50 / 1,000,000), and
- integer division does not recognize remainders.
-
- In the next statement we assume that the result of the division is a
- decimal fraction and that we can store it in a float. But this doesn't
- work either. When we add more decimal places by using a double variable
- for the result, we still get a result of 0. The problem here is that when
- the two integer variables i and l are divided, the integer portion of the
- result, 0.000050, is 0. At this point, we can't retrieve the decimal
- fraction. Assigning it to a float or a double merely gives you a
- floating-point representation of 0!
-
- Type Casting
-
- C provides a solution to our division dilemma with a construction called a
- "type cast." A type cast explicitly converts a value to a specified type
- before any operations are done on that value. Consider the following
- example:
-
- int i1 = 10, i2 = 3;
- printf("%d\n", i1 / i2);
- printf("%f\n", (float) i1 / i2);
-
- In the first printf() statement, we divide the two integers and produce
- the integer result of 3. In the second printf() statement, we add (float)
- before i1. This is the type cast: It converts the value of i1 to a float.
- Because a type cast has a higher precedence than the arithmetic operators,
- it converts i1 to a float before the division operation. Now the division
- operation contains a float and an int! QuickC's default type conversion
- then converts i2 to a float as well, and the result is the float value
- 3.33333. If you look at the last two statements of the MIXED.C program,
- you can see we used a type cast in the same way, with the correct result
- of 0.000050.
-
- Type casts are useful for handling variables of lower-ranking data types
- (int, for example) that must occasionally be used in calculations to
- produce a result of a "higher" type (such as float). It is more efficient
- in terms of both storage and processing time to declare such variables as
- the lower type and to use type casts when necessary. Later, you will find
- type casts valuable when you must convert values to a specific type.
-
-
- Getting Input with scanf()
-
- In order to write programs that have real-world utility, we must first
- understand how a C program gets input from the user. The all-purpose C
- function for getting input and storing it in a variable is called scanf().
- (Like printf(), scanf() is a "built-in" QuickC core library function.)
- Figure 3-9 shows how it works.
-
- Let's assume we have a program with a declared integer variable named
- acct_no. When the scanf() statement executes, the program waits for input
- from the user. After the user types the number and a carriage return, the
- input is stored in the variable acct_no, as if it had been assigned by an
- assignment statement. Notice that the acct_no variable in the scanf()
- argument list is preceded by an ampersand (&). Do you remember when we
- placed ampersands in front of variable names in the VARADDRS.C program
- (Listing 3-4 on p. 57) to retrieve the storage addresses of the
- variables? The scanf() function requires as its second argument an address
- at which it can store the input. The & returns the address of the
- following variable. If you omit the address operator from the front of the
- variable name, the value of the variable is interpreted as though it were
- an address, and the input is stored at that address. This can produce
- frightful results if it overwrites information that your program needs!
-
- Format specifier for
- type of value wanted
- │
- ┌┴┐
- scanf ("%d", &acct_no);
- │└────┬─┘
- │ │
- "address of" Variable name
- └─────────────┬──────────────┘
- │
- Variable to store input in
-
- Figure 3-9. Parts of a scanf() statement.
-
- The first argument in the scanf() statement in Figure 3-9 is "%d". This
- looks and works like the format specifiers we used with printf()──it
- specifies the type of the value that the program expects. As with
- printf(), the "%d" specifies an integer. You can also use most of the
- other specifiers you used with printf(). For example:
-
- scanf("%f", &deposit);
-
- gets a value for the float variable deposit.
-
- Notice that scanf(), by itself, does not print a prompt for the user; it
- merely presents the user with a blinking cursor. Therefore, you should
- precede a scanf() statement with a printf() statement that tells the user
- what information to supply. In the example above, we might precede the
- scanf() statement with:
-
- printf("How much is your deposit?");
-
- The cursor now appears following a space after the prompt. You don't need
- to include a newline character: The cursor will move to the next line when
- the user presses Enter after typing the input.
-
- You can also use scanf() to get values for more than one variable at a
- time:
-
- printf("What is your age and weight?");
- scanf("%d %d", &age &weight);
-
- In this example, the user types an age, a space (to separate the values),
- and then a weight. Note that the user can substitute an Enter or a Tab for
- the space.
-
- The CONVERT.C program (Listing 3-19) uses scanf() to prompt a user for a
- temperature in Fahrenheit and then converts the temperature to Centigrade.
-
- ──────────────────────────────────────────────────────────────────────────
- /* convert.c -- converts Fahrenheit temperature */
- /* to Centigrade; gets value from user */
-
- main()
- {
- float ftemp, ctemp;
-
- printf("What is the temperature in Fahrenheit? ");
- scanf("%f", &ftemp);
- ctemp = (ftemp - 32.0) * 5 / 9.0;
-
- printf("The temperature in Centigrade is %5.2f", ctemp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-19. The CONVERT.C program.
-
- A sample user dialog with CONVERT.C follows:
-
- What is the temperature in Fahrenheit? 87
- The temperature in Centigrade is 30.56
-
- We print the prompt with a printf() statement, then use a scanf()
- statement with a floating-point specifier %f to get the input value for
- the float variable ftemp.
-
- The AVGTEMP.C program (Listing 3-20) averages the daily high temperatures
- for a week. When you run the program, it prompts for the high temperature
- for each day of the week, beginning with Monday.
-
- ──────────────────────────────────────────────────────────────────────────
- /* avgtemp.c -- finds average temperature */
- /* for the week */
-
- main()
- {
- int t1, t2, t3, t4, t5, t6, t7;
- float avg;
-
- printf("Enter the high temperature for:\n");
- printf("Monday: ");
- scanf("%d", &t1);
- printf("Tuesday: ");
- scanf("%d", &t2);
- printf("Wednesday: ");
- scanf("%d", &t3);
- printf("Thursday: ");
- scanf("%d", &t4);
- printf("Friday: ");
- scanf("%d", &t5);
- printf("Saturday: ");
- scanf("%d", &t6);
- printf("Sunday: ");
- scanf("%d", &t7);
-
- /* calculate and display average */
- avg = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7.0;
- /* divide by 7.0 to ensure float result */
- printf("The average high temperature for");
- printf(" this week was %5.2f degrees.\n", avg);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-20. The AVGTEMP.C program.
-
- The int variables t1 through t7 store the daily high temperatures, which
- are obtained by a series of scanf() statements. The program then
- calculates an average and prints it out. A sample dialog with this program
- might look as follows:
-
- Enter the high temperature for:
- Monday: 82
- Tuesday: 91
- Wednesday: 97
- Thursday: 104
- Friday: 95
- Saturday: 88
- Sunday: 78
- The average high temperature for this week was 90.71 degrees.
-
- Note: It is important to note that scanf() does not check to make certain
- that the input is compatible with the data type of the variable in which
- it is stored.
-
-
- Shortcut Assignments, Increments, and Decrements
-
- Now that you know how to assign a value to a variable with the assignment
- operator = and how to use arithmetic operators to calculate new values, we
- can show you a few tricks and shortcuts. In the course of a program, it is
- often useful to add a value to a variable repeatedly or to subtract a
- value from a variable repeatedly. For example, a program that counts lines
- needs to add one to a variable (such as total_lines) each time it counts a
- new line. We could do this as follows:
-
- total_lines = total_lines + 1;
-
- That's the way most languages do it. However, because changing the value
- of a variable is such a common occurrence in programming, C provides
- special, concise "arithmetic assignment operators" for the purpose.
-
- Arithmetic Assignment Operators
-
- The arithmetic operators are the +, -, *, /, and %, and the assignment
- operator is the =. The arithmetic assignment operator, as the name
- suggests, is a combination of an arithmetic operator and the assignment
- operator: for example, +=. When a statement executes, QuickC performs the
- specified arithmetic on the variable's value and then assigns the result
- of the calculation to the variable as its new value. Using an arithmetic
- assignment operator, we can write a shorter version of the statement that
- increases the value of total_lines by one:
-
- total_lines += 1;
-
- Below are more examples that use arithmetic assignment operators:
-
- count -= 1;───────────────────────────Subtract 1 from the value of count
- fare += 0.75;──────────────────────────────────Add 0.75 to value of fare
- value *= 10;────────────────────────────────────────Multiply value by 10
-
- You can use any arithmetic operator in an arithmetic assignment operation.
- Table 3-2 lists the five possible arithmetic assignment operators. The
- addition and subtraction assignment operators are the most commonly used.
-
- The OPEQUAL.C program (Listing 3-21) demonstrates the use of arithmetic
- assignment statements. The printf() statements print several arithmetic
- assignment expressions and their results.
-
- Be sure that when you read the printf() statements in the program you can
- correctly predict the following output:
-
- Starting values: m = 10 n = 5
- m += 2 makes m 12
- m -= n makes m 7
- m *= 2 makes m 14
- m = m + 1 makes m 15
- m += 1 makes m 16
-
- Table 3-2. Arithmetic Assignment Operators
- Operator Meaning
- ──────────────────────────────────────────────────────────────────────────
- += Add to value and assign
- -= Subtract from value and assign
- *= Multiply by value and assign
- /= Divide by value and assign
- %= Get remainder from division and assign
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* opequal.c -- shows combination math/assignment */
- /* operators and increment operators */
- main()
- {
- int m = 10, n = 5;
- printf("Starting values: m = %d n = %d\n",
- m, n);
-
- /* combination of arithmetic and assignment */
- printf("m += 2 makes m %d\n", m += 2);
- printf("m -= n makes m %d\n", m -= n);
- printf("m *= 2 makes m %d\n", m *= 2);
-
- /* two ways to increment m */
- printf("m = m + 1 makes m %d\n",
- m = m + 1);
- printf("m += 1 makes m %d\n",
- m += 1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-21. The OPEQUAL.C program.
-
- Increment and Decrement Operators
-
- As the last program demonstrated, both m = m + 1 and m += 1 added one to
- the value of m. If you've done any programming, you know how frequently
- the value of a variable must be increased or decreased by one. This is
- especially true when you create a "counter" variable that keeps track of
- the number of times a statement in a loop executes. C provides an
- ultra-concise operator, the increment operator ++, to add one to the value
- of a variable. Similarly, --, the decrement operator, subtracts one from
- the value of a variable. Consider the following examples:
-
- count++ ;────────────────────────────────────────Add 1 to value of count
- index-- ;─────────────────────────────────Subtract 1 from value of index
-
- Note that these increment and decrement operators are really arithmetic
- assignment statements. They add (or subtract) one and assign the resulting
- value to the variable.
-
- count++;──────────────────is equivalent to───────────────────count += 1;
- index--;──────────────────is equivalent to───────────────────index -= 1;
-
- (Most programmers do not use a space between the increment [or decrement]
- operator and the variable name. However, in C it is perfectly legal to use
- intervening spaces, as in count + +.) INCDEC.C (Listing 3-22) shows how
- the increment and decrement operators change the value of a variable.
- Compare the program statements to the following output:
-
- a is 10
- ++a is 11
- --a sets a back to 10
-
- ──────────────────────────────────────────────────────────────────────────
- /* incdec.c -- shows effect of */
- /* increments and decrements */
-
- main()
- {
- int a = 10;
-
- printf("a is %d\n", a);
- printf("++a is %d\n", ++a);
- printf("--a sets a back to %d\n", --a);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-22. The INCDEC.C program.
-
- Pre-increment vs Post-increment
-
- In the INCDEC.C program we put the increment or decrement operator in
- front of the variable name. However, you also can use it after the
- variable name. In either case the variable is incremented or decremented;
- but there is one important difference. When you use the operator in front
- of a variable name, the incrementing or decrementing is done immediately.
- When you use the operator after the variable name, the incrementing or
- decrementing is not done until the next use of the variable. The PREPOST.C
- program (Listing 3-23) shows how this works. The output of the program
- illustrates how incrementing is delayed:
-
- b is 100
- b++ is still 100
- but after it's used, b is incremented to 101
-
- ++b, on the other hand, is immediately 102
-
- Notice what happens to b when we use the increment operator after it,
- rather than before it. The first printf() statement with the value b++
- prints the original value of 100, showing that it has not yet been
- incremented. The next printf() statement, however, prints 101.
-
- As a practical matter, the distinction between pre-increments and
- post-increments (or decrements) is usually important only when the
- variable is incremented or decremented while it is being used with other
- operators in a single expression. For example, suppose you want to
- increment counter and also assign it to total in the same statement.
- Assuming counter is currently 10:
-
- total = counter++;
-
- assigns 10 to total, because counter is assigned to total but not
- incremented until the next time it is used. On the other hand:
-
- total = ++counter;
-
- assigns 11 to total, because counter is incremented immediately and then
- assigned.
-
- ──────────────────────────────────────────────────────────────────────────
- /* prepost.c -- shows effect of pre- */
- /* and post-increments */
- /* and decrements */
-
- main()
- {
- int b = 100;
-
- printf("b is %d\n", b);
- printf("b++ is still %d\n", b++);
- printf("but after it's used, ");
- printf("b is incremented to %d\n\n", b);
-
- printf("++b, on the other hand, ");
- printf("is immediately %d\n", ++b);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-23. The PREPOST.C program.
-
-
- Relational Operators
-
- If you have some programming experience, you know that most programs must
- make decisions based on the values of certain variables. Variables are
- tested or compared, and the result of the test determines which program
- statement will execute next. The next two chapters cover the variety of
- "control structures" that C provides for this purpose. Let's build the
- foundation for those discussions by looking at the operators that C uses
- for testing or comparing values.
-
- A relational operator compares two values, which can be variables, literal
- numbers, or whole expressions. A combination of relational operators and
- values is called a relational expression. An example is count > 10, which
- translates as "is the value of count greater than 10?" The > in this
- expression is the "greater than" relational operator. The expression is
- true or false depending on the current value of the variable count. If
- count is 8, for example, the expression is false.
-
- Table 3-3 illustrates the ways we can compare two values, a and b. In
- reality, the values can be constants, variables, or expressions──anything
- that expresses a numeric value. (Remember from our discussion of ASCII
- that characters, too, are essentially numeric values.)
-
- We described the value of a relational expression as being "true" or
- "false." These terms are useful ways for us to follow the logic of a
- program, but the actual value of a relational statement, like everything
- else in the computer, is numeric. When a statement is true, its value is
- 1; when a statement is false, its value is 0. On the following page, the
- RELATION.C program (Listing 3-24) uses printf() statements to show the
- values of some statements that use relational operators.
-
- The program generates the following output:
-
- a = 5 b = 3 c = 4
- Expression a > b has a value of 1
- Expression a == c has a value of 0
- Expression a > (b + c) has a value of 0
-
- Because a is 5 and b is 3, the expression a > b has a value of 1, or true.
- Because c is 4, a == c has a value of 0, or false. The third expression
- combines relational and arithmetic operators: It first calculates the
- quantity (b + c), and then it compares the value to a.
-
- Table 3-3. Relational Operators
- Expression Meaning
- ──────────────────────────────────────────────────────────────────────────
- a < b Is a less than b?
- a > b Is a greater than b?
- a == b Is a equal to b?
- a != b Is a not equal to b?
- a <= b Is a less than or equal to b?
- a >= b Is a greater than or equal to b?
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* relation.c -- shows effect of */
- /* relational operators */
-
- main()
- {
- int a = 5, b = 3, c = 4;
- printf("a = %d\t b = %d\t c = %d\n", a, b, c);
-
- printf("Expression a > b has a value of %d\n",
- a > b);
- printf("Expression a == c has a value of %d\n",
- a == c);
- printf("Expression a > (b + c) has a value of %d\n",
- a > (b + c));
- printf("Expression a = b has a value of %d\n",
- a = b); /* what happened here? */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-24. The RELATION.C program.
-
- Relational == vs Assignment =
-
- Here's a pitfall to watch out for: In C, a single equal sign = is the
- assignment operator, but a double equal sign == is the relational "equals"
- operator. In some languages (such as BASIC), a single operator, =, serves
- both purposes. So, if you are familiar with the BASIC usage, you might
- make errors with these operators until you get used to the difference. A
- common symptom of this error is a test that always appears to be either
- true or false. For example, if you type the assignment count = 10 instead
- of the relational count == 10 and then use the result in a control
- structure (such as a loop or if statement), QuickC always sees the result
- of the test as "true." Why? Because although relational expressions return
- a value of 1 for "true," QuickC considers any nonzero value to be "true"
- in this type of test. Because the sample statement with = is actually an
- assignment, its value is 10 (the number assigned), which QuickC interprets
- as "true" during a relational test.
-
- ──────────────────────────────────────────────────────────────────────────
- Assignment and "Equals" Relation
- The following table lets you compare the assignment and relational
- "equals" operators in C to those in other common languages:
-
- Language Assignment Relation
- ──────────────────────────────────────────────────────────────────────
- C
- =
- ==
- BASIC = =
- Pascal := =
- FORTRAN = .EQ.
- Logo make =
- COBOL MOVE EQUAL TO
- ──────────────────────────────────────────────────────────────────────
-
- ────────────────────────────────────────────────────────────────────────
-
- Precedence of Relational Operators
-
- In RELATION.C, we used parentheses in the expression a > (b + c). If you
- check QuickC's operator precedence help screen (Figure 3-8 on p. 77), you
- will see that relational operators have a lower precedence than arithmetic
- operators. Therefore, even if you don't use parentheses, b + c is
- calculated first, and only then is the result compared to a. Nevertheless,
- it is a good programming practice to use parentheses to visually clarify
- an expression.
-
-
- Logical Operators
-
- Sometimes it is necessary or useful to test for more than one thing in the
- same expression or statement. For example, you might want to test to see
- if either the temperature or pressure in a boiler has exceeded the safety
- limit. Let's assume the test for temperature is (temp < 900) and the test
- for pressure is (pressure < 5000). We can combine the two tests as
- follows:
-
- (temp < 900) && (pressure < 5000)
-
- The && is called the AND logical operator. It compares the results of two
- relational values and returns a value of true (1) only if both are true.
- QuickC first makes the temp test, then it makes the pressure test (testing
- is from left to right). Then the && operator checks to see if both tests
- were true.
-
- The OR logical operator, ||, works like the AND operator, except that it
- returns a value of true (1) if either or both of the tests are true. Thus,
- the statement
-
- (ch == 'q') || (turn > last_turn)
-
- is true if either the current value of ch is `q' or the current value of
- turn is greater than that of last_turn, or both. You could use this
- statement to check if a game is over.
-
- Using two relational operators, && and ||, and two possible results of a
- test (true and false), there are four possible results for a relational
- statement involving two tests. The TRUTH.C program (Listing 3-25 on the
- following page) prints these out by making comparisons using ones and
- zeros that represent the result of already completed relational tests.
- Recall that QuickC regards a value of 1 to be "true" and a value of 0 to
- be "false." Thus, 1 AND 1 is 1 means "True and true is true."
-
- 1 AND 1 is 1
- 1 AND 0 is 0
- 0 AND 0 is 0
- 1 OR 1 is 1
- 1 OR 0 is 1
- 0 OR 0 is 0
-
- ──────────────────────────────────────────────────────────────────────────
- /* truth.c -- shows logical operators */
- main()
- {
- printf("1 AND 1 is %d\n", 1 && 1);
- printf("1 AND 0 is %d\n", 1 && 0);
- printf("0 AND 0 is %d\n", 0 && 0);
- printf("1 OR 1 is %d\n", 1 || 1);
- printf("1 OR 0 is %d\n", 1 || 0);
- printf("0 OR 0 is %d\n", 0 || 0);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 3-25. The TRUTH.C program.
-
- Once again, if you check the QuickC operator precedence help screen, you
- will notice that logical operators, such as && and ||, have a lower
- precedence than the relational operators, such as < or ==. Therefore, we
- didn't need parentheses around the relational expressions in our examples
- because QuickC evaluated them before it checked the logical operators.
- Again, we used parentheses because they make these complex expressions
- easier to read.
-
- The last operator we need to discuss is the !, or "not" operator. Its
- function is simple enough──it reverses the truth value of a relational
- expression. For example, if a is 10, a > 5 is true, but !(a > 5) is false.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 4 Repetition and Looping
-
- In all of our programs so far, QuickC has executed the program statements
- sequentially, from the first statement to the end of the program. However,
- most of a program's important work involves controlled repetition, in
- which a group of statements repeatedly does a particular job until the
- work is done. For example, consider the data-entry routine of a database
- program. This group of statements (used to receive, validate, and store
- data) must be repeated as long as the user wants to enter new data
- records. This set of repeating statements is called a loop because it is
- executed as though the statements were arranged in a circle. However, when
- the user wants to stop entering data, the program must be able to
- recognize a "quit" command and stop repeating the data-entry statements.
-
- As you study C, you will find many other examples of the need for
- controlled repetition. For example, a program that retrieves data from a
- file must repeatedly read and process data items until it reaches the end
- of the file. If you program in another language, you probably use loops
- regularly to initialize and access elements of an array or a set of
- variables.
-
- C uses three types of loops: the for loop, the while loop, and the do
- loop. Although these loops are fundamentally similar, they let you control
- the looping action in different ways to suit different needs. This chapter
- focuses on how to use these three types of loops and some of their common
- variations.
-
-
- The for Loop
-
- The for loop repeats a group of program statements as long as a specified
- condition is true. Generally, you use it to specify a fixed number of
- repetitions: for example, processing the accounts for each month of the
- year.
-
- The anatomy of a for loop is as follows:
-
- for (start; condition; update)
- {
- statements;
- }
-
- In this generalized for loop, start is one or more statements that
- initialize the variables used by the loop; condition is a relational
- expression that is tested to see whether the loop should continue to run;
- and update is one or more statements that change the values of variables
- in the loop. The group of statements between the braces that follow the
- for line is called the "body" of the loop. These statements execute as
- long as the condition in the parentheses is true. (The body can also
- consist of only one statement, in which case the braces are optional. We
- tend to use braces for even a single statement, however, because they make
- the body of the loop easier to distinguish.)
-
- The FORLOOP.C program (Listing 4-1) uses a for loop to count from 1 to
- 10. After we declare the variable i, we begin the loop structure with the
- keyword for. The parentheses that follow the for contain the control
- statements for the loop. Note that semicolons separate the control
- statements.
-
- ──────────────────────────────────────────────────────────────────────────
- /* forloop.c -- a simple for loop that */
- /* counts to ten */
-
- main()
- {
- int i;
- for (i = 1; i <= 10; i++)
- {
- printf("%d\n", i); /* body of loop */
- }
- printf("All done!\n"); /* executed when i > 10 */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-1. The FORLOOP.C program.
-
- The start statement establishes the variable i as the loop's control
- variable. This is the variable whose value is tested to determine when the
- loop will stop running. (Many people use i, j, or k for loop control
- variables. This tradition owes its roots to FORTRAN. However, any legal
- name will do.)
-
- The next statement, i <= 10, is the loop's test, or condition. It
- specifies that the body of the loop execute repeatedly as long as the
- value of i is less than or equal to 10. The test condition is a relational
- statement that compares the loop control variable to an assigned value and
- returns a value of 1 (true) or 0 (false).
-
- The last statement in the for loop parentheses is i++. This update
- statement changes the value of the loop control variable each time the
- loop body executes. Here we use the ++ increment operator to increase i by
- one each time it executes, and, in fact, most for loops use update
- statements that either increment or decrement the control value by one.
- Using values other than one, however, is almost as easy: The statement
- value += 10, for example, adds 10 to value each time it executes. You can
- also use multiplication or division rather than addition or subtraction.
-
- Let's step through FORLOOP.C one statement at a time to see how it works:
-
- ■ Set i to 1.
-
- ■ Check i to see if it is less than or equal to 10.
-
- ■ Because the result of this test is true, execute the body of the loop.
- (The body consists of a printf() statement that prints the value of i.)
-
- ■ Execute the update statement, i++. (Set i to i + 1, or 2.)
-
- ■ Check the test statement again to see if i is still less than or equal
- to 10. If it is, execute the body of the loop again. Continue the cycle
- until the test condition is false (when the value of i increases to
- 11).
-
- Figure 4-1 on the following page shows this program as a flowchart. You
- can follow the arrows to trace the flow of execution.
-
- ──────────────────────────────────────────────────────────────────────────
- Choosing a Control Variable
- If you are used to writing loops in BASIC, remember that with C, you must
- declare the loop control variable before you use it in the loop. Select a
- data type for the control variable that can accommodate the full range of
- values the variable will hold when the loop is run.
-
- For example, a loop that will run 50,000 times requires a control variable
- of type unsigned int because a signed int value cannot exceed 32,767.
- ──────────────────────────────────────────────────────────────────────────
-
- ┌────────────────┐
- │ Initialize │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- │ i = 1 │ ┌─────────▒─────────────────
- └───────┼────────┘ │° for (i = 1; i <= 10; i++°
- │ │° ▒ ▒ °
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒{▒▒▒▒▒▒▒▒▒▒ ▒ °
- /\ ▒ │° printf("%d\n", i);▒ °
- / \ ▒▒▒▒▒▒▒ │° } ▒ ▒ °
- / TEST \ No │° ▒ ▒ °
- ┌───/ i <= 10 \ ────── END │° ▒ ▒ °
- │ \ / │° ▒ ▒ °
- │ \ ? / │° ▒ ▒ °
- │ \ / │° ▒ ▒ °
- │ \/ │° ▒ ▒ °
- │ │ Yes └───────────────▒────────▒──
- │ ┌───────────────┐ ▒ ▒
- │Do body of loop │ ▒ ▒
- │ │ print f... │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒
- │ └───────┬────────┘ ▒
- │ │ ▒
- │ ┌───────────────┐ ▒
- │ │ Add 1 to i │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- │ └───────┬────────┘
- │ │
- └─────────┘
-
- Figure 4-1. The for loop.
-
- Why does the loop stop running? Let's look at the situation when i = 10:
- The printf() statement in the body of the loop prints the number 10. The
- update statement then increments the loop by 1 and the test statement
- executes. Because the value of i is now 11, the test fails (returns a
- value of "false"). This causes the program to skip the loop body and
- execute the next statement, which prints the message All done!
-
- for Loop Style
-
- As with other C statements, the statements within the parentheses of a for
- loop can extend to more than one line if necessary. As noted in our
- discussion of conventions, we align the braces vertically for the loop
- body, as shown in FORLOOP.C. An older style aligns the braces as follows:
-
- for (i = 1; i <= 10; i++) {
- printf("%d\n",i);
- }
-
- With this style, the braces can get lost in a long listing, making it
- difficult to find where the body of the loop begins and ends. Aligning the
- braces vertically makes them easier to spot and highlights the body of the
- loop.
-
- Also note that we indent the body of a C loop to the right of the line
- that specifies it. To indent text in the QuickC editor, simply press the
- Tab key. The default indention in QuickC is eight characters, but you can
- change this value at the Options box of the View menu. We use a tab of
- four characters in our listings.
-
- Pitfalls to Avoid in for Loops
-
- An easy mistake to make when writing for loops is to put a semicolon after
- the closing parenthesis:
-
- for (i = 1; i <= 10; i++);───────────────────────────────Semicolon added
-
- This does not cause a compiler error: In C, a semicolon by itself is a
- "null statement." Such a statement does nothing, but it counts as a legal
- statement. Using a semicolon after the parenthesis makes the null
- statement the body of the loop. Adding the semicolon to FORLOOP.C causes
- the loop to do "nothing" 10 times; the program then prints the value of i
- (which is 11 after the loop exits) and All Done!
-
- Also, always remember to put braces before and after a loop body that
- consists of more than one statement. If you do not use braces, only the
- first statement following the parentheses executes as the body of the
- loop. The remaining statements will execute only once, after the loop
- terminates. (This is another reason for adopting the practice of always
- putting braces around the statements in a loop body, even when the body
- has only one statement.)
-
- Multistatement for Loops
-
- FORLOOP.C has only one statement in the body of the loop, but most
- programs are much more complex. Let's develop a program that will print a
- table of square roots, squares, and cubes for the integers from 1 through
- 9. Because this program must calculate and print three values for each
- number, it needs several statements in the body of the for loop.
-
- Using QuickC Library Functions
-
- To write such a program, we need a means of producing the square root of a
- number. Although C does not have operators for calculating squares or
- cubes directly, we can get these values simply by multiplying a variable
- by itself two and three times respectively. To get the square root,
- however, we must call on QuickC's sqrt() function. This function returns
- the square root of any value you pass to it. For example, if i = 4, then
- sqrt(i) = 2.
-
- The square root function, sqrt(), is an example of a QuickC library
- function (sometimes called a "library routine"). We've already used
- several QuickC "core" functions, such as printf() and scanf(). Because
- these functions are part of the QuickC environment, you can use them
- without any special commands. (Appendix B lists all the built-in core
- functions.)
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- Sometimes it is convenient to break out of a loop during its execution.
- Perhaps you recognize a problem with its output, or perhaps you find
- yourself in a runaway loop──one whose test will not or cannot fail. To
- break out of a loop, press Ctrl-Break.
- ──────────────────────────────────────────────────────────────────────────
-
- However, sqrt() is not on this list. It is one of many library routines
- that are defined in the header files (often called "include files") of the
- \INCLUDE directory. One of the early tasks in learning QuickC is becoming
- familiar with its external library functions. Fortunately, QuickC makes it
- easy to explore the function library.
-
- QuickC's extensive on-line help screens let you call up a summary of any
- function to find out whether it is a core function or an external library
- function. To find out about sqrt(), select Topic from the Help menu. Next,
- select the appropriate topic, math; this produces a list of library
- functions that include the sqrt() function. When you select this function,
- a small help window appears at the top of the QuickC screen. (See Figure
- 4-2.) The first entry in this window informs you that sqrt() resides in
- both the float.h and math.h include files.
-
- QuickC also lets you browse through include files while you are working on
- a program. Simply select Include from the View menu, select the \INCLUDE
- directory from the window (if necessary), and then select the include file
- you want to view. When you finish, select Open Last File from the File
- menu, and QuickC returns you to the program you were working on.
-
- Of course, the preferred reference for all QuickC library functions is the
- Microsoft QuickC Run-Time Library Reference, one of the manuals that come
- with QuickC. It introduces the library functions by category.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-2 can be found on p.98 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-2. Library function help window.
-
- Using an Include File in a Program
-
- To use functions or other definitions from an include file in your
- program, you must specify the name of the file you want to call before the
- start of main(). For example:
-
- #include <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). In addition, before we compile the program, we need to tell QuickC
- where to find the compiled library code that corresponds to the definition
- of sqrt() in the include file math.h. We can do this by creating what is
- called a "program list." To do this, first select Set Program List from
- the File menu. In the dialog window (Figure 4-3 on the following page),
- type in the name of your program followed by the extension .mak. (Thus,
- for TABLE.C you type table.mak.) Next, select Edit Program List from the
- File menu.
-
- ──────────────────────────────────────────────────────────────────────────
- /* table.c -- prints square root, square, and cube */
- /* for the numbers 1 thru 9 */
-
- #include <math.h> /* include math functions so we */
- /* can do square root */
-
- main()
- {
- int i;
- printf("i\t sqrt(i)\tsquare(i)\tcube(i)\n\n");
- for (i = 1; i < 10; i++)
- /* beginning of body of loop */
- {
- printf("%d\t", i);
- printf("%f\t", sqrt(i));
- printf("%d\t\t", i * i);
- printf("%d\n", i * i * i);
- }
- /* end of body of loop */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-2. The TABLE.C program.
-
- The dialog window, shown in Figure 4-4, lists all the C programs in the
- current directory. Select TABLE.C from the window with the mouse or
- keyboard, and then select the Add/Remove button. Finally, select the Save
- button to save the edited program list. You are now ready to run TABLE.C.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-3 can be found on p.100 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-3. Set Program List dialog window.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-4 can be found on p.100 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-4. Edit Program List dialog window.
-
- We use the #include <math.h> statement before the definition of the main()
- function to tell QuickC to use the math.h include file in the program. A
- printf() statement then prints the table header. Because we only want to
- print the header once, we don't place this statement inside the loop!
-
- Next comes the for loop. The loop specifications establish the test
- condition as i < 10 and the update as increments of 1.
-
- The body of the loop consists of four printf() statements: The first
- prints the value of i; the next three print, in order, the square root,
- the square, and the cube for each value. The program results in the
- following neatly formatted table:
-
- i sqrt(i) square(i) cube(i)
- 1 1.000000 1 1
- 2 1.414214 4 8
- 3 1.732051 9 27
- 4 2.000000 16 64
- 5 2.236068 25 125
- 6 2.449490 36 216
- 7 2.645751 49 343
- 8 2.828427 64 512
- 9 3.000000 81 729
-
- Multiple Initializations and Calculations in for Loops
-
- An almost universal rule in C states that anywhere you can put a single C
- statement, you can put multiple statements. For example, in a for loop,
- you can initialize two variables in the first part of the loop
- specification, as follows:
-
- for (count = 1, total = 0; count < values; count++)
- {
- total += count;
- }
-
- Here we initialize the for loop by setting the loop control variable count
- to 1. At the same time, we set the variable total to zero. This loop adds
- all the integers between 1 and the number specified in values. Note that a
- comma separates the two statements in the initialization: Semicolons
- separate the three parts of the loop specification (start or
- initialization, test, and update); however, commas separate multiple
- statements within each part.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- If you want QuickC to search for a file in the current directory instead
- of the default include directory, enclose the filename in quotes: #include
- "graph.h". If you want QuickC to search another directory, specify the
- full pathname: #include "c:\qc\mydefs\defs.h".
- ──────────────────────────────────────────────────────────────────────────
-
- You can also use multiple calculations in the update portion of the loop
- specification. For example, we can rewrite the above loop as follows:
-
- for (count = 1, total = 0; count < values;
- total += count, count++)
- {;}
-
- Here we moved the statement that added each new value of count to total
- out of the loop body and put it in the update part of the loop
- specification. However, a loop must have a body to be legal, so we added a
- single semicolon (a null statement) as the loop body. (The null statement
- is somewhat dangerous because you might accidentally delete the stray
- semicolon. We try to avoid this by indenting the semicolon to the loop
- body position. We also enclose the semicolon in braces. The braces are
- unnecessary, but they help to emphasize the importance of the semicolon in
- the loop structure.)
-
- The use of multiple statements and null bodies in loops is a matter of
- programming style. Many C programmers try to be as concise as possible, so
- you will often encounter these usages in C code. We present these variants
- to acquaint you with common C programming practices; you gain no
- performance advantage by doing all initializations and calculations within
- the loop specification.
-
- The INFLATE.C program (Listing 4-3) is another example that uses multiple
- initializations and calculations. At first glance, you might think that
- the braces in the for loop have been forgotten or misplaced.
-
- ──────────────────────────────────────────────────────────────────────────
- /* inflate.c -- shows multiple initialization */
- /* and calculations in for loop */
-
- main()
- {
- int year;
- float value, rate;
- printf("What do you think the inflation rate will be?");
- scanf("%f", &rate);
- printf("If the dollar is worth 100 cents in 1987\n");
- printf("and the inflation rate is %2.2f, then:\n", rate);
-
- for (year = 1988, value = 1.0; year <= 1999;
- value *= (1.0 - rate),
- printf("in %d the dollar will be worth", year),
- printf(" %2.0f cents\n", value * 100), ++ year)
- {;}
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-3. The INFLATE.C program.
-
- The program asks you to estimate the average inflation rate for the next
- decade or so. (We're sure your guess is as good as ours!) If you enter
- .06, the program generates the following:
-
- What do you think the inflation rate will be? .06
- If the dollar is worth 100 cents in 1987
- and the inflation rate is 0.06, then:
- in 1988 the dollar will be worth 94 cents
- in 1989 the dollar will be worth 88 cents
- in 1990 the dollar will be worth 83 cents
- in 1991 the dollar will be worth 78 cents
- in 1992 the dollar will be worth 73 cents
- in 1993 the dollar will be worth 69 cents
- in 1994 the dollar will be worth 65 cents
- in 1995 the dollar will be worth 61 cents
- in 1996 the dollar will be worth 57 cents
- in 1997 the dollar will be worth 54 cents
- in 1998 the dollar will be worth 51 cents
- in 1999 the dollar will be worth 48 cents
-
- The program uses scanf() to obtain the estimated inflation rate. Then it
- prints the introduction to the table and enters a for loop. Because the
- table prints yearly values, we call the loop control variable year. (Note,
- by the way, that control variables need not start at 0 or 1.) The
- initialization part of the loop also sets value to 1.0. (In other words,
- the dollar starts at its full value.) The test part of the loop causes the
- printing of values for the years 1988 through 1999.
-
- The update part of the loop specification does the work of this loop──the
- loop has a null body. Each year the current value is multiplied by 1.0 -
- rate to show the effects of inflation. The arithmetic assignment operator,
- *=, causes this new amount to become the new value. The printf()
- statements print the amount in cents by first multiplying value by 100;
- the format specifier %2.0f rounds it off to whole cents. Finally, ++year
- increments year, and the loop is ready for another pass.
-
- ──────────────────────────────────────────────────────────────────────────
- A for Loop Using Characters
- Because the PC's ASCII character set is merely a set of integer values
- from 0 to 255, a for loop can process characters as easily as it does
- ordinary integers. For example, the int control variable can be
- initialized by setting it to the character value `a'. There is no problem
- with this, because `a' is simply the integer value 97. Then, by specifying
- a loop test condition such as i <= `m', you determine that the body of the
- loop will be repeated 13 times, once for each letter in the first half of
- the alphabet.
- ──────────────────────────────────────────────────────────────────────────
-
- Nesting for Loops
-
- Sometimes it is useful to have one of the statements in the body of a loop
- be another loop. This is called nesting loops. For example, you might
- design a program to read a disk data file that is arranged so that each
- line contains four data fields. An "outer" loop could process each line,
- and an "inner" loop could process each field. The outline for this program
- might be:
-
- open_file(name);
- for (line = 1; line <= last_line; line++)
- {
- for (field = 1; field <= 4; field++)
- {
- process_field;
- }
- }
- save_file(name);
-
- The first, or outer, loop uses the control variable line and the test line
- <= last_line to read each line of the file in turn. (In this example, we
- assume that the number of lines in the file has been previously
- determined.) The inner for loop uses the control variable field to step
- through the four fields of each line. The body of this nested loop calls
- the function process_field to do the actual reading of data. When the last
- field in the line is processed, the inner loop exits. Because we are still
- in the body of the outer loop, the outer loop continues by moving to the
- next line; then the inner loop runs again. Only when the inner loop runs
- for the last line do the nested loop structure and the save_file()
- statement execute.
-
- Our next sample program, GRAPHBOX.C (Listing 4-4 on page 106), uses
- several for loops, including a pair of nested ones, to draw a box on the
- screen using PC graphics characters.
-
- As we mentioned earlier, the IBM PC uses the ASCII values from 128 to 255
- to represent the "extended character set," which includes many shapes that
- you can use to create effective graphics. To find the appropriate
- characters for drawing a box, select the help screens for ASCII characters
- from the Help menu (or press the F1 key). The second of the two screens
- contains the extended characters. For example, with character number 201
- you can draw the upper-left corner of the box.
-
- To display these characters, you must use the QuickC core library function
- called putch(). Call the function by specifying the ASCII code of the
- desired character in parentheses. For example, to draw the corner
- character mentioned above, specify:
-
- putch(201);
-
- Using #define
-
- Our box-drawing program uses many different characters to represent the
- corners and sides of the box, plus the newline, return, and blank
- characters. Remembering the ASCII codes for all these characters is a
- difficult task, and relying on memory could lead to coding mistakes. But C
- has a feature that helps eliminate this problem.
-
- C provides a mechanism for assigning symbolic names to frequently used
- values in a program. The preprocessor directive #define lets you specify a
- name and assign a value to it, as in the following example:
-
- #define UPLEFT 201
-
- Before QuickC compiles your program, the preprocessor finds each
- occurrence of the name UPLEFT and replaces it with the number 201. You
- remember the name; QuickC remembers the number. You can also use #define
- with characters. If you use the definition #define color "green" and you
- use the statement printf(color); in your program, the preprocessor
- translates the statement into printf("green"); before QuickC compiles it.
-
- Always place #define statements before the definition of main(), and do
- not end them with a semicolon. (If you use a semicolon, the preprocessor
- will treat it as part of the value to be substituted. This often leads to
- a bug that causes a compiler error.)
-
- You can use #define to make your code more readable by substituting
- easy-to-remember names for numbers. For example, you could use NL for the
- newline character instead of 10, the newline ASCII value. Also, #define
- makes it easy to change many values in a program without having to change
- many individual statements.
-
- ──────────────────────────────────────────────────────────────────────────
- #define vs Variables
- You might ask why you should use #define when you could do the same thing
- more easily with ordinary variables. After all, you could declare int nl =
- 10; and then use putch(nl); (to simplify punctuation) and thereby avoid
- the preprocessor step. But there are two reasons why this isn't a good
- idea.
-
- First, using #define produces more efficient code than does using a
- variable. When your program uses variables, QuickC must compile extra
- machine instructions to store, change, or fetch the needed values. With
- #define, on the other hand, the preprocessor compiles the values directly
- into the compiled code: The program doesn't need any extra instructions.
- As a result, your compiled code is faster and more compact.
-
- Second, a variable should represent a quantity that is subject to change
- by the program. The ASCII value 10 for a newline character, however, is a
- constant. Using #define guarantees that the value you define cannot
- accidentally be changed while the program is running.
-
- Incidentally, the new ANSI C standard creates the keyword const to let you
- avoid #define directives. Using the new keyword, you might declare const
- int nl = 10; to define the constant nl.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* graphbox.c -- defined to use PC-specific graphics characters */
-
- #define NL 10
- #define CR 13
- #define BLANK 32
- #define UPLEFT 201
- #define UPRIGHT 187
- #define LOWLEFT 200
- #define LOWRIGHT 188
- #define LINE 205
- #define SIDE 186
-
- main()
- {
- int i, j, height, width;
-
- /* get height and width from user */
- printf("How high a box do you want? ");
- scanf("%d", &height);
- printf("How wide do you want it to be? ");
- scanf("%d", &width);
-
- /* draw top of box */
- putch(UPLEFT);
- for (i = 0; i < (width - 2); i++)
- putch(LINE);
- putch(UPRIGHT);
- putch(NL);
- putch(CR); /* go to next line */
-
- /* draw sides of box */
- for (i = 0; i < (height - 2); i++) /* outer loop */
- {
- putch(SIDE); /* left side */
- for (j = 0; j < (width - 2); j++) /* inner loop */
- {
- putch(BLANK);
- }
- putch(SIDE); /* right side */
- putch(NL);
- putch(CR); /* move to next line */
- }
-
- /* draw bottom of box */
- putch(LOWLEFT);
- for (i = 0; i < (width - 2); i++)
- putch(LINE);
- putch(LOWRIGHT);
- putch(NL);
- putch(CR); /* box is done, move cursor to new line */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-4. The GRAPHBOX.C program.
-
- To continue with the previous example, if you convert your program to run
- on a mainframe that uses a non-ASCII character set, you need only to
- change the value of NL in the #define statement to reflect the new
- character value throughout the program.
-
- When you run the GRAPHBOX.C program, it asks:
-
- How high a box do you want? 8──────────────────────Enter height in lines
- How wide do you want it to be? 20──────────────Enter width in characters
-
- Figure 4-5 shows the graphics box that this program generates on your
- screen.
-
- ╔═══════════════════╗
- ║ ║
- ║ ║
- ║ ║
- ║ ║
- ║ ║
- ║ ║
- ║ ║
- ╚═══════════════════╝
-
- Figure 4-5. Character graphics box produced by GRAPHBOX.C.
-
- The program begins with nine #define statements that name the needed
- characters and their values. The first section of main() prompts the user
- for a height and width. Then a putch() displays the character for the
- upper-left corner of the box. Next, a for loop prints a graphics
- double-line character width - 2 times. (We print two less than width
- characters to leave room for the upper-left and upper-right corner
- characters.)
-
- The third section of main() draws the sides of the box. After subtracting
- the top and bottom lines, we want to print height - 2 lines: This is
- provided for by the test statement in the next for loop. For each line,
- the program prints the SIDE character (the double bar) and then uses a
- nested for loop to print width - 2 blank characters to position the cursor
- at the right side of the box. Another SIDE character completes the line;
- then an NL and a CR move the cursor to the next line.
-
- The statements that print the bottom line are the same as those that
- printed the top line, except that they use the special characters for the
- lower-left and lower-right corners of the box.
-
-
- The while Loop
-
- C contains another loop structure, called the while loop, which takes the
- following general form:
-
- while (test)
- {
- statements;
- }
-
- Structurally, the while loop is a for loop with only the test part of the
- specification, its condition, in parentheses. You initialize loop
- variables in a statement before the while, and you update or increment the
- loop with a statement in the loop body. Thus, although the for loop
- features compactness and holds the entire loop specification in the
- parentheses, the while loop is easier to read because the parentheses
- contain only the test expression. The WHILE.C program (Listing 4-5 at the
- bottom of the page) shows a simple example.
-
- The program produces the following output:
-
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- Done!
-
- The statement int count = 1; declares and initializes the loop control
- variable. At the while statement, the condition count < 11 is tested.
- Because it is true, the body of the loop executes. The body consists of a
- printf() statement that prints the current value of count, and the
- statement count++; which increments count. The test condition is then
- checked again, and the loop continues printing numbers until count reaches
- 11. At this point the test fails, the loop terminates, and the statement
- printf("Done!\n"); executes. Figure 4-6 shows a flowchart of this while
- loop.
-
- ──────────────────────────────────────────────────────────────────────────
- /* while.c -- a simple while loop */
-
- main()
- {
- int count = 1;
-
- while (count < 11) /* loop condition */
- /* body of loop */
- {
- printf("%d\n", count);
- count++;
- }
- printf("Done!\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-5. The WHILE.C program.
-
- At this point you might ask why you need while statements if they are
- merely variant forms of for loops. The answer is simple. A for loop is
- designed to work with a specific series of values (such as numbers from 1
- to 10 or 1 to total_lines), and it usually counts up or down. A while
- loop, however, is designed to run indefinitely as long as some condition
- remains true. It also can test many kinds of conditions.
-
- For example, suppose you want to write a program that draws endlessly
- changing graphic patterns until the user presses a key to stop it. A while
- loop is ideal for this purpose when used with the QuickC library function
- kbhit(), which returns a 1 (true) if a key is pressed and a 0 (false) if
- no key is pressed. (A loop that waits for some external event to take
- place is called a "polling loop.") The main loop of your graphics program
- might appear as follows:
-
- while (!kbhit())
- {
- draw statements;
- }
-
- The draw statements create the graphics while the test part of the while
- loop specification polls the keyboard. As long as the user does not press
- a key, the kbhit() function returns a 0, or false. Notice that we use an
- !, which is the "logical not" operator, in front of kbhit(). "Not false"
- is the same as "true," so the test for the while loop is satisfied and the
- body of the loop executes as long as the user doesn't press a key. If you
- find this reverse logic difficult to understand, try translating the loop
- specification into words:
-
- while───────────────────────────────────────────────────────"As long as"
- !───────────────────────────────────────────────────────────────────"no"
- kbhit()─────────────────────────────────────────────────"key is pressed"
-
- ┌────────────────┐ ┌─────────────────────────┐
- │ Initialize │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒int count = 1 °│
- │ count = 1 │ │° °│
- └────────────────┘ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒while (count < 11) °│
- ▒ │° { °│
- /\ ▒ ▒▒▒▒printf("%d\n", count);°│
- / \ ▒▒▒▒▒▒▒ ▒│° °│
- / TEST \ No ▒▒▒▒count++; °│
- ┌───/count < 11 \ ────── END ▒│° } °│
- │ \ / ▒│° °│
- │ \ ? / ▒│° °│
- │ \ / ▒└─────────────────────────┘
- │ \/ ▒
- │ │ Yes ▒
- │ ┌───────────────┐ ▒
- │Do body of loop │ ▒
- │ │ print f... │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- │ │ count++; │
- │ └───────┬────────┘
- │ │
- └─────────┘
-
- Figure 4-6. The while loop.
-
- When the user presses a key, kbhit() returns "true," but the logical not
- operator reverses the result into "not true," or 0, and the loop exits.
-
- Note that a polling while loop needs no counter variable or incrementing.
- The while loop needs only to test a condition that will eventually change.
- (If the condition never changes, the program never stops.)
-
- Using while to Animate a Character
-
- We can nest while loops much as we nested for loops. The ANIMATE.C program
- (Listing 4-6) uses a set of nested while loops to produce simple
- animation──making a character appear to move back and forth across the
- screen.
-
- When you run ANIMATE.C, a "double arrow" graphics character (» or «) and
- the flashing cursor move back and forth across the screen until you press
- a key.
-
- ANIMATE.C starts with #define statements that specify the right arrow and
- left arrow PC graphics characters as well as the backspace and blank
- characters, which we use for moving the cursor back and for erasing the
- previously drawn arrow.
-
- The outer loop uses while (!kbhit()), which keeps the program running
- until you press a key. The inner while() loop moves the arrow to the right
- by repeatedly
-
- 1. displaying the right arrow character,
-
- 2. backing up the cursor,
-
- 3. erasing the previously displayed arrow by overprinting it with a blank
- space character, and
-
- 4. incrementing pos to display an arrow in the next space.
-
- (Remember, the blank moves the cursor to the space with the displayed
- arrow.) A while loop then tests pos to stop the arrow when it reaches the
- right side of the screen (position 79).
-
- Notice the two nested while loops that we use to slow the display so the
- eye can follow it. This loop simply counts to 1000. We often use delay
- loops such as this to slow a program to accommodate human perception or
- peripheral devices that cannot keep up with the CPU. (Sophisticated delay
- loops read and use the system clock.)
-
- Another set of while loops moves the arrow from the right side of the
- screen to the left side. You should have little trouble figuring out how
- it works. Note that the arrow must move backward, so we decrement (rather
- than increment) pos and test for pos > 1 to see when the arrow reaches the
- left side of the screen.
-
- What happens when the arrow reaches the left side of the screen? The body
- of the outer loop finishes, and control returns to the test statement in
- the outer loop. Assuming no key has been pressed, the body of the loop
- then executes again.
-
- ──────────────────────────────────────────────────────────────────────────
- /* animate.c -- animates a graphics character */
- /* until a key is pressed */
-
- /* Special characters */
- #define RTARROW 175
- #define LFTARROW 174
- #define BLANK 32
- #define BACKSPACE 8
-
- main()
- {
- int pos, i, j = 1;
- while (!kbhit())
- {
- pos = 1;
- while (pos < 79)
- {
- putch(RTARROW);
- i = 1;
- while (i < 1000)
- {
- j = i + 10;
- i++;
- }
- putch(BACKSPACE);
- putch(BLANK);
- pos++;
- }
- while (pos > 1)
- {
- putch(LFTARROW);
- i = 1;
- while (i < 1000)
- {
- j = i + 10;
- i++;
- }
- putch(BACKSPACE);
- putch(BLANK);
- putch(BACKSPACE);
- putch(BACKSPACE);
- pos--;
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-6. The ANIMATE.C program.
-
- Combining while and for Loops
-
- The following program, MIXLOOPS.C (Listing 4-7), accepts a character from
- the user and counts through the alphabet until it reaches the character,
- beeping once for each count. The loop continues to accept characters until
- the user enters a blank. (Note: As written, the program works only with
- lowercase alphabetic characters; you can extend it to accept others by
- changing the starting value of the variable i.)
-
- A session with MIXLOOPS.C might run as follows:
-
- c──────────────────────────────────────────────────User enters character
- In FOR loop!─────────────────────────────Beeps each time line is printed
- In FOR loop!
- In FOR loop!
- ───────────────────────────────────────User enters blank to end program
-
- The outer loop, a while loop, contains the test:
-
- while ((ch = getche()) != ' ')
-
- This introduces another noncore QuickC library function called getche(),
- which stands for "get character with echo." This function accepts a
- character from the user and echoes (displays) it on the screen. Because
- this function is located in the conio.h include file, you must add the
- appropriate #include line before the definition of main().
-
- An important feature of this loop is its use of a function call whose
- value is both assigned to a variable and tested in the loop condition.
- We'll learn more about function calls in the next chapter.
-
- ──────────────────────────────────────────────────────────────────────────
- /* mixloops.c -- reads characters, */
- /* beeps for ASCII count, */
- /* uses a while and a for */
- #include <conio.h>
-
- main()
- {
- char ch;
- int i;
-
- while ((ch = getche()) != ' ') /* get a char. */
- {
- for (i = 'a'; i <= ch; ++i) /* count up to alphabet pos.*/
- {
- printf("In FOR loop!\n");
- printf("\a"); /* sound beep each time */
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-7. The MIXLOOPS.C program.
-
- When the user enters a character, the assignment ch = getche() assigns the
- ASCII value of the character to the variable ch. The not equals operator
- != then compares the character value to the ASCII value for the blank
- character, specified as ' '. This results in a true or false value that
- the while loop tests.
-
- If the user does not enter a space, the for loop, which makes up the body
- of the while loop, executes. The for loop tests for i <= ch. Thus, if the
- user enters the character f, the loop counts from the ASCII value of `a'
- to that of `f': The body of the for loop executes one time each for the
- values `a' through `f', and you hear six beeps.
-
- MIXLOOPS.C is a good example of the appropriate use of while and for
- loops. The outer, while loop waits indefinitely for a condition to change
- (the user enters a space); the inner loop, the for loop, counts to a
- definite value (the ASCII value of the character entered in the while
- loop).
-
-
- The do Loop
-
- The third (and final) C looping structure is the do loop, which takes the
- following general form:
-
- do
- {
- statements;
- }
- while (test);
-
- The do loop is very similar to the while loop, with one major exception──
- the while loop performs the test and then executes the body of the loop;
- the do loop executes the body of the loop and then performs the test.
- Thus, the body of a do loop always executes at least once, even if the
- result of the first test is false.
-
- The DO.C program (Listing 4-8) demonstrates a simple do loop that
- performs the now-familiar task of counting from 1 to 10.
-
- ──────────────────────────────────────────────────────────────────────────
- /* do.c -- a simple do-while loop */
-
- main()
- {
- int i = 1;
- do
- {
- printf("%d\n", i);
- i++;
- }
- while (i < 11);
- printf("Done!\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-8. The DO.C program.
-
- Of the three C looping structures, the do loop is by far the least used.
- Usually when you test for a change in condition, a while loop is more
- appropriate because you want the program to react immediately to user
- input, especially a "quit" command.
-
- Use the do loop to repeat an action until some condition changes only when
- the test need not be made immediately. A good example is the TIMER.C
- program (Listing 4-9).
-
- ──────────────────────────────────────────────────────────────────────────
- /* timer.c -- uses do loop to */
- /* check elapsed time */
- #include <time.h>
-
- main()
- {
- long start, end, /* starting and ending times */
- /* measured in seconds since */
- /* Jan. 1, 1970 */
- ltime; /* used to get val from time function */
- int seconds; /* elapsed time to be set */
-
- printf("QuickC Egg Timer\n");
- printf("Enter time to set in seconds: ");
- scanf("%d", &seconds);
- start = time(<ime); /* get system elapsed seconds */
- /* since 1-1-70 */
- end = start + seconds; /* calculate alarm time */
-
- do
- {;} /* null statement for loop body */
- while (time(<ime) < end); /* wait for alarm time */
-
- printf("Time's Up!\a\a\a\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-9. The TIMER.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- Pascal programmers should note the similarity of C's do and Pascal's
- repeat until loops. The difference is that the C do loop repeats the body
- until the specified condition is false, whereas the Pascal loop repeats
- the body until the condition is true.
- ──────────────────────────────────────────────────────────────────────────
-
- This program lets you specify a time in seconds, after which the program
- beeps three times and prints Time's Up! The program uses the noncore
- library function time(), which, when given the address of a long type
- variable, stores in that variable the number of seconds that have elapsed
- since Jan. 1, 1970, as measured by your PC's clock. As you would expect,
- the function also returns this value for use in the calling statement.
-
- After the initial messages are printed and the user enters a number, the
- program calls the time() function. Because this function, like scanf(),
- requires an address as its parameter, you must call the function as
- time(<ime), using the address operator & to specify the address of the
- long variable ltime. The returned value, the elapsed seconds from Jan. 1,
- 1970 to the second the user enters a number, is assigned to the variable
- start. We add the number of seconds specified by the user to this variable
- and store the result in the variable end. This variable thus contains the
- number of elapsed seconds at which the program will terminate.
-
- The do loop then begins. Because this is a timer program and the user
- wants to wait some period of time, the test does not need to be performed
- before the body of the loop executes, so the do loop is appropriate. The
- body of the loop is a null statement──all we want to do is wait. The test
- while (time(<ime) < end) repeatedly calls the time() function and checks
- the returned value until the elapsed time exceeds the value in end.
-
-
- Debugging and Loops
-
- It's rare for a program to work correctly the first time you run it.
- Debugging is the art of knowing what to look for in a program that has
- errors and of correctly interpreting what you see. Some common errors in C
- programs that involve elements we have already discussed include:
-
- ■ Syntax errors
-
- ■ Uninitialized variables
-
- ■ Wrong or incompatible data types
-
- ■ Incorrectly specified loops
-
- Throughout this book we point out common programming errors. Syntax errors
- are the easiest to fix: The compiler enforces the rules of C syntax and
- informs you when and where you have erred. (Sometimes, though, you must
- sort out the real problems from the syntax errors that occur as a result
- of an earlier error!)
-
- True bugs are much harder to detect and fix because they cannot be
- detected by QuickC. We'll define a "logic bug" as an error that does not
- violate the rules of C but generates program results that are either
- completely or partially incorrect. For example, C contains no rule that a
- variable must be initialized. There is no rule preventing you from
- assigning the result of a double calculation to an int variable. And
- loops, with their sometimes complex conditions and specifications, offer
- plenty of opportunity for bugs, such as the problem that arises when you
- use a semicolon after a for loop specification.
-
- Until recently, debugging a C program was a tedious process that involved
- putting printf() statements in strategic parts of a program to reveal the
- values of key variables or the order in which program statements executed
- (or both). Then came programs called "debuggers" that could run and report
- on a C program. (If you bought QuickC with Microsoft C 5.0, you also
- received CodeView, a sophisticated debugger.) QuickC represents the next
- step in the evolution of debugging: The debugging features are built into
- the QuickC environment itself. (QuickC's debugger only works in the medium
- model.)
-
- The BUGS.C program (Listing 4-10) is a bug-ridden program that we will
- fix using the QuickC Debug menu and facilities. It features a while loop
- and is supposed to let the user enter as many as five numbers and get
- their total and average.
-
- Type this program exactly as shown and run it. (If you spot some bugs
- along the way, give yourself a star. But please type the program as shown
- so you can step through the debugging exercise properly.) When you run the
- program, this is what happens:
-
- Continue (y/n)? y───────────────────────────────────────────────Type `y'
- Enter a number:
- Enter a number:
- Enter a number:
- Enter a number:
- Enter a number:
-
- ──────────────────────────────────────────────────────────────────────────
- /* bugs.c -- for practice with debugger */
-
- main()
- {
- char response;
- int number, max_numbers = 5, count = 0, total = 0;
- float average;
-
- printf("Continue (y/n)? ");
- response = getche();
- while ((response != 'n') && (count < max_numbers))
- printf("\nEnter a number: ");
- scanf("%d", &number);
- total += number;
- printf("Continue (y/n)? ");
- response = getche();
- average = total / count;
- printf("\nTotal is %d\n", total);
- printf("Average is %f\n", average);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 4-10. The BUGS.C program.
-
- The program is running out of control. To break out of it and return to
- the QuickC environment, press Ctrl-Break. Now the screen displays the
- following messages:
-
- Enter a number:
- Enter a number:
- E^C
-
- run-time error R6014 : (1 of 1)
- - control-BREAK encountered
-
- Program returned (255). Press any key
-
- Press a key to return to QuickC. (You can ignore this error message.)
-
- Now you need to figure out which bug or bugs caused the program to fail.
- Because the program uses a while loop, it seems likely that something is
- causing one statement of the loop to be repeated endlessly. To debug the
- program, you must first select the Compile menu and then select the Debug
- option. (The Debug menu is shown in Figure 4-7.) This tells QuickC to
- gather debugging information as it compiles the program. Now select Build
- Program from the bottom of the Compile menu to recompile the program with
- debug information. Then go to the Debug menu and select Trace On. (This is
- a toggle setting──select it to turn it on, denoted by a check mark to the
- left of Trace On; select it again to turn it off.) Trace On highlights the
- statement currently being executed (in color if you have a color display).
- This lets you easily follow the flow of the program as it executes.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-7 can be found on p.117 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-7. The Debug menu.
-
- Another option in this menu is Screen Swapping. When you turn on Screen
- Swapping, QuickC alternates between the output screen and the QuickC
- environment screen as every statement executes. This causes a flickering
- effect that can be annoying. Even with Screen Swapping off, QuickC shows
- the output screen whenever any statement produces output or requests
- input. We suggest you leave Screen Swapping off. Finally, select Start
- from the Run menu to run the program.
-
- As the program runs, notice that the statement currently being executed is
- highlighted. (If you have a color monitor, you can change colors by
- selecting Options from the View menu.) The first printf() statement
- executes and is followed by the scanf() statement that solicits a
- response. Type y to continue.
-
- After that, the program runs away. Notice that the loop specification line
- and the next printf() line execute continually. Do you see why? We didn't
- use braces to mark the beginning and end of the loop body. Therefore, the
- printf() line is executed as the body of the loop. Because this statement
- doesn't get or change values for either of the loop's two control
- variables, response and count, the loop test never becomes false, and the
- loop never terminates. Now you can stop the program (with Ctrl-Break) and
- insert the braces before and after the indented lines.
-
- This example illustrates how easy it is to use the QuickC debugger. You
- simply turn on the debugging features, observe the problem, and go back to
- the program to fix it. Because everything is done in the same QuickC
- environment, you don't have to save or reload any files.
-
- ──────────────────────────────────────────────────────────────────────────
- Controlling the Debugger from the Keyboard
- It is often easier to use keyboard commands rather than menu selections to
- debug a running program. You can use the following QuickC keyboard
- commands while debugging a program:
-
- Function Key Result
- ──────────────────────────────────────────────────────────────────────
- F8 Execute next statement, trace through function
- F10 Execute next statement, trace around function
- F7 Execute until current cursor position is reached
- F4 Display the output screen
- ──────────────────────────────────────────────────────────────────────
-
- (When a statement calls a function you have defined elsewhere, F8 traces
- through the definition of the function. F10, on the other hand, does not
- detour to trace a called function.)
- ──────────────────────────────────────────────────────────────────────────
-
- Now run the program again. As the highlight moves on the screen, notice
- that the whole body of the loop executes. That's an improvement. You now
- can run BUGS.C and enter a series of numbers to be totaled and averaged.
- Let's say you enter three numbers──8, 12, and 10. This is what happens:
-
- Continue (y/n)? y
- Enter a number: 8
- Continue (y/n)? y
- Enter a number: 12
- Continue (y/n)? y
- Enter a number: 10
- Continue (y/n)? n
- run-time error R6003
- - integer divide by 0
-
- Program returned (255). Press any key
-
- Clearly the program still doesn't work right. A look at the listing shows
- that the program is supposed to add each new number to total and, after
- the last number is entered, divide total by count to get average.
- Apparently count is still zero when the loop exits, thus triggering the
- divide by zero error. Why? To find out, let's use another feature of the
- QuickC debugger, "watch variables."
-
- Move the cursor in the text area to the variable name count. Select the
- Debug menu again, and then select Add Watch. The window shown in Figure
- 4-8 appears. The Watch window is a device that lets you designate program
- variables for QuickC to monitor. When the value of one of these variables
- changes, QuickC displays its new value in a window at the top of the
- screen. This eliminates the need to put extra printf() statements in your
- program to monitor variables.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-8 can be found on p.119 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-8. Adding a watch variable.
-
- Because you already selected it with the cursor, the word count appears in
- the window. Simply select OK to add count to the list of watch variables.
- (To remove watch variables later, select Delete Last Watch or Delete All
- Watch from the Debug menu.)
-
- You can also specify another display format for the value of the watch
- variable by adding a comma and a format specifier to the variable──for
- example, count,d──which specifies an integer format display for count. The
- format specifiers are similar to those you used with printf() and scanf().
-
- Next, select Add Watch twice and add the variables number and total to the
- watch list. (Either position the cursor on the names or type them in the
- dialog box.)
-
- Finally, let's set a breakpoint. This is a place in the program at which
- execution will stop. This lets you examine the status of the watch
- variables. Move the cursor to the last line in the body of the while
- loop──response = getche();──and choose Toggle Breakpoint (or press F9)
- from the Debug Menu. (Select the toggle again with the cursor on the same
- line to remove the breakpoint, or choose Clear All Breakpoints. You can
- set any number of breakpoints.)
-
- Now you're ready to run the program again, so choose Start from the Run
- menu. Again, the program prompts you for numbers, and you can watch the
- statements in the while loop as they execute. The program stops at the
- breakpoint at the end of the body of the loop. At the top of the screen, a
- small window lists the watch variables and their current values. After you
- inspect them, select Continue from the Run menu (or press F5) to resume
- program execution. (Figure 4-9 shows the screen display with the current
- statement and breakpoint highlighted; the watch variable information
- window is at the top.)
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 4-9 can be found on p.120 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 4-9. Debugging in progress.
-
- As the loop cycles, notice that number accepts the value of the number you
- entered, and total grows as you add new numbers. But count always remains
- zero. Have you figured out why? We forgot to put a statement in the body
- of the loop that increments count. Adding count++; after total += number;
- completes our debugging of the program.
-
- You can do more complex things with the debugger, so be sure to read
- Chapter 8 of the Microsoft QuickC Programmer's Guide for more information.
- For example, when you learn about arrays and structures, you can use watch
- variables to display them, too. Meanwhile, you also can use the debugger
- as a learning tool for tracing the flow of programs in this book, the
- sample programs provided by Microsoft, or other C programs.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 5 Decisions and Branching
-
- All programming languages must be able to perform controlled "branching."
- Branching uses the result of a test or condition to determine which
- statement (or group of statements) will execute next. In this chapter we
- discuss the variations of branching in C and learn how to use them with
- looping statements.
-
-
- The if Statement
-
- In C, as in most languages, the if keyword introduces a branching
- statement. The following structure is the simplest form of branching:
-
- if (condition)
- statement(s);
-
- An if statement, like a while loop, evaluates a condition first. The
- condition can be any combination of values and relational or logical
- operators that yields a true (nonzero) or false (zero) value──answer ==
- `y', for example. If the condition is true, the following statement (or
- group of statements in braces) executes. (As with loops, the statement or
- statements controlled by the condition are called the "body" of the
- statement.) If the condition is false, the following statement or group of
- statements does not execute, and execution continues with the next
- statement or group of statements. A simple example follows:
-
- if (balance < 0)
- printf("Your account is overdrawn!\n");
- printf("Your current balance is %8.2f\n", balance);
-
- If the customer's balance is less than zero, the first printf() statement
- executes, telling the customer the account is overdrawn; then the second
- printf() statement, which prints the current balance, executes. If the
- customer's balance is zero or more, the condition is false, and the first
- printf() statement does not execute──the program skips it. Only the second
- printf() statement executes.
-
- In the above example, we indent the first printf() statement to show that
- the if controls it──it is the body of the if statement. (In C, we indent
- statements for our benefit only: The compiler doesn't require indention.)
- Always enclose the condition in parentheses, and do not use a semicolon
- directly after the parentheses because the complete if statement includes
- the if, the condition, and the statement body.
-
- The IF.C program (Listing 5-1) features the if statement.
-
- ──────────────────────────────────────────────────────────────────────────
- /* if.c -- simple IF statement */
-
- char ch;
- main()
- {
- printf("Do you want to continue y/n? "); /* prompt */
- if (ch = getche() == 'y')
- printf("\nLet's continue ...\n"); /* if true */
- printf("\nAll done.\n"); /* always executed */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-1. The IF.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- If you know BASIC or Pascal, note that C does not use the then keyword
- before the body of the if statement. Most other languages use the
- following form for the if statement:
-
- if (condition)
- then statement(s)
-
- If you mistakenly use then with if, the QuickC compiler will catch the
- error, of course, and you will soon stop making it.
- ──────────────────────────────────────────────────────────────────────────
-
- IF.C asks the user if he or she wants to continue. The test expression ch
- = getche() == `y' gets the response character, assigns it to ch, and tests
- it. The program generates one of two responses:
-
- Do you want to continue y/n? y──────────────────────────────User types y
- Let's continue ...
- All done.
-
- Do you want to continue y/n? n─────User types any character other than y
- All done.
-
- Note that the program prints Let's continue ... only if the user types y;
- however, it always prints All done.
-
- The if statement represents a fork in the road: One of two possible
- courses is followed, depending on the result of the test. The flowchart in
- Figure 5-1 depicts such a branch, with the test shown in a diamond-shaped
- box.
-
- ┌────────────────────────────────┐
- No │ │
- ┌───────────┐ /\ ┌───────────┐ ┌──────────┐
- │ printf │ / \ │ printf │ │ printf │
- │ ("Do you │ / if \ │ ("\nLet's │ │ ("\nAll │
- │ want to ├──/ch=getche() \───│ continue ├───│ done.\n");│
- │ continue │ \ =='y') / │ ...\n"); │ │ │
- │ y/n"); │ \ / │ │ │ │
- │ │ \ / │ │ │ │
- └───────────┘ \/ └───────────┘ └───────────┘
-
- Figure 5-1. Flowchart for the if statement.
-
- Comparing if and while
-
- Notice the structural similarity of the following two statements:
-
- if (score > 90)
- printf("Excellent!");
-
- and
-
- while (question <= total_questions)
- ask_question();
-
- Both statements test a condition and (if the condition is true) execute
- the following statement. The important difference between the two
- constructions is that the if statement executes the body of the statement
- only once; but the while statement executes the body repeatedly (as long
- as the test continues to be true).
-
- If you think about the two statements, you can see why each is appropriate
- for its assigned task. A statement that prints the final score of a quiz
- needs to be executed only once. On the other hand, a statement that calls
- a function that asks the next question in the quiz must be executed
- repeatedly.
-
- Using a Group of Statements with an if
-
- The body of an if statement can contain any number of statements. Consider
- the following example:
-
- if (choice == 'd')
- {
- printf("How much do you want to deposit? ");
- scanf("%f", &deposit);
- balance += deposit;
- printf("Thank you. Your new balance is ");
- printf("%8.2f", balance);
- }
-
- When choice is `d', the program executes all five statements between the
- braces. Notice that we use the same indention for the braces and the
- statements.
-
- Nested if Statements
-
- Just as the body of a loop can contain another loop, the body of an if
- statement can contain another if statement. For example, a simple text
- formatter might use the following code fragment:
-
- if (pos == line_length)
- if (++line_count > lines_page)
- {
- print_footer;
- putch(FF);
- ++page_number;
- print_header;
- }
-
- If the first if statement is true (the character position equals the line
- length), the program executes the body of the statement, which is itself
- an if statement. This statement increments line_count by one, and if the
- result is greater than lines_page, the body of the inner if statement
- executes. These statements print a footer, output a "form feed" character,
- add one to the page number, and print a header for the next page. (Because
- we must repeatedly test for the end of line and the end of page, we would
- actually place these if statements inside a while loop. As you might
- expect, you will often use if statements inside loops, and we will show
- you examples of these later in this chapter.)
-
- Providing Alternatives with else
-
- The if statement has an adjunct──else──that is useful for executing a
- statement or group of statements only if the given condition is false. The
- general form of the if-else statement is simply an extension of the simple
- if statement:
-
- if (condition)
- statement(s);
- else
- statement(s);
-
- Consider the following example:
-
- if (age >= 18)
- {
- printf("To vote, enter number of candidate: ");
- scanf("%d", &candidate);
- }
- else
- printf("Sorry, you must be at least 18 to vote.\n");
-
- The first group of statements executes only if age is greater than or
- equal to 18. The statement following else executes only if that condition
- is false. If-else statements let you provide appropriate responses for
- both true and false results.
-
- Note that we align the else with its corresponding if because together
- they form one if statement of two parts. Correspondingly, we also indent
- the statement(s) controlled by the else to match the statements under the
- if.
-
- The IFELSE.C program (Listing 5-2) uses an if statement with an else to
- simulate the logon sequence for a bulletin board system.
-
- ──────────────────────────────────────────────────────────────────────────
- /* ifelse.c -- IF with ELSE */
-
- char ch;
- int num;
- main()
- {
- printf("Are you a new user? y/n? ");
- if (ch = getche() == 'y')
- {
- /* executed if IF is true */
- printf("\n\nYou must register to use this\n");
- printf("bulletin board. Please read\n");
- printf("Bulletin #1 first. Thank You.\n");
- }
- else
- /* executed if IF is false */
- {
- printf("\n\nEnter your secret number: ");
- scanf("d", &num);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-2. The IFELSE.C program.
-
- If the user replies y to the question Are you a new user?, the statements
- following the if execute. If the user types n (or anything else), the
- statements following the else execute instead.
-
- Here is a sample dialogue:
-
- Are you a new user? y/n? y
-
- You must register to use this
- bulletin board. Please read
- Bulletin #1 first. Thank You.
-
- Are you a new user? y/n? n
-
- Enter your secret number: 31415
-
- Matching an else to an if
-
- As you write more advanced programs, you will need to use more complex if
- statements, such as:
-
- if (temp < 900)
- if (temp > 750)
- printf("Warning! Boiler overheating!\n");
- else
- printf("Start emergency shutdown!\n");
-
- This program is meant to check the temperature and print a warning if the
- temperature is between 750 degrees and 900 degrees, or print an emergency
- warning if the temperature is greater than 900 degrees. It might look
- correct, but it's not.
-
- When this if statement actually executes, it prints nothing if the
- temperature exceeds 900, and it prints the emergency warning if the
- temperature is less than 750! We actually want the else to go with the
- outer if to print the emergency warning only if the temperature exceeds
- 900. However, although we physically aligned the else so that it appears
- to go with the outer if, the compiler reads the statement differently. It
- considers the else to belong to the inner (nested) if.
-
- Always remember that QuickC matches a given else with the preceding
- unenclosed if that doesn't already have an else. Now that we understand
- this rule, we can fix the program by enclosing the inner if in braces so
- that the else is not attached to it:
-
- if (temp < 900)
- {
- if (temp > 750)
- printf("Warning! Boiler overheating!\n");
- }
- else
- printf("Start emergency shutdown!\n");
-
-
- The Conditional Assignment Statement ?
-
- Compared to BASIC or even Pascal, C might seem to be a sparse language
- that provides the essential tools for programming but few frills. However,
- we've already seen several elements (such as the special increment and
- decrement operators) by which C provides "shorthand" expressions to
- simplify commonly encountered programming chores. Another such common
- programming task is assigning one of two values to a variable, depending
- on the result of a test. For example, suppose we want to set the variable
- max to the larger of the values of the variables n1 and n2. Of course, we
- can use an if and else, as follows:
-
- if (n1 > n2)
- max = n1;
- else
- max = n2;
-
- But we can also use C's conditional assignment statement to do the job──
- and in one line of code. The general form for the conditional assignment
- statement is:
-
- variable = (expression) ? value1 : value2;
-
- QuickC evaluates the expression in parentheses first. In this form of
- assignment statement, if the expression is true (nonzero), value1 is
- assigned to variable; if the expression is false (zero), value2 is
- assigned to variable. Note that a question mark follows (expression) and a
- colon (:) separates the two values.
-
- We can now rewrite our earlier statement for assigning a value to max as
- follows:
-
- max = (n1 > n2) ? nl : n2;
- │ │ │ └─────────────────────────────── Assign if false
- │ │ └───────────────────────────────────── Assign if true
- │ └────────────────────────────────────────── Expression to test
- └──────────────────────────────────────────── Variable to receive value
-
- This translates as "If n1 > n2, then assign the value of n1 to max;
- otherwise, assign the value of n2 to max." Although this statement might
- look odd, it's easy to use and quite handy.
-
- Assigning Truth Values
-
- If the two possible values for a variable are actually "true" and "false,"
- you don't need to use the conditional assignment statement. Simply assign
- the result of the expression to the variable. For example,
-
- frozen = (temp <= 32)
-
- sets the value of frozen to true (nonzero) if the temperature is less than
- or equal to 32 and sets it to false otherwise.
-
- The SHORTIF.C program (Listing 5-3 on the following page) illustrates
- some shorthand and conditional assignments.
-
- ──────────────────────────────────────────────────────────────────────────
- /* shortif.c -- shows 'shorthand' IF / ELSE */
- /* -- gets absolute value of number */
-
- main()
- {
- int num, pos, abs;
- printf("Enter a whole number: ");
- scanf("%d", &num);
-
- pos = (num >= 0); /* is number positive? */
-
- abs = (pos) ? num : -num; /* assigns negative of */
- /* number if number is negative */
- if (pos)
- printf ("The number is positive.\n");
- else
- printf("The number is negative.\n");
- printf("Absolute value of number is: %d\n", abs);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-3. The SHORTIF.C program.
-
- First, the program gets a number from the user. Then it tests the number
- to see if it is positive. Notice we do this by assigning the result of the
- expression (num >= 0) to the variable pos. This value now contains "true"
- if the number is positive, or "false" if it is not. (Remember that these
- are actually numeric values, 1 and 0, that have the logical effects of
- "true" and "false" when used in tests.)
-
- Next, the program uses a conditional assignment statement to calculate the
- absolute value of the number. (The absolute value of a number is its value
- disregarding its sign. Thus, both 5 and -5 have an absolute value of 5.)
- Recall that a conditional assignment statement assigns one of two values
- to a variable based on the truth result of an expression. However, you can
- also use a single variable that has a truth value instead of an
- expression. Because the variable pos was assigned a truth value earlier,
- we can use it here as the test for the conditional assignment.
-
- Now let's look at the assignment statement and the if-else branches. If
- the entered number (num) is positive, pos contains "true," and the
- statement assigns num to the absolute value abs. In other words, the
- absolute value of a positive number is simply the number itself.
-
- If num is negative, however, then pos contains "false," and the statement
- assigns the second value, -num, to abs. The negative of a negative number
- is a positive number and, therefore, the absolute value. The examples on
- the following page demonstrate the output when the program is run twice──
- first with a positive number and then with a negative number.
-
- Enter a whole number: 23
- The number is positive.
- Absolute value of number is: 23
- Enter a whole number: -58
- The number is negative.
- Absolute value of number is: 58
-
-
- Multipath Branching
-
- Thus far, we've discussed simple branches (the single if) and two-way
- branches (the if and else). Simple branches are most useful for testing a
- condition that can have only one of two values──typically "true" and
- "false." But what about those situations in which you must test for one of
- several values? This commonly occurs in a menu from which a user must
- choose one of several items.
-
- Consider, for example, a program that offers the user a choice of readings
- from a home weather station. Let's say the user can choose among
- temperature, humidity, pressure, and wind velocity. Here's one way we
- could set up the menu:
-
- printf("Enter reading wanted: t = temp h = humidity\n");
- printf("p = pressure w = wind velocity ");
- ch = getche();
- if (ch == 't')
- printf("Current temperature is %5.2\n", temp)
- else
- if (ch == 'h')
- printf("Humidity is %4.2f\n", humidity);
- else
- if (ch == 'p')
- printf("Air pressure is %5.2f\n", pressure);
- else
- if (ch == 'w')
- printf("Wind velocity is %d\n", wind);
- else
- {/* default */
- printf("Invalid choice. Choose ");
- printf(" t, h, p, or w.\n");
- }
-
- This chain of if statements, each hooked to the preceding statement's
- else, will work, but it has many disadvantages. Its many levels of nesting
- are difficult to read. Also, the many indentions run the code off the edge
- of the screen, requiring awkward line breaks.
-
- This type of code also creates a conceptual problem. The structure of
- these statements suggests that each if statement is dependent on all of
- the preceding if statements. This suggests that we are checking for some
- kind of special case that is true only if all the ifs in the series are
- true. But nothing is further from the truth──we merely want to compare ch
- to four possible values and provide a branch for each value. (We also need
- a default branch to handle invalid user-entry values.)
-
- We can improve the visual organization of our branches in the following
- manner:
-
- if (ch == 't')
- printf("Current temperature is %5.2\n", temp)
- else if (ch == 'h')
- printf("Humidity is %4.2f\n", humidity);
- else if (ch == 'p')
- printf("Air pressure is %5.2f\n", pressure);
- else if (ch == 'w')
- printf("Wind velocity is %d\n", wind);
- else { /* default */
- printf("Invalid choice. Choose ");
- printf("t, h, p, or w.\n");
- }
-
- This arrangement is clearer and more compact. All of the branches now have
- the same level of indention, showing that they are co-equal and not
- dependent on each other. However, it is important to note that else if is
- not a distinct command: Changing indention doesn't change the way the
- compiler handles this code.
-
-
- The switch Statement
-
- C offers a special switch statement that makes writing multiple branches
- much easier. The general form of switch follows:
-
- switch (variable)
- {
- case 'constant1':
- statement(s);
- break;
- case 'constant2':
- statement(s);
- break;
- case 'constant_n':
- statement(s);
- break;
- default:
- statement(s);
- }
-
- Specify the name of the variable to be tested in parentheses after the
- word switch. As with the other loops and branches, don't use a semicolon
- at the end of the first line: The entire structure comprises one
- statement.
-
- The body of the switch statement (enclosed in braces) is a list of
- possible branches. Each branch consists of the word case followed by a
- constant value (a number or character) in parentheses. During execution,
- this constant is compared with the switch variable: If they are equal, the
- statements for that case execute. Note that single quotes enclose each
- constant, and the line ends in a colon.
-
- One or more statements follow each case line. (Do not enclose a group of
- statements in braces──the compiler handles all statements under a given
- case as a single unit.) The last statement in each branch is the keyword
- break. The break statement immediately ends execution of the switch
- statement; program execution resumes at the statement that follows the
- body of the switch statement. Usually you will conclude each case in a
- switch statement with the keyword break. If a switch statement behaves
- erratically, look for missing break statements in the individual cases.
- Also, include a default: to handle invalid values.
-
- Sometimes, however, you will want to execute a set of statements if the
- switch variable has any one of several values. You can do this by placing
- the set of statements after a series of switch values, as in the
- following:
-
- switch (ch)
- {
- case 'q':
- case 'Q':
- show_score();
- end_game();
- break;
- case ...
- }
-
- No statement is associated with 'q', so execution falls through to the
- code for 'Q', the show_score() and end_game() functions execute, and break
- is encountered.
-
- A switch statement can contain any number of branches, which is why the
- last branch in our format description uses the notation constant_n. A
- special case, default:, is an optional branch that is usually placed after
- the last explicit case in the switch statement. It specifies the branch
- that executes if none of the conditions for the other cases match the
- value of variable. Although default: is optional, programmers frequently
- use it to respond to erroneous values, such as an invalid choice.
-
- Let's use the switch statement to rewrite our weather station menu:
-
- switch (ch)
- {
- case 't':
- printf("Current temperature is %5.2\n", temp)
- break;
- case 'h':
- printf("Humidity is %4.2f\n", humidity);
- break;
- case 'p':
- printf("Air pressure is %5.2f\n", pressure);
- break;
- case 'w':
- printf("Wind velocity is %d\n", wind);
- break;
- default:
- printf("Invalid choice. Choose ");
- printf("t, h, p, or w.\n");
- }
-
- Figure 5-2 on the following page illustrates this switch statement. The
- multiple branches suggest tracks in a railroad switching yard, the
- probable origin of the name.
-
- ┌───────┐ ┌──────────┐
- ┌───────────│ 't' │───│printf... │
- │ │ │ │break; │
- │ └───────┘ └──────────┘
- │ ┌───────┐ ┌──────────┐
- │ ┌──────│ 'h' │───│printf... │
- │ │ │ │ │break; │
- Value of ──────┘ │ └───────┘ └──────────┘
- ┌────────┐ ────────┘ ┌───────┐ ┌──────────┐
- │ │ │ ─────────────│ 'p' │───│printf... │
- switch │ ( ch ) │ * │ │ │break; │
- │ │ └───────┘ └──────────┘
- └────────┘ ────────┐ ┌───────┐ ┌──────────┐
- ──────┐ └──────│ 'w' │───│printf... │
- │ │ │ │break; │
- │ └───────┘ └──────────┘
- │ ┌───────┐ ┌──────────┐
- └───────────│default│───│printf... │
- │ │ │break; │
- └───────┘ └──────────┘
-
- Figure 5-2. The switch statement.
-
-
- The break Statement
-
- The break statement has other uses than as the last statement in each case
- of a switch statement. A break statement can also be used with the three
- looping statements: (for, while, and do). In all cases, however, break has
- the same effect──it immediately "breaks out of" the enclosing structure
- and causes execution to resume after the end of the switch or loop
- structure. The BREAK.C program (Listing 5-4) uses break to exit from a
- while loop:
-
- This program uses the rand() library function to generate a series of
- random numbers. On each pass through the while loop, the program generates
- and displays one random number in the range 0 through 32,767. The
- statement
-
- if (number < 32000)
- break;
-
- terminates the while loop if the program generates a random number greater
- than 32,000. The output might look something like the following:
-
- 41
- 18467
- 28145
- 16827
- 491
- 2995
- 11942
- 5436
- 32391
- Broken out of WHILE loop.
-
- ──────────────────────────────────────────────────────────────────────────
- /* break.c -- shows how to get out of loop with BREAK */
-
- #include <stdio.h>
- #define TRUE 1
-
- main()
- {
- int number;
- while (TRUE) /* endless loop */
- {
- /* get a random number between 0 and 32767 */
- number = rand();
- printf("%d\n", number);
-
- /* break out of loop if random number */
- /* is greater than 32000 */
- if (number > 32000)
- break; /* exit WHILE loop */
- }
- printf("Broken out of WHILE loop.\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-4. The BREAK.C program.
-
- The last value, 32391, triggered the break statement. The while loop
- terminates, and the printf() statement following the body of the while
- loop executes.
-
- The SWITCH.C program (Listing 5-5 on the following page) shows you how to
- create a simple menu using a while loop containing a switch statement. The
- program asks the user to select one of four math routines (octal
- representation, hex representation, square, or square root), prompts for a
- number to be converted, and prints the result. The user types q to exit
- the program.
-
- Following is a sample dialogue with SWITCH.C:
-
- Select a math routine:
- o = octal h = hex s = square
- r = square root q = quit: o
- Enter a whole number: 30
- Result: 36
-
- Select a math routine:
- o = octal h = hex s = square
- r = square root q = quit: r
- Enter a whole number: 10
- Result: 3.162278
-
- Select a math routine:
- o = octal h = hex s = square
- r = square root q = quit: q
-
- Program returned (113). Press any key
-
- ──────────────────────────────────────────────────────────────────────────
- /* switch.c -- demonstrates switch statement */
- /* prints values according */
- /* to user's choice */
- #include <math.h> /* for sqrt() */
- #define TRUE 1
- main()
- {
- char choice; /* routine wanted by user */
- int number; /* number entered by user */
-
- while (TRUE) /* endless loop */
- {
- printf("\nSelect a math routine:\n");
- printf("o = octal h = hex s = square\n");
- printf("r = square root q = quit: ");
- choice = getche(); printf("\n");
-
- if (choice == 'q')
- break; /* exits WHILE loop; ends program */
-
- /* rest of program executed if choice <> 'q' */
- printf("Enter a whole number: ");
- scanf("%d", &number);
-
- switch (choice) /* print according to */
- /* choice requested */
- {
- case 'o': /* print octal */
- printf("Result: %o\n", number);
- break; /* break here in each case */
- /* exits the switch statement */
-
- case 'h': /* print hex */
- printf("Result: %x\n", number);
- break;
-
- case 's': /* square */
- printf("Result: %d\n", number * number);
- break;
-
- case 'r': /* square root */
- printf("Result: %f\n", sqrt(number));
- break;
-
- default:
- printf("Choice must be o, h, s, r, or q\n");
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-5. The SWITCH.C program.
-
- We enclose the menu in an endless while loop because the user will be
- making choices indefinitely. Notice that we use while (TRUE) instead of
- while (1). Both have the same effect, but the code is clearer when we use
- a #define statement to make TRUE equal to 1 and use the descriptive name
- in the program.
-
- After the program displays the menu and getche() gets the user's choice,
- an if with a break statement tests for the possibility that the user wants
- to quit. If the user quits, the while loop terminates and the program
- ends.
-
- If the user did not enter q, the program obtains the number to be
- processed. A switch statement then processes the number. The constants in
- the various cases correspond to the menu options so the switch statement
- can match the user's choice with the appropriate case. If the choice is
- `o' or `h', a format specifier returns the appropriate value; if the
- choice is `s' or `r', the value is calculated.
-
- The default: case handles any value not specified in the menu by printing
- a list of valid values. Note that the default: case needs no break because
- there are no further statements in the switch statement that can be
- executed.
-
- switch vs if-else
-
- Using switch gives you a structure that is at least as clear as the series
- of else ifs shown earlier, because each case is clearly distinct. The
- switch has the additional advantage that the variable to be compared is
- stated clearly once, at the beginning of the structure, rather than being
- buried inside the individual tests.
-
- Whether you decide to use switch or the if-else form is a matter of style.
- A good rule of thumb is to use switch whenever four or more possible
- values (including a default) are involved. Some programmers argue that
- switch is clearer for even three possible branches.
-
- There is, however, one situation in which you cannot use switch──even when
- many branches must be used. A switch statement can be used only to test
- simple constant values. You cannot, for example, do the following:
-
- switch (expenditure)
- {
- case < 10.00:───────────────────────Expression is illegal for switch
- printf("petty cash");
- break;
- case < 100.00:
- printf("see office manager");
- break;
- case < 500.00:
- printf("see district manager");
- break;
- default:
- printf("see head office");
- }
-
- Unlike many versions of the Pascal case statement and similar structures
- in other languages, the C switch statement cannot compare a value against
- ranges of values. It also cannot be used with relational expressions. For
- these programming tasks, you must use multiple if-else structures.
-
-
- The continue Statement
-
- Under some conditions we might need to skip some of the statements in the
- body of a loop and return to the loop's test condition. For example, if a
- program offers a menu operation that has potentially irrevocable
- consequences (such as overwriting the contents of a file), you might want
- to ask Do you really want to overwrite this file? If the user answers no,
- the program must skip the remaining statements and return to the menu. The
- continue statement lets you do this.
-
- A continue statement takes the following general form. We illustrate it
- here with a while loop, although you can use it in any kind of loop (but
- not a switch).
-
- while (condition)
- {
- some statements;
- if (condition)
- continue;
- rest of statements;
- }
-
- The if statement tests a condition as usual: If its condition is true, the
- continue executes. This restarts the loop before the rest of the
- statements in the body of the loop execute, and the while loop condition
- is tested again.
-
- The CONTINUE.C program (Listing 5-6) uses a simple example of a continue
- statement. The program also illustrates a "toggle switch" variable, sw. A
- toggle switch changes to the opposite of its current value each time you
- use it. If it's "on," the next time you use it you turn it off.
-
- The body of the endless while loop first prints out the current status of
- the sw switch. Next, the program uses a break statement to give the user
- an opportunity to quit. The program then asks the user whether the switch
- should be toggled. If the answer is not `y,' a continue statement skips
- the last statement in the loop body and the switch is not toggled. If the
- answer is `y,' the continue doesn't execute, and the last statement sw =
- !sw toggles the switch. (Recall that the ! operator reverses the truth
- value of the associated variable.)
-
- The next program, M.C (Listing 5-7), demonstrates various combinations of
- for loops and if statements and includes a continue statement. The program
- draws a letter M within the dimensions specified in the #define statements
- at the beginning of the program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* continue.c -- shows CONTINUE in a loop */
- main()
- {
- int sw = 0;
- char ch;
- while (1) /* endless loop */
- {
- /* print current status */
- if (sw)
- printf("\nSwitch is ON\n");
- else
- printf("\nSwitch is OFF\n");
-
- printf("Do you want to quit? ");
- if (ch = getche() == 'y')
- break; /* exit loop on yes */
-
- printf("\nDo you want to toggle the switch? ");
- if (ch = getche() != 'y')
- continue; /* restart loop on no */
-
- sw = !sw; /* toggle switch */
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-6. The CONTINUE.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* m.c -- draws a letter M */
- /* using IF and CONTINUE */
-
- /* define characters */
- #define CH 'M' /* character to "draw" with */
- #define BLANK ' '
- #define NL 10
- #define CR 13
- #define LEFT 20 /* left side of M */
- #define RIGHT 46 /* right side of M */
- #define BOTTOM 22 /* last line to use */
- main()
- {
- int pos, line;
- /* space to left side */
- for (line = 1; line <= BOTTOM; line++)
- {
- for (pos = 1; pos < LEFT; pos++)
- {
- putch(BLANK);
- }
- putch(CH); /* draw left side */
- /* are we past midpoint? */
- if (line > ((RIGHT - LEFT) / 2))
- {
- /* yes, so just draw right side */
- for (pos = LEFT; pos < RIGHT; pos++)
- {
- putch(BLANK);
- }
- putch(CH);
- putch(NL);
- putch(CR);
- continue; /* start loop over, do next line */
- }
- /* not past midpoint, check for interior */
- for (pos = LEFT; pos < RIGHT; pos++)
- {
- if ((pos == (LEFT + line)) ||
- (pos == (RIGHT - line)))
- putch(CH);
- else
- putch(BLANK);
- }
- putch(CH);
- putch(NL);
- putch(CR); /* could also use printf("\n"); */
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-7. The M.C program.
-
- The M.C program generates the following output:
-
- M M MM
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M M
- M M M
- M M
- M M
- M M
- M M
- M M
- M M
- M M
- M M
- M M
-
- Drawing the letter M involves drawing two distinct sections──the V-shaped
- inner part and the straight-sided outer part. The overall control for
- drawing all the individual lines resides in the outermost for loop. Each
- line is started by a small for loop that moves to the left side of the M
- and draws the character "M" there.
-
- What happens next depends on whether the current line number is in the top
- or bottom part of the M. We determine this by testing to see if the
- distance down the screen in lines is greater than half the distance across
- the M in characters. (This test is arbitrary. Feel free to try other
- formulas and to vary the size of the M by changing the #define
- directives.)
-
- If we are not below the bottom of the V part of the M, the for statement
- in the body of the if moves to the right side, which is then drawn. The
- continue (which doesn't require an if here) then skips the rest of the
- statements, which aren't needed.
-
- If the if statement is false, we are still in the upper portion of the M:
- The body of the if is skipped, and the rest of the statements in the body
- of the outer loop execute. The if statement works on the principle that
- the inner lines of the M are drawn one space further to the right and to
- the left from the sides of the M for each line further down the screen.
- Thus, the appropriate positioning for the inner lines is found by adding
- and subtracting the current line number.
-
-
- The goto Statement
-
- C's goto statement transfers control to the line containing the specified
- label. For example, you might use goto with an if statement, as follows:
-
- printf("Do you want to continue? \n");
- if (ch = getche() == 'y')
- goto yes;
- printf("Goodbye\n")
- goto end;
- yes: printf("Let's continue ...\n");
- end:
-
- Here, if the user enters y, the goto immediately causes execution to skip
- to the printf() statement that follows the label yes: (which must end with
- a colon). If the user does not enter y, the second goto skips the "yes"
- branch.
-
- ──────────────────────────────────────────────────────────────────────────
- The Appropriate Use of continue
- The continue statement is rarely used in C programs. Often (in Microsoft
- C, for example), continue's function is handled by an else branch for the
- relevant if statement or by a new if statement. However, if you have a
- complicated, multiple-nested set of if-else statements, using a continue
- statement might simplify things.
- ──────────────────────────────────────────────────────────────────────────
-
- If this looks confusing, that's because it is. You can do the operation
- much more clearly with an if and an else:
-
- printf("Do you want to continue? \n");
- if (ch = getche() == 'y')
- printf("Let's continue ...\n");
- else
- printf("Goodbye\n");
-
- Nearly all contemporary computer scientists discourage the use of goto
- because it obscures program logic and makes code difficult to decipher, as
- anyone who has ever tried to debug an old-style BASIC program knows. If
- your programming background is in the older versions of BASIC or FORTRAN,
- resist the impulse to use goto statements. Examine the logic of your
- program: You probably will see, as in the above example, that an if-else
- with appropriate conditions (or a switch, break, or continue) lets you
- express the operation more clearly. That's why you can go for months
- without encountering a goto in C programs. (An occasional exception is the
- goto that breaks out of a multiple-nested loop. You can't use break in
- this situation because it only breaks out of the current loop. But even in
- this case, you can avoid a goto by redesigning the program structure to
- use "flag" variables. We will discuss flag variables later.)
-
-
- More Complex Conditions for Branching
-
- Because we have been concentrating on the mechanics of branching, we have
- used only simple test conditions in our branching statements. In the last
- chapter, we showed you how to use logical operators (&& and ||) to create
- multiple conditions for controlling loops. You can also use these compound
- conditions to control the execution of if statements.
-
- The next program, PIXELS.C (Listing 5-8), introduces the QuickC Graphics
- Library. It generates random positions for pixels (points of light on the
- screen) and uses a compound condition to display only selected pixels.
-
- Running the Program
-
- We've already included header files in several of our programs. The
- machine code represented by these header files was in the standard library
- (such as MLIBCE.LIB, the medium memory model with floating-point
- emulation), so all you needed to specify was #include and the appropriate
- header file. QuickC knew where to find the default library.
-
- To use graphics, however, you must specify the graph.h header file. The
- definitions it contains reside in a separate Graphics Library,
- GRAPHICS.LIB. You might need to tell QuickC where to find this library. As
- with the TABLE.C program in the last chapter, you need a program list.
- Create a program list called PIXELS.MAK and add PIXELS.C to it. (If you
- are unsure how to proceed, reread "Creating a Program List" in Chapter
- 4.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* pixels.c -- creates shapes */
- /* from random pixels */
- #include <graph.h> /* for graphics */
-
- main()
- {
- int pixels, xpos, ypos;
- /* window coordinates */
- int xmin = 100, xmax = 540;
- int ymin = 50, ymax = 150;
-
- srand(0); /* init random nums */
- _setvideomode(_HRESBW); /* CGA 640 x 200 */
- _setcolor(1); /* white foreground */
-
- /* generate random pixel locations */
- for (pixels = 1; pixels < 10000; pixels++)
- {
- xpos = rand() % 639;
- ypos = rand() % 199;
-
- /* set pixel if within window */
- if ((xpos > xmin && xpos < xmax) &&
- (ypos > ymin && ypos < ymax))
- _setpixel(xpos, ypos);
- }
- getch(); /* freeze screen until key pressed */
- /* restore original video mode */
- _setvideomode(_DEFAULTMODE);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-8. The PIXELS.C program.
-
- You will need to add the Graphics Library (GRAPHICS.LIB) to the program
- list as well unless you specified that the Graphics Library was to be
- included in your standard library when you ran the SETUP program. (See
- Chapter 2.)
-
- For future programs in this book, we will not remind you to create a
- program list. In general, if the program uses any standard functions that
- are not part of the core library (listed in Appendix :ARB), you must
- create a program list with the name of the program in it before you can
- compile the program to memory. If you wish, you can simply try to compile
- the program and create the program list if you get the error unresolved
- external.
-
- PIXELS.C starts by including the graph.h header file, which contains
- definitions for graphics at every resolution and color supported by the
- IBM graphics adapters (CGA, EGA, VGA, and so on, each with several modes).
- We discuss graphics modes and graphics routines in Chapter 15. Here,
- simply note that the _setvideomode() statement in PIXELS.C sets the video
- mode to the constant _HRESBW, which represents the two-color
- high-resolution CGA mode having a resolution of 640 pixels by 200 pixels.
- The _setcolor statement sets the foreground color (the color of the
- displayed pixels) to white.
-
- The values xmin, xmax, ymin, and ymax contain the coordinate positions of
- the screen "window" in which the program plots pixels. Figure 5-3 shows
- the screen and the coordinates of the selected window, as well as some
- sample output for the program.
-
- The heart of the program is the for loop that plots 10,000 random pixel
- positions. Notice that we use the % (modulus) operator to select values in
- the range 0 through 639 for X, and 0 to 199 for Y. (This corresponds to
- the 640-by-200 resolution for the specified CGA high-resolution mode.)
-
- The if statement checks for an X and a Y position within the window
- specified by xmin, xmax, ymin, and ymax. Notice that the && logical AND
- operator ensures that each value is greater than or equal to the minimum
- and less than or equal to the maximum. The && between the two expressions
- in parentheses tests the random value to see if it fits in both the X and
- Y ranges.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 5-3 can be found on p.144 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 5-3. Screen coordinates and output for PIXELS.C.
-
- Variations of PIXELS.C
-
- You can create many interesting shapes by substituting different
- conditions in the if statement. The GALAX.C program (Listing 5-9)
- establishes a center point (center_x, center_y) and a radius. The if
- statement in the for loop uses a formula that determines if a point is
- within the circle──if it is, the program plots the pixel.
-
- The result of running GALAX.C (Figure 5-4 on the following page) looks
- more like an ellipse than a circle because in the 640-by-200 mode, pixels
- are spaced together more closely horizontally than they are vertically.
- Because the program must calculate many square roots, it runs a little
- slowly. We use an if (kbhit()) to let you stop the program whenever you
- press a key.
-
- ──────────────────────────────────────────────────────────────────────────
- /* galax.c -- creates an ellipse by selecting */
- /* from random pixels */
- #include <graph.h> /* for graphics */
- #include <math.h> /* for sqrt() */
- #include <conio.h> /* for kbhit() */
- main()
- {
- int pixels, radius = 50;
- double center_x = 320, center_y = 100,
- xpos, ypos;
- srand(0);
- _setvideomode(_HRESBW);
- _setcolor(1);
- for (pixels = 1; pixels < 25000; pixels++)
- {
- /* draws filled ellipse, due */
- /* to dimensions of hi-res screen */
- /* generate random location */
- xpos = rand() % 639;
- ypos = rand() % 199;
- if (sqrt /* is distance within radius? */
- ((xpos - center_x) * (xpos - center_x)
- + (ypos - center_y) * (ypos - center_y))
- < radius)
- _setpixel(xpos, ypos);
- if (kbhit())
- break; /* exit if key pressed */
- }
- getch(); /* freeze screen until key pressed */
- _setvideomode(_DEFAULTMODE);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 5-9. The GALAX.C program.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 5-4 can be found on p.146 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 5-4. Output of GALAX.C.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 6 Functions and Function Calls
-
- One of the great advantages that C offers a programmer is its huge variety
- of library functions, which cover everything from manipulating text to
- controlling memory allocation. The seasoned C programmer soon learns that
- the C library contains most of the tools needed to perform a given task.
- However, the real power of C derives from the ease with which you can
- design customized C functions that perform the specific and unique
- operations your program requires. In this chapter, we will show you how to
- create and use these functions.
-
-
- Functions and Program Design
-
- Every C program must have at least one user-defined function, namely
- main(). Most real-world C programs, however, consist of many user-written
- functions, because the procedure for processing data usually involves many
- different steps. A program that calculates statistics, for example, might
- have to ask the user for data, check the data for validity, store the data
- in memory or on disk, process the data (often according to several
- criteria), and report the results, possibly in a variety of formats. If
- all of this program code were in the main() function, the resulting jumble
- would hamper a programmer trying to visualize where one step ends and the
- next one begins. Debugging the program would be nearly impossible because
- you would have difficulty figuring out which of the intertwined parts
- worked correctly and which ones did not. And if you decided to revise the
- program to add new capabilities, you could not easily find the appropriate
- place to add new code.
-
- Most experienced programmers design programs using a "top-down" approach.
- This method resembles writing an outline for a report. First, list the
- principal ideas or steps. Then divide those ideas or steps into subtopics,
- and continue subdividing until you feel ready to write the actual
- sentences. Using a similar approach, the main() function for our
- statistics program might read:
-
- main()
- {
- data_menu();
- while (more_data)
- {
- get_data();
- check_data();
- store_data();
- }
- process_data();
- report_menu();
- do_report();
- }
-
- Each of the names followed by parentheses in the definition of main() is a
- call to a user-defined function. Notice how this "outline" clearly shows
- the overall flow of the program. First, the program offers the user
- choices in a data-entry menu; then it enters a loop that receives,
- validates, and stores data as long as more data is entered. The data is
- then processed. Another menu lets the user generate a specific type of
- report, and finally the program prints that report.
-
- Note that using separate functions does more than merely keep the parts of
- a program conceptually separate; it also provides an orderly way of
- communicating information between different sections of the program. Each
- function receives information, such as the values of certain variables,
- and after it finishes executing, returns the transformed information to
- another section of the program.
-
- In a C program, any function can call any other function. This means that
- your user-defined functions can call other user-defined functions as well
- as C library functions. For example, the definition of do_report(), shown
- on the opposite page, might call other user-defined functions, each of
- which prints a different kind of report, corresponding to the choices
- offered in the report_menu() part of the program.
-
- do_report()
- {
- switch (choice)
- {
- case 'b' :
- bar();
- break;
- case 'p' :
- pie();
- break;
- case 'l' :
- line();
- break;
- case 't' :
- table();
- break
- }
- }
-
- Normally, you declare each user-defined function in main() before the
- program calls it. The definitions of these user-defined functions usually
- follow the end of the definition of main(). (You also can define groups of
- functions in separate files: We will discuss this in Chapter 12.) Figure
- 6-1 proposes a general outline for a program that declares and uses three
- user-defined functions.
-
- Dividing a program into logically organized user-defined functions also
- lets you develop the program one piece at a time. You can start by putting
- "stub" definitions in the functions, such as:
-
- pie()
- {
- printf("executing pie()\n");
- }
-
- Function
- declarations
- ┌────────────────┐ Function definitions
- │ main ( ) │ ┌──────────────────────────────────────────┐
- │ { │ ┌─┼─────────────────────────┐ │
- │ func1 ( ); │ ─┼─┼───────┐ │ │
- │ func2 ( ); │ ─┘ │┌────────────┐ ┌────────────┐ ┌────────────┐
- │ func3 ( ); │ ───┘│ func1 ( ) │ │ func2 ( ) │ │ func3 ( ) │
- │ ───────── │ │ { │ │ { │ │ { │
- │ ───────── │ │ ─────── │ │ ─────── │ │ ─────── │
- │ ───────── │ │ ─────── │ │ ─────── │ │ ─────── │
- │ ───────── │ │ ─────── │ │ ─────── │ │ ─────── │
- │ } │ │ } │ │ } │ │ } │
- └────────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
-
- Figure 6-1. Outline for a C program with functions.
-
- These let you test the overall control structures of the program (the
- loops, branches, and switches) before you write the actual routines that
- perform the various tasks. Then you can replace functions one at a time
- with their real definitions. After you are certain that a function works
- properly, you can move to the next function. The basic philosophy of this
- type of programming is "divide and conquer."
-
-
- Declaring and Defining a Function
-
- Now that we've discussed the advantages of user-written functions in
- program development, let's look at the mechanics of declaring, defining,
- and using your own functions. Always remember that you must declare your
- functions before you use them. In earlier chapters, when you used the
- #include directive to allow calling C library functions in your program,
- QuickC inserted function declarations and definitions into the program.
- For functions you create yourself, however, you must provide the
- declaration and a definition.
-
- Declaring a Function
-
- Let's declare and define a function that prints an error message. The
- general format of a simple function declaration is as follows:
-
- return_type name();
-
- In the declaration, return_type refers to the data type (int, float, etc.)
- of the value that the function returns. Because our first example is a
- function that does not return a value, we must use the special word void
- as the return type. (The word void, in this instance, doesn't mean
- "invalid," but rather "empty.")
-
- The function name──error, in our example──must be followed by parentheses,
- which hold the parameters the function is designed to use. In the case of
- an error message function, parameters might include a string to print and
- values that would indicate that the message be printed in high-intensity
- text, accompanied by a beep, and so on. You must always include
- parentheses──even if, as in the case of our error() example, you use no
- parameters. (The compiler uses the parentheses to distinguish a function
- from a variable.) Note also that you must use a semicolon at the end of
- the line. Thus, we declare our error message─printing function as follows:
-
- void error();
-
- As with variable declarations, you can declare several functions of the
- same type on one line, as in the following declaration:
-
- void error(), greeting(), warning();
-
- Usually, programmers place the declarations for user-defined functions in
- the definition of main(), before any other statements (except possibly
- comments).
-
- main()
- {
- void error(); /* function declaration */
- other function declarations;
- ...
- other statements;
- ...
- }
-
- However, if a program has many user-defined functions, you might want to
- put the declarations before main(). This enables you to see the
- declarations more easily and separates them from the code of main()
- proper.
-
- Defining a Function
-
- After you declare a function, you must define it with statements that will
- execute when the program calls the function. The first line of the
- function definition essentially repeats the original function declaration:
-
- void error()
-
- However, remember that, with a definition, you use no semicolon at the end
- of the line. The next line should contain an opening brace ({ ); then come
- the statements that define the function; and finally, the definition ends
- with a closing brace (} ). Thus, we write our error() function definition
- as follows:
-
- void error()
- {
- printf("Error!\a\n");
- }
-
- When a statement calls this function, it prints the word Error! and sounds
- a beep. (Notice the \a [alert] escape sequence, which is listed in Figure
- 3-5 on p. 68.)
-
- ──────────────────────────────────────────────────────────────────────────
- ANSI and Function Declarations
- QuickC and other current versions of C do not require you to declare
- functions that return no value or an int value. The new ANSI standard and
- modern programming practice encourage you to declare all functions,
- however. This is to help the reader see how the functions work and to
- allow the compiler to check for inconsistencies between the definition of
- a function and the way it is called. For these reasons, which we explain
- in greater detail later, we declare all functions in our example programs.
- ──────────────────────────────────────────────────────────────────────────
-
- Calling the User-defined Function
-
- We call our user-defined error() function the same way we call a library
- function like printf()──by naming it in a statement in main() or in the
- body of another function. Consider the following example:
-
- main()
- {
- ...
- if ((number < 1) || (number > 9))
- error(); /* function call */
- }
-
- The if statement calls the error() function only if number is either less
- than 1 or greater than 9. Again, note that the function call must include
- the parentheses with the function name.
-
- The DBLBAR.C program (Listing 6-1) calls the user-defined function line()
- to print a double bar before and after a program title. Note that we
- declare line() before the statements in main(), that we call line() twice
- from within main(), and that we define line() following the end of main().
- Figure 6-2 shows the flow of control in this program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* dblbar.c -- prints header using */
- /* line() function */
-
- #define DOUBLE_BAR 205
-
- main()
- {
- void line(); /* declare line() function */
-
- line(); /* call line() function */
- printf("dblbar.c -- prints header using\n");
- printf("line() function\n");
- line(); /* call line() again */
- }
-
- void line() /* function definition */
- {
- int pos;
- for (pos = 1; pos <= 40; pos++)
- putch(DOUBLE_BAR);
- printf("\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-1. The DBLBAR.C program.
-
- ┌──First call
- ┌───────────────────┐ │ and return
- │ main ( ) │ │ ┌ - - - - - - - - - - - - -
- │ { │ │ | |
- │ void line ( ); │ │ | ┌───────────────────┐ |
- ┌─┼line ( ); ────────┼─────────────|───┼void line ( )- - - -
- │ │ printf ("...); │ | │ { │
- │ │ printf ("...); │ | │ int pos; │
- - -│line ( );- - - - -│- - - - - - - │ for ( ) │
- | │ │ } │ │ │ printf ("...); │
- | │ └───────────────────┘ │ ┌───┼─}- - - │
- | │ │ │ │ | │
- | │ Second call─┘ │ │ | │
- | │ and return │ └───────|───────────┘
- | └───────────────────────────────────┘ |
- | |
- └ - - - - - - - - - - - - - - - - - - - - - - - - ┘
-
- Figure 6-2. Flow of control in DBLBAR.C.
-
- You can also follow the flow of this program by using QuickC's debugger.
- Select the Debug option in the Compile dialog box, and then select Trace
- On from the Debug window. When you recompile the program, you see the
- highlighted statement move through main() until it reaches the first call
- to line(). The debugger then highlights the statements in line().
- Highlighting returns to main() with the statement following the call to
- line(). Control shifts in the same manner when the program encounters the
- second call to line().
-
- A Disadvantage in Using Functions
-
- The double-bar program demonstrates the advantage of using functions──they
- reduce the size of the program. Any time we need to draw a line, we simply
- call line() rather than repeat the whole for loop. However, using
- functions has a potential disadvantage. At the machine-code level, each
- time the program calls a function, the current status of the calling
- program (including the contents of CPU registers) has to be saved,
- information must be passed to the function via a memory area called the
- "stack," and various other housekeeping operations must be performed to
- return control to the calling statement. Therefore, calling a function
- involves a lot more "overhead" than merely using a copy of the desired
- code wherever you need it in your program. (The performance difference is
- not noticeable with only a few function calls, but you might notice it
- when you execute a function call thousands of times within a loop.) The
- loss of efficiency is not critical in most cases and usually is far
- outweighed by the benefits of using functions. But keep in mind that the
- more function calls you use, the more important the overhead factor
- becomes.
-
-
- Local and Automatic Variables
-
- A defined function, such as line(), can contain its own variable
- declarations within its definition. Variables declared within a function
- definition are called local variables, and they are accessible only within
- the function in which they are declared. Outside the function braces, the
- variables don't exist. To be accessible to all the functions in your
- program, a variable must be declared globally──outside of any function.
- The extent of a variable's accessibility, or "visibility," is called the
- "scope" of a variable.
-
- In Figure 6-3, note that variables defined in func1() cannot be accessed
- from main(). They are "invisible" to main(), func2(), or anywhere else
- outside of the definition of func1(). For example, the definition of
- line() in DBLBAR.C declares an int variable pos, which is used in the for
- loop. (See Listing 6-1 on p. 152.) Suppose the main() function in
- DBLBAR.C contained the following line:
-
- printf("The variable pos in line() has a value of %d\n", pos);
-
- This line would produce a compiler error, because main() doesn't "know"
- that a variable called pos exists──pos is the private property of the
- line() function.
-
- ┌─────────────┐ ADDRESS
- │ main ( ) │
- │ { │
- │ int n;- - - - - -Visible only 8706
- │ func1 ( );│ in main () (remains
- │ func2 ( );│ until end
- │ } │ of program)
- └─────────────┘
- ┌─────────────┐
- │ func1 ( ) │
- │ { │
- │ int n;- - - - - -Visible only 8694
- │ ─────── │ in func1 () (reused)
- │ ─────── │
- │ } │
- └─────────────┘
- ┌─────────────┐
- │ func2 ( ) │
- │ { │
- │ int n;- - - - - -Visible only 8694
- │ ─────── │ in func2 () (reused)
- │ ─────── │
- │ } │
- └─────────────┘
-
- Figure 6-3. Local variables.
-
- Similarly, if you declare a variable called pos within main(), it is
- private to main() and not accessible from within a function called by
- main().
-
- Because variables defined inside functions are local in C, you can use
- variables with the same name in different functions. Thus, many of your
- program functions can use a variable named count, yet QuickC maintains and
- refers to each one separately.
-
- Variables Used in Functions Are Automatic
-
- Another important characteristic of a local variable is that it is
- "automatic": The variable is created (meaning that internal storage is
- allocated and the address recorded) each time its function is called.
- Conversely, the variable is destroyed and its internal storage released
- when the function ends and control returns to the calling statement. Only
- a local variable can be automatic (and temporary) because the compiler
- knows that it is valid only while the function executes. (A global
- variable must be stored permanently, because the compiler must always
- assume that the program will need its value again.) Automatic variables
- permit more efficient storage allocation because the same block of memory
- can store many temporary variables as the program executes. C programs
- that use local, automatic variables also are smaller than comparable
- programs that make the same variables global.
-
- The LOCAL.C program (Listing 6-2 on the following page) illustrates the
- way local variables work. (Again, refer to Figure 6-3.) The main()
- function declares an int variable, n, and prints its value and internal
- address. The program then calls the func1() and func2() functions. Each of
- these functions also defines a variable called n and prints its value and
- address.
-
- ──────────────────────────────────────────────────────────────────────────
- The Scope of Variables
- In Pascal, you can "nest" function or procedure definitions within each
- other. A variable defined in a function or procedure is not only
- accessible to that function or procedure, but also to any definitions
- nested within the outer definition. This can make questions of the scope
- of certain variables rather complex. In C you cannot nest function
- definitions; therefore, a variable defined within a function is accessible
- only within that function.
-
- If you've programmed in older versions of BASIC, you probably expect all
- variables to be global, that is, accessible throughout the program. You
- also might remember times that this "feature" created nasty bugs. For
- example, you might have used count as the control variable of a loop in
- one subroutine and then days later used another variable called count in a
- different subroutine. Depending on the order in which BASIC called the
- subroutines, hard-to-trace bugs probably resulted because BASIC remembered
- the last value in count when it started the new count. If you are a BASIC
- programmer coming to C, rest assured that QuickC will never confuse the
- count of one function with the count of another.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* local.c -- local variables defined */
- /* within functions */
-
- main()
- {
- int n = 12;
- int func1(), func2();
- printf("n in main(): val %d ", n);
- printf("address %d\n", &n);
-
- printf("Calling func1()\n");
- func1();
- printf("Calling func2()\n");
- func2();
- }
-
- int func1()
- {
- int n = 8; /* local variable */
- printf("n in func1(): val %d ", n);
- printf("address %d\n", &n);
- }
-
- int func2()
- {
- int n = 20; /* local variable */
- printf("n in func2(): val %d ", n);
- printf("address %d\n", &n);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-2. The LOCAL.C program.
-
- The program's output (which may vary from setup to setup) demonstrates
- that QuickC recognizes each variable's correct value and address without
- any confusion:
-
- n in main(): val 12 address 8706
- Calling func1()
- n in func1(): val 8 address 8694
- Calling func2()
- n in func2(): val 20 address 8694
-
- Also notice that n in func1() and n in func2() use the same address. This
- occurs because when func1() ends, it discards its reference to the now
- useless local variable n. When the program calls func2(), QuickC reuses
- the same address for the new func2() local automatic variable n.
-
- Note that the address of the n in main() wasn't reused when the n in
- func1() or func2() was created. Variables declared in main() have no
- special status──the n in main() is as automatic and local as the variable
- in the other functions. Remember, however, that QuickC discards an
- automatic variable only when the function in which it is defined
- terminates. Because main() doesn't end execution until the program itself
- ends, its variables (including n) are not destroyed and reused.
-
- The auto Storage Class
-
- All variables declared within function definitions are, by default,
- automatic. Consider the following example:
-
- void plot_object()
- {
- auto int length, width;
- ...
- }
-
- The variables length and width are automatic because they are declared
- within a function. The auto designation merely reminds us of this. Note
- that auto is not a data type. Rather, it (and the keywords register,
- static, and extern) is what is called a "storage class." It refers to how
- QuickC manages the variable as the program runs. Specify the storage class
- before and in addition to the variable's data type (int in the previous
- example).
-
-
- Static Variables
-
- Occasionally you will need a variable to retain its value after its
- function terminates. To do this, you must declare the variable used in the
- function with the static storage type, as follows:
-
- static int total;
-
- Now the value of total calculated during a previous call is still there
- when you call the function again. You use this storage class to keep
- running totals, for example. Note that a static variable is still local
- and accessible only within the function in which it is defined.
-
- The STATIC.C program (Listing 6-3 on the following page) uses the
- function countline() to count the words and characters in a line of text.
- (It simply counts a word whenever it encounters a space.) The static
- variables chars and words accumulate the counts. Because the variables are
- static, they retain the previous total each time the function is called.
-
- A sample of the program's output follows:
-
- Type some lines of text.
- Start a line with a . to quit.
-
- By now you should be able to
- Words so far 7. Chars. so far 28
- function very well in C!
- Words so far 12. Chars. so far 52
- .
-
- ──────────────────────────────────────────────────────────────────────────
- /* static.c -- demonstrates a static variable */
- /* that holds count of lines, */
- /* words, and characters */
-
- main()
- {
- void countline();
- printf("Type some lines of text.\n");
- printf("Start a line with a . to quit.\n\n");
-
- while (getche() != '.')
- countline(); /* accumulate word and */
- /* line counts */
- }
-
- void countline()
- {
- static int words = 0; /* static variables */
- static int chars = 0;
- char ch;
- ++chars; /* count char typed when */
- /*function was called */
-
- while ((ch = getche()) != '\r')
- {
- ++chars;
- if (ch == ' ')
- ++words;
- }
- ++words; /* count last word */
-
- printf("\nWords so far %d. Chars. so far %d\n", words, chars);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-3. The STATIC.C program.
-
-
- External Variables
-
- You can declare global variables in C when you want two or more functions
- to share relevant values or to communicate with each other by periodically
- changing the value of a common variable. A global variable in C is
- referred to as external because it is defined outside the function
- definitions in the program.
-
- To declare an external variable, simply put its declaration outside of any
- function definition. External variables are usually placed after any
- #includes and #defines, but before the definition of main(). In the
- following example, we declare scale and palette outside of the function,
- making them external (global) and accessible throughout the program. The
- variables length and width, on the other hand, are local and accessible
- only within main().
-
- #include <stdio.h>
- #define VERSION 1.0
-
- int scale = 1.5, /* global variables */
- palette = 1; /* go here */
-
- main()
- {
- int length, width; /* local variables */
- ... /* go here */
-
- The EXTERNAL.C program (Listing 6-4) shows how you might use an external
- variable. We declare the variable length before main() to make it an
- external variable. After the user supplies a value for length, main()
- calls three functions: square(), triangle(), and circle(). Each of these
- functions accesses and uses the value of length to calculate the
- appropriate area.
-
- ──────────────────────────────────────────────────────────────────────────
- /* external.c -- shows an external variable */
-
- #define PI 3.14159
- int length; /* external (global) variable */
- /* declared before main() */
-
- main()
- {
- void square(), triangle(), circle();
-
- printf("What length do you want to use? ");
- scanf("%d", &length);
-
- square(); /* calculate areas */
- triangle();
- circle();
- }
-
- void square()
- {
- float area;
- area = length * length;
- printf("A square with sides of %d has an area of %f\n",
- length, area);
- }
-
- void triangle()
- {
- float area;
- area = (length * length) / 2;
- printf("A right triangle with sides of %d has an area %f\n",
- length, area);
- }
- void circle()
- {
- float area;
- area = (length * length * PI);
- printf("A circle with radius of %d has area of %f\n",
- length, area);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-4. The EXTERNAL.C program.
-
- Try to resist the temptation to make all your variables external; this
- invites the problems we discussed earlier. Variables used in only one
- function should remain local. Variables used by only two or three
- functions might be better handled as parameters passed from one function
- to another. (We will discuss function parameters shortly.)
-
-
- Register Variables
-
- Let's look at one more storage type for variables, the register type.
- Microprocessors such as the 8088 and 80286 have several built-in storage
- locations called registers. A program can store and retrieve data from a
- register more quickly than from a location in regular memory, where C
- usually stores variables. As a result, you gain a performance advantage by
- assigning a register to a frequently used variable in a time-sensitive
- application.
-
- The only problem with using registers is that usually there aren't enough
- registers to store all the data of a given operation. As shown in Figure
- 6-4, the IBM family of Intel microprocessors (8088, 8086, and 80286) have
- four general-purpose, 16-bit registers that hold data being manipulated at
- the machine level.
-
- 16 bits
- (2 bytes)
- │
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- ┌─────────────┬─────────────┐
- │ AH │ AL │ AX
- ├─────────────┼─────────────┤
- │ BH │ BL │ BX
- ├─────────────┼─────────────┤
- │ CH │ CL │ CX
- ├─────────────┼─────────────┤
- │ DH │ DL │ DX
- └─────────────┴─────────────┘
- 8 bits 8 bits
- (1 byte) (1 byte)
-
- Figure 6-4. General-purpose registers for the Intel 8086 family.
-
- In other languages, the compiler software determines which variables, if
- any, will be assigned registers. In C, however, you can tell the compiler
- to assign a register to a specific variable when you declare that
- variable, as follows:
-
- register int count;
-
- This declaration tells QuickC to store the count variable in a CPU
- register. (You can't specify which physical register to use, however.)
- Because registers in the 8088, 8086, and 80286 cannot store variables with
- values larger than two bytes, only char and int variables can be
- accommodated. Additionally, you cannot declare external or static
- variables as register variables, because registers cannot serve as
- permanent storage locations. Finally, QuickC assigns only two variables
- per function as register variables: You can make more declarations, but
- only the first two are honored. In fact, depending on the CPU workload,
- there is no guarantee that both, or even one, of the registers will be
- available. If speed is important to your program, try the declarations to
- see if they help.
-
- Which variables should you declare to be register variables? Obvious
- candidates include loop control variables or variables that are part of
- statements performed in a loop. But even though most loops execute many
- times, you shouldn't specify these variables as register storage type.
- QuickC uses optimization techniques to try to produce the fastest machine
- code possible from your program, and one of its basic speedup techniques
- is the assigning of registers to variables in loops. Thus, you gain little
- by specifying these as register variables──in fact, you might even confuse
- the compiler and end up with less efficient code. The best variables to
- specify as register type are variables that are not involved with loops
- yet are used three or more times each time the function is called.
-
-
- Passing Information to a Function
-
- Thus far, our user-defined functions have not required that any
- information be passed when we called them. Most functions, however,
- require one or more items of information, called arguments, or parameters.
- This is often true of the QuickC core library functions: printf() needs to
- know what to print and how to print it; scanf() needs to know what
- information to get from the user and where to store it; and so on. Indeed,
- because it uses no parameters, the line() function in Listing 6-1 on p.
- 152 is extremely limited. As it stands, it always prints a line of 40
- characters. However, suppose we want 20 or 60 characters. Let's define
- this function so that when you call it, a parameter tells it how long the
- line should be:
-
- void line (length) /* length is parameter */
- int length; /* declaration of parameter */
- {
- int pos;
- for (pos = 1; pos <= length; pos++)
- putch(DOUBLE_BAR);
- }
-
- When you call this function, the length parameter (placed in parentheses
- in the function definition) receives the current value of the variable
- length. The items named in the definition of a function are called "formal
- parameters." The definition of the line() function includes one formal
- parameter, length. When you call a function, you must include an actual
- value, such as a constant or a variable, for each formal parameter the
- function requires. Thus, if we use the statement line(10); to call the
- line() function, the value 10 is the "actual parameter" corresponding to
- the formal parameter, length. Inside the line() function, length becomes a
- variable of type int with the value 10.
-
- Notice that we declare the parameter length as an int in the function
- definition for line(). This declaration serves the same purpose as the
- declaration of an ordinary variable: It tells the compiler what type of
- data the parameter represents. Notice also, however, that unlike the
- declaration of an ordinary variable, which follows the opening brace of
- the function definition body, the declaration of a function parameter
- precedes the opening brace. An alternative syntax, favored by many
- programmers, places the parameter type before the name within the
- parentheses, as in the following:
-
- void line (int length)
-
- Note that when a function uses parameters, the parameter should also be
- put in the declaration of the function in or before main(). Thus, in a
- program that uses line(), the declaration would be:
-
- main()
- {
- void line (length);
- ...
- }
-
- or:
-
- void line (int length);
-
- It's easiest to use the same form for the function declaration and for the
- first line of the function definition──but remember that the declaration
- ends with a semicolon, whereas the definition does not.
-
- Once you declare the parameter length, the line() function refers to it as
- though it had been declared and initialized as an ordinary variable. In
- our example, length sets the limit for the loop condition, thus
- controlling the length of the line.
-
- Parameters make functions versatile. The program now can call line() and
- set the length of the line with any appropriate value: a number, a
- variable name, a #define constant, or an expression.
-
- By the way, you can call the line() function using a variable with the
- same name as the parameter (length). The variable in the function that
- calls line() belongs to the calling function, and the parameter "belongs"
- to the called function. They are, in effect, separate local variables──the
- only connection between them is the value.
-
- Let's look at the ALERT.C program (Listing 6-5), which uses a function
- with a parameter. The beep() function uses a parameter named times to
- control the number of times a beep is sounded. When the function executes,
- the if statement checks to see if times has a value in the range of 1 to
- 4. If it doesn't, the program prints an error message. Notice that the
- error message prints the name of the function and the value that length
- passed to it. Including this type of information helps you debug your
- programs.
-
- If the value of times is in the correct range, a for loop with the limit
- of times generates the correct number of beeps. Try changing the calling
- statement in main() to beep(0) or beep(100).
-
- ──────────────────────────────────────────────────────────────────────────
- /* alert.c -- sounds alarm by calling a */
- /* beep() function with a parameter */
-
- main()
- {
- void beep(times); /* function declaration */
- printf("*** Alert! Alert! ***\n");
- beep(3); /* call beep() with parameter */
- }
-
- void beep(times)
- int times; /* declare function parameter */
- {
- int count;
-
- /* check that parameter is between 1 and 4 */
- if ((times < 1) || (times > 4))
- {
- printf("Error in beep(): %d beeps specified.\n",
- times);
- printf("Specify one to four beeps");
- }
- else /* sound the beeps */
- for (count = 1; count <= times; count++)
- printf("\a"); /* "alert" escape sequence */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-5. The ALERT.C program.
-
- How Parameters Work
-
- Now that you know how to use parameters, let's take a detailed look at how
- they work. Figure 6-5 on the following page shows what happens when a
- program calls a function that has a parameter. When it executes the
- function call line(short), QuickC places the value of the variable short
- in an internal memory area called the "stack" and passes control to the
- line() function. The line() function "knows" from its definition that it
- should expect one parameter, called length. It also knows that it is an
- int value. Thus, the function reads two bytes (an int) from the stack and
- creates a temporary storage location for them. This value can now be
- accessed by length within the line() function. It is basically a local,
- automatic variable that behaves as if it had been declared and initialized
- within the function definition.
-
- Caller
- ┌──────────────────────┐
- │ main ( ) │ ┌─────────Discarded when
- │ int short=10; │ │ function terminates
- │ { ││ │ │
- │┌───────────┘└───────┐│ │
- ││ line (short); ├┼───10 ───┐
- │└────────────────────┘│ ──── │ Called function
- │ ───────────────── │ ──── │ ┌──────────────────────┐
- │ ───────────────── │ ──── │ │ void line (length) │
- │ } │ ──── └──│ int length; (=10) │
- └──────────────────────┘ Stack │ { │
- │ ────────── │
- │ ────────── │
- │ ────────── │
- │ } │
- └──────────────────────┘
-
- Figure 6-5. Function parameters and the stack.
-
- Note that the parameter in a function call is not normally affected by the
- operation of the function. The function operates with a local variable it
- creates, not with the variable in the calling statement.
-
- Multiple User-written Functions
-
- The TIMER2.C program (Listing 6-6) uses a callable custom function,
- delay(), to improve the timer program we used in Chapter 4. The main()
- function asks the user for the number of seconds to be timed and the
- interval by which the program should count off the time, beeping once at
- each interval. The while loop repeatedly calls delay(interval) to wait for
- interval seconds; then it sounds the beep (by calling the beep() function)
- and prints the elapsed seconds. Be sure you understand the positions and
- components of the function declarations, the function definitions, and the
- parameter declarations for the delay() and beep() functions.
-
- ──────────────────────────────────────────────────────────────────────────
- Passing Parameters in Pascal and C
- In Pascal, you can pass either the value of a variable or its address in a
- parameter. (The first is a "call by value," the second is a "call by
- reference.") In C, function parameters are always passed by value: The
- variable itself is never passed. The value can, however, represent the
- address of a variable. In this case the variable itself, and not merely
- the value, can be accessed and changed by the called function. (This is
- called a "pointer," which we discuss in Chapter 8.)
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* timer2.c -- interval timer */
- /* calls delay(), uses beep */
-
- main()
- {
- /* function declarations */
- void beep (times);
- void delay (seconds);
-
- /* variable declarations */
- int seconds, interval, tick;
-
- printf("Set for how many seconds? ");
- scanf("%d", &seconds);
- printf("Interval to show in seconds? ");
- scanf("%d", &interval);
- printf("Press a key to start timing\n");
- getch();
-
- tick = 0; /* run "clock" for */
- while (tick < seconds) /* time specified */
- {
- delay(interval); /* wait interval seconds */
- tick += interval;
- printf("%d\n", tick);
- beep(1);
- }
- beep(3);
- }
-
- void delay(seconds)
- /* wait for number of seconds specified */
- /* See TIMER.C in chapter 4 for details */
- /* on the library function time(). */
-
- int seconds; /* parameter declaration */
- {
- /* variable declarations */
- long start, end, /* starting and ending times */
- /* measured in seconds since */
- /* Jan. 1, 1970 */
- ltime; /* used to get value from time function */
-
- start = time(<ime); /* get system-elapsed seconds */
- /* since 1-1-70 */
- end = start + seconds; /* calculate alarm time */
-
- do
- {;} /* null statement for loop body */
- while (time(<ime) < end); /* wait for end of time */
- }
-
- void beep(times)
- /* parameter declaration */
- int times;
- {
- /* variable declaration */
- int count;
-
-
- /* check that parameter is between 1 and 4 */
- if ((times < 1) || (times > 4))
- {
- printf("Error in beep(): %d beeps specified.\n",
- times);
- printf("Specify one to four beeps\n");
- }
- else /* sound the beeps */
- for (count = 1; count <= times; count++)
- printf("\a"); /* "alert" escape sequence */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-6. The TIMER2.C program.
-
- The delay() function has the same code as the TIMER.C program in Chapter
- 4, except that it adds the parameter seconds to the current time to
- determine what time should be compared with the system time in the do
- loop. A sample run follows:
-
- Set for how many seconds? 30
- Interval to show in seconds? 5
- Press a key to start timing
- 5────────────────────────────────────────────────Beeps after each number
- 10
- 15
- 20
- 25
- 30
-
- Putting User Functions in an Include File
-
- Notice in TIMER2.C that the definition of the beep() function follows that
- of delay(). We've used the beep() function before, and we will use it
- again in several other programs. You would save program space if you put
- the definitions of beep() and related functions (perhaps line(), another
- function that prints characters in reverse video, a function that draws a
- box around a string, and so on) in a header file. Then you could include
- these functions in any program without typing or pasting them in by hand.
- Simply save the definitions in a file that uses the traditional .h
- extension (hilite.h, for example). Then insert the line #include
- "hilite.h" at the beginning of your program. (You can use the existing
- INCLUDE subdirectory, but your file system would be better organized if
- you created a subdirectory called INCLUDE\USER to hold these user-written
- include files.)
-
-
- Functions with Many Parameters
-
- Functions can use any number of parameters. The actual parameters
- specified in the function call parentheses are assigned, in order, to the
- corresponding formal parameters in the function definition.
-
- As a general rule, however, functions that use more than five parameters
- become cumbersome to use. If you must use more than this number,
- reconsider the operations your function performs and try to do the
- operations in two or more simpler functions. After all, one of the chief
- benefits of using functions is that they keep each piece of a program at a
- manageable size. The LINES.C program (Listing 6-7) uses five parameters
- in its line() function, which draws a colored line on the screen. (To run
- this program, you need a CGA, EGA, or Hercules board.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* lines.c -- calls line() with */
- /* five parameters */
-
- #include <graph.h>
-
- main()
- {
- void line (x1, y1, x2, y2, color);
-
- int x1, x2, y1, y2, i, color;
-
- _setvideomode(_MRES16COLOR); /* 320-by-200 16 col. */
- srand(2); /* new random seed */
- for (i = 0; i < 100; i++)
- {
- x1 = rand() % 319; /* random coordinates */
- x2 = rand() % 319;
- y1 = rand() % 199;
- y2 = rand() % 199;
- color = (rand() % 14) + 1; /* random color 1-15 */
- line(x1, y1, x2, y2, color); /* draw a line */
- }
- while(!kbhit()); /* wait for key to be hit */
-
- _setvideomode(_DEFAULTMODE); /* restore video mode */
- }
-
- void line (x1, y1, x2, y2, color)
- int x1, y1, x2, y2, color;
- {
- _moveto(x1, y1); /* position at first endpoint */
- _setcolor(color);
- _lineto(x2, y2); /* draw line to second endpoint */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-7. The LINES.C program.
-
- The line() function draws a line between the points (x1,y1) and (x2,y2)
- using a specified color. As you learned from PIXELS.C in Chapter 5, you
- must use a system of coordinates to specify pixel locations on the
- graphics screen. This program uses the 320-by-200, 16-color mode, with
- coordinates starting at (0,0) in the upper-left corner of the screen and
- ending with (319,199) in the lower-right corner.
-
- After the program sets the video mode and initializes the random number
- generator with the QuickC library function srand(2), a for loop executes.
- The body of the loop generates random sets of endpoints for lines. (The
- QuickC random number function rand() generates random integers between 0
- and 32,767. To produce a random number between 0 and a number n, we use
- the expression rand() % n. Because the modulus operator gets the remainder
- by dividing the first number by the second, the result is a number greater
- than or equal to 0 [no remainder] and less than the second number.) A
- similar expression generates a random color between 1 and 15. (We can't
- use color 0 because that is the background color──a line drawn in that
- color would be invisible.)
-
- Finally, the program calls line() and passes five parameters: the two
- pairs of endpoint coordinates and the color number. Notice that commas
- must separate the parameters. The line() function draws the line by first
- calling the QuickC graphics function _moveto() to position the cursor at
- the randomly specified point (x1,y1). Another graphics function,
- _setcolor(), sets the current drawing color to the value of color.
- Finally, the QuickC _lineto() function draws the line to the second
- endpoint (x2,y2). The for loop in main() then repeats the process to draw
- 100 random lines in random colors.
-
-
- Functions That Return Information
-
- Sending values to a function is only one way that information flows
- between a calling program and a function in C. A function can also send
- information to the program. For example, in the expression
-
- ch = getche()
-
- getche() returns a value (the character) to the calling statement, which
- in turn assigns the value to the variable ch. To have a function return a
- value to its caller, add a statement to the function definition that
- consists of the keyword return with the value to be returned enclosed in
- parentheses, as follows:
-
- return (value);
-
- Replace value with any information you want the function to return──the
- value of a variable, the result of a calculation, a character the function
- has read, or anything else that can be expressed using one of C's data
- types. When the flow of execution reaches a return statement, the function
- immediately terminates, returning the specified value to the calling
- statement. (A function can have several return statements to return
- different values under different conditions, such as in the branches of an
- if or switch statement. However, the function always terminates at the
- first return statement it encounters.) In the calling statement, the
- returned value replaces the function call, and execution of the statement
- continues.
-
- Let's look at a very simple example, a function that accepts a quantity in
- yards, converts it to feet, and returns the result:
-
- int ytof(yards)
- int yards;
- {
- return(yards * 3);
- }
-
- This function takes the number of yards passed to it through its formal
- parameter yards. The return statement calculates the number of feet with
- the expression yards * 3 and returns the value to the caller. Suppose the
- calling program uses the following statement:
-
- distance = ytof(course_length);
-
- and assume that the user supplies a course_length of 750 yards. When the
- statement calls ytof(), it passes the value of course_length to the ytof()
- function. The function returns 2250 (750 * 3), which replaces the function
- call in the statement. The statement now reads:
-
- distance = 2250;
-
- and the assignment operator assigns this value to distance.
-
- Also note that the definition of the ytof() function begins not with the
- type void but rather with the type int. The return type void signifies
- that the function does not return a value. (We also must declare the
- parameter yards to be an int in the definition of ytof().)
-
- Now let's look at a more useful example. Some languages have a built-in
- exponentiation operator; C does not. However, the math.h include file
- contains a function called pow() that you can call as pow(x,y). It raises
- the first parameter (x) to the power specified in the second parameter
- (y). Because this function uses double values, it can handle both integer
- and floating-point values with great precision. The EXPO.C program
- (Listing 6-8 on the following page) creates an integer version of this
- function that can respond to various types of input.
-
- The expo() function takes two parameters (the number to be raised to a
- power and the power to raise it to) and returns a value to the calling
- statement. However, part of designing functions that return values is
- deciding how to handle special cases. This program must be able to handle
- three special cases: an exponent less than 0 (negative), an exponent equal
- to 0, and an exponent equal to 1. Thus, we designed EXPO.C to respond to
- valid inputs, special inputs, and error conditions with appropriate
- messages and return values.
-
- ──────────────────────────────────────────────────────────────────────────
- /* expo.c -- uses exp() function to */
- /* calculate powers */
-
- main()
- {
- int expo(number, power);
- int number, power;
-
- printf("Enter a number: ");
- scanf("%d", &number);
- printf("Raise to what power? ");
- scanf("%d", &power);
-
- printf("Result: %d", expo(number, power));
- }
-
- int expo(number, power)
- {
- int count, value;
- int total = 1; /* store value of calculation */
- if (power < 0) /* reject negative exponents */
- {
- printf("Error in expo(): negative exponent\n");
- return(0);
- }
-
- if (power == 0) /* any number to 0 power is 1 */
- return(1);
-
- if (power == 1) /* any number to 1 power is itself */
- return(number);
-
- /* calculate for power > 1 */
- for (count = 1; count <= power; count++)
- total *= number;
- return(total);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-8. The EXPO.C program.
-
- A negative exponent results in a fraction that is less than 1. For
- example, 2 to the -3 power is the same as:
-
- 1 1
- ─── which is equal to ─── or 0.125
- 2^3 8
-
- This function handles only positive powers because it uses int type
- variables that can't handle fractions. The first if statement tests the
- power parameter and prints an error message if it is less than zero. The
- return statement returns a value of zero to the calling program, and the
- function terminates.
-
- Now let's look at the remaining two cases in expo(). If the power
- specified in calling expo() is 0, a return statement returns 1 (any number
- to the zero power is one). If the power specified is 1, expo() returns the
- number itself.
-
- Finally, a for loop calculates all other cases (positive powers greater
- than 1). Because we initialized total to 1 at the beginning of the
- function, the expression total *= number in the for loop multiplies number
- by itself power times.
-
- The main() function simply lets you test the expo() function by assigning
- it a number and a power.
-
-
- Recursion
-
- Thus far, calling functions and returning values from them has been a
- simple and straightforward matter. However, you can use function calls in
- a way that disturbs these rules slightly──with amazing results. Recursion
- is an idea that some find difficult to grasp at first, yet a little
- perseverance will lead you to a programming tool of great beauty,
- elegance, and power. Here's how it works: In C, any function can call any
- other function. In fact, a function can call itself──and that is the
- essence of recursion.
-
- A classic example of recursion is the calculating of the factorial of a
- number. Recall that the factorial of a number is the product of all the
- integers between 1 and that number, inclusive. For example, the factorial
- of 4 (written by mathematicians as 4!) is 1 * 2 * 3 * 4, or 24.
-
- A program could calculate a factorial by the "brute force" method, using a
- for loop and multiplying the loop control variable by the previous total.
- But there is another way to calculate factorials. Consider that 4! = 4 * 3
- * 2 * 1, and 3! = 3 * 2 * 1. From this we can deduce that 4! = 4 * 3!, or
- in general terms, that the factorial of a number n is equal to n * (n -
- 1)!, which in turn is equivalent to n * (n - 1) * (n - 2)!, and so on.
- Eventually we get to 0!, which, by definition, is equal to 1.
-
- ──────────────────────────────────────────────────────────────────────────
- Special Return Values and Error Numbers
- Why return a value if an error occurs? Because the value returned can warn
- the caller that an error has occurred. The warning value selected is a
- value that the function will not return by normal operation. If normal
- operation of the function returns a positive number, a number such as 0 or
- -1 sometimes indicates an error. For functions that don't normally return
- a number, the return value usually indicates something relevant about the
- function's operation: For example, the return value of scanf() is the
- number of data fields read, and a 0 indicates an error. Other functions
- return 0 if their operation was successful; a nonzero return value not
- only indicates an error but also represents an error number that specifies
- the precise problem. As you continue to work with QuickC, you will become
- familiar with how library functions handle errors and what the return
- values mean.
- ──────────────────────────────────────────────────────────────────────────
-
- Figure 6-6a depicts the calculation of 4! and then generalizes the
- calculation of a factorial in Figure 6-6b. Note that you keep breaking
- down the expression by subtracting 1 from the current value of n, taking
- its factorial, and multiplying it by the preceding value. Eventually the
- expression becomes equal to n - n, or 0. But 0! = 1, so we no longer need
- to break down n any further. Multiplying all the values together gives the
- factorial of the original number n.
-
- The RECURSE.C program (Listing 6-9) demonstrates how to use this
- recursive method to calculate factorials.
-
- 4!
- / \
- 4 * 3!
- / \
- 3 * 2!
- / \
- 2 * 1!
- / \
- 1 * 0!
- │
- │
- 1
- 4! = 24
-
- (A) BREAKING DOWN 4!
-
-
- n!
- / \
- n * (n-1)! ┌───────────────────┐
- / \ ▒▒▒▒▒▒▒▒│ (n-(n-1) * (n-n)! │
- (n-1) * (n-2)! ▒ │ = 1 * 0! │
- / \ ▒ │ = 1 * 1 = 1 │
- (n-2) * (n-3)! ▒ └───────────────────┘
- . ▒
- . ▒
- . ▒
- (n-(n-1) * (n-n)!
-
- (B) BREAKING DOWN n!
-
- Figure 6-6. Breaking down factorial expressions.
-
- ──────────────────────────────────────────────────────────────────────────
- /* recurse.c -- demonstrates recursion */
-
- int level = 1; /* recursion level */
- main()
- {
- int num, result;
-
- printf("Factorial of what number? ");
- scanf("%d", &num);
- result = factorial(num);
- printf("Result is: %d\n", result);
- }
-
- factorial(int number)
- {
- int result;
- printf("entering: ");
- printf("level %d. number = %d. &number = %d\n",
- level++, number, &number);
-
- if (number == 0)
- result = 1;
- else
- result = number * factorial(number - 1);
-
- printf("exiting : ");
- printf("level %d. number = %d. &number = %d. ",
- --level, number, &number);
- printf("result = %d\n", result);
-
- return(result);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-9. The RECURSE.C program.
-
- A Recursive Function at Work
-
- Let's examine how the factorial(number) function in RECURSE.C works. An if
- statement terminates the function and returns 1 if number is 0 (because 0!
- is 1). For any other value of number, the else branch executes
-
- result = number * factorial(number - 1);
-
- This is a recursive call because factorial() is actually calling itself.
-
- Now let's follow execution from start to finish. The main() function makes
- the first call to factorial() with the value of number. Because number
- initially is not 0, factorial() executes the else branch and calls itself
- with number - 1. In this new call, execution again encounters the else
- branch, and another call to factorial() results. Although this call also
- uses number - 1, the number here is actually the value factorial()
- received from its previous call, or number - 2.
-
- As a result of the repeated calls of factorial() to itself, the decreasing
- values passed with each call accumulate on the stack (because the values
- passed to a function call are not removed from the stack until the call
- terminates). Also, remember that each call to factorial() creates a new
- set of the automatic variables number and result. These variables
- accumulate (one set for each call) until the function terminates. It is as
- though QuickC places a "bookmark" in each function call when the next call
- executes so that the compiler can keep track of what remains to be done in
- each.
-
- The printf() statements in the program record the above process, as
- follows:
-
- Factorial of what number? 4
- entering: level 1. number = 4. &number = 8720
- entering: level 2. number = 3. &number = 8706
- entering: level 3. number = 2. &number = 8692
- entering: level 4. number = 1. &number = 8678
- entering: level 5. number = 0. &number = 8664
- exiting : level 5. number = 0. &number = 8664. result = 1
- exiting : level 4. number = 1. &number = 8678. result = 1
- exiting : level 3. number = 2. &number = 8692. result = 2
- exiting : level 2. number = 3. &number = 8706. result = 6
- exiting : level 1. number = 4. &number = 8720. result = 24
- Result is: 24
-
- Notice the new address for each call's version of the number variable:
- This proves that separate automatic variables are being created. (The
- actual addresses the program returns may vary from setup to setup.)
-
- The "turning point" in the recursive process occurs when number decreases
- to 0, the call factorial(0) is made, and the if branch finally executes.
- Finally the function returns to the caller (the preceding version of
- factorial()), after assigning the value 1 to result. Now the preceding
- call can "pick up its bookmark" and replace factorial(number - 1) with 1,
- multiply it by the value of number that was saved in its automatic
- variable, and then return this value to its preceding caller. You can see
- this happening in the second half of the output listing: The calls move
- back through the recursion levels, back through the addresses of the
- accumulated automatic variables, with each result being multiplied by the
- preceding one, until the function returns to main() with the correct
- result, 24.
-
- Recursion and Stack Size
-
- Some problems are naturally recursive──searching through directories and
- their subdirectories, parsing commands into subcommands, or working with
- tree structures. One thing to keep in mind, however, is that recursion
- uses a lot of memory for storing automatic variables and the stack. (The
- stack holds not only the parameters passed for each call but also the
- register values and return addresses.) Try running RECURSE.C with larger
- input numbers. You can only use numbers to 7 before type int, which stores
- the result, overflows. If you rewrite the program to use long, you can use
- numbers to 16. With type double, you can generate some truly impressive
- factorials, but trying to generate 62! causes a stack overflow error. The
- stack, which by default can store 2048 bytes, simply cannot hold any more
- recursions. (You can specify a larger stack size by selecting Set Runtime
- Options from the Run menu. Still, there is a limit in our medium memory
- model because the stack and the program data share one 64 KB segment. In
- Chapter 11, we also will show you how to use a compiler command-line
- switch to use memory models in which the stack has an entire 64 KB segment
- to itself.)
-
-
- Noninteger Functions
-
- The value returned by a function does not have to be an integer type.
- Functions can return a float, a char, or any other standard C data type.
- So far, we have declared and defined functions using a return data type,
- such as:
-
- int expo(number, power)
-
- An older style of C programming omits the return type when the function
- returns an int, because the compiler defaults to int if no type is
- specified. We base our style on the new ANSI standard, which encourages
- declaring return types for all functions and using void for functions that
- do not return values. For example, we might define a function that
- calculates the cube root of a number as:
-
- float cube_root(number)
-
- This specifies that the cube_root() function returns a value of type
- float. Remember that we also must declare this function in or before
- main():
-
- main()
- {
- char response;
- int x, y;
- float result;
- float cube_root(number); /* function declaration */
-
- The GETYN.C program (Listing 6-10 on the following page) demonstrates the
- declaration and use of a noninteger function. It defines a function,
- getyn(), that prompts for a "yes or no" answer, checks to make sure the
- character entered by the user is either a y or an n, and returns the
- entered character. We declare the function at the start of main() and
- define it as follows:
-
- char getyn()
-
- because it returns a value of type char. Notice that this function does
- not use a parameter. Although many functions that return values, such as
- the expo() function, require parameters, some functions receive their
- information not from the calling statement but from some other source. In
- the case of getyn(), and indeed with the standard functions that read
- characters (getch(), getche(), and so on), the user supplies the value.
-
- ──────────────────────────────────────────────────────────────────────────
- /* getyn.c -- calls char function getyn() */
- /* with error checking */
-
- #define TRUE 1
-
- main()
- {
- char ch;
- char getyn();
-
- printf("Do you want to continue? ");
- if ((ch = getyn()) == 'y')
- printf("Answer was y\n");
- else
- printf("Answer was n\n");
- printf("Value of ch was %c\n", ch);
- }
-
- char getyn()
- {
- char ch;
- while (TRUE)
- {
- printf(" (y or n) ");
- ch = getche();
- printf("\n");
- if ((ch == 'y') || (ch == 'n'))
- /* valid response, break out of loop */
- break;
- /* give error message and loop again */
- printf("please enter ");
- }
- return(ch);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-10. The GETYN.C program.
-
- In the getyn() function, a while loop prompts the user to enter a y or n,
- a getche() gets a character, and an if statement checks to see if the
- character is a y or an n. If it is, a break statement exits the loop, and
- the return statement returns the character. If the character is something
- other than y or n, a prompt asks the user to reenter a value, and then the
- loop repeats. Putting an input-type statement in a loop provides a
- framework for error checking.
-
- The getyn() function is a handy tool that you can use in place of getch()
- or getche() whenever you want the user to enter a valid response to a
- yes/no question.
-
-
- Function Prototypes
-
- We've seen that you can declare the return type for a function at the
- beginning of your program, such as:
-
- float distance(x1, y1, x2, y2);
-
- This declaration tells QuickC that your function returns a float value.
- That takes care of the value coming out of the function. But what about
- the values going into the function──the function parameters?
-
- As it stands, this definition does not specify a data type for the
- parameters. Consequently, we might design the function to work with
- integer values and then accidentally give it some double values as real
- parameters. If you don't specify the data type of the function parameter,
- QuickC isn't even aware of the potential problem.
-
- The MIXTYPES.C program (Listing 6-11) shows what can happen when the type
- of data passed to the function does not match the expected type.
-
- We wrote this program in the older C style: It has no function declaration
- and only the minimum function definition. After all, the examine()
- function merely prints its parameter. (It expects a parameter of the
- default int type, hence the "%d" format specifier in the printf() control
- string.)
-
- In main() we incorrectly declare the variable n as float and call
- examine() with it.
-
- ──────────────────────────────────────────────────────────────────────────
- /* mixtypes.c -- shows problem with calling */
- /* a function with wrong type parameter */
-
- main()
- {
- /* didn't bother to declare int function */
- float n = 5.0;
- int i;
-
- printf("n in main() is %f\n", n);
- i = examine(n); /* pass float to function */
- printf("examine() returned n as %d\n", i);
- }
-
- examine(num) /* function didn't declare return type */
- {
- printf("examine() says n is %d\n", num);
- return(num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-11. The MIXTYPES.C program.
-
- What happens when you run MIXTYPES.C?
-
- n in main() is 5.000000
- examine() says n is 0
- examine() returned n as 0
-
- The printf() statement in main() verified a float type. But when we try to
- print the value of this parameter inside examine(), we see that its value
- is now 0. Because we didn't specify a type for the num parameter in
- examine(), the float passed to the function without comment. However,
- because the function expects num to be an int (the default), the float is
- fetched from the stack as if it were an int. (Treating the 4-byte value
- 5.0 as a 2-byte int returns two zero bytes, or 0.) Finally, examine()
- returns this incorrect value to main().
-
- How can we avoid this problem? In Chapter 3, we discussed strategies for
- ensuring sensible type conversions, such as using type casts. Here we need
- to tell QuickC the data type of the function's parameters. You do this by
- providing a complete declaration called a function prototype.
-
- A function prototype declares the name of the function, its return type,
- the data type of each parameter, and, optionally, the parameter's name.
- Below are some sample declarations:
-
- int factorial(int number);
- int expo(int number, int power);
- void line(int x1, int y1, int x2, int y2, int color);
- char getyn(void);
-
- These are all functions we used earlier in this chapter. Although you have
- already used return type declarations, the full ANSI prototypes add an
- additional specification──the data type for each formal parameter. The
- prototype for factorial() indicates that it returns a value of type int
- and accepts one parameter of type int. The prototype for expo() specifies
- that it returns an int and accepts two int parameters.
-
- ──────────────────────────────────────────────────────────────────────────
- Type Checking in Pascal and C
- In the type-mixing situation described above, a Pascal compiler would show
- an error. Pascal checks actual parameter types against the expected
- parameter types and is very strict about making you define types for
- everything. The traditional C philosophy, on the other hand, expects the
- programmer to anticipate problems carefully, so the compiler permits the
- mixing of function parameter types. Thus, because C does not force you to
- declare types for function parameters, it often cannot tell you that
- anything is wrong when the types of the actual and expected parameters
- don't match. If you follow modern C programming practice and define
- function return and parameter types, the compiler will alert you to many
- potential problems.
- ──────────────────────────────────────────────────────────────────────────
-
- The line() function uses a return type of void because it does not return
- a value; it does, however, take five int parameters. Finally, getyn()
- returns a char but has a parameter of void, which signifies that the
- function takes no parameters. In addition, the prototypes specify the
- names of the parameters. (Although the names are optional──you could say,
- for example, int factorial(int)──we recommend using the parameter names in
- the declaration so that all the information is in one place.)
-
- The beginning of the function definition should also contain the function
- return type, function name, and the list of formal parameters (the
- parameter names), as in the following:
-
- int factorial(int number)
- int expo(int number, int power)
- void line(int x1, int y1, int x2, int y2, int color)
- char getyn(void)
-
- You also could declare the parameter types separately on the following
- lines. (Again, the void in parentheses after getyn signifies that the
- function takes no parameters. Also, notice that the function definitions
- do not end with semicolons.)
-
- Advantages of Using Prototypes
-
- Using prototypes might involve a little more thought and a little more
- typing, but it offers many advantages, which is why the ANSI C standard
- and QuickC support it. First, the complete prototype contains all of the
- information you need to use the function: what you can put in and what you
- can expect to get out. Indeed, if you look up a library function using the
- on-line help, you will find the complete prototype prominently displayed.
-
- It is important to note that if you use prototypes, QuickC will check both
- the type and number of the parameters in your function calls against the
- type and number of the parameters you specify in the prototype. In cases
- where types are mixed, QuickC will try to promote smaller to larger types.
- (See the examples in Chapter 3.) If you use the wrong number of
- parameters, QuickC will display an error message.
-
- The PROTO.C program (Listing 6-12 on the following page) is a revision of
- the problem program MIXTYPES.C, rewritten to use function prototypes. This
- program produces more reasonable output than that produced by MIXTYPES.C:
-
- n in main() is 5.000000
- examine() says n is 5
- examine() returned n as 5
-
- With the prototype, QuickC knew that examine() needed an int. When the
- program tried to pass it a float, QuickC converted the value to an int
- before passing it to the function. (Some conversions can cause variables
- to lose precision, but the resulting value will be much more likely to be
- acceptable.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* proto.c -- demonstrates function prototyping */
- /* and parameter checking */
-
- main()
- {
- float n = 9995.997;
- int i;
- int examine(int num); /* declare function */
- /* with prototype */
-
- printf("n in main() is %f\n", n);
- i = examine(n); /* pass float to function */
- printf("examine() returned n as %d\n", i);
- }
-
- int examine(num)
- {
- printf("examine() says n is %d\n", num);
- return(num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-12. The PROTO.C program.
-
-
- Putting It All Together: A Larger Program
-
- To sum up our work with functions and function calls, we present the
- number game GETCLOSE.C (Listing 6-13), which uses 10 functions (counting
- main()). Although this program is much longer than previous programs,
- notice how the use of functions breaks this large, more complex program
- into manageable pieces. Look at the following listing; then we'll see how
- easy to understand this program actually is.
-
- ──────────────────────────────────────────────────────────────────────────
- /* getclose.c -- a number game using */
- /* random numbers */
-
- #define TRUE 1
- #define FALSE 0
-
- /* external variables */
- int number, /* total number in current game */
- moves, /* number of moves in current game*/
- target, /* target number to reach */
- done, /* true if game is over */
- score, /* score of current game */
- wins = 0, /* number of games won */
- losses = 0, /* number of games lost */
- total = 0; /* total score */
-
- char move;
- /* function prototype declarations */
- void intro(void); /* tell player about game */
- char getyn(void); /* get yes/no response */
- int random(int num); /* random between 1 and num */
- void new_target(void); /* target number for game */
- char get_move(void); /* get player's move */
- void do_move(void); /* generate num from move */
- void check_move(void); /* won, lost, or continue? */
- void show_score(void); /* show score for game */
- void show_total(void); /* show total score */
- main()
- {
- intro(); /* print instructions */
-
- while (TRUE) /* new games until user quits */
- {
- printf("\nDo you want to continue? ");
- if (getyn() != 'y')
- break; /* exit program */
-
- done = FALSE;
- number = moves = score = 0;
- new_target(); /* target number for this game */
- while (!done) /* play one game */
- {
- get_move();/* user selects random number */
- do_move(); /* generate random number */
- /* and add */
- check_move(); /* win, lose, or continue? */
- }
- show_score(); /* score for this game */
- show_total(); /* total score */
- }
- }
-
- void intro(void)
- {
- printf("Welcome to Getclose\n\n");
- printf("The object of this game is to\n");
- printf("try to get as close to the target\n");
- printf("number as possible in as few\n");
- printf("moves as possible by choosing from\n");
- printf("various ranges of random numbers.\n");
- printf("You score if you get within 4 of the\n");
- printf("target. You get a 100-point bonus for\n");
- printf("hitting the target, but you get no score\n");
- printf("if you go over.\n\n");
- }
- char getyn(void)
- /* get yes or no answer */
- /* repeats until valid entry */
- {
- char ch; /* character to read and return */
- while (TRUE)
- {
- printf(" (y or n) ");
- ch = getche();
- printf("\n");
- if ((ch == 'y') || (ch == 'n'))
- /* valid response, break out of loop */
- break;
- /* give error message and loop again */
- printf("please enter ");
- }
- return(ch);
- }
-
- int random(int num)
- /* generate random number between 1 and num */
- /* doesn't use library function srand() because */
- /* we don't want the same seed each time */
- {
- long seconds, result;
- time(&seconds); /* randomize with system time */
- return(abs ((int)seconds * rand() % num) + 1);
- }
-
- void new_target(void)
- /* generate a new target number */
- /* between 50 and 99 */
- {
- target = 50 + random(49);
- printf("\nYour target for this game is %d\n",
- target);
- }
-
- char get_move(void)
- {
- while (TRUE)
- {
- printf("\nPick a random number from 1 to\n");
- printf("a) 5 b) 10 c) 25 d) 50 e) 100 ");
- move = getche();
- if ((move >= 'a') && (move <= 'e'))
- {
- ++moves; /* count the move */
- break; /* valid response */
- }
- /* invalid response, try again */
- printf("\nPlease type a, b, c, d, or e\n");
- }
- }
-
- void do_move(void)
- {
- int num = 0; /* random value to obtain */
- switch (move)
- {
- case 'a' :
- num = random(5);
- break;
- case 'b' :
- num = random(10);
- break;
- case 'c' :
- num = random(25);
- break;
- case 'd' :
- num = random(50);
- break;
- case 'e' :
- num = random(100);
- break;
- }
- number += num; /* add new number to total */
- printf("\n\nYou got a %d. Number is now: %d ", num, number);
- printf("(Target is %d)\n", target);
- }
-
- void check_move(void)
- {
- int temp;
- if (number > target)
- {
- printf("\nYou went over! ");
- printf("No score this game.\n");
- losses++;
- done = TRUE; /* to break out of loop */
- }
- if (number == target)
- {
- printf("\nYou hit the target ");
- printf("for 100 bonus points!\n");
- score = (100 / moves) + 100;
- total += score;
- wins++;
- done = TRUE;
- }
- if ((number >= (target - 4)) && (number < target))
- {
- temp = 100 / moves;
- /* does player want to go for broke? */
- printf("\nTake %d points (y) or continue (n)? ",
- temp);
- if (getyn() == 'y')
- {
- score = temp;
- total += score;
- wins++;
- done = TRUE;
- }
- }
- }
-
- void show_score(void)
- {
- printf("\nYou scored %d points in %d moves.\n",
- score, moves);
- }
-
- void show_total(void)
- {
- printf("You have won %d game(s) ", wins);
- printf("and lost %d.\n", losses);
- printf("Your total score is %d\n", total);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 6-13. The GETCLOSE.C program.
-
- Overview of the Game
-
- GETCLOSE.C is a number game in which you try to reach a randomly generated
- target number between 50 and 99 in as few moves as possible. Each "move"
- consists of choosing one of five possible ranges of random numbers: 1─5,
- 1─10, 1─25, 1─50, and 1─100. You start at zero, and each number you choose
- is added to your total. Thus, each move brings your total closer to the
- target number. If you get within 4 of the target, you can settle for a
- score that depends on the number of moves you've made, or you can continue
- to try to hit the target exactly. If you actually hit the target number,
- you get a 100-point bonus. If you go over, however, you lose and score
- nothing. The program lets you play new games, and it keeps track of your
- total score and the number of games you have won and lost.
-
- The strategy of the game involves deciding how large a range from which to
- pick the next random number. If you pick from the larger ranges, you can
- reach the target number in only a few moves, gaining you a high score. But
- the big ranges also present you with a greater chance of overshooting the
- target, giving you no points at all. (Playing the game is a little like
- playing blackjack, except that you have five different decks from which to
- choose cards.)
-
- Playing the Game
-
- Type in the program, run it, and play a few games to get an idea of the
- different operations the program performs. Then read the next section to
- explore the program's inner workings. The following is a sample game:
-
- Welcome to Getclose
-
- The object of this game is to
- try to get as close to the target
- number as possible in as few
- moves as possible by choosing from
- various ranges of random numbers.
- You score if you get within 4 of the
- target. You get a 100-point bonus for
- hitting the target, but you get no score
- if you go over.
-
-
- Do you want to continue? (y or n) y
-
- Your target for this game is 93
-
- Pick a random number from 1 to
- a) 5 b) 10 c) 25 d) 50 e) 100 e
-
- You got a 31. Number is now: 31 (Target is 93)
-
- Pick a random number from 1 to
- a) 5 b) 10 c) 25 d) 50 e) 100 d
-
- You got a 13. Number is now: 44 (Target is 93)
-
- Pick a random number from 1 to
- a) 5 b) 10 c) 25 d) 50 e) 100 d
-
- You got a 19. Number is now: 63 (Target is 93)
-
- Pick a random number from 1 to
- a) 5 b) 10 c) 25 d) 50 e) 100 d
-
- You got a 13. Number is now: 76 (Target is 93)
-
- Pick a random number from 1 to
- a) 5 b) 10 c) 25 d) 50 e) 100 c
-
- You got a 16. Number is now: 92 (Target is 93)
-
- Take 20 points (y) or continue (n)? (y or n) y
-
- You scored 20 points in 5 moves.
- You have won 1 game(s) and lost 0.
- Your total score is 20
-
- The main() Function
-
- As you have seen, the main() function of a C program controls the overall
- flow of the program, while calls to various functions do the actual work.
- To explore GETCLOSE.C, look at the structure of main(). Even without
- knowing how the functions work, you can see the general structure of the
- program. First, it calls the intro() function to explain the game briefly.
- (Notice that we commented each of the function prototype declarations in a
- block before main(). This quickly lets you understand the purpose of every
- function.)
-
- The outer while loop permits unlimited games until the user decides to
- quit. At the start of each game, new_target() generates a new target
- number, and then the inner while loop processes the player's moves until
- the game ends. In this loop, getmove() prints a menu and lets the user
- select a range of random numbers for the next move. The do_move() function
- gets the random number and adds it to the player's current total;
- check_move() then compares the current total and the target number and
- decides if the player has won, lost, or can continue playing.
-
- Finally, when the inner loop (which represents one game) ends,
- show_score() displays the score of the last game, and show_total()
- displays the total score and the games won and lost thus far.
-
- You can also use the QuickC Calls menu to help you understand how the
- function calls work. First, compile GETCLOSE.C with Debug selected, and
- then move the cursor to the body of a function you want to examine. Use
- the Debug menu to set a breakpoint there, then run the program. When the
- program stops at the breakpoint, pull down the Calls menu to see a list of
- all called functions. Then display the text of any listed function by
- selecting it. The Calls menu is especially useful for examining programs
- that nest function calls.
-
- Modifying GETCLOSE.C
-
- One of the best ways to improve your knowledge of C is to take a program
- such as GETCLOSE.C and try to add features to it. You might already have
- some ideas in mind from playing and studying the game. Here are some
- possibilities:
-
- 1. Give points for how close the player gets to the target number.
-
- 2. Generate a "poison number" between 1 and the target number minus 10.
- If the player gets within 4 of this number without going over, he or
- she loses.
-
- 3. Similarly, you could specify a "free number" that, when reached, is
- not counted as a turn, thus potentially increasing the player's score.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 3 ADVANCED C TOPICS
- ────────────────────────────────────────────────────────────────────────────
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 7 Arrays
-
- In programming, it is often advantageous to collect variables into sets or
- lists, so that many values can be stored and manipulated as a single
- conceptual unit. Such a collection of variables, when all those variables
- are the same type, is called an array. Arrays are used to organize values
- in applications that range from the "top ten" scores in a video game to
- the payroll records for thousands of employees.
-
- To visualize the advantage of arrays over simple variables, imagine that
- you run a business and that you want to store each employee's working
- hours in your computer. If you have even two employees, a year's worth of
- variables might look like this:
-
- int emp1_jan_1, emp2_jan_1;
- int emp1_jan_2, emp2_jan_2;
- ...──────────────────────────────────And so on for 362 intervening lines
- int emp1_dec_31, emp2_dec_31;
-
-
- Clearly, even the typing is a monumental undertaking. You can express the
- same number of variables as an array in C in a single line of code:
-
- int employees[2][365];
-
- Without a doubt, arrays let you organize data more concisely than do
- simple variables. This is convincingly illustrated in Figure 7-1.
-
- Although the details of declaration, storage, and retrieval differ from
- language to language, the basic nature of an array does not. In its
- simplest form, an array consists of one or more variables of the same
- storage type (size and number of bytes), arranged one after the other,
- continuously upward in memory. All variables in an array are referenced by
- a single name, called an identifier.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 7-1 can be found on p.190 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 7-1. Arrays provide superior organization over simple variables for
- many common C programs.
-
-
- How Arrays Are Stored in Memory
-
- The elements in an array are stored consecutively in memory. An array
- consisting of only four int values, for example, is stored in memory as
- shown in Figure 7-2. Four int values (of two bytes each on the IBM PC)
- are arranged together in ascending order in memory. That is, the array
- begins with the leftmost int──the one lowest in memory──and continues
- upward in memory with one or more adjoining int values.
-
- ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
- Array of ──── │ int │ int │ int │ int │
- 4 ints └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
-
- Memory ───────── 46 47 48 49 50 51 52 53
- location
-
- Figure 7-2. An array of four int values as stored in memory.
-
-
- How to Declare Arrays
-
- When you declare an array, you must tell the compiler how many items of
- which data type to set aside in memory for storage and the name to use
- when referencing that storage.
-
- The rules for declaring an array in C are relatively straightforward:
-
- ■ First, declare the type (char, float, int, etc.).
-
- ■ Second, declare the name. Array names use the same naming conventions
- as variable names.
-
- ■ Third, declare the number of items in the array by placing an integer
- constant expression inside square brackets; for example, [15].
-
- You recall, of course, that an "integer constant expression" is any
- integer constant value or any combination of integer constant values and
- arithmetic operators. Thus, 15 is an integer constant value, and so are 3,
- 0x0F, and `a'. The last is a character constant that C views as an integer
- constant. But 3.0 is a floating-point constant and thus illegal for
- specifying the array size. The expression 15 * 2 is an integer constant
- expression because it is one integer constant value multiplied by another.
-
- Remember, you cannot specify the number of elements in an array with a
- variable. The following array declaration, for example, is illegal:
-
- int line_num = 15;
- int widths[line_num];
-
- ──────────────────────────────────────────────────────────────────────────
- BASIC and Pascal Array Declarations
- Declaring an array in C is similar to declaring an array in BASIC and
- Pascal. In BASIC, an array is declared as widths%(15); in Pascal, the same
- array is declared as widths : ARRAY [1...15] OF INTEGER. In C, we declare
- this array as:
-
- int widths[15];
-
- All three of these declarations identify an array named widths composed of
- 15 integer variables. Each sets aside room in memory for 15 int values (30
- bytes on the IBM PC because each int occupies two bytes). As you can see,
- declaring an array in C retains the simplicity of BASIC but is clearer
- than BASIC.
-
- The 1...15 expression in the Pascal declaration tells the compiler that
- the elements in the array are represented by the numbers 1 through 15. In
- C, array elements are always numbered beginning with 0 and ascending to
- the specified number.
- ──────────────────────────────────────────────────────────────────────────
-
- You cannot use the expression line_num to declare the number of elements
- in the array widths. The C compiler looks at only one small piece of a
- program at a time. It first sees int line_num = 15 and generates code to
- store the value 15 into the variable line_num. When it reaches the array
- declaration, all it knows of line_num is that it currently contains the
- value 15; QuickC has no idea how that value will change as the program
- executes.
-
- In C, a constant variable, that is, one declared with the ANSI keyword
- const, is not considered a constant value. Therefore, the following
- example is illegal:
-
- const int line_num = 15;
- int widths[line_num];
-
- Using the const keyword declares to the compiler that you do not intend to
- change the value of line_num. It does not prevent a bug in your program
- from accidentally changing that value.
-
- If you attempt to declare the size of an array with a variable, even a
- const variable, QuickC will print the following error message:
-
- Error C2057:
- expected constant expression
-
- Examine the ARRAY1.C program (Listing 7-1). This "do-nothing" program
- demonstrates the correct way to declare arrays.
-
- In this program, SIZEOARRAY is specified using #define as 26. The number
- of items in ages is therefore declared with 26 * 2, which is legal.
-
- ──────────────────────────────────────────────────────────────────────────
- /* array1.c -- how to declare arrays legally */
-
- #define SIZEOARRAY 26
-
- main()
- {
- char initials[26];
- int num_men[26], num_women[SIZEOARRAY];
- float ages[SIZEOARRAY * 2];
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-1. The ARRAY1.C program.
-
-
- Referencing and Using Array Items
-
- The way you reference the items in an array (whether to store values in
- them or to fetch values from them) looks very much like the declaration.
- You merely state the identifier for the array and place the offset of the
- item within square brackets. The offset is always measured from the
- beginning of the array, with the first item having an offset of 0.
-
- For example, the expression
-
- widths[1] = 3;
-
- stores the value 3 in the second item of the array named widths; that is,
- the item whose offset from the beginning of the array is 1.
-
- Conversely, the expression
-
- this_width = widths[1];
-
- retrieves the value of the second item of the array widths and assigns
- that value to the variable this_width.
-
- In C, the offset into an array can be the result of any expression that
- returns a value. It can be the value of a variable, the result of a
- computation or logical test, or even the returned value of a function
- call. The only restriction is that the array offset must be specified with
- an integer value. The following are legal specifications:
-
- widths[1]───────────────────────────────────────────Offset is a constant
- widths[i]──────────────────────────────────────Offset is an int variable
- widths[i++]──────────────────────────────────────Offset is a computation
- widths[getnum()]───────────────────────────Return value of function call
- widths['a']────────────────────────────────────Offset is a char constant
-
- However, the following float type offset causes a compiler error:
-
- widths[fltval]──────────────────────────────────────float offset (error)
-
- Because there are no fractional memory locations, specifying an array's
- offset with a float causes the following QuickC error:
-
- error C2108:
- non-integral index
-
- With a legal offset specification, an array element is no different from
- an ordinary variable of that type. You can perform the same operations
- with an array item as you can with any ordinary variable. Consider, for
- example:
-
- widths[1] = 3;
- total = widths[0] + widths[1] + widths[2];
-
- In the first operation, the value 3 is stored in the second element of the
- array named widths. In the second, values stored in three array elements
- are added together. Notice that we use the same notation to access each──
- an offset in brackets.
-
- An Example
-
- Now that you have the basic rules for declaring arrays and accessing items
- in those arrays, examine the XMAS.C program (Listing 7-2).
-
- In XMAS.C, we declare the array widths to contain 20 items of the type
- int. That array is then filled with values by the for loop; this is one
- way to store values in an array. Finally, each item in the array widths is
- passed to the function Center_out(); this is one way to access the values
- in an array.
-
- ──────────────────────────────────────────────────────────────────────────
- /* xmas.c -- fills an array with values, then passes */
- /* each of those values to a function */
-
- main()
- {
- int i, j, widths[20];
- void Center_out();
-
- for (i = 0, j = 1; i < 18; ++i, j += 2)
- {
- widths[i] = j;
- }
- widths[i++] = 3;
- widths[i] = 3;
-
- for (i = 0; i < 20; i++)
- {
- Center_out('X', widths[i]);
- }
-
- }
-
- void Center_out(char character, int width)
- {
- int i;
-
- for (i = 0; i < ((80 - width) / 2); ++i)
- {
- putch(' ');
- }
- for (i = 0; i < width; ++i)
- {
- putch(character);
- }
- putch('\r');
- putch('\n');
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-2. The XMAS.C program.
-
- Whenever you reference an array element, the value of that element becomes
- available for use──you can assign the value to other variables or pass it
- to a function. Note, however, that passing an array element by giving its
- name and an offset merely passes a copy of that element, not the element
- itself. This is the method for passing ordinary variables.
-
-
- Bounds Checking Arrays in Your Code
-
- In XMAS.C, the for loop prevents your specifying an offset beyond the end
- of the array widths:
-
- for (i = 0; i < 20; ++i)
-
- In many programs, however, the ability to exceed an array's bounds is not
- prevented by your code but is controlled by the user. For example, the
- SADD.C program (Listing 7-3) is a simple adding machine that lets the
- user enter as many as three numbers, one per line, and terminate entry
- with any non-numeric character such as an x.
-
- Now run SADD.C and enter (on separate lines) the numbers 1, 2, and 3,
- followed by an x character. The program displays the correct sum,
- which is 6:
-
- 1
- 2
- 3
- x
- ----------
- 6
-
- ──────────────────────────────────────────────────────────────────────────
- /* sadd.c -- a small adding machine that illustrates */
- /* the need for array bounds checking */
-
- main()
- {
- int offset = 0, i, result = 0;
- int stack[3];
-
- while (scanf("%d", &stack[offset]) == 1)
- {
- ++offset;
- }
- for (i = 0; i < offset; ++i)
- {
- result += stack[i];
- }
- printf("----------\n");
- printf("%d\n", result);
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-3. The SADD.C program.
-
- Now run the program again, but this time enter four numbers:
-
- 1
- 2
- 3
- 4
- x
- ----------
- 20
-
- The result shown is 20, which is wrong. But QuickC doesn't recognize that
- an error occurred and prints no error message. The C language itself,
- unlike Pascal and BASIC, contains no provisions to prevent offsets beyond
- the end of an array.
-
- To understand why SADD.C referenced stack[3], even though no fourth item
- exists (remember that arrays begin with item 0), let's look again at the
- way arrays are arranged in memory. As a bonus, we'll find out where the
- value 20 came from. Figure 7-3a shows how the compiler translates into
- memory the declarations from SADD.C (shown on the next page).
-
- ┌──────────────────────────────────────────────────────────────┐
- │° Initialized °│
- │° int offset=0, i, result=0;▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ °│
- │° int stack[3]; ▒ °│
- │° ▒▒▒▒▒while (scanf("%d", &stack [offset]) == 1) ▒ °│
- │° ▒ ++offset; ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ °│
- │° ▒ ▒ ▒ °│
- │° ▒ ┌────────┬────────┬────────┬───────┬────────┬───────┤
- │° ▒ │ ? │ ? │ ? │ 0 │ ? │ 0 │
- │° ▒ └────────┴────────┴────────┴────────┴────────┴────────┤
- │° ▒ stack[0] stack[1] stack[2] result i offset├─────┐
- │° ▒ °│ │
- └─────────────▒────────▒────────▒────────▒─────────────────────┘ │
- ▒▒▒▒▒▒▒▒▒▒1▒▒▒▒▒▒▒▒2▒▒▒▒▒▒▒▒3▒▒▒▒▒▒▒▒4 │
- │ │ │
- └──────────────┬─────────────┘ │
- │ │
- Stores these input values │
- while incrementing offset──────────────────────────┘
-
- (A)
-
-
- ┌────────┬────────┬────────┬────────┬────────┬────────┐
- │ 1 │ 2 │ 3 │ 4 │ ? │ 4 │
- └────────┴────────┴────────────────┴────────┴────────┘
- stack[0] stack[1] stack[2]│ result i offset
- │
- Array ends here───┘ │
- │
- Position of stack[3]
-
- (B)
-
- Figure 7-3. Consequence of referencing beyond the end of an array.
-
- int offset = 0, i, result = 0;
- int stack[3];
-
- First, the variables offset and result are set to 0. Then the while loop
- fills out the items in the 3-element array stack. The last value entered
- was 4, which was the fourth value placed into stack. But stack contains no
- fourth item (that is, no stack[3] ), so the fourth value is placed into
- result because result occupies the place in memory that follows the stack
- array.
-
- The arrangement depicted in Figure 7-3a occurs because QuickC places auto
- variables into memory from the top down, but it places static and global
- variables into memory from the bottom up.
-
- Now look closely at Figure 7-3b. Because result begins with a value of 4
- instead of 0, the for loop adds 1, 2, and 3 to it, resulting in a sum of
- 10. The for loop continues (because offset is 4) by adding stack[3] to
- result (both reference the same address and the same value, 10), thus
- yielding the erroneous value of 20.
-
- To make programs less sensitive to improper user input, always provide
- code that detects array out-of-bounds conditions. You can do this by
- simply terminating the program when an error is detected, but writing your
- program so that it can recover from errors is better. The SADD2.C program
- (Listing 7-4) shows a common approach to array bounds checking that
- corrects the previous program's weakness.
-
- ──────────────────────────────────────────────────────────────────────────
- /* sadd2.c -- a small adding machine that includes */
- /* array bounds checking */
-
- #define MAXSTAK 3
-
- main()
- {
- int offset = 0, i, result = 0;
- int stack[MAXSTAK];
-
- while (scanf("%d", &stack[offset]) == 1)
- {
- if (++offset >= MAXSTAK)
- {
- printf("Stack Full\n");
- break;
- }
- }
- for (i = 0; i < offset; ++i)
- {
- result += stack[i];
- }
- printf("----------\n");
- printf("%d\n", result);
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-4. The SADD2.C program.
-
- In this revision of SADD.C, we use the preprocessor #define directive to
- create an alias for the size of the array stack. Thus, you can later
- easily change the number of items in stack by changing a single #define
- and then recompiling.
-
- Next, we insert an if statement that checks a preincremented offset to be
- sure it does not exceed the number of items in stack, that is, MAXSTAK. If
- offset becomes too large, the if statement first causes a warning message
- to be printed and then breaks out of the while loop. The user gets an
- accurate sum for the numbers entered, despite the error, and any
- out-of-range numbers are simply ignored.
-
-
- How to Initialize Arrays
-
- When you declare an auto array (an array declared inside a function and
- without the keyword static), it is initially filled with garbage values,
- regardless of its type and size. (This also occurs when you create auto
- variables.) On the other hand, static arrays and arrays declared outside
- of functions always have their initial values set to zero──again just like
- ordinary variables. For example:
-
- char Letters[26];───────────────────────────────────Initialized to zeros
-
- main()
- {
- char vowels[5];──────────────────────────────────Starts with garbage
- static char consonants[21];─────────────────────Initialized to zeros
-
- When zero is not an appropriate initial value, you can give static and
- global variables starting values of your choice. To initialize an array:
-
- ■ Follow the right square bracket (]) in the array identifier with an
- assignment operator (=) and a left brace ({).
-
- ■ Then state each initializing value, followed by a comma.
-
- ■ Finally, terminate your comma-separated list of initializers with a
- right brace (}) and the usual semicolon.
-
- For example, to initialize the array Letters with the letters of the
- alphabet, you need simply declare it as:
-
- char Letters[26] = { 'a', 'b', 'c', 'd', 'e',
- 'f', 'g', 'h', 'i', 'j',
- 'k', 'l', 'm', 'n', 'o',
- 'p', 'q', 'r', 's', 't',
- 'u', 'v', 'w', 'x', 'y',
- 'z', };
-
- Or, for an array of numbers you could declare:
-
- int Values[5] = { 12, 2, 44, 19, 7 };
-
- In both of the above examples, the type of the initializing values matches
- the type of the array and is a constant value. This is mandatory. Values
- in array initializing
-
- lists must be constant values or constant expressions, and those values
- must fit in the number of bytes declared for each array item. The array
- Letters is initialized with the type char, and `a' is a char constant. The
- array Values is initialized with type int, and 12 is an integer constant.
- A float array would have to be initialized with the type float, such as
- 76.98, which is a floating-point constant.
-
- The ASIMOV.C program (Listing 7-5) contains in the initialized array
- Letters the name of a famous Isaac Asimov character. By entering the
- correct series of numbers, you can reveal that name.
-
- ──────────────────────────────────────────────────────────────────────────
- /* asimov.c -- illustrates how to initialize an */
- /* array with starting values */
-
- #define MAXL 16
- char Letters[MAXL] = {
- 'e', 'I', 'a', 'N', 'o', 'R', 'O', 'o',
- 'u', 't', 'o', 'R', 'l', 'o', 'B', 'b', };
-
- main()
- {
- int num, i;
-
- printf("Guess my identity with numbers.\n");
- printf("(any non-number quits)\n\n");
-
- while (scanf("%d", &num) == 1)
- {
- if (num <= 0)
- {
- printf("Guesses must be above zero\n");
- continue;
- }
- for (i = 1; i <= num; ++i)
- {
- printf("%c", Letters[(i * num) % MAXL]);
- }
- printf("\n");
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-5. The ASIMOV.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- The list of initializers for Letters ends with a trailing comma. That is
- not an error. Trailing commas in initializer value lists are optional but
- enable long lists to be rearranged easily with your text editor.
- ──────────────────────────────────────────────────────────────────────────
-
- Letting the Compiler Supply the Size
-
- When you declare the values for an initialized array, it is not always
- possible, or necessary, to state explicitly the number of items in the
- array. C provides an alternative. For example, the following declaration
- omits the size of the array:
-
- int Primes[ ] = {1, 2, 3, 5, 7, 11,};
- └─────────────────────────────────── Number of items omitted
-
- Whenever you omit the size, the C compiler counts the number of
- initializers and dimensions the array to the correct size for that count.
- Because the preceding example contains six initializers, that declaration
- is equivalent to declaring:
-
- int Primes[6] = {1, 2, 3, 5, 7, 11,};
-
- When the size of an array is omitted, you might expect bounds checking in
- your code to be difficult. Fortunately, you can use the sizeof operator to
- find the number of bytes in an array and thus specify, using #define, a
- bounds-checking value. When used with the preceding declaration, the
- expression
-
- #define MAXL (sizeof(Primes)/sizeof(int))
-
- gives MAXL a value of 6.
-
- The sizeof keyword, when given the name of an array, yields the number of
- bytes in that array. The #define, then, is the number of bytes in the
- array Primes (12 bytes) divided by the number of bytes in an int (2
- bytes).
-
- Overinitializing and Underinitializing
-
- One good reason to omit the size of an array is that C permits a mismatch
- between the number of initializing values and the size you declare. When
- there are fewer initializers, the compiler silently fills the remainder
- with zero values. When there are too many initializers, the compiler
- complains and stops. Some compilers (especially under UNIX) will issue a
- warning and truncate the array.
-
- The UNDOVER.C program (Listing 7-6) demonstrates this behavior. As the
- program stands, it prints the following message:
-
- The first 6 primes are: 1 2 3 5 7 11
-
- Now change Primes[6] to Primes[8] in the declaration and run the program
- again. This time it prints:
-
- The first 8 primes are: 1 2 3 5 7 11 0 0
-
- The new result shows that underinitializing causes the compiler to fill
- the leftover items with zero values. Now change the declaration again,
- this time from Primes[8] to Primes[3]. This time the QuickC compiler stops
- and issues the message:
-
- error C2078:
- too many initializers
-
- ──────────────────────────────────────────────────────────────────────────
- /* undover.c -- illustrates the effect of underinitializing and */
- /* overinitializing arrays */
-
- int Primes[6] = { 1, 2, 3, 5, 7, 11 };
- #define NUMP (sizeof(Primes) / sizeof(int))
-
- main()
- {
- int i;
-
- printf("The first %d primes are: ", NUMP);
- for (i = 0; i < NUMP; ++i)
- {
- printf("%d ", Primes[i]);
- }
- printf("\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-6. The UNDOVER.C program.
-
-
- Arrays and Functions
-
- As you saw earlier in XMAS.C, passing one of an array's elements to a
- function is like passing an ordinary variable to a function. That is, a
- copy of the value of that element is passed, not the element itself.
-
- However, when you pass whole arrays to functions, the situation changes.
- Although you are still passing by value, what you are actually passing is
- the address of the array (its location in memory). The effect of this is
- that you appear to be passing the array itself and that the array itself
- will be changed. (See the next chapter for further details.) For now,
- we'll simply show you how to pass arrays to functions and how to use those
- arrays when they get there.
-
- Passing Arrays to Functions
-
- To pass an array to a function, merely state the array's name (minus the
- offset) in the function call. For example, if you have declared an array
- as follows:
-
- static int list[7] = { 5, 1, 3, 7, 2, 4, 6 };
-
- you would pass that array to a function, called Bub_sort() for example, by
- stating its name, as follows:
-
- Bub_sort(list);
-
- This tells the compiler to send the entire array named list to Bub_sort().
-
- At the receiving end, in Bub_sort(), you need to declare the type of the
- received array. To do so, declare an array in the normal C manner and
- leave the square brackets empty:
-
- Bub_sort(vals)
- int vals[];
- {
-
- This declares that a function named Bub_sort() will receive one argument,
- the int array vals. Because Bub_sort() is receiving an array──via the
- array's address──it receives the array itself and not a copy of that
- array. This allows Bub_sort() to change the original array.
-
- The BUBSORT.C program (Listing 7-7) demonstrates the differences between
- passing array elements to functions and passing entire arrays.
-
- ──────────────────────────────────────────────────────────────────────────
- /* bubsort.c -- passing an array to a function */
- /* affects the original array */
-
- #define NUMINTS 6
-
- extern void Bub_sort();
- extern void Triple();
- main()
- {
- int num = 2, i;
- static int list[NUMINTS] = { 6, 5, 4, 3, 2, 1 };
-
- printf("num before Triple = %d\n", num);
- Triple(num);
- printf("num after Triple = %d\n", num);
- printf("list[0] before Triple = %d\n", list[0]);
- Triple(list[0]);
- printf("list[0] after Triple = %d\n", list[0]);
- printf("Before sorting -> ");
- for (i = 0; i < NUMINTS; ++i)
- {
- printf("%d ", list[i]);
- }
- printf("\n");
-
- Bub_sort(list);
- printf("After sorting -> ");
- for (i = 0; i < NUMINTS; ++i)
- {
- printf("%d ", list[i]);
- }
- printf("\n");
-
- }
- void Triple(int x) /* function doesn't affect original */
- {
- x *= 3;
- }
-
- void Bub_sort(int vals[NUMINTS]) /* function changes original */
- {
- int i, j, temp;
-
- for (i = (NUMINTS - 1); i > 0; --i)
- {
- for (j = 0; j < i; ++j)
- {
- if (vals[j] > vals[j+1])
- {
- temp = vals[j];
- vals[j] = vals[j+1];
- vals[j+1] = temp;
- }
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-7. The BUBSORT.C program.
-
- This program first calls the Triple() function, passing it both an
- ordinary variable and one of the elements in the array list. The value of
- the original isn't changed in either case; only the value (a copy) of what
- is sent is changed.
-
- Next, we pass the array list to the function Bub_sort() (a simple bubble
- sorting function). The program prints the array before and after the
- function call to demonstrate that the Bub_sort() function changes the
- values in the original array; it does not sort a copy.
-
- Variations
-
- When a function receives an array, the number of elements in the
- declaration of that array is usually omitted because the size
- specification is optional:
-
- Bub_sort(vals)
- int vals[ ];
- └──────────────────────────────────────── Size of array omitted
-
- Restating that size in the function declaration, however, is often a good
- idea. As we have seen, C does no array bounds checking on your behalf, so
- restating the size of the received array helps to clarify and document
- your program:
-
- Bub_sort(vals)
- int vals[NUMINTS];
- └────────────────────────────────────── Restated for clarity
-
- The type of the received array should also match that of the original. If
- the types do not match, your program might not work properly. The reason
- we say "might not" is that you might want to mismatch types intentionally.
- The HEXOUT.C program (Listing 7-8) demonstrates such a planned mismatch.
- This program asks you to enter a floating-point number and then prints out
- that number, one byte at a time, in hexadecimal notation.
-
- HEXOUT.C first reads a floating-point value into the array fary──a float
- type array consisting of only one element. That array is then passed to
- Hexout(). In Hexout(), we declare the type of the received array as
- unsigned char. This "deception" causes Hexout() to handle the array fary
- as if it were an array of single unsigned bytes, whereas the original was
- actually a 4-byte float type. We will explain this shortly. This deception
- illustrates one of C's primary strengths, the freedom of the programmer to
- change types in midstream.
-
- ──────────────────────────────────────────────────────────────────────────
- /* hexout.c -- prints a floating-point variable in */
- /* hexadecimal format */
-
- extern void Hexout();
- main()
- {
- float fary[1];
-
- printf("Enter a floating-point number\n");
- printf("(Any non-numeric entry quits)\n\n");
- while (scanf("%f", &fary[0]) == 1)
- {
- Hexout(fary);
- }
- }
-
- void Hexout(unsigned char chary[])
- {
- int i;
-
- for (i = 0; i < sizeof(float); ++i)
- {
- printf("%02X ", chary[i]);
- }
- printf("\n\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-8. The HEXOUT.C program.
-
-
- How Array Offsets Advance
-
- When you reference an array element with an array name and an offset,
- QuickC invisibly converts the element offset to a bytes-in-memory offset.
- To illustrate this, let's look at how two different type arrays are
- organized in memory. Figure 7-4 shows that an array of char type occupies
- one byte of memory for each element and that an array of float type
- occupies four bytes of memory for each element.
-
- For the char array, chary[4], each element occupies one byte, so each
- element offset specification corresponds to the bytes-in-memory offset.
- For the float array fary[2], however, each element occupies four bytes, so
- each element offset specification corresponds to a bytes-in-memory offset
- of 4 bytes. In the latter case, when you specify
-
- fary[1]
-
- you are really telling the compiler to reference a value that is four
- bytes into the array fary[]. Because a float value occupies four bytes of
- memory, your element offset of 1 becomes a bytes-in-memory offset of 4
- bytes.
-
- This explains how, in the preceding program, HEXOUT.C, it is possible to
- print out a float one byte at a time. Inside the function Hexout(), the
- compiler thinks it is handling an array of unsigned char type, in which
- each element occupies a single byte. Thus, i increments in unmultiplied
- steps of single bytes.
-
- char chary[4] = {'t', 'e', 's', 't'};
-
- │ 1 byte │
- │ │
- ├────────┼────────┬────────┬────────┐
- │ 't' │ 'e' │ 's' │ 't' │
- └────────┴────────┴────────┴────────┘
-
- (A) ARRAY OF char VALUES
-
-
- float fary [2] = {1.2, 4.3};
-
- │ 1 byte │
- │ │
- ├────────┴────────┬────────┬────────┬────────┬────────┬────────┬────────┐
- │ 1.2 │ 4.3 │
- └────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘
- fary[0] fary[0]
-
- (B) ARRAY OF float VALUES
-
- Figure 7-4. How offsets differ by number of bytes based on the type of the
- array.
-
-
- Multidimensional Arrays
-
- In C you can easily create and use arrays of two, three, or many
- dimensions. Two-dimensional arrays correspond to such useful items as
- calendars, spreadsheets, and maps. We begin with the rules for
- two-dimensional arrays and then apply them to arrays of three and more
- dimensions. Three-dimensional arrays find application in items such as 3-D
- graphics, layered indexes, and solid topology. Many-dimensioned arrays
- delve into the arcane worlds of higher math and complex games.
-
- Two-dimensional Arrays
-
- Two-dimensional arrays represent rectangular grids of data. As illustrated
- in Figure 7-5, they are organized in rows first and then columns. Because
- computer memory is linear, those rows and columns are stored in memory one
- row after the other.
-
- The rules for declaring a two-dimensional array are similar to those for
- declaring a one-dimensional array. They take the following form:
-
- type name[rows][columns];
-
- As with one-dimensional arrays, you must specify the number of rows and
- the number of columns as integer constant expressions. Thus, the
- expression
-
- int week[7][8];
-
- declares a two-dimensional array of type int, named week, with 7 rows (for
- 7 days) and 8 columns (for 8 working hours per day). You can fill it with
- any number of useful items, for example, the number of lines of code
- written per hour per day.
-
- Columns
- │
- ┌───────────┴───────────┐
- │ │
- ┌───────┬───────┬───────┐ ───┐
- │ 1 │ 2 │ 3 │ │
- ├───────┼───────┼───────┤ │
- │ 4 │ 5 │ 6 │ ├── Rows
- ├───────┼───────┼───────┤ │
- │ 7 │ 8 │ 9 │ │
- └───────┴───────┴───────┘ ───┘
- │
- │ Arranged in memory as
-
- ┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐
- │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
- └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
- │ │ │ │
- └───────────┬───────────┴───────────┬───────────┴───────────┬───────────┘
- │ │ │
- Zeroth row First row Second row
-
- Figure 7-5. Two-dimensional arrays are arranged in memory row by row.
-
- Referencing two-dimensional array elements is as simple as referencing
- elements of one-dimensional arrays. Specify the row and column with
- integer expressions:
-
- int day = 6, /* Saturday */
- hour = 2; /* 10:00 A.M. */
-
- printf("Wrote %d lines of code.\n", week[day][hour]);
-
- Initializing Two-dimensional Arrays
-
- Before we go into the somewhat more complex rules for initializing
- two-dimensional arrays, enter the MAGIC.C program (Listing 7-9). This
- program initializes an array of type int with scrambled numbers and then
- asks the user to rearrange those numbers into the correct order by
- continually swapping squares adjacent to the 0 with the 0. The object is
- to rearrange the numbers into ascending order, with 0 at the top left,
- counting up row by row.
-
- ──────────────────────────────────────────────────────────────────────────
- /* magic.c -- demonstrates use of a two-dimensional */
- /* array of type int */
-
- main()
- {
- static int square[3][3] = { 5, 8, 3, 4, 2, 0, 7, 1, 6 };
- int zrow = 1, zcol = 2; /* location of the zero */
- int num, row, col, i, j, rowdist, coldist;
-
- while (1)
- {
- printf("Swap what with zero?\n");
- printf("(Q to quit)\n");
-
- /* Print the square. */
- for (i = 0; i < 3; ++i)
- {
- for (j = 0; j < 3; ++j)
- {
- printf(" %d ", square[i][j]);
- }
- printf("\n");
- }
-
- /* Enter the user number. */
- if ((num = getch()) == 'Q')
- exit(0);
- num -= '0';
- if (num < 1 || num > 9)
- {
- printf("Not a legal number.\n\n");
- continue;
- }
- /* Find that square. */
- for (row = 0; row < 3; ++row)
- {
- for (col = 0; col < 3; ++col)
- {
- if (num == square[row][col])
- {
- goto GOTIT;
- }
- }
- }
- GOTIT:
- /* Check for a legal move. */
- if (row > 2 || col > 2)
- {
- printf("Bad Box Specification\n\n");
- continue;
- }
- rowdist = zrow - row;
- if (rowdist < 0)
- rowdist *= -1;
- coldist = zcol - col;
- if (coldist < 0)
- coldist *= -1;
- if (rowdist > 1 || coldist > 1)
- {
- printf("Not a Neighbor\n\n");
- continue;
- }
-
- /* Make the move. */
- square[zrow][zcol] = square[row][col];
- square[row][col] = 0;
- zrow = row;
- zcol = col;
-
- /* See if done, and solved. */
- for (i = 0; i < 3; ++i)
- {
- for (j = 0; j < 3; ++j)
- {
- if (square[i][j] != ((i * 3) + j))
- {
- break;
- }
- }
- }
- if ((i * j) == 9)
- break;
- }
- printf("\n\aYOU GOT IT !!!\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-9. The MAGIC.C program.
-
- We initialize the two-dimensional array in MAGIC.C by filling it row by
- row. When, for clarity, you want to specify where one row ends and the
- next begins, you can enclose each row's initializers in another set of
- braces:
-
- static int square[3][3] = {
- {5, 8, 3},─────────────────────────────────────────────────────Row 0
- {2, 4, 0},─────────────────────────────────────────────────────Row 1
- {7, 1, 6}──────────────────────────────────────────────────────Row 2
- };
-
- This amounts to specifying each row as its own subset of initializers,
- clearly a more readable arrangement.
-
- To underinitialize a 3-by-3 array, we could use the following:
-
- static int square[3][3] = {
- 5, 8, 3, 2, 4, 7, 1, 6 };
- └─────────────────────── One initializer short
-
- Here the last column of the last row is omitted and thus is 0 by default.
- We also could underinitialize by a selected row, as follows:
-
- static int square[3][3] = {
- {5, 8, 3},
- {2, 4},──────────────────────────────────────────────────Row 1 short
- {7, 1, 6}
- };
-
- Here we omit the third column of the second row, thus setting the value to
- 0.
-
- Two-dimensional Arrays and Functions
-
- As with one-dimensional arrays, you pass a two-dimensional array to a
- function by merely stating its name:
-
- Make_move(board);
-
- Again, this passes the two-dimensional array itself, not a copy.
-
- For the receiving function, Make_move(), you must always declare the size
- of the columns. The number of rows──as with one-dimensional arrays that
- have only one row──is optional, as shown in the following legal example:
-
- Make_move(field)
- int field[][3];
-
- The TTT.C program (Listing 7-10 on the next page) is a somewhat
- unsophisticated tic-tac-toe game that will help you understand how to use
- two-dimensional arrays. It's an easy game to win. Forcing a tie, however,
- is difficult. For clarity, the declaration for field in Make_move()
- includes the number of rows.
-
- ──────────────────────────────────────────────────────────────────────────
- /* ttt.c -- a tic-tac-toe game demonstrates */
- /* passing two-dimensional arrays */
- /* to functions */
-
- main()
- {
- static char board[3][3] = {
- { '-', '-', '-' },
- { '-', '-', '-' },
- { '-', '-', '-' },
- };
- int row, col, ch;
- extern char Check_winner();
- extern void Make_move(), Draw_field();
-
- printf("You are X and make the first move.\n");
- while (1)
- {
- printf("Specify coordinate for your X.\n");
- printf("(For example, a2, or Q to quit)\n");
-
- /* Print the square. */
- Draw_field(board);
-
- /* Enter the user's coordinates. */
- if ((row = getch()) == 'Q')
- exit(0);
- row -= 'a';
- col = getch() - '1';
-
- /* Check for a legal move. */
- if (row < 0 || row > 2 || col < 0 || col > 2)
- {
- printf("Bad Square Specification\n\n");
- continue;
- }
- if (board[row][col] != '-')
- {
- printf("Sorry, Square Occupied\n\n");
- continue;
- }
-
- /* Make the move. */
- board[row][col] = 'X';
- if ((ch = Check_winner(board)) != '-' || ch == 't')
- break;
- Make_move(board);
- if ((ch = Check_winner(board)) != '-' || ch == 't')
- break;
- }
- Draw_field(board);
- if (ch == 't')
- printf("It's a tie!\n");
- else if (ch == 'X')
- printf("You win!\n");
- else
- printf("I win!\n");
- }
-
- char Check_winner(char field[][3])
- {
- int row, col;
-
- for (row = col = 0; row < 3; ++row, ++col)
- {
- if (field[row][0] != '-' /* horizontal */
- && field[row][0] == field[row][1]
- && field[row][1] == field[row][2])
- {
- return(field[row][0]);
- }
- if (field[0][col] != '-' /* vertical */
- && field[0][col] == field[1][col]
- && field[1][col] == field[2][col])
- {
- return(field[0][col]);
- }
- }
- if (field[0][0] != '-' /* right diagonal */
- && field[0][0] == field[1][1]
- && field[1][1] == field[2][2])
- {
- return(field[0][0]);
- }
- if (field[0][2] != '-' /* left diagonal */
- && field[0][2] == field[1][1]
- && field[1][1] == field[2][0])
- {
- return(field[0][2]);
- }
-
- for (row = 0; row < 3; ++row) /* any moves left */
- {
- for (col = 0; col < 3; ++col)
- {
- if (field[row][col] == '-')
- {
- return('-');
- }
- }
- }
- return ('t');
- }
-
- void Make_move(char field[3][3])
- {
- int row, col;
-
- for (row = 2; row >= 0; --row)
- {
- for (col = 2; col >= 0; --col)
- {
- if (field[row][col] == '-')
- {
- field[row][col] = 'O';
- return;
- }
- }
- }
- }
-
- void Draw_field(char field[][3])
- {
- int row, col;
-
- printf("\n 1 2 3\n\n");
- for (row = 0; row < 3; ++row)
- {
- printf("%c ", 'a' + row);
- for (col = 0; col < 3; ++col)
- {
- printf(" %c ", field[row][col]);
- }
- printf("\n");
- }
- printf("\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-10. The TTT.C program.
-
- Arrays of Three and More Dimensions
-
- In C you can give an array an unlimited number of dimensions, but remember
- that the more dimensions an array has, the more unmanageable it becomes.
- You have already seen how two-dimensional arrays are declared,
- initialized, and passed to functions. The rules for using more dimensions
- are a simple extension of those same concepts.
-
- Declaring Multidimensional Arrays
-
- The general rule for declaring multidimensional arrays is as follows:
-
- type name[exp][exp][exp][exp] ...
- │ │ │ │ └─────────────────────────────────── Etc.
- │ │ │ └──────────────────────────── Fourth dimension
- │ │ └────────────────────────────────── Third dimension
- │ └────────────────────────────────────── Second dimension
- └──────────────────────────────────────────── First dimension
-
- First, specify the type that will be stored in each array item, and then
- name the entire array. Each exp is an integer constant expression that
- specifies the number of elements in that dimension. Each succeeding
- square-bracketed [exp] defines another dimension.
-
- Think of a three-dimensional array as a cube. Figure 7-6 shows a
- conceptualization of a cube that corresponds to the following declaration:
-
- #define DEPTH 3
- #define ROWS 3
- #define COLS 3
-
- int cube[DEPTH][ROWS][COLS];
- │ └─────┬────┘
- │ └─────────────── Size of each two-dimensional array
- └─────────────────── Number of two-dimensional arrays (depth)
-
- You can also think of this three-dimensional array as an array of three
- two-dimensional arrays.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 7-6 can be found on p.213 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 7-6. Three-dimensional arrays can be thought of as a cube.
-
- Initializing Multidimensional Arrays
-
- When you use a list of values to initialize a multidimensional static or
- global array, the compiler reads that list from left to right, filling the
- array row by row for each plane of the depth. The following declaration
- places the initializing values into cube, beginning with the value 1, as
- shown in Figure 7-7.
-
- int cube[3][3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9,
- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
- 22, 23, 24, 25, 26, 27};
-
- To specify the order for initializing, you can enclose any group with
- braces. Those braces correspond to the depth first, then to the rows and
- columns. You can therefore rewrite the above declaration more clearly as
- follows:
-
- int cube[3][3][3] =
- {
- { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} },
- { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} },
- { {19, 20, 21}, {22, 23, 24}, {25, 26, 27} }
- };
-
- Here each inner set of braces encloses a given row's list of initializers.
- Use this technique when you need to underinitialize a given row, column,
- or depth. Clearly, as you progress beyond three dimensions, initializing
- can become very confusing. Just remember the general rules, or, in
- despair, simplify your algorithm.
-
- ┌───────────────────────\
- │\┌───────┬───────┬───────\
- │ │ 19 │ 20 │ 21 │ \
- │\├───────┼───────┼───────┤ \
- │ │ 22 │ 23 │ 24 │ \
- │\├───────┼───────┼───────┤ \
- ┌─\ │ 25 │ 26 │ 27 │ \
- │ \└───────┴───────┴───────┘ \
- │ \ ┌───────────────────────\
- │ \ │\┌───────┬───────┬───────\
- │ \ │ │ 10 │ 11 │ 12 │ \
- │ \ │\├───────┼───────┼───────┤ \
- │ \ │ │ 13 │ 14 │ 15 │ \
- │ \ │\├───────┼───────┼───────┤ \
- │ │ │ 16 │ 17 │ 18 │ \
- Depth──┤ \└───────┴───────┴───────┘ \
- │ \ ┌────────────────────────\
- │ \ │\┌───────┬───────┬───────┐──┐
- │ \ │ │ 1 │ 2 │ 3 │ │
- │ \ │\├───────┼───────┼───────┤ │
- │ \ │ │ 4 │ 5 │ 6 │ ├──Row
- │ \│\├───────┼───────┼───────┤ │
- │ \ │ 7 │ 8 │ 9 │ │
- └─────────────────────────────────\└───────┴───────┴───────┘──┘
- │ │
- └───────────┬───────────┘
- Columns
-
- Figure 7-7. Initializing a three-dimensional array.
-
- Using Multidimensional Arrays in Functions
-
- To pass a multidimensional array to a function, you need specify only the
- name of that array as an argument:
-
- Draw_planes(cube);
-
- On the receiving end──in the function Draw_planes()──you must specify the
- sizes of all but the leftmost dimension. That size is optional, as in the
- following:
-
- int
- Draw_planes(box);
- int box[][3][3];
- {
-
- The BOX.C program (Listing 7-11) shows the initialization of a
- three-dimensional array and then prints out the result.
-
- ──────────────────────────────────────────────────────────────────────────
- /* box.c -- demonstrates the result of initializing */
- /* a three-dimensional array */
-
- main()
- {
- static int cube[3][3][3] = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
- 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27 };
- int plane;
- extern void Draw_plane();
-
- for (plane = 0; plane < 3; ++plane)
- {
- Draw_plane(cube, plane);
- }
- }
-
- void Draw_plane(int box[3][3][3], int slice)
- {
- int row, col;
-
- printf("Plane[%d] =\n", slice);
- for (row = 0; row < 3; ++row)
- {
- for (col = 0; col < 3; ++col)
- {
- printf( "%2d ", box[slice][row][col]);
- }
- printf("\n");
- }
- printf("\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-11. The BOX.C program.
-
-
- Advanced Topics and Tricks
-
- In this section we discuss three advanced techniques that can be very
- handy:
-
- ■ Negative subscripting
-
- ■ Large and huge arrays
-
- ■ Passing pieces of arrays
-
- Negative Subscripting
-
- Recall that unless you include code to perform bounds checking, C lets you
- reference items both beyond the end and before the beginning of an array.
- You have already seen the consequences of referencing beyond an array's
- end. Referencing before its beginning is something new, as Figure 7-8
- demonstrates.
-
- If you declare three consecutive arrays such as the following:
-
- int first[3], second[3], third[3];
-
- and then reference with a negative subscript:
-
- second[-1]
-
- you actually reference either third[2] or first[2], depending on whether
- the arrays are auto or static, as shown in Figure 7-8. QuickC places auto
- arrays into memory from right to left (top down) and static arrays from
- left to right (bottom up).
-
- second [-1]
- │
- │
- ┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐
- │ 7 │ 8 │ 9 │ 4 │ 5 │ 6 │ 1 │ 2 │ 3 │
- └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
- │ │ │ │
- └───────────┬───────────┴───────────┬───────────┴───────────┬───────────┘
- │ │ │
- third [3] second [3] first [3]
-
- (A) ARRAY DECLARED auto
-
-
- second [-1]
- │
- │
- ┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐
- │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
- └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
- │ │ │ │
- └───────────┬───────────┴───────────┬───────────┴───────────┬───────────┘
- │ │ │
- first [3] second [3] third [3]
-
- (B) ARRAY DECLARED static
-
- Figure 7-8. Effect of negative subscripting on auto and static arrays.
-
- The L2WORDS.C program (Listing 9-11 on pp. 282─83) illustrates this
- technique.
-
- Large and Huge Arrays
-
- On the IBM PC an integer is 2 bytes long, so you have to be careful when
- declaring arrays larger than 32,767 elements. That is because 32,767 is
- the highest positive value a 2-byte integer can hold. If you successively
- reference the elements in an array with the following loop:
-
- int i;
- for (i = 0; i < 40000; i++)
- printf("%dn", array[i]);
-
- the 0th through 32,767th elements print out correctly, but the 32,768th
- element prints out as array[-32768].
-
- Integers are signed variables, so they wrap to negative numbers when they
- exceed their highest positive value. Therefore, to reference elements in
- large arrays, use either unsigned int or long offsets.
-
- Another problem occurs when arrays grow to more than 65,536 bytes total on
- the IBM PC. In this case, use the huge keyword in the array declaration,
- as follows:
-
- int huge bigbox[100][100][100];
-
- Here the keyword huge is required because the total size of the array
- bigbox is 100 x 100 x 100 times 2 (two bytes per int), or 2,000,000 bytes
- total. This tells the compiler to set aside more space for this array than
- the space reserved for ordinary variables. Whenever you use large arrays
- that require the huge keyword, compile with the large memory model. That
- model will be discussed in greater detail in Chapter 12. (Of course, you
- will need lots of memory in your computer, too.)
-
- Passing Pieces of Arrays
-
- When you reference array elements with fewer dimensional offsets than were
- present in that array's declaration, you are actually referencing the
- address of a subarray. If, for example, you declare:
-
- int square[3][3];
-
- and then later reference that array without specifying the second
- dimension:
-
- Print_row(cube[1]);
-
- you would pass the address of cube's second row (a one-dimensional
- subarray) to Print_row(). Then declare Print_row() to receive a
- one-dimensional array:
-
- Print_row(row)
- int row[];
- {
-
-
- The Bitwise Operators, Tiny Arrays
-
- Just as arrays can get larger and larger and more and more complex, it is
- also possible to go the other direction and store data in the individual
- bits of a single byte. You can manipulate individual bits of a byte using
- the bitwise operators. Those operators are:
-
- Operator Description
- ──────────────────────────────────────────────────────────────────────────
- & The bitwise AND operator
- | The bitwise OR operator
- ^ The bitwise exclusive-OR operator
- ~ The unary inversion operator (ones-complement)
- >> The unary right-shift operator
- << The unary left-shift operator
- ──────────────────────────────────────────────────────────────────────────
-
- Each of these affects the individual bits in the bytes of a value, which
- can be either a constant or a variable. Remember, a char uses 8 bits, an
- int 16 bits, and a long 32 bits. First, we demonstrate the application of
- the bitwise operators, and then discuss the logic of each.
-
- The BITWISE.C program (Listing 7-12) lets you enter values interactively
- and then apply the bitwise operators to them. By running this program, you
- will better understand the discussion that follows. (Note that a set bit
- is represented with a 1 and a clear bit is represented with a 0.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* bitwise.c -- demonstrates the bitwise operators */
-
- #include <stdio.h>
-
- main()
- {
- unsigned int val1, val2, result;
- int ch;
- extern void show();
-
- while(1)
- {
- printf("\nval1: ");
- if (scanf("%d", &val1) != 1)
- break;
-
- printf("val2: ");
- if (scanf("%d", &val2) != 1)
- break;
- printf("\tval1 = ");
- show(val1);
- printf("\tval2 = ");
- show(val2);
-
- printf("Bitwise Operator: ");
- while ((ch = getchar()) == '\n')
- {
- continue;
- }
- if (ch == EOF)
- break;
- switch (ch)
- {
- case '&':
- result = val1 & val2;
- printf("Executing: result = val1 & val2;\n");
- break;
- case '|':
- result = val1 |= val2;
- printf("Executing: result = val1 | val2;\n");
- break;
- case '^':
- result = val1 ^= val2;
- printf("Executing: result = val1 ^ val2;\n");
- break;
- case '~':
- result = ~val1;
- printf("Executing: result = ~val1;\n");
- printf("\tresult = ");
- show(result);
- result = ~val2;
- printf("Executing: result = ~val2;\n");
- break;
- case '<':
- result = val1 <<= val2;
- printf("Executing: result = val1 <<val2;\n");
- break;
- case '>':
- result = val1 >>= val2;
- printf("Executing: result = val1 >>val2;\n");
- break;
- case 'q':
- case 'Q':
- return(0);
- default:
- continue;
- }
- printf("\tresult = ");
- show(result);
- }
- }
- void bitout(unsigned char num[])
- {
- int bytes = 2, i, j;
-
- /* IBM PC stores ints low/hi. */
- for (i = bytes-1; i >= 0; --i)
- {
- for (j = 7; j >= 0; --j)
- {
- putchar((num[i]&(1<<j))?'1':'0');
- }
- }
- }
-
- void show(unsigned int val)
- {
- extern void bitout();
-
- printf("(%05u decimal)", val);
- bitout(&val);
- printf(" binary\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 7-12. The BITWISE.C program.
-
- The Binary Bitwise Operators
-
- The bitwise AND, the bitwise OR, and the bitwise exclusive-OR are binary
- operators. That is, like the addition operator, they operate on two
- values──not one. You can invoke them as follows:
-
- result = val1 & val2; /* bitwise AND */
- result = val1 | val2; /* bitwise OR */
- result = val1 ^ val2; /* bitwise exclusive-OR */
-
- Or you can invoke them with the op= form:
-
- result &= val1; /* bitwise AND */
- result |= val1; /* bitwise OR */
- result ^= val1; /* bitwise exclusive-OR */
-
- The Bitwise AND Operator
-
- The bitwise AND operator, &, compares the bits in two values and produces
- a value based on the comparison of the same bits in each:
-
- var1 & var2 yields result
- ──────────────────────────────────────────────────────────────────────────
- 1 1 1
- 0 1 0
- 1 0 0
- 0 0 0
- ──────────────────────────────────────────────────────────────────────────
-
- For the bitwise AND, the result bit is set only if the same bit in both
- values is set. Otherwise, the result bit is cleared.
-
- The bitwise AND operator is useful for turning off (clearing) a selected
- bit in a variable. A typical application for the & operator is to turn off
- a blinking cursor when you are accessing screen memory directly:
-
- var1 = 3;
- var1 &= 0xFFFE;─────────────────────────────────────────Turn off low bit
-
- This results in the following calculation:
-
- 0000000000000011 ───────────────────────────────────── var1
- & 1111111111111110 ─────────────────────────────────── OxFFFE
- ──────────────────
- Yields ───── 0000000000000010
-
- │
- └─────────────────────────── Low bit cleared
-
- The Bitwise OR Operator
-
- The bitwise OR operator, |, compares the bits in two values and sets the
- result bit for any bit that is set in either of the values:
-
- var1 | var2 yields result
- ──────────────────────────────────────────────────────────────────────────
- 1 1 1
- 0 1 1
- 1 0 1
- 0 0 0
- ──────────────────────────────────────────────────────────────────────────
-
- For the bitwise OR, the result bit is set if either or both corresponding
- bits in both values are set. Otherwise, the result bit is cleared.
-
- The bitwise OR operator is useful for turning on (setting) a selected bit
- in a variable. A typical application for the | operator is to turn on the
- high bit of a character variable before sending that character to the
- printer:
-
- var1 = 0;
- var1 |= 1;───────────────────────────────────────────────Turn on low bit
-
- This results in the following calculation:
-
- 0000000000000000 ───────────────────────────────────── var1
- | 0000000000000001 ────────────────── Equivalent to 1 decimal
- ──────────────────
- Yields ───── 0000000000000001
-
- │
- └─────────────────────────────── Low bit set
-
- The Bitwise exclusive-OR Operator
-
- The bitwise exclusive-OR operator, ^, compares the bits in two values and
- produces a set bit only if one bit or the other is set, but not both:
-
- var1 ^ var2 yields result
- ──────────────────────────────────────────────────────────────────────────
- 1 1 0
- 0 1 1
- 1 0 1
- 0 0 0
- ──────────────────────────────────────────────────────────────────────────
-
- For the bitwise exclusive-OR, ^, the result bit is set if one or the other
- of the corresponding bits in the values is set, but not both.
-
- The bitwise exclusive-OR operator is useful for toggling (setting,
- clearing, setting, etc.) a selected bit in a variable. A typical
- application for the ^ operator is to toggle a flag in a game, thereby
- determining which of two players is to make the next move:
-
- var1 = 0;
- var1 ^= 1;
- var1 ^= 1;
-
- This results in the following calculations:
-
- 0000000000000000 ───────────────────────────────────── var1
- ^ 0000000000000001 ────────────────── Equivalent to 1 decimal
- ──────────────────
- Yields ───── 0000000000000001
-
- ▓ │
- ▓ └──────────────────── Low bit set (Player 2)
- ▓
- ▓
-
-
- 0000000000000001 ──────────────────────── New value of var1
- ^ 0000000000000001 ────────────────── Equivalent to 1 decimal
- ──────────────────
- Yields ───── 0000000000000000
-
- │
- └─────── Low bit toggled to clear (Player 1)
-
- The Unary Bitwise Operators
-
- The unary bitwise operators affect the bits of a single value. These
- operators are:
-
- Operator Description
- ──────────────────────────────────────────────────────────────────────────
- ~ The unary ones-complement operator
- >> The unary right-shift operator
- << The unary left-shift operator
- ──────────────────────────────────────────────────────────────────────────
-
- The Unary Ones-Complement Operator
-
- The ones- complement of a variable is derived by inverting all the bits in
- that value. If a bit is set, that bit changes to clear, and vice versa. In
- C, the ~ causes the bits in a value to be inverted, as follows:
-
- var1 0000000000000111
-
- ~var1 1111111111111000──────────────────────────────────────────Result
-
- Two applications are common for the ones-complement operator. One is to
- set selected bits in a variable regardless of the number of bytes occupied
- by that variable. Suppose you have an int and you want all but the zeroth
- bit set. One way to do this is by:
-
- number = 0xFFFE;
-
- This does the job, but pays the price of assuming an int always occupies
- two bytes of storage. Although that is true on the IBM PC, it is not the
- case on most 32-bit machines. The correct way to set all but the zeroth
- bit──and the portable way──is to use the ones-complement operator:
-
- number = ~1;
-
- The second common application for the ones-complement operator is turning
- off selected bits. One way to turn off the zeroth bit, while leaving all
- the other bits in a variable unchanged, is:
-
- masks &= 0xFFFE;
-
- But again, the ones-complement operator should be used for portability:
-
- masks &= ~1;
-
- The Unary Shift Operators
-
- The shift operators move all the bits in a variable right or left by the
- number of bit positions specified. The shift operators are used as
- follows:
-
- result = value <<bits;
- result = value >>bits;
-
- Here the first line shifts the value in value left──from the low toward
- the high bit──by the number of bit positions specified by bits. The second
- line shifts value in the opposite direction──right, or from the high
- toward the low bit. For example:
-
- val1 0000000000111000
- val1 <<3 0000000111000000───────────────────────────────────Left shift
- val1 >>3 0000000000000111──────────────────────────────────Right shift
-
- When shifting left, the bits on the right are filled with clear bits. With
- QuickC, the fill bits for a right shift are always set. For portability,
- however, always use unsigned variables when right-shifting.
-
- The shift operators are useful for aligning a bit prior to ORing it into a
- variable. Shifting also provides a quick way to multiply or divide by 2.
- Each bit you shift to the left multiplies a number by 2; each bit you
- shift to the right divides it by 2.
-
- int val = 1;
-
- val <<= 1;──────────────────────────────────────────────val now equals 2
- val <<= 1;──────────────────────────────────────────────val now equals 4
- val <<= 1;──────────────────────────────────────────────val now equals 8
-
- Summary of Bitwise Operators
-
- If you haven't already done so, enter, compile, and run the BITWISE.C
- program (Listing 7-12 on p. 218). Watching the actions of bits as the
- program applies each bitwise operator will give you a feel for bits and
- will lead you to develop sophisticated applications of your own. You will
- find the bitwise operators used a great deal in the hardware-specific
- chapters at the end of this book.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 8 Addresses and Pointers
-
- One of the chief strengths of C is its ability to manipulate individual
- areas of memory with almost the same precision that assembly language
- provides. This chapter discusses this ability in detail by showing you a
- new kind of variable called a "pointer"──a variable whose contents
- identify a memory address. Using pointers can greatly increase the speed
- at which your programs execute, lets you access your computer's hardware
- directly, and allows you to write subroutines that manipulate variables
- directly (via the address).
-
-
- Addresses Reviewed
-
- The concept of memory addresses is vital to C programming. Recall, for
- example, that all arguments passed to scanf() must be preceded by an
- ampersand (&). In the following expression:
-
- scanf("%d", &num);
-
- the & tells scanf() to read an integer from the keyboard and to place that
- input value into the variable num, whose address is passed with the
- expression &num.
-
- You also used addresses with arrays. In the previous chapter we mentioned
- that when an entire array is passed to a function, it is passed as
- addresses. For example, the code fragment
-
- char choices[4] = {'Q', 'E', 'S', 'L'};
-
- Get_move(choices);
-
- passes the address of the choices array to the function Get_move(), rather
- than the individual elements of that array. When you use an array name
- without specifying an element in square brackets, the compiler uses the
- internal starting memory address of that array as its value.
-
- One of C's strengths is the ease with which it lets you manipulate the
- values of variables by way of their addresses. This type of address
- manipulation, known as indirection, is accomplished with pointers.
-
-
- What Is a Pointer?
-
- A pointer, in its simplest form, is a variable whose value (contents) is
- an address, or a number corresponding to a specific location in memory.
- That is, if address_var is a pointer-type variable, and num is an integer
- variable, the expression
-
- address_var = #
-
- causes the address of the variable num to be placed into the pointer
- variable named address_var. This assignment ignores the actual value of
- num.
-
- You can use pointers in your programs to:
-
- ■ Save information from functions that return addresses
-
- ■ Indirectly return more than one value from a function
-
- ■ Speed up execution by manipulating pointers rather than large blocks of
- data
-
- ■ Access and modify text screen memory
-
- ■ Call functions using their addresses, thus creating more flexible code
-
- ■ Access and manipulate strings
-
- Before you can use a pointer, also called a "pointer to," you must declare
- it. Declaring a pointer is much like declaring an ordinary variable, the
- only difference being that you must always precede the pointer's name with
- the * character.
-
- The following example declares two variables: an integer called num and a
- pointer called address_var.
-
- int num, *address_var;
-
- The * before address_var tells the compiler that address_var is a pointer
- whose contents will be an address. Because address_var is declared as type
- int, the compiler knows that address_var will contain the address of an
- integer variable. Figure 8-1 illustrates this process.
-
- In this example, we declare two variables and two pointers (Figure 8-1a).
- The variables are num (an int) and fval (a float). We also declare two
- pointers, address_var and faddress_var.
-
- The pointer address_var contains the address of an int type of variable;
- pointer faddress_var contains the address of a float type of variable. The
- two assignment statements in Figure 8-1b store the addresses in the
- appropriate pointers. The result of the assignment is that address_var now
- holds num's address (and thus points to num), and faddress_var holds
- fval's address (and thus points to fval).
-
- int num/ *address_var; ───┐
- │▒▒▒▒▒▒▒▒▒▒ These declarations
- float fval, *faddress-var;──┘ ▒ create...
-
-
- Memory── 4096 4097 4098 4099 4050 4051 4052 4053 4054 4055
- locations ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- in bytes │ │ │ │ │ │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ │ │ │
- │ │ └───────────┬───────────┘ │
- │ │ │ │
- num address-var fval faddress-vars
-
- (A)
-
-
- address_var = # ───┐
- │▒▒▒▒▒▒▒▒▒▒ These assignments
- faddress-var = &fval; ─┘ ▒ yield...
-
-
- ┌────&num──────┐ ┌──────────&fval──────────┐
- │ │ │ │
- Memory── 4096 4097 4098 │4099 4050 4051 4052 4053 4054 │4055
- locations ┌─────┬─────┬──────────┬─────┬─────┬─────┬─────┬──────────┐
- in bytes │ │ 4096 │ │ │ 4050 │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ │ │ │
- │ │ └───────────┬───────────┘ │
- │ │ │ │
- num address-var fval faddress-var
- ▒ ▒
- ▒ ▒ ▒ ▒
- ▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- Points to Points to
-
- (B)
-
- Figure 8-1. Result of assigning an address to a pointer.
-
-
- Accessing Variables with Pointers
-
- The * operator (pronounced "star"), when used to signify a pointer, is
- called the indirection operator because it lets you access variables
- indirectly. When you use the * operator in front of a pointer (other than
- in its declaration) you tell the compiler to fetch or store the value that
- the pointer points to. For example, in the fragment
-
- int num, *address_var;
-
- address_var = #
- *address_var = 3;
-
- you first declare an int variable (num) and a pointer to an int
- (address_var). Next, the value you store in address_var is the address of
- the variable num. Finally, the * in front of address_var tells the
- compiler to store the value 3 in the variable whose address is stored in
- address_var. Because address_var contains the address of num, that value
- is stored in num. (See Figure 8-2 on p. 234.)
-
- The POINTER.C program (Listing 8-1) illustrates the procedure for
- declaring, assigning a value to, and using pointers.
-
- ──────────────────────────────────────────────────────────────────────────
- /* pointer.c -- demonstrates pointer declaration, */
- /* assignment, and use */
-
- #define WAIT printf("(press any key)"); getch(); \
- printf("\n\n")
-
- main()
- {
- int num, *address_var;
-
- num = 0;
- address_var = #
-
- printf("The address of the variable ");
- printf("\"num\" is: 0x%04X\n", &num);
- printf("The value in the pointer ");
- printf("\"address_var\" is: 0x%04X\n", address_var);
- printf("The value in the variable ");
- printf("\"num\" is: %d\n", num);
- WAIT;
- printf("Since \"address_var\" points to \"num\"\n");
- printf("the value in ");
- printf("\"*address_var\" is: %d\n", *address_var);
- WAIT;
- printf("To verify this, let's store 3 in\n");
- printf("\"*address_var\", then print out ");
- printf("\"num\" and \"*address_var\"\n");
- printf("again.\n");
- WAIT;
- printf("Doing: *address_var = 3;\n\n");
- *address_var = 3;
-
- printf("The address of the variable ");
- printf("\"num\" is: 0x%04X\n", &num);
- printf("The value in the pointer ");
- printf("\"address_var\" is: 0x%04X\n", address_var);
- printf("The value in the variable ");
- printf("\"num\" is: %d\n", num);
- WAIT;
- printf("Since \"address_var\" points to \"num\"\n");
- printf("the value in ");
- printf("\"*address_var\" is: %d\n", *address_var);
- WAIT;
-
- printf("Now we will add 15 to \"num\" and print\n");
- printf("\"num\" and \"*address_var\" again.\n");
- WAIT;
-
- printf("Doing: num += 15;\n\n");
- num += 15;
-
- printf("The address of the variable ");
- printf("\"num\" is: 0x%04X\n", &num);
- printf("The value in the pointer ");
- printf("\"address_var\" is: 0x%04X\n", address_var);
- printf("The value in the variable ");
- printf("\"num\" is: %d\n", num);
- WAIT;
- printf("Since \"address_var\" points to \"num\"\n");
- printf("the value in ");
- printf("\"*address_var\" is: %d\n", *address_var);
- WAIT;
-
- printf("Doing: return (*address_var);\n\n");
- return (*address_var);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-1. The POINTER.C program.
-
- The output of this program follows. Compare it to the listing.
-
- The address of the variable "num" is: 0x1388
- The value in the pointer "address_var" is: 0x1388
- The value in the variable "num" is: 0
- (press any key)
-
- Since "address_var" points to "num"
- the value in "*address_var" is: 0
- (press any key)
- To verify this, let's store 3 in
- "*address_var", then print out "num" and "*address_var"
- again.
- (press any key)
-
- Doing: *address_var = 3;
-
- The address of the variable "num" is: 0x1388
- The value in the pointer "address_var" is: 0x1388
- The value in the variable "num" is: 3
- (press any key)
-
- Since "address_var" points to "num"
- the value in "*address_var" is: 3
- (press any key)
-
- Now we will add 15 to "num" and print
- "num" and "*address_var" again.
- (press any key)
-
- Doing: num += 15;
-
- The address of the variable "num" is: 0x1388
- The value in the pointer "address_var" is: 0x1388
- The value in the variable "num" is: 18
- (press any key)
-
- Since "address_var" points to "num"
- the value in "*address_var" is: 18
- (press any key)
-
- Doing: return (*address_var);
-
- In the POINTER.C program, the pointer address_var contains the address of
- num (as a result of the assignment address_var = &num) and therefore
- yields the value stored in num. That is, we indirectly access num via its
- address (*address_var = 3). Because address_var contains num's address,
- you can use *address_var anywhere you would use num. For example, we could
- have ended the program with return (num) to produce the same result.
-
-
- Passing Pointers to Functions
-
- Until now, with the exception of arrays, we have passed arguments to
- functions by value. Thus, you might think we could write a function that
- squares the argument passed to it as follows:
-
- square(int num)
- {
- num *= num;
- }
-
- This doesn't work, however, because the variable num is a local variable
- to the function square(), and the result is not accessible by other
- functions. Thus, calling square() with
-
- main()
- {
- int val = 5;
-
- square(val);
- }
-
- does not result in main()'s variable val being squared, because main()
- doesn't "see" the variable num.
-
- You can get around this by having square() return a value, as follows:
-
- main()
- {
- int val = 5;
-
- val = square(val);────────────────────────Value returned by square()
- }
-
- square(int num)
- {
- num *= num;
- return (num);─────────────────────────────────square() returns value
- }
-
- Another approach is to use pointers. When you pass a pointer to a
- function, you still pass a copy of its value, but the value you pass is an
- address. Therefore, in square(), you must declare num as a pointer because
- it will receive an address:
-
- square(int *address_var) ─────────────────── Pointer receives an address
- {
-
- *address_var *= *address_var;
- } └────────────────────────────── Multiplication operator
-
- This form of square() receives an address as its argument. The pointer to
- hold that address, address_var, is declared as int *address_var because it
- receives the address of an int variable.
-
- To use this new square() function, we must pass it an address. We can do
- this in either of two ways. We can use the & operator, as follows:
-
- main()
- {
- int val = 5;
-
- square(&val);────────────────────────────────────────Pass an address
- }
-
- Or we can pass a pointer:
-
- main()
- {
- int val = 5, *here;
-
- here = &val;
- square(here);─────────────────────The value of here is val's address
- }
-
- After making our declarations, we place the address of val into the
- pointer here. When we pass here to square(), its value──the address of
- val──is what is actually passed. This results in val being squared.
-
- The SQUARE.C program (Listing 8-2) summarizes this passing of pointers
- and addresses in an interactive quiz. In it, we've expanded on our
- original square() subroutine. In the new Square(), we return two values
- from a single function! The first, returned by return, is an error
- status──zero for a successful square and -1 for any attempt to square a
- number larger than 181 or less than -181 (the square root of 32,767, the
- largest signed int on the IBM PC). We return the second value with the
- pointer *where.
-
- ──────────────────────────────────────────────────────────────────────────
- /* square.c -- a quiz to demonstrate passing */
- /* pointers and addresses in functions */
-
- main()
- {
- int val, count, guess;
-
- for (count = 1; count < 255; ++count)
- {
- val = count;
- printf("What is the square of %d?\n", val);
- if (scanf("%d", &guess) != 1)
- return(0); /* non-number exits */
-
- if(Square(&val) != 0) /* pass val's address */
- {
- printf("Range Error\n");
- exit(1);
- }
- if (val != guess)
- printf("Wrong. It is %d.\n", val);
- else
- printf("Right!\n");
- printf("Continue? ");
- if (getche() != 'y')
- break;
- }
- }
- int Square(int *where)
- {
- if (*where > 181 || *where < -181)
- return (-1);
- *where = (*where) * (*where);
- return (0);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-2. The SQUARE.C program.
-
- In this program, we use a separate variable, count, in the for loop
- because the value of val is indirectly changed by the call to Square(). If
- we had used val as follows:
-
- for (val = 1; val < 255; ++val)
-
- you would be prompted only for the numbers 1, 2, 5, and 26, and then you
- would receive a Range Error.
-
-
- Pointers and Arrays
-
- Pointers let you manipulate strings and arrays more succinctly and
- efficiently. We'll learn about strings in the next chapter. Here we will
- discuss the relationship between arrays and pointers, detailing potential
- pitfalls along the way.
-
- Recall from the previous chapter that referencing an array by name,
- without an offset, yields that array's address. What we didn't tell you
- was that the address of an array is the same as the address of the array's
- first element. For example, in the following array declaration:
-
- int coins[4] = {25, 10, 5, 1};
-
- the reference
-
- Find_change(coins, amount);
-
- actually causes the address of the array coins[4] to be passed to the
- function Find_change(). Because the address of an array is the location in
- memory of its beginning, we can also reference that array with the
- expression
-
- &coins[0]
-
- Here the address operator & yields the address of the first item in the
- array coins and, therefore, the address of the array itself.
-
- You can assign the address of another variable to a pointer with the &
- operator (address_var = &num[1]). Because each item in an array is a
- variable, the assignment
-
- address_var = &coins[0];
-
- results in address_var containing the address of the first integer in the
- array coins.
-
- Because &coins[0] and coins are equivalent, the following expression is
- the same as the one above:
-
- address_var = coins;
-
- Now here comes the exciting part. When a value, say 1, is added to a
- pointer, it increments the address in that pointer by the number of bytes
- in the type to which it points. For example, in Figure 8-2 the variable
- address_var begins with a value that is the address of coins. Notice what
- happens when we add 1 to address_var. Because address_var is a pointer to
- the type int, and because an int occupies two bytes (on the IBM PC), the
- value in address_var is increased by 2. The new value in address_var is
- thus the address of coins[1] (the next element in the array).
-
- Points to
- ▒▒▒▒▒▒▒▒▒▒
- ▒ ▒
- ┌─────────────┐ ▒
- │ │
- Memory locations── 4392 4393 4394 4395 4396 4397 4398 4399
- in bytes ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- │ 4394 │ │ │ │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ │ │
- │ │ │ │
- │ │ │ │
- address-var coins[0] coins[1] coins[2]
-
- ▒ ▒
- ▒ ▒
- 4392 4393 ▒ ▒
- ┌─────┬─────┐ ▒ ▒
- │ 4394 │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒
- └─────┴─────┘ Next points to ▒
- │ ▒
- │ ▒
- │ ▒
- Increment value───address_var += 1; ▒
- of pointer ▒
- ▒
- ▒
- 4392 4393 ▒
- ┌─────┬─────┐ ▒
- │ 4398 │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- └─────┴─────┘ Then points to
- │
- │
- │
- Increment pointer───address_var += 1;
- again
-
- Figure 8-2. The value of a pointer increases by multiples of the number of
- bytes in the data type to which it points.
-
- The CHANGE.C program (Listing 8-3) demonstrates how the pointer coin_ptr
- advances through the array coins, each step determined by the number of
- bytes for the type int. Compile the program with the Debug option set
- because we want to trace its execution.
-
- ──────────────────────────────────────────────────────────────────────────
- /* change.c -- a change-making program demonstrates */
- /* how pointers advance the correct */
- /* number of bytes based on type */
-
- #define NCOINS (4)
- #define CENT (0x9b) /* IBM PC cent character */
- #define WAIT printf("(Press any key to continue)"); \
- getch(); printf("\n\n")
-
- main()
- {
- static int coins[NCOINS] = {25, 10, 5, 1};
- int *coin_ptr, i = 0;
- int pennies1, pennies2, count;
- float amount;
-
- printf("Enter an amount and I will ");
- printf("give you change.\nAmount: ");
- if (scanf("%f", &amount) != 1)
- {
- printf("I don't know how to change that!\n");
- exit(1);
- }
- pennies2 = pennies1 = (int)(amount * 100.0);
-
- coin_ptr = coins;
- for (i = 0; i < NCOINS; ++i)
- {
- WAIT;
- count = 0;
- while ((pennies1 -= coins[i]) >= -1)
- ++count;
- if (count > 0)
- {
- printf("%4d %2d%c", count, coins[i], CENT);
- printf(" coins by array offset.\n");
- }
- if (pennies1 == 0)
- break;
- pennies1 += coins[i];
-
- count = 0;
- while ((pennies2 -= *coin_ptr) >= 0)
- ++count;
- if (count > -1)
- {
- printf("%4d %2d%c", count, *coin_ptr, CENT);
- printf(" coins by pointer indirection.\n");
- }
- if (pennies2 == 0)
- break;
- pennies2 += *coin_ptr;
- ++coin_ptr;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-3. The CHANGE.C program.
-
- After you compile CHANGE.C, turn off screen swapping and specify the
- following four watch variables:
-
- coin_ptr
- *coin_ptr
- i
- coins[i]
-
- (See pp. 119-20 if you forgot how to specify watch variables. You don't
- need to specify the types with a comma; the defaults are correct.) Now
- step through the program with the F8 function key. Observe that as ++i
- followed by coins[i] steps through the array, so does ++coin_ptr followed
- by *coin_ptr. Figure 8-3 shows the screen as the program is being traced.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 8-3 can be found on p.236 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 8-3. Incrementing a pointer moves it through an array in steps
- that correspond to the number of bytes in the data type.
-
- This equivalence between arrays and incrementing pointers is one of C's
- chief strengths. It can also be confusing and can lead to some unexpected
- bugs. In the CHANGE.C program, we perform bounds checking with the for
- loop. If we rewrite that loop without i, we need to do one of two things.
- One option is to put some stop value into our array, such as the last
- element (0) in the following:
-
- int coins[5] = {25, 10, 5, 1, 0};
-
- This approach is often used with string variables.
-
- The other option requires that we add some means of detecting when the
- address in coin_ptr becomes too large, as follows:
-
- for (coin_ptr = coins; coin_ptr < &coins[4]; ++coin_ptr)
-
- This approach is more common for situations where a stop value is not
- practical. In a database, for example, you might have 32 bytes available
- and want to use all 32 for a mailing address, with none reserved for a
- terminating value.
-
-
- Pointer Arithmetic
-
- QuickC permits fewer arithmetic operations on pointers than on other kinds
- of variables. Because pointers contain addresses as their values, whenever
- you change one, you reference a new location inside your computer's
- memory. Obviously, you don't want to reference random locations──not only
- would they be meaningless, but they might overwrite crucial memory
- locations and crash your PC.
-
- To help avoid such meaningless addresses, C permits only a handful of
- mathematical operations to be performed on pointers. They are:
-
- Addition You can add values to addresses (like incrementing with ++).
- This is most useful with arrays.
-
- Subtraction You can subtract values from addresses (decrementing with --
- and subtracting with -).
-
- Comparison You can compare one address to another to see if it is greater
- than, less than, equal to, or not equal to the other.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- C does not provide many safeguards against referencing incorrect
- addresses. QuickC, however, lets you compile with Pointer Check turned on.
- Although this provides a measure of safety (by verifying that pointer
- values address program data), it results in slower-executing programs.
- ──────────────────────────────────────────────────────────────────────────
-
- The operations allowed on pointers are a small set when compared to the
- operations allowed on numeric variables and array items. Let's examine why
- you cannot use the other arithmetic operations:
-
- Multiplication Doubling an address or even multiplying it by, let's say,
- 523 would yield a new address value that, at best, would be somewhere in
- your data and at worst would be beyond the end of your program, possibly
- in the code of another memory resident program (like QuickC or
- COMMAND.COM).
-
- Division Halving an address or even dividing it by, let's say, 10 would
- yield a new address value that, at best, would be somewhere inside your
- own code and at worst would be inside the MS-DOS interrupt vectors.
-
- Bitwise operators You cannot manipulate the bits in an address. This
- would result in a totally random address.
-
- Unary negation You can't reverse the sign of an address because addresses
- are always unsigned.
-
- Now let's look at the CHANGE2.C program (Listing 8-4). This rewrite of
- CHANGE.C illustrates the incrementing of pointers and the comparison of
- two pointers.
-
- ──────────────────────────────────────────────────────────────────────────
- /* change2.c -- modified to demonstrate passing */
- /* an address to a function */
-
- #define NCOINS (4)
- #define CENT (0x9B) /* IBM PC cent character */
-
- main()
- {
- static int coins[NCOINS] = {25, 10, 5, 1};
- int pennies;
- float amount;
-
- printf("Enter an amount and I will ");
- printf(" give you change.\nAmount: ");
- if (scanf("%f", &amount) != 1)
- {
- printf("I don't know how to change that!\n");
- exit(1);
- }
- pennies = (int)(amount * 100.0);
-
- Show_change(coins, &coins[NCOINS], pennies);
-
- }
- Show_change(int amts[], int *end, int due)
- {
- int count;
-
- while (amts < end) /* compare pointers */
- {
- count = 0;
- while ((due -= *amts) >= -1)
- {
- ++count;
- }
- if (count > 0)
- printf("%4d %2d%c\n", count, *amts, CENT);
- if (due == 0)
- break;
- due += *amts;
-
- ++amts; /* increment a pointer */
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-4. The CHANGE2.C program.
-
- The function Show_change() receives addresses of the array coins and the
- fourth element in that array (one past its end). This introduces some new
- concepts: the interchangeability of the declaration coins[] with the
- declaration *coins and the importance of left versus right values.
-
-
- The Interchangeability of *amts and amts[]
-
- In the following declaration:
-
- Show_change(int amts[]);
- {
-
- the expression int amts[] tells the compiler to pass this array to the
- function Show_change(). However, you can also use an array declaration of
- the form *amts interchangeably with amts[]. The two are equivalent. In
- fact, if you declare an array as amts[], you can use that array's name as
- though it were a pointer:
-
- Show_change(int amts[])
- { └──────────────────────────── Declared as an array
- ...
- due = *amts;
- └──┴──────────────────────────────── but used as a pointer
-
- and vice versa:
-
- Show_change(int *amts[])
- { └────────────────────────── Declared as a pointer
- ...
- due = amts[i];
- └──┴─────────────────────────────── but used as an array
-
- Note, however, that this interchangeability works only when the array is
- declared as one of a function's received arguments. An attempt to use that
- singular equivalence elsewhere results in either a Syntax or an lvalue
- error.
-
-
- lvalue vs rvalue
-
- An lvalue is any variable whose value can change (have a new value
- assigned to it). An rvalue is a variable whose value cannot change. The
- easiest way to differentiate between the two is to remember that rvalues
- go to the right of the assignment operator and lvalues go to the left. Why
- is this important?
-
- Arrays are usually rvalues because of the way C generates its intermediate
- code. C treats an array as a label (like the target of a goto is a label).
- As an address of a location, an array is a constant value much like the
- number 3 is a constant.
-
- Confusing lvalues and rvalues with array names is a common source of
- errors for the beginning C programmer. Always remember that array names
- cannot be assigned to, incremented, or decremented, except when they are
- declared as one of the received arguments of a function, as follows:
-
- char *Amount;───────────────────────────────────Global pointer or lvalue
- int Bills[4] = {20,10,5,1};────────────────────────────Array or rvalue
-
- some_function(char amts[];)──────────────────────Equivalent to a pointer
- {
- char *address_var,───────────────────────────Local pointer or lvalue
- old_coins[];──────────────────────────────────────Syntax error
-
- ++Amounts;─────────────────────────────────────────────────────Legal
- ++Bills;─────────────────────────────────Illegal operation on rvalue
- ++amts;────────────────────────────────────────────────────────Legal
- ++address_var;─────────────────────────────────────────────────Legal
- ++old_coins;───────────────────────────────────────────────────Legal
- }
-
- In this sample program, *Amount and *address_var are pointers, values that
- can be incremented. Although amts[] is declared as an array, the
- interchangeability we discussed earlier permits us to increment it as
- though it were a pointer. On the other hand, because Bills is not a
- function's argument (it is a global array), it is an rvalue that cannot be
- incremented. Finally, old_coins[] generates a syntax error because only
- arrays in function argument declarations can be used without specifying
- the size of their leftmost dimension.
-
-
- Type Casting Pointers and Addresses
-
- Occasionally you will need to perform an arithmetic operation on a pointer
- other than addition, subtraction, or comparison. Fortunately, C is very
- flexible, and it permits you to perform those other operations on pointers
- by using type casts (also called "casts"). In Chapter 3 you used a type
- cast to convert one type to another: You can also use that technique with
- pointers. For example, suppose you need to divide a pointer's value by 2.
- You could use the method:
-
- unsigned long temp;
- int *point = some_address;
-
- temp = (unsigned long)point;
- temp /= 2;
- point = (int *)temp;
-
- First, we assign the address some_address to the pointer int *point. Next,
- we type cast the value in point (the address of some_address) to force a
- change to unsigned long, and then we store the resulting value in temp.
- Because it is legal to divide an unsigned long, we divide temp by 2. Then
- we cast that result, still an unsigned long, to the type int * (meaning
- pointer to an int). Finally, we place the correctly typed new value in
- point.
-
- The PEEK.C program (Listing 8-5) illustrates this use of type casting.
- PEEK.C asks the user for a number, then treats that number as an address
- and shows you the value stored at that address.
-
- ──────────────────────────────────────────────────────────────────────────
- /* peek.c -- demonstrates how to cast an int to a */
- /* pointer */
-
- main()
- {
- char *mem_ptr;
- unsigned int address;
-
- while (1)
- {
- printf("Examine what memory location?\n");
- printf("Enter location in decimal: ");
- if (scanf("%u", &address) != 1)
- break;
-
- mem_ptr = (char *)address; /* cast */
-
- printf("The value in %u is 0x%02X\n",
- address, (unsigned char)*mem_ptr);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-5. The PEEK.C program.
-
-
- far Pointers
-
- So far, we've assumed that all pointers occupy two bytes of memory. Two
- bytes can represent only addresses in the range 0 through 65535, however──
- not nearly enough to reference every location in the latest PCs.
- Fortunately, QuickC provides a 4-byte pointer, called a far pointer, that
- can address more than four billion bytes of memory.
-
- In particular, far pointers are useful for directly accessing the text
- screen's memory and for producing sophisticated graphics programs. In this
- section we'll show you how to manipulate the text screen of a graphics
- adapter.
-
- To declare a far pointer, merely add the keyword far to the pointer
- declaration, as follows:
-
- int far *screenp;
-
- We must use a far pointer to access screen memory because that memory is
- located at 0xB000000 (for machines with CGA) or 0xB800000 (for machines
- with EGA or VGA), locations that clearly will not fit into a 2-byte
- pointer (two bytes can hold only four hex digits, not eight). To place
- this hexadecimal constant into a far pointer, use the following type cast:
-
- screenp = (int far *)0xB000000;──────────────────────────────────────CGA
- screenp = (int far *)0xB800000;───────────────────────────────EGA or VGA
-
- This tells the compiler to handle the constant 0xB000000 (or 0xB800000) as
- a far address and to assign that address to the far pointer variable
- screenp.
-
- The SCRINV.C program (Listing 8-6) demonstrates a simple technique for
- manipulating text screen memory. Every time you press a key, the screen
- flips over. (Type Q to quit.) In the listing, adjust the constant assigned
- to screenp to suit your hardware: For EGA or VGA, replace 0xB000000 with
- 0xB800000.
-
- This program uses a pointer as if it were an array. Although we declare
- screenp as a far pointer:
-
- int far *screenp;
-
- we reference its elements using an offset in square brackets, as follows:
-
- temp = screenp[i];
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- Be careful when casting pointers to integers. You should always type cast
- to an unsigned long because that type will be large enough to hold all
- addresses. Specifying unsigned will prevent addresses from being (wrongly)
- considered negative, which could lead to incorrect results.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrinv.c -- using a far pointer to access text */
- /* screen memory */
-
- #define ROWS 25
- #define COLS 80
-
- main()
- {
- int far *screenp;
- int temp, i;
-
- do
- {
- /* use 0xB800000 for EGA or VGA */
- screenp = (int far *)0xB000000;
-
- for (i = 0; i < ((ROWS*COLS)/2); ++i)
- {
- temp = screenp[i];
- screenp[i] = screenp[(ROWS*COLS)-i-1];
- screenp[(ROWS*COLS)-i-1] = temp;
- }
- } while (getch() != 'Q');
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-6. The SCRINV.C program.
-
-
- Functions That Return Addresses
-
- In Chapter 6, we demonstrated that functions can return values and that
- those values are of type int unless you declare otherwise. You can also
- declare functions that return addresses. The C library contains many
- functions of this type, and your functions can also take advantage of the
- speed and compactness this procedure offers.
-
- You declare a function that returns an address the way you declare a
- pointer variable──with a type, a *, and a name. For example, the following
- function returns the address of a char type:
-
- char *function(int arg)
- {
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- We declared screenp as a pointer to an int because each character on your
- PC text screen is represented by two bytes of information──one byte is the
- character and the other is that character's attribute (normal, blinking,
- inverse, etc.). (We will discuss this organization and the various
- attributes in Chapter 14.)
- ──────────────────────────────────────────────────────────────────────────
-
- This is like using a function as a pointer. Let's try another example.
- Examine the function Range() in the following fragment from an upcoming
- program.
-
- char *Range(int key)
- {
- static char k2[] = {'a', 'b', 'c'},
- k3[] = {'d', 'e', 'f'},
- k4[] = {'g', 'h', 'i'};
- char *kp;
-
- if (key == 2)
- {
- return (k2);───────────────────────────────────────Address of k2
- }
- else if (key == 3)
- {
- return (&k3[0]);───────────────────────────────────Address of k3
- }
- kp = k4;
- return (kp);───────────────────Return value of a pointer, an address
- }
-
- This example demonstrates that you can return an address in three ways: as
- an array name (k2), as the address of the first element in an array
- (&k3[0]), or as a pointer (kp).
-
- Now let's call the above function from another named main():
-
- main()
- {
- char *keys;
- extern char *Range();────────Range will return the address of a char
-
- Notice that you can only use the return value of Range() after you
- correctly declare it, both in its own declaration and in (or before) any
- functions that call it.
-
- You can use a pointer value returned by a function the same way you use a
- pointer──with one exception. The address returned by a function is an
- rvalue: Thus, you can neither place it to the left of the assignment
- operator nor change it by computation. The following examples illustrate
- three correct ways to use the value returned by Range():
-
- keys = Range(2);────────────────────Address assigned to keys (a pointer)
-
- printf("%cn", Range(2)[1]);─────────────────────Address used as an array
-
- printf("%cn", *(Range(2)+1));──────────────────Address used as a pointer
-
- The first example assigns the address value returned by Range() to a
- pointer variable (keys). The second example uses the address returned by
- Range() as if it were an array, printing the second element. The third
- example uses the address returned by Range() as if it were a pointer,
- printing the value stored in that address plus one.
-
- The PHWORD.C program (Listing 8-7) asks the user for a telephone number
- and then, using the letters of the telephone dial, prints out all the
- possible words that can be made from that number.
-
- ──────────────────────────────────────────────────────────────────────────
- /* phword.c -- generates all the possible words */
- /* in a phone number; demonstrates */
- /* functions that return addresses */
-
- #define MAXD (7) /* 7 digits max */
-
- main()
- {
- int digits[MAXD], ndigits = 0, line = 0;
- char *letters;
- signed char digit;
- int a, b, c, d, e, f, g;
- extern char *Range();
-
- printf("Enter Phone Number (7 digits): ");
- do
- {
- digit = getch() - '0';
- if (digit == ('-' - '0'))
- continue;
- if (digit < 0 || digit > 9)
- {
- printf("\nAborted: Nondigit\n");
- return(1);
- }
- digits[ndigits++] = digit;
- printf("%d", digit);
- } while (ndigits < 7);
- printf("\n");
-
- for( a = 0; a < 3; ++a)
- for( b = 0; b < 3; ++b)
- for( c = 0; c < 3; ++c)
- for( d = 0; d < 3; ++d)
- for( e = 0; e < 3; ++e)
- for( f = 0; f < 3; ++f)
- for( g = 0; g < 3; ++g)
- {
- printf("%c", Range(digits[0])[a]);
- printf("%c", Range(digits[1])[b]);
- printf("%c", Range(digits[2])[c]);
- printf("%c", Range(digits[3])[d]);
- printf("%c", Range(digits[4])[e]);
- printf("%c", Range(digits[5])[f]);
- printf("%c", Range(digits[6])[g]);
- printf("\n");
- if (++line == 20)
- {
- printf("Press any key for more");
- printf(" (or q to quit): ");
- if (getch() == 'q')
- return (0);
- printf("\n");
- line = 0;
- }
- }
- }
-
- char *Range(int key)
- {
- static char keys[10][3] = {
- {'0', '0', '0'},
- {'1', '1', '1'},
- {'a', 'b', 'c'},
- {'d', 'e', 'f'},
- {'g', 'h', 'i'},
- {'j', 'k', 'l'},
- {'m', 'n', 'o'},
- {'p', 'r', 's'},
- {'t', 'u', 'v'},
- {'w', 'x', 'y'}
- };
-
- return (keys[key]);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-7. The PHWORD.C program.
-
- The PHWORD.C program also illustrates another point about arrays. When you
- reference a multidimensional array with only a partial list of offsets,
- the value generated is the address of the portion you referenced. Thus,
- although keys in Range() is a two-dimensional array, referencing with only
- a single dimension, as follows:
-
- return (keys[key]);
-
- yields the address of only the row specified. In other words, it yields
- the address of a one-dimensional array that is a subset of the
- two-dimensional array.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- The extern keyword tells QuickC that the variable or function named will
- be found elsewhere, either later in the current file or in another file
- that you plan to compile separately. It can also be used to tell QuickC
- that you plan to use a variable found in a library routine.
- ──────────────────────────────────────────────────────────────────────────
-
- Notice in PHWORD.C that main() screens your telephone number for illegal
- characters. The function Range() would be more portable if we checked for
- illegal values inside of it and returned an error code. The trick is to
- return an error address that is always illegal. Defined in the standard
- header file stdio.h is the perfect value to convey address errors──NULL.
- This special zero address value is guaranteed to be illegal. By using NULL
- rather than 0, you ensure the portability of your programs.
-
- The following is a rewrite of Range() that uses NULL:
-
- #include <stdio.h> /* for NULL */
-
- char *Range(int key)
- {
- static char keys[10][3] = ...
-
- if (key < 0 || key > 9)
- {
- return (NULL);
- }
-
- Now Range() does its own error checking. It can return NULL, even though
- it is declared char *, because NULL is a special address value that is
- illegal regardless of the expected return type.
-
-
- Dynamic Arrays
-
- In the previous chapter, we explained that arrays in C must be
- dimensioned with integer constant expressions, and therefore you cannot
- change the size of a declared array. But now that you have pointers at
- your disposal, the situation is somewhat different. By using standard C
- Library routines, you can allocate memory while the program is running
- (that is, "dynamically") and thus create arrays "on the fly." You can also
- use other C library routines to change the size of dynamically allocated
- arrays, again while the program is running.
-
- The ability to create, change the size of, and discard arrays from within
- your running program opens a host of new programming possibilities. It
- frees your program from having to know ahead of time how many lines of
- text a user will type, for example, or how many characters it will receive
- via a modem. When you design a database, it is clearly better to allow
- users to add fields at will, rather than restricting them to a
- predetermined record structure. Games are generally more interesting when
- players can add characters at any time. Text editors are more powerful
- when the user can interactively define keyboard macros.
-
- The standard library routines for the dynamic allocation and reallocation
- of memory are listed in Table 8-1 on the following page. The return types
- for these functions are declared in the header file <malloc.h>. If you
- look at those declarations (using the Include option on the View menu),
- you will see that they are all declared as pointer type void *. This new
- type, when applied to a function's return value, permits the returned
- address to be legally assigned to any type of pointer. This makes it very
- easy for us to create dynamic arrays of any type.
-
- Table 8-1 Memory Allocation Library Routines
- ──────────────────────────────────────────────────────────────────────────
- malloc() Memory allocate
- calloc() Computed memory allocate
- realloc() Reallocate memory
- free() Free allocated memory
- sbrk() Request memory from system
- ──────────────────────────────────────────────────────────────────────────
-
- The malloc() Memory Allocation Function
-
- The malloc() function is the most frequently used library allocation
- function. It takes a single argument, the number of bytes of memory you
- wish to allocate (reserve), and returns the address of that memory. If
- malloc() cannot find as much free memory as you specify, it returns a NULL
- value. The correct form for using malloc() (including a check for failure)
- follows:
-
- #include <stdio.h>──────────────────────────────────────────────For NULL
- #include <malloc.h>─────────────────────────────For malloc() declaration
- ...
- int *iptr;────────────────────────────────────────────To receive address
- size_t bytes = 100;──────────────────────────────────────Number of bytes
-
- if ((iptr = malloc(bytes)) == NULL)
- {
- /* handle error here */
- }
- printf("Now let's fill the array iptr[]\n");
-
- The parentheses in the malloc() expression force the result of the
- assignment──the value of iptr──to be compared to NULL. If malloc succeeds
- in allocating memory, iptr contains the address of that dynamically
- allocated memory.
-
- Because the value of iptr evaluates as an address, you can use iptr as if
- it were an array. For example, the following expression is perfectly
- legal:
-
- iptr[5]
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- Note in the above example that we declare bytes as type size_t. This type
- is defined in <malloc.h> as an unsigned int for QuickC. Because the type
- size_t is a part of the ANSI standard, you should use it rather than
- unsigned int to ensure the portability of your programs.
-
- However, to transport programs written with size_t to different machines,
- you might need to use #define to make size_t an unsigned int.
- ──────────────────────────────────────────────────────────────────────────
-
- The TOTAL.C program (Listing 8-8) asks you to type numbers, one per line,
- and dynamically builds an array of those numbers. When you enter a
- non-numeric character, the program displays your list of numbers from the
- array and totals them.
-
- This program introduces two new elements to our memory allocation
- routines: free() and realloc(). The free() function releases memory that
- you reserve with malloc() or realloc(). The realloc() function copies
- memory into a larger or smaller block of memory.
-
- ──────────────────────────────────────────────────────────────────────────
- /* total.c -- how to build an array on the fly */
-
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* for size_t */
-
- main()
- {
- int *iptr, count = 0, i, total;
- size_t bytes = sizeof(int);
-
- /* Start the array with room for one value. */
- if ((iptr = malloc(bytes)) == NULL)
- {
- printf("Oops, malloc failed\n");
- exit(1);
- }
-
- printf("Enter as many integer values as you want.\n");
- printf("I will build an array on the fly with them.\n");
- printf("(Any non-number means you are done.)\n");
-
- while (scanf("%d", &iptr[count]) == 1)
- {
- ++count;
- /* Enlarge the array. */
- if ((iptr = realloc(iptr,bytes*(count+1))) == NULL)
- {
- printf("Oops, realloc failed\n");
- exit(1);
- }
- }
- total = 0;
- printf("You entered:\n");
- for (i = 0; i < count; i++)
- {
- printf("iptr[%d] = %d\n", i, iptr[i]);
- total += iptr[i];
- }
- printf("\nTotal: %d\n", total);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-8. The TOTAL.C program.
-
- The free() function takes a single argument, the address returned by
- malloc() or realloc(), and uses it to release that memory. Note that if
- you pass free() an address other than one returned by one of these
- functions, your program may crash.
-
- The realloc() function takes two arguments: first, the address returned by
- malloc() or one returned from a previous call to realloc(); and second, a
- new size in bytes. The function copies the contents of the old memory to
- the new memory (truncating if the new size is smaller) and returns the
- address of the new memory. Like malloc(), realloc() returns a NULL address
- if it fails.
-
- The calloc() Memory Allocation Routine
-
- QuickC supplies a companion routine to malloc() called calloc() (for
- "calculated allocate"). The calloc() function also allocates memory, but
- with a twist that makes it ideal for arrays. Instead of merely allocating
- a number of bytes, it takes a pair of arguments: the number of items and
- the number of bytes (sizeof) of each item. The form for using calloc() is
- as follows:
-
- address = calloc(items, sizeof(item));
-
- Like malloc(), calloc() returns the address of successfully allocated
- memory or NULL if not enough memory is available.
-
- The advantage offered by calloc() is that it initializes allocated memory
- to zero values, whereas malloc() can leave memory that is filled with
- garbage. The free() function also releases memory reserved by calloc().
-
- The sbrk() Memory Extension Function
-
- The malloc() family of routines keeps track of all the memory you
- allocate. These routines use extra bytes to keep a list of allocated
- memory and still more bytes to ensure that all addresses are even. If your
- program is short on space, those pieces of memory might be too valuable to
- waste on mere housekeeping. These functions also need to search through
- lists of available memory to find blocks of the requested size. If you
- have allocated many chunks of memory, that search slows the execution of
- your program.
-
- QuickC's sbrk() function offers a quick and efficient way to allocate
- memory when you don't need to keep track of how much has been allocated.
- When you load a program into memory, it is arranged as shown in Figure
- 8-4. The address of the end of the data segment serves to record the
- highest memory location you can legally access. The sbrk() function
- requests that the highest location be extended by a specified number of
- bytes, as follows:
-
- address = sbrk(bytes);
-
- The value returned by sbrk() is the address of the old limit before it was
- extended (in other words, the address of the first byte of the newly
- acquired memory). The address of the first byte beyond the new memory
- allocation then replaces the previously stored value.
-
- ┌- - - - - - - - - - -─┐───New highest after sbrk()
- | |
- | |
- ├──────────────────────┤───Offset from DS
- ├─────────────────────┤
- │ └─────────┼────Highest memory location
- │ │ your program can use
- │ │
- │ DATA │────Uninitialized data
- │ │
- │ │
- ├──────────────────────┤
- │ │
- │ DATA │────Initialized data
- │ │
- ├──────────────────────┤
- │ │
- │ │ │
- │ │ CODE │────Your program's code
- │ │ │
- │ │ │
- Increasing │ │
- memory └──────────────────────┘───DS
-
- Figure 8-4. The sbrk() function lets you extend the limit of accessible
- memory.
-
- Because sbrk() returns an address of -1 on failure, the full call to
- sbrk(), including an error check, is as follows:
-
- #include <malloc.h> /* for size_t */
- ...
- int *iptr;
- size_t bytes = 100;
- ...
- if ((iptr = sbrk(bytes)) == (int *)-1)
- {
- /*handle error here */
- }
- /* you have 100 bytes at the address in iptr */
-
- Note that we must typecast the -1 to (int *) so that the comparison will
- be to the same type as iptr.
-
- The TOTAL2.C program (Listing 8-9 on the following page) uses sbrk() to
- transform the earlier TOTAL.C program into an adding machine of unlimited
- capacity. We can use sbrk() in TOTAL2.C because we only take memory and
- never release or exchange any. Because sbrk() extends memory continuously,
- our array always remains intact. With malloc(), on the other hand, memory
- may not be allocated continuously, so you must call realloc() to enlarge
- and possibly move your array.
-
- Unfortunately, giving back pieces of memory that you acquired with sbrk()
- requires advanced programming expertise. If you need to juggle memory
- (taking, then giving back part, and so on), malloc() and realloc() are
- much easier to use. Do not, however, mix sbrk() and the malloc() routines
- in the same program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* total2.c -- how to build an array on the fly */
- /* using sbrk() */
-
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* for size_t */
-
- main()
- {
- int *iptr, count = 0, i, total;
- size_t bytes = sizeof(int);
-
- /* Start the array with room for one value. */
- iptr = sbrk(0);
- if (sbrk(bytes) == (int *)-1)
- {
- printf("Oops, sbrk failed\n");
- exit(1);
- }
-
- printf("Enter as many integer values as you want.\n");
- printf("I will build an array on the fly with them.\n");
- printf("(Any non-number means you are done.)\n");
-
- while (scanf("%d", &iptr[count]) == 1)
- {
- ++count;
- /* Enlarge the array. */
- if (sbrk(bytes) == (int *)-1)
- {
- printf("Oops, sbrk failed\n");
- exit(1);
- }
- }
- total = 0;
- for (i = 0; i < count; i++)
- total += iptr[i];
- /* just print the total this time */
- printf("%d\n", total);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-9. The TOTAL2.C program.
-
-
- Advanced Pointer Techniques
-
- Perhaps you've heard horror stories about C pointers and incomprehensible
- code. Well, some of those stories are true. Reading and understanding
- poorly written code is like trying to untangle a plate of spaghetti. C
- gives you the freedom to design many types of strange but useful
- constructs. But C also gives you the freedom to design the
- incomprehensible. This section discusses some of C's magnificent but
- potentially arcane constructs──those dealing with the more elaborate and
- sophisticated uses of pointers.
-
- Arrays of Pointers
-
- C lets you create arrays of any type of elements. Thus, you can even
- create an array whose elements are pointers. For example, to create an
- array of 10 pointers, in which each item is a pointer to a float, simply
- declare the following:
-
- float *array_name[10];
-
- The * preceding the array name in this declaration tells the compiler that
- the array is an array of pointers; therefore, each element holds an
- address. The float signifies that all pointers will point to float
- variables.
-
- You can use this technique for speeding up sorting routines, for example.
- Because an address on a PC occupies only two bytes (except for far
- pointers), while the data it points to occupies four bytes (for a float),
- it's faster to exchange two 2-byte addresses than to exchange the data.
- The advantage offered by arrays of pointers becomes even more evident when
- we use them with strings in the next chapter.
-
- The REVERSE.C program (Listing 8-10 on the following page) reads in lines
- of characters. The addresses of those lines are stored in an array of
- pointers to char. (See Figure 8-5.) An empty input line causes the lines
- of text pointed to by the array of pointers to be printed in reverse
- order.
-
- cptrs[]
- ┌─────┐
- Pointer to first line────│ │─────────This is the text
- ├─────┤
- Pointer to second line────│ │─────┐ that we typed in.
- ├─────┤ │
- etc. │ │───┐ └─────It will be reve
- ├─────┤ │
- │ │──┐└─────rsed. Line 3 is
- ├─────┤ └─────────────┘
- │ │─────┐ here. Line 4 is
- ├─────┤ └──────────┘
- │ │─────┐ here. Line 5 is
- ├─────┤ └──────────┘
- │ │─────┐ Here. And so on...
- ├─────┤ └──────────┘
- │ │
- ├─────┤ Continuous memory
- │ │ allocated with srbk()
- └─────┘
- Array of
- pointers
-
- Figure 8-5. An array of pointers, each element of which contains a line of
- text in allocated memory.
-
- ──────────────────────────────────────────────────────────────────────────
- /* reverse.c -- demonstrates an array of pointers */
- /* by reversing lines of text */
-
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* for size_t */
-
- #define MAXL 20
-
- main()
- {
- char *cptrs[MAXL]; /* array of pointers */
- char *cp;
- int count, i, j, ch;
- extern char *Getbyte();
-
- printf("Type in several lines of text, and I will\n");
- printf("print them back out in reverse order.\n");
- printf("(Any blank line ends input):\n");
-
- for (i = 0; i < MAXL; ++i)
- {
- cp = Getbyte();
- cptrs[i] = cp; /* assign address to pointer */
- count = 0;
- while ((ch = getchar()) != '\n') /* gather line */
- {
- *cp = ch;
- cp = Getbyte();
- ++count;
- }
- *cp = '\0';
- if (count == 0) /* all done if blank line */
- break;
- }
- printf("---------<reversed>---------\n");
- for (j = i-1; j >= 0; --j)
- {
- printf("%s\n", cptrs[j]);
- }
- }
-
- char *Getbyte(void)
- {
- char *cp;
-
- if ((cp = sbrk(1)) == (char *)-1)
- {
- printf("Panic: sbrk failed\n");
- exit(1);
- }
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-10. The REVERSE.C program.
-
- The fact that we can print an array of characters with printf()
- illustrates the correspondence between arrays of char and strings. We will
- discuss that relationship in detail in the next chapter.
-
- Pointers to Pointers
-
- As you have seen, a pointer is a variable whose value is an address, and
- that address is usually the location in memory of another variable.
- However, in C, that other variable can also be a pointer. There is no
- limit to how far you can extend this "pointer-to-a-pointer" relationship──
- you can have pointers to pointers to pointers and so on, ad infinitum.
- Here, however, we'll minimize the danger of creating "spaghetti code" by
- restricting ourselves to pointers to pointers, sometimes referred to as
- "handles."
-
- Figure 8-6 illustrates the relationship of a pointer to a pointer. The
- variable pp contains as its value the address of p. The variable p in turn
- contains as its value the address of num, an ordinary integer. Because p
- points to an int, pp is a pointer to a pointer to an int.
-
- The following example shows how to declare a pointer to a pointer:
-
- int **pp, *p, num;
- │ └─────────────────────────────────────────── Pointer to an int
- └──────────────────────────────────── Pointer to a pointer to an int
-
- The two * characters tell the compiler that pp is a pointer to a pointer
- and holds as its value the address of another pointer.
-
- When accessing the values pointed to by pp, the number of *s determines
- which value is obtained. Consider the following initialization:
-
- p = #─────────────────────────────────────────────────Address of num
- pp = &p;────────────────────────────────────────────────────Address of p
-
- Points to a variable
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- ▒ ▒
- ┌───────────┐ ▒
- │ │
- Memory── 621 622 623 624 625 626 627 628 629 630
- locations ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- in bytes │ 629 │ │ 621 │ │ 3 │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ │
- │ │ │
- p pp num
- ▒
- ▒ ▒
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- Points to a pointer
-
- Figure 8-6. Pointer to a pointer: a variable whose value is the address of
- another pointer.
-
- The following statement yields the value stored in the address that pp
- points to:
-
- *pp
-
- Because pp points to p, *pp yields the address stored in p, that of num.
- Placing another * in front of pp:
-
- **pp
-
- tells the compiler to fetch the value stored in the pointer pointed to by
- pp. Because pp points to p, and p in turn points to num, **pp fetches the
- value of num. Thus, all three of the following yield the value stored in
- the variable num:
-
- **pp
- *p
- num
-
- One useful application for a pointer to a pointer is in traversing arrays
- of pointers. The REVERSE2.C program (Listing 8-11) is a rewrite of the
- previous REVERSE.C. In this version, we replace the final for loop with a
- while loop that decrements pp, a pointer to a pointer.
-
- ──────────────────────────────────────────────────────────────────────────
- /* reverse2.c -- demonstrates a pointer to a pointer */
-
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* for size_t */
-
- #define MAXL 20
-
- main()
- {
- char *cptrs[MAXL];
- char **pp; /* pointer to pointer */
- char *cp;
- int count, i, ch;
- extern char *Getbyte();
-
- printf("Type in several lines of text, and I will\n");
- printf("print them back out in reverse order.\n");
- printf("(Any blank line ends input):\n");
-
- for (i = 0; i < MAXL; ++i)
- {
- cp = Getbyte();
- cptrs[i] = cp; /* assign address to pointer */
- count = 0;
- while ((ch = getchar()) != '\n') /* gather line */
- {
- *cp = ch;
- cp = Getbyte();
- ++count;
- }
- *cp = '\0';
- if (count == 0) /* all done if blank line */
- break;
- }
- printf("---------<reversed>---------\n");
- pp = &cptrs[i];
- while (pp >= cptrs)
- {
- printf("%s\n", *(pp--));
- }
- }
-
- char *Getbyte(void)
- {
- char *cp;
-
- if ((cp = sbrk(1)) == (char *)-1)
- {
- printf("Panic: sbrk failed\n");
- exit(1);
- }
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-11. The REVERSE2.C program.
-
- This program shows that a pointer to a pointer is decremented (or
- incremented) by the number of bytes in a pointer:
-
- printf("%s\n", *(pp--))
- └──┬──┘
- └──┬───────────────────────────────────────── The same
- ┌─────┴──┐
- printf("%s\n", cptrs[i--])
-
- Recall that the address in a pointer changes by a number of bytes that
- corresponds to the type to which it points. A char pointer changes by one
- byte, while a float pointer changes by four bytes. A pointer to a pointer
- changes by the number of bytes in an address because it points to a
- pointer, and thus to an address. Because cptrs[] is an array of pointers,
- and pp points to one of those addresses, decrementing pp causes it to
- point to the immediately preceding element in that array. Figure 8-7 on
- the following page illustrates this process.
-
- ──────────────────────────────────────────────────────────────────────────
- Pointer Pointer
- Pointers are so versatile that they can contain the address of almost
- anything. However, you cannot use pointers to obtain the address of the
- following C elements: constants (such as 5); variables declared with the
- keyword register; labels (the targets of goto); and keywords (such as if,
- while, and so on).
- ──────────────────────────────────────────────────────────────────────────
-
- cptrs[]
- ┌─────┐
- ┌───end────│ │─────────This is the text
- │ ├─────┤
- │ --pp────│ │─────┐ that we typed in.
- │ ├─────┤ │
- pp │ --pp────│ │───┐ └─────It will be reve
- ┌───────┐ │ ├─────┤ │
- │ │ ──┤ --pp────│ │──┐└─────rsed. Line 3 is
- └───────┘ │ ├─────┤ └─────────────┘
- Points to │ --pp────│ │─────┐ here. Line 4 is
- pointer │ ├─────┤ └──────────┘
- │ --pp────│ │─────┐ here. Line 5 is
- │ ├─────┤ └──────────┘
- └─start────│ │─────┐ Here. And so on...
- ├─────┤ └──────────┘
- │ │
- ├─────┤ Continuous memory
- │ │ allocated with srbk()
- └─────┘
- Array of
- pointers
-
- Figure 8-7. Decrementing a pointer to a pointer moves it down through an
- array of pointers.
-
- Pointers to Functions
-
- It is often useful to know the address of a function. You declare a
- pointer to a function as follows:
-
- int (*pointer_name)();
-
- This declares the variable pointer_name to be a pointer *pointer_name. The
- trailing parentheses tell the compiler that the pointer *pointer_name
- contains the address of a function. The int specifies that the function
- pointed to returns an int.
-
- To obtain the address of a function, merely state its name. However, be
- sure you declare the function before you take its address:
-
- int (*funptr)();─────────────────────────────────A pointer to a function
- extern int Quit();───────────────────────────────────A function declared
-
- funptr = Quit;──────────────────────Address of Quit() assigned to funptr
-
- In this example, funptr contains the address of Quit(), and we can call
- Quit() through funptr, as follows:
-
- *funptr();
-
- The preceding * tells the compiler to use the value pointed to by funptr
- (the address of Quit()). The trailing parentheses tell the compiler to
- call the function whose address we just fetched.
-
- The CHOOSE.C program (Listing 8-12) goes one step further by creating an
- array of pointers to functions. First, the program asks you to choose a
- menu item. Then it translates your choice into an array offset and calls
- the function whose address is stored at that offset.
-
- ──────────────────────────────────────────────────────────────────────────
- /* choose.c -- an array of pointers to functions */
- /* used to create a menu */
-
- void Choice1(), Choice2(), Choice3();
-
- void (*Dochoice[3])() = {Choice1, Choice2, Choice3};
-
- main()
- {
- int ch;
-
- printf("Select 1, 2 or 3: ");
- ch = getch(); putch(ch);
- ch -= '1';
- if (ch < 0 || ch > 2)
- printf("\nNo such choice.\n");
- else
- Dochoice[ch]();
-
- }
-
- void Choice1(void)
- {
- printf("\nThis is choice 1\n");
- }
-
- void Choice2(void)
- {
- printf("\nThis is choice 2\n");
- }
-
- void Choice3(void)
- {
- printf("\nThis is choice 3\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 8-12. The CHOOSE.C program.
-
- Arrays of pointers to functions are best applied in interactive programs.
- Believe it or not, you'll find it easier to design word processors and
- complex games once you master this technique.
-
- The following example illustrates the advantage of using an array of
- pointers to functions instead of a simpler switch statement. Examine the
- following fragment from a hypothetical text processor:
-
- int (*commands[128])() = {
- ...
- Go_left, /* l */
- Mark_line, /* m */
- Next_search, /* n */
- ...
- };
-
- This array has 128 pointers to functions, each of which corresponds to a
- key on the keyboard. Pressing l causes Go_left() to be called, moving the
- cursor left. If the user wishes to change the meaning of the keys,
- swapping the functions of l and n, for example, you need only use the
- following:
-
- int (*temp)();───────────────────────────────────────────Scratch pointer
- int from, to;
-
- from = 'l';
- to = 'n';
- temp = commands[from];
- commands[from] = commands[to];
- commands[to] = temp;
-
- Here we first declare a scratch variable to be used in the swap. We
- declare it as a pointer to a function because we will be swapping pointers
- to functions. We then assign to temp the address stored in commands[from],
- where from is the offset that corresponds to the numeric value of the
- letter `n'. Because that array item is a pointer to the function
- Next_search(), we are saving the address of that function. We then copy
- the address in commands[to] into commands[from]. Finally, we assign the
- address saved in temp to commands[to]. The result of this exchange is that
- typing n now causes the Go_left() function to be called, and typing l
- causes the Next_search() function to be called, thereby reversing their
- roles.
-
- Contrast this flexible form of programming with an inflexible switch
- statement, such as the following:
-
- switch(key)
- {
- ...
- case 'l':
- Go_left();
- break;
-
- case 'm':
- Mark_line();
- break;
-
- case 'n':
- Next_search();
- break;
- ...
- }
-
- Clearly, a program that a user can customize is more difficult to write,
- yet a versatile program is always worth the extra effort.
-
- Unscrambling the Spaghetti
-
- In the previous sections of this chapter you've seen some complicated
- declarations. You will see more of them in the chapters to follow, so it
- behooves us to establish some rules that will help us understand complex
- declarations.
-
- Remember: Always start reading at the inside of a declaration with the
- name (identifier); then work your way outward. For example, to unscramble
- the following declaration:
-
- int (*name)();
-
- follow the definition from the inside out: name is a pointer to a function
- of type int. Thus, this declaration is a pointer to a function that
- returns an int.
-
- Let's try this same technique on a different declaration:
-
- float (*name)[3];
-
- In this example, name is a pointer to an array of three float variables.
- Thus, it is a pointer to an array of three floats. Contrast that
- declaration with the following:
-
- float *name[3];
-
- Here name is an array of three pointers to float variables. This example
- is an array of three pointers to floats. The difference lies in the
- parentheses. Be sure to obey the order of precedence for operators.
-
- As an example of using parentheses, try to decipher the following
- declaration from CHOOSE.C:
-
- int (*funs[4])();
-
- Here the * operator has a higher precedence than the [] operators, so *
- binds to funs first. Therefore, funs is a pointer, and four such pointers
- exist in an array; these pointers point to functions that return the type
- int. Thus, the declaration is an array of four pointers to functions that
- return int.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 9 Strings
-
- A "string" is a sequence of ASCII characters──this sentence, for example,
- is a string. Strings give your programs life by enabling them to
- communicate with the user. Nearly all programs──from our simple printf()
- statements to the sophisticated dialogues of complex interactive
- programs──use strings of one type or another.
-
- Unlike BASIC and Pascal, the C language has no built-in string-type
- variable. Instead, C uses the convention that a string is an array of type
- char whose final, or terminating, value is the special character '\0'──a
- one-byte zero value. Figure 9-1 on the following page illustrates such an
- array.
-
- We refer to this arrangement as a convention because nothing in C prevents
- you from handling strings in another manner. For example, you might store
- strings as arrays of short variables, using one byte to hold the character
- and the other to hold the character's attributes (more on this in Chapter
- 13). Or you might store strings as a value length followed by length
- number of characters. However, because you will most often handle strings
- in the conventional way, we will emphasize that method in our discussion
- of strings.
-
- Address of bytes── 9876 9877 9878 9879 9880 9881 9882 9883
- in memory ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- │ 'H' │ 'e' │ 'l' │ 'l' │ 'o' │'\n' │'\O' │ │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │
- └──────────────┬──────────────┘ │
- │ │
- ASCII characters Terminating zero
-
- Figure 9-1. In C, a string is an array of type char terminated by a zero
- value.
-
-
- Declaring and Initializing Strings
-
- A string is merely an array of type char, and you initialize it the same
- way you would any other array. The following example fills the char array
- named phrase with ASCII character constants that spell "Hello" followed by
- the newline character:
-
- char phrase[] = {'H', 'e', 'l', 'l', 'o', '\n', '\0'};
-
- We made this array a conventional C string by adding a terminating zero
- value (the character constant '\0'). As with all arrays, string arrays can
- be initialized only if you use the keyword static or declare them
- globally──outside of all functions.
-
- The HELLO.C program (Listing 9-1) illustrates the proper way to
- initialize string arrays. It also demonstrates the printf() format command
- %s, which tells printf() to print the next argument as a string.
-
- Because zero-terminated char arrays so commonly represent strings in C,
- the language provides a built-in shorthand. When C finds text enclosed in
- full quotation marks (called string constants), it immediately stores that
- text as an array of type char and adds the terminating '\0'. This
- characteristic of C provides you an alternate way to initialize arrays.
-
- ──────────────────────────────────────────────────────────────────────────
- /* hello.c -- legal ways to initialize strings as */
- /* arrays of char values */
-
- char Gphrase[] = {
- 'H','e','l','l','o','\n','\0' }; /* global initialization */
-
- main()
- {
- static char gphrase[] = {
- 'h','e','l','l','o','\n','\0' }; /* local initialization */
-
- printf("Global: %s", Gphrase);
- printf("Local: %s", gphrase);
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-1. The HELLO.C program.
-
- For example, you can create the same arrays as those declared in HELLO.C
- by substituting the following lines of code:
-
- char Gphrase[] = "Hello\n";────────────────────────Global initialization
-
- static char lphrase[] = "Hello\n";──────────────────Local initialization
-
- As an aid in declaring long string constants, the compiler combines
- adjacent quoted strings into a single string constant. This feature lets
- you easily initialize long strings, as in the following example:
-
- static char long_phrase[] = "This is one long "
- "sentence that the compiler "
- "combines into a single string.";
-
- C uses the rule that if nothing but white space (spaces, tabs, or
- newlines) separates two quoted strings, those strings are concatenated to
- form a single string. Thus, the above QuickC declaration is equivalent to
- the following:
-
- static char long_phrase[] =
- "This is one long sentence that the compiler combines into a single
- string.";
-
- Under pre-ANSI C, long string initializers can be emulated with the
- #define preprocessor directive. Recall that you can extend #define lines
- by ending each with a backslash and a newline character (that is, type \
- and press Enter). Because this #define technique is portable to all
- compilers, we will use it throughout the rest of the book:
-
- #define PHRASE \
- "This is one long sentence that the compiler \
- combines into a single string."
-
- static char long_phrase = PHRASE;
-
- ──────────────────────────────────────────────────────────────────────────
- A Constant Reminder
- When you declare string constants, remember that it is illegal for a
- newline character to appear anywhere between full quotation marks. The
- following example is illegal:
-
- static char long_phrase = "This is one
- long sentence that the...";
-
- and results in the following QuickC error message:
-
- error C2001:
- newline in constant
-
- If you want to insert a newline character into a string constant, use the
- escape sequence for a newline character (\n) instead:
-
- static char long_phrase[] = "This is one \n long sentence that the ..."
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The String Pool and String Addresses
-
- QuickC copies all of a program's quoted strings into a common area of
- memory called the string pool. They are copied there, one after the other,
- in the order that they occur in the program. (Figure 9-2 illustrates this
- process.)
-
- The STRPOOL.C program (Listing 9-2) dumps the contents of the string pool
- to your terminal screen. Note in STRPOOL.C that any char array that ends
- with a zero value, such as Cent_string, is placed into the string pool.
-
- We place nonprinting characters into quoted strings as we did with
- printf()──that is, a newline character, with \n; a carriage return, with
- \r; and a tab, with \t. Other special characters that you can place in
- string constants are the full quotation mark, with \"; the formfeed
- character, with \f; the backspace character, with \b; and the bell (beep)
- character, with \a.
-
- You can include any character from the PC's extended character set in a
- string constant by using a \x followed by a two-digit hexadecimal number.
- For example, \x9B is used to represent the ¢ character. (QuickC's General
- help screens include a handy table that lists these escape sequences.)
-
- Note also in the program that we assigned the address of a string to a
- pointer (cp = Start). Nowhere are pointers used more heavily than with
- strings.
-
- Your program String pool
- ┌────────────────────────────────┐ ┌─────┬─────┬─────┬─────┬─────┬─────
- │°char phrase [ ] = "Hello\n";───┼─────┼'H' │ 'e' │ 'l' │ 'l' │ 'o' │'\n'
- │°main ( ) °│ ├─────┼─────┼─────┼─────┼─────┼─────
- │°{ °│ ┌──┼'\O'─┼'T' │ 'y' │ 'p' │ 'e' │ ' '
- │° printf("Type in a line of");─┼──┘ ├─────┼─────┼─────┼─────┼─────┼─────
- │° printf(" text and I will");──┼──┐ │ 'i' │ 'n' │ ' ' │ 'a' │ ' ' │ 'l'
- │° . °│ │ ├─────┼─────┼─────┼─────┼─────┼─────
- │° . °│ │ │ 'i' │ 'n' │ 'e' │ ' ' │ 'o' │ 'f'
- │° . °│ │ ├─────┼─────┼─────┼─────┼─────┼─────
- │° °│ └──┼'\O'─┼' ' │ 't' │ 'e' │ 'x' │ 't'
- │° °│ ├─────┼─────┼─────┼─────┼─────┼─────
- └────────────────────────────────┘ │ ' ' │ 'a' │ 'n' │ 'd' │ ' ' │ 'I'
- ├─────┼─────┼─────┼─────┼─────┼─────
- │ ' ' │ 'w' │ 'i' │ 'l' │ 'l' │'\O'
- ├─────┼─────┼─────┼─────┼─────┼─────
- | | | | | |
- | | | | | |
-
- Figure 9-2. Quoted string constants are placed one after the other into
- the string pool.
-
- ──────────────────────────────────────────────────────────────────────────
- /* strpool.c -- dumps the string pool to show how */
- /* quoted strings are stored */
-
- #define PHRASE \
- "This is one long sentence that the compiler \
- combines into a single string."
-
- char Start[] = "start";
- char Long_phrase[] = PHRASE;
- char Short_phrase[] = "This is a short phrase";
- char Cent_string[] = "\x9B";
-
- main()
- {
- static char local_phrase[] = "This is local";
- char *cp;
-
- printf("Dump of the string pool:\n");
- printf("-----------------------\n");
-
- printf("\""); /* print leading quote */
-
- /*
- * Note that the address of a string can be
- * assigned to a pointer: cp = Start
- */
- for (cp = Start; *cp != '^'; ++cp)
- {
- if (*cp == '\0') /* print '\0' as a quote */
- printf("\"\n\"");
- else if (*cp == '\n' ) /* print '\n' as '\' 'n' */
- printf("\\n");
- else
- printf("%c", *cp);
- }
- printf("^"); /* marks end */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-2. The STRPOOL.C program.
-
-
- Pointers and Initialized Strings
-
- In the last chapter we assigned the address of an array to a pointer. We
- can also initialize a pointer to char with the address of a quoted string
- constant, as follows:
-
- char *str = "This is a phrase";
-
- This example initializes the char pointer *str to contain the address of
- the quoted string constant. Because the compiler places all string
- constants into the "string pool," the address in *str is that of the
- letter "T" (the first character of the char array) in the string pool.
-
- Recall that an array declaration creates an rvalue and a pointer
- declaration creates an lvalue. Consider the following declarations:
-
- char ary[] = "This is a phrase";
- char *str = "This is another";
-
- The ary[] declaration creates an rvalue (an address reference, such as a
- label) that cannot be changed with calculations.
-
- The *str declaration, on the other hand, creates an lvalue (a pointer
- variable whose value is an address), which can be changed with
- calculations. You can, for example, increment the pointer as follows:
-
- ++str;
-
- The distinction between lvalue and rvalue can be a confusing one for
- beginning C programmers. Remember that an array name (such as ary[]) is a
- fixed location and cannot be changed; a pointer (such as *str) is a
- variable and can be changed.
-
- The BIFFRED.C program (Listing 9-3) demonstrates that you can use
- pointers to manipulate strings in the string pool. Examine the program
- before you run it. Can you predict what it will do?
-
- ──────────────────────────────────────────────────────────────────────────
- /* biffred.c -- strings in the string pool can be */
- /* manipulated via pointers */
-
- char Start[] = "start";
-
- main()
- {
- char *cp;
- int pass;
-
- for (pass = 0; pass < 2; ++pass)
- {
- printf("My name is FRED\n");
-
- cp = Start;
-
- while (*cp != 'F')
- ++cp;
-
- *cp = 'B';
- *++cp = 'I';
- *++cp = 'F';
- *++cp = 'F';
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-3. The BIFFRED.C program.
-
-
- Formatting Strings with printf()
-
- So far, we've used printf() to print and format numbers (int with %d and
- float with %f, for example), individual characters with %c, and quoted
- strings with %s. The ability of printf() to print strings, however, goes
- far beyond the mere echoing of quoted string constants. In the following
- example:
-
- printf("%s\n", ary);
-
- the expression ary can be the address of any char type array that ends
- with the character constant value '\0'. It can be a quoted string constant
- such as
-
- printf("%s\n", "This is a phrase");
-
- or the address of a string from either a char array or a value in a
- pointer, as in the following examples:
-
- char *str, ary[] = "This is a phrase";
-
- str = ary;
-
- printf("%s\n", ary);─────────────────────────────────Address of an array
- printf("%s\n", str);──────────────────────────────────Value in a pointer
-
- Because all quoted strings are placed into the string pool and replaced
- with their starting address in that string pool, it follows that the
- format specification in the control string of this example:
-
- printf("%s\n", str);
- └──┬─┘
- └────────────────────────────────────────────── Control string
-
- can also be expressed as either an array address or the value in a
- pointer, as follows:
-
- char *str, ary[] = "This is a phrase.";
- char *cp, ctl[] = "%s\n";
-
- str = ary;
- cp = ctl;
-
- printf(ctl, ary);────────────────────────────────────Addresses of arrays
- printf(cp, str);──────────────────────────────────────────Pointer values
- printf(ctl, str);─────────────────────────────────────Mixture of the two
-
- The CONTROL.C program (Listing 9-4 on the following page) demonstrates
- this equivalence. This program asks you to type either an l or an r, and
- then it prints out a string with the corresponding left or right
- justification.
-
- CONTROL.C lets you see how the printf() format specifier %s is used to
- format strings. The various options you can use with %s are summarized in
- Table 9-1 on the following page. You can also combine them as in the
- following statement, which prints the first four letters of computer
- right-justified in a 25-character field.
-
- printf("%25.4s\n", "computer");
-
- ──────────────────────────────────────────────────────────────────────────
- /* control.c -- demonstrates string justification */
- /* using printf() */
-
- char Some_text[] = "Some Text";
- char Left_control[] = "<<%-15s>>";
- char Right_control[] = "<<%15s>>";
-
- main()
- {
- char ch;
-
- while (1)
- {
- printf("Select l)eft r)ight or q)uit: ");
- ch = getch();
- putch(ch);
-
- printf("\n\n");
- switch((int) ch)
- {
- case 'l':
- case 'L':
- printf(Left_control, Some_text);
- break;
- case 'r':
- case 'R':
- printf(Right_control, Some_text);
- break;
- case 'q':
- case 'Q':
- exit (0);
- default:
- printf("Huh?");
- break;
- }
- printf("\n\n");
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-4. The CONTROL.C program.
-
- Note: In these format specifiers, num must be a decimal integer. You can
- combine the last option, %.nums, with any of the others, producing, for
- example, %25.5s.
-
- Table 9-1 Variations of the printf() %s Specifier
- ──────────────────────────────────────────────────────────────────────────
- %s Prints the string exactly as it is
- %nums Prints the string right-justified in a field of width num
- %-nums Prints the string left-justified in a field of width num
- %.nums Prints num characters of string
- ──────────────────────────────────────────────────────────────────────────
-
-
- String Input and Output
-
- The standard C Library contains several functions specifically designed to
- facilitate input and output of strings. Here we discuss some that read
- from your keyboard or print to your screen. The next chapter ("Managing
- Files") deals with file-handling counterparts to these functions. In
- Chapter 13, we will present additional routines that directly access the
- keyboard and screen hardware.
-
- String Input with scanf()
-
- We've already used scanf() several times: Now let's discuss it in detail.
- The scanf() function uses the same % specifiers that printf() does, but it
- uses them to read values, not to print them. Unfortunately, scanf()
- handles strings a little differently than does printf(). Where printf()
- prints the entire string to a terminating '\0', scanf() reads only
- space-delimited words of text. That is, for each %s in its control string,
- scanf() reads all characters up to, but not including, space, tab, or
- newline. Therefore, scanf() is best used for reading words rather than
- lines of text.
-
- The scanf() routine, when used with %s to read words of text, takes the
- form
-
- scanf("%s", buf);
-
- where buf is the address of a char array (buffer) into which scanf()
- places the text it reads from the keyboard. The array buf can be either a
- char array or a pointer to memory created by malloc(). (Note that you do
- not need to use an ampersand with an array name.) The scanf() function
- appends a terminating '\0' to the text in buf.
-
- The short SCANLINE.C program (Listing 9-5 on the following page)
- illustrates a simple way to use scanf() for reading words of text from the
- keyboard. It asks you to type in a line of text and then uses scanf() to
- print the words of that text, one word per line.
-
- When you run SCANLINE.C, notice that it prints nothing until you press the
- Enter key. This is because scanf() is a "buffered I/O" routine. It reads
- from the standard input (the keyboard), but it "sees" nothing until you
- "flush the standard input buffer" by pressing the Enter key. (We discuss
- this concept of buffered versus unbuffered I/O in the next chapter.)
-
- The scanf() function provides two variations for the %s specifier. (See
- Table 9-2.) These let you read more than individual words.
-
- Table 9-2 Variations of the scanf() %s Specifier
- ──────────────────────────────────────────────────────────────────────────
- %nums Reads num characters including space, tab, or newline
- characters (Specify num as a decimal integer.)
- %[range] Reads a specified range of characters
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* scanline.c -- demonstrates how scanf() reads */
- /* the individual words of a line */
-
- #define INTRO \
- "Type in lines of text. They will be printed out\n\
- one word per line, thus demonstrating scanf().\n\
- (Type Ctrl-Z to Quit)\n"
-
- main()
- {
- char buf[512]; /* should be big enough */
-
- printf(INTRO);
-
- /*
- * scanf() returns the number of items
- * its control string matched
- */
- while (scanf("%s", buf) == 1)
- {
- printf("%s\n", buf);
- }
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-5. The SCANLINE.C program.
-
- The following example reads 127 characters from the keyboard and places
- them into the array buf:
-
- char buf[128];
-
- scanf("%127s", buf);──────────────────────────────────────────%nums form
- buf[127] = '\0';
-
- This form of scanf() has two disadvantages. First, because newline
- characters can be read into buf, you can't easily tell whether buf
- contains a complete line or a partial line or a number of lines. Second,
- because this form does not append a terminating '\0' to the text, you must
- add it yourself.
-
- For better control, use the more complex scanf() %[range] directive. Here
- range is any list of characters that you want to include in buf. The
- following example:
-
- scanf("%[0123456789]", buf);
-
- reads in only the digits 0 through 9. Anything else causes scanf() to stop
- reading and terminate buf with a '\0'.
-
- A more useful variation of the %[range] directive can be constructed using
- the ^ character. When you use a ^ as the first character in range, scanf()
- reads all characters up to, but not including, any characters in range and
- stops reading at the first excluded character. This version of scanf()
- also appends a terminating '\0' to the characters it reads. The following
- example shows how to use this variation:
-
- scanf("%[^\n]", buf);─────────────────────────────Read all but a newline
- scanf("%[\n]", dummy);───────────────────────────────Read only a newline
-
- The first line tells scanf() to read all characters up to, but not
- including, the newline character and to place those characters into buf.
- The second line tells scanf() to read only a newline character (the one
- that terminated the first scanf()) and to place it into dummy. The scanf()
- function can be tricky to use (witness the need for the second statement),
- but with practice, you will find it a valuable and powerful programming
- tool.
-
- The SCRANGE.C program (Listing 9-6) summarizes the scanf() function. It
- prompts for, and reads in, several lines of text, displaying exactly what
- scanf() reads as it executes.
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrange.c -- illustrates scanf()'s control */
- /* directives */
-
- main()
- {
- char buf[512], /* should be big enough */
- dummy[2]; /* for \n and \0 */
- int num;
-
- do
- {
- printf("Running:\n");
- printf("\tscanf(\"%%d\", &num);\n");
- printf("\tscanf(\"%%[^\\n]\", buf);\n");
- printf("\tscanf(\"%%[\\n]\", dummy);\n");
-
- printf("\nType enough to satisfy this:\n");
- printf("(Set num equal to zero to quit)\n");
-
- scanf("%d", &num);
- scanf("%[^\n]", buf);
- scanf("%[\n]", dummy);
-
- printf("\n\tnum = %d\n", num);
- printf("\tbuf[] = \"%s\"\n", buf);
- printf("\n\n");
-
- } while (num != 0) ;
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-6. The SCRANGE.C program.
-
- Lines of Text with gets() and puts()
-
- Although we can use variations of scanf() to read lines of text, the
- QuickC library contains a pair of routines specifically tailored for
- reading and writing strings as lines of text. A line of text, in this
- case, is any string that includes a terminating newline. This is the most
- natural form of text entry because it corresponds to a line of text on the
- screen.
-
- Although the newline, '\n', is used throughout C to represent the end of a
- line of text, it does not correspond to the characters produced or
- expected by your hardware. The Enter key, for example, actually produces
- the '\r' character. And printing a '\n' to your screen moves the cursor
- down but not to the left on the screen. Fortunately, scanf() and gets()
- convert an Enter keypress ('\r') to a newline ('\n'), and both printf()
- and puts() convert a newline ('\n') into a carriage return/linefeed
- combination ('\r' '\n') when writing to your screen.
-
- The gets() (pronounced "get s") function reads all typed characters up to
- and including a newline (generated when you press Enter) and places those
- characters into a char array. The newline is then replaced with a '\0' to
- form a C string. The puts() (pronounced "put s") function prints a string
- on the screen and adds a newline to the end.
-
- The DIALOG.C program (Listing 9-7) uses gets(), puts(), and printf() to
- carry on a simple conversation. Note that because the gets() function
- returns NULL if it fails, we must use the directive #include <stdio.h> to
- incorporate the definition of NULL.
-
- ──────────────────────────────────────────────────────────────────────────
- /* dialog.c -- a conversation using gets() and puts() */
-
- #include <stdio.h> /* for NULL and BUFSIZ */
-
- #define THE_QUESTION \
- "And what is your view on the current price of corn\n\
- and the stability of our trade import balance?"
-
- main()
- {
- char name[BUFSIZ],
- buf[BUFSIZ];
- extern char *gets();
-
- name[0] = '\0'; /* clear the name */
-
- puts("\n\nHi there. And what is your name?");
-
- if (gets(name) != NULL && name[0] != '\')
- {
- printf("\nPleased to meet you, %s.\n", name);
- puts(THE_QUESTION);
- /*
- * force an extra <enter> before replying
- */
- do
- {
- if (gets(buf) == NULL)
- break;
-
- } while (*buf != '\0'); /* wait for empty line */
-
- puts("Sorry. I needed to think about that.");
- printf("Nice talking to you, %s.\n", name);
- }
- else
- puts("How rude!");
-
- puts("Goodbye.");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-7. The DIALOG.C program.
-
-
- String Manipulation Routines
-
- As you can see, the string I/O routines in DIALOG.C are not very
- sophisticated. Fortunately, the QuickC library contains a host of
- functions that permit more complex string manipulations. We won't describe
- all of the functions here──each has its own page in the Microsoft QuickC
- Run-Time Library Reference──but we do list many of them in Table 9-3 on
- page 279. We will, however, use many of these functions in one large
- program and then discuss those selected string-handling routines.
-
- The ACME.C program (Listing 9-8) asks the user to fill out an employment
- application for a fictional company. It isn't particularly user friendly,
- and it terminates if you type something it can't understand.
-
- ──────────────────────────────────────────────────────────────────────────
- /* acme.c -- illustrates an assortment of the */
- /* C library string-handling routines */
-
- #include <stdio.h> /* for NULL */
- #include <string.h> /* for strchr(), et al */
-
- #define NAME_PATTERN \
- "first<space>last or\n\
- first<space>middle<space>last"
-
- #define ADDRESS_PATTERN \
- "number<space>street<comma><space>city"
-
- char Buf[BUFSIZ]; /* global I/O buffer */
- main()
- {
- char *ocp, *cp, *first, *last, *street, *city;
- void Prompt(), Cant();
-
- printf("Acme Employment Questionnaire\n");
-
- /*
- * Expect first<space>last or
- * first<space>middle<space>last
- */
- Prompt("Full Name");
-
- /* search forward for a space */
- if ((cp = strchr(Buf,' ')) == NULL)
- Cant("First Name", NAME_PATTERN);
- *cp = '\0';
- first = strdup(Buf);
- *cp = ' ';
-
- /* search back from end for a space */
- if ((cp = strchr(Buf,' ')) == NULL)
- Cant("Last Name", NAME_PATTERN);
- last = strdup(++cp);
-
- /*
- * Expect number<space>street<comma><space>city<comma>
- */
- Prompt("Full Address");
-
- /* search forward for a comma */
- if ((cp = strchr(Buf,',')) == NULL)
- Cant("Street", ADDRESS_PATTERN);
- *cp = '\0';
- street = strdup(Buf);
-
- /* Search forward from last comma for next comma */
- if ((ocp = strchr(++cp,',')) == NULL)
- Cant("City", ADDRESS_PATTERN);
- *ocp = '\0';
- city = strdup(++cp);
-
- printf("\n\nYou Entered:\n");
- printf("\tFirst Name: \"%s\"\n", first);
- printf("\tLast Name: \"%s\"\n", last);
- printf("\tStreet: \"%s\"\n", street);
- printf("\tCity: \"%s\"\n", city);
-
- }
-
- void Cant(char *what, char *pattern)
- {
- printf("\n\n\bFormat Error!!!\n");
- printf("Can't parse your %s.\n", what);
- printf("Expected an entry of the form:\n\n");
- printf("%s\n\nAborted\n", pattern);
- exit(1);
- }
-
- void Prompt(char *str)
- {
- while (1)
- {
- printf("\n%s: ", str );
- if (gets(Buf) == NULL || *Buf == '\0')
- {
- printf("Do you wish to quit? ");
- if (gets(Buf) == NULL || *Buf == 'y')
- exit (0);
- continue;
- }
- break;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-8. The ACME.C program.
-
- The strchr() String Function
-
- The first new function in ACME.C is strchr() (for "string character").
- This routine requires two arguments──a string to search and a character to
- look for in that string:
-
- strchr(Buf, ' ')
- └────┴────────────────────── Search string for a space character
-
- If strchr() finds the character in the string, it returns the address of
- that character. If it doesn't find the character, it returns NULL. Thus,
- we can handle the error as follows if the character is not in the string:
-
- ┌────┬─────────────── Search Buf for a space character
- if ((cp = strchr(Buf, ' ')) == NULL)
- └──────────────────────────┼───────────────────────── Save address
- └─────────────── Then test for an error
-
- In the example, cp is a pointer to char into which we assign the address
- returned by strchr(). If the result of that assignment (the value of cp)
- is NULL, the string Buf contains no space character.
-
- Because strchr() returns the address of a string, you must either declare
- it in your program as char *strchr(); or use the statement #include
- <string.h> (as we did in ACME.C), to supply the declaration for strchr().
-
- The strdup() String Function
-
- The second new function in ACME.C is strdup(). This is a Microsoft QuickC
- function that does not exist in other C libraries. When passed a string,
- strdup() makes a copy of that string and returns the address of the copy.
- Because this type of "string duplication" is not portable, we'll show you
- a version (Listing 9-9) that is. The implementation of this portable
- version of strdup() introduces two new string-handling functions, strlen()
- and strcpy().
-
- ──────────────────────────────────────────────────────────────────────────
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* malloc */
-
- char *
- strdup(str)
- char *str;
- {
- char *newstr;
- int bytes;
-
- bytes = strlen(str);
- if ((newstr = malloc(bytes + 1)) == NULL)
- return (NULL);
- 88 (void)strcpy(newstr, str);
- return (newstr);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-9. The strdup() function.
-
- The strlen() String Function
-
- The strlen() function counts the number of characters in a string
- (excluding the terminating '\0') and returns that count. For example, the
- assignments
-
- static char word[] = "Biff";
-
- bytes = strlen(word);
-
- cause bytes to be assigned the value 4 because the string word contains
- four letters.
-
- The strcpy() String Function
-
- The strcpy() function copies its second argument (a string) into its
- first, a buffer large enough to hold that copy. The value returned by
- strcpy() is the address of its first argument. Because we wanted to ignore
- that return value in our version of strdup(), we typecast the call as type
- void:
-
- if ((newstr = malloc(bytes + 1)) == NULL)
- return (NULL);
- (void)strcpy(newstr, str);
-
- To create the space for the copy, we call malloc() with an argument of
- bytes + 1, which creates room for both the copy of the string and the
- appended terminating '\0'. (Remember strlen(), which gave us the value in
- bytes, does not count the terminating '\0'.)
-
- Table 9-3 QuickC Library String Manipulation Functions
- ──────────────────────────────────────────────────────────────────────────
- strlen(str) Returns the length of a string str, not counting the
- terminating '\0'
- strcat(s1, s2) Concatenates the second string (s2) to the end of the
- first (s2)
- strcmp(s1, s2) Compares two strings (s1 and s2); returns 0 if they
- are the same, otherwise returns the arithmetic
- difference of the first two nonmatching characters
- stricmp(s1, s2) Compares two strings without regard to case
- strncmp(s1, s2, n) Compares n characters in the two strings (s1 and s2)
- strcpy(buf, str) Copies a string (str) into a char buffer buf, which
- must be large enough to hold both the string and its
- terminating '\0'
- strncpy(buf, str, n) Copies n characters of the string str into the buffer
- buf
- strchr(str, ch) Finds a character (ch) in a string (str); returns the
- address of ch if found, otherwise returns NULL
- strcspn(s1, s2) Finds a substring in s1 that begins with anything
- other than one of the characters in s2; returns the
- address of that substring if found, otherwise returns
- NULL
- strstr(s1, s2) Finds the first occurrence of the substring s1 in the
- larger string s2; returns the address of that
- substring if found, otherwise returns NULL
- strrev(str) Reverses the characters in the string str; returns
- the address of that reversed string
- strupr(str) Converts a string (str) to uppercase characters
- strset(str, ch) Clears a string (str), converting all its characters
- to the character ch
- strdup(str) Duplicates a string (str), returning the address of
- the new copy
- sprintf(str, cntl, Formatted print into a string (str), converting args
- args,...) based on the control string cntl
- sscanf(str, cntl, Formatted convert, like scanf(), but converts from
- addrs,...) the string rather than from the keyboard
- ──────────────────────────────────────────────────────────────────────────
-
- You should be aware that although stricmp(), strcspn(), and strupr() are
- supplied with the Microsoft QuickC library, they are not a part of ANSI C.
- Do not use them if you want your programs to be portable to other
- compilers and computers.
-
- C vs BASIC String Functions
-
- As you have seen, sophisticated C string handling can require complicated
- programming. Although the C library string-handling routines can emulate
- much of BASIC, the following example demonstrates that such emulation is
- usually less straightforward:
-
- A$ = B$────────────────────────────────────────────────────────────BASIC
- first = strdup(Buf);───────────────────────────────────────────────────C
-
- Some functions common to BASIC are missing from C. Among them are LEFT$,
- MID$, and RIGHT$. Listing 9-10 shows a C version of LEFT$. We leave it as
- an exercise for you to write C versions of the other two BASIC commands.
-
- C offers two principal advantages over BASIC──it permits the programmer to
- extend string-handling library routines with customized routines, and it
- allows easy access to strings from pointers.
-
- ──────────────────────────────────────────────────────────────────────────
- #include <stdio.h> /* for NULL */
- #include <string.h> /* for strdup() */
-
- char *
- leftstr(str, cnt)
- char *str;
- int cnt;
- {
- char *cp;
-
- if (strlen(str) < cnt || cnt <= 0)
- return (NULL);
- if (strlen(str) == cnt)
- return (strdup(str));
- cp = strdup(str);
- cp[cnt - 1] = '0';
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-10. The leftstr() function.
-
-
- Arrays and Strings
-
- Because a string is nothing more than an array of type char, you can use a
- two-dimensional array of type char as an array of strings. However, you
- must be sure to terminate each row (string) with a '\0' character, as
- follows:
-
- char names[3][6] = {
- { 'J','o','e','\0' },
- { 'D','u','k','e','\0' },
- { 'O','z','z','i','e','\0' }
- };
-
- You also can take the easier route of using string constants (quoted
- strings) as array initializers:
-
- char names[3][6] = { "Joe", "Duke", "Ozzie" };
-
- Both forms create identical arrays, as illustrated in Figure 9-3. Also
- notice that underinitializing rows sets the trailing characters in rows 0
- and 1 to '\0'.
-
- char names [3] [6] = {"Joe", "Duke", "Ozzie"};
-
- Columns
- │
- ┌───────────────────────┴───────────────────────┐
- │ ┌────────┼─┐
- ┌─┌───────┬───────┬───────┬───────┬──────┬───────┐ │
- │ │ 'J' │ 'o' │ 'e' │ '\O' │ '\O' │ '\O' ─┼─ Auto-initia
- │ ├───────┼───────┼───────┼──────┼───────┼───────┤ │ trailing ze
- Rows of ─┤ │ 'D' │ 'u' │ 'k' ││ 'e' │ '\O' │ '\O' ─┘
- strings │ ├───────┼───────┼───────┼┼──────┼──────┼───────┤
- │ │ 'O' │ 'z' │ 'z' ││ 'i' ││ 'e' │ '\O' │
- └─└───────┴───────┴───────┴┼──────┴┼──────┴──────┘
- │ │ │
- └───────┴───────┴────────── String-
- terminating
- zeros
-
- Figure 9-3. A two-dimensional array of char values as an array of strings.
-
- As we've already seen, strings can be easily manipulated by pointers.
- Because of this, arrays of pointers to strings are often used in place of
- the two-dimensional arrays of char. The previous sample arrays, declared
- and initialized as an array of pointers, appear as follows:
-
- char *names[3] = { "Joe", "Duke", "Ozzie" };
- └──────────────────────────────────────────── Array of pointers
-
- This pointer form also uses storage space more efficiently than the
- two-dimensional array. Compare the memory use of this form, depicted in
- Figure 9-4, with that of the preceding approach (shown in Figure 9-3).
-
- ┌──────────┐ ┌───────┬───────┬───────┬───────┐
- │ │──────│ 'J' │ 'o' │ 'e' │ '\O' │
- ├──────────┤ ├───────┼───────┼───────┼───────┼───────┐
- │ │──────│ 'D' │ 'u' │ 'k' │ 'e' │ '\O' │
- ├──────────┤ ├───────┼───────┼───────┼───────┼───────┼───────┐
- │ │──────│ 'O' │ 'z' │ 'z' │ 'i' │ 'e' │ '\O' │
- └──────────┘ └───────┴───────┴───────┴───────┴───────┴───────┘
- └─────┬────┘
- │
- Array of 3 pointers
- │
- ┌──────┴─────┐
- char *names[3] = {"Joe", "Duke", "Ozzie"};
-
- Figure 9-4. Arrays of pointers to strings use memory efficiently.
-
- The L2WORDS.C program (Listing 9-11) illustrates one application for an
- array of pointers to strings. It asks you to enter a line of text, then it
- breaks that line into individual words and returns an array of pointers to
- the substrings that form those words. Line2words() assumes that spaces
- separate words, but it can take multiple words as a single word if you
- surround them with full quotation marks. A routine like Line2words() is
- useful for writing your own command-line interpreter (COMMAND.COM).
-
- ──────────────────────────────────────────────────────────────────────────
- /* l2words.c -- employs an array of pointers to */
- /* strings to break a line of text */
- /* into its component words */
-
- #include <stdio.h> /* for NULL and BUFSIZ */
-
- main()
- {
- char **Line2words(); /* declare function type */
- char **list; /* pointer to pointer */
- char buf[BUFSIZ]; /* buffer for input */
- int count, i, quote_flag;
-
- printf("Enter a line of text and I will break\n");
- printf("it up for you.\n");
-
- if (gets(buf) == NULL)
- exit(1);
-
- list = Line2words(buf, &count);
-
- for (i = 0; i < count; i++)
- {
- quote_flag = 0;
- printf("<");
- if (list[i] != buf)
- {
- if( list[i][-1] == '"') /* negative subscript */
- {
- ++quote_flag;
- printf("\"");
- }
- }
- printf("%s", list[i]);
-
- if (quote_flag)
- printf("\"");
-
- printf(">\n");
- }
- }
- #define MAXW 64
-
- char **Line2words(char *line, int *count)
- {
- static char *words[MAXW];
- int index;
-
- index = 0; /* zero internal index */
-
- while (*line != '\0')
- {
- /* turn spaces and tabs into zeros */
- if (*line == ' ' || *line == '\t')
- {
- *(line++) = '\0';
- continue;
- }
- words[index] = line++; /* found a word */
-
- /* is it quoted? */
- if ( *(words[index]) == '"')
- {
- /* Yes, advance pointer to just past quote. */
- ++words[index];
-
- /* find next quote. */
- while (*line && *line != '"')
- {
- ++line;
- }
-
- /* and turn it into a '\0'. */
- if (*line)
- *(line++) = '\0';
- }
- else
- {
- /* otherwise skip to next space */
- while (*line && *line != ' ' && *line != '\t')
- {
- ++line;
- }
- }
- if (++index == MAXW)
- break;
- }
- *count = index; /* set count via pointer */
- return (words); /* return address of array */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-11. The L2WORDS.C program.
-
- L2WORDS.C does a few tricky things: First, notice that we declare the
- function Line2words() as char **. This means that it returns a pointer to
- a pointer. That pointer contains the address of the first element of our
- array of pointers. The first element in that array points to the first
- word.
-
- Second, notice that when the program prints words, it checks lines[i][-1]
- (negative subscripting) to see if the string has full quotation marks
- around it. If it does, the program replaces them when it prints the word.
-
-
- The Arguments to main()──argv and argc
-
- When you run a program from the command interpreter (COMMAND.COM under
- MS-DOS, or sh or csh under UNIX), you can specify arguments for the
- program on the command line. For example, when you run QuickC by typing
-
- C> qc file.c
-
- QuickC starts with the file named file.c already loaded. All C programs,
- including QuickC, retrieve arguments from the command line in the same
- way. That is, every C program begins execution with the function named
- main(), and that function, like any other, can receive arguments.
- Traditionally called argc and argv, these arguments are received by main()
- as follows:
-
- main(argc, argv)
- int argc;
- char *argv[];
-
- These arguments to main() contain all the information that you need to
- access the command-line arguments: argc is the number of command-line
- arguments, and argv is an array of pointers to those arguments.
-
- The SHOWARGS.C program (Listing 9-12) shows how to access and use the
- arguments passed to main(). To run this program from within QuickC, you
- must first set the command-line arguments with Set Runtime Options on the
- Run menu.
-
- When you run SHOWARGS.C with the following command-line preset in the Set
- Runtime Options dialog box:
-
- kit makes lovely paper
-
- the program prints the following:
-
- argc = 5───────────────────────────────────────────Five pointers in argv
-
- argv[0] -> "C"
- argv[1] -> "kit"
- argv[2] -> "makes"
- argv[3] -> "lovely"
- argv[4] -> "paper"
- argv[5] -> NULL
-
- ──────────────────────────────────────────────────────────────────────────
- /* showargs.c -- shows how to access the arguments */
- /* passed to main() */
-
- #include <stdio.h> /* for NULL */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int i;
-
- printf("argc = %d\n", argc);
- printf("\n");
-
- for (i = 0; i < argc; ++i)
- {
- printf("argv[%d] -> \"%s\"\n", i, argv[i]);
- }
- printf("argv[%d] -> NULL\n", i);
- printf("\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-12. The SHOWARGS.C program.
-
- The first string that argv points to (an array of pointers to strings) is
- usually the name of your program. (Under QuickC, your program will always
- be named C when you run it from the Run menu, but argv[0] is correct when
- you run your program later as a .EXE file.)
-
- Because argv is an array of pointers to char, you often will see it
- alternatively declared as follows:
-
- main(argc, argv)
- int argc;
-
- main(argc, argv)
- int argc;
- char **argv;
- { └─────────────────────────────────────────── A pointer to a pointer
-
- Recall that this pointer to a pointer and the declaration char *argv[] are
- interchangeable.
-
- The main() function is actually passed three arguments, but the third
- argument, called envp, is seldom used. Like argv, it is an array of
- pointers to strings and must be declared as follows:
-
- main(argc, argv, envp)
- int argc;
- char *argv[], *envp[];
- {
-
- The strings that envp points to are your system's environmental variables,
- such as PATH.
-
- Take a moment to modify SHOWARGS.C so that it matches the SHOW2.C program
- (Listing 9-13). After you run this program, choose DOS Shell from the
- File menu and type set. Compare the output produced by the MS-DOS SET
- command to that produced by this program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* show2.c -- shows how to use main()'s envp */
-
- #include <stdio.h> /* for NULL */
-
- main(argc, argv, envp)
- int argc;
- char *argv[], *envp[];
- {
- int i;
-
- printf("argc = %d\n", argc);
- printf("\n");
-
- for (i = 0; i < argc; ++i)
- {
- printf("argv[%d] -> \"%s\"\n", i, argv[i]);
- }
- printf("argv[%d] -> NULL\n", i);
- printf("\n");
-
- for (i= 0; envp[i] != NULL; ++i)
- {
- printf("envp[%d] -> \"%s\"\n", i, envp[i]);
- }
- printf("envp[%d] -> NULL\n", i);
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-13. The SHOW2.C program.
-
-
- Character Classification and Transformation
-
- You often need to be able to classify individual characters of a string
- (such as uppercase versus lowercase) and then transform them (such as
- converting uppercase to lowercase). QuickC includes a standard C header
- file called ctype.h, which defines many character classifications and
- transformation routines. (Use the View Include menu to examine it.) To
- access ctype.h, merely use #include to include it at the head of your
- program.
-
- The routines in ctype.h are not true functions: They are #define macros.
- We'll describe #define macros in detail in Chapter 12. In the meantime,
- you can use these routines because they work like function calls.
-
- The Character Classification Routines
-
- Each of the character classification routines in Table 9-4 takes a single
- argument──the character to classify──and returns a 1 for true or a 0 for
- false.
-
- The WHATCHAR.C program (Listing 9-14) prints all possible classifications
- for each character in a line of entered text. The program limits the line
- of text to 20 characters so that the display doesn't scroll off the
- screen.
-
- Table 9-4 The Character Classification Routines in ctype.h
- ──────────────────────────────────────────────────────────────────────────
- isalnum() Tests for alphanumeric ('A' through 'Z,' 'a' through 'z,' and
- '0' through '9')
- isalpha() Tests for a letter ('A' through 'Z' and 'a' through 'z')
- isascii() Tests for an ASCII character (0x00 through 0x7F)
- iscntrl() Tests for a control character (less than ' ' or equal to 0x7F)
- isdigit() Tests for a digit ('0' through '9')
- isgraph() Tests for printable character (inverse of iscntrl() but
- excludes space)
- islower() Tests for lowercase letter ('a' through 'z')
- isprint() Tests for printable character (inverse of iscntrl())
- ispunct() Tests for punctuation character
- iswhite() Tests for white space ('\t,' '\n,' '\f,' and ' ')
- isupper() Tests for uppercase letter ('A' through 'Z')
- isxdigit() Tests for a hexadecimal digit ('A' through 'F,' 'a' through
- 'f,' '0' through '9')
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* whatchar.c -- demonstrates the character */
- /* classification routines in ctype.h */
-
- #include <stdio.h> /* for NULL and BUFSIZ */
- #include <ctype.h> /* for iscntl(), et al */
- #define MAXL 20
-
- main()
- {
- char buf[BUFSIZ];
- int i;
-
- printf("Enter a line of text (20 chars max):\n");
- if (gets(buf) == NULL)
- exit(1);
-
- for (i = 0; i < MAXL; ++i)
- {
- if (buf[i] == '\0')
- break;
- printf("'%c' ->", buf[i]);
- if (isalpha(buf[i])) printf(" isalpha");
- if (isascii(buf[i])) printf(" isascii");
- if (iscntrl(buf[i])) printf(" iscntrl");
- if (isgraph(buf[i])) printf(" isgraph");
- if (isprint(buf[i])) printf(" isprint");
- if (isdigit(buf[i])) printf(" isdigit");
- if (isupper(buf[i])) printf(" isupper");
- if (islower(buf[i])) printf(" islower");
- if (ispunct(buf[i])) printf(" ispunct");
- if (isspace(buf[i])) printf(" isspace");
- if (isalnum(buf[i])) printf(" isalnum");
- if (isxdigit(buf[i])) printf(" isxdigit");
- printf("\n");
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-14. The WHATCHAR.C program.
-
- The include file ctype.h also defines routines to transform characters.
- Each of the routines in Table 9-5 takes a single argument, the character
- to transform, and returns the transformed character, as in the following
- example:
-
- ch = toupper('a');
-
- Here toupper() is given a lowercase 'a'. Because 'a' is lowercase,
- toupper() transforms it to an uppercase 'A' and assigns that value to the
- variable ch.
-
- The INVERT.C program (Listing 9-15) uses both the character
- classification and transformation routines to reverse a line of entered
- text. That is, it prints the line backward and inverts the case of each
- character.
-
- Table 9-5 The Character Transformation Routines in ctype.h
- ──────────────────────────────────────────────────────────────────────────
- toascii() Converts a non-ASCII character to an ASCII character (clears
- all but the low-order seven bits)
- toupper() Converts a lowercase character to an uppercase character
- tolower() Converts an uppercase character to a lowercase character
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* invert.c -- combines character classification and */
- /* transformation to invert text */
-
- #include <stdio.h> /* for NULL */
- #include <ctype.h> /* for toupper, et al. */
-
- main()
- {
- char buf[BUFSIZ];
- int i;
-
- printf("Type in a line of text and I will invert it.\n");
-
- if (gets(buf) == NULL)
- exit(1);
- /* Print the string backward. */
- for (i = (strlen(buf) - 1); i >= 0; --i)
- {
- if (isupper(buf[i])) /* upper to lower */
- putchar(tolower(buf[i]));
- else if (islower(buf[i])) /* lower to upper */
- putchar(toupper(buf[i]));
- else
- putchar(buf[i]);
- }
- putchar('\n');
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 9-15. The INVERT.C program.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 10 Managing Files
-
- C files are primarily disk files that contain text, executable images of
- programs, or data. These disk files represent stored programs and data
- that form a common "library" of information that is available to a wide
- range of programs.
-
- The QuickC library functions that handle file input and output are
- arranged in three categories, or levels, as illustrated in Figure 10-1 on
- the following page. At the top level are the buffered (stream I/O)
- routines; below those are the unbuffered (raw I/O) routines; and at the
- bottom are the direct BIOS interfaces. The low-level routines are not a
- part of portable C because they access PC-specific internal routines. The
- higher-level routines, however, are universal to all C compilers. We will
- not cover the low-level BIOS routines in this book.
-
- The top-level file I/O routines are called "buffered stream" routines
- because they interpose themselves between your program and files. They
- read and write large blocks of information (buffering), and then they pass
- a continuous series (stream) of bytes to your program, as needed.
-
- ┌────────────────────┐
- │ │
- │ Buffer │────────────────┐
- │ │ │
- └───────────────────┘ │
- │ │
- │ │
- ┌───────────────────┐ │
- ┌─────────│Top-level │ │
- │ │functions like │ ┌───────────────┐
- ┌───────────────┐ │fopen() and fgetc() │ │ │
- │ │ └────────────────────┘ │ Your │
- │ Disk │ ┌────────────────────┐ │ program │
- │ │ │Mid-level │ │ │
- └──────────────┘ │functions like │ └──────────────┘
- │ └─────────│open() and read() │──────────┘ │
- │ └────────────────────┘ │
- │ ┌────────────────────┐ │
- │ │Low-level │ │
- └───────────────│functions like │────────────────┘
- │_bios_disk() │
- └────────────────────┘
-
- Figure 10-1. The three levels of file I/O.
-
- The middle file level is called unbuffered because it lets your program
- access files directly. Reads and writes do not pass through an
- intermediate buffer; they pass directly between the operating system and
- your program. These mid-level routines can execute faster than the
- top-level routines, but they are more complex to use.
-
- Both top-level and mid-level file routines have two modes──text and
- binary. Text mode is used with text files, or files that contain ASCII
- text (which is readable by persons). Binary mode is used with files that
- contain binary information, such as executable programs. In text mode,
- Ctrl-Z (a byte containing the value 0x1A) marks the end of a file. In
- binary mode, Ctrl-Z can legally be a part of the file; the operating
- system keeps track of file length.
-
-
- Top-Level I/O
-
- All buffered file I/O functions require that you begin your program with
- #include <stdio.h>. That header file contains the definition for FILE, the
- data type that you use to manipulate files. The type FILE is used as shown
- on the next page.
-
- #include <stdio.h>
- ...
- FILE *fp;
-
- Remember, always use #include <stdio.h> for the definition of the type
- FILE. Then declare a file pointer to point to the data type FILE.
-
- Opening Files with fopen()
-
- Before you can access a file for reading or writing or both, you must
- first open that file. For buffered I/O routines (those that use a file
- pointer), open the file with the fopen() function, as follows:
-
- fp = fopen(filename, activity);
- │ └───────────────── Open to read, write, or both
- └──────────────────────────────────── Name of file to open
-
- The fopen() function requires two arguments: the name of the file to open
- (a string or the address of a string) and an activity (also a string) as
- listed in Table 10-1. The activity determines whether the file is open
- for reading, writing, or appending. (In this case, read means to take
- information sequentially from a file, write means to put information
- sequentially into a file, and append means to add information to the end
- of a file.)
-
- The fopen() function returns a value of type FILE *. In our example we
- assigned this value to file pointer fp, which we will use to access and
- manipulate the file. If fopen() fails, it returns NULL. Therefore, the
- complete call to fopen(), including error handling, is as follows:
-
- fp = fopen("test.c", "r");
- if (fp == NULL)
- {
- /* handle error here */
- }
-
- This opens the TEST.C file for reading (activity "r"). After the file
- pointer returned by fopen() is assigned to fp, we test fp to see if it is
- NULL. We test for an error here because it is possible that the file
- TEST.C does not exist.
-
- Table 10-1 Possible Modes (Activities) for fopen()
- Mode Description
- ──────────────────────────────────────────────────────────────────────────
- "r" Open for reading only. The file must already exist.
- "w" Open for writing only. Creates the file if it does not exist.
- "a" Open for appending (write-only, starting at the end of a file).
- Creates the file if it does not exist.
- "r+" Open for both reading and writing. The file must already exist.
- "w+" Open for both reading and writing. Creates the file if it does
- not already exist.
- "a+" Open for both reading and writing, starting at the end of the
- file. Creates the file if it does not exist.
- ──────────────────────────────────────────────────────────────────────────
-
- Each open file requires its own file pointer. The following two open
- files, for example, require two separate file pointers:
-
- #include <stdio.h>
- ...
- FILE *fp_in, *fp_out;
- ...
- fp_in = fopen("test.txt", "r");
- fp_out = fopen("test.bak", "w");
-
- In this example, fp_in is the file pointer for the file opened for reading
- (activity "r"), and fp_out is the file pointer for the file opened for
- writing (activity "w").
-
- ──────────────────────────────────────────────────────────────────────────
- File Access in BASIC and C
- If you're used to BASIC file handling, you'll find that QuickC offers
- fewer "built-in" conveniences but ultimately provides more power and
- flexibility. In BASIC, you might open a random access file with the
- following statement, which specifies the file identification number and
- record length:
-
- OPEN "C:\ACCT\TRANS" FOR RANDOM AS #1 LEN = 256
-
- Before you can use the file, you have to use FIELD statements to associate
- whatever numeric or string variables you are going to use with the
- corresponding data fields in the file record. Because most versions of
- BASIC don't have a data type similar to the C struct, you have to
- manipulate numerous separate variables to move data to and from the file.
- The built-in random access support does allow you to get a record by its
- record number directly using the GET statement, however.
-
- C has a different approach: A file can contain any valid C data type, such
- as a struct, which already has its fields defined, so you don't have to
- set up file data fields. On the other hand, file manipulation methods,
- such as random access, are not built-in in C. You can achieve random
- access, however, by converting a record number to an offset and then using
- the library function fseek() to position C's file pointer to the correct
- record. You can also use the fgetpos() and fsetpos() functions to
- manipulate the file pointer.
-
- Also, because C uses function calls rather than BASIC's procedural
- commands to manipulate files, you can quickly check for errors by putting
- the function call in an if statement.
- ──────────────────────────────────────────────────────────────────────────
-
- Reading Characters with fgetc()
-
- There's more to reading a file than merely opening the file to read. To
- see what we mean, examine the STRINGS.C program (Listing 10-1), which
- reads a file one character at a time and looks for strings of five or more
- printable characters. The program takes a command-line argument, so before
- you run it, you must create the argument using the Set Runtime Options
- screen from the Run menu. In the Command Line box, type c:\qc\qc.exe (or
- the name of any existing file). Figure 10-2 on the next page shows the
- screen after you type the command.
-
- ──────────────────────────────────────────────────────────────────────────
- /* strings.c -- opens a file and searches it for */
- /* possible strings */
-
- #include <stdio.h> /* for FILE, BUFSIZ, & EOF */
- #include <ctype.h> /* for isprint() */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- FILE *fp;
- char buf[BUFSIZ];
- int ch, count;
-
- if (argc != 2)
- {
- fprintf(stderr, "usage: strings file\n");
- exit(1);
- }
- if ((fp = fopen(argv[1], "rb")) == NULL)
- {
- fprintf(stderr, "Can't open %s\n", argv[1]);
- exit(1);
- }
-
- count = 0;
- while ((ch = fgetc(fp)) != EOF)
- {
- if (! isprint(ch) || count >= (BUFSIZ - 1))
- {
- if (count > 5)
- {
- buf[count] = 0;
- puts(buf);
- }
- count = 0;
- continue;
- }
- buf[count++] = ch;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-1. The STRINGS.C program.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 10-2 can be found on p.296 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 10-2. The Set Runtime Options dialog box lets you enter a command
- line.
-
- When we run STRINGS.C on a large file such as QC.EXE, the program prints
- many screens of possible strings. For convenience, you might add a
- "paging" feature to the program.
-
- STRINGS.C uses the fgetc() function, a file-oriented version of the
- getchar() routine we've used before. After it is passed a single argument
- (a file pointer), the function returns the next character read from the
- file pointed to. Assigning that character to a variable of type int lets
- us detect EOF (End Of File) easily.
-
- int ch;
-
- if ((ch = fgetc(fp)) == EOF)
- {
- /* handle end of file here */
- }
-
- Notice that STRINGS.C calls fopen() with the activity argument "rb". This
- is a PC-specific extension of the normal "open for reading" argument. The
- b tells fopen() to open the file in binary mode but to do no character
- translation for us──that is, to give fgetc() every byte from the file as
- is. If we did not specify the b, fopen() would have opened the file in
- text mode. Had we used text mode, however, our program would not have read
- all of QC.EXE because the Ctrl-Z character, which is a legal byte in
- binary files, would have marked the end of the file. Table 10-2 shows the
- difference between these two modes.
-
- Also notice that STRINGS.C ends without closing the file. C, unlike BASIC,
- closes all open files when you exit the program. This is true whether you
- exit main() with a return or from another function with an exit().
-
- Table 10-2 Text vs Binary Modes for fopen()
- ──────────────────────────────────────────────────────────────────────────
- t text mode Translates carriage return/linefeed combinations
- into single linefeeds on input and makes the
- reverse translation on output. Ctrl-Z marks the
- end of the file.
- b binary mode Suppresses the above translations. The operating
- system keeps track of the file's length.
- ──────────────────────────────────────────────────────────────────────────
-
- Closing Files with fclose()
-
- Although MS-DOS lets you have as many as 20 simultaneously open files, you
- might want to close each open file before you open another one. Closing a
- file writes everything to disk, updates the directory entry for that file,
- and frees a file pointer.
-
- When you open files with fopen(), you can close them with fclose(), as
- follows:
-
- if (fclose(fp) == EOF)
- {
- /* unable to close file */
- }
- /* fp may be reused here */
-
- If fclose() cannot close a file (because the floppy disk containing that
- file was removed, for example), it returns EOF.
-
- Line I/O with fgets() and fputs()
-
- The standard C Library contains a pair of file-oriented routines called
- fgets() ("file get string") and fputs() ("file put string"). They are
- similar to the gets() and puts() pair we discussed in the last chapter:
- fgets() reads lines of text from files and fputs() writes lines of text
- into files. Use them as follows:
-
- #include <stdio.h>
- #define SIZE 512
- ...
- FILE *fp_in, *fp_out;
- char buf[SIZE];
- ...
- /* open fp_in for reading and fp_out for writing */
- ...
- if (fgets(buf, SIZE, fp_in) == NULL)
- {
- /* error reading or EOF */
- }
- /* a line of text is now in buf */
- ...
-
- if (fputs(buf, fp_out) == EOF)
- {
- /* error writing */
- }
-
- The fgets() function takes three arguments: the address of a char buffer,
- the maximum number of characters to read into that buffer, and a file
- pointer to a file opened for reading. In the example, fgets() reads a
- maximum of SIZE characters (up to and including the first newline
- character) and appends a terminating '\0' to the characters to form a
- string. The fputs() function requires two arguments: the address of a
- zero-terminated string (in buf) and a file pointer to a file opened for
- writing. In the example, fputs() writes the string in buf to fp_out──
- including any newline in that string. Note as well that fputs() does not
- add any newlines.
-
- The fputs() and fgets() functions differ from their counterparts puts()
- and gets(). Each handles the newline character in a different way, as
- follows:
-
- ──────────────────────────────────────────────────────────────────────────
- gets(buf) Reads characters from keyboard and places them into buf.
- Replaces the trailing newline character ('\n') with a '\0'.
-
- fgets(buf, Reads a maximum of len characters from a file opened for
- len, fp) reading. Places len or fewer characters (up to and
- including a newline) into buf. Retains the newline
- character and adds a terminating '\0'.
-
- puts(buf) Prints the string in buf to the screen and adds a newline
- character to the output on the screen.
-
- fputs(buf, fp) Prints (writes) the string in buf into the file (opened for
- writing) pointed to by the file pointer fp. Does not add a
- newline character to the output.
- ──────────────────────────────────────────────────────────────────────────
-
- The CCOPY.C program (Listing 10-2) reads one file and writes to a second.
- The "C" preceding "COPY" (in the program name) signals that this COPY
- "crunches" its input──eliminating all empty lines, leading tabs, and
- spaces. You could use this program to prepare files before sending them
- over a slow modem.
-
- ──────────────────────────────────────────────────────────────────────────
- /* ccopy.c -- copies a file, cutting blank lines and */
- /* leading space from lines of copy */
-
- #include <stdio.h> /* for FILE, BUFSIZ, NULL */
- #include <ctype.h> /* for iswhite() */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- FILE *fp_in, *fp_out;
- char buf[BUFSIZ];
- char *cp;
-
- if (argc != 3)
- {
- printf("usage: ccopy infile outfile\n");
- exit(1);
- }
- if ((fp_in = fopen(argv[1], "r")) == NULL)
- {
- printf("Can't open %s for reading.\n", argv[1]);
- exit(1);
- }
- if ((fp_out = fopen(argv[2], "w")) == NULL)
- {
- printf("Can't open %s for writing.\n", argv[2]);
- exit(1);
- }
-
- printf("Copying and Crushing: %s->%s ...",
- argv[1], argv[2]);
-
- while (fgets(buf, BUFSIZ, fp_in) != NULL)
- {
- cp = buf;
- if (*cp == '\n') /* blank line */
- continue;
- while (isspace(*cp))
- {
- ++cp;
- }
- if (*cp == '\0') /* empty line */
- continue;
- if (fputs(cp, fp_out) == EOF)
- {
- printf("\nError writing %s.\n", argv[2]);
- exit(1);
- }
- }
- printf("Done\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-2. The CCOPY.C program.
-
- To run this program you need to set its command line from the Set Runtime
- Options dialog box. The Command Line text box requires two filenames as
- arguments──first, the file to read, and second, the file to write to. For
- example, you might enter the filenames strings.c temp. The first name is
- the existing text file to be read (note that the fopen() in CCOPY.C uses
- "r" for text mode). The second name is the new file that will be created
- (activity "w").
-
- Within a loop, fgets() reads a line of text from the first file, the
- program crunches that line, and fputs() writes the condensed line into the
- second file. After you run CCOPY.C, choose DOS Shell from the File menu
- and look at the newly created file using the TYPE command and Ctrl-S.
-
- Error Detection with feof() and ferror()
-
- Using fgets() has a drawback──it returns NULL for both EOF (which you
- expect) and read errors (which you don't expect). However, you can
- differentiate between the two by using feof() and ferror().
-
- The feof() function tests a file opened for reading and associated with a
- file pointer to see if the end of that file has been reached. It returns
- true (nonzero) at the end of the file; otherwise it returns 0. The
- ferror() function returns true if there is any error with the file──
- including reaching the end of file. The following example shows how to use
- them together to differentiate between the two conditions:
-
- if (feof(fp_in))
- {
- /* reached end of file while reading */
- }
- else if (ferror(fp_in))
- {
- /* some read error has occurred */
- }
-
- EOF is meaningful only when reading; use ferror() alone when writing to a
- file:
-
- if (ferror(fp_out))
- {
- /* some write error has occurred */
- }
-
- Always include error-checking routines in your programs to protect
- yourself from careless users. Users sometimes remove floppy disks while
- the drive light is on or try writing to disks that are write-protected.
- Error detection lets you either take corrective action or notify users of
- their mistakes.
-
- Block I/O with fread() and fwrite()
-
- So far we've treated files as lines of text. However, you will often want
- to read and write files in specific blocks whose size is measured in
- bytes. (Executable program files and data files, for example, generally
- contain no meaningful lines of text.) To do this, the standard C Library
- provides a pair of routines called fread() and fwrite(). Their forms are
- nearly identical:
-
- fread(buffer, size, count, fp_in);
- │ │ │ └──────────────────────── A file pointer
- │ │ └────────────────────────── How many size items
- │ └──────────────────────────── How many bytes per item
- └───────────────────── Address of (size * count) bytes buffer
-
-
- fwrite(buffer, size, count, fp_out);
- │ │ │ └──────────────────────── A file pointer
- │ │ └────────────────────────── How many size items
- │ └──────────────────────────── How many bytes per item
- └───────────────────── Address of (size * count) bytes buffer
-
- Both routines require that you specify #include <stdio.h> to define FILE
- for the file pointer and to define the new type size_t for the variables
- size and count:
-
- size_t size;
- size_t count;
- FILE *fp;
-
- QuickC defines the type size_t in the <stdio.h> header file as an unsigned
- long. Because it might be defined differently with other compilers, you
- should use size_t for portability.
-
- Both functions return the number of bytes actually read or written. When
- that number is less than size times count, an error has occurred. In the
- case of fread(), however, that error can also indicate that you've reached
- the end of the file. Therefore, you need to use feof() to distinguish end
- of file from other errors.
-
- The UPPITY.C program (Listing 10-3) shows one way to use fread() and
- fwrite(). It reads an entire file into memory (using malloc() to obtain
- that memory), converts it to uppercase, then writes the entire file to a
- new file having the .UP extension.
-
- ──────────────────────────────────────────────────────────────────────────
- /* uppity.c -- makes an uppercase copy of a file using */
- /* fread() and fwrite() */
-
- #include <string.h> /* for strrchr() */
- #include <stdio.h> /* for NULL */
- #include <malloc.h> /* for malloc() */
- #include <ctype.h> /* for isupper() */
-
- #define HUNK 512
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char *cp, newname[128], *np;
- FILE *fp;
- int hunks = 0, bytes = 0, totbytes = 0;
- int i;
- if (argc != 2)
- {
- printf("usage: uppity file\n");
- exit(1);
- }
-
- if ((fp = fopen(argv[1], "rb")) == NULL)
- {
- printf("\"%s\": Can't open.\n", argv[1]);
- exit(1);
- }
- if ((cp = malloc(HUNK)) == NULL)
- {
- printf("Malloc Failed.\n");
- exit(1);
- }
-
- while ((bytes = fread(cp + (HUNK * hunks), 1, HUNK, fp)) == HUNK)
- {
- totbytes += bytes;
- ++hunks;
- if ((cp = realloc(cp, HUNK + (HUNK * hunks))) == NULL)
- {
- printf("Realloc Failed.\n");
- exit(1);
- }
- }
- if (bytes < 0)
- {
- printf("\"%s\": Error Reading.\n", argv[1]);
- exit(1);
- }
- totbytes += bytes;
-
- for (i = 0; i < totbytes; ++i)
- if (islower(cp[i]))
- cp[i] = toupper(cp[i]);
-
- (void)fclose(fp);
-
- if ((np = strchr(argv[1], '.')) != NULL)
- *np = '\0';
- strcpy(newname, argv[1]);
- strcat(newname, ".up");
- if ((fp = fopen(newname, "wb")) == NULL)
- {
- printf("\"%s\": Can't open.\n", argv[1]);
- exit(1);
- }
-
- if (fwrite(cp, 1, totbytes, fp) != totbytes)
- {
- printf("\"%s\": Error writing.\n", argv[1]);
- exit(1);
- }
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-3. The UPPITY.C program.
-
- UPPITY.C continually reallocates memory for each HUNK (512 bytes) of the
- file read in. A more direct approach would find the size of the file, then
- read in that many bytes with a single fread(). You can do this with the
- stat() function. Unfortunately, to use stat() you must understand
- "structures," and we won't be describing those until the next chapter.
- Keep in mind that you might want to modify UPPITY.C when you learn how to
- use structures.
-
- Predeclared File Pointers
-
- When you run any QuickC program, five file pointers are always provided
- for five preopened files. Those file pointers are stdin, stdout, stderr,
- stdaux, and stdprn. (See Table 10-3.) Because these preopened file
- pointers are defined in stdio.h, you must include that header file if you
- want to use them.
-
- To demonstrate the use of these file pointers, we revised CCOPY.C (the
- "crunch-and-copy program") to produce the CCOPY2.C program (Listing
- 10-4). This revision checks for the presence of a second (output)
- filename. If it is missing, fputs() directs the output to stdout (your
- screen).
-
- Table 10-3 QuickC's Preopened File Pointers
- ──────────────────────────────────────────────────────────────────────────
- stdin The standard input. Your keyboard viewed as a file. Also, input
- to your program provided by redirection using <file from the
- MS-DOS command line.
- stdout The standard output. Your screen viewed as a file. Also, output
- to disk files provided by redirection using >file from the
- MS-DOS command line.
- stderr The standard error output. Always your screen. This file
- pointer is unaffected by redirection from the MS-DOS command
- line.
- stdaux The standard auxiliary. Usually your serial port or COM1. This
- file pointer provides easy access to your modem.
- stdprn The standard printer output. Usually your parallel port or PRN.
- This file pointer provides an easy way to generate hard copy
- from within a QuickC program.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* ccopy2.c -- copies a file, cutting blank lines and */
- /* leading space from lines of copy */
-
- /* Modified to demonstrate stdout and stderr */
-
- #include <stdio.h> /* for FILE, BUFSIZ, NULL */
- #include <ctype.h> /* for iswhite() */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- FILE *fp_in, *fp_out;
- char buf[BUFSIZ];
- char *cp;
-
- if (argc < 2)
- {
- fprintf(stderr, "usage: ccopy2 infile {outfile}\n");
- exit(1);
- }
- if ((fp_in = fopen(argv[1], "r")) == NULL)
- {
- fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);
- exit(1);
- }
- if (argc == 3)
- {
- if ((fp_out = fopen(argv[2], "w")) == NULL)
- {
- fprintf(stderr, "\"%s\": Can't open.\n", argv[2]);
- exit(1);
- }
- }
- else
- fp_out = stdout;
-
- while (fgets(buf, BUFSIZ, fp_in) != NULL)
- {
- cp = buf;
- if (*cp == '\n') /* blank line */
- continue;
- while (isspace(*cp))
- {
- ++cp;
- }
- if (*cp == '\0') /* empty line */
- continue;
- if (fputs(cp, fp_out) == EOF)
- {
- fprintf(stderr, "Error writing.\n");
- exit(1);
- }
- }
- if (! feof(fp_in)) /* error reading? */
- {
- fprintf(stderr, "\"%s\": Error reading.\n", argv[1]);
- exit(1);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-4. The CCOPY2.C program.
-
- Formatted File I/O with fprintf() and fscanf()
-
- CCOPY2.C didn't print error messages with printf(); instead, it used the
- file-oriented version of printf(), called fprintf(), to send error
- messages to stderr, which is always your screen. This ensures that you
- will always see error messages, even when the program is printing its
- output to a file.
-
- C's file-oriented counterparts to printf() and scanf() are called
- fprintf() and fscanf(). They are identical to their nonfile brethren, with
- one exception: Each requires a file pointer as its first argument, as
- follows:
-
- fprintf(fp_out, control, args ...);
- └─┬──┘ └───────┬────────┘
- │ └─────────────────────────── Same as printf()
- └─────────────────────────────────────────── A file pointer
-
- fscanf(fp_in, control, addresses ...);
- └─┬─┘ └─────────┬───────────┘
- │ └──────────────────────────── Same as scanf()
- └──────────────────────────────────────────── A file pointer
-
- Random Access with fseek()
-
- Sophisticated applications, such as databases, must be able to move around
- in files (recall that files are continuous streams of bytes) reading and
- writing selected portions. The fseek() function lets a program access any
- file element by determining the position of the next read or write in a
- file, as follows:
-
- fseek(fp, offset, origin)
- │ │ │ └───────────────────────────────────── From where
- └──┬──┘ └───────────────────────────────── How far to reposition
- └───────────────────────────────────────────────── File pointer
-
- The offset, in bytes, tells fseek() how far to move in the file, and it
- must be type long. The origin determines where to measure offset from; it
- can be any one of three values──the beginning, current position, or end of
- the file. (See Table 10-4.)
-
- If fseek() cannot reposition in a file, it returns the value -1L (the L is
- needed because fseek() returns the type long). If fseek() is successful,
- it returns the new position in the file, measured in bytes from the
- beginning of the file.
-
- Table 10-4 origin Positions for fseek()
- ──────────────────────────────────────────────────────────────────────────
- SEEK_SET From the beginning of the file; offset must always be
- positive.
- SEEK_CUR Relative to the current position. A negative offset moves
- toward the beginning of the file; a positive offset moves
- toward the end of the file. (You can move beyond the end of
- the file, thus enlarging the file.)
- SEEK_END From the end of the file; offset can be positive or
- negative. Movement is the same as SEEK_CUR, but relative to
- the end of the file.
- ──────────────────────────────────────────────────────────────────────────
-
- The PHONE.C program (Listing 10-5 on the following page) is a miniature
- telephone number database. When run without command-line arguments, it
- asks you for numbers to add to its database file. Run with a command-line
- argument, it searches for an entry that matches the argument and prints
- the data it finds.
-
- ──────────────────────────────────────────────────────────────────────────
- /* phone.c -- a telephone number mini-database that */
- /* demonstrates fseek() */
-
- #include <stdio.h> /* for FILE, BUFSIZ, NULL */
-
- #define MAXL (128)
- char Name[MAXL];
- char Number[MAXL];
- char File[] = "C:\\TMP\\PHONE.DB";
- int Count;
- FILE *Fp;
- int Distance = (MAXL * MAXL);
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- if (argc == 1)
- Ask();
- else
- Find(argv[1]);
-
- }
-
- Find(char *str)
- {
- int i;
-
- if ((Fp = fopen(File, "r")) == NULL)
- {
- fprintf(stderr, "\"%s\": Can't Read\n", File);
- exit (1);
- }
- if (fread(&Count, 1, sizeof(int), Fp) != sizeof(int))
- {
- fprintf(stderr, "\"%s\": Error Reading\n", File);
- exit (1);
- }
- for (i = 0; i < Count; i++)
- {
- fread(Name, 1, MAXL, Fp);
- fread(Number, 1, MAXL, Fp);
- if (ferror(Fp))
- {
- fprintf(stderr, "\"%s\": Error Reading.\n", File);
- exit (1);
- }
- if (strcmp(*str, *Name) == 0)
- {
- printf("Name: %s\n", Name);
- printf("Number: %s\n", Number);
- return;
- }
- }
- fprintf(stderr, "\"%s\": Not in database.\n", str);
- return;
- }
-
- Ask()
- {
- if ((Fp = fopen(File, "r+")) == NULL)
- Make();
- else if (fread(&Count, 1, sizeof(int), Fp) != sizeof(int))
- {
- fprintf(stderr, "\"%s\": Error Reading\n", File);
- exit (1);
- }
- printf("Name: ");
- if (gets(Name) == NULL || *Name == '\0')
- return;
- printf("Number: ");
- if (gets(Number) == NULL || *Number == '\0')
- return;
- if (fseek(Fp, (long)(Distance * Count), SEEK_CUR) != 0)
- {
- fprintf(stderr, "\"%s\": Error Seeking.\n", File);
- exit (1);
- }
- fwrite(Name, 1, MAXL, Fp);
- fwrite(Number, 1, MAXL, Fp);
- if (ferror(Fp))
- {
- fprintf(stderr, "\"%s\": Error Writing.\n", File);
- exit (1);
- }
- if (fseek(Fp, 0L, SEEK_SET) != 0)
- {
- fprintf(stderr, "\"%s\": Error Seeking.\n", File);
- exit (1);
- }
- ++Count;
- if (fwrite(&Count, 1, sizeof(int), Fp) != sizeof(int))
- {
- fprintf(stderr, "\"%s\": Error Writing\n", File);
- exit (1);
- }
- return;
- }
- Make()
- {
- if ((Fp = fopen(File, "w+")) == NULL)
- {
- fprintf(stderr, "\"%s\": Can't Create\n", File);
- exit (1);
- }
- Count = 0;
- if (fwrite(&Count, 1, sizeof(int), Fp) != sizeof(int))
- {
- fprintf(stderr, "\"%s\": Error Creating\n", File);
- exit (1);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-5. The PHONE.C program.
-
- The PHONE.C program might seem more complex than it really is. We included
- many error-checking routines to prevent the user from making careless
- errors. Note how the program checks the first character of each input line
- for a zero character (*Name == '\0'). This shows that the user pressed
- Enter without typing any information.
-
- Moving with the rewind() Function
-
- Moving to the beginning of a file (rewinding) is so common in C programs
- that the standard C Library includes a special function to perform that
- task. Called rewind(), it takes a single argument──a file pointer for the
- opened file──and moves the position of the next read or write to the
- beginning of the file. Consider the following:
-
- rewind(fp);
-
- rewind() returns no value and therefore gives no indication of failure.
- Other than this difference, however, the above rewind() is identical to
- the following fseek():
-
- fseek( fp, OL, SEEK_SET)
- │ └────────────────────── Move from beginning of file
- └────────────────────────────────── Offset must be a long
-
- Determining Position in a File with ftell()
-
- Moving through a file with fseek() often requires that you first know your
- current position in the file. When you pass the ftell() function a file
- pointer, it returns your present position in that file. That position, a
- long value, is the measure in bytes from the beginning of the file.
- Consider the following:
-
- if ((pos = ftell(fp)) == -1L)
- {
- /* can't find position */
- }
- /* current position is pos bytes from beginning */
-
- Used in that way, ftell() is identical to the following fseek() call:
-
- if ((pos = fseek(fp, 0L, SEEK_CUR)) == -1L)
- {
- /* can't find position */
- }
- /* current position is pos bytes from beginning */
-
- As you progress in learning C, you will find need for functions that we
- have not covered in our discussions. For a complete summary of top-level
- (stream) I/O routines, refer to Section 4.8 of the Microsoft QuickC
- Run-Time Library Reference.
-
-
- Mid-level (Unbuffered) File I/O
-
- Most of the top-level (buffered) stream file input/output functions have
- mid-level, unbuffered counterparts that permit direct access to disk
- files. Because they do not buffer data, they are frequently faster and
- more efficient, often allowing disk files to be read directly into a
- program's memory. (The top-level fread() function, for example, actually
- calls the mid-level read() to do its work.)
-
- One disadvantage of the unbuffered routines is that they offer only the
- most basic of services. Although these routines offer a read() and a
- write(), there are no corresponding mid-level versions of fgets(),
- fputs(), fscanf(), fprintf(), or fgetc(). Another disadvantage is that you
- cannot use unbuffered functions in the same program that uses calls to
- top-level functions. If you mix them, as shown in Figure 10-3, you risk
- losing synchronization of data. That is, if you first call fgetc(), then
- call read(), the read() will not begin with the next byte following the
- fgetc(). The fgetc() reads and buffers 512 bytes from the file, then it
- returns the first one of those buffered bytes. The call to read(),
- however, reads a single byte directly from the disk.
-
- 512-byte buffer
- ┌─┌────────────────────┐
- │ │ Now is the │ 'N'
- │ │ time... ├──────────┐
- fgetc() reads and buffers │ └───────────────────┘ │
- 512 bytes at a time ───────┤ │ │
- │ ┌─────────┴──────────┐ │
- ┌────────────────┐ │ │ Top-level │ ┌──────────────
- │ ├──────┼│ fgetc() │ │
- │ File: "Now is │ └─└────────────────────┘ │ Your
- │ the time..." │ │ program
- │ │ ┌────────────────────┐ │
- │ ├───────│ Mid-level │ └──────────────
- └────────────────┘ │ read() ├──────────┘
- └────────────────────┘ 'N'
- │
- read() reads ─────┘
- only one byte
-
- Figure 10-3. Data synchronization is lost when you mix buffered with
- unbuffered file I/O routines.
-
- Opening a File with open()
-
- Unlike fopen(), open() returns its identifying value as a simple integer.
- This value, called a "file descriptor," is later passed to all other
- mid-level routines. To use the open() function, you must specify #include
- <fcntl.h> (not <stdio.h>, as you would with fopen()), as follows:
-
- #include <fcntl.h>
- ...
- int fd;──────────────────────────────────────────────The file descriptor
- ...
- if ((fd = open(filename, oflag)) < 0)
- {
- /* handle error here */
- }
-
- The file descriptor, fd, is type int. The first argument, filename, is a
- string or the address of a string, and the second, oflag, is an int that
- supplies open() with a file activity (read, write, append, create) and a
- file mode (text or binary). The values for oflag are defined in fcntl.h,
- and their meanings are listed in Table 10-5. Note that you can combine
- oflag values by using the bitwise OR operator (|). For example, the
- following declaration opens the file TEST.EXE for reading in binary mode:
-
- fd = open("TEST.EXE", O_BINARY | O_RDONLY)
- │ │ └────────────── For reading only
- │ └─── Combined with bitwise OR operator
- └────────────────────── Open in binary mode
-
- If it fails, open() returns a negative integer value. Thus, all file
- descriptor values are greater than or equal to zero.
-
- Table 10-5 Values for oflag Declared in fcntl.h
- Value Description
- ──────────────────────────────────────────────────────────────────────────
- O_RDONLY Accesses as read-only.
- O_RDWR Accesses as read-write.
- O_WRONLY Accesses as write-only.
- O_BINARY Sets mode for a binary file.
- O_TEXT Sets mode for a text file.
- O_APPEND Opens for appending.
- O_CREAT Creates file if it doesn't exist.
- O_EXEL Returns error if file already exists.
- O_TRUNC Truncates existing file to zero
- length.
- ──────────────────────────────────────────────────────────────────────────
-
- Of the possible values for oflag, you must use one of the first three
- activities in Table 10-5, (O_RDONLY, O_RDRW, or O_WRONLY); all others in
- the table are optional and can be added using bitwise OR. Unless specified
- otherwise, the mode is set for a text file; reads and writes begin at the
- start of the file; the file is not created if it doesn't exist; and the
- file is not truncated.
-
- If you combine the O_CREAT value with O_RDWR or O_WRONLY to create a file,
- open() requires a third argument called pmode ("permissions" mode). Use
- the argument as follows:
-
- fd = open(filename, oflag, pmode);
- │ └The permissions of the newly created file
- └──────────────────(O_RDWR or O_WRONLY|O_CREAT)
-
- The possible values for pmode, listed in Table 10-6, determine whether
- the created file will be a read-only file, a write-only file, or a
- readable and writable file. (You must combine the two defined pmode values
- with a bitwise OR operator to create a readable and writable file.)
- Because pmode values are defined in sys\stat.h>, you must specify the
- following include files to create a file with open():
-
- #include <fcntl.h>──────────────────────────────────────For oflag values
- #include <sys\types.h>────────────────────────────────────────For stat.h
- #include <sys\stat.h>───────────────────────────────────For pmode values
-
- Note that #include <sys\types.h> must always precede #include <sys\stat.h>
- because the first contains the definitions needed by the second.
-
- With MS-DOS, you cannot create a file that is write-only. Because all
- files are always readable, you can omit the S_IREAD value for pmode. (If
- you do use the value, MS-DOS ignores it.)
-
- The following example is a complete call to open(), including all the
- #include directives:
-
- #include <fcntl.h>
- #include <sys\types.h>
- #include <sys\stat.h>
-
- int fd;
-
- fd = open("TEST.EXE", O_RDWR|O_BINARY|O_CREAT|O_TRUNC,S_IREAD|S_IWRITE);
-
- This example opens a file named TEST.EXE in binary mode for reading and
- writing. It creates the file if it doesn't exist, and truncates it if it
- does.
-
- Table 10-6 Values for pmode from <sys\stat.h>
- ──────────────────────────────────────────────────────────────────────────
- S_IWRITE Creates a writable file.
- S_IREAD Creates a readable file.
- ──────────────────────────────────────────────────────────────────────────
-
- Closing a File with close()
-
- Just as fclose() closes a file based on a file pointer, the unbuffered
- close() library function closes a file based on a file descriptor, as
- follows:
-
- if (close(fd) != 0)
- {
- /* handle error closing here */
- }
-
- A successfully executed close() returns a zero value; any nonzero return
- value indicates an error.
-
- When your program exits, QuickC closes all files opened with the mid-level
- open(). Because you can have only 20 files open at one time, you should
- close files inside your program. Closing a file with close() frees that
- file's file descriptor for reuse.
-
- Writing to a File with write()
-
- The write() function is used to write to files. It is simpler to use than
- the top-level fwrite() function because it requires only three arguments,
- as in the following:
-
- write(fd_out, buf, bytes)
- └──┬─┘ │ └────────────────────── Number of bytes to write
- │ └───────────────────── Where to write those bytes from
- └───────────── File descriptor for a file opened for writing
-
- The expression buf is the address in memory of the first byte that you
- want to write to the file. That address can be any address expression, but
- it is usually the address of an array. The final argument, bytes,
- represents the number of characters you want to write to the file.
-
- The write() function normally returns the number of bytes written (the
- same value as bytes). If write() fails, however, it returns a smaller or
- negative number.
-
- The SCRSAVE.C program (Listing 10-6) demonstrates one way to use open()
- and write(). It copies the contents of the text screen into a local
- buffer, which is then written to a disk file. (The program will not
- overwrite an existing file.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrsave.c -- demonstrates write() by saving the */
- /* text screen to a file */
-
- #include <stdio.h> /* for stderr */
- #include <fcntl.h> /* for O_CREAT | O_BINARY */
- #include <sys\types.h> /* for stat.h */
- #include <sys\stat.h> /* for S_IREAD | S_IWRITE */
-
- #define SCRCHARS (25 * 80)
- int Buf[SCRCHARS];
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int *cp, *ep, fname[16];
- int far *sp;
- int fd_out, bytes;
-
- if (argc != 2)
- {
- fprintf(stderr, "usage: scrsave file\n");
- exit(0);
- }
- if (strlen(argv[1]) > 8)
- {
- fprintf(stderr, "\"%s\": Filename too long.\n", argv[1]);
- exit(1);
- }
- strcpy(fname, argv[1]);
- strcat(fname, ".SCR");
- if (access(fname, 0) == 0)
- {
- fprintf(stderr, "\"%s\": Won't overwrite.\n", fname);
- exit(1);
- }
- if ((fd_out = open(fname, O_WRONLY | O_CREAT | O_BINARY,
- S_IREAD | S_IWRITE)) < 0)
- {
- fprintf(stderr, "\"%s\": Can't create.\n", fname);
- exit(1);
- }
- /* Copy the screen into a near buffer. */
- ep = &Buf[SCRCHARS - 1];
- cp = Buf;
- /* use 0xB8000000 for EGA or VGA */
- sp = (int far *)(0xB0000000);
- for (; cp < ep; ++cp, ++sp)
- *cp = *sp;
- /* Write it. */
- bytes = write(fd_out, Buf, SCRCHARS * 2);
- if (bytes != SCRCHARS * 2)
- {
- fprintf(stderr, "\"%s\": Error writing.\n", fname);
- exit(1);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-6. The SCRSAVE.C program.
-
- Note that we copy the screen rather than write it directly because write()
- expects a normal pointer, whereas accessing the screen requires a far
- pointer.
-
- Reading a File with read()
-
- Use the read() function to read from files. It is a simpler function to
- use than fread() because it takes only three arguments, as follows:
-
- read(fd_in, buf, bytes)
- │ │ └───────────────────────── Number of bytes to read
- │ └──────────────────────────── Where to place those bytes
- └─────────────── File descriptor for a file opened for reading
-
- In this example, buf is either an array or the address of allocated
- memory. Be sure it is large enough to hold the number of bytes specified
- by the argument bytes, however, because the compiler does not check this
- for you.
-
- If the call to read() is successful, it returns the same value as bytes.
- If it returns a smaller value, then that value represents the number of
- bytes left in the file. A zero return value signifies the end of the file,
- and a -1 return value shows that a read error occurred.
-
- The SCRREST.C program (Listing 10-7) reads a file, copying as much as a
- screenful of what it reads to text-screen memory. It works with any file
- type, but reading files created with SCRSAVE.C (Listing 10-6) is its most
- useful application. Before you run the program, pull down the Debug menu
- and activate Screen Swapping On.
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrrest.c -- demonstrates read() by restoring */
- /* text screen from any file */
-
- #include <stdio.h> /* for stderr */
- #include <fcntl.h> /* for O_RDONLY | O_BINARY */
-
- #define SCRCHARS (25 * 80)
- int Buf[SCRCHARS];
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int *cp, *ep;
- int far *sp;
- int fd_in, bytes;
-
- if (argc != 2)
- {
- fprintf(stderr, "usage: scrrest file.scr\n");
- exit(0);
- }
- if ((fd_in = open(argv[1], O_RDONLY | O_BINARY)) < 0)
- {
- fprintf(stderr, "\"%s\": Can't open to read.\n", argv[1]);
- exit(1);
- }
- /* Read it. */
- bytes = read(fd_in, Buf, SCRCHARS * 2);
- if (bytes < 0)
- {
- fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
- exit(1);
- }
- if (bytes == 0)
- {
- fprintf(stderr, "\"%s\": Empty File.\n", argv[1]);
- exit(1);
- }
- /* Copy the buffer to screen memory. */
- ep = &Buf[bytes / 2];
- cp = Buf;
-
- /* use 0xB8000000 for EGA or VGA */
- sp = (int far *)(0xB0000000);
- for (; cp < ep; ++cp, ++sp)
- *sp = *cp;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-7. The SCRREST.C program.
-
- Positioning with lseek()
-
- The unbuffered lseek() function lets a program position its next read or
- write to begin anywhere in a file. Almost identical to the buffered
- fseek(), lseek() takes a file descriptor as its first argument, rather
- than a file pointer. Therefore, use the lseek() function as follows:
-
- #include <io.h> /* defines lseek() */
- #include <stdio.h> /* for origin, etc. */
-
- long newpos, offset = 100L;
- int fd;
-
- newpos = lseek(fd, offset, origin);
- │ │ │ └─── From where (current, begin, or end)
- │ │ └───────────────── Move this many bytes forward
- │ └────────────────────── In this file (file descriptor)
- └───────────────────────────────────────────── New position in file
-
- In this example, fd is a file descriptor for a file previously opened with
- open(). The second argument, offset, is the number of bytes to move in the
- file and must be of the type long. If offset is negative, you move toward
- the beginning of the file. The last argument, origin, can be one of the
- three possible definitions that specify where the move begins. These
- definitions are the same as those used by fseek(), which were mentioned in
- Table 10-4 on p. 305. Also, as with fseek(), you must specify #include
- <stdio.h> to access those definitions.
-
- After a successful repositioning, lseek() returns the new position in the
- file. A return value of -1L indicates an error. (Note that lseek() returns
- the type long.)
-
- The VIEW.C program (Listing 10-8) is a simple file-viewing program that
- illustrates how to use lseek() to move through a file. Pressing + moves
- you forward in the file, pressing - moves you backward, and typing q or Q
- ends the program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* view.c -- demonstrates lseek() by displaying */
- /* a file and moving around in it */
-
- #include <fcntl.h> /* for open() */
- #include <stdio.h> /* for SEEK_CUR, etc. */
-
- #define HUNK 512
- #define MOVE 512L
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char ch, buf[HUNK];
- long position = 0L;
- int bytes, eofflag = 0, fd_in;
-
- if (argc != 2)
- {
- fprintf(stderr, "Usage: view file\n");
- exit(0);
- }
-
- if ((fd_in = open(argv[1], O_RDONLY)) < 0)
- {
- fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);
- exit(1);
- }
-
- for (;;)
- {
- bytes = read(fd_in, buf, HUNK);
- if (bytes == 0)
- {
- if (! eofflag)
- {
- fprintf(stderr, "\n<<at end of file>>\n");
- ++eofflag;
- }
- else
- exit(0);
- }
- else if (bytes < 0)
- {
- fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
- exit(1);
- }
- else
- {
- eofflag = 0;
- position = lseek(fd_in, 0L, SEEK_CUR);
- if (position == -1L)
- {
- fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);
- exit(1);
- }
- Print(buf, bytes);
- do
- {
- ch = getch();
- if (ch == 'q' || ch == 'Q')
- exit(0);
- } while (ch != '+' && ch != '-');
-
- if (ch == '-')
- {
- position = lseek(fd_in, -2 * MOVE, SEEK_CUR);
- if (position == -1L)
- {
- fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);
- exit(1);
- }
- }
- }
- }
- }
-
- Print(char *buf, int cnt)
- {
- int i;
-
- for (i = 0; i < cnt; ++i, ++buf)
- {
- if (*buf < ' ' && *buf != '\n' && *buf != '\t')
- printf("^%c", *buf + '@');
- else
- putchar(*buf);
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-8. The VIEW.C program.
-
- Finding Current Position with tell()
-
- Notice that VIEW.C finds the current position in the viewed file with the
- following:
-
- position = lseek(fd_in, 0L, SEEK_CUR);
-
- Because the need to know the current position is so common, the QuickC
- library provides the tell() function. Similar to the top-level ftell()
- routine, tell() takes a single argument, a file descriptor, and returns
- the current position in the file associated with that file descriptor.
- That position is a type long measure in bytes from the beginning of the
- file. If tell() fails for any reason, it returns a value of -1L.
-
-
- The File System
-
- Not only do programs read and write to files, they often need to manage
- the file system as a whole. By the file system, we mean the MS-DOS
- directory hierarchy, the organization of directories, and the naming of
- directories and files. For example, your program might need to create or
- remove a directory or file, or relocate in the directory hierarchy (change
- the working directory), or create unique temporary filenames. In this
- section we discuss the file system and the C Library routines that let you
- manipulate it. We also warn you of possible pitfalls and present a few
- routines that let you handle errors gracefully.
-
- Directories
-
- MS-DOS does not permit you to use fopen() or open() to open a directory.
- You can, however, create and remove directories or establish any directory
- as your current working directory. The routines for handling directories
- are listed in Table 10-7. All of the routines require that you first
- specify #include <direct.h>, which contains their declarations.
-
- The directory-handling functions chdir(), mkdir(), and rmdir() take a
- single argument──a string or the address of a string that specifies a full
- pathname (such as C:\TMP\JUNKDIR), or a directory name relative to the
- current working directory (such as JUNKDIR). All three return an integer 0
- if they are successful; otherwise, they return a -1. Consider, for
- example, the code fragment on the next page.
-
- Table 10-7 The Directory-Handling Library Functions
- ──────────────────────────────────────────────────────────────────────────
- chdir(path) Changes the current working directory to path. Returns 0 if
- successful.
- mkdir(path) Creates a new directory named path. Returns 0 if
- successful.
- rmdir(path) Removes the directory whose name is path. Returns 0 if
- successful.
- getcwd(buf, n) Places the full pathname of your current working directory
- into the char buffer buf of length n. Returns NULL if an
- error occurs.
- ──────────────────────────────────────────────────────────────────────────
-
- #include <direct.h>
-
- if (chdir("C:\\TMP") != 0)
- {
- /* chdir failed, so exit */
- }
- if (mkdir("JUNKDIR") != 0)
- {
- /* mkdir failed, so exit */
- }
- if (rmdir("JUNKDIR") != 0)
- {
- /* rmdir failed, so exit */
- }
-
- The #include <direct.h> directive provides definitions for the three
- routines that follow it. The chdir() function changes the current working
- directory to C:\TMP. (Note that in C you must use a double backslash to
- produce a single backslash.) Next, inside C:\TMP, the program uses mkdir()
- to create a new subdirectory called JUNKDIR. The final call to rmdir()
- removes that same subdirectory.
-
- The last routine in Table 10-7, getcwd() ("get current working
- directory"), takes two arguments and returns the address of a string. You
- can call this function using one of two forms. In the following form:
-
- #include <direct.h>
- #include <stdio.h> /* for NULL */
-
- char buf[512];
- if (getcwd(buf, 512) == NULL)
- {
- /* couldn't get current working directory */
- }
-
- The getcwd() function is passed the address of a char buffer, buf, into
- which it places the name of the current working directory. The length 512
- is the number of bytes in the buffer. (Remember that the buffer must be
- large enough for both the name and a terminating '\0'.)
-
- The second form for calling getcwd() is as follows:
-
- #include <direct.h>
- #include <stdio.h> /* for NULL */
-
- char *name;
- if ((name = getcwd(NULL, 0)) == NULL)
- {
- /* couldn't get current working directory */
- }
-
- This form passes getcwd(), the special zero address NULL, and a length of
- zero. This causes getcwd to use malloc() to allocate enough space for the
- name of the current working directory name (plus 1 for the terminating
- '\0'), to copy that name into the newly allocated space, and to return the
- address of that space. Both forms of getcwd() return NULL if the operation
- fails.
-
- The DIRX.C program (Listing 10-9) demonstrates all four of the
- directory-handling subroutines. It first creates a subdirectory in the
- current directory, then relocates to that subdirectory and creates a
- sub-subdirectory. Finally, it returns to the original directory and
- attempts to remove the first subdirectory it created. It fails at this
- point because it is illegal to remove a subdirectory that is not empty. If
- you run the program again, it will fail immediately──it cannot execute the
- first mkdir() because a directory with that name already exists.
-
- ──────────────────────────────────────────────────────────────────────────
- /* dirx.c -- directory examples */
-
- #include <direct.h>
- #include <stdio.h>
-
- #define SUBDIR "SUBDIR"
- #define SUBSUBDIR "SUBSUB"
-
- main()
- {
- char *current_dir;
- void Err();
-
- if ((current_dir = getcwd(NULL, 0)) == NULL)
- Err("getcwd()", "Can't get current directory.");
-
- if (mkdir(SUBDIR) != 0)
- Err(SUBSUBDIR, "Can't make directory.");
-
- if (chdir(SUBDIR) != 0)
- Err(SUBDIR, "Can't cd into directory.");
-
- if (mkdir(SUBSUBDIR) != 0)
- Err(SUBSUBDIR, "Can't make directory.");
-
- if (chdir(current_dir) != 0)
- Err(SUBDIR, "Can't cd back to.");
-
- if (rmdir(SUBDIR) != 0)
- Err(SUBDIR, "Can't remove directory.");
-
- }
-
- void Err(char *what, char *msg)
- {
- fprintf(stderr, "\"%s\": %s\n", what, msg);
- exit (1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-9. The DIRX.C program.
-
- Manipulating Files by Name
-
- Several standard C Library routines make it easy for you to remove and
- rename files and also to create unique filenames from within a program.
- These routines (listed in Table 10-8) are useful in databases, compilers,
- games, and any other program that needs to manipulate files.
-
- The routines unlink() and remove() are identical. Each takes a single
- argument──the address of a string──and erases (removes from the disk) the
- file whose name is specified in that string. The filename that you specify
- can either be a full path such as C:\TMP\JUNK, which removes the file JUNK
- from the directory C:\TMP, or it can be a relative pathname such as JUNK,
- in which case the called routine removes the file JUNK from the current
- working directory.
-
- The rename() function can do more than merely rename files. It can rename
- directories and move files from one directory to another (but not from one
- disk to another). Consider the following example, in which JUNK is a file
- and DIR1 and DIR2 are directories:
-
- rename("JUNK", "OLDJUNK");─────────────────────────────────Rename a file
- rename("DIR1\\JUNK", "DIR2\\JUNK");──────────────────────────Move a file
- rename("DIR1", "OLDDIR1");────────────────────────────Rename a directory
-
- The first line renames the file JUNK in the current working directory as
- OLDJUNK; the second line moves the file JUNK in the subdirectory DIR1 into
- the subdirectory DIR2. Note that you could have renamed JUNK during the
- move. Also remember that, in C, you must use two backslashes to produce a
- single backslash.
-
- The third line of the above example renames the directory DIR1 as OLDDIR1.
- It is important to note that directories, unlike files, cannot be moved.
-
- Table 10-8 Routines That Manipulate Files by Name
- ──────────────────────────────────────────────────────────────────────────
- unlink(path) Removes (erases) the file whose name is specified by path.
- Returns 0 if the call is successful.
- remove(path) Same as unlink().
- rename(old, Renames the file old, giving it the new name new. Also
- new) allows the renaming of directories. Files can be moved with
- this routine. Returns 0 if successful.
- mktemp(tmplt) Fills out the template tmplt with a filename that does not
- already exist.
- ──────────────────────────────────────────────────────────────────────────
-
- The mktemp() function generates a unique filename that is guaranteed not
- to exist on your disk. Use it as follows:
-
- #include <io.h> /* defines mktemp() */
- #include <stdio.h> /* for NULL */
-
- static char template[] = "C:\\TMP\\XXXXXX";
-
- if (mktemp(template) == NULL)
- {
- /* No unique name possible */
- }
-
- First we specify #include <io.h> for the definition of mktemp(). In that
- header file, mktemp() is defined as returning the address of a string
- (that is, char *). We also must use #include <stdio.h> to define NULL,
- which mktemp() returns if it fails.
-
- The template passed to mktemp() must take the form baseXXXXXX; that is, it
- may be any prefix, path, or part of a filename, ending with six X
- characters. The mktemp() function replaces the X characters with one
- alphanumeric character followed by five digits, thus forming a unique name
- (one that does not already exist on the disk), such as A00000.
-
- The FMENU.C program (Listing 10-10) uses all of these routines within a
- small file-handling menu program. It enables you to rename/move a file or
- directory, remove a file, or create a unique file. You can use FMENU.C as
- the core of your own programs that let the user control files without
- exiting to MS-DOS. Before you compile and run FMENU.C, follow the steps
- outlined in the box on page 325, "Making New Library Routines Available
- to QuickC."
-
- ──────────────────────────────────────────────────────────────────────────
- /* fmenu.c -- demonstrates file renaming, etc. */
-
- #include <direct.h>
- #include <stdio.h>
- #include <string.h>
-
- #define MAXPATH (80)
- char From_name[MAXPATH],
- To_name[MAXPATH];
-
- int Input(char *prompt, char buf[])
- {
- printf("%s: ", prompt);
- if (gets(buf) == NULL || *buf == '\0')
- return (0);
- return (1);
- }
- void Rename(void)
- {
- printf("->Rename/move\n");
- if (!Input("From", From_name)) return;
- if (!Input("To", To_name)) return;
- if (rename(From_name, To_name) != 0)
- perror("RENAME");
- else
- printf("Renamed: \"%s\" -> \"%s\"\n",
- From_name, To_name);
- }
- void Remove(void)
- {
- printf("->Remove\n");
- if (!Input("Remove", From_name)) return;
- if (!Input("Are You Sure", To_name)) return;
- if (*To_name != 'y' && *To_name != 'Y')
- return;
- if (remove(From_name) != 0)
- perror(From_name);
- else
- printf("Removed: \"%s\"\n", From_name);
- }
- void Maketemp(void)
- {
- printf("->Maketemp\n");
- if (!Input("In What Directory", From_name))
- return;
- (void)strcat(From_name, "\\XXXXXX");
- if (mktemp(From_name) == NULL)
- printf("Can't create a unique name.\n");
- else
- printf("Created: \"%s\"\n", From_name);
- }
- void Quit(void)
- {
- printf("->Quit\n");
- if (!Input("Are You Sure", From_name))
- return;
- if (*From_name != 'y' && *From_name != 'Y')
- return;
- exit(0);
- }
-
- main()
- {
- static void (*doit[])() = {Rename, Remove, Maketemp, Quit};
- int ch;
- while (1)
- {
- printf("--------------------------------------------\n");
- printf("1) Rename/move a file or rename a directory.\n");
- printf("2) Remove a file.\n");
- printf("3) Make a unique temporary file.\n");
- printf("4) Quit.\n");
- printf("--------------------------------------------\n");
- printf("Select: ");
-
- do
- {
- ch = getchar();
- } while (ch < '1' || ch > '4');
- getchar(); /* gobble trailing newline */
- printf("%c\n\n", ch);
- ch -= '1';
- doit[ch]();
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 10-10. The FMENU.C program.
-
- FMENU.C uses a technique we discussed in Chapter 8──an array of pointers
- to functions. Each menu choice corresponds to a function in that array,
- and each of those functions utilizes a different routine for file
- manipulation. Note that FMENU.C contains an error-printing routine you
- haven't seen before──perror().
-
- Printing Clear and Meaningful Diagnostics with perror()
-
- All C programs use a system-defined global variable called errno, which is
- set and cleared with each system or I/O call. A standard C Library routine
- called perror() prints an appropriate error message based on the current
- value in errno. For example, suppose an fopen() for reading a file named
- JUNK fails because the file didn't exist. In that case QuickC sets errno
- to 2, and perror(), when called as
-
- perror("JUNK");
-
- prints the following to the standard error output:
-
- JUNK: No such file or directory
-
- Using perror() helps your program generate clearer and more meaningful
- diagnostic messages. However, remember to call perror() immediately after
- a library routine returns an error. If you call another library routine
- before perror(), it might change errno and cause perror() to print an
- incorrect message. For example,
-
- if((fp = fopen(fname, "rb")) == NULL)
- {
- fprintf(stderr, "Program Aborted because\n");
- perror(fname);
- exit(1);
- }
-
- does not work because the fprintf() preceding perror() succeeds and thus
- sets errno to zero, causing perror() to print the incorrect message
- Undefined error.
-
-
- Advanced Error Handling
-
- A program that can recover from any error is called "robust." Robust
- programs are not merely carefully written programs──they are programs that
- include library routines for handling all abnormal conditions and that
- issue clear diagnostic messages to the user. Table 10-9 lists the most
- useful routines for handling abnormal conditions.
-
- Table 10-9 Abnormal-Condition Handlers and Diagnostic Routines
- ──────────────────────────────────────────────────────────────────────────
- signal() Traps errors that can terminate a program, such as Ctrl-C
- and floating-point exceptions.
- setjmp() Prepares for a jump between functions.
- longjmp() Executes a jump between functions.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- Making New Library Routines Available to QuickC
- To compile and run the FMENU.C program successfully, perform the following
- steps to add a few routines to QuickC that are not normally available.
-
- 1. Create the following program and save it using F.C as its filename.
-
- #include <direct.h>
- main()
- {
- rename();
- mktemp();
- perror();
- }
-
- 2. Exit QuickC and run the following MS-DOS command line (ignore any
- warnings about actual arguments):
-
- qcl /c /am f.c
-
- 3. Run the following command line to create an add-on Quick Library:
-
- link c:\lib\quicklib.obj+f.obj,f.qlb,,/q;
-
- where c:\lib is the location for your QuickC libraries as determined
- when you ran SETUP.
-
- 4. Rerun QuickC with the following:
-
- qc /lf
-
- For a more detailed explanation of Quick Libraries, see Chapter 12 of
- this book and Section 10.1 of your Microsoft QuickC Programmer's
- Guide.
-
- Signals
-
- Signals are conditions that cause a program to terminate prematurely. The
- signals for MS-DOS are listed in signal.h: They include Ctrl-C,
- Ctrl-Break, and floating-point errors such as division by zero. A text
- editor is an example of a program that should not terminate if one of
- these conditions occurs. The user might, for example, be editing a
- temporary copy of a file──you would want to write a user's changes to disk
- before exiting, no matter what.
-
- To handle errors such as these, use the signal() function as follows:
-
- #include <signal.h>
-
- status = signal(sig, funct);
- │ │ └────── Function address or SIG_IGN or SIG_DFL
- │ └──────────── One of the signals defined in signal.h
- └──────────────────────────────────────────────── SIG_ERR on error
-
- ──────────────────────────────────────────────────────────────────────────
- Error-handling Philosophies: BASIC vs C
- Most versions of BASIC build an error-handling mechanism into the language
- in the form of the ON ERROR ... GOTO label construct. When an error is
- encountered, control switches to the appropriate label or line number.
- Although you can turn this facility on and off, you don't have fine
- control of it.
-
- To review the situation in C, each function is responsible for reporting
- errors back to its caller. This procedure is more flexible than that used
- by BASIC, but it admits some inconsistencies. Functions that return
- pointers (such as fopen(), which returns a pointer to the file opened)
- often return a null pointer, which can be tested against the predefined
- value NULL. Other functions return the value -1 to indicate an error and
- store the specific error number in the global variable errno, using
- error-number values defined in the include file errno.h. Still other
- functions cannot return error values because no values are reserved for
- that purpose: All values might conceivably be returned by normal
- operation. You can, however, use the function ferror() to find out if any
- error occurred during input or output to a particular file. If you are not
- sure how a particular function handles error conditions, a quick way to
- find out is to use QuickC's on-line help facility discussed earlier in
- this book.
-
- In return for the greater flexibility C provides, you must explicitly test
- for an error (usually by putting the function call in an if or while
- statement or by calling ferror()) and then call any error-handling
- functions.
-
- The signal mechanism (discussed in this section) provides an additional,
- UNIX-compatible way to handle error conditions reported by the operating
- system. This mechanism is similar to the BASIC mechanism in that it
- establishes a global connection between a particular error condition and
- an error-handling function.
- ──────────────────────────────────────────────────────────────────────────
-
- We specify #include <signal.h> for definitions of signal(), its return
- value, and all of the possible values for sig. The signal() function takes
- two arguments. The first specifies the type of error, the values of which
- are listed in <signal.h> and summarized for MS-DOS in Table 10-10 on the
- following page. The second argument is the name (or address) of a function
- to be called if sig occurs, or one of the two predefined values: SIG_IGN
- (ignore this signal) or SIG_DFL (resume the default action, that is,
- terminate the program). Figure 10-4 illustrates the use of signal().
-
- #include <signal.h>
- int Sigflag = 0; /* global */
- main ()
- {
- extern int Funct ();
- 1 if (signal (SIGINT, Funct) == SIG_ERR)
- {
- printf ("Signal () failed. \n");
- exit (0);
- }
- for (;;) /* forever */
- {
- ┌─────────── printf ("Waiting For Ctrl-C\n");
- │ 5 ───2 3
- │ if (Sigflag != 0) │
- │ break 6 │
- │ } │
- │ } 7 │
- │ │
- │ Funct ()───────────────────────────────────┘
- │ {
- │ ++Sigflag;
- │ } 4
- └─────┘
-
- 1 Calling siganl() sets up the program to handle the Ctrl-C interrupt.
-
- 2 User presses Ctrl-C (or Ctrl-Break) during the perpetual for loop which
- is printing Waiting for Ctrl-C at the time.
-
- 3 Funct() is immediately called and increments Sigflag.
-
- 4 Funct() returns and...
-
- 5 the printf() statement previously interrupted is then executed (again).
-
- 6 We check Sigflag and because it was set to a nonzero value when Funct()
- was called, we exit the perpetual for loop by breaking out of it.
-
- 7 Program ends.
-
- Figure 10-4. Analysis of signal().
-
- As a rule, the signal-handling function, Funct(), should not perform any
- I/O operation. Rather than handling the error itself, it should set a
- global flag variable, then return to let the main body of the code handle
- the error. The main program stops, and the signal-handling function,
- Funct(), is called with the signal number sig as its argument. When
- Funct() finishes and returns, the main program continues from the exact
- point at which it stopped.
-
- Handling signals under MS-DOS is fairly simple because only six signals
- are defined, and only three of those actually do anything. However, if you
- move your code to XENIX or UNIX, you should be prepared to handle thirty
- or more signals, all of which can affect your program.
-
- Table 10-10 Signals Defined for MS-DOS
- ──────────────────────────────────────────────────────────────────────────
- SIGABRT Abnormal program termination. Terminates the program and
- exits with a return value of 3.
- SIGFPE Floating-point exception (such as division by zero or an
- invalid operation). Terminates the program.
- SIGINT Interrupt for keyboard. Sent when the user types the key
- sequence Ctrl-C. Terminates the program.
- ──────────────────────────────────────────────────────────────────────────
-
- Jumping Between Functions with setjmp() and longjmp()
-
- Sometimes when a signal occurs, your program might not be able to continue
- its main body of code. A signal caused by division by zero, for example,
- would result in a completely wrong answer should it continue. For
- situations such as these, when you need to jump to an earlier stage of the
- program, the standard C Library offers two functions: setjmp() and
- longjmp().
-
- setjmp() prepares the program for an eventual jump to an earlier state, as
- follows:
-
- #include <setjmp.h>
-
- jmp_buf env;
-
- if (setjmp(env) != 0)
- {
- /* We got here because of a longjmp()
- from someplace else */
- }
- /* all prepared for a longjmp() */
-
- We specify #include <setjmp.h> for the definition of jmp_buf. The variable
- env is declared as the type jump_buf and is the buffer that will hold all
- the information QuickC needs to perform a jump between functions. Next,
- the call to setjmp() prepares for an eventual call to longjmp(). The
- result of this preparation is always 0. When setjmp() returns a 0, you
- know that the program is set up for a later call to longjmp() but that the
- call has not occurred. A later call to longjmp() causes the program to
- call setjmp() again, but this time the call returns a nonzero value.
-
- Use the longjmp() routine as follows:
-
- longjmp(env, ret);
-
- The program calls longjmp() with the same env with which it called
- setjmp() earlier. The ret argument must be a nonzero number because it is
- the value returned by setjmp(). (Figure 10-5 illustrates this
- relationship.)
-
- #include <set jmp.h>
- jmp_buf Env; /* global */
- main ()
- {
- ┌───── 1 if setjmp (Env) != 0) ─────────────────────┐
- │ { │
- │ printf ("Exiting at A\n"); │
- │ exit (0); A │
- │ } │
- │ │
- └─────────── printf ("Calling Foo ()\n"); │
- ┌────────── 2 Foo(); │
- │ │
- │ printf ("Exiting at B\n"); │
- │ Exit (0); B │
- │ } │
- │ │
- └── Foo() │
- { │
- printf ("In Foo ()\n"); │
- │
- longjmp (Env, 1); 3 ─────────────────────┘
- }
-
- 1 The first call to setjmp() returns 0, so flow continues with the first
- line after the if.
-
- 2 Foo() is called.
-
- 3 In Foo(), longjmp() returns us to 1. This time, however, setjmp() retur
- 1, so we exit at A. Note that B is never reached.
-
- Figure 10-5. Analysis of setjmp() and longjmp().
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 11 Advanced Data Types
-
- Many programs, such as databases, spreadsheets, catalogs, and indexes,
- group information in such a way that each item needs to be a different C
- data type. (See Figure 11-1 on the following page.) To facilitate writing
- these programs, C offers a "structure" type──a special array-like form in
- which each element can be a different type.
-
- These kinds of programs also need to be able to store different types of
- data, at one time or another, at the same place in memory. The street
- number in Figure 11-1, for example, could be numeric, such as 212,
- requiring an integer variable, or it could be alphanumeric, such as 212B,
- requiring a string variable. The C union data type solves this problem by
- letting you store different types at the same place in memory.
-
- This chapter shows you how to program with structures and unions. It also
- discusses the less frequently used data types enum and bit fields.
- Finally, we'll detail typedef, an alternative to the #define preprocessor
- directive that lets you create new types from old.
-
- ┌──────────────────────────────────────────────────────────┐
- │ char char char │
- │ NAME _______________ _______________ _______________ │
- │ first last middle │
- │ │
- │ long char │
- │ ADDRESS ___________ _________________________________ │
- │ street_num street │
- │ │
- │ char char long │
- │ ________________ _____________ _____________ │
- │ city state zip │
- │ │
- │ int long │
- │ PHONE (______) ___________________ │
- │ area_code phone │
- │ │
- └──────────────────────────────────────────────────────────┘
-
- Figure 11-1. To enter the information on an address/phone index card into
- a computer, you need to use different data types organized as a single
- conceptual unit.
-
-
- Structure──An Array of Different Types
-
- An obvious limitation of arrays is that the variables in a single array
- must all be of the same type (all char, all int, and so on). However, you
- will frequently need to group variables of different types together so
- that you can manipulate them as a single conceptual unit. The information
- on the index card in Figure 11-1 is a good example. Because all of the
- different "types" of information actually relate to a single person, it is
- more convenient and conceptually sound to place all of that information in
- a single array. Unfortunately, arrays cannot handle different data types.
- To group strings and integers, for example, you must use a structure,
- which can hold any mixture of types, including arrays, pointers, and
- integers.
-
- Think of a structure as a special kind of array. However, whereas the
- variables in an array are called elements and are referenced by an offset,
- the variables in a structure are called "members" and are referenced by
- name.
-
- You declare a structure with the C keyword struct. The first step in
- setting up a structure is to declare a pattern, or template, for the
- variables it will contain and to give that pattern a name. A pattern for
- the structure that contains the address-book information in Figure 11-1,
- for example, appears at the top of the next page.
-
- To declare a structure pattern, follow the keyword struct with the name of
- the pattern (cardstruct). Next, list the variables, or members, of the
- structure between a set of braces. Note that although this list resembles
- a list of variable declarations, you are not allocating memory for storage
- of the structure's members──you are merely creating a template that
- reserves those names for future use.
-
- ┌───────────────────────────────────────────────────────── Keyword
- │ ┌─────────────────────────────────────────── Name of pattern
- │ │ ┌──────────────────────────── Variables between braces
- struct cardstruct {
- char *first, *last, *middle;────────────────────── Member list start
- long street_num;
- char *street, *city, *state;
- long zip;
- int area_code;
- long phone;───────────────────────────────────────── Member list end
- };
- │└──────────────────────────────────────────────────── Closing semicolon
- └────────────────────────────────────────────── Variables between braces
-
- Structure Variables
-
- To reserve memory for a structure's members, you must declare structure
- variables that follow the pattern you defined. The following declaration
- sets aside memory for two structure variables (card1 and card2) using the
- above cardstruct pattern:
-
- struct cardstruct card1, card2;
-
- This declaration starts with the keyword struct, as did the pattern, but
- this time struct is followed by the name of a previously declared pattern
- and then by the names of the structure variables. Remember, you manipulate
- card1 and card2 in the program──the pattern cardstruct merely declares new
- structures. This statement reserves memory (allocates enough storage) for
- the predefined members of those two structure variables, as shown in
- Figure 11-2.
-
- struct cardstruct card 1, card 2;
- └────┘ └────┘
- │ │
- ┌────┘ └─────────────────┐
- ┌───────┬──────┐ ┌──────┬───────┐
- │ *first │ │ *first │
- ├───────┼───────┤ ├───────┼───────┤
- │ *last │ │ *last │
- ├───────┼───────┤ ├───────┼───────┤
- │ *middle │ │ *middle │
- ├───────┼───────┼───────┬───────┐ ├───────┼───────┼───────┬───────┐
- │ street_num │ │ street_num │
- ├───────┼───────┼───────┴───────┘ ├───────┼───────┼───────┴───────┘
- │ *street │ │ *street │
- ├───────┼───────┤ ├───────┼───────┤
- │ *city │ │ *city │
- ├───────┼───────┤ ├───────┼───────┤
- │ *state │ │ *state │
- ├───────┼───────┼───────┬───────┐ ├───────┼───────┼───────┬───────┐
- │ zip │ │ zip │
- ├───────┼───────┼───────┴───────┘ ├───────┼───────┼───────┴───────┘
- │ area_code │ │ area_code │
- ├───────┼───────┼───────┬───────┐ ├───────┼───────┼───────┬───────┐
- │ phone │ │ phone │
- └───────┴───────┴───────┴───────┘ └───────┴───────┴───────┴───────┘
- │1 byte │
- │ │
-
- Figure 11-2. Declaring structure variables sets aside enough memory for
- the variables defined by cardstruct.
-
- Accessing Structure Members
-
- To access a member of a structure in C, specify the name of the structure
- variable that contains the member, then the . (pronounced "dot") operator,
- then the name of the member you need to access, as in the following
- example:
-
- printf("%d\n", card1.area_code);
- │ │ └──────────────────── Name of member of structure
- │ └─────────────────────────────────────────── A "dot"
- └───────────────────────────────────── Name of structure
-
- This expression prints the value of the integer area_code, one of the
- member variables in the structure variable named card1.
-
- You can manipulate members of structures as you would any C variables: You
- can assign values to them, use them in computations, and so on. The only
- difference is that you must reference each member variable with the name
- of its structure (card1 or card2, for example), a dot, and then its own
- name.
-
- The CARD.C program (Listing 11-1) demonstrates structures by prompting
- you to fill out information for a fictional address-book card; then it
- prints out the information you entered.
-
- ──────────────────────────────────────────────────────────────────────────
- /* card.c -- demonstrates how to declare structures */
- /* and how to use structure members */
-
- #include <stdio.h> /* for NULL and stdin */
- #include <string.h> /* for strdup() */
-
- #define MAXN 79
-
- struct cardstruct { /* global pattern */
- char *first, *last, *middle;
- long street_num;
- char *street, *city, *state;
- long zip;
- int area_code;
- long phone;
- };
-
- main()
- {
- char *Str_Input();
- long Lint_Input();
- struct cardstruct card1;
-
- card1.first = Str_Input("First Name");
- card1.last = Str_Input("Last Name");
- card1.middle = Str_Input("Middle Name");
- card1.street_num = Lint_Input("Street Number");
- card1.street = Str_Input("Street Name");
- card1.city = Str_Input("City");
- card1.state = Str_Input("State");
- card1.zip = Lint_Input("Zip Code");
- card1.area_code = (int)Lint_Input("Area Code");
- card1.phone = Lint_Input("Phone Number");
-
- printf("\n\n");
- printf("%s %s %s\n", card1.first, card1.middle,
- card1.last);
- printf("%ld %s, %s, %s %ld\n", card1.street_num,
- card1.street, card1.city, card1.state,
- card1.zip);
- printf("(%d) %ld\n", card1.area_code, card1.phone);
- }
-
- char *Str_Input(char *prompt)
- {
- char buf[MAXN+1], *ptr;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- buf[strlen(buf) - 1] = '\0'; /* strip '\n' */
- if (strlen(buf) == 0)
- exit(0);
- if ((ptr = strdup(buf)) == NULL)
- exit(0);
- return (ptr);
- }
-
- long Lint_Input(char *prompt)
- {
- char buf[MAXN + 1];
- long num;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- if (sscanf(buf, "%ld", &num) != 1)
- exit(0);
- return (num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-1. The CARD.C program.
-
- CARD.C uses the members of the structure card1 exactly as it would
- ordinary variables. It assigns values to them with the = operator and
- passes those values to printf() to be printed.
-
- Shorthand Structure Declarations
-
- As a bit of shorthand, you can declare structure patterns and allocate
- storage for structure variables in a single statement, as follows:
-
- When you allocate storage for structure variables as a part of the
- declaration, the name of the pattern becomes optional and you can omit it:
-
- ┌───────────────────────────────────────── Name of pattern omitted
- struct {
- /* list of members here */
- } card1, card2;
- └──────┴─────────────────────────────── Structures allocated storage
-
- You must use the pattern name, however, if you intend to declare
- additional structure variables using that pattern name later in the
- program:
-
- struct cardstruct card3, card4;
-
- Structure Assignment
-
- When you declare structure variables with the same pattern, you can assign
- one to another, as follows:
-
- card2 = card1;
-
- This assignment copies the values of all card1 members into the
- corresponding members of card2.
-
- If you try to assign one structure variable to another when those
- structures are declared with different pattern names (even if the members
- of both are identical), QuickC returns the following error message:
-
- error C2115:
- '=' : incompatible types
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- One way to make a program such as CARD.C more robust, or user friendly, is
- to enable the program to handle telephone numbers that contain a hyphen
- (-) character. Consider the necessary revisions to CARD.C. Why is this
- enhancement difficult in a program that uses scanf() to parse user input?
- ──────────────────────────────────────────────────────────────────────────
-
- If you need to assign values from one structure to another of a different
- pattern, you must assign the members individually. For example, if card1
- uses the pattern cardstruct and memo uses another pattern, memostruct, you
- could assign the members of one to the other in the following way:
-
- card1.first = memo.first_name;
- card1.last = memo.last_name;
- card1.middle = memo.mid_name;
-
- Passing Structures to Functions
-
- Passing a structure to a function passes a copy of its members. This
- prevents the called function from changing the original structure. To pass
- a structure to a function, simply state the structure's name, as follows:
-
- Showcard(card1);
-
- In this example, a copy of the structure variable card1──including copies
- of all its members──is passed to the function Showcard(). Remember,
- structures differ from arrays in this regard: When you pass a structure to
- a function, you pass only a copy of that structure; when you pass an
- array, you pass the address of that array, thus allowing the original
- array to be changed by the calling function.
-
- In the receiving function (such as Showcard() below), you must declare the
- type of the received argument with struct and the pattern name
- (cardstruct). This tells the compiler that Showcard() is receiving a
- structure as its argument, and that the pattern for that structure is
- named cardstruct:
-
- ┌────────────────────────── Receive copy of
- Showcard(struct cardstruct card)
- { └────────────────── structure based on this pattern
- /* body of function */
- }
-
- The CARD2.C program (Listing 11-2 beginning on the following page) is a
- revised CARD.C program. In it, we fill out two cards and then print those
- cards using the Showcard() function.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- There are two drawbacks to passing structures to functions. First, not all
- compilers support the passing of structures, so if portability is
- important, you might want to avoid this technique. Second, as structures
- get larger, QuickC takes longer to copy them for each function call. This
- can become very time-consuming if it occurs in the middle of a loop. Thus,
- to speed the processing of your programs and enable the original to be
- changed, we advise you to use pointers to structures.
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* card2.c -- demonstrates structure assignment and */
- /* how to pass a structure to a function */
-
- #include <stdio.h> /* for NULL and stdin */
- #include <string.h> /* for strdup() */
-
- #define MAXN 79
-
- struct cardstruct { /* global pattern */
- char *first, *last, *middle;
- long street_num;
- char *street, *city, *state;
- long zip;
- int area_code;
- long phone;
- };
-
- main()
- {
- int i;
- char *Str_Input();
- long Lint_Input();
- struct cardstruct card1, card2;
-
- for (i = 0; i < 2; i++) /* do twice */
- {
- printf("\nCard %d:\n\n", i + 1);
-
- card1.first = Str_Input("First Name");
- card1.last = Str_Input("Last Name");
- card1.middle = Str_Input("Middle Name");
- card1.street_num = Lint_Input("Street Number");
- card1.street = Str_Input("Street Name");
- card1.city = Str_Input("City");
- card1.state = Str_Input("State");
- card1.zip = Lint_Input("Zip Code");
- card1.area_code = (int)Lint_Input("Area Code");
- card1.phone = Lint_Input("Phone Number");
-
- if (i == 0)
- card2 = card1; /* structure assignment */
- }
- Showcard(card2);
- Showcard(card1);
-
- }
- Showcard(struct cardstruct card)
- {
- printf("\n\n");
-
- printf("%s %s %s\n", card.first, card.middle, card.last);
- printf("%ld %s, %s, %s %ld\n", card.street_num,
- card.street, card.city, card.state, card.zip);
- printf("(%d) %ld\n", card.area_code, card.phone);
- }
-
- char *Str_Input(char *prompt)
- {
- char buf[MAXN + 1], *ptr;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
- if (strlen(buf) == 0)
- exit(0);
- if ((ptr = strdup(buf)) == NULL)
- exit(0);
- return (ptr);
- }
-
- long Lint_Input(char *prompt)
- {
- char buf[MAXN + 1];
- long num;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- if (sscanf(buf, "%ld", &num) != 1)
- exit(0);
- return (num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-2. The CARD2.C program.
-
- In CARD2.C, Showcard() receives a copy of card1 from main(). Note that the
- members of the Showcard() structure, card, are accessed with the same
- "dot" notation as the originals in main().
-
- Pointers to Structures
-
- Passing a pointer to a structure, rather than a copy of a structure, to a
- function has two advantages. It permits the function to modify the members
- of the original structure. Also, far fewer bytes must be copied when a
- pointer is passed than are copied when a structure is passed──the result
- is faster executing code.
-
- You declare a pointer to a structure the same way that you declare a
- pointer to any other type──by preceding its name with a *, as follows:
-
- struct cardstruct *cardptr;
- │ └────── A pointer to a structure of pattern cardstruct
- └─────────────────────────────────────────── Name of pattern
-
- This example declares a pointer variable, cardptr, whose contents will be
- an address. The struct cardstruct in the declaration tells the compiler
- that cardptr will point to a structure variable based on the pattern
- cardstruct. (See Figure 11-3.)
-
- Before you can use the pointer cardptr, it must be given a value. Because
- it is a pointer to a structure, we will assign it the address of the
- structure variable card1 from CARD.C:
-
- cardptr = &card1;
-
- The & operator fetches the address of a structure. (Note that this differs
- from arrays, where the array name itself yields the address.) To assign
- the address of a structure variable to a pointer to a structure, declare
- both the pointer and the structure with the same pattern name. If you
- declare them with different pattern names, QuickC returns the following
- warning message:
-
- warning C4049:
- '=' : indirection to different types
-
- The & operator can also pass the address of a structure directly to a
- function:
-
- Enter(&card1);
- └───────────────────────────── Pass address of card1 to a function
-
- struct cardstruct *cardptr;
- └───────┘
- │
- ┌───────┬──────┐ Points to ┌───────┬───────┐
- │ address │────────────│ *first │
- └───────┴───────┘ ├───────┼───────┤
- │ *last │
- ├───────┼───────┤
- │ *middle │
- ├───────┼───────┼───────┬───────┐
- │ street_num │
- ├───────┼───────┼───────┴───────┘
- │ *street │
- ├───────┼───────┤
- │ *city │
- ├───────┼───────┤
- │ *state │
- ├───────┼───────┼───────┬───────┐
- │ zip │
- ├───────┼───────┼───────┴───────┘
- │ area_code │
- ├───────┼───────┼───────┬───────┐
- │ phone │
- └───────┴───────┴───────┴───────┘
- Structure of pattern cardstruct
-
- Figure 11-3. A pointer to a structure contains the address of a structure
- variable.
-
- We also must declare the received argument for the Enter() function as a
- pointer to a structure, as follows:
-
- Enter(struct cardstruct *item)
- { └───────────────── Pointer to receive an address
-
- Again, be sure that you declare the same pattern name for both the passed
- and the received structures.
-
- Accessing Structure Members with a Pointer
-
- To access the members of a structure with a pointer, you need to use a new
- symbol, ->. Called "to", -> is actually two characters──a "minus"
- character followed by a "greater than" character. The following code
- illustrates the use of the -> operator. In it, the pointer cardptr
- accesses the phone member of the structure card1:
-
- struct cardstruct {──────────────────────────────────── Define a pattern
- char *first, *last, *middle;
- int age;
- };
-
- struct cardstruct card1, *cardptr;
- │ │ └──────────────────── Declare a pointer and
- │ └───────────────────────────── a structure variable
- └────────────────────────────────────── both of that pattern
- cardptr = &card1;───────────────────── Assign card1's address to cardptr
- cardptr->phone = 5551212;──────────── Access member of card1 via cardptr
- └────────────────────────────── Points "to" member phone of card1
-
- The CARD3.C program (Listing 11-3) is another revision of CARD.C. This
- modification has Showcard() receiving the address of a structure. Rather
- than printing a copy, it prints the original via a pointer to the
- structure.
-
- ──────────────────────────────────────────────────────────────────────────
- /* card3.c -- demonstrates pointers to structures */
-
- #include <stdio.h> /* for NULL and stdin */
- #include <string.h> /* for strdup() */
-
- #define MAXN 79
-
- struct cardstruct { /* global pattern */
- char *first, *last, *middle;
- long street_num;
- char *street, *city, *state;
- long zip;
- int area_code;
- long phone;
- };
- main()
- {
- int i;
- char *Str_Input();
- long Lint_Input();
- struct cardstruct card1, card2;
-
- for (i = 0; i < 2; i++) /* do twice */
- {
- printf("\nCard %d:\n\n", i + 1);
-
- card1.first = Str_Input("First Name");
- card1.last = Str_Input("Last Name");
- card1.middle = Str_Input("Middle Name");
- card1.street_num = Lint_Input("Street Number");
- card1.street = Str_Input("Street Name");
- card1.city = Str_Input("City");
- card1.state = Str_Input("State");
- card1.zip = Lint_Input("Zip Code");
- card1.area_code = (int)Lint_Input("Area Code");
- card1.phone = Lint_Input("Phone Number");
-
- if (i == 0)
- card2 = card1;
- }
- Showcard(&card2); /* pass addresses of structures */
- Showcard(&card1);
-
- return (0);
- }
-
- Showcard(cardptr)
- struct cardstruct *cardptr; /* pointer receives an address */
- {
- printf("\n\n");
-
- printf("%s %s %s\n", cardptr->first, cardptr->middle,
- cardptr->last);
- printf("%ld %s, %s, %s %ld\n", cardptr->street_num,
- cardptr->street, cardptr->city, cardptr->state,
- cardptr->zip );
- printf("(%d) %ld\n", cardptr->area_code, cardptr->phone);
- }
-
- char *Str_Input(char *prompt)
- {
- char buf[MAXN + 1], *ptr;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL) exit(0);
- buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
- if (strlen(buf) == 0)
- exit(0);
- if ((ptr = strdup(buf)) == NULL)
- exit(0);
- return (ptr);
- }
-
- long Lint_Input(char *prompt)
- {
- char buf[MAXN + 1];
- long num;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- if (sscanf(buf, "%ld", &num) != 1)
- exit(0);
- return (num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-3. The CARD3.C program.
-
- Arrays of Structures
-
- Structures can be organized in arrays like any other type of variable. You
- declare an array of structures as follows:
-
- struct cardstruct {
- /* members declared here */
- } cards[3];
- └───────────────────────────── An array of three structures
-
- This example declares an array of three structures (cards[3]) and defines
- the pattern cardstruct at the same time. If you had already defined the
- pattern, you could declare the same array as follows:
-
- struct cardstruct cards[3];
-
- Use an array of structures the same way you use any other array. For
- example, the following statement prints the first member of the second
- card:
-
- printf("%s", cards[1].first);
-
- The expression cards[1] accesses the second structure of the array, and
- the .first yields the member named first from that structure.
-
- To pass the address of one of the structures in the array cards, use the &
- operator followed by the structure's offset in square brackets.
-
- ┌──────────────────────────────────────────────────────────── Address of
- &cards[i]
- └─────────────────── the <FI>i<FS>th structure in the array of str
-
- The ROLO.C program (Listing 11-4) is a complete address book built from
- the earlier CARD.C program. It asks you to fill out the three cards in our
- array of structures. Then it prints out the information in those cards. By
- combining this use of structures with the file-handling routines of
- PHONE.C (from the previous chapter), you have the basis for a truly
- useful phone-index program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* rolo.c -- demonstrates pointers to structures */
-
- #include <stdio.h> /* for NULL and stdin */
- #include <string.h> /* for strdup() */
-
- #define MAXN 79
- #define MAXCARDS 3
-
- struct cardstruct { /* global pattern */
- char first[MAXN],
- last[MAXN],
- middle[MAXN];
- unsigned long street_no;
- char street[MAXN],
- city[MAXN],
- state[MAXN];
- unsigned long zip;
- unsigned int area;
- unsigned long phone;
- };
- struct cardstruct cards[MAXCARDS];
-
- main()
- {
- int i;
-
- for (i = 0; i < MAXCARDS; ++i)
- {
- printf("\n<card %d of %d>\n", i + 1, MAXCARDS);
- Input(&cards[i]);
- }
- for (i = 0; i < MAXCARDS; ++i)
- {
- printf("\n<%d> ", i + 1);
- Showcard(&cards[i]);
- }
- }
- Input(struct cardstruct *cardp)
- {
- char *Str_Input();
- long Lint_Input();
-
- strcpy(cardp->first,Str_Input("First Name"));
- strcpy(cardp->last,Str_Input("Last Name"));
- strcpy(cardp->middle,Str_Input("Middle Name"));
- cardp->street_no = Lint_Input("Street Number");
- strcpy(cardp->street,Str_Input("Street"));
- strcpy(cardp->city,Str_Input("City"));
- strcpy(cardp->state,Str_Input("State"));
- cardp->zip = Lint_Input("Zip Code");
- cardp->area = (int)Lint_Input("Area Code");
- cardp->phone = Lint_Input("Phone Number");
- }
-
- char *Str_Input(char *prompt)
- {
- char buf[MAXN + 1], *ptr;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
- if (strlen(buf) == 0)
- exit(0);
- if ((ptr = strdup(buf)) == NULL)
- exit(0);
- return (ptr);
- }
-
- long Lint_Input(char *prompt)
- {
- char buf[MAXN + 1];
- long num;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- if (sscanf(buf, "%ld", &num) != 1)
- exit(0);
- return (num);
- }
-
- Showcard(struct cardstruct *cardptr)
- {
- printf("\n\n");
- printf("%s %s %s\n", cardptr->first, cardptr->middle,
- cardptr->last);
- printf("%ld %s, %s, %s %ld\n", cardptr->street_no,
- cardptr->street, cardptr->city, cardptr->state,
- cardptr->zip);
- printf("(%d) %ld\n", cardptr->area, cardptr->phone);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-4. The ROLO.C program.
-
- ROLO.C uses an array of three structures. Notice that the cards[] array
- consists of structures that themselves contain arrays.
-
- Arrays of Pointers to Structures
-
- Not only can you create arrays of structures, you can also create arrays
- of pointers to structures. These arrays of pointers offer the advantage of
- increased efficiency. For example, when sorting, it is faster to swap two
- pointers than it is to exchange the vastly greater number of bytes of the
- structures themselves.
-
- You declare an array of pointers to structures as follows:
-
- struct cardstruct *cardps[3]
-
- This example declares an array of three pointers in which each pointer
- points to a structure of the pattern cardstruct. Figure 11-4 illustrates
- such an arrangement.
-
- You can initialize cardps[] (an array of pointers to structures) to
- contain the address of the corresponding elements in the array of
- structures cards[] as follows:
-
- cardps[0] = &cards[0];
- cardps[1] = &cards[1];
- cardps[2] = &cards[2];
-
- This lets you use the -> operator to indirectly reference the members of
- each structure in cards[] with the pointers in cardps[]. For example, the
- street member of the second structure of the array of structures cards[]
- can be indirectly referenced through the array of pointers to structures
- in cardps[], as follows:
-
- strcpy(cardps[1]->street, "Any St.");
- └───────────── Points "to" the member street of cards[1]
-
- Structure Recursion and Linked Lists
-
- Structures are so versatile that they can hold every possible type in C,
- including themselves. This remarkable ability to be self-inclusive opens
- whole new sets of programming possibilities. The most common of these is
- the technique shown in Figure 11-5 (on p. 348) that uses "linked lists."
-
- ┌─────────────────────┐
- ┌─│ first [] │
- │ ├─────────────────────┤
- │ │ last [] │
- │ ├─────────────────────┤
- │ │ middle [] │
- │ ├───────────┬─────────┘
- │ │street_num │
- │ ├───────────┴─────────┐
- │ │ street [] │
- │ ├─────────────────────┤
- │ │ city [] │
- │ ├─────────────────────┤
- │ │ state [] │
- │ ├───────────┬─────────┘
- │ │ zip │
- │ ├──────┬────┘
- │ │area │
- ┌────┬────┐ │ ├──────┴────┐
- │ address │───┘ │ phone │
- ├────┼────┤ ├───────────┴─────────┐
- │ address │─────│ first [] │
- ├────┼────┤ ├─────────────────────┤
- │ address │───┐ │ last [] │
- └────┴────┘ │ ├─────────────────────┤
- cardps [3] │ │ middle [] │
- │ ├───────────┬─────────┘
- │ │street_num │
- │ ├───────────┴─────────┐
- │ │ street [] │
- │ ├─────────────────────┤
- │ │ city [] │
- │ ├─────────────────────┤
- │ │ state [] │
- │ ├───────────┬─────────┘
- │ │ zip │
- │ ├──────┬────┘
- │ │area │
- │ ├──────┴────┐
- │ │ phone │
- │ ├───────────┴─────────┐
- └─│ first [] │
- ├─────────────────────┤
- │ last [] │
- ├─────────────────────┤
- │ middle [] │
- ├───────────┬─────────┘
- │street_num │
- ├───────────┴─────────┐
- │ street [] │
- ├─────────────────────┤
- │ city [] │
- ├─────────────────────┤
- │ state [] │
- ├───────────┬─────────┘
- │ zip │
- ├──────┬────┘
- │area │
- ├──────┴────┐
- │ phone │
- └───────────┘
- cards [3]
-
- Figure 11-4. An array of pointers to structures. Each element points to a
- structure in an array of structures.
-
- ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
- │ │ ┌─│ │ ┌─│ │
- ├─────────────────┤ │ ├─────────────────┤ │ ├─────────────────┤
- ├─────────────────┤ │ ├─────────────────┤ │ ├─────────────────┤
- ├─────────┬───────┘ │ ├─────────┬───────┘ │ ├─────────┬───────┘
- ├─────────┴───────┐ │ ├─────────┴───────┐ │ ├─────────┴───────┐
- ├─────────────────┤ │ ├─────────────────┤ │ ├─────────────────┤
- ├─────────────────┤ │ ├─────────────────┤ │ ├─────────────────┤
- ├─────────┬───────┘ │ ├─────────┬───────┘ │ ├─────────┬───────┘
- ├────┬────┘ │ ├────┬────┘ │ ├────┬────┘
- ├────┴────────────┐ │ ├────┴────────────┐ │ ├────┴────────────┐
- ├─────────────────┤ │ ├─────────────────┤ │ ├─────────────────┤
- │ nextcard ├──┘ │ nextcard ├──┘ │ nextcard │
- └─────────────────┘ └─────────────────┘ └─────────────────┘
-
- Figure 11-5. In a linked list, each structure contains a pointer to
- another structure of the same type.
-
- A linked list is an arrangement of structures in which each structure
- contains a pointer to (the address of) its neighbor. For example, to
- declare such a linked list in ROLO.C, we must modify the structure pattern
- as follows:
-
- struct cardstruct {
- char first[MAXN],
- last[MAXN],
- middle[MAXN];
- unsigned long street_no;
- char street[MAXN],
- city[MAXN],
- state[MAXN];
- unsigned long zip;
- unsigned int area;
- unsigned long phone;
- struct cardstruct *nextcard;────────────────────────────────── Added
- └ Pointer to another structure of this same pattern
-
- The new member *nextcard is a pointer to a structure, but it points to a
- structure of its own pattern. By declaring several structures of this
- pattern with
-
- struct cardstruct card1, card2, card3, card4;
-
- and then initializing the nextcard member of each to contain the address
- of its neighbor, you create a linked list:
-
- card1.nextcard = &card2;
- card2.nextcard = &card3;
- card3.nextcard = &card4;
-
- The ROLO2.C program (Listing 11-5) uses malloc() to build a linked list
- of structures while the program is running. Using this approach, we can
- add as many cards to our address book as we want (subject to the limit of
- the computer's memory).
-
- ──────────────────────────────────────────────────────────────────────────
- /* rolo2.c -- demonstrates a linked list */
-
- #include <stdio.h> /* for NULL and stdin */
- #include <string.h> /* for strdup() */
- #include <malloc.h> /* for malloc() */
-
- #define MAXN 79
-
- struct cardstruct { /* global pattern */
- char first[MAXN],
- last[MAXN],
- middle[MAXN];
- unsigned long street_no;
- char street[MAXN],
- city[MAXN],
- state[MAXN];
- unsigned long zip;
- unsigned int area;
- unsigned long phone;
- struct cardstruct *nextcard;
- };
-
- main()
- {
- int i;
- struct cardstruct card, *first, *current;
-
- first = (struct cardstruct *)malloc(sizeof(struct cardstruct));
- if (first == NULL)
- exit(1);
- if (Input(&card) != 0)
- exit(1);
- *first = card;
- current = first;
-
- while (Input(&card) == 0)
- {
- current->nextcard =
- (struct cardstruct *)malloc(sizeof(struct cardstruct));
- if (current->nextcard == NULL)
- exit(1);
- current = current->nextcard;
- *current = card;
- }
- current->nextcard = NULL;
-
- Dumplist(first);
- }
- Dumplist(struct cardstruct *head)
- {
- do
- {
- Showcard(head);
- } while ((head = head->nextcard) != NULL);
- }
-
- Showcard(struct cardstruct *cardptr)
- {
- printf("\n\n");
-
- printf("%s %s %s\n", cardptr->first, cardptr->middle,
- cardptr->last);
- printf("%ld %s, %s, %s %ld\n", cardptr->street_no,
- cardptr->street, cardptr->city, cardptr->state,
- cardptr->zip );
- printf("(%d) %ld\n", cardptr->area, cardptr->phone);
- }
-
- Input(struct cardstruct *cardp)
- {
- char *Str_Input();
- long Lint_Input();
-
- printf("\n<new card> (Empty first name Quits)\n");
- strcpy(cardp->first,Str_Input("First Name"));
- if (*(cardp->first) == '\0')
- return (1);
- strcpy(cardp->last,Str_Input("Last Name"));
- strcpy(cardp->middle,Str_Input("Middle Name"));
- cardp->street_no = Lint_Input("Street Number");
- strcpy(cardp->street,Str_Input("Street"));
- strcpy(cardp->city,Str_Input("City"));
- strcpy(cardp->state,Str_Input("State"));
- cardp->zip = Lint_Input("Zip Code");
- cardp->area = (int)Lint_Input("Area Code");
- cardp->phone = Lint_Input("Phone Number");
- return (0);
- }
-
- char *Str_Input(char *prompt)
- {
- char buf[MAXN + 1], *ptr;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
- if ((ptr = strdup(buf)) == NULL)
- exit(0); return (ptr);
- }
-
- long Lint_Input(char *prompt)
- {
- char buf[MAXN + 1];
- long num;
-
- printf("%s: ", prompt);
- if (fgets(buf, MAXN, stdin) == NULL)
- exit(0);
- if (sscanf(buf, "%ld", &num) != 1)
- num = 0;
- return (num);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-5. The ROLO2.C program.
-
- Notice that the last structure in the list always has its nextcard member
- set to NULL. That's how the program marks the end of the linked list.
-
- This program also illustrates two other interesting properties of
- structures. First, when you apply the sizeof operator to a structure or to
- a structure's pattern, it yields the total number of bytes for all the
- members of the structure:
-
- malloc(sizeof(struct cardstruct));
-
- Second, we had to type cast the value returned by malloc() to a type
- appropriate for the pointer to which the value is assigned:
-
- first = (struct cardstruct *)malloc(sizeof(struct cardstruct));
- └────────┬────────┘
- └─────────────── Type cast to a pointer to a structure
-
- Note that you must use the structure pattern name in the type cast, not
- the structure variable name. Had we omitted the type cast, QuickC would
- complain with:
-
- Warning C4049:
- '=' indirection to different types
-
- Initializing Structures with Starting Values
-
- As in arrays, you can initialize structures that are static or global when
- you declare them. The type of the initializing value must, of course,
- match the type of the corresponding member. An attempt to initialize with
- the wrong type will yield the following QuickC warning:
-
- Warning C4047:
- "initializing": different levels of indirection
-
- The following structure is declared correctly:
-
- static struct cardstruct card = {
- "Bob",─────────────────────────────────────────────Member first
- "Roberts",──────────────────────────────────────────Member last
- "Mason",──────────────────────────────────────────Member middle
- 42─────────────────────────────────────────────Member street_no
- "Willow Way",─────────────────────────────────────Member street
- "Tonopah",──────────────────────────────────────────Member city
- "Nevada",──────────────────────────────────────────Member state
- 84521L,──────────────────────────────────────────────Member zip
- 916,────────────────────────────────────────────────Member area
- 5551212L───────────────────────────────────────────Member phone
- };
-
- As with arrays, if you specify fewer initializers than members, QuickC
- gives the trailing uninitialized members the default value of zero.
-
-
- Union──Multiple Types in the Same Space
-
- You can think of a "union" as the opposite of a structure. While struct is
- a collection of many types, each with its own location in memory, a union
- is a collection of many types that all share the same location in memory.
- Thus, a union can contain different types at various times, but it can
- contain only a single value of a single type at any given time.
-
- Although its uses are limited, a union is a blessing when you do encounter
- a need for one. For example, consider writing a function that needs to
- print either an int or a float, yet doesn't know ahead of time what type
- it will receive as its argument. Before we can show you how to write such
- a function, however, we need to cover the basics of declaring and using
- unions.
-
- You declare a union as you would a structure, except you use the keyword
- union instead of struct:
-
- ┌────────────────────────────────────────────── Name of pattern
- union twotype {
- float ftype;──────────────────────────────────────────────── Members
- int itype;
- } one_of_many ;
- └──────────────────────── Name of a union variable of pattern twotype
-
- This example tells the compiler to reserve memory for the variable
- one_of_many, which will hold either a float or an int. Because the float
- is larger, union reserves four bytes──enough space to hold either type.
-
- As a general rule, you should place the largest member first in a union
- declaration. Some compilers allocate memory based only on the first
- member, rather than searching all members for the largest. QuickC is well
- behaved in this regard, however. It allocates the correct number of bytes
- for a union, regardless of the order of the member declarations.
-
- As with structure members, you access the members of a union with the
- "dot" operator. However, the compiler interprets the type of the union as
- the type specified by the member name, as follows:
-
- one_of_many.ftype = 1.0;────────────────────────────Interpret as a float
- one_of_many.itype = 1;───────────────────────────────Interpret as an int
-
- The UDEMO.C program (Listing 11-6) is a simple demonstration of how a
- union works. After asking the user to enter a type, it uses scanf() to
- read that type and printf() to echo it to the screen.
-
- ──────────────────────────────────────────────────────────────────────────
- /* udemo.c -- demonstrates a union at work */
-
- #include <stdio.h>
-
- char *Strings[6] = {
- "Quit",
- "line of text",
- "floating-point double value",
- "long integer value",
- "floating-point value",
- "integer value"
- };
-
- struct Unitstruct {
- union {
- char wtype[BUFSIZ];
- double dtype;
- long ltype;
- float ftype;
- int itype;
- } manyu;
- int type_in_union;
- };
-
- main()
- {
- struct Unitstruct one_of_many;
-
- while ((one_of_many.type_in_union = Menu()) != 0 )
- {
- Inputval(&one_of_many);
- Printval(&one_of_many);
- }
- }
-
- Inputval(struct Unitstruct *one_of_many)
- {
- printf("\nEnter a %s: ", Strings[one_of_many->type_in_union]);
- switch(one_of_many->type_in_union)
- {
- case 1:
- fgets(one_of_many->manyu.wtype, BUFSIZ, stdin);
- break;
- case 2:
- scanf("%lf", &(one_of_many->manyu.dtype));
- while (getchar()!= '\n');
- break;
- case 3:
- scanf("%ld", &(one_of_many->manyu.ltype));
- while (getchar()!= '\n');
- break;
- case 4:
- scanf("%f", &(one_of_many->manyu.ftype));
- while (getchar()!= '\n');
- break;
- case 5:
- scanf("%i", &(one_of_many->manyu.itype));
- while (getchar()!= '\n');
- break;
- }
- }
-
- Printval(struct Unitstruct *one_of_many)
- {
- printf("The %s you entered\nwas: ", Strings[one_of_many->type_in_union]
- switch (one_of_many->type_in_union)
- {
- case 1:
- fputs(one_of_many->manyu.wtype, stdout);
- break;
- case 2:
- printf("%lf", one_of_many->manyu.dtype);
- break;
- case 3:
- printf("%ld", one_of_many->manyu.ltype);
- break;
- case 4:
- printf("%f", one_of_many->manyu.ftype);
- break;
- case 5:
- printf("%i", one_of_many->manyu.itype);
- break;
- }
- printf("\n\n");
- }
-
- Menu()
- {
- int i;
- char ch;
- for (i = 0; i < 6; ++i)
- {
- printf("%d) %s\n", i, Strings[i]);
- }
- printf("Which: ");
- do
- {
- ch = getch();
- } while (ch < '0' || ch > '5');
- printf("%c\n", ch);
- return (ch - '0');
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-6. The UDEMO.C program.
-
- Unions and Functions
-
- Unlike a structure, you cannot pass a union to a function. Instead, you
- must pass the value of the type currently stored in that union. For
- example, the statement
-
- printf("%f", one_of_many.ftype);
- │ └───────── Sends the float value in one_of_many
- └────────────────────────────────────────────── Expects a float
-
- sends printf() the float value in one_of_many, which matches the printf()
- %f format specifier. Note that it is meaningless in C to use a union
- variable (such as one_of_many) without a corresponding "dot" and member
- name.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- The UDEMO.C program illustrates a common technique for managing unions.
- Because a union contains no inherent indication of the type it contains,
- unions are often made members of structures, with another member used to
- store that indication:
-
- struct Unitstruct{
- union {
- char wtype[BUFSIZE];
- double dtype;
- long ltype;
- float ftype;
- int itype;
- } manyu;
- int type_in_union;
- };
-
- By packaging a union and an int together in a structure like this, we are
- better able to keep track of the type stored in the union at any given
- time.
- ──────────────────────────────────────────────────────────────────────────
-
- Unions Received by Functions
-
- C permits you to use a union as the type of an argument received by a
- function, but the procedure can be risky. The following statement
- illustrates one way to declare a received variable in a subroutine as a
- union:
-
- #define FLT 0 /* floating-point type */
- #define INT 1 /* integer type */
-
- Printval(val, type)
- union twotype val;
- int type;
- {
- switch (type)
- {
- case FLT: printf("%f", val.ftype); break;
- case INT: printf("%d", val.itype); break;
- }
- }
-
- This function receives two arguments: a union of two possible types and an
- int that specifies which of the two possible types is in that union.
-
- But beware. Depending on how the compiler passes arguments to functions,
- this approach can fail. In QuickC, a float is four bytes and an int is two
- bytes; therefore, the stack (received arguments) resembles Figure 11-6a
- when passing a float and Figure 11-6b when passing an int. However,
- because the pattern for twotype reserves four bytes, passing an int to
- Printval() causes the type argument to appear in the wrong place.
-
- You can resolve this dilemma by constraining union members to types that
- use the same number of bytes. That is, if you declare twotype as follows:
-
- union twotype {
- float fval;
- long ival;
- };
-
- it would contain either of two types, but each type requires four bytes. A
- better solution is to package a union and an int together inside a
- structure, as you saw earlier. That approach avoids the potential pitfalls
- of declaring a function that receives a bare-bones union.
-
- Pointers to Unions
-
- Pointers to unions behave like pointers to structures. You retrieve the
- address of a union with the & operator and the union variable name, as
- follows:
-
- &one_of_many
-
- Printval (float, int)
- │ │ High address
- │ │ ┌────────────────┐
- │ └────├── int ──┤2-byte int
- │ ├────────────────┤
- │ ├── ──┤
- └──────────├── float ──┤4-byte float
- ├── ──┤
- Start of arguments ────└────────────────┘
- Low address
-
- (A)
-
-
- Printval (int, int)
- │ │ High address
- │ │ ┌────────────────┐
- │ │ ├── ──┤Second argument missing
- │ │ ├────────────────┤
- │ └──────├── int ──┤2-byte int
- │ ├────────────────┤
- └───────────├── int ──┤2-byte int
- Start of arguments ────└────────────────┘
- Low address
-
- (B)
-
- Figure 11-6. Passing different-size data types to the same function can
- cause confusion.
-
- To fetch the address of a union member, specify the & operator, the union
- variable name, the "dot" operator, and the member name, as follows:
-
- &one_of_many.ftype
-
- Declaring pointers to unions and manipulating values via the addresses in
- those pointers is also identical to the form used by structure pointers.
- Declare a pointer to a union as follows:
-
- union manytype *up;
- └───────────── Pointer to a union of the pattern manytype
-
- Place a value (an address) into that pointer in the following form:
-
- up = &one_of_many;
-
- To access the type of the value stored in the union whose address is in
- up, use the -> operator as follows:
-
- up->ftype = 1.0;
-
- Structures and unions are closely related. The main difference is that a
- structure holds many values simultaneously; a union holds only a single
- type of value at any one time. As you have seen, structures can include
- unions as members. It is also legal for unions to contain structures as
- members. We'll use this latter technique at the end of this chapter, when
- we discuss bit fields.
-
-
- Enumerated Data with enum
-
- Many kinds of information are best represented by a finite list of
- discrete integer values──for example, the days of the week, the months of
- the year, or even the phases of the moon. Such kinds of information, in
- which every possibility is known in advance, lend themselves to
- enumeration──a listing of all possible values for a given topic or
- concept.
-
- If you need to represent the days of the week in a program as discrete
- integers, you could make the following declarations and assignments:
-
- int monday = 0, tuesday = 1, wednesday = 2, thursday = 3,
- friday = 4, saturday = 5, sunday = 6;
-
- and later use those values as follows:
-
- pay_day = friday;
-
- The previous approach, although reasonable, has a potential pitfall.
- Because the days of the week are int variables, the program might change
- their values, and so render them meaningless. To avoid this problem we can
- use the following directives:
-
- #define MONDAY 0
- #define TUESDAY 1
- [etc.]
-
- The program can't change these values because they are integer constant
- aliases. But this is still not an ideal solution because you cannot group
- #define definitions under a single conceptual name.
-
- The best solution uses the C enumerated data type, enum, whose members are
- constants grouped under a single name. To represent the days of the week
- using enum, first declare a pattern similar to a structure or union
- pattern:
-
- ┌────────────────────────────────────────────── Name of pattern
- enum week_days {
- monday,───────────────────────────────────────────────────── Members
- tuesday,
- wednesday,
- thursday,
- friday,
- saturday,
- sunday
- } pay_day;
- └────────────────────────────────────────────── Enumerated variable
-
- This example declares a pattern called week_days, an enumerated data type,
- and the enumerated variable pay_day. Note that the members don't need to
- be preceded by a type keyword because the members of enum are always of
- type int. Also notice that you don't need to assign the members any
- values: The declaration itself gives the members constant integer values,
- starting with 0 for monday and counting through 6 for sunday.
-
- Another difference between enum and struct or union is that you access
- members of enum simply by stating the member's name without the "dot" or
- "->" notation:
-
- payday = monday;
-
- Any attempt to change the value of an enumerated member (monday = 5, for
- example) results in the following QuickC error message:
-
- error C2106:
- '=' : left operand must be lvalue
-
- This reminds you that the members of an enumerated data type, like all
- other constants, are rvalues and can appear only to the right of an
- assignment operator.
-
- Also note that you cannot use a pointer to indirectly change the value of
- an enumerated variable member. For example, the following assignment:
-
- int *p;
-
- p = &monday;────────────────────────────Can't take address of a constant
- *p = 5;
-
- fails because you can't retrieve the address of a constant. This attempt
- generates the following QuickC error message:
-
- error C2101:
- '&' on constant
-
- The TODAY.C program (Listing 11-7 on the following page) demonstrates one
- advantage to using enum──improved readability. The program asks you to
- specify the day on which you want to be paid. It then checks to make
- certain that you specified a legal day.
-
- The pattern week_day in TODAY.C shows that you can initialize an enum
- member to any integer value. Any uninitialized member, however, is
- assigned a value one higher than the member before it. For example, the
- declaration
-
- enum folks {
- mo = -1,
- roseann,
- betsy = 0,
- kit,
- joey = 1
- };
-
- sets mo to a -1, roseann and betsy to 0, and kit and joey to 1. This also
- shows that enum members can have duplicate values.
-
- ──────────────────────────────────────────────────────────────────────────
- /* today.c -- demonstrates using enum */
-
- main()
- {
- enum week_days {
- monday = 1, /* start with 1 */
- tuesday,
- wednesday,
- thursday,
- friday,
- saturday,
- sunday
- } pay_day;
-
- static char *day_names[] = {
- "",
- "monday",
- "tuesday",
- "wednesday",
- "thursday",
- "friday",
- "saturday",
- "sunday"
- };
-
- printf("What day do you want to be paid on?\n");
-
- for (pay_day = monday; pay_day <= sunday; ++pay_day)
- {
- printf("%d. %s\n", pay_day, day_names[pay_day]);
- }
-
- printf("Which (%d-%d): ", monday, sunday);
-
- do
- {
- pay_day = getch();
- pay_day -= '0';
- } while (pay_day < monday || pay_day > sunday);
-
- printf("%d\n\n", pay_day);
-
- printf("You selected %s\n", day_names[pay_day]);
-
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-7. The TODAY.C program.
-
-
- Bit Fields
-
- In Chapter 7, we discussed how to use bitwise operators to store data in
- the individual bits of bytes. Another, and simpler, way to store and
- access information in bits is with "bit fields."
-
- Bit fields offer two advantages over the bitwise operators. First, you can
- access bit fields by name (such as blink) rather than by an obscure mask
- (such as (1 << 7)). Second, the compiler generates code for bit fields
- that you normally would have to write yourself. Examine, for example, the
- following bit-field assignment:
-
- blink = 1;
-
- where blink is the name of the sixteenth bit of a 2-byte int. This
- statement is comparable to the following assignment using bitwise
- operators:
-
- ch |= (1 << 15);
-
- C's bit fields are especially handy when you need to manipulate items with
- built-in bit information. The characters in your screen memory are
- examples of such items. Recall that each screen character is represented
- by a 2-byte int. One byte is the character itself; the other is the
- attribute byte. (See Figure 11-7.)
-
- ┌────────Most significant bit
- ┌──────┐─┐
- Blinking (1 bit) ─────│ 7 │ │
- ┌─├─ ─┤ │
- │ │ 6 │ │
- │ ├─ ─┤ │
- Background (3 bits) ───┤ │ 5 │ │
- │ ├─ ─┤ │
- │ │ 4 │ │
- └─├─ ─┤ ├───Attributes (1 byte)─┐
- Intensity (1 bit) ─────│ 3 │ │ │
- ┌─├─ ─┤ │ │
- │ │ 2 │ │ │
- │ ├─ ─┤ │ │
- Foreground (3 bits) ───┤ │ 1 │ │ │
- │ ├─ ─┤ │ │
- │ │ 0 │ │ │
- └─├───────┤─┤ ├──1 int
- │ 7 │ │ │
- ├─ ─┤ │ │
- │ 6 │ │ │
- ├─ ─┤ │ │
- │ 5 │ │ │
- ├─ ─┤ │ │
- │ 4 │ │ │
- ├─ ─┤ ├───Character (1 byte)──┘
- │ 3 │ │
- ├─ ─┤ │
- │ 2 │ │
- ├─ ─┤ │
- │ 1 │ │
- ├─ ─┤ │
- │ 0 │ │
- └──────┘─┘
- └────────Least significant bit
-
- Figure 11-7. One character in screen memory is represented by two
- consecutive bytes.
-
- The following is an example of one such screen int declared using bit
- fields:
-
- ┌──────────────┬────────────── One integer...divided like this
- unsigned int character :8,
- foreground :3,
- intensity :1,
- background :3,
- blink :1;
- └─────────┴──────────────────── Name for...this many bits
-
- In this declaration, we tell QuickC to use the bits in one unsigned
- integer. Next, we specify the names for each group of bits in that
- integer, beginning with 8 bits, to which we give the name character, and
- continuing through all 16 bits until we end with blink as the name of the
- final bit.
-
- You may name as many bits as there are in the type declared (8 for a char,
- 32 for a long, and so on). Only integer types can be used as bit fields,
- and only integer constants can be used to declare the number of bits.
- Always declare the bits from the bottom up (from the least significant to
- the most significant bits). A colon separates the name for each group of
- bits from the number of bits assigned to it; a comma separates each name
- :bits from the next; and, of course, a semicolon must end the entire
- declaration.
-
- If you declare fewer bits than there are in a type, the unused bits are
- simply ignored. If you declare more, an additional variable of the same
- type is allocated:
-
- ┌───────────────────────────────────────────────────── 8 bits
- unsigned char character :8,
- foreground :3,
- intensity :1,
- background :3,
- blink :1;
- └─── 16 bits total allocates two char variables
-
- The name :bits combination is what defines a bit field as opposed to an
- ordinary variable. For example, the above declaration produces the same
- allocation as the following series of declarations:
-
- unsigned char character :8;
- unsigned char foreground :3;
- unsigned char intensity :1;
- unsigned char background :3;
- unsigned char blink :1;
-
- In this example, the compiler gathers the bits from the declared bit
- fields into the most compact unit, regardless of how many bit fields you
- declare.
-
- Because you are not permitted to retrieve the address of a bit field, you
- usually will declare bit fields inside structures, as follows:
-
- struct screen_char_struct {
- unsigned int character :8,
- foreground :3,
- intensity :1,
- background :3,
- blink :1;
- } screen_ch ;
- └───┬───┘
- └───────────────── Structure variable whose members are bit fields
-
- This approach has two advantages. First, you can access the individual bit
- fields with the usual structure/member notation. This improves
- readability:
-
- screen_ch.blink = 1;─────────────────Retrieve the address of a structure
-
- Second, you can access the address of a structure, but you cannot retrieve
- the address of a bit field. This lets you manipulate bit fields with
- pointers, which can increase the speed of your program:
-
- &screen_ch
-
- The SCRMENU.C program (Listing 11-8) demonstrates how to use bit fields
- to modify text-screen display. It lets you select an attribute; then it
- toggles the setting for that attribute for every character on the screen.
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrmenu.c -- uses bit fields to modify your text */
- /* screen's attributes */
-
- char *Choice_Words[] = {
- "Quit",
- "Foreground",
- "Intensity",
- "Background",
- "Blinking"
- };
- enum Choices {
- Quit,
- Foreground,
- Intensity,
- Background,
- Blinking
- };
-
- /* use 0xB800000 for EGA or VGA */
- #define SCR_START (0xB0000000)
- #define SCR_SIZE (25 * 80)
- main()
- {
- enum Choices choice;
-
- printf("Select from the following by number:\n");
-
- for (choice = Quit; choice <= Blinking; ++choice )
- {
- printf("%d. %s\n", choice, Choice_Words[choice]);
- }
-
- printf("\nWhich: ");
- do
- {
- choice = getch();
- choice -= '0';
- if (choice < Foreground || choice > Blinking)
- continue;
- Redraw( choice );
- } while (choice != Quit);
-
- }
-
- Redraw(enum Choices field)
- {
- struct screen_char {
- unsigned int character :8,
- foreground :3,
- intensity :1,
- background :3,
- blink :1;
- } scrchar, far *sp, far *ep;
-
- sp = (struct screen_char far *)SCR_START;
- ep = sp + SCR_SIZE;
-
- while (sp < ep)
- {
- scrchar = *sp;
- switch (field)
- {
- case Foreground:
- scrchar.foreground = (scrchar.foreground)? 0 : 7;
- break;
- case Intensity:
- scrchar.intensity = (scrchar.intensity)? 0 : 1;
- break;
- case Background:
- scrchar.background = (scrchar.background)? 0 : 7;
- break;
- case Blinking:
- scrchar.blink = (scrchar.blink)? 0 : 1;
- break;
- }
- *(sp++) = scrchar;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 11-8. The SCRMENU.C program.
-
- SCRMENU.C combines bit fields with enum and the #define preprocessor
- directive to virtually rid the body of the program of obscure constructs.
- Also, notice that we use a pointer to a structure to access the screen.
-
-
- Advanced typedef
-
- So far, we've used the #define preprocessor directive to create aliases,
- both for increased program clarity and as a shorthand method of entering
- repetitive code. We have also seen, in Chapter 3, that new types can be
- defined by using typedef. Superficially, #define and typedef appear to be
- interchangeable. To create simple aliases, you can use either one.
- Situations arise, however, in which typedef is suitable, but #define is
- not.
-
- For example, suppose you need to create a new type called string, an array
- of type char. Now suppose you attempt to create this new type with
- #define, as follows:
-
- #define string char s[128]
-
- You later would not be permitted to make the declaration
-
- string str1, str2;
-
- because the preprocessor would expand it to be
-
- char s[128] str1, str2;
-
- which is illegal. (Note the missing comma, among other things.) In
- situations such as this one, typedef is ideal. Rather than beginning with
- a #define directive, suppose you use the following:
-
- typedef char string[128];
-
- This creates a new type called string, which you can use later to declare
- variables of that new type:
-
- string str1, str2;
-
- Because we used typedef to define string, the compiler correctly
- translates this into
-
- char str1[128], str2[128];
-
- which is what we intended in the first place.
-
- The secret to using typedef is to follow three simple steps. First,
- declare an ordinary variable of the type you want:
-
- char s[128];
-
- Second, place the word typedef at the front:
-
- typedef char s[128];
-
- Third, replace the variable's name with the new type name:
-
- typedef char string[128];
-
- You can now use the newly defined type string exactly as you would one of
- C's built-in types, such as int.
-
- In addition to doing what #define cannot, typedef also lends clarity to
- otherwise obscure constructs. For example, consider the following two
- pointers to functions:
-
- int (*quit_fun)(), (*restart_fun)();
-
- This could be confusing if it were to appear throughout your program.
- Using typedef, however, you can create a new type called funptr:
-
- typedef (*funptr)();
-
- Now you can use funptr throughout your program to declare variables of
- that new type, as follows:
-
- funptr quit_fun, restart_fun;
-
- Use typedef judiciously──it is the most easily abused concept in C. The
- indiscriminate use of typedef, rather than making your program more
- readable, can make it more obscure and (sometimes) indecipherable.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 12 Large Projects
-
- As your programs become larger and more complex, revising and maintaining
- them become less straightforward. Consequently, as your programming skills
- increase, you inevitably will find yourself looking for more efficient
- ways of handling programs. For example, you might want to:
-
- ■ Use one function in several programs without having to retype it every
- time.
-
- ■ Compile a program one way for testing and another for actual use──
- without having to rewrite it.
-
- ■ Combine several .C files into a single program, while recompiling only
- those files that need to be changed.
-
- ■ Transport one of your programs to another machine or compiler and
- compile it without needing to rewrite it.
-
- This chapter offers solutions for these and other common programming
- needs. We'll discuss how to use the C preprocessor for conditional
- compilation and for creating macros. Next, we'll show you how to create
- and manage QuickC's "program lists." Finally, we'll show you how to
- develop custom C libraries and how to access them from within QuickC.
-
-
- Advanced C Preprocessor
-
- Although compiling under QuickC appears to be a single swift process, it
- is actually three processes combined into one. First, your C program is
- "preprocessed." In this phase, conditional compilation occurs, and other
- preprocessing directives are executed: For example, #define MAX 3 converts
- all instances of MAX to 3. Second, the QuickC compiler translates your
- preprocessed code into machine language, or code that the computer can
- understand. Finally, your compiled machine code is combined (linked) with
- the precompiled code in the standard C Library of functions (such as
- printf()) to form the finished, executable program.
-
- Conditional compilation occurs in the preprocessing stage. Using lines
- that begin with a # character (pronounced "pound" or "number"), you can
- write code that compiles one way for testing and another for actual use.
- You can also write code that compiles differently on different machines or
- different compilers.
-
- The C preprocessor recognizes only lines of text that begin with the #
- character, such as #define and #include. Table 12-1 lists the complete
- set of these "preprocessor directives."
-
- Table 12-1 The Preprocessor Directives
- ╓┌─┌───────────────┌─────────────────────────────────────────────────────────╖
- Directive Description
- ──────────────────────────────────────────────────────────────────────────
- #define x y Uses x as an alias for y throughout the program.
- #include <file> Reads file from the INCLUDE subdirectory and inserts it
- into your program at this point.
- #include "file" Reads file from the current working directory and inserts
- it into your program at this point.
- #ifdef x If x is defined, compiles all program code between this
- and the next matching #endif, #elif, or #else.
- #if (x) If the integer constant expression x is true (nonzero),
- compiles all program code between this directive and the
- next matching #endif, #elif, or #else.
- Directive Description
- ──────────────────────────────────────────────────────────────────────────
- next matching #endif, #elif, or #else.
- #ifndef x If x is not defined, compiles all program code between
- this and the next matching #endif, #elif, or #else.
- #else The inverse of the above three if directives. If the if is
- true, the code before the #else is compiled. If the if is
- false, the code following the #else is compiled.
- #elif (x) The else if extension for #if in a chain of conditions.
- #endif Terminates the current matching #if, #ifdef, or #ifndef.
- #line lineno Sets the current line number to lineno and the current
- "file" file to file.
- #pragma Sets "compiler-specific" options.
- #define x(y) z Defines preprocessor macros.
- ──────────────────────────────────────────────────────────────────────────
-
-
- Conditional Compilation
-
- Occasionally, you will need to compile only part of your code──for
- example, during debugging, or when you compile different versions for
- different users, or while compiling your program on a different computer
- or compiler. The C preprocessor offers an assortment of directives to
- facilitate this selective compiling process, called "conditional
- compilation."
-
- The #if and #endif Directives
-
- The most frequently used conditional directives are #if and #endif. The
- #if directive tests what is known as a restricted constant expression in
- your code to see if that expression is zero. If it is a nonzero (true)
- value, QuickC compiles all the code between that #if and its matching
- #endif. Use the directive as follows:
-
- #define BYTES 4
-
- #if (BYTES == 4)
- /* compile this code */
- #endif
-
- In this example, the expression (BYTES == 4) is a "constant expression"
- because it becomes (4 == 4) (the logical comparison of two integer
- constants). It is also a "restricted" constant expression, which is a
- constant expression that cannot contain:
-
- ■ sizeof operations
-
- ■ enumerated constants
-
- ■ typecasts
-
- ■ floating-point constants
-
- Therefore, the following directives are legal:
-
- #if (BYTES < 8)
- #if ((6 * 9 / 3) != (2 % 1))
-
- and the following are not:
-
- #if (sizeof(int) == 4)────────────────────────────────────sizeof illegal
-
- enum {true, false} yorn;
- #if (true == 0)──────────────────────────────Enumerated constant illegal
-
- #if (NULL == (char *)0)─────────────────────────────────Typecast illegal
-
- #if (MIN < 4.2)───────────────────────────────────float constant illegal
-
- One common use for the #define directive is in debugging. The program in
- Listing 12-1 on the following page, BUG.C, illustrates one possible way
- to use #define to change the behavior of your program. By using #define to
- define DEBUG_LEVEL to one of the values 0, 1, or 2, then recompiling and
- running, you will cause the program to print one of three messages to your
- screen. For a #define value of 0, nothing is printed; for 1, the calls to
- the subroutine sub() are documented; and for 2, entry into and exit from
- main() are printed.
-
- ──────────────────────────────────────────────────────────────────────────
- /* bug.c -- shows how different levels of debugging */
- /* output can be produced using #if */
-
- #define DEBUG_LEVEL 2 /* 0 = none, 1-2 for debug */
- #include <stdio.h>
-
- main()
- {
- int ret;
-
- #if (DEBUG_LEVEL == 2)
- fprintf(stderr, "Entering main()\n");
- #endif
-
- #if (DEBUG_LEVEL == 1)
- fprintf(stderr, "Calling sub()\n");
- #endif
-
- ret = sub();
-
- #if (DEBUG_LEVEL == 1)
- fprintf(stderr, "sub() returned %d\n", ret);
- #endif
-
- #if (DEBUG_LEVEL == 2)
- fprintf(stderr, "Leaving main()\n");
- #endif
-
- }
-
- sub()
- {
- return (5);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-1. The BUG.C program.
-
- defined and #ifdef
-
- You can use the defined keyword with the #if directive to detect whether
- or not a name has been specified by #define:
-
- #if defined(name)
-
- If defined(name) determines that name was used in a #define directive, it
- evaluates to true. The keyword defined is used by the preprocessor only in
- this context; therefore, you can use it anywhere in your program without
- causing a conflict.
-
- The defined variation of #if replaces the pre-ANSI directive #ifdef. That
- is, although the following are equivalent:
-
- #if defined(name)
- #ifdef name
-
- the first form is preferable.
-
- You can use the same technique to see if a name has not been specified
- with #define, as follows:
-
- #if !defined(name)
- #ifndef name
-
- Again, the first form is preferable to the second.
-
- The defined variation of #if is especially useful for writing programs
- that will be compiled on another type of computer or a different compiler.
- The BITOUT.C program (Listing 12-2) is an adaptation of the Bitout()
- function used in the BITWISE.C program (Listing 7-12 on p. 218). After
- the user enters an integer, the program prints that integer in binary
- form. Note that it uses #if defined to print the bits one way on an
- 80286-based computer and another way on a 68000-based machine.
-
- ──────────────────────────────────────────────────────────────────────────
- /* bitout.c -- compiles one way on an IBM PC and */
- /* another on a 68000 chip─based machine */
-
- #define CHIP_80286 /* don't define on a 68000 machine */
- #include <stdio.h>
-
- main()
- {
- int num;
-
- printf("Enter an integer number and I will print"
- " it out in binary\nNumber: ");
-
- if (scanf("%d", &num) != 1)
- {
- fprintf(stderr, "Not an integer\n");
- exit(1);
- }
- Bitout(num);
- }
-
- Bitout(unsigned int num)
- {
- int i, j;
- unsigned char *cp;
-
- cp = (char *)#
- #if defined(CHIP_80286) /* IBM PC */
- for (i = 1; i >= 0; --i)
- #endif
- #if !defined(CHIP_80286) /* otherwise 68000 machine */
- for (i = 0; i < 4; ++i)
- #endif
- {
- for (j = 7; j >= 0; --j)
- putchar((cp[i] & (1 << j)) ? '1' : '0');
- }
- putchar('\n');
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-2. The BITOUT.C program.
-
- #else and elif
-
- We can simplify the two #if directives in BITOUT.C by using the #else
- directive:
-
- #if defined(CHIP_80286)
- for (i = 1; i >= 0; --i)
- #else
- for (i = 0; i < 4; ++i)
- #endif
-
- In this example, the preprocessor compiles the first for statement if
- CHIP_80286 has been defined using #define; otherwise, it compiles the
- second for statement.
-
- By using the #elif (else if) directive, you can create a whole chain of
- conditions. The following series of directives, for example,
-
- #if defined(CHIP_8086)
- for (i = 1; i >= 0; --i)
- #elif defined(CHIP_80286)
- for (i = 1; i >= 0; --i)
- #elif defined(CHIP_68000)
- for (i = 0; i < 4; ++i)
- #else
- fprintf(stderr, "Unknown chip\n");
- return;
- #endif
-
- tells the preprocessor to compile the first for statement if CHIP_8086 is
- defined, to compile the second for statement if CHIP_80286 is defined, or
- to compile the third for statement if CHIP_68000 is defined. If none of
- these is defined, the preprocessor compiles code to print an error and
- return.
-
- Logical Operators and #if
-
- Many of the preceding #if tests use similar code. You can take a coding
- shortcut by combining #if expressions using the C logical operators && and
- ||. For example, you can shorten the previous #elif sequence by using the
- logical OR operator as follows:
-
- ┌──────────────────────────────────── Logical OR
- #if defined(CHIP_8086) || defined(CHIP_80286)
- for (i = 1; i >= 0; --i)
- #elif defined(CHIP_68000)
- for (i = 0; i < 4; ++i)
- #else
- fprint(stderr, "Unknown chip\n");
- return;
- #endif
-
- The #if directives and their corresponding #endif and #elif directives can
- be nested. However, when you nest them, we recommend that you use indents
- to show the levels of nesting, as follows:
-
- #if defined(IBMPC)
- #if defined(CGA) || defined(EGA)
- sp = (int far *)0xB8000000;
- #else
- sp = (int far *)0xB0000000;
- #endif
- #else
- fprintf(stderr, "No Screen Memory\n");
- return;
- #endif
-
- In this example, if IBMPC is not defined, the last #else executes. If
- IBMPC is defined, the program checks to see if either CGA or EGA (for the
- corresponding graphic adapter cards) is defined. If either is, we assign
- the address value 0xB8000000 (the location of screen memory for those
- cards) to the pointer sp. Otherwise, we use the address 0xB0000000 (the
- location of screen memory for the regular monochrome adapter).
-
- You can avoid problems when using # preprocessor directives by remembering
- two general rules. First, the # must always begin a line. Second, each
- directive can occupy only one line unless you extend it by typing a
- backslash and pressing Enter:
-
- #if defined(EGA) \─────────────────────────────────────────Line extended
- || \────────────────────────────────────────────────Line extended
- defined(CGA)
-
- Predefined Names
-
- QuickC always predefines two names: __FILE__ and __LINE__. (Note that both
- have two leading and two trailing underscore characters.) The name
- __FILE__ is always the name of the current C source file being compiled.
- It is a quoted string constant, so you can safely use it anywhere that
- strings are legal. The predefined name __LINE__ is an integer constant
- number that is always the current line number in the current file. You can
- use it anywhere as a legal integer constant.
-
- These two predefined names are generally used to print meaningful
- diagnostics during debugging. The ERR.C program (Listing 12-3)
- demonstrates their use for tracing the flow of a small program. By placing
- a #define ERR inside a #if directive, you can turn on and off custom
- tracing with a single change in code:
-
- #define TRACE 0 /* change to 1 to turn on */
-
- #if (TRACE > 0)
- #define ERR printf("Tracing: \"%s\" line %d\n",\
- __FILE__, __LINE__ );
- #else
- #define ERR
- #endif
-
- If TRACE is defined as a value greater than zero, QuickC traces the
- program. If, on the other hand, TRACE is 0, then tracing is disabled.
-
- ──────────────────────────────────────────────────────────────────────────
- /* err.c -- illustrates __FILE__ and __LINE__ in */
- /* tracing a small program */
-
- #define ERR printf("Tracing: \"%s\" line %d\n",\
- __FILE__, __LINE__);
- main()
- {
- ERR
- err1();
- ERR
- err2();
- ERR
- }
-
- err1()
- {
- ERR
- err2();
- }
-
- err2()
- {
- ERR
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-3. The ERR.C program.
-
- #pragma Instructions to the Compiler
-
- You can use the #pragma preprocessor directive to give compiler-specific
- instructions to the compiler (that is, instructions that usually must be
- given as part of the MS-DOS command line or by presetting QuickC's compile
- time options). Use it in the following way:
-
- #pragma instruction
-
- #pragma pack(1|2|4)
-
- The pack pragma tells the compiler to place structure members into memory
- on 1-byte, 2-byte, or 4-byte boundaries. Ordinarily, QuickC places
- structure members into memory so that int and long types always begin in
- an even address, which is equivalent to pack(2). (See Figure 12-1a.) By
- using the #pragma pack() preprocessor directive, you can tell the compiler
- to store structures in a smaller space (see Figure 12-1b) or to spread
- them out into a larger space with pack(4). (See Figure 12-1c.)
-
- ┌──
- │ struct {
- │ char a;
- │ int b;
- For the declaration ──┤ char c;
- │ long d;
- │ };
- └──
-
- ┌───────┐─┐
- 400 │ │ ├─a
- ├───────┤─┤
- 401 │ │ │
- ┌───────┐─┐ ┌───────┐─┐ ├─ ─┤ │
- 402 │ │ ├─a 402 │ │ ├─a 402 │ │ ├─unused
- ├───────┤─┤ ├───────┤─┤ ├─ ─┤ │
- 403 │ │ ├─unused 403 │ │ │ 403 │ │ │
- ├───────┤─┤ ├─ ─┤ ├─b ├───────┤─┤
- 404 │ │ │ 404 │ │ │ 404 │ │ │
- ├─ ─┤ ├─b ├───────┤─┤ ├─ ─┤ ├─b
- 405 │ │ │ 405 │ │ ├─c 405 │ │ │
- ├───────┤─┤ ├───────┤─┤ ├───────┤─┤
- 406 │ │ ├─c 406 │ │ │ 406 │ │ ├─c
- ├───────┤─┤ ├─ ─┤ │ ├───────┤─┤
- 407 │ │ ├─unused 407 │ │ │ 407 │ │ ├─unused
- ├───────┤─┤ ├─ ─┤ ├─d ├───────┤─┤
- 408 │ │ │ 408 │ │ │ 408 │ │ │
- ├─ ─┤ │ ├─ ─┤ │ ├─ ─┤ │
- 409 │ │ │ 409 │ │ │ 409 │ │ │
- ├─ ─┤ ├─ d └───────┘─┘ ├─ ─┤ ├─d
- 410 │ │ │ 410 │ │ │
- ├─ ─┤ │ ├─ ─┤ │
- 411 │ │ │ 411 │ │ │
- └───────┘─┘ └───────┘─┘
-
- (A) RESULT OF USING (B) RESULT OF US
- ING (C) RESULT OF USING
- #pragma pack (2) #pragma pack (1) #pragma pack (4)
- (QuickC default)
-
- Figure 12-1. The pack() pragma determines how structures are placed
- into memory.
-
- The PACK.C program (Listing 12-4) illustrates this structure packing.
- When you run the program, note the addresses it prints. Then change the 1
- in #pragma pack(1) to a 2 and recompile and run PACK.C again. Finally,
- change that 2 to a 4 and repeat the process.
-
- An extension to the #pragma pack() directive lets you turn packing on and
- off:
-
- #pragma pack(1)─────────────────────────────────────Set one-byte packing
- ...
- #pragma pack() no───────────────────────────────────────Turn packing off
- ...
- #pragma pack() yes──────────────────────────────────Turn packing back on
-
- The example first tells the compiler to pack all structure members to the
- nearest 1-byte boundary. Next, the no tells the compiler to stop packing
- and revert to its default even-byte boundary arrangement. Finally, the yes
- tells the compiler to resume packing on 1-byte boundaries.
-
- ──────────────────────────────────────────────────────────────────────────
- /* pack.c -- demonstrates structure packing with */
- /* the #pragma pack() directive */
-
- #pragma pack(4) /* 1, 2 or 4 */
-
- main()
- {
- struct {
- char ch1;
- int int1;
- char ch2;
- long int2;
- } s;
-
- printf("ch1 -> %lu\n", (unsigned long)(&s.ch1));
- printf("int1 -> %lu\n", (unsigned long)(&s.int1));
- printf("ch2 -> %lu\n", (unsigned long)(&s.ch2));
- printf("int2 -> %lu\n", (unsigned long)(&s.int2));
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-4. The PACK.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- The Intel 80386 chip executes at its fastest if int and long types begin
- on modulo 4-byte address boundaries. The Intel 80286 and earlier chips
- execute fastest when those types begin on even addresses. If size is more
- important to you than speed, use the #pragma pack(1) directive.
- ──────────────────────────────────────────────────────────────────────────
-
- Preprocessor Macros
-
- The #define preprocessor directive has a second form that is called a
- #define macro, or a preprocessor macro. The #define macro is an extremely
- powerful tool, used by programmers to place "in-line" code into a program
- in a manner that resembles a subroutine call. Take a moment to use
- QuickC's View Include feature to look at the <stdio.h> header file. Notice
- in line 105 of that file that the getc() function you have been using all
- along is not really a function at all. It is a #define macro. Because it
- is a macro, the preprocessor expands each occurrence of getc(stdin) in
- your program to the following:
-
- (────(stdin)->cnt >= 0 ? 0xFF & *(stdin)->_ptr++ : _filbuf(stdin))
-
- Certainly, it is easier to type getc(stdin) than to type this complex code
- sequence.
-
- The form for a #define macro is as follows:
-
- #define TRIPLE(x) (x*3)
-
- In this example, the defined name is TRIPLE and the (x) is its formal
- argument. The expression TRIPLE(x) is defined as an alias for the
- expression (x*3). This means that anywhere in the program that you use the
- following expression:
-
- TRIPLE(2)
-
- the actual argument (here 2) replaces every occurrence of the formal
- argument, x, in the original definition. This produces the following
- expansion:
-
- TRIPLE(2)
- │
- ├───────────────────────────────────────────────────────── Expands to
- │
-
- (2*3)
-
- To illustrate further, examine the following macro definition for MAX, a
- macro that compares two values and yields a new value that is the higher
- of the two:
-
- ┌─┬──────────────────────────────────── Two formal arguments
- #define MAX(x,y) (((x) > (y)) ? (x) : (y)) separated by a comma
- └──┬───┘ └──────────┬───────────┘
- │ └────────────────────────── Macro definition
- └────────────────────────────────────────────────────── Macro
-
- This example shows that macros can take more than one formal argument──but
- arguments must be separated from one another by commas. The x in the macro
- replaces each x in the macro definition with its corresponding actual
- argument, and each y replaces its corresponding y. If you use the above
- macro definition in your code and then use the following expression:
-
- oldest = MAX(age1, age2);
-
- with int variables age1 and age2, the preprocessor expands the macro as
- follows:
-
- oldest = (((age1) > (age2)) ? (age1) : (age2));
-
- Potential Problems with Macros
-
- Use preprocessor macros with care──actual arguments to macros can cause
- unexpected changes, such as reading an extra character. You should avoid
- using the following types of arguments because they can produce unwanted
- side effects:
-
- ■ function calls
-
- ■ other macros
-
- ■ the increment (++) and decrement (--) operators
-
- ■ the assignment operator (=)
-
- For example, consider the following ISQ macro:
-
- #define ISQ(letter) ((letter) == 'q' || (letter) == 'Q')
-
- This macro detects whether a letter is an uppercase or lowercase 'Q' and
- is useful for testing if a user is quitting a program. You correctly use
- this macro as follows:
-
- ch = getchar();
-
- if (ISQ(ch))
- exit(0);
-
- In the preceding code, ch is a char variable; therefore, the if statement
- expands to
-
- if (((ch) == 'q' || (ch) == 'Q'))
-
- which is what you expect. However, if you use this macro incorrectly──for
- example, with a function call such as getchar(),
-
- if (ISQ(getchar()))
-
- it expands to an expression that doesn't do what you expect:
-
- if (((getchar()) == 'q' || (getchar()) == 'Q'))
-
- This example illustrates a common problem. The first call to getchar()
- reads a character and compares the value to 'q'. If that character is not
- a 'q', getchar() is called again to read a new character and to compare
- the new character to 'Q'. This is not what you intended, however. You want
- MAX to read only the first character and then to compare that character to
- both 'q' and 'Q'.
-
- Macros and Semicolons
-
- Never end a macro definition with a semicolon. For example, the following
- macro converts a printable character into a control character value:
-
- #define CTRL(x) ('x' - '@');
-
- The expression CTRL(A) expands to the expression ('A' - '@'); and yields
- the desired ASCII value 1 (Ctrl-A). However, the trailing semicolon causes
- a syntax error when you use the macro in an expression such as:
-
- printf("And 'A' prints as %c\n", CTRL(A));
-
- Note the syntax error that results when this expands to
-
- printf("And 'A' prints as %c\n", ('A'-'@'););
- └────────────────────── Wrong
-
- Macros and Quotes
-
- As in normal #define directives, preprocessor macros do not substitute
- actual arguments inside full quotation marks. For example, the following
- macro would be a useful tool for debugging:
-
- #define PERR(x) printf("The value of x is %d\n", x)
-
- Unfortunately it won't work. Because the first x is inside full quotation
- marks, it isn't expanded. However, the final x is expanded:
-
- int val = 5;
- PERR(val);
- └────┬───┘
- └───────────┬─────────────────────────────────────────── Expands to
- │
- ┌──────────────────────────────────┐
- printf("The value of x is %d\n", val);
-
- We can rectify this by using the preprocessor's "stringizing" operator #.
- When placed before a formal argument in a macro definition, the # causes
- that argument to be expanded and quoted. Thus, the correct way to define
- PERR is as follows:
-
- #define PERR(x) printf("The value of " #x " is %d\n", x)
- └─────────── Stringizing operator
-
- This correctly expands as:
-
- int val = 5;
- PERR(val);
- └───┬───┘
- └───────────┬──────────────────────────────────────────── Expands to
- │
- ┌──────────────────────────────────────────┐
- printf("The value of " "val" " is %d\n", val);
-
- The example works because the compiler joins adjacent quoted string
- constants into a single string. The result is that printf() correctly
- prints the following:
-
- The value of val is 5
-
-
- Using QuickC for Large Projects
-
- Imagine you are writing a text editor program such as the one shown in
- Figure 12-2 on p. 381. With sufficient memory, QuickC can easily load and
- compile programs of this size. However, the larger a program is, the
- longer it takes to compile, load, and save. Therefore, you can manage
- large programs more easily when you break them into several smaller files
- by logically grouping the subroutines according to use. This approach has
- several advantages.
-
- ■ When a program consists of several files, you need to recompile only
- those files that change.
-
- ■ Grouping subroutines by usage lets you easily trace the logic of the
- program during debugging.
-
- ■ Perfected subroutines that no longer need to be recompiled can be
- shared by many programs.
-
- QuickC Program Lists
-
- The QuickC "program list" feature compiles several small files or library
- modules and combines them into a single executable program. This lets you
- create complex, large programs from many small, easily maintained files.
- Before we examine this feature, enter and save the following three files:
- TEXED.C (Listing 12-5), KEYS.C (Listing 12-6), and FILE.C (Listing
- 12-7 on the following page). These are three small pieces of our
- imaginary text editor in Figure 12-2. Although these modules don't do
- much, they demonstrate the basics of using QuickC program lists.
-
- ──────────────────────────────────────────────────────────────────────────
- /* texed.c -- main entry point to the editor; the */
- /* menu and signal handlers are here */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char ch;
-
- while (1)
- {
- printf("\nTexEd Main Menu\n");
- printf("Select from:\n");
- printf("0) Quit\n\n");
- printf("1) Load File\n");
- printf("2) Save File\n");
- printf("3) Edit File\n");
- printf("Which: ");
- do
- {
- ch = getch();
- ch -= '0';
- } while (ch < 0 || ch > 3);
- printf("%d\n\n", (int)ch);
- switch(ch)
- {
- case 0: exit(0);
- case 1: Load_file(); break;
- case 2: Save_file(); break;
- case 3: Edit_file(); break;
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-5. The TEXED.C file.
-
- 2000-line text editor Broken into separate files
- ┌───────────────────┐ ┌─┌───────────────────┐
- │ │ │ │ Main() │
- │ │ │ │ Signal handlers │────texted.c
- │ │ │ │ menu() │
- │ │ │ ├───────────────────┤
- │ │ │ │ │
- │ │ │ │ Read and process │────keys.c
- │ │ │ │ typed keys │
- │ │ │ ├───────────────────┤
- │ │ │ │ │
- │ │───texted.c───│ │ Update the │────screen.c
- │ │ │ │ screen │
- │ │ │ ├───────────────────┤
- │ │ │ │ │
- │ │ │ │ Special commands │────cmds.c
- │ │ │ │ like 'delete line'│
- │ │ │ ├───────────────────┤
- │ │ │ │ │
- │ │ │ │ File read and │────file.c
- │ │ │ │ write routines │
- └───────────────────┘ └─└───────────────────┘
-
- Figure 12-2. A large program is often best split into several smaller and
- more manageable files.
-
- ──────────────────────────────────────────────────────────────────────────
- /* keys.c -- The keyboard input-handling routines */
- /* for the texed editor */
-
- Edit_file()
- {
- char ch;
-
- printf("\nYou are now in the editor.\n");
- printf("Press 'Q' to exit back to main menu.\n");
-
- do
- {
- ch = getch();
- putch(ch);
- } while (ch != 'Q');
-
- printf("\n\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-6. The KEYS.C file.
-
- ──────────────────────────────────────────────────────────────────────────
- /* file.c -- the file I/O routines for texed */
-
- Load_file()
- {
- printf("\nLoading ..... done.\n");
- }
-
- Save_file()
- {
- printf("\nSaving ...... done.\n");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-7. The FILE.C file.
-
- Next select Set Program List from the File menu and enter texed.mak in the
- File Name text box. This program list name is composed of two parts. The
- first, texed, is the name of your finished program. The second, the
- extension .mak, signifies that this program list file is a "make" file.
- (We'll explain make files in the next section.)
-
- After you enter the name texed.mak, QuickC prompts "`texed.mak' does not
- exist Create?". A Yes response displays the Edit Program List dialog box.
- This is where you specify the files in your program list. Enter the
- filenames TEXED.C, KEYS.C, and FILE.C. As you enter each, its name appears
- in the bottom window labeled Program List. After you enter all three
- files, your screen appears as in Figure 12-3. Now choose the Save List
- option to save your program list on disk and return to the main QuickC
- screen.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 12-3 can be found on p.382 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 12-3. The Edit Program List dialog box.
-
- At the bottom left of the QuickC screen you now see Program List: Texed.
- This message signals that you will build your program from several files,
- not only from the one currently loaded, and that those files are in the
- program list named Texed. TEXED.MAK has three files in it, and TEXED.C is
- one of them. Now, every time you open TEXED.C, QuickC reminds you that a
- .MAK file and various other files are connected to it.
-
- To compile a program from a program list, display the Compile dialog box,
- but this time, instead of selecting Compile File, select Build Program.
- Notice that each of your files is loaded in turn and compiled. After all
- three have been compiled, the Microsoft Overlay Loader executes. This link
- program combines your compiled files, along with any precompiled routines
- that you use from the standard C Library (such as printf()). This process
- creates a single, executable program that you can run from within QuickC.
-
- Program List Files
-
- Program list files contain rules and instructions that tell QuickC how to
- build your program. They are composed of four elements: comment lines
- (lines that begin with a # character), production rules, dependencies, and
- link commands. Look inside the TEXED.MAK file that follows. This is a make
- file, a subset of the kind used by the MAKE program.
-
- #
- # Program; Texed
- #
-
- .c.obj:───────────────────────────Production rule--turn a .C into a .OBJ
- qcl -c -W1 -Ze -AM $*.c─────────How to accomplish the above rule
-
- texed.obj : texed.c────────────┬────────────────────────────Dependencies
- │
- keys.obj : keys.c──────────────┤
- │
- file.obj : file.c──────────────┘
-
- Texed.exe : texed.obj keys.obj file.ob─More dependencies used to go from
- del Texed.lnk .OBJs to. EXEs
- echo texed.obj+ >>Texed.lnk
- echo keys.obj+ >>Texed.lnk
- echo file.obj >>Texed.lnk
- echo Texed.exe >>Texed.lnk
- echo Texed.map >>Texed.lnk
- link @Texed.lnk /NOI $(LDFLAGS);
-
- Production Rules in Program List Files
-
- A production rule is a description of how one file type is changed into
- another. For example, the production rule in our program list
-
- .c.obj:
- └──┴────────────────────────────────────Turn a .C file into a .OBJ file
-
- tells QuickC to change .C files (such as KEYS.C) into precompiled object
- files called .OBJ files (such as KEYS.OBJ). Microsoft's Overlay Loader
- later links these .OBJ files with functions from the standard C Library to
- produce the executable program.
-
- The line following the production rule is the command line:
-
- qcl -c -W1 -Ze -AM $*.c
-
- This tells QuickC how to make the transformation specified in the first
- line. It is the same command line you would enter at the MS-DOS prompt,
- except for the $*.c. The $*.c tells QuickC to use the name of a real .C
- file, such as KEYS.C, at this position in the command line. The other
- elements represent the following:
-
- Element Description
- ──────────────────────────────────────────────────────────────────────────
- qcl The command-line version of QuickC.
- -c Compile to a .OBJ file and stop. That is, do not continue by
- calling LINK.
- -W1 Set the compile time warning level to level 1.
- -Ze Handle language extensions, such as far, as reserved keywords.
- -AM Use the medium-memory model.
- ──────────────────────────────────────────────────────────────────────────
-
- Production rules are one of the features of QuickC program lists that make
- building programs easy. For example, when you select the Compile menu to
- compile KEYS.C, you don't have to enter the command line. Instead, QuickC
- runs the following MS-DOS command:
-
- qcl -c -W1 -Ze -AM keys.c
-
- and compiles KEYS.C into KEYS.OBJ.
-
- Dependency Lines in the Program List File
-
- Dependency lines tell QuickC how to create one file from two or more
- files. A dependency has the following form:
-
- target : infile1 infile2 ...
-
- This tells QuickC to create a new "target" file (usually a .OBJ or .EXE)
- if any infiles have changed and to make that target by running the
- specified MS-DOS command line. Examine the following dependency lines in
- TEXED.MAK:
-
- texed.obj : texed.c
-
- keys.obj : keys.c
-
- file.obj : file.c
-
- Texed.exe : texed.obj keys.obj file.obj
- del Texed.lnk
- ...
-
- Note that each of the first three lines is followed by a blank line (no
- MS-DOS command line). We'll discuss these first three dependencies first,
- then we'll cover the last in detail.
-
- When a dependency does not specify an MS-DOS command line, QuickC uses the
- previously defined production rule (.c.obj:) in place of the missing
- command line. For the first three lines, then, the command line derived
- from the production rule becomes the following:
-
- texed.obj : texed.c
- qcl -c -W1 -Ze -AM texed.c
-
- keys.obj : keys.c
- qcl -c -W1 -Ze -AM keys.c
-
- file.obj : file.c
- qcl -c -W1 -Ze -AM texed.c
-
- Running the Linker
-
- The dependency for TEXED.EXE, the executable file of your finished
- program, is as follows:
-
- Texed.exe : texed.obj keys.obj file.obj───────────────────────Dependency
-
- The dependency tells QuickC to create TEXED.EXE from TEXED.OBJ, KEYS.OBJ,
- and FILE.OBJ.
-
- The LINK program combines your .OBJ files with subroutines from the
- standard C Library and creates an executable (.EXE) program as the result.
- When you run the LINK program, it asks you the following series of
- questions:
-
- Object Modules [.OBJ]:
- Run File [.EXE]:
- List File [NUL.MAP]:
-
- First, LINK asks for the names of the .OBJ files you want to combine to
- form your program. Add a + to each file that you specify to tell LINK that
- you will add more .OBJ files. The LINK program continues to prompt for
- object modules until you list one without a trailing +:
-
- Object Modules [.OBJ]:texed.obj+
- Object Modules [.OBJ]:keys.obj+
- Object Modules [.OBJ]:file.obj
-
- Next, LINK prompts for the name of your executable program (Run File). In
- our example, we enter the name texed.exe.
-
- Finally, LINK asks for the name of a "map" file. A map file merely
- contains a cross-referenced listing of your executable program. Therefore,
- you respond as follows:
-
- List File [NUL.MAP]:texed.map
-
- Using LINK from a Text File
-
- When you run LINK, you have the option to use a text file that contains
- the prewritten answers to its questions. To do this, when you run LINK,
- specify the file by preceding its name with an "at" character, @, as
- follows:
-
- link @Texed.lnk
- └──────── File questions containing answers to LINK's questions
-
- To understand how this works, let's examine the MS-DOS commands that
- follow the dependency for TEXED.EXE in your TEXED.MAK program list file:
-
- Texed.exe : texed.obj keys.obj file.─────────────── Dependency
- del Texed.lnk──────────────────────┐
- echo texed.obj+ >>Texed.lnk │
- echo keys.obj+ >>Texed.lnk │
- echo file.obj >>Texed.lnk ├─────── MS-DOS command lines
- echo Texed.exe >>Texed.lnk │
- echo Texed.map >>Texed.lnk │
- link @Texed.lnk /NOI $(LDFLAGS);───┘
-
- First, the file TEXED.LNK is deleted in case it already exists. Next, five
- "redirect and append" commands (>>) tell echo to place the text into the
- file TEXED.LNK. TEXED.LNK now contains the following lines:
-
- texed.obj+
- keys.obj+
- file.obj
- Texed.exe
- Texed.map
-
- Finally, LINK executes using the file @TEXED.LNK as the file that contains
- the answers to its questions. This is equivalent to running LINK yourself
- and answering those questions as follows:
-
- Object Modules [.OBJ]:texed.obj+
- Object Modules [.OBJ]:keys.obj+
- Object Modules [.OBJ]:file.obj
- Run File [.EXE]:Texed.exe
- List File [NUL.MAP]:Texed.map
-
- Other Arguments to LINK
-
- The LINK command in our TEXED.MAK program list file has two arguments in
- addition to the @TEXED.LNK argument:
-
- link @Texed.lnk /NOI $(LDFLAGS);
- │ └──────────────────────────────── Other flags
- └────────────────────────────────── Don't ignore case
-
- The first, /NOI, tells LINK not to ignore case. That is, it tells LINK to
- treat uppercase letters as different from lowercase letters in variable
- and function names.
-
- The second, $(LDFLAGS), is a make macro definition that QuickC defines as
- nothing. To modify your .MAK program list file and add arguments to LINK,
- you must define LDFLAGS. See Chapter 11 in your Microsoft QuickC
- Programmer's Guide for information about this procedure. But beware, the
- LDFLAGS macro is the only macro that QuickC recognizes and preserves.
-
- Keeping Track of Changes
-
- QuickC keeps track of which files have changed in a program list. To see
- how this works, select Build Program from the Run menu. Now load the
- KEYS.C file and change it by inserting a blank line anywhere. Save that
- file and select Build Program again. This time, QuickC recompiles the
- KEYS.C file, but it does not compile the other two .C files because they
- haven't changed.
-
- Before QuickC runs a command line to create the target file from the
- infiles (based on a dependency in its program list), it first checks the
- modification dates for the target file and for each of the infiles. If
- this target was created after the infiles, QuickC doesn't need to
- recompile because the infiles have not changed. Specifically, in the
- dependency
-
- texed.obj : texed.c
-
- if TEXED.OBJ is newer than TEXED.C (its modification date and time is more
- recent), then QuickC does not recompile because TEXED.C has not changed
- since the last time it was compiled. But if TEXED.C is newer or if
- TEXED.OBJ doesn't yet exist, QuickC creates a new TEXED.OBJ by applying
- the .c.obj production rule and thus running the MS-DOS command line:
-
- qcl -c -W1 -Ze -AM texed.c
-
- This ability to know which files need to be recompiled makes QuickC a
- powerful tool for developing large and complex programs that are composed
- of many individual .C files.
-
- Header Files
-
- Programs formed from separate .C files often share identical declarations.
- For example, examine the two files in Listings 12-8a and 12-8b on the
- following page. These parts of a larger text editor program both use
- structures of the same pattern, and both use the #define directive to
- define the values OK and ERROR. (These listings are not intended to be
- compiled and run independently.) If you need to change the structures (by
- adding a member, for example), or to change the definition of ERROR (from
- 1 to -1, for example), you must make changes in both files (and possibly
- many other files if the text editor program uses those values throughout).
-
- ──────────────────────────────────────────────────────────────────────────
- #define OK 1
- #define ERROR 0
- menu()
- {
- struct key_struct {
- char key;
- unsigned char move;
- } *kp, *Read_kbd();
- int cur_key, cur_move;
-
- kp = Read_kbd();
- cur_key = kp->key;
- cur_move = kp->move;
- if (cur_key == ERROR)
- return (cur_move);
- return (cur_key);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-8a. The TEXED.C file.
-
- ──────────────────────────────────────────────────────────────────────────
- #define OK 1
- #define ERROR 0
- struct key_struct {
- char key;
- unsigned char move;
- };
-
- struct key_struct *Read_key()
- {
- struct key_struct k;
-
- k.key = getch();
- if (k.key == ERROR)
- k.move = getch();
- return (&k);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-8b. The KEYS.C file.
-
- Therefore, your program is easier to maintain if you gather such common
- definitions into a single, separate file called a "header file," or a .h
- file. Listing 12-9 shows one such header file for our text editor
- program. Now you can easily make changes that affect all files. Simply
- modify TEXED.C and KEYS.C to use the #include preprocessor directive, as
- shown in Listings 12-10a and 12-10b. Because we use full quotation marks
- with that directive (rather than angle brackets as with #include
- <stdio.h>), the compiler looks for the header file in our current working
- directory.
-
- ──────────────────────────────────────────────────────────────────────────
- #define OK 1
- #define ERROR 0
-
- struct key_struct {
- char key;
- unsigned char move;
- };
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-9. The texed.h header file.
-
- ──────────────────────────────────────────────────────────────────────────
- #include "texed.h"
- menu()
- {
- struct key_struct *kp, *Read_kbd();
- int cur_key, cur_move;
-
- kp = Read_kbd();
- cur_key = kp->key;
- cur_move = kp->move;
- if (cur_key == ERROR)
- return (cur_move);
- return (cur_key);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-10a. The TEXED.C file (modified).
-
- ──────────────────────────────────────────────────────────────────────────
- #include "texed.h"
- struct key_struct *Read_key()
- {
- struct key_struct k;
-
- k.key = getch();
- if (k.key == ERROR)
- k.move = getch();
- return (&k);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-10b. The KEYS.C File (modified).
-
- Variables in Header Files
-
- You can also place declarations in header files that make variables global
- to all files. However, you cannot initialize variables in header files
- that are shared by more than one .C file. That is, in the header file
- texed.h,
-
- char Last_key;─────────────────────────────────────────────────────Legal
- int Upper_flag = 1;─────────────────────────────────────────────Illegal
-
- the declaration for Last_key is always legal, but the declaration for
- Upper_flag is illegal because this .h file is specified by #include in
- several .C files.
-
- You can declare and initialize a global variable only once in a program.
- If you want to declare and initialize a global variable in one file and
- access that variable from another file, you must make this an explicit
- operation by placing the extern keyword in the second file, as follows:
-
- /* First file */ /* Second file */ /* Third file */
- ... ... ...
- int Key = 1; extern int Key; extern int Key;
- └────────────────────────────────────── Initialized once among
- several files
-
- The extern keyword tells QuickC that the integer Key is located in another
- file.
-
- If a global variable is not initialized as part of its declaration, you
- can declare it in all files without the extern keyword, as follows:
-
- /* First file */ /* Second file */ /* Third file */
- ... ... ...
- int Key = 1; extern int Key; extern int Key;
-
- The extern keyword tells QuickC that the integer Key is located in another
- file.
-
- If a global variable is not initialized as part of its declaration, you
- can declare it in all files without the extern keyword, as follows:
-
- /* First file */ /* Second file */ /* Third file */
- ... ... ...
- int Key; int Key; int Key;
-
- This is the same as declaring it once in a header file and then specifying
- that header with #include, as follows:
-
- /* Header file "head.h" */
- ...
- int Key;
-
- /* First file */ /* Second file */ /* Third file */
- ... ... ...
- #include "head.h" #include "head.h" #include "head.h"
-
- ──────────────────────────────────────────────────────────────────────────
- Dependencies in Header Files
- Because a change in a header file results in a change in a .C file, you
- might wonder if you can place header files into your QuickC program list
- as a dependency, as follows:
-
- texed.obj : texed.c texed.h
-
- keys.obj : keys.c texed.h
-
- As this dependency is written, it tells QuickC (in our program list) to
- recompile TEXED.OBJ if either TEXED.C or texed.h changes and to recompile
- KEYS.OBJ if either KEYS.C or texed.h changes.
-
- QuickC does allow you to place header file dependencies into your program
- lists: It recognizes and maintains them, but it does not treat .h files as
- real dependencies. That means, for example, that TEXED.OBJ is not
- recompiled if only texed.h changes. This is intentional and not a bug.
- ──────────────────────────────────────────────────────────────────────────
-
- Libraries
-
- In addition to listing .C files in a QuickC program list file, you can
- also list library (.LIB) files. Libraries are files that contain
- precompiled .OBJ files that you can use as part of a program.
-
- During the course of your programming, you will develop many general
- subroutines that can be used in many programs. By placing those
- subroutines into a special library, you can access them through a QuickC
- program list without having to recompile them. For example, consider the
- following three subroutines: leftstr.c (Listing 12-11), midstr.c (Listing
- 12-12), and rightstr.c (Listing 12-13). (These subroutines, shown on
- pages 392-93, are C analogs to the BASIC functions LEFT$, MID$, and
- RIGHT$.)
-
- To create a library for these three subroutines, enter them using the
- QuickC editor, and then save each as an individual .C file. Now exit
- QuickC and compile each with qcl and the following MS-DOS commands:
-
- qcl /c /AM leftstr.c
- qcl /c /AM midstr.c
- qcl /c /AM rightstr.c
-
- qcl is the command-line version of QuickC. The /c tells QuickC to create a
- .OBJ file from the .C file, and /AM tells QuickC to use the medium-memory
- model.
-
- After you generate the three .OBJ files, you create a library for them by
- running the LIB program and answering its questions, as follows:
-
- Library name:basic.lib
- Library does not exist. Create?y
- Operations:+leftstr.obj&
- Operations:+midstr.obj&
- Operations:+rightstr.obj
- List file:
-
- The first and second lines tell LIB to create a library named BASIC.LIB.
- In the three Operations: lines, the + tells LIB that we are adding a .OBJ
- file to the library. The & following two of the lines is a signal that
- more files will be added. At the List file prompt we simply press Enter
- because our library is small, and we don't need a list of its contents.
- After a short wait, QuickC produces a library file named BASIC.LIB that we
- can place into any program list. To return to the QuickC menu, enter exit
- at the MS-DOS prompt.
-
- Now we'll create a program to test our library and demonstrate how to use
- a library from a program list. Enter the TEST.C program (Listing 12-14 on
- p. 393) and save it on disk. Next, choose Set Program List from the File
- menu and enter TEST.MAK as the name of the program list.
-
- After you press Enter and answer Yes to Test.mak doesn't exist. Create?,
- the Edit Program List dialog box appears. Select test.c as the first item
- in the list. Notice that the name of our library is not displayed. That's
- okay; simply type basic.lib. Finally, save this program list.
-
- ──────────────────────────────────────────────────────────────────────────
- /* leftstr.c -- a C version of BASIC's LEFT$ */
-
- #include <stdio.h>
-
- char *Leftstr(char *str, int cnt)
- {
- static char *cp = NULL;
- char *malloc();
-
- if (cnt > strlen(str))
- cnt = strlen(str);
- if (cp != NULL)
- free(cp);
- if ((cp = malloc(cnt + 1)) == NULL)
- return (NULL);
- strncpy(cp, str, cnt);
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-11. The leftstr.c subroutine.
-
- ──────────────────────────────────────────────────────────────────────────
- /* midstr.c -- a C version of BASIC's MID$ */
-
- #include <stdio.h>
-
- char *Midstr(char *str, int where, int cnt)
- {
- static char *cp = NULL;
- char *malloc();
-
- if (cnt > strlen(str + where))
- cnt = strlen(str + where);
- if (cp != NULL)
- free(cp);
- if ((cp = malloc(cnt + 1)) == NULL)
- return (NULL);
- strncpy(cp, str+where, cnt);
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-12. The midstr.c subroutine.
-
- ──────────────────────────────────────────────────────────────────────────
- /* rightstr.c -- a C version of BASIC's RIGHT$ */
-
- #include <stdio.h>
-
- char *Rightstr(char *str, int cnt)
- {
- static char *cp = NULL;
- char *malloc();
-
- if (cnt > strlen(str))
- cnt = strlen(str);
- if (cp != NULL)
- free(cp);
- if ((cp = malloc(cnt + 1)) == NULL)
- return (NULL);
- strcpy(cp, str + strlen(str) - cnt);
- return (cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-13. The rightstr.c subroutine.
-
- ──────────────────────────────────────────────────────────────────────────
- /* test.c -- tests the routines in basic.lib */
- /* Program list: test.c and basic.lib */
-
- #include <stdio.h>
-
- main()
- {
- static char string[] = "This is a test.";
- char *cp, *Leftstr(), *Midstr(), *Rightstr();
-
- printf("Testing: \"%s\"\n", string);
-
- if ((cp = Leftstr(string, 4)) == NULL)
- {
- printf("Error in Leftstr()\n");
- exit(1);
- }
- printf("Leftstr() returned: \"%s\"\n", cp);
-
- if ((cp = Midstr(string, 4, 5)) == NULL)
- {
- printf("Error in Midstr()\n");
- exit(1);
- }
- printf("Midstr() returned: \"%s\"\n", cp);
-
- if ((cp = Rightstr(string, 5)) == NULL)
- {
- printf("Error in Rightstr()\n");
- exit(1);
- }
- printf("Rightstr() returned: \"%s\"\n", cp);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 12-14. The TEST.C program.
-
- At the QuickC editor, choose Compile from the Run menu. Because we are
- compiling from a program list, use Build Program to compile TEST.C and
- then combine it with the subroutines in BASIC.LIB.
-
- One additional advantage offered by .LIB files is that you can place them
- in your environmental LIB directory. From there, QuickC can find them no
- matter where you are in the directory hierarchy. The result of all this is
- that you need only one copy of common subroutines in a single library, and
- you can access those subroutines through a program list from any
- directory.
-
- Quick Libraries
-
- QuickC offers another kind of library, called a Quick Library. This
- alternative library can be loaded into memory when you first run QuickC.
- The advantage it offers is that you don't need to use a program list to
- access the subroutines in it.
-
- Let's build a Quick Library using the same subroutines that we used to
- build BASIC.LIB: leftstr.c (Listing 12-11), midstr.c (Listing 12-12),
- and rightstr.c (Listing 12-13). Begin by running qcl to create the .OBJ
- files:
-
- qcl /c /AM leftstr.c
- qcl /c /AM midstr.c
- qcl /c /AM rightstr.c
-
- Now run LINK to create the Quick Library:
-
- link
- Object Modules [.OBJ]:c:\lib\quicklib.obj +
- Object Modules [.OBJ]:leftstr.obj +
- Object Modules [.OBJ]:midstr.obj +
- Object Modules [.OBJ]:rightstr.obj /Q
- Run File [C:QUICKLIB.QLB]:basic.qlb /NOI
- List File [NUL.MAP]:
- Libraries [.LIB]:
-
- In this example, c:\lib\quicklib.obj is the full pathname of a special
- object file that you must use as the first listing in your Quick Library.
- (We've stored the file in the C:\LIB directory, but you can use any
- directory. We suggest that you specify your environmental LIB directory.)
- The + characters tell LINK that we will list more object files. The /Q
- after the last .OBJ tells LINK that this is a Quick Library. Then we
- specify basic.qlb as the name of our Quick Library and follow that name
- with /NOI, which tells LINK not to ignore case. Finally, we press Enter to
- skip the final prompts, List File and Libraries.
-
- Now you can have QuickC load the BASIC.QLB Quick Library every time you
- run QuickC. To do this, use the QuickC /l command-line argument, as
- follows:
-
- qc /lbasic.qlb test.c
-
- This tells QuickC first to load the BASIC.QLB Quick Library and then to
- load TEST.C in the editor. (Before you do this on your system, erase the
- TEST.MAK program list; otherwise, QuickC will try to use the .OBJ files
- from the disk rather than from your new Quick Library.)
-
- Now, when you compile TEST.C, QuickC always finds the functions Leftstr(),
- Midstr(), and Rightstr() in memory. Notice how much faster TEST.C compiles
- when you use this approach.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 4 C AND THE HARDWARE
- ────────────────────────────────────────────────────────────────────────────
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 13 Keyboard and Cursor Control
-
- Almost every PC program needs to get information from the keyboard and to
- display information on a monochrome or color screen. So far, our programs
- have used the standard C Library functions such as getchar(), scanf(),
- putchar(), and printf(), and occasionally we've used command-line
- arguments. Using these approaches produces portable code. However, it also
- produces a bland interface that fails to take advantage of many PC
- capabilities. If you want your programs to do more than display mere text
- on the screen, study this and the next two chapters, which explore PC I/O.
- You will learn how to use function keys and cursor control keys, how to
- control the location and appearance of text on the screen, how to use
- color in text and in graphics, and how to construct graphic figures.
-
- In this chapter, we examine the keyboard and cursor control. We look at
- QuickC's numerous I/O functions and provide a more detailed discussion of
- the generic getchar() and the PC-specific getche() and getch(). We
- describe scan codes, show how to use ANSI.SYS to redefine keys and to
- provide cursor control, and discuss BIOS routines. Finally, we use the
- int86() function to create a library of BIOS-based screen-control and
- cursor-control functions.
-
-
- Keyboard Input Functions
-
- You use the standard C I/O functions to read and to display a variety of
- input: characters, strings, integers, and floating-point numbers. But the
- standard input functions don't detect non-ASCII keys, such as the function
- keys. And they don't provide many of the input control features typically
- required by programs such as word processors, spreadsheets, and games. To
- get that control, we need to process input at a "lower" level than that of
- standard I/O functions.
-
- Three QuickC functions read keyboard input character by character:
- getchar(), getche(), and getch(). Each reads one character at a time and
- reports its value to the calling program. (Actually, getchar() is not a
- true function; instead, it is defined as a macro in stdio.h.)
-
- Input Examples
-
- The programs on the opposite page illustrate how the three input functions
- respond to the same input──in this case, the input is the word hat
- followed by Enter.
-
- The GETCHAR.C program (Listing 13-1) produces the following output:
-
- Please enter a word.
- hat<Enter>
- 1.. 2.. 3..───────────────────────Counting delayed until you press Enter
- 3 characters altogether
-
- Counting doesn't start until you type the word and press Enter.
-
- Next, look at GETCHE.C (Listing 13-2), which generates the following
- output:
-
- Please enter a word.
- h1.. a2.. t3.. <Enter>───────────────────────────────────Immediate count
- 3 characters altogether
-
- This time each letter is counted as it is typed.
-
- Finally, examine GETCH.C (Listing 13-3), which produces the following
- output:
-
- Please enter a word.
- 1.. 2.. 3..──────────────────Input not displayed 3 characters altogether
-
- This time the input is invisible; only the output is displayed.
-
- The functions behave differently, and you use them for different purposes.
- The getchar() function buffers and echoes input; getche() does not buffer
- input but echoes it; getch() neither buffers nor echoes input. Buffered
- input goes into a temporary storage area before being transferred to the
- calling program. (Pressing Enter "empties" the buffer.) Echoed input is
- displayed on the screen.
-
- The getchar() function handles arrow keys or function keys inconsistently
- from one system to another. Try using GETCHAR.C with these keys as input
- and see how your system responds. The getche() and getch() functions do
- read these keys in a consistent manner, however. Try GETCHE.C, for
- example, with an arrow key or function key as input. Each of these keys,
- as you'll see, is counted as two keystrokes, and characters other than
- those you typed are echoed on the screen. This is perfectly proper and
- reasonable behavior, as you'll see when we discuss scan codes.
-
- ──────────────────────────────────────────────────────────────────────────
- /* getchar.c -- using getchar() */
-
- #include <stdio.h>
- main()
- {
- int count = 1;
-
- printf("Please enter a word.\n");
- while (getchar() != '\n') /* here it is */
- printf("%d.. ", count++);
- printf("\n%d characters altogether\n", count - 1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-1. The GETCHAR.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* getche.c -- using getche() */
- #include <conio.h> /* note different file included */
- main()
- {
- int count = 1;
-
- printf("Please enter a word.\n");
- while (getche() != '\r') /* changed comparison */
- printf("%d.. ", count++);
- printf("\n%d characters altogether\n", count - 1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-2. The GETCHE.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* getch.c -- using getch() */
- #include <conio.h>
- main()
- {
- int count = 1;
-
- printf("Please enter a word.\n");
- while (getch() != '\r')
- printf("%d.. ", count++);
- printf("\n%d characters altogether\n", count - 1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-3. The GETCH.C program.
-
- The getchar() Buffer
-
- The program using getchar() doesn't receive the generated code until this
- buffer is flushed. This occurs when you press Enter or when the buffer is
- filled. Because the getchar() buffer is 512 bytes, normal keyboard input
- does not fill it. QuickC sets up this input buffer when any input function
- from the stdio.h family is called, and all the input functions of that
- family, such as scanf() and gets(), share it. Thus, when your program uses
- both scanf() and getchar(), they share the same input buffer.
-
- Differences in Usage
-
- First, getchar() requires the stdio.h include file, while getch() and
- getche() use conio.h, the include file for console I/O functions. Second,
- getch() and getche use \r instead of \n to represent the action of Enter,
- and they do not interpret Ctrl-Z as an end-of-file indicator.
-
- The reason for these last two differences is that getchar(), by default,
- reads input in the text mode, and getch() and getche() read input in the
- binary mode. In the text mode, as you may recall, the carriage
- return/linefeed combination is converted to a linefeed on input, and the
- linefeed is converted to a carriage return/linefeed on output. The binary
- mode makes no conversions. As a result, getchar() uses \n to detect the
- Enter key, but getch() and getche() must use \r.
-
- The second difference is that getchar(), when used in the text mode,
- recognizes the Ctrl-Z character as marking the end of a file. This lets
- you simulate the end-of-file condition from the keyboard by entering
- Ctrl-Z. The binary mode used by getche() and getch() does not recognize
- Ctrl-Z (or any other character) to mark the end of a file. As a result,
- constructions such as
-
- while((ch = getche()) != EOF) /* NO */
-
- do not work for keyboard input. When using getch() or getche() in such a
- loop, you must specify a keyboard character to indicate the end of input.
- We've used \r, and in many later examples we'll use the Esc key.
-
- Although the getchar() function uses text mode by default, you can call
- QuickC's setmode() function to place getchar() in binary mode. (See
- setmode() in the Microsoft QuickC Run-Time Library Reference for details.)
- However, you cannot switch getche() and getch() to text mode.
-
- ──────────────────────────────────────────────────────────────────────────
- Reminder
- Don't mix buffered functions such as getchar() and gets() with unbuffered
- functions such as getche() and getch(). The buffered functions transmit
- characters from the input buffer when it is flushed; the unbuffered
- functions read keys as they are pressed. Thus, a program mixing buffered
- and unbuffered input functions might not process the characters in the
- order they were typed.
- ──────────────────────────────────────────────────────────────────────────
-
- Table 13-1 summarizes the different behavior of the character input
- functions.
-
- Table 13-1 Character Input Functions
- getchar() getche() getch()
- ──────────────────────────────────────────────────────────────────────────
- Buffered o
- Echoes o o
- Uses \n o
- Uses \r o o
- Uses stdio.h o
- Uses conio.h o o
- Text mode (default) o
- Binary mode o o
- Backspace editing o
- Reads ASCII keys o o o
- Reads non-ASCII keys o o
- ──────────────────────────────────────────────────────────────────────────
-
- Typical Uses for Character Input Functions
-
- The primary advantage of using the buffered getchar() is that it lets
- users edit input with the Backspace key before they send it to the
- program. The nonbuffered form, on the other hand, requires users to type
- less because they needn't press Enter. For example, suppose your program
- uses the following prompt:
-
- Continue? <y/n>
-
- With getchar(), the user must type y and press Enter, while getche()
- requires only a y. Likewise, the getche() function is useful in programs
- that use a typed character to select a menu item. Consider the following
- fragment:
-
- while ((ch = getchar()) != 'q') /* oops example */
- switch (ch)
- {
- case 'a': ...
- case 'b': ...
- case 'c': ...
- default: printf("Not a valid choice\n");
- }
-
- To choose case a, the user types a and presses Enter. The loop processes
- the a, recycles and processes the \n generated by the Enter key, and
- prints the default message. Replacing getchar() with getche() eliminates
- the need to press the Enter key and hence the need to add programming to
- process the extraneous \n.
-
- The non-echoed, nonbuffered getch() is useful, of course, when you don't
- want to display input on the screen. For example, you might use the k key
- to move an image on the screen. Also, a program that requires a user to
- type a secret password shouldn't display it on the screen.
-
- Let's use getch() to construct a simple password program. In a real
- application, we would ensure password security by also using encryption
- and periodic updating. In the PASSWORD.C program (Listing 13-4 on the
- following page), we'll build the password into the program and concentrate
- on processing the user's input.
-
- ──────────────────────────────────────────────────────────────────────────
- /* password.c -- requires a password to complete the */
- /* program; illustrates a use of getch() */
-
- #include <stdio.h>
- #include <conio.h>
- #include <string.h>
- #define GUESS_LIMIT 4
- #define WORD_LIMIT 10 /* maximum length of password */
- #define TRUE 1
- #define FALSE 0
- char *Password = "I'mOk";
- main()
- {
- int g_count = 0; /* guesses taken */
- int w_count; /* letters accepted */
- int in_count; /* letters entered */
- char entry[WORD_LIMIT + 1];
- char ch;
- int correct, go_on;
-
- do
- {
- puts("Enter the secret password.");
- in_count = w_count = 0;
- /* the following loop accepts no more chars */
- /* than entry[] will hold, but keeps track */
- /* of total number typed */
- while ((ch = getch()) != '\r')
- {
- if (w_count < WORD_LIMIT)
- entry[w_count++] = ch;
- in_count++;
- }
- entry[w_count] = '\0';
- if (in_count != w_count)
- correct = FALSE; /* too many chars */
- else
- correct = (strcmp(entry, Password) == 0);
- g_count++;
- go_on = !correct && g_count < GUESS_LIMIT;
- if (go_on)
- puts("\nNo good; try again.");
- } while (go_on);
- if (!correct)
- {
- puts("Sorry, no more guesses. Bye.");
- return(1);
- }
- puts("Welcome to Swiss bank account 2929100.");
- puts("Your current balance is $10,232,862.61.");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-4. The PASSWORD.C program.
-
- Note the following loop:
-
- while ((ch = getch()) != '\r')
- {
- if (w_count < WORD_LIMIT)
- entry[w_count++] = ch;
- in_count++;
- }
-
- It uses an if statement to prevent overflowing the array, yet it continues
- to read additional characters if the limit is exceeded. We could have made
- this loop stop at the character limit, but that would tell the illicit
- user the number of characters in the actual password.
-
- The structure of the do while loop reflects the two conditions that
- terminate the loop: a correct password or too many attempts. If the loop
- ends and correct is still false, the program knows that the reason for
- termination was too many attempts.
-
- ──────────────────────────────────────────────────────────────────────────
- Character and String Input in BASIC and C
- If you are used to BASIC, you know that you can read a character from the
- keyboard (with no echo) using the INKEY$ function. This function is
- similar to C's getch(). C conveniently provides the alternative getche()
- function for character input with echo, while in BASIC you would need a
- separate PRINT statement to echo the input character. Note that neither
- the BASIC function nor the C functions mentioned recognize Ctrl-Z as a
- signal for the end of file.
-
- Both BASIC and C provide generalized input functions that can handle a
- series of numeric or string variables. In BASIC, the INPUT statement
- allows you to supply a prompt string and accept input into one or more
- variables. For example:
-
- INPUT "ENTER NAME AND AGE: ",NAME$,AGE
-
- The scanf() function in C is similar in that it allows you to receive
- input for a series of variables of different types. The scanf() function,
- however, allows you a much greater degree of control over the format of
- each input value, the interpretation of white space, and the characters
- used to separate input values. Unlike the INPUT function, scanf() makes no
- provision for a prompt string, so it is normally preceded by a separate
- printf() statement with the desired string.
-
- In a typical trade-off for these two languages, BASIC's INPUT statement
- provides very rudimentary error checking and editing of the input line.
- While scanf() will reject any input that does not match the
- specifications, it does not terminate or restart when bad input is
- encountered. The C programmer is responsible for error checking to
- determine whether the values entered are actually reasonable and complete.
- ──────────────────────────────────────────────────────────────────────────
-
-
- Reading Non-ASCII Keys
-
- Some keys, such as the function keys, the cursor control keys, and Alt-key
- combinations, have no ASCII code. How can a QuickC program read them?
- Before answering this question, we need to discuss how the keyboard
- actually works.
-
- The Keyboard Processor and Scan Codes
-
- Information does not flow directly from the keyboard to a C program.
- Instead, pressing (or closing) a given key generates a "closure" code that
- indicates the physical location of the key. A microprocessor within the
- keyboard reads this code and then generates a new code, called a "system
- code." It also reports if the user is holding down the Shift, Ctrl, or Alt
- key. Finally, it generates a third code (two bytes called the "extended
- scan code") for the keystroke (or keystroke combination) and places it in
- a storage area called the "keyboard buffer." If the key is still "closed"
- after a predetermined period of time elapses, another keystroke is placed
- in the buffer. Thus, you can generate a string of characters by holding
- down a key. Releasing the key generates an "opening" code that tells the
- keyboard microprocessor that you are finished with that key. By default,
- the keyboard buffer holds a maximum of 16 extended scan codes.
-
- The purpose of the keyboard buffer is to hold characters that are typed
- faster than an application can process them. It is distinct from the
- buffer created for the stdio.h input functions.
-
- The getch() and getche() functions do not read the keyboard directly.
- Instead, they read the extended scan codes in the keyboard buffer. Because
- this code is more extensive than the standard ASCII code, programs can use
- it to identify function keys, cursor keys, and other keys lacking an ASCII
- code. (The only difference between getch() and getche() is that getche()
- echoes input; therefore, our next discussions about getch() actually apply
- to both functions.)
-
- Using Scan Codes
-
- Each extended scan code is two bytes. The first byte, which we call the
- "ASCII byte," contains the ASCII code, if any, for the keystroke. The
- second byte, which we call the "scan byte," contains a scan code for the
- key. This code is based on the physical position of the key on the
- keyboard, and, in some cases, on whether the Shift, Ctrl, or Alt key is
- pressed.
-
- The contents of an extended scan code reveal whether or not it represents
- an ASCII character. If it does, the ASCII byte is nonzero. If it does not,
- the ASCII byte is set to zero, and the numeric value of the scan byte
- encodes the keystroke or keystroke combination. For example, in Figure
- 13-1, the uppercase Q character is represented by an ASCII byte of 81
- because that is its ASCII code. The scan code of 16 means the Q key is the
- sixteenth key in the keyboard numbering scheme. The F1 key has no ASCII
- representation, so the ASCII byte is 0. However, because it is the 59th
- key on the keyboard, the scan byte is 59.
-
- ┌────────┬────────┐ ┌────────┬────────┐
- Press Q │ 81 │ 16 │ Press F1 │ 00 │ 59 │
- └────────┴────────┘ └────────┴────────┘
- ASCII Scan └────────┬────────┘
- byte byte Extended scancode
-
- Figure 13-1. Scan codes.
-
- How does getch() use these extended codes? First, it looks at the ASCII
- byte. If the byte is nonzero, getch() knows it has found an ASCII
- character. It returns that value and then skips the scan byte and moves to
- the next ASCII byte. For example, it returns 0x41 for Shift-A, 0x61 for a,
- and 0x01 for Ctrl-A.
-
- When the ASCII byte is 0, getch() lets the program know it has found a
- non-ASCII keystroke by returning a value of 0. Because getch() needs to
- know which non-ASCII character was pressed, it does not skip to the next
- ASCII byte; instead, it goes to the scan byte. Thus, the next call to
- getch() results in it reading the scan code that goes with the 0 ASCII
- byte. In other words, only one call of getch() is needed to read an ASCII
- keystroke, but two calls are needed to read a non-ASCII keystroke. Also,
- the scan codes are returned only for the non-ASCII keystrokes.
-
- Suppose, for example, that you type the Shift-Q combination and then press
- the F1 key. The codes 81 16 00 59 are placed in the keyboard buffer. The
- first call to getch() returns the 81. The next call to the function skips
- to the 00 and returns that value, and the third call returns the 59. Thus,
- a program that plans to use the F1 key must look for return values of 0.
- When it encounters one, the program should check to see if the next call
- returns 59. If so, F1 was pressed. The return value of 0 is a flag that
- says, "Special processing required here."
-
- Now, how does getchar() process non-ASCII characters? It copies ASCII
- values into the program buffer created by the standard I/O buffer. When it
- finds a 0 ASCII byte in the buffer, it skips to the next input character.
- The 0 ASCII bytes and the scan codes never make it to the I/O buffer, let
- alone to the program.
-
- A Scan Code Example
-
- The SCANCODE.C program (Listing 13-5 on the following page) demonstrates
- these functions by reading input. If the input is ASCII, the program
- prints the ASCII code. If the input is non-ASCII, the program prints the
- scan codes.
-
- Following is some sample output:
-
- Press keys and see the codes!
- Press the Esc key to quit.
-
- Q has ASCII code 81──────────────────────────────────────────────Shift-Q
- Scan code is 59───────────────────────────────────────────────────────F1
- t has ASCII code 116────────────────────────────────t has ASCII code 116
- ^T has ASCII code 20──────────────────────────────────────────────Ctrl-T
-
- ──────────────────────────────────────────────────────────────────────────
- /* scancode.c -- displays ASCII or scan code */
- /* This program illustrates using getch() to detect */
- /* special keys such as function keys. */
-
- #include <conio.h>
- #define ESC '\033' /* ESC key */
- main()
- {
- int ch;
-
- printf("Press keys and see the codes!\n");
- printf("Press the Esc key to quit.\n");
-
- while ((ch = getch()) != ESC)
- {
- if (ch != 0)
- {
- if (ch <= 32) /* control characters */
- printf("^%c has ASCII code %d\n",
- ch + 64, ch);
- else
- printf("%c has ASCII code %d\n", ch, ch);
- }
- else /* ch IS 0 */
- {
- ch = getch(); /* get scan code */
- printf("Scan code is %d\n", ch);
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-5. The SCANCODE.C program.
-
- What happens if you use getch() and getche() without checking for the zero
- value? They would interpret the ASCII byte and scan byte as two ASCII
- bytes, thus interpreting 00 59 as code for Ctrl-@ and for the semicolon
- character, instead of F1.
-
- Scan Code Values
-
- In this book we will use only those codes listed below in an include file
- called keys.h. When we need to use these keys, you can include that file,
- which is shown in Listing 13-6.
-
- Not all keystrokes produce scan codes. For example, Shift, Ctrl, and Alt
- modify the scan codes produced when other keys are pressed. The SCANCODE.C
- program demonstrates this. For example, press Alt. Nothing happens until
- you simultaneously press a second key.
-
- The operating system normally intercepts the Ctrl-Break combination as the
- code for terminating a program. Thus, getch(), getche(), and getchar()
- never read Ctrl-Break. (We will discuss how to handle Ctrl-Break later in
- this chapter.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* keys.h -- scan codes for several keys */
-
- #define F1 59 /* function key F1 */
- #define F2 60 /* function key F2 */
- #define F3 61 /* and so on */
- #define F4 62
- #define F5 63
- #define F6 64
- #define F7 65
- #define F8 66
- #define F9 67
- #define F10 68
- #define HM 71 /* Home key */
- #define UP 72 /* Up Arrow */
- #define PU 73 /* Page Up */
- #define LT 75 /* Left Arrow */
- #define RT 77 /* Right Arrow */
- #define END 79 /* End key */
- #define DN 80 /* Down Arrow */
- #define PD 81 /* Page Down */
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-6. The keys.h include file.
-
-
- Console I/O Functions
-
- The getch() and getche() functions belong to the console I/O family of
- functions. These functions communicate with the console (the keyboard and
- screen) more directly than do the I/O functions of the stdio.h family.
- However, unlike the stdio.h family, console I/O functions are not in the
- standard C Library and are therefore not necessarily portable. They are
- important because they provide special services not offered by the
- standard I/O package. The console I/O functions declared in the conio.h
- header file are:
-
- cgets()
- cprintf()
- cputs()
- cscanf()
- getch()
- getche()
- putch()
- ungetch()
-
- kbhit()
- inp()
- outp()
-
- The first eight functions in this list closely resemble the stdio.h
- functions with corresponding names. For example, cgets() resembles gets(),
- cprintf() is similar to printf(), and so on. We've already seen the
- kbhit() function. We'll discuss the inp() and outp() functions in Chapter
- 14.
-
- Character Output Functions
-
- Now that we've used the character input functions, let's look at the
- console character output functions. The putch() function works much like
- putchar(). One difference is that putchar() is buffered and putch() is
- not. This means that putch() output goes to the screen directly; putchar()
- output goes to an intermediate storage area first. The second difference
- is that putchar() works in text mode by default, while putch() works in
- binary mode. The main practical consequence of this is in how newlines are
- handled. The C newline character (\n) represents going to the beginning of
- the next line. This actually consists of two operations: a linefeed (LF)
- and a carriage return (CR). In QuickC, the newline character is
- represented by the LF character, ^J. The text mode produces the desired
- effect by mapping an LF to a CR-LF combination on the screen. In the
- binary mode, no such mapping takes place, so you must explicitly generate
- both an LF and a CR character (\n and \r).
-
- A third difference is that the text mode used by putchar() interprets a
- tab character (\t) as a tabbing instruction; the binary mode used by
- putch() interprets it as an ASCII value to be displayed. With the IBM
- character set, using putch() to generate a tab character results in a
- small circle on the screen.
-
- The REKEY.C program (Listing 13-7) demonstrates how to use the console
- I/O functions getch() and putch() to map the characters you type to a
- different set of characters on the screen.
-
- Note that we initialize the Newchars[] array to 26 letters. The
- construction Newchars[ch - 'a'] causes the array index to be zero when ch
- is a, corresponding to the array value q. Similarly, if ch is b, the index
- is 1; and the array value is the next letter in the initialization string,
- w. The initialization continues in this fashion, as shown in Figure 13-2.
-
- The toupper() and tolower() QuickC macros (defined in ctype.h) convert
- cases; thus, we don't need to use another 26-element array for uppercase
- letters. Note the way in which the program explicitly translates Enter
- (read by getch() as \r) to an output of \r and \n.
-
- ┌────────┬────────┬────────┬────────┬────────┬────────┐
- │ q │ w │ e │ r │ t │ │
- └────────┴────────┴────────┴────────┴────────┴────────┘
- 'a'-'a' 'b'-'a' 'c'-'a' 'd'-'a' 'e'-'a'
-
- Newchars [0] [1] [2] [3] [4]
-
- Figure 13-2. The Newchars[ch - a] array.
-
- ──────────────────────────────────────────────────────────────────────────
- /* rekey.c -- transliterates typed input */
- /* This program illustrates getch() and putch(). */
-
- #include <stdio.h>
- #include <conio.h>
- #include <ctype.h>
- #define ESC '\033' /* the escape key */
- char Newchars[] = "qwertyuiopasdfghjklzxcvbnm";
- /* values to be assigned to the a,b,c keys, etc. */
- main()
- {
- char ch;
-
- printf("Type characters and see them transformed;\n");
- printf("Press the Esc key to terminate.\n");
- while ((ch = getch()) != ESC)
- if (islower(ch))
- putch(Newchars[ch - 'a']);
- else if (isupper(ch))
- {
- ch = tolower(ch);
- putch(toupper(Newchars[ch - 'a']));
- }
- else if (ch == '\r')
- {
- putch('\n');
- putch('\r');
- }
- else
- putch(ch);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-7. The REKEY.C program.
-
- Console String I/O
-
- Often we want a program to read a string──for example, the name of a file.
- Or we want to generate a string. These activities can be done character by
- character, but it is more convenient to use functions designed to handle
- strings. The console functions cgets() and cputs() perform these tasks. In
- action, these functions are similar to gets() and puts(), but there are
- some differences.
-
- Like gets(), cgets() reads an input string into an array. However, the
- first element of the array holds the maximum allowable size of the input
- string, including a terminating null character. You must initialize this
- element correctly. The second element holds the actual number of bytes
- used, and it is set by cgets() after it reads the input. The string itself
- starts at the third element. Thus, the array must be two bytes longer than
- the maximum string size, including a null character, as shown in Figure
- 13-3 on the following page.
-
- ┌─────────Available space───────────┐
- 0 1 │ 2 3 4 5 6 7 │
- ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- │ 6 │ 4 │ L │ A │ R │ A │ \O │ │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ └──cgets() puts string here
- │ └──cgets() puts string size here
- └──You put available space here
-
- Figure 13-3. Storage of an array read by cputs().
-
- The cgets() function reads input until the maximum length of the string
- (not counting the null character) is reached or until the user presses
- Enter. The console beeps if you try to read beyond the limit, and you
- can't enter additional characters. The function will, however, let you use
- the Backspace key to correct input. This function returns a pointer to the
- beginning of the stored string; that is, if the array name is str, cgets()
- returns a pointer to str[2].
-
- The cputs() function takes a pointer to a string as its argument and
- displays that string on the console. Unlike puts(), cputs() does not
- append a newline character; therefore, you must explicitly include the
- \r\n combination to generate a new line. The return value is the last
- character written. The function returns 0 if the string is a null string
- and -1 if there is an error.
-
- The short STRIO.C program (Listing 13-8) illustrates how the two
- functions work. Notice how we use store + 2 as an argument for cputs(). We
- do this because the string starts at the location pointed to by store + 2.
- We kept the character limit small to make it easy to see what happens when
- you try to exceed it.
-
- ──────────────────────────────────────────────────────────────────────────
- /* strio.c -- uses cgets() and cputs() */
- /* program list -- strio.c (cgets() not in core library) */
- #include <conio.h>
- #define MAXSIZE 6
- main()
- {
- char store[MAXSIZE + 2];
-
- store[0] = MAXSIZE; /* puts limit in first element */
- cputs("What's your name?\n\r");
- cgets(store);
- cputs("\n\rI'll remember you, ");
- cputs(store + 2);
- cputs("!\n\r");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-8. The STRIO.C program.
-
- The following is a sample run of the program:
-
- What's your name?
- Steph
- I'll remember you, Steph!
-
- Note the \n\r at the beginning of the second cputs() statement. This
- prevents the message from being printed over the input line.
-
- Instead of using cputs(store + 2), we could have used cputs(&store[2]).
- Or, because the return value of cgets() points to the start of the
- string──not to the start of store[]──we could have declared a pointer and
- used it as follows:
-
- char *start;
- ...
- start = cgets(store);
- ...
- cputs(start);
-
- Formatted I/O
-
- Finally, the cscanf() and cprintf() functions provide console analogues to
- the standard I/O functions scanf() and printf(). The main differences are
- that cscanf() and cprintf() work directly with the console, that cprintf()
- requires you to use the \r\n combination instead of \n, and, of course,
- that they are less portable.
-
-
- Keyboard Control with ANSI.SYS
-
- Using getch() or getche() and the scan codes, a QuickC program can detect
- a function key or a cursor control key. But how can you turn that
- information into action? How, for example, can pressing the Left Arrow key
- be made to move the cursor one space to the left? There are three common
- techniques: One uses the ANSI.SYS driver provided with MS-DOS and PC-DOS;
- the second uses BIOS calls; and the third directly accesses video memory.
- Table 13-2 compares the three methods.
-
- The first method is the simplest, so we begin with it. Many terminals have
- internal hardware that lets you control cursor position and other screen
- attributes by sending "escape sequences" from your program to the
- terminal. These all begin with the ESC character, followed by different
- sequences corresponding to different actions. For example, the sequence
- ESC[2B moves the cursor down two lines in the same column. By using
- printf() to generate such a string, you can move the cursor around. The
- original IBM PC hardware design omitted this convenient feature.
-
- Table 13-2 Cursor and Screen Control Methods
- Method ANSI.SYS BIOS Direct Memory Access
- ──────────────────────────────────────────────────────────────────────────
- Speed ranking 3 2 1
- Ease-of-use 1 2 3
- ranking
- Portability ANSI-compatible BIOS-compatible Display-specific
- ──────────────────────────────────────────────────────────────────────────
-
- MS-DOS version 2.0 came to the rescue, however, by providing the ANSI.SYS
- "driver" as a software fix. (A driver is software designed to handle
- specific hardware I/O devices.) The ANSI.SYS software intercepts output,
- examining it for escape sequences. When it finds a valid sequence, it
- performs the requested action. To use this method, you need ANSI.SYS up
- and running, and you need to know the proper escape sequences.
-
- Starting ANSI.SYS
-
- Running ANSI.SYS is not like running an ordinary program. You don't, for
- example, type ansi. Instead, you place this line in your CONFIG.SYS file:
-
- DEVICE=ANSI.SYS
-
- If the ANSI.SYS file is in a different directory from the CONFIG.SYS file,
- give the full pathname, as in the following example:
-
- DEVICE=C:\DOS\ANSI.SYS
-
- Now, when you boot your computer, ANSI.SYS is installed as part of MS-DOS.
-
- Using ANSI.SYS Escape Sequences
-
- One handy escape sequence lets you assign a string to a particular key.
- That is, it makes typing a single key have the same effect as typing the
- string. First, let's examine the format of the escape sequence required by
- ANSI.SYS:
-
- ESC[ASCIIcode;"string";ASCIIcodep
-
- Here ESC represents the escape character (ASCII 033). The first ASCIIcode
- represents the ASCII number of the key to which you assign the string. For
- non-ASCII keys, such as F1, use 0ancode, where the number following the 0;
- is the scan code for the key. Next, string represents, in string form, the
- characters you want to assign to the key. For example, the string could be
- dir/w. The final ASCIIcode lets you represent an assigned character in
- ASCII form instead of as a string. For example, you can use 13 instead of
- a carriage return. Finally, the character p terminates the escape
- sequence. You can use as many strings and ASCII codes as you like as long
- as you separate them with semicolons. For example, you can represent CD by
- "CD", by "C";68, or by 67;68, where 67 and 68 are ASCII codes for C and D.
- The ASGNKEY.C program (Listing 13-9), for example, assigns meanings to
- the F5 through F10 keys. These meanings remain in effect until you reboot.
-
- Because all the key assignments follow the same form, we use a macro to
- represent the general form. In the macro, printf() displays the escape
- sequence. First comes \033, the octal code for ESC. Then the left bracket
- and the 0; indicate a scan code. (The scan code itself is the variable K
- of the macro.) Next comes another semicolon and an open quote. (You must
- escape these with a \ when you use them within a string.) Next, the string
- itself is represented by the variable S. Then come the closing quote,
- another semicolon, a 13 (to represent a carriage return), and the
- closing p.
-
- ──────────────────────────────────────────────────────────────────────────
- /* asgnkey.c -- uses ansi.sys to assign meanings */
- /* to several function keys */
- /* Note: This requires ANSI.SYS to be installed. */
-
- #define KASSIGN(K, S) printf("\033[0;%d;\"%s\";13p", K, S)
- /* This macro assigns string S to key K */
- #define F5 63
- #define F6 64
- #define F7 65
- #define F8 66
- #define F9 67
- #define F10 68
- main()
- {
- KASSIGN(F5, "DIR *.C");
- KASSIGN(F6, "DIR *.H");
- KASSIGN(F7, "DIR *.OBJ");
- KASSIGN(F8, "DIR *.EXE");
- KASSIGN(F9, "DIR /W");
- KASSIGN(F10,"CD \\");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-9. The ASGNKEY.C program.
-
- Running this program changes the function key assignments, but you have to
- go to MS-DOS before you can see the effects. Once you exit to MS-DOS,
- pressing F5 through F8 causes MS-DOS to list the specified types of files
- (*.c, *.h, and so on). The F9 function key lists your directories in the
- compact form (the /W option). Also, F10 switches to the root directory.
- The defining string uses \\ for root because that is how you express a
- single \ in a C string. Because the code itself includes 13 for Return,
- you don't press Enter when using these function keys.
-
- You can easily modify this program to read in the desired function key
- number and the string interactively. But bear in mind that these
- assignments supersede existing ones and that they hold until you reboot.
- If, for example, you assign a function to F1, you override the editing
- function given to it by MS-DOS.
-
- Note that QuickC uses its own routines to read the keyboard, and it
- bypasses these function key definitions. So, while in QuickC, you still
- can use F5 to run a program. But if you call up an MS-DOS shell from
- QuickC, the new assignments apply.
-
- Cursor and Screen Control
-
- Now let's apply the ANSI.SYS method to a simple menu model. The goal is to
- write a program that clears the screen and displays a simple menu with one
- choice highlighted. The Up Arrow and Down Arrow keys move the cursor and
- highlighting to a different choice, and pressing Enter selects the
- highlighted choice. To do this, we need more escape codes. Table 13-3 on
- the following page lists some representative examples from which we'll
- select the ones we need. Our program will use the various cursor control
- sequences to move the cursor. The highlighting of a choice is handled
- using the SGR (Set Graphics Rendition) escape sequence, which lets you
- specify certain "attributes." Each character to be displayed can be
- assigned an attribute that controls its presentation: color, reverse
- video, blinking, and so on. In Table 13-3, ESC[ represents the Escape
- character, and num is a numeric parameter for which you substitute a
- specific number. Numbering of rows and columns starts with 1. For all but
- the last code sequence, any omitted num is assumed to be 1.
-
- Table 13-3 ANSI.SYS Escape Sequences
- ╓┌─┌──────────────┌──────────────┌─────────────┌─────────────────────────────╖
- Name Mnemonic Escape Code Description
- ──────────────────────────────────────────────────────────────────────────
- Cursor CUP ESC[num;numH Moves the cursor to the
- Position position specified by the
- numeric parameters. The first
- num is the line number; the
- second is the column number.
- Cursor Up CUU ESC[numA Moves the cursor up num lines
- in the same column.
- Cursor Down CUD ESC[num Moves the cursor down num
- lines in the same column.
- Cursor Forward CUF ESC[numC Moves the cursor right num
- Name Mnemonic Escape Code Description
- ──────────────────────────────────────────────────────────────────────────
- Cursor Forward CUF ESC[numC Moves the cursor right num
- columns.
- Cursor Back CUB ESC[numD Moves the cursor left num
- columns.
- Erase Display ED ESC[u Erases the entire display and
- homes the cursor.
- Set Graphics SGR ESC[numm Sets character attributes as
- Rendition indicated by num. Possible
- values include 0 for normal, 1
- for high intensity, 5 for
- blink, and 7 for reverse
- video.
- ──────────────────────────────────────────────────────────────────────────
-
-
- To highlight a line of text, we must first print the escape code for
- highlighting on that line and then print the text. To confine highlighting
- to the menu line, we turn off highlighting at the end of the menu output.
-
- To move the cursor and highlighting, we use getch() and the scan codes to
- detect when the arrow keys are pressed. If the Down Arrow key is pressed,
- for example, the program moves the cursor and reprints the menu, changing
- which line is highlighted. Listing 13-10 shows the completed MENU.C
- program, and Figure 13-4 on p. 419 shows the menu at work.
-
- ──────────────────────────────────────────────────────────────────────────
- /* menu.c -- uses ANSI.SYS for cursor control and */
- /* for video reverse in a sample menu */
- /* Note: Requires that ANSI.SYS be installed. */
-
- #include <conio.h>
- #define ITEMS 5 /* number of menu items */
- #define UP 72 /* scan code for up arrow */
- #define DOWN 80 /* scan code for down arrow */
- #define VIDREV "\033[7m" /* reverse video attribute */
- #define ATTOFF "\033[0m" /* turn attributes off */
- #define ED() printf("\033[2J") /* erase display */
- #define HOME() printf("\033[H") /* home the cursor */
- #define CUU(Y) printf("\033[%dA", Y); /* cursor up */
- #define CUD(Y) printf("\033[%dB", Y); /* cursor down */
-
- char *Menu[ITEMS] = {"Add a number to the list",
- "Delete a number from the list",
- "Clear the list",
- "Sum the list",
- "Quit"};
- char *Heading =
- "Use arrow keys to highlight choice. "
- "Use Enter key to select choice.";
-
- void showmenu(int);
- int getmesg(int);
- main()
- {
- int messno = 0; /* message to be highlighted */
- ED();
- showmenu(messno);
- while (messno != ITEMS - 1)
- {
- messno = getmesg(messno);
- ED();
- switch (messno)
- {
- case 0 :
- case 1 :
- case 2 :
- case 3 : printf("...pretending to work...");
- printf("Hit any key to continue\n");
- getch();
- ED();
- showmenu(messno);
- break;
- case 4 : printf("Quitting!\n");
- break;
- default: printf("Programming error!\n");
- break;
- }
- }
- }
-
- /* showmenu() displays the menu */
- void showmenu(highlite)
- int highlite; /* message number to be highlighted */
- {
- int n;
- char *start;
- HOME();
- printf("%s", Heading);
- for (n = 0; n < ITEMS; n++)
- {
- if (n == highlite)
- start = VIDREV; /* turn on reverse video */
- else
- start = ATTOFF;
- printf("\n\n%s%s%s", start, Menu[n], ATTOFF);
- }
- HOME();
- CUD(2 + 2 * highlite);
- }
-
- /* getmesg() selects a menu item */
- int getmesg(mnum)
- int mnum; /* current message number */
- {
- char ch;
- while ((ch = getch()) != '\r')
- if (ch == 0)
- {
- ch = getch();
- switch (ch)
- {
- case UP : if (mnum > 0)
- {
- CUU(2);
- showmenu (--mnum);
- }
- else
- {
- CUD(2 * ITEMS - 2);
- showmenu(mnum = ITEMS - 1);
- }
- break;
- case DOWN : if (mnum < ITEMS - 1)
- {
- CUD(2);
- showmenu(++mnum);
- }
- else
- {
- CUU(2 * ITEMS - 2);
- showmenu(mnum = 0);
- }
- break;
- }
- }
- return mnum;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-10. The MENU.C program.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 13-4 can be found on p.419 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 13-4. The MENU.C program at work.
-
- ──────────────────────────────────────────────────────────────────────────
- Watching the Keyboard in BASIC and C
- The ANSI.SYS techniques discussed here allow you to achieve the
- functionality of the KEY statement in BASIC. The BASIC KEY n, string
- statement allows you to assign a string to the PC function key Fn──that
- is, to create a simple "keyboard macro." The ANSI method is more general
- (it can be used with any key, not just a function key) and is also not
- limited to assigning short strings.
-
- BASIC statements such as ON KEY provide a very useful facility called
- "event-driven programming." After you use the KEY statement to assign a
- key number to one of the keyboard keys, a press of that key while the
- program is running will be "trapped." The ON KEY(n) subroutine statement
- causes subroutine to be executed whenever the key that's assigned number n
- is pressed. This allows programs to respond to input immediately.
-
- C has no such built-in facilities. You can, however, put the program in an
- outer loop that calls the kbhit() function to see if a key has been
- pressed. If a key has been pressed, you can use getch() to read the key.
- After assigning the key to a variable of type char, you can use it in a
- switch statement that calls the appropriate function to handle the command
- received. This isn't true event-driven programming, because the response
- to a key comes only when the program is at the top of the loop, but QuickC
- programs run fast enough that the effect is often the same. The use of a
- special device driver or an environment such as Microsoft Windows can
- allow for true event-driven programming.
- ──────────────────────────────────────────────────────────────────────────
-
- The MENU.C program first defines several macros using printf() and the
- escape codes to represent some of the ANSI.SYS sequences from Table 13-3
- on p. 416. If you plan to use such macros often, you should create an
- include file for the macro definitions.
-
- Because we are illustrating ANSI.SYS and not numeric analysis, the program
- does no actual calculation. However, the switch statement in main()
- provides the skeleton for controlling program flow. The getmesg() function
- returns the array index of the selected message, and the switch selects a
- response based on that value. The switch is in a loop, so you can
- repeatedly make choices until you select Quit.
-
- In main(), the HOME() macro uses the CUP escape code to home the cursor.
- Because we omitted the two numeric parameters, the default values of 1 are
- used, which effectively home the cursor.
-
- The showmenu() function displays the menu. It receives the array index of
- the element to be highlighted. That message then starts with highlighting
- turned on; the other messages have it turned off.
-
- The getmesg() function, as we mentioned, returns the array index of the
- selected item. It also handles the cursor movement. In this function,
- getch() checks for the Up Arrow and the Down Arrow keys. If, for example,
- the Down Arrow key is pressed, CUD moves the cursor down two lines to the
- next message. The array index is also incremented to tell showmenu() which
- message to highlight. To keep the cursor inside the menu, we compare its
- position to the menu limits. If the cursor is on the bottom line of the
- menu, then pressing the Down Arrow key moves it to the top line.
-
- This program works as designed, but it runs slowly, and the redrawing of
- the screen is not very smooth. The ANSI.SYS approach to cursor and screen
- control is relatively simple, but using BIOS calls or direct memory access
- gives better performance.
-
-
- Using QuickC to Access the BIOS
-
- One way to create programs that take advantage of the special capabilities
- of an IBM PC/XT, PC/AT, or compatible without getting too involved in the
- hardware is to use BIOS calls.
-
- Background for the IBM BIOS
-
- BIOS is an acronym for Basic Input/Output System. It consists of a set of
- assembly-language routines permanently stored in what is called Read-Only
- Memory, or ROM, of the IBM PC. The computer can read and utilize
- information in ROM, but it cannot alter ROM. That preserves the integrity
- of the routines. The BIOS includes routines to read the keyboard, to
- control the video display, and to read from and write to disk drives. Most
- higher-level programming ultimately makes use of these routines. For
- instance, QuickC's getch() uses one of the keyboard routines, and many
- MS-DOS commands ultimately use the BIOS routines to do low-level work.
-
- In short, you can think of the BIOS as a built-in library of functions.
- All you need to do is find out what services are offered and how to use
- them.
-
- The ultimate source of information about the BIOS is the IBM Personal
- Computer Technical Reference Manual. This manual includes
- assembly-language listings of all the routines. We'll describe those
- routines as we use them.
-
- Using the BIOS
-
- Two problems face the QuickC programmer who wants to use the BIOS. One is
- that the routines, which are written in assembly language, don't work the
- same as C functions, so you have to learn a little about assembly language
- and about the hardware to understand them. The second is that these BIOS
- routines are accessed not by function calls but by "interrupt signals."
- For this reason, these routines are commonly called "interrupt routines,"
- or simply "interrupts." Let's clarify this topic first.
-
- Interrupt Routines
-
- The heart of a PC is its central processing unit, or CPU, but a PC
- contains other processors, too. For example, the keyboard processor
- handles keyboard input, and another processor handles data flow between
- the CPU and memory. To enable the CPU to keep in touch with its
- environment, an interrupt system was developed. Certain devices and
- assembly-language instructions can generate signals that take control of
- the microprocessor. The Intel 8086 family of microprocessors permits as
- many as 256 distinct interrupt signals, but fewer are actually used. When
- the CPU detects an interrupt signal, it "interrupts" its current activity
- and executes the set of assembly-language instructions identified with
- that particular signal.
-
- ──────────────────────────────────────────────────────────────────────────
- How Interrupts Work
- When you boot a PC, it sets up a table of addresses known as the
- "interrupt vectors." At the first address is the routine to be executed if
- interrupt signal 0 is detected. At the second address is the routine to be
- executed if interrupt signal 1 is detected, and so on. When an interrupt
- is detected, the corresponding address is found in the table, and the
- instructions beginning at that address are executed. At the end of those
- instructions, a "return from interrupt" instruction tells the
- microprocessor to resume its interrupted activity.
-
- The operating system also uses the interrupt table. When MS-DOS or PC-DOS
- is first loaded, it adds its own batch of interrupt routines. Memory
- resident programs also work by storing their addresses in the interrupt
- vector table. Incidentally, MS-DOS can substitute its own version of a
- ROM-based BIOS routine by overwriting the appropriate interrupt vector
- with a new address. The ROM itself is unchanged, but the computer is
- directed to the new address instead when the interrupt is issued. This
- method is sometimes used as a software "fix" for faulty BIOS routines.
- (The only way to update the actual BIOS is to get a newer version of the
- ROM chip.)
- ──────────────────────────────────────────────────────────────────────────
-
- Software Interrupts
-
- In assembly language, generating interrupts is simple. For example, to
- generate interrupt signal 0x10 (the video I/O interrupt), you use the
- following instruction:
-
- int 10h
-
- What if one interrupt arrives while another interrupt routine is
- executing? This situation is handled by a priority ranking. A
- higher-priority interrupt can interrupt a lower-priority routine, but not
- vice versa.
-
- C, as a general, portable language, doesn't have a built-in interrupt
- instruction. But the QuickC library offers several non-ANSI C functions
- designed to serve the same purpose. Seven functions make specific BIOS
- calls; they all have names beginning with _bios_ and are declared in the
- bios.h file. The dos.h file declares another 40 functions, most of which
- call specific MS-DOS functions. (Interrupt number 0x21 can be used to
- access many functions loaded into the system by MS-DOS; these are the
- MS-DOS system calls.) Five of the dos.h functions, however, are more
- general and can invoke a choice of interrupts. (See Table 13-4 for a
- summary of these functions.)
-
- We will use the int86() function because it is generally applicable. As
- its name suggests, it generates a specified interrupt for the 8086 family
- of microprocessors. However, before we can use this function, we have to
- see how interrupt routines use registers to transfer data.
-
- Table 13-4 Interrupt-accessing Functions in Order of Decreasing Generality
- Name Use
- ──────────────────────────────────────────────────────────────────────────
- intx86() Invokes interrupts requiring the use of segment
- registers.
- int86() Invokes interrupts not requiring use of the segment
- registers.
- intxdos() Invokes MS-DOS system calls requiring the use of
- segment registers.
- intdos() Invokes MS-DOS system calls not requiring use of the
- segment registers.
- bdos() Invokes MS-DOS system calls that use only the DX and AL
- registers.
- _bios_...() family Invokes specific BIOS interrupts.
- _dos_...() family Invokes specific MS-DOS calls.
- ──────────────────────────────────────────────────────────────────────────
-
- Interrupts, Assembly Language, and Registers
-
- Like C functions, interrupts pass information back and forth between the
- routine and the calling program. Instead of using arguments, however,
- interrupts use the microprocessor registers. The int86() function gets
- around this difference by using unions to pass the register information to
- and from the calling C program.
-
- Registers are small work and storage areas built into the CPU. For
- example, the 8088 chip, the most commonly used member of the 8086 family,
- has 13 registers, each capable of holding 16 bits. Four of the registers
- are general-purpose registers used for arithmetic and logical operations;
- they are called AX, BX, CX, and DX. Four "segment" registers store the
- addresses of various memory segments; these registers are called CS, DS,
- SS, and ES. Four more "pointer/index" registers keep track of addresses
- used in a program; they are called SP, BP, SI, and DI. Finally, the
- instruction pointer (IP) keeps track of the address of the next
- instruction to be executed. Also, the processor has nine "flags" that can
- be turned on or off. The flags can be considered to be individual bits in
- a flag register. These, then, are the resources open to an interrupt
- routine.
-
- There is one further complication. Each of the general-purpose registers
- can be considered to be two 8-bit registers. The AX register, for example,
- can be divided into the AH (H for high byte) and the AL (L for low byte)
- registers. Assigning a value to the AX register affects the whole
- register, but assigning a value to AL or AH affects only half of the
- register. Similarly, the BX register is divided into the BH and BL
- registers, and so on.
-
- Now that we have some background about registers, let's see how int86()
- works.
-
- The int86() Function
-
- The int86() function will be our tool for initiating interrupt routines,
- initializing registers, and reading registers. Its library description
- begins with the following:
-
- #include <dos.h>
- int int86(intno, inregs, outregs);
- int intno; /* Interrupt number */
- union REGS *inregs; /* Register values on call */
- union REGS *outregs; /* Register values on return */
-
- This syntax summary says to include the dos.h header file when using this
- function. Also, int86() takes three arguments. The first is the number of
- the desired interrupt. The second is the address of a union containing the
- register values passed to the interrupt. The third is the address of the
- union into which the post-interrupt register values are copied.
-
- To use int86(), you need to know how the type union REGS is defined. That
- information resides in the dos.h file, as follows:
-
- /* word registers */
-
- struct WORDREGS {
- unsigned int ax;
- unsigned int bx;
- unsigned int cx;
- unsigned int dx;
- unsigned int si;
- unsigned int di;
- unsigned int cflag;
- };
-
- /* byte registers */
-
- struct BYTEREGS {
- unsigned char al, ah;
- unsigned char bl, bh;
- unsigned char cl, ch;
- unsigned char dl, dh;
- };
-
- /* general-purpose registers union -- overlays the
- corresponding word and byte registers. */
-
- union REGS {
- struct WORDREGS x;
- struct BYTEREGS h;
- };
-
- Together, these definitions give two views of the registers.
-
- The WORDREGS structure provides the 16-bit view. This structure has seven
- members representing the four general-purpose registers, two of the
- pointer registers, and the "carry" flag (which we won't use). These are
- the registers most commonly used by the interrupts. (The int86x() function
- uses an additional structure to give access to more registers.) In this
- structure, for example, the ax member represents the AX register.
-
- The BYTEREGS structure gives the 8-bit view. This structure represents the
- four general-purpose registers, with each register split into two 1-byte
- registers. Thus, the ch member of this structure represents the CH
- register, the high byte of CX.
-
- The unusual part of this function is the definition of the union REGS. It
- superimposes the word view and the byte view. For example, suppose you use
- the following declaration:
-
- union REGS myreg;
-
- To assign a value to the AX register, use a statement such as the
- following:
-
- myreg.x.ax = 1026;
-
- The .x notation specifies the WORDREGS member of myreg; therefore,
- myreg.x.ax is the AX member of that structure.
-
- To assign a value to the BL register (the low byte of the BX register),
- use the following .h notation:
-
- myreg.h.bl = 22;
-
- This specifies the BYTEREGS member of the union.
-
- Recall that a union uses the same storage area for all its members. This
- means that myreg.h.al and myreg.h.ah overlie myreg.x.ax. To get the high
- byte of the 1026 that was assigned to myreg.x.ax, refer to myreg.h.ah (see
- Figure 13-5).
-
- union REGS myreg;
- word
- ┌───────────────────┬───────────────────┐
- / │ │ │ \ AX register
- / │ myreg.x.ax │ \
- / └───────────────────┴───────────────────┘ \
- / / ┌─────────── /───/──┬──\───\────────────┐ \ \
- ┌───────────────────┐/ / │ \ \┌───────────────────┐
- │ one byte │ / │ \ │ one byte │
- │ myreg.h.ah │/────────┴────────\│ myreg.h.al │
- AH └───────────────────┘─────────┬─────────└───────────────────┘ AL
- register │ │ │ register
- │ │ │
- └───────────────────┴───────────────────┘
- ┌───────────────────┬───────────────────┐
- │ │ │ DX
- │ │ │
- └───────────────────┴───────────────────┘
-
- For a REGS union: .x means the 16-bit version
- .h means the 8-bit version
-
- Figure 13-5. The REGS union.
-
- You now know enough theory to use int86(). However, you still need to know
- what values to pass as arguments to the BIOS routines. Let's look at a
- simple example.
-
- Interrupt 0x16──The Keyboard I/O Interrupt
-
- Because we have been discussing the keyboard, let's look at interrupt
- routine 0x16, which reads the keyboard. The QuickC library provides the
- _bios_kbrd() function to access this specific routine. However, we will
- use int86() in order to demonstrate a more general approach to using
- interrupts.
-
- Like many interrupts, 0x16 includes more than one subroutine. It has three
- subroutines; each is termed a "function" or "service." To select a
- particular function, you must place the "function code" number in the AH
- register before calling the interrupt. Let's take a look at what each
- function does.
-
- Interrupt 0x16, Function Code 0──Get Character
-
- This function reads the keyboard buffer if a character is present;
- otherwise, it waits until a keystroke is placed in the buffer. When it
- reads a key, it places the ASCII byte in the AL register and the scan byte
- in the AH register. (Note that the return values are written over the
- values we originally placed in AH and AL.) The code is removed from the
- keyboard buffer once it is read. The getch() function is based on this
- function.
-
- Interrupt 0x16, Function Code 1──Check Keyboard Buffer
-
- This function checks to see if the keyboard buffer is empty or not. If it
- is empty, the "zero flag" (ZF) is set to 1; otherwise, the flag is cleared
- (set to zero). If a character is present, the ASCII byte is placed in AL
- and the scan byte in AH, but the code in the buffer is left there. The
- kbhit() function is based on this function.
-
- Interrupt 0x16, Function Code 2──Get Keyboard Status
-
- This function reports on the status of the Shift and Ctrl keys. Each of
- eight keys is assigned a particular bit in the AL register. If one of the
- eight keys is closed, the corresponding bit is set to 1. Table 13-5 shows
- the corresponding bits and keys.
-
- Table 13-5 Keyboard Status Bits
- Bit Set to 1 If
- ──────────────────────────────────────────────────────────────────────────
- 0 Right Shift is closed
- 1 Left Shift is closed
- 2 Ctrl is closed
- 3 Alt is closed
- 4 Scroll Lock is active
- 5 Num Lock is active
- 6 Caps Lock is active
- 7 Insert mode is active
- ──────────────────────────────────────────────────────────────────────────
-
- Reading ASCII and Scan Codes
-
- Let's use function code 0 to construct a more general version of getch()
- that we'll call Readkey(). It will return both the ASCII and the scan
- bytes. Using it will give you a better picture of how the keyboard codes
- work and show you that using interrupts from QuickC is not all that
- difficult. The readkey.c program (Listing 13-11) contains the Readkey()
- function.
-
- The dos.h file defines the union REGS type and declares the int86()
- function. We define symbolic constants to represent the interrupt number
- and the function code number. Finally, we define a two-member structure
- called SCANCODE for holding the two keyboard codes. Readkey() uses the reg
- structure to set the AH register to the proper function code and then it
- calls int86(). Because preserving the original register values is
- unnecessary, the same structure stores both the input values and the
- returned values of the registers. Finally, the program copies the two
- relevant register values into the structure that the function returns.
-
- The int86() syntax calls for two pointers to union REGS as arguments. In
- practice this usually calls for using the address operator applied to the
- appropriate union, as we have done here.
-
- Having developed the Readkey() function, let's use it in the next program,
- SHOWCODE.C, (Listing 13-12). To run this program within the QuickC
- environment, be sure that Screen Swapping On is active (on the Debug
- menu).
-
- This program reads a key, prints it (if it is printable), and displays
- both the ASCII and scan codes. Using it can be instructive. Following, for
- example, is the output for m, Shift-M, Ctrl-M, Alt-M, Enter, and Esc:
-
- m: ascii = 109, scan = 50────────────────────────────────────────────m
- M: ascii = 77, scan = 50──────────────────────────────────────Shift-M
- ^M: ascii = 13, scan = 50───────────────────────────────────────Ctrl-M
- : ascii = 0, scan = 50────────────────────────────────────────Alt-M
- ^M: ascii = 13, scan = 28────────────────────────────────────────Enter
- ^[: ascii = 27, scan = 1──────────────────────────────────────────Esc
-
- ──────────────────────────────────────────────────────────────────────────
- /* readkey.c -- contains the Readkey() function */
- #include <dos.h>
- #define KEYINTR 0x16 /* keyboard read interrupt */
- #define GETCHAR 0 /* read scancode function */
- struct SCANCODE {
- unsigned char ascii; /* ascii code */
- unsigned char scan; /* scan code */
- };
-
- struct SCANCODE Readkey()
- {
- union REGS reg;
- struct SCANCODE scancode;
-
- reg.h.ah = GETCHAR; /* specify function */
- int86(KEYINTR, ®, ®); /* note use of & oper.*/
- scancode.ascii = reg.h.al;
- scancode.scan = reg.h.ah;
- return (scancode);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-11. The readkey.c function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* showcode.c -- shows ASCII and scan codes for */
- /* keystrokes */
- /* Note: Set Screen Swapping On in the Debug menu. */
- #include <stdio.h>
- #include <dos.h>
- #define KEYINTR 0x16 /* keyboard read interrupt */
- #define GETCHAR 0 /* read scancode function */
- #define ESC '\033' /* escape key */
- struct SCANCODE {
- unsigned char ascii; /* ascii code */
- unsigned char scan; /* scan code */
- };
- struct SCANCODE Readkey();
-
- main()
- {
- struct SCANCODE keys;
-
- printf("Press keys to see their scancodes. ");
- printf("Press the Esc key to quit.\n");
- do {
- keys = Readkey();
- if (keys.ascii > 0 && keys.ascii < 040)
- printf("^%c: ascii = %3d, scan = %3d\n",
- keys.ascii + 0100, keys.ascii,
- keys.scan);
- else if (keys.ascii >= 40)
- printf(" %c: ascii = %3d, scan = %3d\n",
- keys.ascii, keys.ascii, keys.scan);
- else
- printf(" : ascii = %3d, scan = %3d\n",
- keys.ascii, keys.scan);
- } while (keys.ascii != ESC);
- }
-
- struct SCANCODE Readkey()
- {
- union REGS reg;
- struct SCANCODE scancode;
-
- reg.h.ah = GETCHAR;
- int86(KEYINTR, ®, ®);
- scancode.ascii = reg.h.al;
- scancode.scan = reg.h.ah;
- return (scancode);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-12. The SHOWCODE.C program.
-
- The scan code for the first four characters is the same (50) because the
- same primary key (the M key) was used in each case. The modifying key, if
- any, then caused the ASCII part of the code to be changed. Note how the
- ASCII part for Alt-M is 0. Also note how the Enter key has the same ASCII
- code as Ctrl-M but a different scan code. The scan code is different
- because a different physical key was pressed. Incidentally, if you need to
- write a program that discriminates between input of Ctrl-M and the Enter
- key, you can use Readkey() to check the scan code. (The getch() function
- cannot distinguish between the two keystrokes.)
-
- The following represents another sample run; this time the input is F1,
- Ctrl-F1, Shift-F1, Alt-F1, and Esc:
-
- : ascii = 0, scan = 59───────────────────────────────────────────F1
- : ascii = 0, scan = 94──────────────────────────────────────Ctrl-F1
- : ascii = 0, scan = 84─────────────────────────────────────Shift-F1
- : ascii = 0, scan = 104───────────────────────────────────────Alt-F1
- ^[: ascii = 27, scan = 1──────────────────────────────────────────Esc
-
- In this example, the scan code changes even though the same primary key
- was pressed each time. With ASCII characters, the ASCII code discriminates
- among diferent combinations, but with the control keys, the ASCII byte is
- always 0, so the scan code itself must change. Also, notice that these
- keystrokes are nonprinting; therefore, the program displays only the
- codes.
-
- Finally, this example uses the following input: 1, the End key, Shift-End,
- and Esc:
-
- 1: ascii = 49, scan = 2─────────────────────────────────────────────|
- : ascii = 0, scan = 79───────────────────────────────────────────End
- 1: ascii = 49, scan = 79─────────────────────────────────────Shift-End
-
- Note how Shift-End and 1 produce the same ASCII code (49) but different
- scan codes (2 and 79). We mention this because the QuickC editor uses the
- Shift-End combination to highlight a line. If the programming for QuickC
- relied on getch(), that would be impossible to do. Apparently QuickC, like
- our program, checks the scan code too. This lets it assign a different
- function to Shift-End.
-
-
- Cursor and Screen Control with BIOS Calls
-
- Now that you know how to use interrupts, we can extend that technique to
- cursor and screen control. To illustrate these applications, we will
- construct a rudimentary first step toward a word processor. With this
- program, you can do the following:
-
- ■ Start with a clear screen.
-
- ■ Enter text from the keyboard and see it on the screen.
-
- ■ Use the arrow keys to move the cursor.
-
- ■ Use the function keys to turn highlighting on and off.
-
- ■ Highlight or unhighlight existing text by moving the cursor over it.
-
- To provide these features, we'll construct a library of approximately a
- dozen BIOS-based functions. Rather than jumping back and forth between
- program development and BIOS use, we'll develop the entire library first.
-
- Incidentally, the QuickC Graphics Library, which we discuss in Chapter
- 15, provides an alternative means for implementing these features. Using
- the Graphics Library, however, produces executable programs substantially
- larger than those using the BIOS approach.
-
- The Video I/O Interrupt
-
- The first step is to find the appropriate interrupt routine. Interrupt
- 0x10, the video I/O interrupt, controls the display. Because maintaining a
- video display is more complex than monitoring a keyboard, this interrupt
- turns out to be much more involved than the keyboard I/O interrupt. It
- provides many subroutines, or functions, and many of them use several
- registers. Table 13-6 on the following pages lists and describes the
- functions we use in this book. The table mentions "attributes" and
- "pages." Attributes, as we saw in our discussion of ANSI.SYS, determine
- how a character is to be displayed. A page is a screenful of display. Some
- video modes can store more than one page at once, although only one can be
- displayed at any given time. We discuss these terms further as needed.
-
- When using int86() to invoke these functions, you set AH to the
- appropriate function code number and initialize any other registers given
- in the description. The first argument to int86() should be 0x10, the
- interrupt number.
-
- Table 13-6 Selected Video I/O Interrupt 0x10 Functions
- ──────────────────────────────────────────────────────────────────────────
- FUNCTION CODE 0: Set the Display Mode
- Action: Switches to desired mode and clears display.
- Register setup: Place 0 in AH
- Place desired mode in AL
-
- Choose from the following modes:
- Mode Meaning
- 0 40 x 25 B/W Text
- 1 40 x 25 Color Text
- 2 80 x 25 B/W Text
- 3 80 x 25 Color Text
- 4 320 x 200 Color Graphics
- 5 320 x 200 B/W Graphics
- 6 640 x 200 B/W Graphics
- 7 80 x 25 Monochrome
- 13 320 x 200 Color EGA
- 14 640 x 200 Color EGA
- 15 640 x 350 B/W EGA
- 16 640 x 350 Color EGA
- 17 640 x 480, 2-Color VGA
- 18 640 x 480, 16-Color VGA
- 19 320 x 200, 256-Color VGA
-
- FUNCTION CODE 2: Select Cursor Position
- Action: Moves cursor to the specified row and number.
- Register setup: Place 2 in AH
- Place row number in DH
- Place column number in DL
- Place page number in BH
-
- Numbering of rows and columns starts with 0, not 1.
-
- FUNCTION CODE 3: Read Cursor Position
- Action: Reports the row and column of cursor position.
- Register setup: Place 3 in AH
- Place page number in BH
- Returns: Row number is in BH
- Column number is in DL
- Cursor type is in CH, CL
-
- FUNCTION CODE 5: Select Active Display Page
- Action: Selects the page for modes supporting multiple pages.
- Register setup: Place 5 in AH
- Place page number in AL
-
- FUNCTION CODE 6: Scroll Up an Area of the Screen
- Action: Scrolls up a section of the screen a specified amount.
- Register setup: Place 6 in AH
- Place number of lines to scroll in AL (0 in AL produces
- a blank window)
- Place blank-line attribute in BH
- Place upper-left row number in CH
- Place upper-left column number in CL
- Place lower-right row number in DH
- Place lower-right column number in DL
-
- FUNCTION CODE 8: Read Character and Attribute
- Action: Reports the character and attribute code at the current
- cursor position.
- Register setup: Place 8 in AH
- Place page number in BH (text modes)
- Returns: Character at cursor is in AL
- Attribute at cursor is in AH
-
- FUNCTION CODE 9: Write Character and Attribute
- Action: Writes a specified character and attribute to the
- current cursor position.
- Register setup: Place 9 in AH
- Place page number in BH (text modes)
- Place character in AL
- Place attribute (text modes) or color (graphics modes)
- in BL
- Place number of characters in CX
-
- Note: The character is written the indicated number of times starting at
- the current cursor position; the cursor position remains unchanged.
-
- FUNCTION CODE 15: Return Current Video State
- Action: Reports the video mode, number of text columns, and the
- current page value.
- Register setup: Place 15 in AH
- Returns: Current mode is in AL
- Number of columns is in AH
- Current active page number is in BH
- ──────────────────────────────────────────────────────────────────────────
-
- Developing a Library of C Functions
-
- Our next step is to develop a set of C functions that use the video I/O
- interrupt. In this section, we will design several functions, each general
- enough to be useful for a variety of programs. We'll develop the functions
- individually but then collect them in one file so that they can share a
- common set of include files and definitions. You'll find the contents of
- this combined file, which is called SCRFUN.C, in Listing 13-23 beginning
- on p. 441. Finally, we'll use the LIB utility to make a library of the
- video functions.
-
- Setting the Cursor
-
- First, we need two C functions: one to set the cursor and another to
- report the current cursor position. We use functions 2 and 3 of the video
- interrupt to develop our own Setcurs() and Getcurs() functions (Listing
- 13-13).
-
- Pass the desired row, column, and page to Setcurs(), and it positions the
- cursor. Use Getcurs() to place row and column information in variables
- whose addresses we pass. What about the page variable? For now, use the
- default value of 0. The following SETCURS.C program (Listing 13-14) is a
- short example that uses Setcurs() to see if our programming is on the
- right track.
-
- After you type in a row and column in the form 10 20, the program places
- the cursor there and then prints a message starting at that location. It's
- not a spectacular program, but it shows that our function is working
- correctly. As we build this library with other functions, you might want
- to write similar test programs. With QuickC it doesn't take long to do.
-
- ──────────────────────────────────────────────────────────────────────────
- #include <dos.h>
- #define VIDEO 0x10
- #define SETCURSOR 2
- #define GETCURSOR 3
-
- /* Setcurs() -- sets cursor to given row, column */
- void Setcurs(row, col, page)
- unsigned char row, col, page;
- {
- union REGS reg;
-
- reg.h.ah = SETCURSOR;
- reg.h.dh = row;
- reg.h.dl = col;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- }
-
- /* Getcurs() -- reports current cursor position */
- void Getcurs(pr, pc, page)
- unsigned char *pr, *pc, page;
- {
- union REGS reg;
-
- reg.h.ah = GETCURSOR;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- *pr = reg.h.dh; /* row number */
- *pc = reg.h.dl; /* column number */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-13. The setcurs() function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* setcurs.c -- moves cursor, checks out Setcurs() */
- #include <dos.h>
- #include <stdio.h>
- #define VIDEO 0x10
- #define SETCURSOR 2
- void Setcurs(unsigned char, unsigned char,
- unsigned char);
- main()
- {
- int row, col;
-
- printf("Enter row and column: (q to quit)\n");
- while (scanf("%d %d", &row, &col) == 2)
- {
- Setcurs(row, col, 0);
- printf("Enter row and column: (q to quit)");
- }
- }
-
- /* Setcurs() -- sets cursor to row, column, and page */
- void Setcurs(row, col, page)
- unsigned char row, col, page;
- {
- union REGS reg;
-
- reg.h.ah = SETCURSOR;
- reg.h.dh = row;
- reg.h.dl = col;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-14. The SETCURS.C program.
-
- Setting the page to 0 worked fine in our example; however, we may need to
- use pages later, so let's look at that topic.
-
- Getting and Setting the Page
-
- The information displayed on the screen is read from a dedicated section
- of memory called video memory. The amount of memory available depends upon
- the video adapter. In some modes, video memory can hold two or more
- screenfuls of data. In those cases, you can divide video memory into
- separate pages, one page per screenful. This lets a program alter one page
- in memory while displaying the other on the screen. To set a page, we will
- use the Setpage() function (Listing 13-15 on the following page).
-
- By default, screen modes start at page 0, and we'll also use that page for
- a while. But to keep our programming general, we need a function that can
- tell our code which is the current page. We use function 15 to develop the
- QuickC Getpage() function (Listing 13-16). The interrupt function places
- the page number in the BH register, and the function returns that value to
- the program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Setpage() -- sets page to given value */
- #include <dos.h>
- #define VIDEO 0x10
- #define SETPAGE 5
- void Setpage(page)
- unsigned char page;
- {
- union REGS reg;
-
- reg.h.ah = SETPAGE;
- reg.h.al = page;
- int86(VIDEO, ®, ®);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-15. The Setpage() function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Getpage() -- obtains the currently active page */
- #include <dos.h>
- #define VIDEO 0x10
- #define GETMODE 15
- unsigned char Getpage()
- {
- union REGS reg;
-
- reg.h.ah = GETMODE;
- int86(VIDEO, ®, ®);
- return reg.h.bh;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-16. The Getpage() function.
-
- Clearing the Screen
-
- Another useful function is one that clears the screen. None of the
- interrupt functions specialize in that, but the Scroll Up function
- (function 6) can perform this task. Note in Table 13-6 that if register
- AL is set to zero, the entire designated area is cleared. However, several
- other registers also must be set. You define the area to be cleared by
- giving the coordinates of the upper-left and the lower-right corners.
-
- The BIOS routine starts numbering with 0, unlike ANSI.SYS, which starts
- with 1. This means the upper-left row and column are 0, the lower-right
- row is 24, and the lower-right column is 79. (We assume you're using an
- 80-by-25 display.) The least straightforward register setting is the
- attribute setting for blank lines in register BH. An attribute is a value
- in the range 0 through 255 that modifies the display. The normal attribute
- is 7 for a "white-on-black" display. ("White" is white on a color display,
- but on a monochrome monitor "white" usually is green or amber.) Other
- values produce reverse video, blinking, underlining (on some monitors),
- and colors (on some monitors). We'll use the value 7.
-
- We use these register values to construct the following Clearscr()
- function (Listing 13-17).
-
- ──────────────────────────────────────────────────────────────────────────
- /* Clearscr() -- clears the screen */
- #include <dos.h>
- #define VIDEO 0x10
- #define SCROLLUP 6
- #define ROWS 25
- #define COLS 80
- void Clearscr()
- {
- union REGS reg;
-
- reg.h.ah = SCROLLUP;
- reg.h.al = 0; /* clear the window */
- reg.h.ch = 0;
- reg.h.cl = 0;
- reg.h.dh = ROWS - 1;
- reg.h.dl = COLS - 1;
- reg.h.bh = NORMAL;
- int86(VIDEO, ®, ®);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-17. The Clearscr() function.
-
- Reading and Writing Characters and Attributes
-
- Before we use BIOS routines to read from and write to the screen, you need
- to know how the video system works. The video adapter has its own memory,
- which it uses to represent the screen. Let's concentrate for now on the
- 80-by-25 text modes, the ones you probably use most often. All standard
- IBM video controllers (Monochrome, CGA, EGA, MCGA, and VGA) use the same
- scheme for their 80-by-25 text modes, so this discussion applies to all.
-
- ──────────────────────────────────────────────────────────────────────────
- Quick Tip
- If you are ambitious, you can generalize this function to work with
- 40-by-25 displays by using function 15 of the 0x10 interrupt to find the
- number of columns actually being used. A call to function 15 places that
- number of columns into the AH register. Subtract 1 from this number (to
- account for the fact that column numbering begins with column 0), save the
- result, and assign it to the DL register before you call function 6.
- ──────────────────────────────────────────────────────────────────────────
-
- You can think of an 80-by-25 screen as holding 2000 cells, each capable of
- displaying a character. Each cell is represented by two bytes in the video
- memory. One byte holds the code for the character, and the second byte
- holds the attribute, which determines how the character is displayed. When
- a program sends output to the screen, the characters actually are first
- stored in video memory. A microprocessor called a video controller then
- scans the video memory, mapping the characters it finds there to the
- screen. Video interrupt 0x10 functions 8 and 9, which read and write
- characters and attributes to the screen, actually work with the video
- memory. (The monochrome display system uses a different memory address
- from the others, but the BIOS calls adjust for that.)
-
- The character code consists of the usual ASCII code plus extensions to the
- code that enable certain non-ASCII characters to be displayed on the
- screen. (IBM provides 128 such additional characters in its extended
- character set.) The attribute code also is simple, especially for
- black-and-white displays. Think of the attribute byte as a series of eight
- bits, numbers 7 to 0, left to right. To generate the normal
- black-and-white display, set the bits to 00000111. To produce reverse
- video, set the bits to 01110000. Note that these binary values translate
- to 0x7 and 0x70, respectively.
-
- In addition, you can intensify the foreground display by setting bit 3 to
- 1 or put the display in "blink" mode by setting bit 7 to 1. The attributes
- we've discussed here produce white-on-black (or black-on-white) characters
- for the monochrome display and for color-text displays. We discuss
- color-related attributes in Chapter 14.
-
- To produce both normal and reverse video, our program must write the
- attribute as well as the character. We use video I/O function 9 instead of
- putch(), because the latter writes only characters, not attributes. Our
- Write_ch_atr() C function (Listing 13-18) uses that interrupt routine.
- This function writes the character-attribute pair num times to display a
- single pair several times in a row. We will use a num value of 1, but to
- preserve generality, we did not build that value into the function.
-
- One of our program goals was converting normal text to reverse video by
- passing the cursor over it. You can do that simply by changing the
- attribute at the cursor location. Because no BIOS function merely changes
- an attribute, we need to write a character-attribute pair. One way to do
- this is to read the current character from the screen and to then rewrite
- it using a different attribute. So let's start by devising a Read_ch_atr()
- (Listing 13-19) function to read the character and attribute at the
- current cursor location.
-
- Because the function must return two values, we pass it the addresses of
- the two variables to which the values will be assigned. To read the
- character and attribute at the current cursor position on page 0 into the
- variables ch and attr, make this call:
-
- Read_ch_atr(&ch, &attr, 0);
-
- We also could have the function return a two-member structure.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Write_ch_atr() -- writes characters and attributes */
- #include <dos.h>
- #define VIDEO 0x10
- #define WRITECHATR 9
- void Write_ch_atr(ch, atr, page, num)
- unsigned char ch, atr, page;
- unsigned int num;
- {
- union REGS reg;
-
- reg.h.ah = WRITECHATR;
- reg.h.al = ch;
- reg.h.bl = atr;
- reg.h.bh = page;
- reg.x.cx = num;
- int86(VIDEO, ®, ®);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-18. The Write_ch_atr() function.
-
- Now we use the last two functions to produce the function our program
- requires. The Rewrite() function (Listing 13-20 on the following page)
- reads the current character and rewrites it with a potentially changed
- attribute.
-
- If speed is an issue, which it usually isn't for keyboard input, you can
- speed up Rewrite() by having it use int86() to call the read and write
- BIOS functions directly instead of going through Read_ch_atr() and
- Write_ch_atr().
-
- ──────────────────────────────────────────────────────────────────────────
- /* Read_ch_atr() -- reads character and attribute at */
- /* cursor location */
- #include <dos.h>
- #define VIDEO 0x10
- #define READCHATR 8
- void Read_ch_atr(pc, pa, page)
- unsigned char *pc, *pa;
- unsigned char page;
- {
- union REGS reg;
-
- reg.h.ah = READCHATR;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- *pc = reg.h.al; /* character at cursor */
- *pa = reg.h.ah; /* attribute at cursor */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-19. The Read_ch_atr() function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Rewrite() -- changes attribute of on-screen */
- character */
- void Read_ch_atr(), Write_ch_atr(); /* used by */
- /* Rewrite()*/
- void Rewrite(at, page)
- unsigned char at, page;
- {
- unsigned char ch, atr;
-
- Read_ch_atr(&ch, &atr, page);
- Write_ch_atr(ch, at, page, 1);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-20. The Rewrite() function.
-
- More Cursor Movement
-
- We already have a function to set the cursor at a given row or column. But
- our primitive text editor really needs functions to move the cursor one
- column to the right when the Right Arrow key is pushed, and so on. We can
- use Setcurs() to create such functions. The Cursrt_lim() function (Listing
- 13-21) demonstrates how to construct a right-movement function.
-
- Getcurs() and Setcurs() require the current page number; the Cursrt_lim()
- function uses Getpage() to obtain that information. Also, the function
- prevents the cursor from going past the column defined by limit. Our
- program will use a limit of 79, corresponding to the right side of the
- screen, but the numeric value is not built into the function. This
- variable limit lets you use the function with a program that confines the
- cursor to a section of the screen or with one that uses a 40-column
- screen.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Cursrt_lim() -- moves cursor one space to the */
- right, but not past a set limit */
- void Getcurs(), Setcurs(); /* functions used */
- unsigned char Getpage(); /* by this function */
-
- unsigned char Cursrt_lim(limit)
- unsigned char limit;
- {
- unsigned char row, col, page;
- unsigned char status = 1;
-
- Getcurs(&row, &col, page = Getpage());
- if (col < limit)
- Setcurs(row, col + 1, page);
- else
- status = 0;
- return status;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-21. The Cursrt_lim() function.
-
- Also, the program uses a return value to inform the calling program
- whether the cursor reached its limit. This gives the calling program the
- option of responding in some way, such as beeping or moving the cursor to
- the beginning of the next line, whenever the limit is reached.
-
- We can modify Cursrt_lim() to create functions corresponding to the other
- arrow keys. We'll show you these when we gather all the functions together
- into one file.
-
- Putting the Library Together
-
- By now we've created a small library of short, BIOS-based C functions.
- Before we use them in our intended sample program, let's reflect on how to
- organize this block of functions. One method is to give each its own file.
- Then, when we want to use a particular function in a program, we can add
- its filename to the QuickC programming list. Or we can simply append the
- function file to the program file. Another approach is to consolidate all
- the functions into one file and to add that file to the program list. This
- is more convenient, but it might result in adding code to your program for
- functions it doesn't use. If you use the functions frequently, the most
- satisfactory approach is to make a library file for them. (This procedure
- was described in Chapter 12.)
-
- Here's one way to make the library. Open the SCRFUN.C file from QuickC.
- Choose Compile from the Run menu and specify the Obj option. Then choose
- Compile File to produce a file called SCRFUN.OBJ. Now go to MS-DOS and
- enter the LIB command. Answer the prompts as shown:
-
- Library name: scrfun
- Library does not exist: create? y
- Operations: +scrfun
- List file: scrfun
-
- The LIB command creates a library file called SCRFUN.LIB in the current
- directory. You can then copy it to your library directory. The LIB command
- also creates a text file called SCRFUN that lists the names of the
- functions in the library.
-
- To help organize these functions, gather all the defined constants
- together into an include file. To this file, add function prototypes for
- all the functions. Then you can use this include file with your program.
- You still must incorporate the actual code by appending the source files
- or adding files to the program list or by using a library, but using the
- include file saves you the trouble of having to declare the functions. It
- also includes definitions useful to a program. We'll use the scrn.h
- include file (Listing 13-22 on the following page) for our programs.
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrn.h -- header file for BIOS video I/O functions */
- /* contained in scrfun.c and scrfun.lib */
- #define VIDEO 0x10
- #define SETMODE 0
- #define SETCURSOR 2
- #define GETCURSOR 3
- #define SETPAGE 5
- #define SCROLL 6
- #define READCHATR 8
- #define WRITECHATR 9
- #define GETMODE 15
- #define NORMAL 0x7
- #define VIDREV 0x70
- #define INTENSE 0x8
- #define BLINK 0x80
- #define COLS 80
- #define ROWS 25
- #define TEXTBW80 2
- #define TEXTC80 3
- #define TEXTMONO 7
-
- void Clearscr(void),
- Setvmode(unsigned char),
- Setpage(unsigned char),
- Setcurs(unsigned char, unsigned char,
- unsigned char),
- Read_ch_atr(unsigned char *, unsigned char *,
- unsigned char),
- Write_ch_atr(unsigned char, unsigned char,
- unsigned char, unsigned int),
- Rewrite(unsigned char, unsigned char),
- Getcurs(unsigned char *, unsigned char *,
- unsigned char);
-
- unsigned char Getvmode(void),
- Getpage(void),
- Curslt_lim(unsigned char),
- Cursrt_lim(unsigned char),
- Cursup_lim(unsigned char),
- Cursdn_lim(unsigned char);
-
- /* macro definitions */
-
- #define Home() Setcurs(0, 0, Getpage())
- /* the next four macros set cursor limits to the */
- /* full screen */
- #define Curslt() Curslt_lim(0)
- #define Cursrt() Cursrt_lim(COLS - 1)
- #define Cursdn() Cursdn_lim(ROWS - 1)
- #define Cursup() Cursup_lim(0)
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-22. The scrn.h include file.
-
- The scrn.h file includes some function numbers that we won't use until
- later chapters. It also has some constants that we'll use in our program.
- Finally, note the macros at the end of the file. The Home() macro homes
- the cursor, and the cursor-movement macros select a range corresponding to
- the entire screen.
-
- For convenience, we've collected all the new functions together as shown
- in Figure 13-6 in a file called SCRFUN.C (Listing 13-23). We discuss the
- Getvmode() and Setvmode() functions in Chapter 15.
-
- ┌───────────────────┐
- ┌─────┐ │ │
- Setcurs() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Getcurs() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Setpage() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Setvmode() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Clearscr() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Read_ch_atr() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Write_ch_atr() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Rewrite() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Getvmode() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Getpage() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Curslt_lim() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Curst_lim() │ ├────────────┼───── │
- └─────┘ │ │
- ┌─────┐ │ │
- Cursup_lim() │ ├──────┼─────────── │
- └─────┘ │ │
- ┌─────┐ │ │
- Cursdn_lim() │ ├────────────┼───── │
- └─────┘ │ │
- └───────────────────┘
- SCRFUN.C
-
- Figure 13-6. The SCRFUN.C program combines the functions we created
- previously.
-
- ──────────────────────────────────────────────────────────────────────────
- /* scrfun.c -- contains several video BIOS calls */
- /* Setcurs() sets the cursor position */
- /* Getcurs() gets the cursor position */
- /* Setpage() sets the current video page */
- /* Setvmode() sets the video mode */
- /* Clearscr() clears the screen */
- /* Read_ch_atr() reads the character and */
- /* attribute at the cursor */
- /* Write_ch_atr() writes a character and */
- /* attribute at the cursor */
- /* Rewrite() rewrites a screen character */
- /* with a new attribute */
- /* Getvmode() gets the current video mode */
- /* Getpage() gets the current video page */
- /* */
- /* The following functions use Setcurs() to move the */
- /* cursor one position at a time up to a limit. */
- /* Curslt_lim() moves cursor one column left */
- /* Cursrt_lim() moves cursor one column right */
- /* Cursup_lim() moves cursor one line up */
- /* Cursdn_lim() moves cursor one line down */
- /* */
- /* Programs using these functions should include the */
- /* scrn.h file */
-
- #include <dos.h>
- #include "scrn.h"
-
- /* sets cursor to row, column, and page */
- void Setcurs(row, col, page)
- unsigned char row, col, page;
- {
- union REGS reg;
-
- reg.h.ah = SETCURSOR;
- reg.h.dh = row;
- reg.h.dl = col;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- }
-
- /* gets current cursor row, column for given page */
- void Getcurs(pr, pc, page)
- unsigned char *pr, *pc, page;
- {
- union REGS reg;
-
- reg.h.ah = GETCURSOR;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- *pr = reg.h.dh; /* row number */
- *pc = reg.h.dl; /* column number */
- }
-
- /* sets page to given value */
- void Setpage(page)
- unsigned char page;
- {
- union REGS reg;
-
- reg.h.ah = SETPAGE;
- reg.h.al = page;
- int86(VIDEO, ®, ®);
- }
-
- /* sets video mode to given mode */
- void Setvmode(mode)
- unsigned char mode;
- {
- union REGS reg;
-
- reg.h.ah = SETMODE;
- reg.h.al = mode;
- int86(VIDEO, ®, ®);
- }
-
- /* clear the screen */
- void Clearscr()
- {
- union REGS reg;
-
- reg.h.ah = SCROLL;
- reg.h.al = 0;
- reg.h.ch = 0;
- reg.h.cl = 0;
- reg.h.dh = ROWS - 1;
- reg.h.dl = COLS - 1;
- reg.h.bh = NORMAL;
- int86(VIDEO, ®, ®);
- }
-
- /* reads the character and attribute at the cursor */
- /* position on a given page */
- void Read_ch_atr(pc, pa, page)
- unsigned char *pc, *pa;
- unsigned char page;
- {
- union REGS reg;
-
- reg.h.ah = READCHATR;
- reg.h.bh = page;
- int86(VIDEO, ®, ®);
- *pc = reg.h.al; /* character at cursor */
- *pa = reg.h.ah; /* attribute at cursor */
- }
-
- /* writes a given character and attribute at the */
- /* cursor on a given page for num times */
- void Write_ch_atr(ch, atr, page, num)
- unsigned char ch, atr, page;
- unsigned int num;
- {
- union REGS reg;
-
- reg.h.ah = WRITECHATR;
- reg.h.al = ch;
- reg.h.bl = atr;
- reg.h.bh = page;
- reg.x.cx = num;
- int86(VIDEO, ®, ®);
- }
-
- /* rewrites the character at the cursor using */
- /* attribute at */
- void Rewrite(at, page)
- unsigned char at, page;
- {
- unsigned char ch, atr;
-
- Read_ch_atr(&ch, &atr, page);
- Write_ch_atr(ch, at, page, 1);
- }
-
-
- /* obtains the current video mode */
- unsigned char Getvmode()
- {
- union REGS reg;
-
- reg.h.ah = GETMODE;
- int86(VIDEO, ®, ®);
- return reg.h.al;
- }
-
- /* obtains the current video page */
- unsigned char Getpage()
- {
- union REGS reg;
-
- reg.h.ah = GETMODE;
- int86(VIDEO, ®, ®);
- return reg.h.bh;
- }
-
- /* moves cursor one column left, but not past */
- /* the given limit */
- unsigned char Curslt_lim(limit)
- unsigned char limit;
- {
- unsigned char row, col, page;
- unsigned char status = 1;
- Getcurs(&row, &col, page = Getpage());
- if (col > limit)
- Setcurs(row, col - 1, page);
- else
- status = 0;
- return status;
- }
-
- /* moves cursor one column right, but not past */
- /* the given limit */
- unsigned char Cursrt_lim(limit)
- unsigned char limit;
- {
- unsigned char row, col, page;
- unsigned char status = 1;
-
- Getcurs(&row, &col, page = Getpage());
- if (col < limit)
- Setcurs(row, col + 1, page);
- else
- status = 0;
- return status;
- }
-
- /* moves cursor one row down, but not past */
- /* the given limit */
- unsigned char Cursup_lim(limit)
- unsigned char limit;
- {
- unsigned char row, col, page;
- unsigned char status = 1;
-
- Getcurs(&row, &col, page = Getpage());
- if (row > limit)
- Setcurs(row - 1, col, page);
- else
- status = 0;
- return status;
- }
-
- /* moves cursor one row down, but not past */
- /* the given limit */
- unsigned char Cursdn_lim(limit)
- unsigned char limit;
- {
- unsigned char row, col, page;
- unsigned char status = 1;
-
- Getcurs(&row, &col, page = Getpage());
- if (row < limit)
- Setcurs(row + 1, col, page);
- else
- status = 0;
- return status;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-23. The SCRFUN.C program.
-
- Our small routines certainly create a big file! However, you need only
- compile it once. After that, you can use the .OBJ or .LIB versions. We
- assume that you create a library file called SCRFUN.LIB.
-
- A Text Program
-
- Finally, after much development, we have at hand all the tools we need for
- our program. The ROAMSCRN.C program (Listing 13-24) shows the results of
- our efforts. To run the program within the QuickC environment, be sure
- that Screen Swapping On is active.
-
- ──────────────────────────────────────────────────────────────────────────
- /* roamscrn.c -- puts text on screen, positions */
- /* cursor with arrow keys, uses F1 */
- /* and F2 to control video inverse */
- /* program list -- roamscrn.c, scrfun.lib */
- /* user include files -- keys.h, scrn.h */
- /* Note: Activate Screen Swapping On in Debug menu */
- #include <conio.h>
- #include "keys.h"
- #include "scrn.h"
- #define BELL '\a'
- #define ESC '\033'
- #define PAGE 0
-
- char *Heading =
- "Use standard keys to enter text. Use arrow keys to "
- "reposition cursor.\nUse F2 to turn on video inverse "
- "and F1 to turn it off.\nHit the ESC key to quit.\n";
-
- main()
- {
- int ch;
- unsigned char atr = NORMAL;
-
- Clearscr();
- Home();
- printf("%s", Heading);
- while ((ch = getch()) != ESC)
- {
- if (ch == '\r')
- {
- putch('\n');
- putch('\r');
- }
- else if (ch != 0)
- {
- Write_ch_atr(ch, atr, PAGE, 1);
- if (!Cursrt())
- putch(BELL);
- }
- else
- {
- ch = getch();
- switch (ch)
- {
- case F1 : atr = NORMAL; break;
- case F2 : atr = VIDREV; break;
- case UP : Rewrite(atr, PAGE);
- if (!Cursup())
- putch(BELL);
- break;
- case DN : Rewrite(atr, PAGE);
- if (!Cursdn())
- putch(BELL);
- break;
- case LT : Rewrite(atr, PAGE);
- if (!Curslt())
- putch(BELL);
- break;
- case RT : Rewrite(atr, PAGE);
- if (!Cursrt())
- putch(BELL);
- break;
- default : break;
- }
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 13-24. The ROAMSCRN.C program.
-
- Let's see how it works. The keys.h include file is the one we used earlier
- in this chapter; it defines the mnemonics for the function keys and the
- cursor control keys. The scrn.h include file is the one we just presented.
- We assume that you bring in the BIOS code by including the SCRFUN.LIB file
- in the program list, but you can also use one of the other methods we
- mentioned if you prefer.
-
- The program begins with the attribute variable atr set to NORMAL. This is
- defined in scrn.h as 7, which is the normal attribute for white-on-black
- text. Next, the program clears the screen, homes the cursor, and prints an
- instructive heading. Finally, in the main part of the program, a large
- while loop uses getch() to read keyboard input until Esc is pressed to
- terminate input.
-
- Next, the program inspects ch, the input character typed by the user. If
- it is \r, the carriage return character generated by the Enter key, the
- program translates that into a newline, that is, into \n\r. If the
- character is some other ASCII or extended ASCII value, the program uses
- Write_ch_atr() to display that character. Why not use putch() here?
- Because putch() has no provision for specifying the attribute. Note, too,
- the following code fragment:
-
- if (!Cursrt())
- putch(BELL);
-
- Write_ch_atr(), like the BIOS call it uses, does not advance the cursor
- after writing the character. Therefore, we use Cursrt() to move the
- cursor. Recall that we created Cursrt_lim() to stop when it reaches the
- right side of the screen and that the macro Cursrt() uses the rightmost
- column as the limit. If the limit is reached, Cursrt() returns a value of
- 0, or false, causing the if statement to execute the putch(BELL) call. The
- action, then, is as follows: First the character is printed, then the
- program attempts to advance the cursor one column to the right. If it can,
- fine; otherwise, the system beeps. If you like, you can replace the
- beeping instructions with a Setcurs() command to relocate the cursor at
- the beginning of the next line.
-
- Finally, this sequence of if-else lines processes the case of ch being 0.
- This means the user entered a non-ASCII character. Another getch() call
- fetches the scan code for the key, and a switch checks for two of the
- function keys and for the arrow keys. Let's see what these keys do.
-
- If the user presses F1, the attribute variable atr is set to NORMAL; if
- the user presses F2, atr is set to VIDREV. This constant, defined in
- scrn.h as 0x70, is the reverse video attribute. The selected value for the
- variable atr is used in subsequent calls to Write_ch_atr() and Rewrite().
- The attribute setting holds until another is selected.
-
- Next, look at what happens when the Up Arrow key is pressed:
-
- case UP : Rewrite(atr, PAGE);
- if (!Cursup())
- putch(BELL);
- break;
-
- The Rewrite() function reads the character, if any, at the current cursor
- position and rewrites it using the current attribute. Then the cursor is
- moved up a line unless it already is at the top line. In that case, the
- system beeps. The purpose of the Rewrite() statement is to cause existing
- text to be replaced by text using the current attribute. For example, if
- you have selected the inverse attribute, then text passed over by the
- cursor is rewritten with that attribute. The coding for the other arrow
- keys is similar.
-
- All in all, the main program is fairly simple. Most of the work involved
- creating C functions to implement the various BIOS calls we needed to
- make.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 14 Monitors and Text Modes
-
- Professional application programs, including QuickC itself, use a much
- fancier screen interface than we have used in our programs. In this
- chapter we produce some of those screen features in QuickC. First we must
- overcome the problem posed by the variety of different display systems.
- IBM supports several monitor-video controller systems: monochrome, CGA,
- EGA, MCGA, and VGA. In general, these systems use different hardware, and
- different memory and port addresses. They also provide different colors,
- resolutions, and graphics capabilities.
-
- Writing programs that run on a range of video controller systems can be
- troublesome, especially if you want something fancier or faster than the
- teletype-like output produced by standard C Library functions. This
- chapter concentrates on solving these problems for text-mode programs. We
- continue using BIOS calls, and we introduce direct memory access and
- ports. We also look at the IBM "graphics character" set, which lets you
- create screen graphics without leaving text mode.
-
-
- Monitors and Controllers
-
- IBM has developed several different video standards, each involving its
- own hardware video controller and corresponding monitors. In the PC
- series, the hardware controllers are on add-on cards called "adapters." In
- the new PS/2 series, however, the circuitry for controlling the monitor is
- built into the motherboard. We use the term "video controller" in this
- book to encompass both the adapter cards and the built-in control
- circuitry.
-
- The most widely used video controller is the Monochrome Display Adapter,
- or MDA. When coupled with a monitor called the Monochrome Display, it
- produces a high-resolution, text-only display consisting of 25 rows of 80
- characters each.
-
- The next most commonly used controller is the Color Graphics Adapter, or
- CGA. It can be used with color or B/W monitors capable of either 40-by-25
- or 80-by-25 text displays (but not the Monochrome Display). It has seven
- separate modes of operation. Although the 80-by-25 display shows as many
- characters as the Monochrome Display, its lower resolution creates coarser
- text characters.
-
- Recently, the Enhanced Graphics Adapter, or EGA, has become popular. It is
- compatible with the Monochrome Display, with normal CGA displays, and with
- a high-resolution monitor called the Enhanced Display (ED). Used with a
- Monochrome Display, it provides a graphics mode in addition to the text
- mode. Used with CGA-style monitors, it provides more colors than the CGA
- board does. Used with the Enhanced Display (or equivalent), it emulates
- the CGA modes with increased text resolution, and it provides three
- additional graphics modes.
-
- The newest controllers are the Multi-Color Graphics Array (MCGA), found on
- the PS/2 Model 30, and the Video Graphics Array (VGA), found on the PS/2
- Models 50, 60, 70, and 80. The MCGA matches CGA resolution but offers an
- enormously greater range of colors. The VGA emulates the EGA modes, adds
- three new graphics modes, offers higher resolution for all text modes, and
- provides more colors.
-
- Table 14-1 summarizes some of the differences in features offered by the
- various video controllers we have introduced. Resolution is given in
- pixels, or picture elements, the elementary display elements from which
- characters and images are built. The size of a pixel depends on the
- controller and the mode. A pixel in mode 0, for example, is twice as wide
- as a pixel in mode 2. However, the VGA controller produces smaller pixels
- than the CGA controller, even when both are in mode 0. Also, not all
- monitors have sufficient resolution to support a controller's use of
- pixels. A CGA monitor, for example, is physically incapable of generating
- the higher resolution (smaller pixel) modes of the EGA and VGA. In
- general, all modes cannot produce the maximum number of colors, and only a
- subset of available colors can be shown at any one time.
-
- Table 14-1 Video Controllers
- Name Horizontal Vertical Colors Modes Monitors
- Resolution Resolution
- ──────────────────────────────────────────────────────────────────────────
- MDA 720 350 2 1 MD
- CGA 640/320 200 16 7 Color, B/W
- EGA 640/320 350/200 64 12 ED, MD,
- Color, B/W
- MCGA 720/360/640/320 400/480/200 262, 11 PSM, ED, MD,
- 144 Color, B/W
- VGA 720/360/640/320 400/480/350/200 262, 15 PSM, ED, MD,
- 144 Color, B/W
- ──────────────────────────────────────────────────────────────────────────
-
-
- Text Modes and Portability
-
- Fortunately, all these video controllers support an 80-by-25 text mode,
- and that simplifies the task of writing programs to run with all
- combinations.
-
- Controller Similarities
-
- A comparison of 80-by-25 text modes for different hardware combinations
- shows both similarities and differences. In all cases, the screen is
- treated as an array of characters rather than as an array of pixels. That
- is, you can only display or alter entire characters, not the individual
- pixels that comprise the characters.
-
- All controllers use two bytes of memory to represent each text-mode
- character. One byte holds the character's ASCII code, and the other byte
- holds the character's display attribute. All video controllers also
- contain random access memory (video RAM) in which character data is mapped
- to the display. That is, the controller periodically scans the video RAM
- to determine which characters it should display. Therefore, to change the
- screen display, you must change the appropriate bytes in the video RAM.
- Note that text-mode video RAM always consists of 4000 bytes: One screen
- holds 80 x 25, or 2000, characters, each represented by two bytes.
-
- All controllers also maintain a table of character fonts called a
- "character generator." The controller uses these pixel patterns to
- physically represent characters on the screen. For example, ASCII code 72
- in the video RAM tells the controller to put an H at a screen location,
- and the character font table specifies the particular "H" pixel pattern to
- use. (See Figure 14-1 on the following page.)
-
- These similarities ease the task of writing text programs that are
- compatible with the various displays.
-
- Video RAM Font ROM
- ┌─────────────────┐ ┌─────────────────┐
- │ 072 (ASCII code)│ │ H (Font info) │
- │ ▒ │ │ ▒ │
- └──▒──────────────┘ └─▒───────────────┘
- ▒ ▒
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- ▒
- ╔═════▒═════════════════════╗
- ║ ┌────────────────────┐ ║
- ║ │ H │ ║
- ║ │ │ ║
- ║ │ │ ║
- ║ │ │ ║
- ║ │ │ ║
- ║ └─────────────────────┘ ║
- ╚═══════════════════════════╝
- Display
-
- Figure 14-1. Producing characters with the MDA.
-
- Differences between displays complicate the programming process. For
- example, one powerful video technique called "direct memory access" uses
- pointers to video RAM to directly alter RAM contents. However, the MDA
- uses a different video RAM address than other controllers, so your program
- must always test for its use. Another programming technique uses "ports"
- to access registers on the controllers. However, the various controllers
- have different numbers of registers, and each register has a different
- port address and performs a different function. This makes for very
- involved hardware programming.
-
- Video controllers also contain differing amounts of video RAM. The MDA has
- only enough memory to hold one screenful, or page, of characters. The
- other controllers hold enough memory to hold four or more pages of text.
-
- Most controllers offer different screen resolutions. Although all the
- controllers display a maximum of 2000 characters on the screen, some can
- generate more pixels than others. For example, the CGA screen consists of
- a matrix of 640 horizontal pixels (for 80 characters) by 200 vertical
- pixels (for 25 display lines). The net result is that each character is
- represented by an 8-by-8-pixel grid, or "character box." The MDA, on the
- other hand, generates 720 horizontal pixels and 350 lines, providing a
- 9-by-14-pixel character box. Thus, MDA characters look better than their
- CGA counterparts because each character is drawn with more detail, as
- shown in Figure 14-2.
-
- Finally, the color displays can use the attribute byte to specify
- foreground and background colors for each character. Table 14-2 provides
- a summary of the different characteristics of video controllers operating
- in text mode.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 14-2 can be found on p.453 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 14-2. Character boxes.
-
- Table 14-2 Summary of Text-Mode Differences
- Controller Video RAM Starting Pages Character Color
- Address Box Size
- ──────────────────────────────────────────────────────────────────────────
- MDA 0xB8000 1 9 x 14 No
- CGA 0xB0000 4 8 x 8 Yes
- EGA (mode 7) 0xB8000 4/8 9 x 14 No
- EGA (modes 2, 3) 0xB0000 4/8 8 x 14 Yes
- MCGA 0xB0000 8 9 x 16 Yes
- VGA 0xB0000 8 9 x 16 Yes
- ──────────────────────────────────────────────────────────────────────────
-
-
- Device-independent Programming
-
- When programming for the PC, you have the choice of programming for
- specific hardware or ignoring the hardware altogether. The direct memory
- access method discussed later in this chapter uses hardware information
- explicitly. To write device-independent programs that don't require
- explicit hardware information, use one or more of the following methods:
-
- 1. Program with the standard C Library output functions such as printf()
- and putchar(). This results in portable code, but it limits the
- positioning of text and doesn't permit the use of color.
-
- 2. Use the ANSI.SYS escape sequences, as described in Chapter 13.
- However, if your program utilizes the cursor-control keys, for
- example, you must use console I/O functions, which restrict
- portability. Also, using ANSI.SYS inhibits some special features of
- the EGA, such as the 43-line display. The ANSI approach works on all
- systems that recognize the standard ANSI codes; IBM PCs and clones
- must have the ANSI.SYS driver installed.
-
- 3. Use IBM PC BIOS calls, as described in Chapter 13. The BIOS includes
- programs for all PC video controllers, and it selects the appropriate
- code for the display and controller you are using. That's why our
- examples in Chapter 13 didn't specify a monitor or controller. BIOS
- calls also support using more than one page of screen memory; but
- because the MDA has only one page, we suggest you restrict
- applications to page 0.
-
- We thoroughly covered the first two choices in the last chapter. Although
- we also discussed BIOS calls, we skipped some of the detail until you
- understood more about the hardware. In the next section we will discuss
- BIOS calls in greater detail.
-
- Working with BIOS Again: Attributes
-
- In Chapter 13, we built a small library (SCRFUN.LIB) of BIOS-based C
- functions that are hardware-independent. In fact, insulating the user from
- the hardware is one of the primary reasons for having a BIOS. For
- instance, we can use the same BIOS calls to control the way in which
- characters are displayed on an MDA, CGA, EGA, or VGA monitor. The
- attribute of a character controls its appearance. Let's see how we can use
- the BIOS to investigate and control attributes.
-
- An attribute is a 1-byte value in which the individual bits have
- particular meanings that affect the appearance of the associated
- character. For example, with the Monochrome Display Adapter, bit 7 of the
- attribute controls the blink function, bits 6─4 control the background,
- bit 3 controls the intensify foreground function, and bits 2─0 control the
- foreground──that is, the pixels constituting the character. (See Figure
- 14-3.) Table 14-3 lists the standard attribute values used by the MDA.
-
- Bit numbers ─────7 6 5 4 3 2 1 0
- ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- │ │ │ │ │ │ │ │ │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ └────────┬────────┘ │ └────────┬────────┘
- │ Background │ Foreground
- Blink bit Intensity bit
-
- Figure 14-3. Monochrome attribute bits.
-
- Table 14-3 Monochrome Attributes
- Bit Pattern Hex Value Meaning
- ──────────────────────────────────────────────────────────────────────────
- 0000 0000 0x00 No display
- 0000 0111 0x07 Normal display
- 0111 0000 0x70 Reverse video
- 0000 0001 0x01 Underline
- 0111 0111 0x77 Whiteout
- 1xxx xxxx 0x80 Blink mode
- xxxx 1xxx 0x08 Intensified foreground
- ──────────────────────────────────────────────────────────────────────────
-
- Note: The last two entries are used with different modes. For example,
- 10000111 (0x87) is normal display with blinking, while 11110000 (0xF0) is
- reverse video with blinking. The x's indicate that those values don't
- affect blinking or intensity.
-
- The other video controllers use bits 6─4 to control the color of the
- background and bits 2─0 to control the color of the foreground. Bits 7 and
- 3 serve the same function as they do for the MDA. Figure 14-4 shows the
- color that each bit controls.
-
- Bit numbers ─────7 6 5 4 3 2 1 0
- ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
- │ BL │ R │ G │ B │ I │ R │ G │ B │
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
- │ │ Red Green Blue │ │ │ Red Green Blue │
- │ └────────┬────────┘ │ └────────┬────────┘
- │ Background │ Foreground
- Blink Intensify
- foreground
-
- Figure 14-4. Color attribute bits.
-
- ──────────────────────────────────────────────────────────────────────────
- The EGA BIOS
- The IBM BIOS was created long before the inception of the EGA. How, then,
- can you use BIOS routines to control the EGA? The EGA card comes with a
- set of BIOS interrupt 0x10 video I/O routines in its own ROM. Recall that
- the address of each interrupt routine is stored in the interrupt vector
- table. When you boot an EGA system, the entry for video interrupt 0x10 is
- loaded with the EGA BIOS address instead of the motherboard BIOS address.
- Thus, the old BIOS routines are bypassed and the new EGA-supplied ones are
- used.
- ──────────────────────────────────────────────────────────────────────────
-
- To produce a blue character on a red screen, use an attribute of 01000001.
- This turns on the red background bit and the blue foreground bit. To make
- the foreground a bright blue, turn on the intensity bit with 01001001.
-
- If you set both the blue and green foreground bits, the blue and the green
- phosphors on the display screen are simultaneously turned on, producing a
- color called cyan. Setting all three foreground bits turns on all three
- colors, which, by the laws of video color mixing, produces white.
- Similarly, clearing all three bits causes no pixels to be turned on,
- producing black. Therefore, the "normal" monochrome attribute of 00000111
- also produces white-on-black characters for the CGA, EGA, and VGA. Table
- 14-4 shows the colors generated by the various 3-bit combinations.
-
- Table 14-4 Text Color Values
- Bit Pattern Hex Foreground Hex Background Color
- ──────────────────────────────────────────────────────────────────────────
- 000 0x0 0x00 Black
- 001 0x1 0x10 Blue
- 010 0x2 0x20 Green
- 011 0x3 0x30 Cyan
- 100 0x4 0x40 Red
- 101 0x5 0x50 Magenta
- 110 0x6 0x60 Dark yellow
- (brown)
- 111 0x7 0x70 White (light gray)
- ──────────────────────────────────────────────────────────────────────────
-
- Note: The hex value 0x8 intensifies the foreground color, and 0x80 makes
- the character blink. Also, all attributes can be combined using logical
- operators.
-
- ──────────────────────────────────────────────────────────────────────────
- Colors
- All colors can be produced by combining three primary colors together in
- varying proportions. The three "additive" primary colors are red, green,
- and blue. For example, when you direct a beam of green light and a beam of
- red light toward a piece of white paper, the area where the beams overlap
- (or are "added") appears yellow.
-
- A color video screen also creates colors by combining the additive
- primaries. Each pixel on a color screen contains individual red, green,
- and blue dots. Turning a pixel blue amounts to turning on the blue dots in
- the pixel. To produce yellow, you turn on the green and the red dots in a
- pixel──the eye perceives only the combined light, which is yellow.
-
- The PC's system of using numbers to represent color imitates the physical
- color-mixing process. For example, in binary notation, the color number
- for red is 100 and the color number for green is 010. Turning on both the
- red and the green bits corresponds to specifying the binary number 110,
- which is the code for yellow.
- ──────────────────────────────────────────────────────────────────────────
-
- Suppose you want a yellow character on a blue background. Because yellow
- is produced by combining red and green light (bits 2 and 1), and
- background blue is bit 4, the corresponding attribute is 00010110, or
- 0x16. (The actual colors you see depend on your monitor and its
- adjustments.)
-
- It's more interesting to display the attributes on the screen than it is
- to read about them, so let's develop a program that changes the attribute
- bits to demonstrate how the colors change. First we must write a function
- that prints a string using a given attribute. Using the functions from our
- SCRFUN.LIB library, we produce the following Print_attr() function
- (Listing 14-1).
-
- Print_attr() writes a character-attribute pair and moves the cursor one
- position to the right. Print_attr() has its limitations. First, it doesn't
- recognize the end of a line. Second, it doesn't have all the fancy
- formatting that printf() has. (You can modify the function to handle the
- end-of-line problem and use the sprintf() function to do the formatting.)
-
- Now let's use the Print_attr() string-displaying function in a program to
- display the various attributes. To demonstrate the role of each attribute
- bit, the program has you type the attribute byte as a binary number. The
- comments in the ATTRIB.C program (Listing 14-2 on the following page)
- explain the workings of the various functions. Before you run the ATTRIB.C
- program, pull down the Debug menu: Screen Swapping On should be active
- (indicated by a check to the left of the option). If it is not active,
- choose it from the menu to activate it.
-
- This program works with any of the previously mentioned standard video
- controllers. If you have a monochrome monitor, check to see what
- nonstandard combinations such as 00000100 produce. If you have a color
- monitor, enjoy the many color combinations.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Print_attr() -- prints the string str using */
- /* attribute attr on the indicated page */
- /* It uses functions from the scrfun.c file. */
-
- void Print_attr(str, attr, page)
- char *str;
- unsigned char attr, page;
- {
- while (*str != '\0')
- {
- Write_ch_atr(*str++, attr, page, 1);
- Cursrt();
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-1. The Print_attr() function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* attrib.c -- this program illustrates attributes */
- /* program list: attrib.c, scrfun.lib */
- /* user include files: scrn.h */
- /* Note: activate Screen Swapping On in Debug menu */
- #include <stdio.h>
- #include <conio.h>
- #include "scrn.h"
- #define PAGE 0
- #define ESC '\033'
- char *Format = "This message is displayed using an "
- "attribute value of %2X hex (%s).";
- int Get_attrib(char *);
- void Print_attr(char *, unsigned char, unsigned char);
-
- main()
- {
-
- int attribute; /* value of attribute */
- char attr_str[9]; /* attr. in string form */
- char mesg[80];
-
- Clearscr();
- Home();
- printf("Enter an attribute as an 8-digit binary "
- "number, such as 00000111, and see a\n"
- "message displayed using that attribute."
- "Hit <Esc> to quit.\n"
- "Attribute = ");
- while ((attribute = Get_attrib(attr_str)) != -1)
- {
- Setcurs(10,0,PAGE);
- sprintf(mesg, Format, attribute, attr_str);
- Print_attr(mesg, attribute, PAGE);
- Setcurs(2, 12, PAGE);
- printf(" "); /* clear old display */
- Setcurs(2, 12, PAGE);
- }
- Clearscr();
- }
-
- /* The following function reads in a binary number */
- /* as a sequence of 1s and 0s. It places the 1 and 0 */
- /* characters in a string whose address is passed as */
- /* an argument. It returns the numeric value of the */
- /* binary number. Bad input is summarily rejected. */
- /* The function returns -1 when you press Esc. */
- int Get_attrib(a_str)
- char a_str[]; /* attribute as binary string */
- {
- int attrib[8];
- int index = 7;
- int ch;
- int attribute = 0; /* attrib. as numeric value */
- int pow;
-
- a_str[8] = '\0'; /* terminate string */
- while ((index >= 0) && (ch = getch()) != ESC)
- {
- if (ch != '0' && ch != '1') /* bad input */
- putch('\a');
- else
- {
- putch(ch);
- a_str[index] = ch; /* string form */
- attrib[index--] = ch - '0'; /* numeric */
- }
- }
- if (ch == ESC)
- return (-1);
- else /* convert numeric array to a number */
- {
- for(index = 0, pow = 1; index < 8;
- index++, pow *= 2)
- attribute += attrib[index] * pow;
- return attribute;
- }
- }
-
- /* The following function prints the string str using */
- /* attribute attr on the indicated page. */
- /* It uses functions from the scrfun.c file. */
-
- void Print_attr(str, attr, page)
- char *str;
- unsigned char attr, page;
- {
- while (*str != '\0')
- {
- Write_ch_atr(*str++, attr, page, 1);
- Cursrt();
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-2. The ATTRIB.C program.
-
- Attributes and Bitwise Operators
-
- You can also manipulate attributes with the C bitwise operators that we
- discussed in Chapter 7. Suppose, for example, a program uses the
- following definitions:
-
- #define NORMAL 0x07
- #define VIDREV 0x70
- #define BLINK 0x80
- #define INTENSE 0x08
-
- To set mode to an intense, normal attribute, we can use the bitwise
- logical OR operator, as follows:
-
- mode = NORMAL | INTENSE;
-
- Because 1 OR anything is 1, all bits set to 1 are left on. Now, suppose
- mode has already gone through several changes. At this point it might be
- normal, reverse video, or have blinking on or off, etc. To turn on the
- intensify mode regardless of the current state, use an instruction like
- the following:
-
- mode = mode | INTENSE;
-
- The only bit this instruction can change is bit 3, the intensity bit,
- because all the other bits of INTENSE are 0, and 0 OR any bit is merely
- that bit. (That is, 0 | 0 is 0, and 1 | 0 is 1.) Furthermore, this
- instruction always sets bit 3 to 1, regardless of its previous value. (0 |
- 1 is 1, and 1 | 1 is 1.) Incidentally, you can also use a combination
- assignment operator to rewrite the last C statement as follows:
-
- mode |= INTENSE; /*unconditionally turns INTENSE on */
-
- Sometimes an instruction must "toggle" a bit. That is, the instruction
- turns on an off bit or turns off an on bit. For this, we use the EXCLUSIVE
- OR operator (^). Recall that this operator produces a "true" value (1) if
- one operand or the other is "true" but not if both are "true." The
- following expression toggles the intensity bit:
-
- mode = mode ^ INTENSE;
-
- If the intensity bit in mode is initially off, the expression becomes 0 ^
- 1, which is 1, or on. If the intensity bit in mode is initially on, the
- expression is 1 ^ 1, which is 0, or off. Again, we can simplify the
- statement with the following combination assignment operator:
-
- mode ^= INTENSE; /* toggles the intensity bit */
-
- Compatible "Graphics"
-
- A system with true graphics capability lets you individually control each
- pixel on the screen. By its very makeup, the MDA lacks that ability. The
- CGA provides graphics modes, and the EGA and VGA have additional graphics
- abilities. Therefore, to produce true graphics, a program must address
- specific hardware.
-
- However, IBM has given the PC a limited but more universal graphics
- capability by extending the character set. The ASCII character set uses
- the values 0 through 127. But because a byte can store any value through
- 255, IBM added 128 additional characters to the set and assigned them code
- values 128 through 255. These constitute the IBM Extended Character Set.
- Many of these characters are mathematical symbols, foreign-language
- characters, and so on. However, 48 of the characters (codes 176 through
- 223) constitute the "graphics characters," which are useful for drawing
- and filling rectangular forms.
-
- With these characters, you can do a limited amount of hardware-independent
- graphics. The QuickC screen, for example, uses these characters to draw
- its boxes and borders. In fact, you can use QuickC to examine the extended
- ASCII set. Pull down the general Help menu and browse through it until you
- reach the screen that displays the extended set.
-
- You can also display the extended set of characters from the keyboard. To
- see what character 206 looks like, first press Num Lock; then, at your
- system prompt, hold down the Alt key and type 206 using the keys in the
- numeric keypad. When you release Alt, the character appears on the
- display.
-
- Programming with the Graphics Character Set
-
- Let's develop a QuickC program to help us investigate the extended
- character set. Below are some of the features we need to develop in a
- program that draws with the graphics characters.
-
- ■ Key-mapping so that a single keystroke generates a graphics character
-
- ■ Cursor control for drawing at different screen locations
-
- ■ An erasing feature
-
- ■ An auto-drawing feature that generates strings of characters across the
- screen
-
- ■ An attribute manipulator for highlighting text or turning on blinking
-
- Many of these goals resemble problems we solved in Chapter 13; therefore,
- we can put our previously developed tools to good use now. For example, we
- can use the getch() function and scan codes to use the function keys and
- the cursor-control keys. We can call the BIOS to clear the screen and to
- provide cursor-movement functions. And by using the method developed for
- REKEY.C (Listing 13-7 on p. 411), we can map the keys. In short, we have
- the tools; now we have to organize them into a workable QuickC program.
-
- The User Interface
-
- Today it is not enough to design a program that works. Interactive
- programs require that the programmer think about the user's point of view.
- In our case, for example, we need to plan how to best use the keyboard to
- control the graphics characters.
-
- For example, which key represents which character? With 48 graphics
- characters, there is no obvious mnemonic method for assigning keys. And it
- is unreasonable to expect a user to remember 48 random assignments. To
- help the user, we display the graphics characters and key assignments at
- the bottom of the screen. We also list other important keys.
-
- Next, we must plan how to manage the drawing process. Drawing with
- graphics characters often involves repeatedly using the same character.
- Merely mapping a graphics character to a key is acceptable only for
- repeating the character left to right because that's the way keyboard
- input normally works. But drawing characters vertically or from right to
- left is more difficult. Cursor control helps, but drawing a vertical line
- would entail pressing the character key, using the Down Arrow key to move
- down a line, using the Left Arrow key to move under the first character,
- and then pressing the character key again. Therefore, we must use a
- different technique.
-
- In our solution to the problem, the character keys select, but do not
- display, a graphics character. To actually display the character, the user
- must press one of the arrow keys. This places the character at the current
- position of the cursor, then shifts the cursor in the direction of the
- arrow key. Until the user selects another character key, the current
- graphics character remains active. Therefore, repeatedly pressing an arrow
- key moves the cursor and leaves a display trail of the current graphics
- character. This simplifies drawing horizontal and vertical lines.
-
- Now let's add some refinements. The PgUp key disenables drawing so that
- the user can move the cursor without displaying characters. The PgDn key
- restores the drawing mode. The Spacebar represents the program's "eraser."
- The user can select the Spacebar as the current graphics character and use
- the cursor keys to delete unwanted characters. Our last refinement lets
- the user select character attributes with the function keys.
-
- We take advantage of QuickC's program list feature to split the program
- into three file modules. Note that two of the modules use SCRFUN.LIB and
- scrn.h and one uses keys.h, all developed in Chapter 13. We also collect
- the define statements for the three modules in an include file called
- grafchar.h.
-
- First, look at the main program, GRAFCHAR.C (Listing 14-3). To run this
- program within the QuickC environment, be sure that Screen Swapping On is
- active (on the Debug menu).
-
- GRAFCHAR.C is a simple program──it merely calls the other two files in the
- program list: initstuf.c and drawchar.c. We will discuss these modules in
- the next two sections. Before you proceed to those sections, however,
- examine the grafchar.h header file (Listing 14-4).
-
- ──────────────────────────────────────────────────────────────────────────
- /* grafchar.c -- draws graphics characters with */
- /* attributes on the screen */
- /* Program list : grafchar.c, initstuf.c, drawchar.c, */
- /* scrfun.lib */
- /* User include files: keys.h, scrn.h, grafchar.h */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include "grafchar.h"
- unsigned char Grchr[NUMCHARS]; /* to store graphics set */
- void Init_stuff(void); /* in initstuf.c */
- void Draw_chars(void); /* in drawchar.c */
-
- main()
- {
- Init_stuff(); /* initialize vital elements */
- Draw_chars(); /* map keys to graphics characters */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-3. The GRAFCHAR.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* grafchar.h -- header file for grafchar.c program */
- /* Version 1 */
- #define NUMCHARS 48 /* number of graphics chars */
- #define SPACE '\040'
- #define BOTLINE 19 /* line # for end of drawing space */
- #define PAGE 0
- #define GCSTART 0xB0 /* ascii for first graphics char */
- #define BEEP '\a'
- #define ESC '\033'
- #define TRUE 1
- #define FALSE 0
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-4. The grafchar.h header file.
-
- Setting Up the Program: initstuf.c
-
- The initstuf.c module (Listing 14-5 on the following page) sets up the
- GRAFCHAR.C program. It initializes the external array grafchar.c to the 48
- graphics characters and clears the screen. At the bottom of the screen, it
- prints the graphics characters and their corresponding keystrokes. Also
- listed are the following non-ASCII keys and their functions: The F1
- function key sets the normal white-on-black text attribute; F2 selects
- reverse video; F3 toggles blinking; and F4 toggles foreground intensity.
- The program uses Print_attr() to show how the last three attributes appear
- on screen. For example, the phrase F3 : Blinking is displayed on screen in
- the blinking mode.
-
- ──────────────────────────────────────────────────────────────────────────
- /* initstuf.c -- initializing module for grafchar.c */
- /* assigns graphics character codes to an array */
- /* and initializes screen */
-
- #include "scrn.h" /* Clearscr(), Home(), Setcurs() */
- #include "grafchar.h"
- extern unsigned char Grchr[]; /* defined in grafchar.c */
- void Print_attr(char *, unsigned char, unsigned char);
- [bn]
- void Init_stuff()
- {
- int i;
-
- /* initialize array with graphics characters */
- for (i = 0; i < NUMCHARS; i++)
- Grchr[i] = GCSTART + i;
- Clearscr();
- Home();
-
- /* show key meanings at bottom of screen */
- Setcurs(BOTLINE + 1, 0, PAGE);
- for (i = 0; i < 40; i++) /* graphics chars */
- {
- putch(Grchr[i]);
- putch(SPACE);
- }
- Setcurs(BOTLINE + 2, 0, PAGE);
- for (i = 0; i < 40; i++) /* key assignments */
- {
- putch('0' + i);
- putch(SPACE);
- }
- Setcurs(BOTLINE + 3, 0, PAGE);
- for (i = 40; i < NUMCHARS; i++) /* second row */
- {
- putch(Grchr[i]);
- putch(SPACE);
- }
- /* show function key assignments */
- printf(" SPACE : ERASE PgUp : No Draw ");
- printf(" PgDn : Draw ESC : Quit");
- Setcurs(BOTLINE + 4, 0, PAGE);
- for (i = 40; i < NUMCHARS; i++) /* second row */
- {
- putch('0' + i);
- putch(SPACE);
- }
- /* more function key assignments */
- Print_attr("F1 : Normal ", NORMAL, PAGE);
- Print_attr("F2 : Reverse Video ", VIDREV, PAGE);
- Setcurs(BOTLINE + 5, 16, PAGE);
- Print_attr("F3 : Blinking ", NORMAL | BLINK, PAGE);
- Print_attr("F4 : Intense ", NORMAL | INTENSE, PAGE);
- Home();
- }
-
- void Print_attr(str, attr, page)
- char *str;
- unsigned char attr, page;
- {
- while (*str != '\0')
- {
- Write_ch_atr(*str++, attr, page, 1);
- Cursrt();
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-5. The initstuf.c module.
-
- Drawing the Characters: drawchar.c
-
- The final module, drawchar.c (Listing 14-6), contains the code that
- translates keystrokes into action. First, it maps the 48 keystrokes to the
- graphics characters. After the program initializes the Spacebar character,
- it uses a switch statement to process PgUp, PgDn, the cursor control keys,
- and the four function keys. Figure 14-5 on p. 467 shows some sample
- output from this program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* drawchar.c -- drawing module for grafdraw.c */
- /* translates keystrokes to graphic characters, */
- /* manages cursor control and function keys */
-
- #include <conio.h>
- #include "keys.h"
- #include "scrn.h"
- #include "grafchar.h"
- extern unsigned char Grchr[]; /* defined in grafchar.c */
-
- void Draw_chars()
- {
- int ch, chout;
- unsigned char attrib = NORMAL;
- unsigned char draw = TRUE;
-
- chout = Grchr[0]; /* default graphics character */
- while ((ch = getch()) != ESC)
- {
- if (ch >= '0' && ch <= '_')
- chout = Grchr[ch - '0'];
- /* this maps the 0 key to the first */
- /* graphics character, etc. */
- else if (ch == SPACE)
- chout = SPACE;
- else if (ch == 0) /* process cursor keys */
- { /* and function keys */
- ch = getch();
- switch (ch)
- {
- case PU : draw = FALSE;
- break;
- case PD : draw = TRUE;
- break;
- case UP : if (draw)
- Write_ch_atr(chout, attrib,
- PAGE, 1);
- if (!Cursup())
- putch(BEEP);
- break;
- case DN : if (draw)
- Write_ch_atr(chout, attrib,
- PAGE, 1);
- if (!Cursdn_lim(BOTLINE))
- putch(BEEP);
- break;
- case LT : if (draw)
- Write_ch_atr(chout, attrib,
- PAGE, 1);
- if (!Curslt())
- putch(BEEP);
- break;
- case RT : if (draw)
- Write_ch_atr(chout, attrib,
- PAGE, 1);
- if (!Cursrt())
- putch(BEEP);
- break;
- case F1 : attrib = NORMAL; break;
- case F2 : attrib = VIDREV; break;
- case F3 : attrib ^= BLINK; break;
- case F4 : attrib ^= INTENSE; break;
- default : putch(BEEP);
- }
- }
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-6. The drawchar.c module.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ Figure 14-5 can be found on p.467 of the printed version of the book. │
- └────────────────────────────────────────────────────────────────────────┘
-
- Figure 14-5. Drawing with GRAFCHAR.C.
-
- Details of the Program
-
- The easiest way to see how the program works is to try it out. Move the
- cursor around with the arrow keys and use the keyboard to select different
- graphics characters. (Remember to have Screen Swapping On active if you're
- working in the QuickC environment.) To help you see why it works the way
- it does, we'll look at some of the programming details next.
-
- In the Draw_chars() function, the following statement maps the keystrokes
- to the graphics characters:
-
- if (ch >= '0' && ch <= '_')
- chout = Grchr[ch - '0'];
-
- In this function, ch is the input character; chout is the output character
- used when the cursor keys are pressed. When the user presses the 0 key,
- the program uses the array index of '0' - '0', or 0, which selects the
- first graphics character in the array. Similarly, the 1 key selects the
- second graphics character, and so on.
-
- Next, look at how the program handles the cursor key:
-
- case UP : if (draw)
- Write_ch_atr(chout, attrib, PAGE, 1);
- if (!Cursup())
- putch(BEEP);
- break;
-
- If draw is set to TRUE, the program displays the current output character
- (chout) using the current attribute (attrib). In this example, the cursor
- then moves up one line unless it already is at the top line, in which case
- the program issues a beep.
-
- The other arrow keys are processed similarly. Note that the Down Arrow key
- uses Cursdn_lim(BOTLINE) instead of Cursdn(). Recall from Chapter 13 that
- Cursdn() is a macro that moves the cursor down as far as line 25;
- Cursdn_lim(), however, is limited by a passed argument. Because we reserve
- the bottom of the screen for the key table, the BOTLINE limit keeps the
- cursor from intruding.
-
- The program uses the following code for selecting an attribute:
-
- case F1 : attrib = NORMAL; break;
- case F2 : attrib = VIDREV; break;
- case F3 : attrib ^= BLINK; break;
- case F4 : attrib ^= INTENSE; break;
-
- F1 and F2 set the attribute to the normal and reverse video modes,
- respectively. F3 and F4 use the EXCLUSIVE OR operator to toggle the
- intensify and the blink modes. Why directly set two modes and toggle the
- other two? The toggled intensify and blink modes can be on simultaneously
- and compounded with the other modes. However, NORMAL must be off when
- VIDREV is on, and vice versa. If both were on at the same time, the screen
- would display a white foreground on a white background──that is, a
- featureless white square. If both were off, the display would be black on
- black, or no display.
-
- Limitations of the Program
-
- To make the GRAFCHAR.C program compatible with all monitors (except
- 40-by-25 displays), we must impose some limitations. Most importantly, the
- program limits attributes to monochrome values; less importantly, it uses
- only page 0 of memory.
-
- If you have a color monitor, you can add color to the program by setting
- the function keys as follows:
-
- case F1 : attrib ^= BLUE; break;
- case F2 : attrib ^= GREEN; break;
- case F3 : attrib ^= RED; break;
- case F4 : attrib ^= BG_BLUE; break;
- case F5 : attrib ^= BG_GREEN; break;
- case F6 : attrib ^= BG_RED; break;
- case F7 : attrib ^= BLINK; break;
- case F8 : attrib ^= INTENSE; break;
-
- Also, make the following definitions:
-
- #define BLUE 0x1
- #define GREEN 0x2
- #define RED 0x4
- #define BG_BLUE 0x10
- #define BG_GREEN 0x20
- #define BG_RED 0x40
-
- This lets you independently toggle each bit of the attribute. When the
- attribute is initially set to NORMAL, blue, green, and red are all toggled
- on. Pressing F2, for example, turns green off and leaves the red-blue
- (magenta) combination.
-
- You should also change the key table at the bottom of the screen to
- reflect the new uses of the function keys.
-
-
- Direct Memory Access
-
- Thus far, we've used BIOS routines to place the proper character and
- attribute bytes into video memory. This method offers two advantages──it
- saves us work and lets us write hardware-independent programs that run on
- the MDA, CGA, EGA, VGA, and MCGA. However, we can create faster programs
- by bypassing the BIOS and placing data directly into video memory. This
- programming technique is called "Direct Video Memory Access," which we
- will refer to as DMA for the remainder of this book. (Don't confuse this
- use of DMA with the DMA chip built into the IBM PC and compatibles, which
- performs a different function.)
-
- DMA Basics
-
- To copy information to or from a memory location, you need to use the
- address of that location. Often, you do so symbolically and indirectly by
- using variables and array names. Pointers provide a more obvious way to
- use addresses. So what do you use to access video memory?
-
- Because video memory is a large block of bytes, it's natural to think of
- it as a large array. As you've learned, arrays can often be described by
- either array notation or pointer notation. But with video memory, you must
- use pointers. The reason is that the compiler chooses the physical
- addresses to which an array corresponds, but you can choose the physical
- address to which a pointer points. In particular, you can choose to have a
- pointer point to the beginning of video memory.
-
- The specific address you use depends on the hardware. The MDA uses 0xB0000
- (720,896 in decimal), and the CGA uses 0xB8000 (753,664 in decimal). The
- EGA and VGA use 0xB0000 for the monochrome mode and 0xB8000 for the color
- text modes.
-
- To use the address you must typecast the numeric value to the proper
- pointer type. Also, in the small and medium memory models, a data pointer
- is a 16-bit quantity. Neither of the addresses we need fits into 16 bits.
- The large memory model uses a 32-bit pointer, but not in a way that lets
- us make our simple assignment. So before we can assign the video RAM
- address to a pointer, we must first examine how the PC and QuickC handle
- memory addresses.
-
- Segmented Memory
-
- The PC has the same problem with large addresses that the small and medium
- models do. The 8086 chip normally uses a 16-bit register for addresses.
- However, this permits the register to address a maximum of 64 KB of
- memory, which falls far short of the address needed to access the video
- RAM.
-
- The 8086 family of microprocessors uses segmented memory to overcome this
- problem. The maximum size of each segment is 64 KB, the size addressable
- by an address register. Typically, a program uses one segment for program
- code and a second segment for data.
-
- Addresses for the program code are 16-bit addresses relative to the
- beginning of the program segment, and data addresses are relative to the
- beginning of the data segment. These relative addresses are called
- "offsets." In C, this offset is what is stored in a 16-bit pointer. The
- following statement:
-
- printf("Address of x is %u\n", &x);
-
- prints out the offset, in bytes, of x from the beginning of a data
- segment. To keep track of where the code and data segments are, the PC
- uses special registers: the CS, or Code Segment register, and the DS, or
- Data Segment register.
-
- To solve the problem of identifying the location of a segment using only
- 16 bits, the PC divides the actual address of the segment by 16 (0x10
- hex). For example, 0xB0000 divided by 0x10 is 0xB000. This divided
- quantity is called the "segment value." (See Figure 14-6.) Thus, a
- segment value of 0xA000 corresponds to a segment address of 0xA0000. As a
- result of this system, segments must start at addresses that are multiples
- of 16.
-
- Suppose you want to specify the 0x20th byte of video memory. The absolute
- address of this byte is 0xB0020. The PC represents this by setting the
- data segment register DS to the segment value of 0xB000 and setting the
- data offset register to 0x20. The following equation expresses the
- relationship more generally:
-
- absolute address = 0x10 x segment value + offset
-
- ┌─┌──────────────────────┐
- │ │ │
- │ │ │
- │ │ │
- │ ├──────────────────────┤
- Data segment───┤ │ 01001101 01001111 │───Data at
- (64 KB) │ ├──────────────────────┤─┐ specifie
- │ │ │ │ location
- DS register │ │ │ │
- ┌───────────────┐ │ │ │ │
- │ Segment value │ │ │ │ ├─Offset
- └──────┬────────┘ │ │ │ │
- │ │ │ │ │
- └─└─────────────────────┘─┘
- Segment value * 16 = Segment address────┘
-
- Figure 14-6. Data addresses are represented by a segment value and an
- offset.
-
- Note that you can represent the same physical address in many ways. For
- example, the absolute address 0xB0020 also can be represented with a
- segment value of 0xB001 and an offset of 0x10.
-
- C, Segments, and Offsets
-
- As you already know, C has two classes of pointers──near and far. Near
- pointers, which are 16 bits, hold only the offset. Far pointers, which are
- 32 bits, use the high 16 bits to hold the segment value and the low 16
- bits to hold the offset, as shown in Figure 14-7. Compact and large
- models use far data pointers by default. Small and medium models use near
- data pointers by default. However, you can use the nonstandard C keyword
- far to create far pointers in the small and medium models. To use the far
- keyword in QuickC, choose Language Extensions in the Compile Options
- dialog box.
-
- Using a Far Pointer
-
- To access the video memory, we must declare a far pointer, initialize its
- high bytes to the segment value for the video RAM, and use its low bytes
- for the offset. Declare a far pointer by using the keyword far, as
- follows:
-
- unsigned short far *far_pnt; /* far pointer */
-
- This creates a 32-bit pointer that points to a 2-byte unit. Each 2-byte
- unit holds the ASCII code and the attribute of a single displayed
- character.
-
- Next, let's set the pointer to an absolute screen address of 0xB0020. This
- corresponds to a segment value of 0xB000 (0xB0000 / 0x10) and an offset of
- 0x20. To place the segment value into the high bytes, left-shift it 16
- places; because the offset goes into the lower bytes, you needn't
- manipulate it at all.
-
- far_pnt = (unsigned short far *) (0xB000L << 16) | 0x20;
-
- Note that we used a typecast to convert the right side (type long) to the
- correct type. Next, we used the L suffix to make the segment value type
- long. Otherwise, 0xB000 would be treated as type int (a 16-bit type on a
- PC), and the 16-bit left-shift would shift all the bits out, leaving only
- zeros. (See Figure 14-8 on the following page.)
-
- Note also that the bitwise OR (|) operator combines the segment value and
- the offset. Because one resides entirely in the upper bytes and the other
- is confined to the lower bytes, this has the same effect as addition, but
- is faster.
-
- Segment value Offset
- ┌────────┴────────┬────────┴────────┐
- ┌────────┬────────┬────────┬────────┐
- │ │ │
- └────────┴────────┴────────┴────────┘
- High bytes Low bytes
-
- Figure 14-7. Filling a far pointer.
-
- │ 1 byte │
- ┌────────┬────────┐
- │ B 8 │ 0 0 │
- └────────┴────────┘
- ▒
- B800 << 16
- ▒
- ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
- ▒ ▒
- ┌────────────────┐
- B 8 0 0 │ 0 0 │ 0 0 │
- └───────────┘ └────────┴────────┘
- Discarded Values
-
- ┌────────┬────────┬────────┬────────┐
- │ 0 0 │ 0 0 │ B 8 │ 0 0 │
- └────────┴────────┴────────┴────────┘
- ▒
- B800L << 16
- ▒
- ┌────────┬────────────────┬────────┐
- │ B 8 │ 0 0 │ 0 0 │ 0 0 │
- └────────┴────────┴────────┴────────┘
-
- Figure 14-8. The left-shift operator.
-
- Finally, note that the far pointer does not hold the absolute address. It
- holds two quantities: segment value and offset. If, for some reason, you
- want the absolute address, you can obtain it by using the following
- expression:
-
- abs_addr = 0x10 * (far_ptr >> 16) + far_ptr & 0xFFFF;
-
- The right-shift produces the segment value; multiplying by 0x10 gives the
- segment address; and the 0xFFFF mask screens the segment value of the
- pointer, leaving just the offset. The variable abs_addr should be type
- long or unsigned long so that it can hold the entire address.
-
- Using Direct Memory Access──An Example
-
- To use DMA to access video RAM, we must declare a far pointer and
- initialize it to point to the beginning of video memory. To do so, we must
- first decide which data type to point to. Think of one page of video
- memory as 2000 character-attribute units, with each unit describing a
- particular screen location. In QuickC, two bytes constitute a short value,
- so our pointer must be a far pointer to unsigned short. Use typedef to
- make VIDMEM a synonym for that type:
-
- typedef unsigned short (far * VIDMEM);
-
- Then declare screen as a pointer of that type, as follows:
-
- VIDMEM screen;
-
- Next, you must decide which segment value to use. For the monochrome mode
- (mode 7), use 0xB000. For the CGA and CGA-compatible modes (0 through 6),
- use 0xB800. Because we must left-shift the segment value 16 bits to use it
- with a C far pointer, we represent our values as follows:
-
- #define MONMEM ((VIDMEM) (0xB000L << 16))
- #define CGAMEM ((VIDMEM) (0xB800L << 16))
-
- The L suffix makes the addresses 32-bit quantities, and the typecasting to
- type VIDMEM gives the numeric values the same type as the screen pointer.
-
- Recall that in the character-attribute pair, the low byte holds the
- character and the high byte holds the attribute. Therefore, if screen is
- the pointer to the beginning of video memory, if offset is the character
- position we wish to set, and if ch and attrib are character and attribute
- values, we use the following statement:
-
- *(screen + offset) = (attrib << 8) | ch;
-
- The left-shift puts attrib into the high byte, and the bitwise OR operator
- combines the resulting values, as shown in Table 14-5. Note that in
- QuickC, attrib must be at least a 16-bit type. If it is an 8-bit type, the
- significant bits are lost. (With older versions of Microsoft C, type char
- was converted to int for calculation, and no bits were discarded.)
-
- Let's use this information in a simple program. The CH2000.C program
- (Listing 14-7 on the following page) echoes any pressed ASCII key.
- However, instead of echoing it once, the program uses DMA to echo it 2000
- times. Also, the program cycles through all possible attribute values,
- making the color version more spectacular than the monochrome version. (Be
- sure Screen Swapping On is active. Also, use CGAMEM instead of MONMEM if
- you are using a color display.)
-
- The program first reads a character. The for loop displays the character
- at all 2000 positions. By using the increment operator on attrib, we
- change the attribute at each position and cycle through all 256
- possibilities.
-
- Table 14-5 Manipulating Character and Attribute in RAM
- Byte(s) Bit Values Hex Equivalents
- ──────────────────────────────────────────────────────────────────────────
- attrib 00000000 00000111 0x0007
- attrib << 8 00000111 00000000 0x0700
- ch 01000001 0x41
- (attrib << 8) | ch 00000111 01000001 0x0741
- ──────────────────────────────────────────────────────────────────────────
-
- ──────────────────────────────────────────────────────────────────────────
- /* ch2000.c -- fills screen with 2000 characters */
- /* This program demonstrates direct memory access */
- /* of video memory. It is set up for the MDA. */
- /* Assign CGAMEM instead of MONMEM to screen for */
- /* CGA and CGA-compatible modes. */
- /* Press a key to fill; press Esc to quit. */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include <conio.h>
- #include "scrn.h"
- typedef unsigned short (far * VIDMEM);
- #define MONMEM ((VIDMEM) (0xB000L << 16)) /* monochrome */
- #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
- #define ESC '\033'
- #define CHARS 2000
- #define AMASK 0xFF /* keep attribute in range */
- main()
- {
- unsigned ch; /* character to be displayed */
- unsigned attrib = 7; /* initial attribute */
- VIDMEM screen; /* pointer to video RAM */
- int offset; /* location on screen */
-
- screen = MONMEM; /* monochrome initialization */
- while ((ch = getch()) != ESC)
- {
- for (offset = 0; offset < CHARS; offset++)
- *(screen + offset) = ((attrib++ & AMASK) << 8) | ch;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-7. The CH2000.C program.
-
- Notice how quickly the program fills the screen. To appreciate the speed
- of this program, rewrite it using the BIOS-based Write_ch_atr() function
- from SCRFUN.LIB and note the difference!
-
- Making DMA More Compatible
-
- CH2000.C is fast and simple, but it doesn't work with all controllers. The
- program needs to be able to choose the correct memory value itself.
- Function 15 of the BIOS 0x10 video I/O interrupt enables it to do so.
- Because this routine returns the current video mode, the program can use
- that value to select the right video RAM address. Our SCRFUN.C file
- includes the Getvmode() function (Listing 14-8) based on that BIOS call.
- Note that the constant GETMODE is defined in the scrn.h file.
-
- Now rewrite CH2000.C as shown in the CH2001.C program (Listing 14-9). The
- constants TEXTMONO, TEXTBW80, and TEXTC80 are defined in scrn.h; they
- represent mode 7 (monochrome), mode 2 (CGA 80-by-25 B/W), and mode 3 (CGA
- 80-by-25 Color), respectively.
-
- ──────────────────────────────────────────────────────────────────────────
- #include <dos.h>
- #include "scrn.h"
- /* Getvmode() -- obtains the current video mode */
- unsigned char Getvmode()
- {
- union REGS reg;
-
- reg.h.ah = GETMODE;
- int86(VIDEO, ®, ®);
- return reg.h.al;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-8. The Getvmode() function.
-
- ──────────────────────────────────────────────────────────────────────────
- /* ch2001.c -- fills screen with 2000 characters */
- /* This program demonstrates direct memory access */
- /* of video memory. It uses the current video mode */
- /* value to select the proper video RAM address. */
- /* Press a key to fill; press Esc to quit. */
- /* Program list: ch2001.c, scrfun.lib */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include <conio.h>
- #include "scrn.h"
- typedef unsigned short (far * VIDMEM);
- #define MONMEM ((VIDMEM) (0xB000L << 16)) /* monochrome */
- #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
- #define ESC '\033'
- #define CHARS 2000
- #define AMASK 0xFF
- main()
- {
- unsigned ch, mode;
- unsigned attrib = 7;
- VIDMEM screen; /* pointer to video RAM */
- int offset;
-
- if ((mode = Getvmode()) == TEXTMONO)
- screen = MONMEM;
- else if (mode == TEXTC80 || mode == TEXTBW80)
- screen = CGAMEM;
- else
- exit(1);
- while ((ch = getch()) != ESC)
- {
- for (offset = 0; offset < CHARS; offset++)
- *(screen + offset) = ((attrib++ & AMASK) << 8) | ch;
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-9. The CH2001.C program.
-
- Storing and Displaying a Screen
-
- Let's use DMA to add some useful capabilities to the GRAFCHAR.C program.
- Although this program lets you draw on the screen using the graphics
- character set, when you quit the program, the drawing is lost. The program
- would be more useful if it let you store the created image in a file so
- you could use it later.
-
- DMA is ideally suited to copying information from the screen to a file and
- back again. We can incorporate the saving code into the program and make
- the recall a separate program.
-
- Saving the Screen
-
- To save the screen, rewrite the main program as SAVEGRAF.C (Listing
- 14-10). In addition, we need to add some new definitions to the
- grafchar.h file. Listing 14-11 shows the new version. Be sure Screen
- Swapping On is active (on the Debug menu) before you run the program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* savegraf.c -- uses DMA to save screen of graphics */
- /* characters and attributes */
- /* Program list - savegraf.c, initstuf.c, drawchar.c, */
- /* savescrn.c, scrfun.lib */
- /* User include files - scrn.h, keys.h, grafchar.h */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include "grafchar.h"
- unsigned char Grchr[NUMCHARS]; /* to store graphics set */
- void Init_stuff(void);
- void Draw_chars(void);
- void Save_screen(void); /* in savescrn.c */
-
- main()
- {
- int ch;
-
- Init_stuff(); /* initialize vital elements */
- Draw_chars(); /* map keys to graphics characters */
- Setcurs(BOTLINE + 1, 0, PAGE);
- printf("%-80s", "Save screen? <y/n> ");
- Setcurs(BOTLINE + 1, 20, PAGE);
- ch = getche();
- if (ch == 'y' || ch == 'Y')
- Save_screen();
- Setcurs(BOTLINE + 2, 0, PAGE);
- printf("%-80s\n", "BYE!");
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-10. The SAVEGRAF.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* grafchar.h -- definitions for savescrn.c and */
- /* recall.c Version 2 */
- #define NUMCHARS 48
- #define SPACE '\040'
- #define BOTLINE 19 /* line # for end of drawing space */
- #define PAGE 0
- #define GCSTART 0xB0 /* ascii for first graphics char */
- #define BEEP '\a'
- #define ESC '\033'
- #define TRUE 1
- #define FALSE 0
- #define CHARS (BOTLINE + 1) * 80 /* number of */
- /* character positions */
- typedef unsigned short (far * VIDMEM);
- #define MONMEM ((VIDMEM) (0xB000L << 16)) /* mono */
- #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-11. The revised grafchar.h header file.
-
- The SAVEGRAF.C program uses Setcurs() to position the text outside the
- drawing area. If the user chooses to save the screen, the Save_screen()
- function (Listing 14-12) does the work.
-
- Program Notes
-
- Because line numbering starts with 0, there are BOTLINE + 1 total lines.
- Therefore, the total number of character-attribute pairs that must be
- saved is that figure times 80; CHARS is defined as that value.
-
- ──────────────────────────────────────────────────────────────────────────
- /* savescrn.c -- saves screen, including attribute */
- /* values, in a file */
- /* Uses direct memory access. */
-
- #include <stdio.h> /* for file handling */
- #include "scrn.h"
- #include "grafchar.h"
- void Save_screen()
- {
- FILE *save;
- char filename[80];
- unsigned char mode;
- unsigned short char_attr; /* character, attribute */
- int offset;
- VIDMEM screen;
-
- if ((mode = Getvmode()) == TEXTMONO)
- screen = MONMEM;
- else if (mode == TEXTC80 || mode == TEXTBW80)
- screen = CGAMEM;
- else
- exit(1);
- Setcurs(BOTLINE + 1, 0, PAGE);
- printf("Please enter name for save file: ");
- scanf("%s", filename);
- if ((save = fopen(filename, "wb")) == NULL)
- {
- fprintf(stderr, "Can't open %s\n", filename);
- exit(1);
- }
- for (offset = 0; offset < CHARS; offset++)
- {
- char_attr = screen[offset];
- fwrite(&char_attr, 2, 1, save);
- }
- fclose(save);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-12. The Save_screen()< function.
-
- In general, screen + offset points to the character-attribute offset
- positions from the beginning of video RAM. The value of that pair is
- *(screen + offset), which can also be written as screen[offset].
-
- The standard I/O function fwrite() copies the contents of screen memory
- one character-attribute pair at a time. This function takes four
- arguments: a pointer to a memory location, the number of bytes per display
- unit (here 2), the number of units to be copied, and a file stream
- pointer. The program first copies the video pair to char_attr because, in
- the default medium memory model used by QuickC, fwrite() expects a near
- pointer. In the large memory model, you could replace the for loop with
- the following code:
-
- fwrite(screen, 2, CHARS, save); /* large model */
-
- Thus, in the large memory model, fwrite() uses a far pointer and can
- access video RAM directly. By specifying CHARS units to be copied by the
- function, you can dispense with the loop.
-
- Recovering the Screen
-
- To recover the stored screen, we need to reverse the storage process. That
- is, the program should open the file in the read mode. The
- attribute-character pairs found there should then be copied into the video
- memory, a natural task for DMA. From these basic techniques we develop the
- RECALL.C program (Listing 14-13). (To run the program, be sure that
- Screen Swapping On is active.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* recall.c -- displays previously stored screen, */
- /* including attributes. Uses DMA. */
- /* Program list: recall.c, scrfun.lib */
- /* User include files: scrn.h, grafchar.h */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include <stdio.h>
- #include <conio.h>
- #include "scrn.h"
- #include "grafchar.h"
-
- main(ac, ar)
- int ac;
- char *ar[];
- {
- unsigned char mode;
- unsigned short char_attr;
- FILE *save;
- unsigned int offset;
- char filename[81];
- VIDMEM screen;
-
- if (ac < 2)
- {
- fprintf(stderr, "Usage: %s filename\n", ar[0]);
- exit(1);
- }
-
- if ((save = fopen(ar[1], "rb")) == NULL)
- {
- fprintf(stderr, "Can't open %s\n", ar[1]);
- exit(1);
- }
-
- if ((mode = Getvmode()) == TEXTMONO)
- screen = MONMEM;
- else if (mode == TEXTC80 || mode == TEXTBW80)
- screen = CGAMEM;
- else
- exit(1);
-
- Clearscr();
- for (offset = 0; offset < CHARS; offset++)
- {
- fread(&char_attr, 2, 1, save);
- screen[offset] = char_attr;
- }
- fclose(save);
- Setcurs(23, 0, PAGE);
- getch(); /* anti-scrolling for QC environment */
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-13. The RECALL.C program.
-
- In the program, fread() recovers from the files what fwrite() placed into
- them. The Setcurs() call positions the MS-DOS prompt outside the drawing
- area when the program ends. The getch() call simply requires the user to
- press a key to terminate the program. Without this code, when you run the
- program in the QuickC environment, the QuickC prompt causes the screen to
- scroll up when the program ends. It still causes scrolling with this
- program, but not until you press a key.
-
-
- Paging
-
- Now let's turn to a text topic that lies beyond the scope of the
- Monochrome Display Adapter── paging. The CGA, EGA, and VGA have enough
- memory to store more than one screenful, or page, of text. The 16 KB video
- memory of the CGA, for example, can hold four text pages. The BIOS
- supports the use of pages by providing routines for setting the page and
- for determining the current page number. Many other BIOS routines require
- page information. The SCRFUN.C file we developed in Chapter 13 contains
- two page-related functions──Getpage() and Setpage(), which are combined in
- Listing 14-14. As usual, the manifest constants are defined in scrn.h.
-
- Paging is very fast, even compared to DMA, because the video RAM doesn't
- need to be rewritten. The video controller simply changes the section of
- video memory that it reads. A typical application for paging stores a help
- screen on one page, while an application uses another page. This permits a
- rapid transition between the two screens without calling data from program
- memory or a disk file.
-
- ──────────────────────────────────────────────────────────────────────────
- /* Getpage() -- obtains the current video page */
- unsigned char Getpage()
- {
- union REGS reg;
-
- reg.h.ah = GETMODE;
- int86(VIDEO, ®, ®);
- return reg.h.bh;
- }
- /* Setpage() -- sets page to given value */
- void Setpage(page)
- unsigned char page;
- {
- union REGS reg;
-
- reg.h.ah = SETPAGE;
- reg.h.al = page;
- int86(VIDEO, ®, ®);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-14. The Getpage() and Setpage() functions.
-
- Let's develop a basic program that can switch back and forth between page
- 0 and a help screen on page 1. We could use BIOS calls to write the
- contents of the two screens, but direct memory access is faster. However,
- to use direct memory access, we need to supply the address of page 1.
- Because each page holds 2000 character-attribute pairs, or 4000 bytes, you
- might expect that page 1 is offset 4000 bytes from the beginning of video
- memory. But computers relate more to powers of 2 than to powers of 10, so
- the actual offset is 4096 bytes, 0x1000 in hex. (You can use the extra
- bytes between pages to color in page borders.)
-
- We use the VIDMEM type pointer again to point to video memory. Because we
- define it to point to a 2-byte unit (the character-attribute pair), the
- offset in VIDMEM units is 2048 pairs, which is 0x800 in hex.
-
- The HELP.C program (Listing 14-15) is fairly simple. (Be sure Screen
- Swapping On is active before you run it.) The key points to note are its
- use of Setpage() to change pages and its use of direct memory access to
- write to the screen. The program uses two direct memory access modules.
- The writechr.c module (Listing 14-16 on the following page) writes a
- character-attribute pair a specified number of times beginning at a
- specified memory location. The writestr.c module (Listing 14-17 on p.
- 483) is similar, but it writes a string once instead of a single character
- repeatedly. By choosing the appropriate memory location, you can use these
- functions to write to either page, no matter which one is currently
- displayed. For convenience, we've collected definitions of the colors in a
- file called color.h (Listing 14-18 on p. 483).
-
- ──────────────────────────────────────────────────────────────────────────
- /* help.c -- uses paging and direct memory access */
- /* to display a help screen */
- /* Program list: help.c, writestr.c, writechr.c, */
- /* scrfun.lib */
- /* User include files: scrn.h, color.h */
- /* Note: activate Screen Swapping On in Debug menu */
-
- #include <stdio.h>
- #include <conio.h>
- #include "color.h"
- #include "scrn.h"
- typedef unsigned int (far * VIDMEM);
- #define CGAMEM ((VIDMEM) (0xB800L << 16))
- #define PAGESIZE 2000
- #define PAGEOFFSET 0x800L
- #define ESC '\033'
- #define ATTR1 (BG_BLUE | YELLOW)
- #define ATTR2 (BG_YELLOW | BLUE)
- #define ATTR3 (BG_RED | YELLOW | BLINK | INTENSE)
- #define CH1 (unsigned short) '\xB1'
- char *str1 = "Press ? key for help.";
- char *str2 = "Press Enter key to return.";
- char *str3 = "Press ESC key to quit.";
- char *str4 = "\xB1HELP!\xB1";
- void Write_chars(VIDMEM, unsigned short, unsigned
- short, unsigned short);
- void Write_str(VIDMEM, unsigned short, char *);
- main()
- {
- int ch;
- unsigned char page = 0;
- unsigned char mode;
-
- mode = Getvmode();
- if (mode != TEXTC80 && mode != TEXTBW80)
- {
- printf("Only modes 2 and 3 supported. Bye.\n");
- exit(1);
- }
- Setpage(page);
- Write_chars(CGAMEM, '\0', ATTR2, PAGESIZE);
- Write_str(CGAMEM + 2 * COLS, ATTR1, str1);
- Write_str(CGAMEM + 2 * COLS, ATTR1, str1);
- Write_str(CGAMEM + 22 * COLS, ATTR1, str3);
- Write_chars(CGAMEM + PAGEOFFSET, '\0', ATTR1, PAGESIZE);
- Write_str(CGAMEM + PAGEOFFSET + 20 * COLS, ATTR2, str2);
- Write_str(CGAMEM + PAGEOFFSET + 22 * COLS, ATTR1, str3);
- Write_chars(CGAMEM + PAGEOFFSET + 10 * COLS + 36,
- CH1, ATTR3, 7);
- Write_str(CGAMEM + PAGEOFFSET + 11 * COLS + 36,
- ATTR3, str4);
- Write_chars(CGAMEM + PAGEOFFSET + 12 * COLS + 36,
- CH1, ATTR3, 7);
-
- while ((ch = getch()) != ESC)
- {
- if (ch == '?' && page == 0)
- Setpage(page = 1);
- else if (ch == '\r' && page == 1)
- Setpage(page = 0);
- }
- Write_chars(CGAMEM, '\0', NORMAL, PAGESIZE);
- Write_chars(CGAMEM + PAGEOFFSET, '\0', NORMAL, PAGESIZE);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-15. The HELP.C program.
-
- ──────────────────────────────────────────────────────────────────────────
- /* writechr.c -- writes char and attribute repeatedly */
- /* using DMA */
- /* write character ch with attribute attr num times */
- /* starting at location pstart -- uses array notation */
-
- typedef unsigned int (far * VIDMEM);
-
- void Write_chars(pstart, ch, attr, num)
- VIDMEM pstart;
- unsigned short ch, attr, num;
- {
- register count;
- unsigned short pair;
- pair = (attr << 8) | (ch & 0x00FF) ;
- for (count = 0; count < num; count++)
- pstart[count] = pair;
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-16. The writechr.c module.
-
- ──────────────────────────────────────────────────────────────────────────
- /* writestr.c -- writes string and attribute using DMA */
- /* Write the string str with attribute attr at */
- /* location pstart -- uses pointer notation. */
-
- typedef unsigned int (far * VIDMEM);
-
- void Write_str(pstart, attr, str)
- VIDMEM pstart;
- unsigned short attr;
- char *str;
- {
- while (*str != '\0')
- *pstart++ = (attr << 8) | (*str++ & 0x00FF);
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-17. The writestr.c module.
-
- ──────────────────────────────────────────────────────────────────────────
- /* color.h -- defines the color attributes */
- /* foreground colors */
- #define BLACK 0x0
- #define BLUE 0x1
- #define GREEN 0x2
- #define RED 0x4
- #define CYAN 0x3
- #define MAGENTA 0x5
- #define YELLOW 0x6
- #define WHITE 0x7
- /* background colors */
- #define BG_BLACK 0x00
- #define BG_BLUE 0x10
- #define BG_GREEN 0x20
- #define BG_RED 0x40
- #define BG_CYAN 0x30
- #define BG_MAGENTA 0x50
- #define BG_YELLOW 0x60
- #define BG_WHITE 0x70
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-18. The color.h include file.
-
- Most of the HELP.C program involves using the new Write_chars() and
- Write_str() functions, so let's examine them. The two functions are quite
- similar in behavior, but to illustrate different programming techniques,
- one uses array notation and the other uses pointer notation.
-
- Write_chars() starts by combining the attribute and character into a
- 2-byte unit. It left-shifts the attribute into the high byte and places
- the character in the low byte. Next, the function performs a logical AND
- operation with the character and 0x00FF to limit the character to the
- range 0 through 0xFF, or 0 through 255. Next, a loop assigns the 2-byte
- pair to num consecutive locations in memory, beginning with the location
- pointed to by pstart. Recall that the notation pstart[count] is equivalent
- to *(pstart + count).
-
- In the program, Write_chars() clears the two pages, setting them to yellow
- and blue, respectively. To clear the screen, the program sets the
- character part of the byte to a null character; the attribute sets the
- color.
-
- The Write_str() function uses pointer notation to display a string. Like
- the preceding function, it combines the left-shifted attribute with a
- masked character value. In this case, str initially points to the first
- character in the string, so *str represents the value of that character.
- The while loop continues until it reaches the terminating null character
- of the string. During each cycle, the increment operator advances the
- video memory pointer and the string pointer after they are used.
-
- In the main program, note how we use addresses to specify locations on the
- screen. Consider, for example, the following statement:
-
- Write_str(CGAMEM + PAGEOFFSET + 11 * COLS + 36,
- ATTR3, str4);
-
- The address CGAMEM locates the beginning of the CGA (and EGA and VGA)
- memory. The PAGEOFFSET value is the offset to the beginning of the next
- page. Each line contains COLS characters, so the expression 11 * COLS is
- the offset to the beginning of line 11 (the twelfth line, because
- numbering starts with zero). Finally, the 36 gives the offset, or the
- indention measured in character widths, from the left side of the display.
-
- Note that the QuickC Graphics Library provides alternatives to many of our
- BIOS-based functions, including functions that clear the screen and set
- the page. However, using the Graphics Library produces final code
- noticeably larger than that of our examples. We use the Graphics Library
- only in graphics programs, in which its power and generality become
- evident.
-
-
- Ports
-
- Any discussion of hardware-dependent programming methods must mention
- "ports," which are information conduits between the CPU and the other
- devices and processors in a PC. In general, each processor or device has
- one or more registers of its own. Values placed in these registers can
- control the operation of the processor or, perhaps, test its state of
- readiness. In the PC, various registers are assigned "port addresses" that
- are completely separate from the memory address system and are handled
- differently. The CPU accesses registers through ports by using special
- port instructions. (See Figure 14-9.)
-
- An 8086 CPU can address as many as 64,000 8-bit ports, but only a small
- fraction of that number (fewer than 200) are actually used. In assembly
- language, you access the ports with the instructions IN and OUT: IN reads
- a register; OUT writes to it. C does not contain these instructions, so
- QuickC supplies the non-ANSI inp() and outp() functions to serve the same
- purpose.
-
- Reading Ports with inp()
-
- As mentioned in Chapter 13, the inp() and outp() functions are defined in
- conio.h. The following is the syntax for inp():
-
- #include <conio.h>
-
- int inp(port)
- unsigned port; /* port number */
-
- This function reads the register at port number port, which can be a value
- in the range 0 through 65,535. It then returns the byte it reads. With
- write-only ports, inp() returns the value 255, or all bits set to 1.
- However, a return value of 255 does not always signal a write-only
- register because 255 is also a valid register setting.
-
- ┌───────────────────┐
- │ Video controller │
- │ ┌─────────┐ │
- │ │register │ │
- └────┴────╥────┴────┘
- ┌───────────┐ ║
- │ ┌─┤ ║
- │ │ │──Port 0x3B8 ║
- │ │ ╞═════════════════╝
- │ CPU └─┤
- │ ┌─┤
- │ │ ╞═════════════════╗
- │ │ │──Port 0x67 ║
- │ └─┤ ║
- └───────────┘ ║
- ┌────┬────╨────┬────┐
- │ │register │ │
- │ └─────────┘ │
- │ Speaker controller│
- └───────────────────┘
-
- Figure 14-9. Ports and registers.
-
- The short PORTINFO.C program (Listing 14-19) lets you access and read
- various ports. Note that it uses the return value of scanf() to terminate
- the input loop. We prompt for hexadecimal port numbers because technical
- manuals usually list them in that form. Note that scanf() returns a value
- equal to the number of successful reads. Therefore, if it reads a hex
- value, it returns 1. If it finds input that is not hex, such as the letter
- q, scanf() returns 0, and the loop terminates.
-
- The following is a sample run:
-
- Enter number (in hex) of the port you wish to read: 3da
- Value returned for port 3da is 199 (decimal) c6 (hex)
- Next port? (q to quit): 61
- Value returned for port 61 is 32 (decimal) 31 (hex)
- Next port? (q to quit): 42
- Value returned for port 42 is 174 (decimal) 60 (hex)
- Next port? (q to quit): 3b8
- Value returned for port 3b8 is 255 (decimal) ff (hex)
- Next port? (q to quit): q
-
- You may get different values from those in this sample run──some of the
- registers change values as you use the computer.
-
- In the IBM PC and compatibles, the 0x3DA port reports status information
- about the MDA. Port 61 controls the speaker, and port 42 regulates the
- frequency of the 8253 timer chip. Finally, port 3B8 is the control port
- for the 6845 video controller on the MDA. (Because the last port is a
- write-only port, the reported value is not necessarily the true one.)
-
- ──────────────────────────────────────────────────────────────────────────
- /* portinfo.c -- reads port values */
- /* program list -- portinfo.c (inp() not in core lib) */
-
- #include <conio.h>
- #include <stdio.h>
- main()
- {
- unsigned int portnum;
- int regvalue;
-
- printf("Enter number (in hex) of the port ");
- printf("you wish to read: ");
- while (scanf("%x", &portnum) == 1)
- {
- regvalue = inp(portnum);
- printf("\nValue returned for port %x is %d (decimal)"
- " %x (hex)\n", portnum, regvalue, regvalue);
- printf("Next port? (q to quit): ");
- }
- }
- ──────────────────────────────────────────────────────────────────────────
-
- Listing 14-19. The PORTINFO.C program.
-
- As you can see, reading a register is a simple procedure. The difficult
- part is wading through the technical literature to see which