Cover

Programming and Problem Solving with Visual Basic .NET
Programming and Problem Solving with Visual Basic .NET
by Nell Dale, Michael McMillan, Chip Weems and Mark Headington ISBN:0763717630
Jones and Bartlett Publishers © 2003 (685 pages)

Learn to write accurate and proficient Visual Basic programs with this comprehensive, nuts-and-bolts resource.


Table of Content
 BackCover
 Programming and Problem Solving with Visual Basic.NET
 Preface
    Chapter Features
    Supplements
    Acknowledgements
 Chapter 1: Overview of Programming and Problem Solving
    1.1: Overview of Programming
    1.2: How Is a Program Converted into a Form That a Computer Can Use?
    1.3: How Is Interpreting a Program Different from Executing It?
    1.4: How Is Compilation Related to Interpretation and Execution?
    1.5: What Kinds of Instructions Can Be Written in a Programming Language?
    1.6: What Is a Computer?
    1.7: Ethics and Responsibilities in the Computing Profession
    1.8: Problem-Solving Techniques
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Case Study Follow-Up Exercises
 Chapter 2: Visual Basic Syntax and Semantics and the Program Entry Process
    2.1: Syntax and Semantics
    2.2: Data Types
    2.3: Classes and Objects
    2.4: Defining Terms: Declarations
    2.5: Taking Action: Executable Statements
    2.6: Program Entry, Correction, and Execution-;Using the Visual Studio Integrated Development Environment
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 3: Event-Driven Output
    3.1: Classes and Methods
    3.2: Formatting Output
    3.3: Handing Events
    3.4: Event Wiring
    3.5: Event-Handler Methods
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 4: Numeric Types and Expressions
    4.1: Overview of Visual Basic Data Types
    4.2: Numeric Data Types
    4.3: Declarations for Numeric Types
    4.4: Simple Arithmetic Expressions
    4.5: Compound Arithmetic Expressions
    4.6: Additional Mathematical Methods
    4.7: Additional String Operations
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 5: Event-Driven Input and Software Design Strategies
    5.1: Getting Data into Programs
    5.2: Entering Data Using Text Boxes in a Form
    5.3: Adding a Text Box to a Form
    5.4: Extracting a Value from a Text Box
    5.5: Creating a Button
    5.6: Handling a Button Event
    5.7: Interactive Input/Output
    5.8: Converting Strings to Numeric Values
    5.9: Noninteractive Input/Output
    5.10: Software Design Strategies
    5.11: What Are Objects and Classes?
    5.12: Object-Oriented Design
    5.13: Functional Decomposition
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 6: Conditions, Logical Expressions, and Selection Control Structures
    6.1: Flow of Control
    6.2: Conditions and Logical Expressions
    6.3: The If Statement
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 7: Classes and Methods
    7.1: Encapsulation
    7.2: Class Interface Design
    7.3: Internal Data Representation
    7.4: Class Syntax
    7.5: Declaring Methods
    7.6: Namespaces
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 8: Inheritance, Polymorphism, and Scope
    8.1: Inheritance
    8.2: Inheritance and the Object-Oriented Design Process
    8.3: How to Read a Class Hierarchy
    8.4: Derived Class Syntax
    8.5: Scope of Access
    8.6: Implementing a Derived Class
    8.7: Copy Constructors
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 9: File I/O and Looping
    9.1: File Input and Output
    9.2: Looping
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 10: Additional Control Structures and Exceptions
    10.1: Additional Control Structures
    10.2: Guidelines for Choosing a Looping Statement
    10.3: Exception-Handling Mechanism
    10.4: Additional Visual Basic Operators
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 11: One-Dimensional Arrays
    11.1: Atomic Data Types
    11.2: Composite Data Types
    11.3: One-Dimensional Arrays
    11.4: Examples of Declaring and Processing Arrays
    11.5: Arrays of Objects
    11.6: Arrays and Methods
    11.7: Special Kinds of Array Processing
    11.8: Dynamic Arrays
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 12: Array-Based Lists
    12.1: Lists
    12.2: List Class
    12.3: Sorting the List Items
    12.4: Sorted List
    12.5: The List Class Hierarchy and Abstract Classes
    12.6: Searching
    12.7: Generic Lists
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Chapter 13: Multidimensional Arrays and Numeric Computation
    13.1: Two-Dimensional Arrays
    13.2: Processing Two-Dimensional Arrays
    13.3: Multidimensional Arrays
    13.4: Floating-Point Numbers
    Testing and Debugging
    Summary of Classes
    Summary
    Quick Check
    Exam Preparation Exercises
    Programming Warm-Up Exercises
    Programming Problems
    Case Study Follow-Up Exercises
 Appendix A: Database Programming in Visual Basic. NET
    Relational Database Organization
    Exploring the Structure of a Relational Database
    Why You Need ADO.NET
    The ADO.NET Object Model
    Programming the Adapter Level
 Appendix B: Visual Basic Reserved Words
 Appendix C: Operator Precedence
 Appendix D: Primitive Data Types
 Appendix E: Formatting Numbers
 Appendix F: Program Style, Formatting, and Documentation
    Comments
    Identifiers
    Formatting Lines and Expressions
    Indentation
 Glossary
    B
    C
    D
    E
    F-H
    I
    K-M
    N-O
    P
    R-S
    T-U
    V-W


Back Cover

Programming and Problem Solving with Visual Basic .NET introduces Visual Basic from an object-oriented viewpoint, with a rigor and accessibility perfect for students learning their first programming language. Rich in the pedagogical style that is the trademark of the Dale series of programming texts, this Visual Basic text provides straightforward explanations, meaningful examples, and engaging exercises. Students develop good programming habits right from the start and acquire a thorough understanding of classes, methods, object-oriented concepts, and algorithms. The Visual Basic Graphical User Interface (GUI) is extensively discussed, which complements the text's emphasis on fundamental programming concepts. The active learning methodology and rich topic coverage of this text thoroughly prepare students for future study.

Key Features

  • Visual Basic .NET is approached as a powerful programming language
  • Object-oriented concepts are introduced early, which allows for the introduction of object-oriented design methodology
  • A large variety of exercises end each chapter
  • Visual Basic Graphical Interface (GUI) is used throughout the book
  • The Dale Pedagogy, a renowned model of teaching based on concept reinforcement, problem solving, and testing and debugging, is applied throughout the text


Programming and Problem Solving with Visual Basic.NET

Nell Dale

University of Texas, Austin

Michael McMillan

Pulaski Technical College

Chip Weems

University of Massachusetts, Amherst

Mark Headington

University of Wisconsin - La Crosse

JONES AND BARTLETT PUBLISHERS
Sudbury, Massachusetts BOSTON TORONTO LONDON SINGAPORE

World Headquarters
Jones and Bartlett Publishers
40 Tall Pine Drive
Sudbury, MA 01776
978-443-5000
<info@jbpub.com>
www.jbpub.com

Jones and Bartlett Publishers Canada
2406 Nikanna Road
Mississauga, ON L5C 2W6
CANADA

Jones and Bartlett Publishers International
Barb House, Barb Mews
London W6 7PA
UK

Copyright © 2003 by Jones and Bartlett Publishers, Inc.

Library of Congress Cataloging-in-Publication Data
Programming and Problem Solving with Visual Basic .Net / Nell Dale…[et al.].
p. cm.
Includes index.

ISBN 0-7637-1763-0

1. Microsoft Visual BASIC. 2. BASIC (Computer program language) I. Dale, Nell B.

QA76.73.B3 P75 2002
005.26'8-dc21
2002016070

All rights reserved. No part of the material protected by this copyright notice may be reproduced or utilized in any form, electronic or mechanical, including photocopying, recording, or any information storage or retrieval system, without written permission from the copyright owner.

Chief Executive Officer: Clayton Jones
Chief Operating Officer: Don W. Jones, Jr.
Executive V.P. & Publisher: Robert W. Holland, Jr.
V.P., Design and Production: Anne Spencer
V.P., Manufacturing and Inventory Control: Therese Bräuer
Editor-in-Chief: J. Michael Stranz
Director, Sales and Marketing: William Kane
Production Manager: Amy Rose
Senior Marketing Manager: Nathan Schultz
Associate Production Editor: Tara McCormick
Editorial Assistant: Theresa DiDonato
Production Assistant: Karen C. Ferreira
Cover Design: Kristin E. Ohlin
Composition: Northeast Compositors
Text Design: Anne Spencer
Printing and Binding: Courier Westford
Cover Printing: John Pow Company, Inc.

This book was typeset in Quark 4.1 on a Macintosh G4. The font families used were Rotis Sans Serif, Rotis Serif, and Prestige Elite. The first printing was printed on 45 lb. Utopia Filmcoat.

Printed in the United States of America
06 05 04 03 02 10 9 8 7 6 5 4 3 2 1

To Al, my husband and best friend, and to our children and our children's children.
N.D.

To my wife Terri, and my children Meredith, Allison, and Mason, for their support and encouragement of my work.
M.M.M.

To Lisa, Charlie, and Abby with love.
C.W.

To Professor John Dyer-Bennet, with great respect.
M.H.

Next Section


Preface

Visual Basic .NET represents a radical change in the evolution of the Basic language. Some have called this newest version the "Javatization" of Visual Basic. There are certainly many similarities between Java and Visual Basic .NET. Like Java, Visual Basic .NET is now compiled to a bytecode representation, with the resulting file run by the Common Language Runtime, which is very similar to the Java Virtual Machine. There is now a set of class libraries for Visual Basic .NET that provide much the same functionality as the base class libraries in Java. Microsoft® also added full object-oriented programming functionality to Visual Basic, including full inheritance. Microsoft even included the ability to write Console-based applications in Visual Basic .NET, using either Visual Studio .NET or, and this came as quite a shock to me when I discovered it, using just a text editor to create the source file for compilation.

I have been teaching Visual Basic for the past four years and have been very disappointed in the introductory Visual Basic textbooks on the market. Too many of them ignore teaching solid programming fundamentals in order to wow the student with the whiz-bang features of the user interface. After describing all the user interface features, these books move right into showing the student how to design full-blown applications, again ignoring the fact that the reader has had at the most 10 to 11 weeks of experience with the language.

In the Fall of 2000, while lamenting the sad state of Visual Basic textbooks, I received the first beta version of Visual Basic .NET. I immediately recognized that this version of Visual Basic is a major shift in philosophy from Visual Basic 6 and its predecessors. At the same time, I decided it was time to write my own Visual Basic textbook using Visual Basic .NET as the target language. One of the first publishers I looked to was Jones and Bartlett. I have been using the Dale books on C++ and Java for several years and like how they focus on problem solving and program writing, testing, and debugging. When I contacted Michael Stranz, the Computer Science acquisitions editor, he informed me that Jones and Bartlett was looking for someone to take the Dale series into Visual Basic. Voila. A relationship and a book were born.

This book is my attempt to right the wrongs that I see in the Visual Basic textbook market. The focus of this book, as with all the Dale books, is on the fundamentals of programming. Each chapter focuses on one of the major programming constructs of the language. User interface details, while not ignored, are kept to the basics of form design in Visual Basic .NET. I wrote this book feeling that once the reader has a good grasp of using Visual Basic .NET, the reader can venture out on his or her own to learn more about user interface design and development.

Chapter Coverage

Chapter 1 begins with basic definitions, computer concepts, problem-solving techniques, ethical issues, and a case study. We introduce objects from the very beginning, with their definition in this first chapter and the consistent use of object-oriented terminology.

In Chapter 2, we examine the String class (and data type) and aspects of its interface. Through the next several chapters, we introduce new library classes and point out their design features. We also tread carefully through the process of declaring a reference variable, instantiating an object, assigning it to a variable, and using the object. These are difficult but essential concepts for beginners to grasp. Our goal is for students to gradually develop a complete and deep understanding of what an object is, how it works, and what makes its interface well-designed. Then, by Chapters 7 and 8, students are ready to build significant and realistic classes.

Chapter 2 further covers sequential control flow, along with simple output to a window using Messagebox.Show. The reader also learns how to use the Visual Studio .NET Integrated Development Environment (IDE) for entering and running Visual Basic .NET programs. In Chapter 3 we examine in more detail how to use Windows Forms and labels for output. Simple event handling is then introduced through the Dispose method for closing a form. The Dispose method is quite simple compared to other form methods and provides a good introduction to class methods.

In Chapter 4, we turn to the numeric types and expression syntax. We cover type conversions, precedence rules, the use of numeric types, and additional methods that can be applied to String objects. We also reinforce the distinction between the reference and primitive types in this chapter. Students gain further experience with form output and event handling in this chapter.

With Chapter 5, we take the next step in event handling by introducing the essential elements of an input dialog. Students learn how to use a textbox for data input and a button to signal when the textbox is ready. Visual Basic .NET makes this process easy since the event wiring is built-in, making it easier to write programs for events than it is in languages such as Java. The chapter closes with a discussion of object-oriented design that introduces the CRC card (Classes, Responsibilities, and Collaborators) as a mechanism for organizing an object-oriented design.

The primary goal of Chapter 6 is to introduce branching and the Boolean type. But the motivation for its use, in the end, is for students to be able to handle events from multiple sources in a single event handler. Students have now learned all of the basic user interface elements necessary to write a wide range of interactive programs.

Chapters 7 and 8 are the heart of the text. In these chapters, we bring together all of the informal discussion of classes and objects to formally introduce the mechanisms for defining new classes, methods, and derived classes. Students learn how to read the documentation for a class hierarchy and how to determine the inherited members of a class. They also see how the classes are related through Visual Basic .NET's scope rules. Namespace syntax is introduced so that multifile programs can be written and user-defined classes can be created and imported to other code. The key object-oriented concepts of encapsulation, inheritance, and polymorphism are treated thoroughly, and their use is demonstrated in the case studies.

Users who are familiar with procedural languages may wonder, at this point, "What happened to looping?" The answer is that event driven I/O has permitted us to postpone it to Chapter 9, where it naturally fits with file I/O and prepares students for the upcoming chapters on arrays. As always, we cover the basic concepts of looping using only the While loop. Object-oriented languages haven't changed the basic fact that beginners have to make conceptual leaps to understanding looping. Because there is more syntax to learn in an object-oriented language, it is tempting to relegate all those "old-fashioned" control structures to a single chapter and just breeze by them as if they are suddenly made easier in the presence of the additional complexity.

We find that students are still well-served by a careful study of the basic programming elements. We focus on how loops are used in algorithms while introducing only the minimum syntax necessary to illustrate the concepts. In that way, students don't develop the misperception that the different forms of control structures are bound to the different syntactic structures in a language. This approach also avoids the all-too-common situation in which students focus on their confusion over choosing among different looping statements when they are really still unsure of the underlying algorithmic mechanism that they wish to express. For those instructors who feel strongly that they prefer to show students all of the control structure syntax at one time, Chapter 10 covers the additional branching and looping structures in a manner that enables appropriate sections to be covered as extensions to Chapters 6 and 9.

Chapter 10 is the "ice cream and cake" section of the book, covering additional control structures that make the coding of certain algorithms easier. In addition to the Select Case, Do, and For statements, Chapter 10 introduces the concept of exception handling. We show students how to use the Try-Catch-Finally statement to catch exceptions. Because we've already covered inheritance, it is a simple matter to define new exception classes that can be thrown between sections of user code. Students then are able to write code that is robust in the face of errors that cannot be handled directly with testing and branching.

Chapters 11, 12, and 13 are devoted to composite data structures. In Chapter 12, the basic concept of a composite structure is introduced and illustrated with the Visual Basic .NET array. In Chapter 13, we show how an array can be used to implement a general-purpose list class. Our prior class designs have been in the context of specific applications, and this is the first taste of an object-oriented design that does not have a predefined client. Then, in Chapter 14, we extend the discussion of arrays to multiple dimensions, and through a case study we show how they can be used to represent mathematical matrices. Given this numerically motivated case study, it is also natural to review the limitations of floating-point numbers as they are represented in the computer.

One last programming topic, interfacing with a database, is introduced in Appendix A. This appendix is included to give the reader a taste of how Visual Basic .NET is used in the "real world."


Chapter Features

Goals Each chapter begins with a list of learning objectives for the student. These goals are reinforced and tested in the end-of-chapter exercises.

Problem-Solving Case Studies A full development of a problem from its statement to a working Visual Basic .NET application is developed. Beginning with Chapter 5, the CRC card design strategy is employed to develop object-oriented designs that are then translated into code. Test plans and sample test data are also presented for many of these case studies.

Quick Checks These questions test the student's recall of major points associated with the chapter goals. Upon reading each question, the student immediately should know the answer, which he or she can verify by glancing at the answer at the end of each question so that the student can review the material in the event of an incorrect response.

Exam Preparation Exercises To help the student prepare for tests, these questions usually have objective answers and are designed to be answerable with a few minutes of work. Answers to selected questions are given in the back of the book, and the remaining questions are answered in the Instructor's Guide.

Programming Warm-Up Exercises These questions provide the student experience in writing Visual Basic .NET code fragments. The student can practice the syntactic constructs in each chapter without the burden of writing a complete program.

Programming Problems These exercises require the student to design solutions and write complete Visual Basic .NET applications.

Case Study Follow-Up Exercises Much of modern programming practice involves reading and modifying existing code. These exercises provide the student with an opportunity to strengthen this critical skill by answering questions about the case study code, or by making changes to it.


Supplements

Instructor's ToolKit

Also available to adopters on request from the publisher is a powerful teaching tool called the Instructor's ToolKit. This is a group of instructor teaching tools, including an electronic version of the Instructor's Guide, a computerized test bank, PowerPoint lecture presentations, and the complete programs from the text.

Programs

The programs contain the source code for all of the complete Visual Basic .NET applications and stand-alone classes that are found within the textbook. They are available as a free download for instructors and students from the publisher's web site:

http://computerscience.jbpub.com/vbnet

The programs from all of the case studies, plus several programs that appear in the chapter bodies, are included. Fragments or snippets of code are not included, nor are the solutions to the chapter-ending Programming Problems. These application files can be viewed or edited using any standard text editor. Most of the programs will run from the command-line compiler, but Visual Studio. NET is recommended for the beginning Visual Basic .NET programmer.

Student Lecture Companion: A Note-Taking Guide

Designed from the PowerPoint presentation developed for this text, the Student Lecture Companion is an invaluable tool for learning. The notebook is designed to encourage students to focus their energies on listening to the lecture as they fill in additional details. The skeletal outline concept helps students organize their notes and readily recognize the important concepts in each chapter.

A Laboratory Course in Visual Basic .NET

Written by Michael McMillan, this lab manual follows the organization of the text. The lab manual is designed to allow the instructor maximum flexibility and may be used in both open and closed laboratory settings. Each chapter contains three types of activities: Prelab, Inlab, and Postlab. Each lesson is broken into exercises that thoroughly demonstrate the concepts covered in the chapter. The applications, application shells (partial applications), and data files accompany the lab manual. They can be downloaded from the Jones and Bartlett web site.


Acknowledgements

The latest author to the Dale team is deeply indebted to the first three authors for providing him with an excellent text to work with. I'd say something about standing on the shoulders of giants, but that phrase has certainly been overused lately. Nevertheless, Nell, Chip, and Mark are all excellent authors and instructors and have developed an ideal environment for learning the concepts of computer programming.

I can't thank enough the students at Pulaski Technical College who have taken my Visual Basic course over the past year. They were subjected to incomplete text, rewritten text, and sometimes no text at all, but they stayed with me, taking me at my word that they were much better off learning Visual Basic .NET with an incomplete, but superior, textbook. One student in particular, Karl Liss, read the text very carefully and pointed out several inconsistencies in the early drafts.

My fellow faculty members at Pulaski Tech, while not directly involved in the work, are always supportive and fun to work with. I want to especially thank my fellow "hallmates," Raymond Williams, Bernica Tackett, and Beata Lovelace, for all their support and good humor. I also need to say a special word of thanks to my division chairperson, David Durr, who not only listened to me complain quite loudly about the past state of Visual Basic textbooks, but also listened to me while I worked out some of the broader issues of teaching students Visual Basic .NET in an object-oriented manner.

The staff at Jones and Bartlett have been wonderful to work with. Michael Stranz has spent many hours with me, on the phone and asynchronously via email, discussing many issues concerning the making of this book. My production editor, Amy Rose, and her assistant Tara McCormick were patient with me as we finished up the book during the same time my school was right in the middle of finals, which is almost as trying a time for instructors as it is for students.

My thanks to the reviewers who helped to make this a stronger text: James Forkner, Pennsylvania State University; Thomas Gambill, University of Illinois; Donald Kussee, Utah Valley State College; James Prater, University of Alabama; Sandy Schleiffers, Colorado State University.

Last, but certainly not least, I need to thank my family—Terri, my wife; Meredith and Allison, my daughters; and Mason, my son, for being supportive of my work even though it meant long hours away from them while I typed away in my office. Hopefully, my children will learn from me how wonderful it is to be able to teach others how to do something you love to do so much.
M. M. M.


Chapter 1: Overview of Programming and Problem Solving

Overview

Goals

  • To be able to define computer program, algorithm, and high-level programming language.

  • To be able to list the basic stages involved in writing a computer program.

  • To be able to distinguish between machine code and Bytecode.

  • To be able to describe what compilers and interpreters are and what they do.

  • To be able to describe the compilation, execution, and interpretation processes.

  • To be able to list the major components of a computer and describe how they work together.

  • To be able to distinguish between hardware and software.

  • To be able to discuss some of the basic ethical issues confronting computing professionals.

  • To be able to apply an appropriate problem-solving method for developing an algorithmic solution to a problem.


1.1 Overview of Programming

What a brief definition for something that has, in just a few decades, changed the way of life in industrialized societies! Computers touch all areas of our lives: paying bills, driving cars, using the telephone, shopping. In fact, it might be easier to list those areas of our lives in which we do not use computers. You are probably most familiar with computers through the use of games, word processors, Web browsers, and other programs. Be forewarned: This book is not just about using computers. This is a text to teach you how to program them.

com·put·er
\k∍m-'pyüt-∍r\ n. often attrib (1646): one that computes; specif: a programmable electronic device that can store, retrieve, and process data[1]

What Is Programming?

Much of human behavior and thought is characterized by logical sequences of actions applied to objects. Since infancy, you have been learning how to act, how to do things; and you have learned to expect certain behavior from other people.

A lot of what you do every day you do automatically. Fortunately, it is not necessary for you to consciously think of every step involved in a process as simple as turning a page by hand:

  1. Lift hand.

  2. Move hand to right side of book.

  3. Grasp top-right corner of page.

  4. Move hand from right to left until page is positioned so that you can read what is on the other side.

  5. Let go of page.

Think how many neurons must fire and how many muscles must respond, all in a certain order or sequence, to move your arm and hand. Yet you do it unconsciously.

Much of what you do unconsciously you once had to learn. Watch how a baby concentrates on putting one foot before the other while learning to walk. Then watch a group of three-year-olds playing tag.

On a broader scale, mathematics never could have been developed without logical sequences of steps for manipulating symbols to solve problems and prove theorems. Mass production would never have worked without operations taking place on component parts in a certain order. Our whole civilization is based on the order of things and actions.

We create order, both consciously and unconsciously, through a process called programming. This book is concerned with the programming of one tool in particular, the electronic computer.

Programming
Planning or scheduling the performance of a task or an event
Electronic computer
A programmable device that can store, retrieve, and process data

Notice that the key word in the definition of computer is data. Computers manipulate data. When you write a program (a plan) for a computer, you specify the properties of the data and the operations that can be applied to it. Those operations are then combined as necessary to solve a problem. Data is information in a form the computer can use—for example, numbers and letters. Information is any knowledge that can be communicated, including abstract ideas and concepts such as "the Earth is round."

Data
Information in a form a computer can use
Information
Any knowledge that can be communicated

Data comes in many different forms: letters, words, integer numbers, real numbers, dates, times, coordinates on a map, and so on. Virtually any kind of information can be represented as data, or as a combination of data and operations on it. Each kind of data in the computer is said to have a specific data type. For example, if we say that two data items are of type Integer, we know now they are represented in memory and that we can apply arithmetic operations to them.

Data type
The specification of how information is represented in the computer as data and the set of operations that can be applied to it

Just as a concert program lists the pieces to be performed and the order in which the players perform them, a computer program lists the types of data that are to be used and the sequence of steps the computer performs on them. From now on, when we use the words programming and program, we mean computer programming and computer program.

Computer programming
The process of specifying the data types and the operations for a computer to apply to data in order to solve a problem
Computer program
Data type specifications and instructions for carrying out operations that are used by a computer to solve a problem

The computer allows us to do tasks more efficiently, quickly, and accurately than we could by hand—if we could do them by hand at all. In order for this powerful machine to be a useful tool, it must be programmed. That is, we must specify what we want done and how. We do this through programming.

How Do We Write a Program?

A computer is not intelligent. It cannot analyze a problem and come up with a solution. A human (the programmer) must analyze the problem, develop the instructions for solving the problem, and then have the computer carry out the instructions. What's the advantage of using a computer if it cannot solve problems? Once we have written a solution for the computer, the computer can repeat the solution very quickly and consistently, again and again. The computer frees people from repetitive and boring tasks.

To write a program for a computer to follow, we must go through a two-phase process: problem solving and implementation (see Figure 1.1).

Click To expand
Figure 1.1: Programming process

Problem-Solving Phase

  1. Analysis and Specification. Understand (define) the problem and what the solution must do.

  2. General Solution (Algorithm). Specify the required data types and the logical sequences of steps that solve the problem.

  3. Verify. Follow the steps exactly to see if the solution really does solve the problem.

Implementation Phase

  1. Concrete Solution (Program). Translate the algorithm (the general solution) into a programming language.

  2. Test. Have the computer follow the instructions. Then manually check the results. If you find errors, analyze the program and the algorithm to determine the source of the errors, and then make corrections.

Once a program has been written, it enters a third phase: maintenance.

Maintenance Phase

  1. Use. Use the program.

  2. Maintain. Modify the program to meet changing requirements or to correct any errors that show up while using it.

The programmer begins the programming process by analyzing the problem, breaking it into manageable pieces, and developing a general solution for each piece called an algorithm. The solutions to the pieces are collected together to form a program that solves the original problem. Understanding and analyzing a problem take up much more time than Figure 1.1 implies. They are the heart of the programming process.

Algorithm
Instructions for solving a problem or subproblem in a finite amount of time using a finite amount of data

If our definitions of a computer program and an algorithm look similar, it is because a program is simply an algorithm that has been written for a computer.

An algorithm is a written or verbal description of a logical sequence of actions applied to objects. We use algorithms every day. Recipes, instructions, and directions are all examples of algorithms that are not programs.

When you start your car, you follow a step-by-step procedure. The algorithm might look something like this:

Click To expand

Without the phrase "but not more than five times" in Step 5, you could be trying to start the car forever. Why? Because if something is wrong with the car, repeating Steps 3 through 5 over and over will not start it. This kind of never-ending situation is called an infinite loop. If we leave the phrase "but not more than five times" out of Step 5, the procedure doesn't fit our definition of an algorithm. An algorithm must terminate in a finite amount of time for all possible conditions.

Suppose a programmer needs an algorithm to determine an employee's weekly wages. The algorithm reflects what would be done by hand:

Click To expand

The steps the computer follows are often the same steps you would use to do the calculations by hand.

After developing a general solution, the programmer tests the algorithm, "walking through" each step manually with paper and pencil. If the algorithm doesn't work, the programmer repeats the problem-solving process, analyzing the problem again and coming up with another algorithm. Often the second algorithm is just a variation of the first. When the programmer is satisfied with the algorithm, he or she translates it into a programming language. We use the Visual Basic programming language in this book.

A programming language is a simplified form of English (with math symbols) that adheres to a strict set of grammatical rules. English is far too complicated and ambiguous for today's computers to follow. Programming languages, because they limit vocabulary and grammar, are much simpler.

Programming language
A set of rules, symbols, and special words used to construct a computer program

Although a programming language is simple in form, it is not always easy to use. Try giving someone directions to the nearest airport using a limited vocabulary of no more than 25 words, and you begin to see the problem. Programming forces you to write very simple, exact instructions.

Translating an algorithm into a programming language is called coding the algorithm. The products of the translation—the code for all the algorithms in the problem—are tested by collecting them into a program and running (executing) the program on the computer. If the program fails to produce the desired results, the programmer must debug it—that is, determine what is wrong and then modify the program, or even one or more of the algorithms, to fix it. The combination of coding and testing the algorithms is called implementation.

Code is the product of translating an algorithm into a programming language. The term code can refer to a complete program or to any portion of a program.

Code
Data type specifications and instructions for a computer that are written in a programming language

There is no single way to implement an algorithm. For example, an algorithm can be translated into more than one programming language. Each translation produces a different implementation (see Figure 1.2a). Even when two people translate an algorithm into the same programming language, they are likely to come up with different implementations (see Figure 1.2b). Why? Because every programming language allows the programmer some flexibility in how an algorithm is translated. Given this flexibility, people adopt their own styles in writing programs, just as they do in writing short stories or essays. Once you have some programming experience, you develop a style of your own. Throughout this book, we offer tips on good programming style.

Click To expand
Figure 1.2: Differences in implementation

Some people try to speed up the programming process by going directly from the problem definition to coding the program (see Figure 1.3). A shortcut here is very tempting and at first seems to save a lot of time. However, for many reasons that become obvious to you as you read this book, this kind of shortcut actually takes more time and effort. Developing a general solution before you write a program helps you manage the problem, keep your thoughts straight, and avoid mistakes. If you don't take the time at the beginning to think out and polish your algorithm, you spend a lot of extra time debugging and revising your program. So think first and code later! The sooner you start coding, the longer it takes to write a program that works.

Click To expand
Figure 1.3: Programming shortcut?

Once a program has been put into use, it is often necessary to modify it. Modification may involve fixing an error that is discovered during the use of the program or changing the program in response to changes in the user's requirements. Each time the program is modified, it is necessary to repeat the problem-solving and implementation phases for those aspects of the program that change. This phase of the programming process is known as maintenance and actually accounts for the majority of the effort expended on most programs. For example, a program that is implemented in a few months may need to be maintained over a period of many years. Thus it is a cost-effective investment of time to carefully develop the initial problem solution and program implementation. Together, the problem-solving, implementation, and maintenance phases constitute the program's life cycle.

In addition to solving the problem, implementing the algorithm, and maintaining the program, writing documentation is an important part of the programming process. Documentation includes written explanations of the problem being solved and the organization of the solution, comments embedded within the program itself, and user manuals that describe how to use the program. Many different people are likely to work on a program over a long period of time. Each of those people must be able to read and understand the code.

Documentation
The written text and comments that make a program easier for others to understand, use, and modify
Start Sidebar
Theoretical Foundations
Binary Representation of Data

In a computer, data is represented electronically by pulses of electricity. Electric circuits, in their simplest form, are either on or off. Usually, a circuit that is on represents the number 1; a circuit that is off represents the number 0. Any kind of data can be represented by combinations of enough 1s and 0s. We simply have to choose which combination represents each piece of data we are using. For example, we could arbitrarily choose the pattern 1101000110 to represent the word Basic.

Data is represented by 1s and 0s in binary form. The binary (base-2) number system uses only 1s and 0s to represent numbers. (The decimal (base-10) number system uses the digits 0 through 9.) The word bit (short for binary digit) often is used to refer to a single 1 or 0. So the pattern 1101000110 has 10 bits. A binary number with 10 bits can represent 210 (1,024) different patterns. A byte is a group of eight bits; it can represent 28 (256) patterns. Inside the computer, each character (such as the letter A, the letter g, or a question mark) is usually represented by a byte.[2] Groups of 16, 32, and 64 bits are generally referred to as words.

The process of assigning bit patterns to pieces of data is called coding—the same name we give to the process of translating an algorithm into a programming language. The names are the same because the only language that the first computers recognized was binary in form. Thus, in the early days of computers, programming meant translating both data and algorithms into patterns of 1s and 0s.

Binary coding schemes are still used inside the computer to represent both the instructions that it follows and the data that it uses. For example, 16 bits can represent the decimal integers from 0 to 216 — 1 (65,535). More complicated coding schemes are necessary to represent negative numbers, real numbers, and numbers in scientific notation. Characters can be represented by bit combinations, in one coding scheme, 01001101 represents M and 01101101 represents m.

The patterns of bits that represent data vary from one family of computers to another. Even on the same computer, different programming languages can use different binary representations for the same data. A single programming language may even use the same pattern of bits to represent different things in different contexts. (People do this too. The four letters that form the word tack have different meanings depending on whether you are talking about upholstery, sailing, sewing, paint, or horseback riding.) The point is that patterns of bits by themselves are meaningless. It is the way in which the patterns are used that gives them their meaning.

Fortunately, we no longer have to work with binary coding schemes. Today the process of coding is usually just a matter of writing down the data in letters, numbers, and symbols. The computer automatically converts these letters, numbers, and symbols into binary form. Still, as you work with computers, you continually run into numbers that are related to powers of 2—numbers like 256, 32,768, 65,536—reminders that the binary number system is lurking somewhere nearby.

End Sidebar

[1]By permission. From Merriam-Webster's Collegiate Dictionary, Tenth Edition © 1994 by Merriam-Webster Inc.

[2]Most programming languages use the American Standard Code for Information Interchange (ASCII) to represent the English alphabet and other symbols. ASCII characters are stored in a single byte. Visual Basic recognizes both ASCII and a newer standard called Unicode, which includes the alphabets of many other human languages. A single Unicode character takes up two bytes in the computer's memory.


1.2 How Is a Program Converted into a Form That a Computer Can Use?

In the computer, all data, whatever its form, is stored and used in binary codes, strings of 1s and 0s. Instructions and data are stored together in the computer's memory using these binary codes. If you were to look at the binary codes representing instructions and data in memory, you could not tell the difference between them; they are distinguished only by the manner in which the computer uses them. It is thus possible for the computer to process its own instructions as a form of data.

When computers were first developed, the only programming language available was the primitive instruction set built into each machine, the machine language, or machine code.

Machine language
The language, made up of binary-coded instructions, that is used directly by the computer

Even though most computers perform the same kinds of operations, their designers choose different sets of binary codes for each instruction. So the machine code for one family of computers is not the same as for another.

When programmers used machine language for programming, they had to enter the binary codes for the various instructions, a tedious process that was prone to error. Moreover, their programs were difficult to read and modify. In time, assembly languages were developed to make the programmer's job easier.

Assembly language
A low-level programming language in which a mnemonic is used to represent each of the machine language instructions for a particular computer

Instructions in an assembly language are in an easy-to-remember form called a mnemonic (pronounced "ni- ` män - ik"). Typical instructions for addition and subtraction might look like this:

Assembly Language | Machine Language
ADD | 100101
SUB | 010011

Although assembly language is easier for humans to work with, the computer cannot directly execute the instructions. Because a computer can process its own instructions as a form of data, it is possible to write a program to translate assembly language into machine code. Such a program is called an assembler.

Assembler
A program that translates an assembly language program into machine code

Assembly language is a step in the right direction, but it still forces programmers to think in terms of individual machine instructions. Eventually, computer scientists developed high-level programming languages. These languages are easier to use than assembly languages or machine code because they are closer to English and other natural languages (see Figure 1.4).

Click To expand
Figure 1.4: Levels of abstraction

A program called a compiler translates algorithms written in certain high-level languages (Visual Basic, C++, Java, Pascal, and Ada, for example) into machine language. If you write a program in a high-level language, you can run it on any computer that has the appropriate compiler. This is possible because most high-level languages are standardized, which means that an official description of the language exists.

Compiler
A program that translates a program written in a high-level language into machine code
Source code
Data type specifications and instructions written in a high-level programming language
Object code
A machine language version of source code

The text of an algorithm written in a high-level language is called source code. To the compiler, source code is just input data. It translates the source code into a machine language form called object code (see Figure 1.5).

Click To expand
Figure 1.5: High-level programming languages allow programs to be compiled on different systems.

A benefit of standardized high-level languages is that they allow you to write portable (or machine independent) code. As Figure 1.5 emphasizes, a single C++ program can be run on different machines, whereas a program written in assembly language or machine language is not portable from one computer to another. Because each computer family has its own machine language, a machine language program written for computer A may not run on computer B.

Visual Basic takes a somewhat different approach than we have described. Visual Basic programs are translated into a standard machine language called Bytecode.

Bytecode
A standard machine language into which Visual Basic source code is compiled

However, there are no computers that actually use Bytecode as their machine language. In order for a computer to run Bytecode programs, it must have another program called the Common Language Runtime (CLR) that serves as a language interpreter for the program. Just as an interpreter of human languages listens to words spoken in one language and speaks a translation of them in a language that another person understands, the CLR reads the Bytecode machine language instructions and translates them into machine language operations that the particular computer executes. Interpretation is done as the program is running, one instruction at a time. It is not the same as compilation, which is a separate step that translates all of the instructions in a program prior to execution. Figure 1.6 shows how the Visual Basic translation process works.

Click To expand
Figure 1.6: Java compiler produces Bytecode that can be run on any machine with the JVM.


1.3 How Is Interpreting a Program Different from Executing It?

There is a significant distinction between direct execution and interpretation of a program. A computer can directly execute a program that is compiled into machine language. The CLR is one such machine language program that is directly executed. The computer cannot directly execute Bytecode. It must execute the CLR to interpret each Bytecode instruction in order to run the compiled Visual Basic program. The CLR does not produce machine code, like a compiler, but instead it reads each Bytecode instruction and gives the computer a corresponding series of operations to perform. Because each Bytecode instruction must first be interpreted, the computer cannot run Bytecode programs as quickly as it can execute machine language. Slower execution is the price we pay for increased portability.

Direct execution
The process by which a computer performs the actions specified in a machine language program
Interpretation
The translation, while a program is running, of non-machine language instructions (such as Bytecode) into executable operations


1.4 How Is Compilation Related to Interpretation and Execution?

It is important to understand that compilation and execution are two distinct processes. During compilation, the computer runs the compiler program. During execution, the object program is loaded into the computer's memory unit, replacing the compiler program. The computer then directly executes the object program, doing whatever the program instructs it to do (see Figure 1.7).

Click To expand
Figure 1.7: Compilation/Execution

We can use the CLR as an example of the process shown in Figure 1.7. The CLR is written in a high-level programming language such as C++ and then compiled into machine language. The machine language is loaded into the computer's memory, and the CLR is executed. Its input is a Visual Basic program that has been compiled into Bytecode. Its results are the series of actions that would take place if the computer could directly execute Bytecode. Figure 1.8 illustrates this process.

Click To expand
Figure 1.8: Compilation and execution of CLR combined with compilation and interpretation of Bytecode

As you look at Figure 1.8, it is important to understand that the output from the compiler can be saved for future use. Once the CLR and the Visual Basic program have been compiled, they can be used over and over without being compiled again. You never need to compile the CLR because that has already been done for you. We have shown its compilation in Figure 1.8 simply to illustrate the difference between the traditional compile-execute steps and the compile-interpret steps that are used with Visual Basic.

Viewed from a different perspective, the CLR makes the computer look like a different computer that has Bytecode as its machine language. The computer itself hasn't changed—it is still the same collection of electronic circuits—but the CLR makes it appear that it has become a different machine. When a program is used to make one computer act like another computer, we call it a virtual machine. For convenience we may refer to the computer as "executing a Visual Basic program," but keep in mind this is just shorthand for saying that "the computer is executing the CLR running a Visual Basic program."

Virtual machine
A program that makes one computer act like another


1.5 What Kinds of Instructions Can Be Written in a Programming Language?

The instructions in a programming language reflect the operations a computer can perform:

  • A computer can transfer data from one place to another.

  • A computer can get data from an input device (a keyboard or mouse, for example) and write data to an output device (a screen, for example).

  • A computer can store data into and retrieve data from its memory and secondary storage (parts of a computer that we discuss in the next section).

  • A computer can compare data values for equality or inequality and make decisions based on the result.

  • A computer can perform arithmetic operations (addition and subtraction, for example) very quickly.

  • A computer can branch to a different section of the instructions.

Programming languages require that we use certain control structures to express algorithms as source code. There are four basic ways of structuring statements (instructions) in most programming languages: by sequence, selection, loop, and with subprograms. Visual Basic adds a fifth way: asynchronously (see Figure 1.9). A sequence is a series of statements that are executed one after another. Selection, the conditional control structure, executes different statements depending on certain conditions. The repetitive control structure, the loop, repeats statements while certain conditions are met. The subprogram allows us to structure our code by breaking it into smaller units. Asynchronous control lets us write code that handles events that originate outside of our program, such as the user clicking a button on the screen with their mouse. Each of these ways of structuring statements controls the order in which the computer executes the statements, which is why they are called control structures.

Click To expand
Figure 1.9: Basic control structures of programming languages

Assume you're driving a car. Going down a straight stretch of road is like following a sequence of instructions. When you come to a fork in the road, you must decide which way to go and then take one or the other branch of the fork. This is what the computer does when it encounters a selection control structure (sometimes called a branch or decision) in a program. Sometimes you have to go around the block several times to find a place to park. The computer does the same sort of thing when it encounters a loop in a program.

A subprogram is a named sequence of instructions written separately from the main program. When the program executes an instruction that refers to the name of the subprogram, the code for the subprogram is executed. When the subprogram has finished executing, execution of the program resumes at the next instruction. Suppose, for example, that every day you go to work at an office. The directions for getting from home to work form a procedure called "Go to the office." It makes sense, then, for someone to give you directions to a meeting by saying "Go to the office, then go four blocks west," without listing all the steps you have to take to get to the office. Subprograms allow us to write parts of our programs separately and then assemble them into final form. They can greatly simplify the task of writing large programs.

Responding to asynchronous events is like working as a pizza delivery person. You wait around the dispatch station with all of the other delivery people. The dispatcher calls your name and gives you some pizzas and a delivery address. You go deliver the pizzas and return to the dispatch station. At the same time, other delivery people may be out driving.[3] The term asynchronous means "not at the same time." In this context it refers to the fact that the user can, for example, click the mouse on the screen at any time while the program is running. The mouse click does not have to happen at some particular time corresponding to certain instructions within the program. The event shown in Figure 1.9 is like the dispatcher. You do not have to write it as part of your program because it is included in Visual Basic. You simply write the event-handler subprograms that the event calls.

Object-Oriented Programming Languages

Early programming languages focused their attention on the operations and control structures of programming. These procedural languages paid little explicit attention to the relationships between the operations and the data. At that time, a typical computer program used only simple data types such as integer and real numbers, which have obvious sets of operations defined by mathematics. Those operations were built directly into early programming languages. As people gained experience with the programming process, they began to realize that in solving complex problems, it is helpful to define new types of data, such as dates and times; these aren't a standard part of a programming language. Each new type of data typically has an associated set of operations, such as determining the number of days between two dates.

Procedural languages thus evolved to include the feature of extensibility: the capability to define new data types. However, they still treated the data and operations as separate parts of the program. A programmer could define a data type to represent the time of day and then write a subprogram to compute the number of minutes between two times, but could not explicitly indicate that the two are related.

Modern programming languages such as Visual Basic allow us to collect a data type and its associated operations into a single entity called an object. They are thus called object-oriented programming languages. The advantage of an object is that it makes the relationship between the data type and operations explicit. The result is that an object is a complete, self-contained unit that can be reused again in other programs. Reusability enables us to write a significant portion of our programs using existing objects, thereby saving us a considerable amount of time and effort.

Object
A collection of data values and associated operations

A class is a description of one or more like objects. Classes are usually collected into groups called namespaces. When we need an object in a program, we instantiate the class that describes the object. That is, we tell the Visual Basic compiler to provide us with one or more of the objects described by a specified class. One characteristic of an object-oriented programming language is having a large library of classes. In this text we present only a small subset of the classes that are available from the Visual Basic library. It is easy to be overwhelmed by the sheer size of Visual Basic's library, but many of those objects are highly specialized and unnecessary for learning the essential concepts of programming.

Class
A description of an object that specifies the types of data values that it can hold and the operations that it can perform
Instantiate
To create an object based on the description supplied by a class

In the next few chapters we consider how to write simple codes that instantiate just a few of the classes in Visual Basic's library. By Chapter 7 we develop enough of the basic concepts of programming to start designing our own classes. Leading up to that point, we learn how to write some specific classes using patterns of Visual Basic code that we provide.

[3]Visual Basic actually allows us to write general asynchronous programs using a construct called a thread. Threaded programs are beyond the scope of this text. We restrict our use of asynchronous structures to handling events.


1.6 What Is a Computer?

You can learn a programming language, how to write programs, and how to run (execute) these programs without knowing much about computers. But if you know something about the parts of the computer, you can better understand the effect of each instruction in a programming language.

Most computers have six basic components: the memory unit, the arithmetic/logic unit, the control unit, input devices, output devices, and auxiliary storage devices. Figure 1.10 is a stylized diagram of the basic components of a computer.

Click To expand
Figure 1.10: Basic components of a computer
Memory unit
Internal data storage in a computer

The memory unit is an ordered sequence of storage cells, each capable of holding a piece of data. Each memory cell has a distinct address to which we refer in order to store data into it or retrieve data from it. These storage cells are called memory cells, or memory locations.[4] The memory unit holds data (input data or the product of computation) and instructions (programs), as shown in Figure 1.11.

Click To expand
Figure 1.11: Memory

The part of the computer that follows instructions is called the central processing unit (CPU). The CPU usually has two components. The arithmetic/logic unit (ALU) performs arithmetic operations (addition, subtraction, multiplication, and division) and logical operations (comparing two values). The control unit controls the actions of the other components so that program instructions are executed in the correct order.

Central processing unit (CPU)
The part of the computer that executes the instructions (program) stored in memory; made up of the arithmetic/logic unit and the control unit
Arithmetic/Logic unit (ALU)
The component of the central processing unit that performs arithmetic and logical operations
Control unit
The component of the central processing unit that controls the actions of the other components so that instructions (the program) are executed in the correct sequence

For us to use computers, we must have some way of getting data into and out of them. Input/Output (I/O) devices accept data that have been prepared (input) and present data that have been processed (output). A keyboard is a common input device. Another is the mouse. A video display is a common output device, as are printers and liquid crystal display (LCD) screens.

Input/Output (I/O) devices
The parts of the computer that accept data to be processed (input) and present the results of that processing (output)

For the most part, computers simply move and combine data in memory. The many types of computers differ primarily in the size of their memories, the speed with which data can be recalled, the efficiency with which data can be moved or combined, and limitations on I/O devices.

When a program is executing, the computer proceeds through a series of steps, the fetch-execute cycle:

  1. The control unit retrieves (fetches) the next coded instruction from memory.

  2. The instruction is translated into control signals.

  3. The control signals tell the appropriate unit (arithmetic/logic unit, memory, I/O device) to perform (execute) the instruction.

  4. The sequence repeats from Step 1.

Computers can have a wide variety of peripheral devices. An auxiliary storage device, or secondary storage device, holds coded data for the computer until we actually want to use the data. Instead of inputting data every time, we can input it once and have the computer store it in an auxiliary storage device. Whenever we need to use the data, we tell the computer to transfer the data from the auxiliary storage device to its memory. An auxiliary storage device therefore serves as both an input and an output device. A disk drive is a cross between a compact disk player and a tape recorder. It uses a thin disk made out of magnetic material. A read/write head (similar to the record/playback head in a tape recorder) travels across the spinning disk, retrieving or recording data. A CD-ROM or a DVD-ROM drive uses a laser to read information stored optically on a plastic disk. The CD-R and DVD-RAM forms of the CD and DVD can be used to both read and write data. A magnetic tape drive is like a tape recorder and is most often used to back up (make a copy of) the data on a disk in case the disk is ever damaged.

Peripheral device
An input, output, or auxiliary storage device attached to a computer
Auxiliary storage device
A device that stores data in encoded form outside the computer's main memory

Together, all of these physical components are known as hardware. The programs that allow the hardware to operate are called software. Hardware is usually fixed in design; software is easily changed. In fact, the ease with which software can be manipulated is what makes the computer such a versatile, powerful tool.

Hardware
The physical components of a computer
Software
Computer programs; the set of all programs available on a computer

In addition to the programs that we write or purchase, there are programs in the computer that are designed to simplify the user/computer interface, making it easier for us to use the machine. The interface between user and computer is a set of I/O devices—for example, the keyboard, mouse, and a screen—that allows the user to communicate with the computer. We work with the keyboard, mouse, and screen on our side of the interface boundary; wires attached to the keyboard and the screen carry the electronic pulses that the computer works with on its side of the interface boundary. At the boundary itself is a mechanism that translates information for the two sides.

Interface
A connecting link at a shared boundary that allows independent systems to meet and act on or communicate with each other

When we communicate directly with the computer through an interface, we are using an interactive system. Interactive systems allow direct entry of programs and data and provide immediate feedback to the user. In contrast, batch systems require that all data be entered before a program is run and provide feedback only after a program has been executed. In this text we focus on interactive systems, although in Chapter 9 we discuss file-oriented programs, which share certain similarities with batch systems.

Interactive system
A system that allows direct communication between user and computer
Operating system
A set of programs that manages all of the computer's resources
Editor
An interactive program used to create and modify source programs or data

The set of programs that simplifies the user/computer interface and improves the efficiency of processing is called system software. It includes the CLR and the Visual Basic compiler as well as the operating system and the editor (see Figure 1.12). The operating system manages all of the computer's resources. It can input programs, call the compiler, execute object programs, and carry out any other system commands. The editor is an interactive program used to create and modify source programs or data.

Click To expand
Figure 1.12: User/Computer interface

[4]The memory unit is also referred to as RAM, an acronym for random-access memory (because we can access any location at random).


1.7 Ethics and Responsibilities in the Computing Profession

Every profession operates with a set of ethics that help to define the responsibilities of people who practice the profession. For example, medical professionals have an ethical responsibility to keep information about their patients confidential. Engineers have an ethical responsibility to their employers to protect proprietary information, but they also have a responsibility to protect the public and the environment from harm that may result from their work. Writers are ethically bound not to plagiarize the work of others, and so on.

The computer presents us with a vast new range of capabilities that can affect people and the environment in dramatic ways. It thus challenges society with many new ethical issues. Some of our existing ethical practices apply to the computer, whereas other situations require new ethical rules. In some cases, there may not be established guidelines, but it is up to you to decide what is ethical. In this section we examine some common situations encountered in the computing profession that raise particular ethical issues.

A professional in the computing industry, like any other professional, has knowledge that enables him or her to do certain things that others cannot do. Knowing how to access computers, how to program them, and how to manipulate data gives the computer professional the ability to create new products, solve important problems, and help people to manage their interactions with the ever-more-complex world that we all live in. Knowledge of computers can be a powerful means to effecting positive change.

Knowledge also can be used in unethical ways. A computer can be programmed to trigger a terrorist's bomb, to sabotage a competitor's production line, or to steal money. Although these blatant examples make an extreme point and are unethical in any context, there are more subtle examples that are unique to computers.

Software Piracy

Computer software is easy to copy. But just like books, software is usually copyrighted—it is illegal to copy software without the permission of its creator. Such copying is called software piracy.

Software piracy
The unauthorized copying of software for either personal use or use by others

Copyright laws exist to protect the creators of software (and books and art) so that they can make a profit from the effort and money spent developing the software. A major software package can cost millions of dollars to develop, and this cost (along with the cost of producing the package, shipping it, supporting customers, and allowing for retailer markup) is reflected in the purchase price. If people make unauthorized copies of the software, then the company loses those sales and either has to raise its prices to compensate or spend less money to develop improved versions of the software—in either case, a desirable piece of software becomes harder to obtain.

Software pirates sometimes rationalize their software theft with the excuse that they're just making one copy for their own use. It's not that they're selling a bunch of bootleg copies, after all. But if thousands of people do the same, then it adds up to millions of dollars in lost revenue for the company, which leads to higher prices for everyone.

Computing professionals have an ethical obligation to not engage in software piracy and to try to stop it from occurring. You never should copy software without permission. If someone asks you for a copy of a piece of software, you should refuse to supply it. If someone says they just want to "borrow" the software so they can "try it out," tell them they are welcome to try it out on your machine (or at a retailer's shop) but not to make a copy.

This rule isn't restricted to duplicating copyrighted software; it includes plagiarism of all or part of code. If someone gives you permission to copy some code, then just like any responsible writer, you should acknowledge that person with a citation in the code.

Privacy of Data

The computer enables the compilation of databases containing useful information about people, companies, geographic regions, and so on. These databases allow employers to issue payroll checks, banks to cash a customer's check at any branch, the government to collect taxes, and mass merchandisers to send out junk mail. Even though we may not care for every use of databases, they generally have positive benefits. However, they also can be used in negative ways.

For example, a car thief who gains access to the state motor vehicle registry could print out a "shopping list" of valuable car models together with their owners' addresses. An industrial spy might steal customer data from a company database and sell it to a competitor. Although these are obviously illegal acts, computer professionals face other situations that are not so obvious.

Suppose your job includes managing the company payroll database. In that database are the names and salaries of the employees in the company. You might be tempted to poke around in the database and see how your salary compares to your associates—this act is unethical and an invasion of your associates' right to privacy. This information is confidential. Any information about a person that is not clearly public should be considered confidential. An example of public information is a phone number listed in a telephone directory. Private information includes any data that has been provided with an understanding that it will be used only for a specific purpose (such as the data on a credit card application).

A computer professional has a responsibility to avoid taking advantage of special access that he or she may have to confidential data. The professional also has a responsibility to guard that data from unauthorized access. Guarding data can involve such simple things as shredding old printouts, keeping backup copies in a locked cabinet, not using passwords that are easy to guess (such as a name or word), and more complex measures such as encryption (keeping it stored in a secret coded form).

Use of Computer Resources

If you've ever bought a computer, you know that it costs money. A personal computer can be relatively inexpensive, but it's still a major purchase. Larger computers can cost millions of dollars. Operating a PC may cost a few dollars a month for electricity and an occasional outlay for paper, disks, and repairs. Larger computers can cost tens of thousands of dollars per month to operate. Regardless of the type of computer, whoever owns it has to pay these costs. They do so because the computer is a resource that justifies its expense.

The computer is an unusual resource because it is valuable only when a program is running. Thus, the computer's time is really the valuable resource. There is no significant physical difference between a computer that is working and one that is sitting idle. Thus, unauthorized use of a computer is different from unauthorized use of a car. If one person uses another's car without permission, that individual must take possession of it physically—that is, steal it. If someone uses a computer without permission, the computer isn't physically stolen, but just as in the case of car theft, the owner is being deprived of a resource that he or she is paying for.

For some people, theft of computer resources is a game—like joyriding in a car. The thief really doesn't want the resources; it is just the challenge of breaking through a computer's security system and seeing how far he or she can get without being caught. Success gives a thrilling boost to this sort of person's ego. Many computer thieves think that their actions are acceptable if they don't do any harm, but whenever real work is displaced from the computer by such activities, then harm is clearly being done. If nothing else, the thief is trespassing in the computer owner's property. By analogy, consider that even though no physical harm may be done by someone who breaks into your bedroom and takes a nap while you are away, such an action is certainly disturbing to you because it poses a threat of potential physical harm. In this case, and in the case of breaking into a computer, mental harm can be done.

Other thieves can be malicious. Like a joyrider who purposely crashes a stolen car, these people destroy or corrupt data to cause harm. They may feel a sense of power from being able to hurt others with impunity. Sometimes these people leave behind programs that act as time bombs, to cause harm long after they have gone. Another kind of program that may be left is a virus—a program that replicates itself, often with the goal of spreading to other computers. Viruses can be benign, causing no other harm than to use up some resources. Others can be destructive and cause widespread damage to data. Incidents have occurred in which viruses have cost billions of dollars in lost computer time and data.

Virus
Code that replicates itself, often with the goal of spreading to other computers without authorization, and possibly with the intent of doing harm

Computing professionals have an ethical responsibility never to use computer resources without permission. This includes activities such as doing personal work on an employer's computer. We also have a responsibility to help guard resources to which we have access—by using unguessable passwords and keeping them secret, by watching for signs of unusual computer use, by writing programs that do not provide loopholes in a computer's security system, and so on.

Software Engineering

Humans have come to depend greatly on computers in many aspects of their lives. That reliance is fostered by the perception that computers function reliably; that is, they work correctly most of the time. However, the reliability of a computer depends on the care that is taken in writing its software.

Errors in a program can have serious consequences. Here are a few examples of real incidents involving software errors. An error in the control software of an F-18 jet fighter caused it to flip upside down the first time it flew across the equator. A rocket launch went out of control and had to be blown up because there was a comma typed in place of a period in its control software. A radiation therapy machine killed several patients because a software error caused the machine to operate at full power when the operator typed certain commands too quickly.

Even when the software is used in less critical situations, errors can have significant effects. Examples of such errors include

  • An error in a word processor that causes your term paper to be lost just hours before it is due

  • An error in a statistical program that causes a scientist to draw a wrong conclusion and publish a paper that must later be retracted

  • An error in a tax preparation program that produces an incorrect return, leading to a fine

Programmers thus have a responsibility to develop software that is free from errors. The process that is used to develop correct software is known as software engineering.

Software engineering
The application of traditional engineering methodology and techniques to the development of software

Software engineering has many aspects. The software life cycle described at the beginning of this chapter outlines the stages in the development of software. Different techniques are used at each of these stages. We address many of the techniques in this text. In Chapter 5 we introduce methodologies for developing correct algorithms. We discuss strategies for testing and validating programs in every chapter. We use a modern programming language that enables us to write readable, well-organized programs, and so on. Some aspects of software engineering, such as the development of a formal, mathematical specification for a program, are beyond the scope of this text.


1.8 Problem-Solving Techniques

You solve problems every day, often unaware of the process you are going through. In a learning environment, you usually are given most of the information you need: a clear statement of the problem, the necessary input, and the required output. In real life, the process is not always so simple. You often have to define the problem yourself and then decide what information you have to work with and what the results should be.

After you understand and analyze a problem, you must come up with a solution—an algorithm. Earlier we defined an algorithm as a step-by-step procedure for solving a problem in a finite amount of time with a finite amount of data. Although you work with algorithms all the time, most of your experience with them is in the context of following them. You follow a recipe, play a game, assemble a toy, or take medicine. In the problem-solving phase of computer programming, you will be designing algorithms, not following them. This means you must be conscious of the strategies you use to solve problems in order to apply them to programming problems.

Ask Questions

If you are given a task orally, you ask questions—When? Why? Where?—until you understand exactly what you have to do. If your instructions are written, you might put question marks in the margin, underline a word or a sentence, or in some other way indicate that the task is not clear. Your questions may be answered by a later paragraph, or you might have to discuss them with the person who gave you the task.

These are some of the questions you might ask in the context of programming:

  • What do I have to work with—that is, what are my data?

  • What do the data items look like?

  • What are the operations to be performed on the data?

  • How much data is there?

  • How will I know when I have processed all the data?

  • What should my output look like?

  • What special error conditions might come up?

Look for Things That Are Familiar

Never reinvent the wheel. If a solution exists, use it. If you've solved the same or a similar problem before, just repeat your solution. People are good at recognizing similar situations. We don't have to learn how to go to the store to buy milk, then to buy eggs, and then to buy candy. We know that going to the store is always the same; only what we buy is different.

In programming, certain problems occur again and again in different guises. A good programmer immediately recognizes a subtask he or she has solved before and plugs in the solution. For example, finding the daily high and low temperatures is really the same problem as finding the highest and lowest grades on a test. You want the largest and smallest values in a set of numbers (see Figure 1.13).

Click To expand
Figure 1.13: Look for things that are familiar.

In Chapter 8, we see how this problem-solving strategy can be implemented in Visual Basic using a mechanism called inheritance, which allows us to define a new object that adds to the capabilities of an existing object.

Solve by Analogy

Often a problem reminds you of one you have seen before. You may find solving the problem at hand easier if you remember how you solved the other problem. In other words, draw an analogy between the two problems. For example, a solution to a perspective projection problem from an art class might help you figure out how to compute the distance to a landmark when you are on a cross-country hike. As you work your way through the new problem, you come across things that are different than they were in the old problem, but usually these are just details that you can deal with one at a time.

Analogy is really just a broader application of the strategy of looking for things that are familiar. When you are trying to find an algorithm for solving a problem, don't limit yourself to computer-oriented solutions. Step back and try to get a larger view of the problem. Don't worry if your analogy doesn't match perfectly—the only reason for starting with an analogy is that it gives you a place to start (see Figure 1.14). The best programmers are people who have broad experience in solving all kinds of problems.

Click To expand
Figure 1.14: Analogy

Means-Ends Analysis

Often the beginning state and the ending state are given; the problem is to define a set of actions that can be used to get from one to the other. Suppose you want to go from Boston, Massachusetts to Austin, Texas. You know the beginning state (you are in Boston) and the ending state (you want to be in Austin). The problem is how to get from one to the other.

In this example, you have lots of choices. You can fly, walk, hitchhike, ride a bike, or whatever. The method you choose depends on your circumstances. If you're in a hurry, you'll probably decide to fly.

Once you've narrowed down the set of actions, you have to work out the details. It may help to establish intermediate goals that are easier to meet than the overall goal. Let's say there is a really cheap, direct flight to Austin out of Newark, New Jersey. You might decide to divide the trip into legs: Boston to Newark and then Newark to Austin. Your intermediate goal is to get from Boston to Newark. Now you only have to examine the means of meeting that intermediate goal (see Figure 1.15).

Click To expand
Figure 1.15: Means-ends analysis

The overall strategy of means-ends analysis is to define the ends and then to analyze your means of getting between them. The process translates easily to computer programming. You begin by writing down what the input is and what the output should be. Then you consider the actions a computer can perform and choose a sequence of actions that can transform the input into the results.

Divide and Conquer

We often break up large problems into smaller units that are easier to handle. Cleaning the whole house may seem overwhelming; cleaning the rooms one at a time seems much more manageable. The same principle applies to programming. We break up a large problem into smaller pieces that we can solve individually (see Figure 1.16). In fact, the object-oriented design and functional decomposition methodologies, which we describe in Chapter 5, are both based on the principle of divide and conquer.

Click To expand
Figure 1.16: Divide and conquer

The Building-Block Approach

Another way of attacking a large problem is to see if any solutions for smaller pieces of the problem exist. It may be possible to put some of these solutions together end to end to solve most of the big problem. This strategy is just a combination of the look-for-familiar-things and divide-and-conquer approaches. You look at the big problem and see that it can be divided into smaller problems for which solutions already exist. Solving the big problem is just a matter of putting the existing solutions together, like mortaring together blocks to form a wall (see Figure 1.17).

Click To expand
Figure 1.17: Building-block approach

With an object-oriented programming language, we often solve a problem by first looking in the class library to see that solutions have been developed previously and then writing a small amount of additional code to put the pieces together. As we see later, this problem-solving technique is the basis for the methodology called object-oriented design.

Merging Solutions

Another way to combine existing solutions is to merge them on a step-by-step basis. For example, to compute the average of a list of values, we must both sum and count the values. If we already have separate solutions for summing values and for counting the number of values, we can combine them. But if we first do the summing and then do the counting, we have to read the list twice. We can save steps if we merge these two solutions: read a value and then add it to the running total and add 1 to our count before going on to the next value. Whenever the solutions to subproblems duplicate steps, think about merging them instead of joining them end to end.

Mental Blocks: The Fear of Starting

Writers are all too familiar with the experience of staring at a blank page, not knowing where to begin. Programmers have the some difficulty when they first tackle a big problem. They look at the problem and it seems overwhelming (see Figure 1.18).

Click To expand
Figure 1.18: Mental block

Remember that you always have a place to begin solving any problem: Write it down on paper in your own words so that you understand it. Once you paraphrase the problem, you can focus on each of the subparts individually instead of trying to tackle the entire problem at once. This process gives you a clearer picture of the overall problem. It helps you see pieces of the problem that look familiar or that are analogous to other problems you have solved. And it pinpoints areas where something is unclear, where you need more information.

As you write down a problem, you tend to group things together into small, understandable chunks of data and operations, which may be natural places to split the problem up—to divide and conquer. Your description of the problem may collect all of the information about data and results into one place for easy reference. Then you can see the beginning and ending states necessary for means-ends analysis.

Most mental blocks are cause by not really understanding the problem. Rewriting the problem in your own words is a good way to focus on the subparts of the problem, one at a time, and to understand what is required for a solution.

Algorithmic Problem Solving

Coming up with an algorithm for solving a particular problem is not always cut-and-dried. It fact, it is usually a trial-and-error process requiring several attempts and refinements. We test each attempt to see if it really solves the problem. If it does, fine. If it doesn't, we try again. We typically use a combination of the techniques we've described to solve any nontrivial problem.

Remember that the computer can only do certain things. Your primary concern, then, is how to make the computer transform, manipulate, calculate, or process the input data to produce the desired output. If you keep in mind the allowable instructions and data types in your programming language, you won't design an algorithm that is difficult or impossible to code.

In the case study that follows, we develop a program for calculating employees' weekly wages. It typifies the thought processes involved in writing an algorithm and coding it as a program, and it shows you what a complete Visual Basic program looks like.

Start Sidebar
Problem-Solving Case Study
A Company Payroll Program

Problem A company needs a program to figure its weekly payroll. The input data, consisting of each employees' identification number, pay rate, and hours worked, is in the file datafile.dat in secondary storage. The program should input the data for each employee, calculate the weekly wages, save the input information for each employee along with the weekly wages in a file, and display the total wages for the week on the screen, so that the payroll clerk can transfer the appropriate amount into the payroll account.

Discussion At first glance, this seems like a simple problem. But if you think about how you would do it by hand, you see that you need to ask questions about the specifics of the process: What is the employee data that we need and how is the data written to the file? How are wages computed? In what file should the results be stored? How does the program know that all of the employees have been processed? How should the total be displayed?

  • The data for each employee includes an employee identification number, the employee's hourly pay rate, and the hours worked. Each data value is written on a separate line.

  • Wages equal the employees pay rate times the number of hours worked up to 40 hours. If an employee worked more than 40 hours, wages equal the employee's pay rate times 40 hours, plus 1½ times the employee's regular pay rate times the number of hours worked above 40.

  • The results should be stored in file called payfile.dat.

  • The program knows to finish the processing when there is no more data in the input file.

  • The total should be shown in a window on the screen that can be closed by the user.

We begin by looking for things that are familiar. An experienced Visual Basic programmer immediately recognizes that this problem contains many different objects that are represented as classes in the Visual Basic library. The input file and the output file are objects, as is the window in which the total payroll is displayed on the screen. The employee identification number, pay rate, hours worked, wages earned, and total wages are objects in the problem that we must find a way to represent in our algorithm. Here's a list of the objects we've identified:

  • Input file, datafile.dat, represented by one of the Visual Basic file classes

  • Output file, payfile.dat, represented by another Visual Basic file class

  • Display window, represented by a Visual Basic Messagebox class

  • Employee identification number

  • Pay rate

  • Hours worked

  • Wages

  • Total wages

Now that we know the objects we are working with, we need to fit them together with operations that enable them to exchange information. The operations coordinate the behavior of the objects in a way that solves the problem, like the choreography that coordinates ballet dancers moving around a stage, interacting with each other.

Let's apply the divide-and-conquer approach to identify the main operations in which our objects must participate. It's clear that there are two main steps to be accomplished. One is to process the input file, and the other is to display the total on the screen. Let's look at each of those steps separately, once again applying divide-and-conquer.

First we consider the processing of the data in the input file. Each data set represents one employee, and we process each employee's data in turn. There are three obvious steps in almost any problem of this type. For each person, we must:

  1. Get the data.

  2. Compute the results.

  3. Output the results.

Our first step is to get the data. (By get, we mean read or input the data.) We need three pieces of data for each employee: employee identification number, hourly pay rate, and number of hours worked. Each data value is written in the input file. Therefore, to input the data, we take these steps:

  • Read the employee number.

  • Read the pay rate.

  • Read the number of hours worked.

The next step is to compute the wages. Let's expand this step with means-ends analysis. Our starting point is the set of data values that was input; our desired ending, the payroll for the week. The means at our disposal are the basic operations that the computer can perform, which include calculation and control structures. Let's begin by working backward from the end.

We know that there are two formulas for computing wages: one for regular hours and one for overtime. If there is no overtime, wages are simply the pay rate times the number of hours worked. If the number of hours worked is greater than 40, however, wages are 40 times the pay rate, plus the number of overtime hours times 1½ times the pay rate. The overtime hours are computed by subtracting 40 from the total number of hours worked. Here are the two formulas:

We now have the means to compute the wages for each person. Our intermediate goal is then to execute the correct formula given the input data. We must decide which formula to use and employ a branching control structure to make the computer execute the appropriate formula. The decision that controls the branching structure is simply whether more than 40 hours have been worked. We now have the means to get from our starting point to the desired end. To figure the wages, then, we take the following steps:

If hours worked is greater than 40.0, then

otherwise

The last step, outputting the results, is simply a matter of directing the computer to write the employee number, the pay rate, the number of hours worked, and the wages into payfile.dat:

  Write the employee number, pay rate, hours worked, and wages into payfile.dat

We now have an algorithm that processes the data for one employee. We need to extend this algorithm to handle all of the employees. Let's use the building-block approach to enclose our three main steps (getting the data, computing the wages, and outputting the results) within a looping structure that continues until each employee has been processed. Once we have computed the wages for one employee, we need to add them to a running total so that we can display it at the end of processing. Our algorithm now coordinates the behavior of the objects to accomplish the first of our two major steps.

The second major step is to display the total wages and stop the program. We again use divide-and-conquer to break this into a series of steps:

  Call a Messagebox object
  Pass the total to the Messagebox
  Show the Messagebox on the screen
  Stop the program

Finally, we must take care of housekeeping chores. Before we start processing, we must prepare the input file for reading, prepare the output file to receive the results, and set the running total to zero.

What follows is the complete algorithm. Calculating the wages is written as a separate subalgorithm that is defined below the main algorithm. Notice that the algorithm is simply a very precise description of the same steps you would follow to do this process by hand.

Main Algorithm
Start example
  Prepare to read a list of employee information (open file object datafile.dat)
  Prepare to write a list of employees' wages (open file object payfile.dat)
  Set the total payroll to zero
  while there is more data in file dataFile
       Read employee number
       Read the pay rate
       Read the number of hours worked
       Calculate pay
       Add the employee's wages to the total payroll
       Write the employee number, pay rate, hours worked, and wages into the
       list (file payFile)
  Call a Messagebox object
  Pass the total to the Messagebox
  Display the Messagebox on the screen
  Stop the program
End example
Subalgorithm for Calculating Pay
Start example

if number of hours worked is greater than 40.0, then

else

End example

Before we implement this algorithm, we should test it by hand, simulating the algorithm with specific data values. Case Study Follow-Up Exercise 2 asks you to carry out this test.

What follows is the Visual Basic program for this algorithm. It's here to give you an idea of what you'll be learning. If you've had no previous exposure to programming, you probably won't understand most of the program. Don't worry; you will soon. In fact, throughout this book as we introduce new constructs, we refer you back to the Payroll program. One more thing: The remarks following apostrophes (') are called comments. They are here to help you understand the program; the compiler ignores them.

Imports System.io

Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim empNum As String
    Dim payRate As Double
    Dim hours As Double
    Dim wages As Double
    Dim total As Double = 0
    Dim theFile As File
    Dim dataFile As StreamReader
    Dim payFile As StreamWriter
#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()
        dataFile = theFile.OpenText("datafile.dat")
        payFile = theFile.CreateText("payfile.dat")
        While (dataFile.Peek <> -1)
            empNum = dataFile.ReadLine()
            payRate = CDbl(dataFile.ReadLine())
            hours = CDbl(dataFile.ReadLine())
            wages = CalcPay(payRate, hours)
            total = total + wages
            ' Put results into payFile
            payFile.WriteLine(empNum & " " & payRate & " " & hours _
                              & " " & wages)
        End While
        MessageBox.Show("Total payroll for the week is $" &      total & _
                        "Close window to exit program.")
        payFile.Close()
        dataFile.Close()
        'Add any initialization after the InitializeComponent() call

    End Sub
    Private Function calcPay(ByVal payRate As Double, _
    ByVal hours As Double)
        Const MAX_HOURS As Double = 40
        Const OVERTIME As Double = 1.5
        If (hours > MAX_HOURS) Then
            Return (MAX_HOURS * payRate) + (hours - MAX_HOURS) * _
                    payrate * OVERTIME

        Else
            Return hours * payRate
        End If
    End Function
    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing _
        As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
#End Region

End Class

Given the following input in file datafile.dat:

534923445
6.54
45
103428439
12.82
38
131909545
8.20
52
739219803
10.00
40

The program outputs the following in file payfile.dat:

534923445 6.54 45 310.65
103428439 12.82 38 487.16
131909545 8.2 52 475.6
739219803 10 40 400

And then it displays the following window:

Click To expand

For such a simple task, the length of this program is rather daunting. Don't worry. A large part of this program involves preparation for inputting and outputting data. These steps become second nature to you very shortly, and you can use the same algorithmic steps again and again (the building-block approach).

End Sidebar


Summary

We think nothing of turning on the television and sitting down to watch it. It's a communication tool we use to enhance our lives. Computers are becoming as common as televisions, just a normal part of our lives. And like televisions, computers are based on complex principles but are designed for easy use.

Computers are unintelligent; they must be told what to do. A true computer error is extremely rare (usually due to a component malfunction or an electrical fault). Because we tell the computer what to do, most errors in computer-generated output are really human errors.

Computer programming is the process of planning a sequence of steps for a computer to apply to data. It involves a problem-solving phase and an implementation phase. After analyzing a problem, we develop and test a general solution (algorithm). This general solution becomes a concrete solution—our program—when we write it in a high-level programming language. The sequence of instructions that makes up our program is then either compiled into machine code (the language the computer uses) or Bytecode (the language the Common Language Runtime, or CLR, uses). After correcting any errors or "bugs" that show up during testing, our program is ready to use.

Once we begin to use the program, it enters the maintenance phase. Maintenance involves correcting any errors discovered while the program is being used and changing the program to reflect changes in the user's requirements.

Data and instructions are represented as binary numbers (numbers consisting of just 1s and 0s) in electronic computers. The process of converting data and instructions into a form usable by the computer is called coding.

A programming language reflects the range of operations a computer can perform. In this text, you will learn to write programs in the high-level programming language called Visual Basic. The basic control structures in the Visual Basic programming language—sequence, selection, loop, subprogram, and asynchronous—are based on the fundamental operations of the computer. Visual Basic provides the ability to collect data and operations into self-contained units called objects that can be reused in other programs.

Computers are composed of six basic parts: the memory unit, the arithmetic/logic unit, the control unit, input devices, output devices, and auxiliary storage devices. The arithmetic/logic unit and control unit together are called the central processing unit. The physical parts of the computer are called hardware. The programs that are executed by the computer are called software.

System software is a set of programs designed to simplify the user/computer interface. It includes the compiler, the operating system, the CLR, and the editor.

Computing professionals are guided by a set of ethics, as are members of other professions. Among the responsibilities that we have are: copying software only with permission and including attribution to other programmers when we make use of their code, guarding the privacy of confidential data, using computer resources only with permission, and carefully engineering our programs so that they work correctly.

We've said that problem solving is a integral part of the programming process. Although you may have little experience programming computers, you have lots of experience solving problems. The key is to stop and think about the strategies that you use to solve problems, and then use those strategies to devise workable algorithms. Among those strategies are asking questions, looking for things that are familiar, solving by analogy, applying means-ends analysis, dividing the problem into subproblems, using existing solutions to small problems to solve a larger problem, merging solutions, and paraphrasing the problem in order to overcome a mental block.

The computer is widely used today in science, engineering, business, government, medicine, production of consumer goods, and the arts. Learning to program in Visual Basic can help you use this powerful tool effectively.


Quick Check

The Quick Check is intended to help you decide if you've met the goals set forth at the beginning of each chapter. If you understand the material in the chapter, the answer to each question should be fairly obvious. After reading a question, check your response against the answers listed at the end of the Quick Check. If you don't know an answer or don't understand the answer that's provided, turn to the page(s) listed at the end of the question to review the material.

Q: | 1. What is a computer program? (p. 3) | 1. A computer program is a sequence of instructions performed by a computer.
Q: | 2. What are the three phases in a program's life cycle? (p. 4) | 2. The three phases of a program's life cycle are problem-solving, implementation, and maintenance.
Q: | 3. Is an algorithm the same as a program? (pp. 4–5) | 3. No. All programs are algorithms, but not all algorithms are programs.
Q: | 4. What is a programming language? (p. 6) | 4. A set of rules, symbols, and special words used to construct a program.
Q: | 5. What are the advantages of using a high-level programming language? (pp. 12–13) | 5. A high-level programming language is easier to use than an assembly language or a machine language, and programs written in a high-level language can be run on many different computers.
Q: | 6. What does a compiler do? (p. 10) | 6. The compiler translates a program written in a high-level programming language to either object code or Bytecode.
Q: | 7. What is the difference between machine code and Bytecode? (pp. 11–13) | 7. Machine code is the native binary language that is directly executed by any particular computer. Bytecode is a standard portable machine language that is executed by the Common Language Runtime, but it is not directly executed by the computer.
Q: | 8. What part does the Common Language Runtime play in the compilation and interpretation process? (pp. 13–15) | 8. It translates the Bytecode instructions into operations that are executed by the computer.
Q: | 9. Name the five basic ways of structuring statements in Visual Basic. (pp. 15–17) | 9. Sequence, selection, loop, subprogram, and asynchronous.
Q: | 10. What are the six basic components of a computer? (pp. 19–22) | 10. The basic components of a computer are the memory unit, arithmetic/logic unit, control unit, input and output devices, and auxiliary storage devices.
Q: | 11. What is the difference between hardware and software? (pp. 20–21) | 11. Hardware is the physical components of the computer; software is the collection of programs that run on the computer.
Q: | 12. In what regard is theft of computer time like stealing a car? How are the two crimes different? (pp. 22–26) | 12. Both crimes deprive the owner of access to a resource. A physical object is taken in a car theft, whereas time is the thing being stolen from the computer owner.
Q: | 13. What is the divide-and-conquer approach? (p. 29) | 13. The divide-and-conquer approach is a problem-solving technique that breaks a large problem into smaller, simpler sub-problems.

Answers

A: | 1. A computer program is a sequence of instructions performed by a computer.
A: | 2. The three phases of a program's life cycle are problem-solving, implementation, and maintenance.
A: | 3. No. All programs are algorithms, but not all algorithms are programs.
A: | 4. A set of rules, symbols, and special words used to construct a program.
A: | 5. A high-level programming language is easier to use than an assembly language or a machine language, and programs written in a high-level language can be run on many different computers.
A: | 6. The compiler translates a program written in a high-level programming language to either object code or Bytecode.
A: | 7. Machine code is the native binary language that is directly executed by any particular computer. Bytecode is a standard portable machine language that is executed by the Common Language Runtime, but it is not directly executed by the computer.
A: | 8. It translates the Bytecode instructions into operations that are executed by the computer.
A: | 9. Sequence, selection, loop, subprogram, and asynchronous.
A: | 10. The basic components of a computer are the memory unit, arithmetic/logic unit, control unit, input and output devices, and auxiliary storage devices.
A: | 11. Hardware is the physical components of the computer; software is the collection of programs that run on the computer.
A: | 12. Both crimes deprive the owner of access to a resource. A physical object is taken in a car theft, whereas time is the thing being stolen from the computer owner.
A: | 13. The divide-and-conquer approach is a problem-solving technique that breaks a large problem into smaller, simpler sub-problems.


Exam Preparation Exercises

    Chapter 1

  1. Explain why the following series of steps is not an algorithm, then rewrite the series so it is.

    Shampooing

    1. Rinse.

    2. Lather.

    3. Repeat.

  2. Describe the input and output files used by a compiler.

  3. In the following recipe for chocolate pound cake, identify the steps that are branches (selection) and loops, and the steps that are references to subalgorithms outside the algorithm.

    Click To expand

  4. Put a check next to each item below that is a peripheral device.

    • _____ a. Disk drive

    • _____ b. Arithmetic/logic unit

    • _____ c. Magnetic tape drive

    • _____ d. Printer

    • _____ e. CD-ROM drive

    • _____ f. Memory

    • _____ g. Auxiliary storage device

    • _____ h. Control unit

    • _____ i. LCD display

    • _____ j. Mouse

  5. Next to each item below, indicate whether it is hardware (H) or software (S).

    • _____ a. Disk drive

    • _____ b. Memory

    • _____ c. Compiler

    • _____ d. Arithmetic/logic unit

    • _____ e. Editor

    • _____ f. Operating system

    • _____ g. Object program

    • _____ h. Common Language Runtime

    • _____ i. Central processing unit

  6. Means-ends analysis is a problem-solving strategy.

    1. What are three things you must know in order to apply means-ends analysis to a problem?

    2. What is one way of combining this technique with the divide-and-conquer strategy?

  7. Show how you would use the divide-and-conquer approach to solve the problem of finding a job.

  8. Distinguish between information and data.


Programming Warm-Up Exercises

    Chapter 1

  1. Write an algorithm for driving from where you live to the nearest airport that has regularly scheduled flights. Restrict yourself to a vocabulary of 50 words plus numbers and place names. You must select the appropriate set of words for this task. An example of a vocabulary is given in Appendix A, the list of reserved words (words with special meanings) in the Visual Basic programming language. The purpose of this exercise is to give you practice in writing simple, exact instructions with an equally small vocabulary.

  2. Write an algorithm for making a peanut butter and jelly sandwich, using a vocabulary of just 50 words (you choose the words). Assume that all ingredients are in the refrigerator and that the necessary tools are in a drawer under the kitchen counter. The instructions must be very simple and exact because the person making the sandwich has no knowledge of food preparation and takes every word literally.

  3. In Exercise 1 above, identify the sequential, conditional, repetitive, and subprograms steps.


Case Study Follow-Up Exercises

    Chapter 1

  1. Using Figure 1.16 as a guide, construct a divide-and-conquer diagram of the Problem-Solving Case Study, A Company Payroll Program.

  2. Use the following data set to test the payroll algorithm presented on page 35. Follow each step of the algorithm just as it is written, as if you were a computer. Then check your results by hand to be sure that the algorithm is correct.

    ID Number

    Pay Rate

    Hours Worked

    327

    8.30

    48

    201

    6.60

    40

    29

    12.50

    40

    166

    9.25

    51

    254

    7.00

    32

  3. In the Company Payroll Program case study, we used means-ends analysis to develop the subalgorithm for calculating pay. What are the ends in the analysis? That is, what information did we start with and what information did we want to end up with?

  4. In the Payroll program, certain remarks are preceded by the symbol '. What are these remarks called, and what does the compiler do with them? What is their purpose?


Chapter 2: Visual Basic Syntax and Semantics and the Program Entry Process

Overview

Goals

  • To be able to describe the composition of a Visual Basic program.

  • To be able to read syntax templates in order to understand the formal rules governing Visual Basic programs.

  • To be able to create and recognize legal Visual Basic identifiers.

  • To be able to declare named constants and variables of type Char and type String.

  • To be able to distinguish reserved words in Visual Basic from user-defined identifiers.

  • To be able to assign values to variables and objects.

  • To be able to construct simple string expressions made up of constants, variables, and the concatenation operator.

  • To be able to construct a code segment that displays a message in a form on the screen.

  • To be able to determine what is displayed by a given code segment.

  • To be able to use comments to clarify your programs.

  • To be able to construct simple Visual Basic application programs.

  • To learn the steps involved in entering and running a Visual Basic program.

Programmers develop solutions to problems using a programming language. In this chapter, we start looking at the rules and symbols that make up the Visual Basic programming language. We also review the steps required to create a program and make it work on a computer.


2.1 Syntax and Semantics

A programming language is a set of rules, symbols, and special words used to construct a program. There are rules for both syntax (grammar) and semantics (meaning).

Semantics
The set of rules that determines the meaning of instructions written in a programming language

Syntax is a formal set of rules that defines exactly what combinations of letters, numbers, and symbols can be used in a programming language. There is no room for ambiguity in the syntax of a programming language because the computer can't think; it doesn't "know what we mean." To avoid ambiguity, syntax rules must be written in a very simple, very precise, formal language called a metalanguage.

Syntax
The formal rules governing how valid instructions are written in a programming language
Metalanguage
A language that is used to describe the syntax rules for another language

Learning to read a metalanguage is like learning to read the rules of a sport. Once you understand the notation, you can read the rule book. It's true that many people learn a sport simply by watching others play, but what they learn is usually just enough to allow them to take part in casual games. You could learn Visual Basic by following the examples in this book, but a serious programmer, like a serious athlete, must take the time to read and understand the rules.

Syntax rules are the blueprints we use to "build" instructions in a program. They allow us to take the elements of a programming language-;the basic building blocks of the language-;and assemble them into constructs-;syntactically correct structures. If our program violates any of the rules of the language-;by misspelling a crucial word or leaving out an important comma, for instance-;the program is said to have syntax errors and cannot compile correctly until we fix them.

Syntax Templates

In this book, we write the syntax rules for Visual Basic using a metalanguage called a syntax template. A syntax template is a generic example of a Visual Basic construct being defined. Graphic conventions show which portions are optional and which can be repeated. A colored word or symbol is written in the Visual Basic construct just as it is in the template. A noncolored word can be replaced by another template. A square bracket is used to indicate a set of items from which one can be chosen.

Start Sidebar
Theoretical Foundations
Metalanguages

Metalanguage is the word language with the prefix meta, which means "beyond" or "more comprehensive." A metalanguage is a language that goes beyond a normal language by allowing you to speak precisely about that language. It is a language for talking about languages. It is like an English grammar book describing the rules of English.

One of the oldest computer-oriented metalanguages is the Backus-Naur Form (BNF), which is named for John Backus and Peter Naur, who developed it in 1960. BNF syntax definitions are written out using letters, numbers, and special symbols. For example, an identifier (a name for something) in Visual Basic must be at least one letter or an underscore character, which may or may not be followed by additional letters, the underscore symbol, or digits. The BNF definition (Visual Basic is actually defined using a modified BNF) of an identifier in Visual Basic is

Identifier ::= NonEscapedIdentifier [ TypeCharacter ] | EscapedIdentifier
NonEscapedIdentifier ::= < IdentifierName but not Keyword >
EscapedIdentifier ::= [ IdentifierName ]
IdentifierName ::= IdentifierStart [ IdentifierCharacter+ ]
IdentifierStart ::=
   AlphaCharacter |
   UnderscoreCharacter IdentifierCharacter
IdentifierCharacter ::=
   UnderscoreCharacter
   AlphaCharacter
   NumericCharacter
   CombiningCharacter
AlphaCharacter ::= < Unicode alphabetic character (classes Lu, Ll, Lt, Lm, Lo,
   Nl) >
NumericCharacter ::= < Unicode decimal digit character (class Nd) >
CombiningCharacter ::= < Unicode combining character (classes Mn, Mc) >
FormattingCharacter ::= < Unicode formatting character (class Cf) >
UnderscoreCharacter ::= < Unicode connection character (class Pc) >
IdentifierOrKeyword ::= Identifier | Keyword

where the symbol ::= is read "is defined as," the symbol | means "or," and the symbols [ and ] are used to enclose optional elements of the definition. When a word is defined completely, it is called a terminal symbol, whereas a word that has further definitions farther down the BNF is called a nonterminal symbol.

The first line of the definition reads: "An identifier is defined as a nonescaped identifier that consists of a type character or an escaped identifier." This line contains nonterminal symbols that must be defined. In the second line, the terminal symbol NonEscapedIdentifier is defined as an <IdentifierName but not Keyword>. If you look further down, an IdentifierName is defined as an IdentifierStart plus [ IdentifierCharacter+ ], which means there can be more than one IdentifierCharacter. Below this line, IdentifierStart is defined as an AlphaCharacter or an UnderScoreCharacter or an IdentifierCharacter.

As you can see, the BNF continues defining each part of an identifier until all the parts are defined. BNF is an extremely simple language, but that simplicity leads to syntax definitions that can be long and difficult to read. An alternative metalanguage, the syntax diagram, is easier to follow. It uses arrows to indicate how symbols can be combined. Here are the syntax diagrams that define an identifier in Visual Basic:

Click To expand

To read the diagrams, start at the left and follow the arrows. When you come to a branch, take any one of the branch paths.

The first diagram shows that an identifier can consist of a letter and optionally any number of letters or digits. The remaining diagram defines the nonterminal symbols letter and digit to be any one of the alphabetic or numeric characters. Here, we have eliminated the BNF nonterminal symbols such as IdentifierCharacter by using arrows to indicate how identifiers are created from the letters, digits, and the underscore character.

Syntax diagrams are easier to interpret than BNF definitions, but they still can be difficult to read. In this text, we introduce another metalanguage called a syntax template. Syntax templates show at a glance the form a Visual Basic construct takes.

One final note: Metalanguages show only how to write instructions that the compiler can translate. They do not define what those instructions do (their semantics). Formal languages for defining the semantics of a programming language exit, but they are beyond the scope of this text. Throughout this book, we describe the semantics of Visual Basic in English.

End Sidebar

Let's look at an example. This template defines a Visual Basic identifier, which is Visual Basic's way of naming something in a program:

Click To expand

The shading indicates the part of the definition that is optional. The three dots (...;) mean that the preceding symbol or shaded block can be repeated. So an identifier in Visual Basic is a letter that can be optionally followed by more letters, underscores, or digits.

Remember that a word not in color can be replaced with another template. These are the templates for letter and digit:

Click To expand

In this template, the brackets again indicate lists of items from which any one can be chosen. So a letter can be any one of the upper- or lowercase letters, and a digit can be any of the numeric characters 0 through 9.

Naming Program Elements: Identifiers

As we noted in our discussion of metalanguages, an identifier is used in Visual Basic to name something. For example, an identifier could be the name of a class, a subprogram (called a function in Visual Basic if it returns a value and a subprocedure if it does not return a value), or a place in the computer's memory that holds data (called a field in Visual Basic). Identifiers are made up of a sequence of letters (A-;Z, a-;z), digits (0-;9), and the underscore character (_), and the sequence cannot exceed 255 characters.

Identifier
A name associated with a class library, class, function, or variable and used to refer to them
Function
A subprogram in Visual Basic that returns a value
Subprocedure
A subprogram in Visual Basic that does not return a value
Field
A named place in memory that holds a data value or a reference to an object

Here are some examples of valid identifiers:

sum_of_squares  _J9box_22A  getData  Bin3D4  count  Count

Here are some examples of invalid identifiers and the reasons why they are invalid:

Invalid Identifier | Explanation
40 Hours | Identifiers cannot begin with a digit.
Get Data | Blanks are not allowed in identifiers.
box-22 | The hypen (-) is a math symbol (minus) in Visual Basic.
empty_? | Special symbols such as ? are not allowed.
Integer | The word Integer is predefined in the Visual Basic language.

The last identifier in the table, Integer, is an example of a reserved word. Reserved words are words that have specific uses in Visual Basic; you cannot use them as programmer-defined identifiers. Appendix A lists all of the reserved words in Visual Basic.

Reserved word
A word that has special meaning in Visual Basic; it cannot be used as a programmer-defined identifier

In the following table we have listed some of the programmer-defined identifiers used in the Payroll program in Chapter 1. Notice that we chose the names to convey how the identifiers are used. You can also see that there is a wide range of program elements that Visual Basic allows us to name.

Identifier | How It Is Used
MAX_HOURS | Maximum normal work hours
total | The sum of weekly wages for all employees (total company payroll)
payFile | The output file (where the employee number, pay rate, hours, and wages are written)
Form1 | The name of an object representing a window on the screen
hours | A real number representing hours worked
wages | A variable that holds the result of calculating the wages for an employee
calcPay | A function for computing an employee's wages
empNum | A string of characters indicating the employee ID number
Start Sidebar
Matters of Style
Using Meaningful, Readable Identifiers

The names we use to refer to things in our programs are totally meaningless to the computer. The computer behaves in the same way whether we call the value 3.14159265 pi or cake, as long was we always call it the same thing. However, it is much easier for somebody to figure out how a program works if the names we choose for elements actually tell something about them. Whenever you have to make up a name for something in a program, try to pick one that is meaningful to a person reading the program.

Visual Basic is a case-insensitive language. Uppercase letters are no different from lowercase letters. The identifiers

PRINTTOPPORTION pRiNtToPpOrTiOn printTopPortion PrintTopPortion

are treated as the same names and are completely interchangeable. As you can see, the last two of these forms are easier to read. In this book, we use combinations of uppercase letters, lowercase letters, and underscores in identifiers. Many Visual Basic programmers use different capitalization of identifiers as a way to indicate what they represent. Later in this chapter we show you the conventions that we, and many Visual Basic programmers, use.

End Sidebar

Now that we've seen how to write identifiers, we look at some of the things that Visual Basic allows us to name.


2.2 Data Types

A computer program operates on data (stored internally in memory, stored externally on disk or tape, or input from a keyboard, mouse, scanner, or electrical sensor) and produces output. As we said in Chapter 1, in Visual Basic each piece of data must be of a specific data type. The data type determines how the data is represented in the computer and the kinds of processing the computer can perform on it.

Some types of data are used so frequently that Visual Basic provides them for us. Visual Basic defines several of these standard (or built-in) types. You are already familiar with three of them from everyday life: integer numbers, real numbers, and characters. By the end of Chapter 6 you'll be equally familiar with a fourth type, Boolean.

Standard (built-in) type
A data type that is automatically available for use in every Visual Basic program

Additionally, Visual Basic allows programmers to define their own data types-;programmer-defined (or user-defined) types. In Visual Basic, each of these types is called a class. In this chapter, we focus on the String data type that allows us to work with character data. In Chapter 4, we examine the numeric types (such as int, long, single, and double) in detail.

Start Sidebar
Background Information
Data Storage

Where does a program get the data it needs to operate? Data is stored in the computer's memory. Remember that memory is divided into a large number of separate locations or cells, each of which can hold a piece of data. Each memory location has a unique address we refer to when we store or retrieve data. We can visualize memory as a set of post office boxes, with the box numbers as the addresses used to designate particular locations.

Click To expand

Of course the actual address of each location in memory is a binary number in a machine language code. In Visual Basic we use identifiers to name memory locations; then the compiler and Bytecode interpreter translate them into binary for us. This is one of the advantages of a highlevel programming language: It frees us from having to keep track of the numeric addresses of the memory locations in which our data and instructions are stored.

End Sidebar

The Char Data Type

The built-in type Char describes data consisting of one alphanumeric character-;a letter, a digit, or a special symbol. Visual Basic uses a particular character set, the set of alphanumeric characters it can represent. Visual Basic's character set is called Unicode, and includes characters for writing in many human languages. In this text we use a subset of Unicode that corresponds to an older character set called the American Standard Code for Information Interchange (ASCII). ASCII consists of the alphabet for the English language, plus numbers and symbols. Here are some example values of type Char.

"A"c  "a"c  "8"c  "2"c  "+"c  "-"c  "$"c  "?"c  "*"c  " "c

Notice how each character is enclosed in quotes and has the letter "c" appended to it. Appending "c" to the character is necessary to distinguish the character from another Visual Basic data type, the String. You will learn about strings later in this chapter. Notice also that the blank, " ", is a valid character.

How do we write a double quote itself as a character? If we write " " ", Visual Basic complains that this is a syntax error. The second quote indicates the end of the (empty) character value, and the third quote starts a new character value. Visual Basic provides a special function, the Chr() function, for writing a double quote as a character. The Chr() function converts an ASCII number to the letter, number, or symbol it represents. The ASCII number for the double-quote mark is 34. To assign the quote mark as the value to a Char variable called quote we write:

Dim quote As Char = Chr(34)

Visual Basic provides operations that allow us to compare data values of type Char. The Unicode character set has a collating sequence, a predefined ordering of all the characters. In Unicode, "A" compares less than "B", "B" less than "C", and so forth. And "1" compares less than "2", "2" less than "3", and so on. None of the identifiers in the Payroll program is of type Char.


2.3 Classes and Objects

In Chapter 1 we outlined two phases of programming: the problem-solving phase and the implementation phase. Often the same vocabulary is used in different ways in the two phases. In the problem-solving phase, an object is an entity or some thing that makes sense in the context of the problem being solved. A group of objects with similar properties and behaviors is described by an object class or class for short. Object-oriented problem solving involves isolating the objects that make up the problem. Objects interact with one another by sending messages.

Object (general sense)
An entity or thing that is relevant in the context of a problem
Class (general sense)
A description of the behavior of a group of objects with similar properties and behaviors
Object (Visual Basic)
An instance of a class
Class (Visual Basic construct)
A pattern for an object
Method
A subprogram that defines one aspect of the behavior of a class
Instantiation
Creating an object, an instance of a class

In the implementation phase, a class is a Visual Basic construct that allows the programmer to describe one or more objects. A class contains variables (data values) and methods (subprograms) that define the behavior of the object. Think of a class in the general sense as a pattern for what an object looks like and how it behaves and the Visual Basic class as the construct that allows you to simulate the object in code. If a class is a description of an object, how do we get an object that fits the description? We use an operator called new, which takes the class name and returns an object of the class type. The object that is returned is an instance of the class. The act of creating an object from a class is called instantiation. These definitions provide new meanings for the terms object and class in addition to how we defined them in Chapter 1. Here is the syntax diagram for a class.

Click To expand

A Visual Basic class may optionally begin with a series of import declarations. An object-oriented language such as Visual Basic provides a very large library of classes that are available for us to use in our programs. There are so many of these in Java's library, in fact, that they must be organized into smaller groups called namespaces. Import declarations are statements that tell the Visual Basic compiler which library namespaces we are using in our class. We do not use a Visual Basic namespace in this chapter, so we postpone looking at the syntax for an Imports statement until the next chapter.

Namespace
A named collection of program building blocks or components in Visual Basic that can be imported by a class

The next line may optionally begin with a series of access modifiers, which are then followed by the word Class and an identifier. This line is called the heading of the class. The heading gives the class a name (the identifier) and may optionally specify some general properties of the class (the modifiers). Modifiers are Visual Basic keywords. We have listed two of Visual Basic's modifiers in the syntax diagram that are relevant here: Public and Private. They are called access modifiers because they specify whether elements outside of the class can use the class. What's outside the class? Any of the namespaces that we list in Imports declarations are outside the class. Thus, if we declare an identifier to be Public, we are allowing all imported namespaces to make use of it.

After the heading you see a series of class declarations and the End Class statement. Everything between the class identifier and the End Class statement is the body of the class. The class declarations contain all of the statements that tell the computer what to do. The simplest Visual Basic class we can write would look like this

Class DoNothing
End Class

and as its name implies, it does absolutely nothing. It is a completely empty shell of a class. Our job as programmers is to add useful instructions to this empty shell.

The String Class

A string is a sequence of one or more characters, such as a letter, a word, a name, or a sentence, enclosed in double quotes. In Visual Basic, a string is an object, an instance of the String class. The following are strings in Visual Basic:

"Programming and " "Problem Solving" " with VB.net "   "."

A string must be typed entirely on one line. For example, the string

"This string is invalid because it
is typed on more than one line."

is not valid because it is split across two lines before the closing double quote. In this situation, the Visual Basic compiler issues an error message at the first line. The message may be something like SYNTAX ERROR.

The quotes are not considered to be part of the string but are simply there to distinguish the string from other parts of a Visual Basic program. For example, "amount" (in double quotes) is the character string made up of the letters a, m, o, u, n, t in that order. On the other hand, amount (without the quotes) is an identifier, perhaps the name of a place in memory. The symbols "12345" represent a string made up of the characters 1, 2, 3, 4, 5 in that order. If we write 12345 without the quotes, it is an integer quantity that can be used in calculations.

A string containing no characters is called the empty string. We write the empty string using two double quotes with nothing (not even spaces) between them.

""

The empty string is not equivalent to a string of spaces; it is a special string that contains no characters.

To write a double quote within a string, put two double quotes together, back-to-back. Here is a string that contains two embedded quotation marks.

"She said, ""Don't quote me on that."" "

The value of this string is:

She said, "Don't quote me on that."

Visual Basic provides operations for joining strings, comparing strings, copying portions of strings, changing the case of letters in strings, converting numbers to strings, and converting strings to numbers. We look at some of these operations later in this chapter and cover the remaining operations in subsequent chapters. In the Payroll program, the identifier empNum is of class String, and string values are used directly in several places.


2.4 Defining Terms: Declarations

Declaration
A statement that associates an identifier with a variable, a function, a class, or a namespace so that the programmer can refer to that item by name

How do we tell the computer what an identifier represents? We use a declaration, a statement that associates a name (an identifier) with a description of an element in a Visual Basic program (just as a dictionary definition associates a name with a description of the thing being named). In a declaration, we name an identifier and what it represents. For example, the Payroll program uses the declaration

Dim empNum As String

to announce that empNum is the name of an object whose contents are of the String data type. When we declare a variable, the compiler picks a location in memory to be associated with the identifier. We don't have to know the actual address of the memory location because the computer automatically keeps track of it for us.

Variables and the actual instructions in a program are stored in various memory locations. We use identifiers to refer to variables. We have also seen that a function can be given an identifier. In addition, a name can be associated with a programmer-defined class, which is a collection of variables and functions.

In Visual Basic, you have to declare many of the identifiers before you use them. This allows the compiler to verify that the use of the identifier is consistent with what it was declared to be. If you declare an identifier to be a variable that can hold a String value and later try to store a number into the field, the compiler detects this inconsistency and issues an error message.

Variables

A program operates on data. Data is stored in memory. While a program is executing, different values may be stored in the same memory location at different times. This kind of memory location is called a variable, and its contents are the variable value. The symbolic name that we associate with a memory location is the variable name or the variable identifier.In practice, we often refer to the variable name more briefly as the variable.

Declaring a variable means specifying both its name and its data type or class. This tells the compiler to associate a name with a memory location, and that the values to be stored in that location are of a specific type or class (for example, Char or String). To indicate to the Visual Basic compiler that you are declaring a variable, you issue the command Dim, along with the identifier name and the data type of the variable. The following statement declares myString to be of class String:

Dim myString As String

Notice that the declaration does not specify what value is stored in myString. It only specifies that the name myString can hold a value of class String. At this point, my-String has been reserved as a place in memory but it contains no data. Shortly, we see how to actually put a value into a variable (see Figure 2.1).

Click To expand
Figure 2.1: Variable

By default, Visual Basic is a strongly typed language. This means that a variable can contain a value only of the type or class specified in its declaration. You can turn off Visual Basic's strong typing, but this is not recommended. Because of the above declaration, the variable myString can contain only a String value. If Visual Basic comes across an instruction that tries to store a value of the wrong type, it will indicate an error by marking the value as a syntax error.

Here's the syntax template for a variable declaration:

TypeName is the name of a type or class such as String. Modifiers are Visual Basic reserved words. Different modifiers have different meanings, and we introduce each one and explain its use as the need arises throughout the next several chapters. Notice that the syntax template shows that they are optional. We can declare variables without using any of the modifiers.

From the syntax template, you can also see that it is possible to declare several variables in one statement:

Dim letter, middleInitial, ch As String

Here all three variables are declared to be String variables. Our preference, though, is to declare each variable with a separate statement:

Dim letter As String
Dim middleInitial As String
Dim ch As String

Declaring each variable with a separate statement allows you to attach comments to the right of each declaration; for example:

Dim firstName As String             ' A person's first name
Dim lastName As String              ' A person's last name
Dim title As String                 ' A person's title, for example Dr.
Dim middleInitial As String         ' A person's middle initial
Dim myChar As String                ' A place to store one letter

These declarations tell the compiler to reserve memory space for five String variables. The comments explain what each variable represents to someone reading the program.

In the syntax template for a class, we saw that it contains a set of class declarations, and some of those declarations are variables. For example, here is a class with two variable declarations, also showing how we might use a Private modifier as part of a declaration.

Public Class Sample                 ' The start of a class called Sample
   Private myChar As String         ' A String variable declared in class
                                    ' Sample
   Private myString As String       ' A String type variable declared in class
                                    ' Sample
End Class                           ' The end of class Sample

Constants

All characters or sequences of characters enclosed in double quotes are string constants.

"A"   "@"   "Howdy boys"   "Please enter an employee number: "

In Visual Basic, as in mathematics, a constant is something whose value never changes. When we use the actual value of a constant in a program, we are using a literal value (or literal).

Literal value (literal)
Any constant value written in a program
Named constant (symbolic constant)
A location in memory, referenced by an identifier, that contains a data value that cannot be changed

An alternative to the literal constant is the named constant (or symbolic constant), which is introduced in a declaration statement. A named constant is just another way of representing a literal value. Instead of using the literal value in an instruction, we give the literal value a name in a declaration statement, then use that name in the instruction. For example, we can write an instruction that prints the title of this book using the literal string "Programming and Problem Solving with VB.Net". Or we can declare a named constant called BOOK_TITLE that equals the same string and then use the constant name in the instruction. That is, we can use either

"Programming and Problem Solving with VB.Net"

or

BOOK_TITLE

in the instruction.

Using the literal value of a constant may seem easier than giving it a name and then referring to it by that name. But, in fact, named constants make a program easier to read because they make the meaning of literal constants clearer. And named constants also make it easier to change a program later on.

The syntax template for a constant declaration is similar to the template for a variable declaration:

Click To expand

The only difference is that we must include the modifier Const, a reserved word, and we follow the identifier with an equal sign (=) and the value to be stored in the constant. The Const modifier tells the Visual Basic compiler that this value will be constant throughout the life of the program and cannot be changed.

The following are examples of constant declarations:

Const STARS As String = "********"
Const BLANK As Char = " "
Const BOOK_TITLE As String = "Programming and Problem Solving with VB.Net"
Const MESSAGE As String = "Error condition"

As we have done above, many Visual Basic programmers capitalize the entire identifier of a named constant and separate the English words with an underscore. The idea is to let the reader quickly distinguish between variable names and constant names when they appear in the middle of code.

It's a good idea to add comments to constant declarations as well as variable declarations. For example:

Const STARS As String = "********"   ' Row of stars to use as a separator
Const BLANK As Char = " "            ' A single blank
Start Sidebar
Matters of Style
Capitalization of Identifiers

Programmers often use capitalization as a quick, visual clue to what an identifier represents. Different programmers adopt different conventions for using uppercase letters and lowercase letters. Some people use only lowercase letters, separating the English words in an identifier with the underscore character:

pay_rate  emp_num  pay_file

The convention used by many Visual Basic programmers and the one we use in this book is the following:

  • Variables and methods begin with a lowercase letter and capitalize each successive English word.

    lengthInYards  middleInitial  hours

  • Class names begin with an uppercase letter but are capitalized the same as variable names thereafter.

    PayRollFrame  Sample  MyDataType  String

    Capitalizing the first letter allows a person reading the program to tell at a glance that an identifier represents a class name rather than a variable or method.

  • Identifiers representing named constants are all uppercase with underscores used to separate the English words.

    BOOK_TITLE  OVERTIME  MAX_LENGTH

These conventions are only that-;conventions. Visual Basic does not require this particular style of capitalizing identifiers. You may wish to capitalize in a different fashion. But whatever you use, it is essential that you use a consistent style throughout your program. A person reading your program will be confused or misled if you use a random style of capitalization.

End Sidebar


2.5 Taking Action: Executable Statements

Up to this point, we've looked at ways of declaring variables and constants in a program. Now we turn our attention to ways of acting, or performing operations, on data.

Assignment

Assignment statement
A statement that stores the value of an expression into a variable

The value of a variable can be set or changed through an assignment statement. For example,

lastName = "Lincoln"

assigns the string value "Lincoln" to the variable lastName (that is, stores the sequence of characters "Lincoln" into the memory associated with the variable lastName).

Here's the syntax template for an assignment statement:

Click To expand

The assignment operator (=) means "is set equal to" or "gets"; the variable is set equal to the value of the expression. Any previous value in the variable is destroyed and replaced by the value of the expression.

Expression
An arrangement of identifiers, literals, and operators that can be evaluated to compute a value of a given type

Only one variable can be on the left-hand side of an assignment statement. An assignment statement is not like a math equation (x + y = z + 4); the expression (what is on the right-hand side of the assignment operator) is evaluated, and the resulting value is stored into the single variable on the left of the assignment operator. A variable keeps its assigned value until another statement stores a new value into it.

Evaluate
To compute a new value by performing a specified set of operations on given values

Because you are accustomed to reading from left to right, the way that the assignment operator moves a value from right to left may at first seem awkward. Just remember to read the = as "is set equal to" or "gets" and it seems more natural.

The value assigned to a variable must be of the same type as the variable. Given the declarations

Dim firstName As String
Dim middleName As String
Dim lastName As String
Dim title As String
Dim middleInitial As Char
Dim myChar As Char

the following assignment statements are valid:

firstName = "Abraham"
middleName = firstName
lastName = "Lincoln"
title = "President"
middleInitial = " "c
myChar = "B"c

However, these assignments are not valid:

Invalid Assignment Statement | Reason
middleInitial = "A." | middleInitial is of type Char; "A." is a string.
letter = firstName | letter is of type Char; firstName is of class String.
letter = " " | letter is of type Char; " " is only one character but it is a string because of the double quotes and no appended "c".
firstName = Thomas | Thomas is an undeclared identifier.
"Edison" = lastName | Only a variable can appear to the left of =.
lastName = | The expression to the right of = is missing.

Figure 2.2 shows variable myChar with a letter B stored in it.

Click To expand
Figure 2.2: Variable with value

String Expressions

Although we can't perform arithmetic on strings, Visual Basic provides the String class with a special string operation, called concatenation, that uses the & (ampersand) operator. The result of concatenating (joining) two strings is a new string containing the characters from both strings. For example, given the statements

Dim bookTitle As String
Dim phrase1 As String
Dim phrase2 As String

phrase1 = "Programming and Problem Solving with "
phrase2 = "VB.Net"

we could write

bookTitle = phrase1 & phrase2

which results in bookTitle being set equal to the string

"Programming and Problem Solving with VB.Net"

The order of the strings in the expression determines how they appear in the resulting string. If we instead write

bookTitle = phrase2 + phrase1

then bookTitle is set equal to the string

"VB.NetProgramming and Problem Solving with "

Concatenation works with named String constants and literal strings as well as String variables. For example, if we have declared the following constants:

Const WORD1 As String = "Programming"
Const WORD3 As String = "Problem Solving"
Const WORD5 As String = "VB.Net"

then we could write the following statement to assign the title of this book to the variable bookTitle:

bookTitle = WORD1 & " and " & WORD3 & " with " & WORD5

As a result, bookTitle is assigned the string

"Programming and Problem Solving with VB.Net"

The preceding example demonstrates how we can combine identifiers and literal strings in a concatenation expression. Of course, if we simply want to assign the complete string to bookTitle, we can do so directly:

bookTitle = "Programming and Problem Solving with VB.Net"

But occasionally we need to assign a string literal that is too long to fit on one line and then a series of concatenation expressions are necessary, as in the following statements.

bookTitle = "The Red-Wing Blackbird hovered precariously in the gusty "
bookTitle = bookTitle & "breeze as he tried to display his brilliant red and "
bookTitle = bookTitle & "yellow epaulets to his rival suitor."

The first statement assigns part of the sentence to bookTitle. The second statement retrieves the value of bookTitle from memory, concatenates the second part of the sentence to the first part, and then assigns the new string back to bookTitle. The third statement again retrieves the value of bookTitle from memory, concatenates the remainder of the sentence to the variable, and the new string replaces the old value of bookTitle (which is destroyed).

Concatenation works only with values of class String. However, if we try to concatenate a value of one of Visual Basic's built-in types with a string, Visual Basic automatically converts the value into an equivalent string and performs the concatenation. For example, the code segment:

Dim result As String
result = "The square of 12 is " & 144

assigns the string "The square of 12 is 144" to the variable result. Visual Basic converts the integer literal 144 into string "144" before performing the concatenation.

Fields

The similarity in appearance between variable and constant declarations in Visual Basic is not a coincidence. Visual Basic doesn't actually distinguish between the declaration of named constants and variables because they are just different kinds of fields. A named constant is merely a field that has been given an initial value, together with the modifier Const, which says that the value can never be changed. If we extend the template for a variable declaration to include the syntax necessary to give the variable an initial value, and add the keyword Const to the list of modifiers, then we have a generic template for a field declaration in Visual Basic. Now that we have defined expressions, we generalize the initial value syntax to include expressions.

Click To expand

The following declarations are legal:

Const WORD1 As String = "Programming and "
Const WORD3 As String = "Problem Solving in " & WORD1
Const WORD5 As String = "VB .NET" & WORD3

and store "Design Visual Basic Introduction" as the value for constant WORD5.

Output

Have you ever asked someone, "Do you know what time it is?" only to have the person smile smugly, and say, "Yes, I do!"? This situation is like the one that currently exists between you and the computer. You now know enough Visual Basic syntax to tell the computer to assign values to variables and to concatenate strings, but the computer won't give you the results until you tell it to display them.

Early computers used printers to display their output. Older programming languages thus had output statements, such as print or write that would type the contents of a variable or a constant on the printer. As technology advanced, printers were replaced by display screens, but output was still shown on the screen as if it was being typed by a printer, line by line. In the 1970s, computer scientists at the Xerox Palo Alto Research Center developed a new approach to output in which a program could display separate panels on the screen and print or draw on each panel independently. The panels were called windows, and opened a new era in the design of user interfaces for computer programs.

Today, virtually every computer operating system supports a graphical user interface (GUI) based on windows. Such interfaces make it much easier for people to use programs; however, they require more work on the part of the programmer than did the old-fashioned printer style of output. Because Visual Basic was developed after the GUI became the standard mechanism for interactive input and output, it has built-in features to simplify the programming of a user interface, which we cover in the next chapter. Here we introduce a very simple way of writing messages on the screen.

Visual Basic provides a function for displaying messages on the screen. This function is called MsgBox. For example,

MsgBox("Suzy" & "  " & "Sunshine")

prints

Suzy  Sunshine

in a window on the screen. The function is invoked by calling the function name with the "something" you want to print placed in parentheses beside the function name. This is called the function's parameter.

Each time the MsgBox function is called, a new window is displayed on the screen. Therefore, if you want to display more than one line of text in the same window, you have to add a special character to the output. This special character is called the carriage return/line feed character, and it is represented in Visual Basic with a special constant, vbCrLf. You can put a carriage return/line feed in a string by concatenating it at the place you want to move down to a new line. For example,

Dim name As String
name = "Suzy" & vbCrLf & " " & vbCrLf & "Sunshine"

creates the variable name with a value of:

Suzy

Sunshine

The same technique can be used with the MsgBox function. The following line

MsgBox("Suzy" & vbCrLf & " " & vbCrLf & "Sunshine")

displays the following in a window on the screen:

Suzy

Sunshine

Note that the vbCrLf does not cause a move to the next line until after the string is printed. The second line contains two blanks, it is not the empty string. We can print variables as well as literals.

Dim myName As String = "Suzy Sunshine"
MsgBox(myName)

prints exactly the same thing on the screen as the statement

MsgBox("Suzy Sunshine")

Beyond Minimalism: Adding Comments to a Program

All you need to create a working program is the correct combination of declarations and executable statements. The compiler ignores comments, but they are of enormous help to anyone who must read the program. Comments can appear anywhere in a program except in the middle of an identifier, a reserved word, or a literal constant.

Visual Basic comments come in two forms. The first is any sequence of characters on the same line that begins with the keyword Rem. The compiler ignores anything that follows Rem. Here's an example:

Rem Aircraft identification information follows

The second, and more common, form starts with an apostrophe ( ' ) and extends to the end of that line of the program:

Start Figure
Dim idNumber As String   ' Identification number of the aircraft
Start Figure

The compiler ignores anything after the apostrophe to the end of the line.

Writing fully commented programs is good programming style. A comment should appear at the beginning of a program to explain what the program does:

' This program computes the weight and balance of a Beechcraft
' Starship-1 airplane, given the amount of fuel, number of
' passengers, and weight of luggage in fore and aft storage.
' It assumes that there are two pilots and a standard complement
' of equipment, and that passengers weigh 170 pounds each.

Another good place for comments is in constant and variable declarations, where the comments explain how each identifier is used. In addition, comments should introduce each major step in a long program and should explain anything that is unusual or difficult to read (for example, a lengthy formula).

It is important to make your comments concise and to arrange them in the program so that they are easy to see and it is clear what they refer to. If comments are too long or crowd the statements in the program, they make the program more difficult to read-;just the opposite of what you intended! In this text, as in the Visual Basic editor, we use color to make the comments stand out from the rest of the Visual Basic code in our examples.

Program Construction

We have looked at the basic elements of Visual Basic codes: identifiers, declarations, variables, constants, expressions, assignment statements, function calls, and comments. Now let's see how to collect these elements into a program. A Visual Basic program, usually called an application, is a class containing class declarations: fields and methods, as well as subprocedure declarations. You will learn about Visual Basic subprocedures in the next chapter.

The first class declaration in a Visual Basic program is the declaration for Form1, which is the name of the main window that is displayed when you run a Visual Basic application. Form1 is where execution of a Visual Basic program begins. When you start a new application in Visual Basic, the basic declarations for Form1 are written for you, as shown below:

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form
    Public Sub New()
        MyBase.New
        Form1 = Me
        InitializeComponent()
    End Sub
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub
#Region " Windows Form Designer generated code "
    Private components As System.ComponentModel.Container
    Dim WithEvents Form1 As System.WinForms.Form
    Private Sub InitializeComponent()
        Me.components = New System.ComponentModel.Container()
        Me.Text = "Form1"
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
    End Sub
#End Region
End Class

We will explain all this code in due course. For now, let's just see where we need to put the code we want to write in order to run a program.

For a simple Visual Basic program, we need to know where to put our declarations and where to put our executable statements. Declarations are placed at the top of the Form1 declaration. Here is a code fragment that declares and initializes several variables:

Public Class Form1
    Inherits System.WinForms.Form

    Const FIRST As String = "Herman"
    Const LAST As String = "Herrmann"

    Const MIDDLE As Char = "G"c
    Dim firstLast As String
    Dim lastFirst As String
    Dim sentence1 As String
    Dim sentence2 As String

Executable statements are placed inside the New subprocedure, which is used to initialize Form1.

Public Sub New()
    MyBase.New
    Form1 = Me
    InitializeComponent()
    firstLast = FIRST & " " & LAST
    Msgbox("Name in first-last format is " & firstLast)
    lastfirst = LAST & ", " & FIRST & ", "
    Msgbox("Name in last-first format is " & lastFirst & MIDDLE & ".")
End Sub

Now we can put these pieces together with the rest of the code generated by Visual Basic to complete the program.

'********************************************************
' PrintName program
' This program prints a name in two different formats
'********************************************************
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form
    Const FIRST As String = "Herman"    ' Person's first name
    Const LAST As String = "Herrmann"   ' Person's last name
    Const MIDDLE As Char = "G"c         ' Person's middle initial
    Dim firstLast As String             ' Name in first-last format
    Dim lastFirst As String             ' Name in last-first format
    Dim format1 As String               ' Variable to hold first-last
    Dim format2 As String               ' Variable to hold last-first

Public Sub New()
       MyBase.New
       Form1 = Me
       InitializeComponent()

       ' executable statements follow
       firstLast = FIRST & " " & LAST
       format1 = "Name in first-last format is " & firstLast
       lastfirst = LAST & ", " & FIRST & ", "
       format2 = "Name in last-first format is " & lastFirst & MIDDLE & "."
       MsgBox(format1 & Chr(13) & Chr(10) & format2,,"PrintName")

End Sub

     Public Overrides Sub Dispose()
         MyBase.Dispose()
         components.Dispose()
     End Sub

#Region " Windows Form Designer generated code "
    Private components As System.ComponentModel.Container
    Dim WithEvents Form1 As System.WinForms.Form
    Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
        Me.Text = "Form1"
    End Sub
#End Region

End Class

The program begins with a comment that explains what the program does. Next comes a set of Imports statements. These statements are necessary because much of the code for handling Windows programming is contained in other classes that are part of the Visual Basic environment. To have access to these classes, we have to import them into our program. The Imports statement performs this task. Next comes the class heading for Form1. This class has to be Public in order to be executed. A class that is made Public can be accessed by any other class that is part of the application. In later chapters we see situations where we want to limit access to a field, function, or class. We do this by using the modifier Private. If we used the Private modifier with Form1, then the class would be invisible to the Visual Basic compiler and it wouldn't know where to start.

The class heading is followed by an Inherits statement. To enable Form1 to have all the features of a Windows form, we use a technique called inheritance. Inheritance will be discussed in detail in Chapter 7. For now, it is enough to know that a Windows form needs the properties and functions that are part of the class Systems.Winform.Form, which is why we use the Inherits statement.

Next are a set of declarations for three constants (FIRST, LAST, and MIDDLE) and two String variables (firstLast and lastFirst). Following these declarations is a subprocedure that initializes Form1 and contains the executable statements that make up the part of our program that displays a name in two ways. These statements follow the comment 'Executable statements follow. The other statements in the subprocedure and in the remainder of the program will be covered in the next chapter.

Note that neither the Public or Private modifier precedes the variables declared within Form1. The reason is that fields declared inside the body of a class are by default Public and do not need to be declared with a modifier. If we wanted these variables to be accessible only by the class, the variables would be declared as Private.

Notice how we use spacing in the PrintName program to make it easy for someone to read. We use blank lines to separate statements into related groups, and we indent the entire body of the Form1 class and the subprocedures. The compiler doesn't require us to format the program this way; we do so only to make it more readable. We have more to say in the next chapter about formatting a program.

Here is what the program displays on the screen when it executes:

Click To expand

Statement Sequence

The body of a subprocedure is an example of a statement sequence. A statement sequence is just a sequence of one or more statements delimited by a program construct, function, or a subprocedure. Now we can define a subprocedure declaration as a heading followed by a statement sequence:

Click To expand

In later chapters we define the syntax of Heading in greater detail. In the case of the New subprocedure, Heading is simply Public Sub New(). Here is the syntax template for a statement, limited to Visual Basic statements discussed in this chapter:

Click To expand

A statement can be empty (the null statement). The null statement is just a blank line. It does absolutely nothing at execution time; execution just proceeds to the next statement. It is not used often.

As the syntax template shows, a statement also can be a local field declaration, an executable statement, or even a statement sequence. The latter means you can use an entire statement sequence wherever a single statement is allowed. In later chapters where we introduce the syntax for branching and looping structures, this fact is very important.

Finally, according to the syntax template for a statement, a field declaration is officially considered to be a statement. A declaration, therefore, can appear wherever an executable statement can. In a statement sequence, we can mix local declarations and executable statements if we wish. However, a declaration must come before it is used.

Dim ch As Char        ' Declaration
ch = "A"c
MsgBox(ch)
Dim str As String     ' Declaration
str = "Hello"
MsgBox(str)

In this book we group the declarations together because we think it is easier to read, and is therefore better style.

' Declarations
Dim ch As Char
Dim str As String
'Executable statements
ch = "A"c
MsgBox(ch)
str = "Hello"
MsgBox(str)


2.6 Program Entry, Correction, and Execution-;Using the Visual Studio Integrated Development Environment

Once you have a program on paper, you must enter it on the keyboard in order for the computer to run the program. Visual Basic programs are entered using a special program called the Visual Studio Integrated Development Environment, also called the Visual Studio IDE, or just the IDE. The IDE is used for entering programs, running programs, and managing applications. Throughout the book we will show you the details of how to use the IDE. In this section, we will give you an overview of loading the IDE and the major features of the IDE.

We begin our tour of Visual Basic by describing briefly the different elements of the Visual Studio IDE. The IDE looks like Figure 2.3 when it is first loaded. The elements of the IDE include the Form Window for designing the graphical user interface (GUI) of your program, the Solution Explorer for keeping track of the files that make up an application, the toolbox holding the controls you place on the GUI, and the Properties window, where you can set and change different attributes of the GUI. The next sections discuss these elements in more detail.

Click To expand
Figure 2.3: The Visual Studio Integrated Development Environment (IDE)

The Title Bar

The top element of the IDE is the title bar (see Figure 2.4), which serves the same purpose in Visual Studio as it does in other Windows applications. The title bar displays the name of the program (WindowsApplication7), the name of the application creating the program (Microsoft Visual Basic.NET), the current mode of the application (design), the name of the form being modified (Form1.vb), and the current mode of the form (Design) in the shaded blue bar. There are buttons for minimizing, maximizing, and closing the application in the top-right corner of the title bar, and the control menu is in the top-left corner.

Click To expand
Figure 2.4: The Visual Studio title bar and menu bar

The Menu Bar

Right below the title bar is the menu bar, again functioning in the same way in Visual Studio as menu bars do in other Windows applications. The first selection in the bar is the File menu, which is used to open, save, and close files, as well as to exit from Visual Studio. The Edit menu includes commands for cutting, copying, and pasting text.

Each menu on the menu bar has a shortcut command associated with it. Notice that each menu item has an underlined letter. That letter and the Alt key make up the shortcut command (also called the Access key) for that command. For example, to access the File menu, you can either select it with the mouse or press the Alt key and the F key at the same time.

The Toolbar

Visual Studio provides a toolbar that includes icon representations of some of the more common commands from the menu bar. Being able to click an icon to initiate a command will possibly save you several steps if you instead initiate the command from the menu bar. When you put your mouse over these icons, a tool tip pops up identifying the icon. The toolbar is shown in Figure 2.5.

Click To expand
Figure 2.5: The Visual Studio toolbar

Commands you can initiate from the toolbar include loading a new project, saving a file, cutting, copying, and pasting text, search, and one command that is not found in the menu bar-;Start. The Start command is used to run a Visual Basic application. We will be using this command a lot in the coming chapters.

The Toolbox

On the left-hand side of the IDE is a window that contains several labeled icons called the toolbox. The toolbox contains the tools (or controls) that you use to create the user interface for your Visual Basic application. Each control is a graphical element of the interface, such as a textbox, a button, or a label. As you can see in Figure 2.6, there are many more controls than just the ones mentioned above, but these three are the most common.

Click To expand
Figure 2.6: The Visual Studio toolbox

Controls are placed on a form by clicking the control in the toolbox and then clicking the form. You can also put a control on a form by clicking the control and then dragging it to the form. Either way, the toolbox makes it quite easy to build user interfaces in Visual Basic.

The Form Window

The Form window (see Figure 2.7) is located in the middle of the IDE, a rectangular grid of black dots. You use this element to create a user interface for a Visual Basic application. The controls found in the toolbox are placed on the form. You can use the dots to help align your controls.

Click To expand
Figure 2.7: The Visual Studio Form window

A form can be adjusted to be just about any size you want by dragging any of the corners and/or sides of the form. You can also make a form cover the whole screen if you so desire by clicking the maximize button on the form.

The Solution Explorer Window

To the right of the Form window, and just below the Toolbar, is the Solution Explorer window, as seen in Figure 2.8. This window displays all of the files that make up your Visual Basic application. Each application you design can have several different types of files, from form files that contain different user interfaces, to references to other files that import code into your application.

Click To expand
Figure 2.8: The Visual Studio Solution Explorer window

The files shown in this window are organized by type. There can be many reference files in an application, so these files are not all shown in the window. However, you can click on the plus sign beside the word Reference to expand the list of reference files. A file that contains the application you are currently designing is shown with its default name-;Form1.vb. You can and should change this to a meaningful name.

The Properties Window

The user interfaces of the applications you design in Visual Basic are made up of forms and controls that are placed on forms. Forms and controls are pre-built objects, each of which has a set of attributes that define the object. For example, a form has properties that define its height, its width, its name, and other properties of a form. These properties are displayed in the IDE in the Properties window. The Properties window is shown in Figure 2.9.

Click To expand
Figure 2.9: The Visual Studio Properties window

The first thing you see in the Properties window is the name of the object selected. The name is in a drop-down combo box, and if you want to view and/or change the properties of another object, you can select the object from this combo box. Below the object name is the list of attributes, displayed in Name-Value columns. Not all the properties can usually be displayed in the Properties window, so the window has a scrollbar you can use to move up and down through the window.

The Code Window

A lot of Visual Basic programming involves placing controls on forms and setting properties for the controls and other objects, but only the most trivial Visual Basic programs do not involve writing program code. To write code for your application, there is a window for entering code called the Code window (see Figure 2.10).

Click To expand
Figure 2.10: The Visual Studio Code window

The Code window is selected from the menu bar or by double-clicking on an object that is programmable. You can relocate the Code window so that it sits side-by-side with the Form window. You can also resize it to display more code.

When you first view the Code window, you will notice that several lines of code have been entered into the window. This code includes a template for the Form1 class, as well as code that Visual Studio generates automatically for generating the user interface. This code is located in an area called a region. The IDE allows you to expand and contract regions in order to cut down on the amount of code in the window.

Designing Your First Visual Basic Program

Now that you know what the different parts of the Visual Studio IDE are, let's see how to use them to design a simple Visual Basic program. The classic first program is to display "Hello, world!" on the screen, and we don't want to go against such a rich tradition! The first step is to load Visual Basic, which means you start Microsoft Visual Studio.NET from the Windows Start menu.

You will be taken to the Visual Studio Start page (see Figure 2.11). This page provides you a list of projects you've worked on recently, plus links to starting a new project (a program) or to load an existing project that isn't in the "recently visited" list. For this example, select Create a New Project.

Click To expand
Figure 2.11: The Visual Studio Start page

From the Start page you go to the New Project dialog box (Figure 2.12). This dialog box allows you to choose the type of Visual Basic project you want to build. The default selection is Windows Application and that is the selection we will make now. Later in the book we will discuss some of the other project types. Click the OK button to go to the Visual Studio IDE.

Click To expand
Figure 2.12: The Visual Studio New Project dialog box

Displaying "Hello, world!" in a Message Box The simplest way to display data in a window is to display it in a message box. A message box is a window that displays on the Windows desktop. We can display data in a message box by accessing the Show method of the MessageBox class. Don't worry for now about what all this means. For now, it's enough to know that when you want to display data in a window, not on a form, you can use this technique.

To use the MessageBox class, we have to go to the Code window, where we will write the code that calls the class. To get to the Code window, right-click your mouse with the cursor anywhere in the IDE. Select View Code from the floating menu; this allows you to go to the Code window.

Once in the Code window, you need to open the region labeled Windows Form Designer generated code. This area is where Visual Studio writes the necessary code to display a form in Windows, and it is also the place where you can begin to place your own code. To add our code to display "Hello, world!", look for the line of code that reads:

InitializeComponent()

Right below this line, begin writing the following line of code:

Messagebox.Show("Hello, world!")

You probably noticed that as soon as you wrote Messagebox., the editor displayed a box with two choices in it. When you are working with an object that the editor recognizes, like the Messagebox class, the editor runs a program called Intellisense that presents you with the possible choices for what you can type next. An example of how Intellisense works is shown in Figure 2.13. When Intellisense presents your choices, you can either type in the selection to finish the statement or you can choose the selection with your mouse and/or keyboard.

Click To expand
Figure 2.13: Using Intellisense to enter a command

Since all we want to do in this example is display the message "Hello, world!", we are almost ready to run the program. However, because we are using a Messagebox object to display the message, we don't need to display the form that is part of our program. To stop the form from displaying, we can issue the End statement, which automatically stops a Visual Basic program when the statement is executed.

Here, then are the two lines of code we need to display "Hello, world!":

Messagebox.Show("Hello, world!")
End

To run the program, press the blue arrow that is pointing to the right in the toolbar. Your output will look like Figure 2.14.

Click To expand
Figure 2.14: "Hello, world!" in Visual Basic.NET

Displaying "Hello, World!" on a Visual Basic Form While the Messagebox object allows you to easily display messages in a pop-up message box, it is more common in Visual Basic to display data and other information on a form. In this section we will show you how to display message in a label (which is a control used for displaying text) using a command button.

The first step is to add a label control to the form. The easiest way to do this is to double-click on the label control in the toolbox. This places the label in the upper-left corner of the form. You can then drag the label to where you want it on the form. For this example, we'll place the label approximately in the upper-center of the form. Click on the Display control to give it focus. Scroll down the Properties window until you find the Location property. In the right-hand box, type in the following coordinates: 72,80.

We need to set the size of the label and the size of the text font so that the message is readable. For the font, we'll use the default font, Microsoft Sans Serif, we'll choose 12 point for the font size, and we'll make the font bold. In the Properties window again, click on the right column of the Font property. A dialog box pops up and you can make your changes (see Figure 2.15). Click the OK button to close the dialog box.

Click To expand
Figure 2.15: The Font properties dialog box

The size of the label needs to be 144 by 32, so go to the Size property and type 144,32 into the right column.

Now we're ready to add a button to the form. When the user pushes the button, the message "Hello, world!" will appear in the label. This is an example of event-driven programming because the event we want to happen (displaying "Hello, world!") only occurs when the user presses the button with the mouse.

Double-click a button control on the toolbox to place it on the form. Click on the button to give it focus and move to its Property window. The font for the button is the same as for the label, so set its Font property appropriately. The size of the button is 96 by 32, and the location of the button is 96, 144. Set the Size and Location properties and we're ready to move to writing the code to display the message.

Displaying a message in a label involves nothing more than setting the label's Text property to the message we want to display. We want this to occur when we click the button, so we need to put the code that sets the label's Text property to "Hello, world!" in the button's Click event. To do this, double-click the button on the form and you are taken to the button's Click event in the Code window. A template for the event is already there and all you have to do is add the code inside the template. The template looks like this:

Private Sub btnDisplay_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                             Handles btnDisplay.Click
End Sub

Actually, the first three lines are all on one line in the Code window, but we've spaced them out a little to make everything easier to read. Visual Basic is a line-oriented language, which means that normally, a complete statement can only be written on a single line. However, this can make for some very long lines, so the language provides a special character called the line-continuation character ( _ ), the underscore, to enable you to spread a single line out over several lines. Simply put an underscore where you want to break up a line. Be sure to leave a space before typing the underscore or the compiler won't recognize it.

The code for setting the label's Text property is quite simple. Here it is:

lblDisplay.Text = "Hello, world!"

We will discuss later what is going on with this line of code, but for now just type it into the template for the Click event so that the template looks like this:

Private Sub btnDisplay_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                             Handles btnDisplay.Click

       lblDisplay.Text = "Hello, world!"
End Sub

The last thing we need to do is put a caption on the form so that we can readily identify the program. Go back to Design view, click on the form, move to the Properties window and find the Text property. Set the property to "Hello, world!".

Now we're ready to run the program. Click on the blue arrow to launch the program. The form should appear on your desktop looking like the form in Figure 2.16. Then click the Display button. The message "Hello, world!" should appear in the label, as shown in Figure 2.17.

Click To expand
Figure 2.16: The Hello, World! program on start-up
Click To expand
Figure 2.17: Pressing the Display button displays the message

To exit the program, click the Close button in the right corner of the form. You have now successfully written an event-driven Visual Basic program.

Start Sidebar
Problem-Solving Case Study
Display a Date in Multiple Formats

Problem You are beginning to work on a program that needs to output dates in several formats. As a start, you decide to write a short Visual Basic application that takes a single date and displays it in the different formats, so you can be certain that all of your string expressions are correct.

Output A window showing the date in four formats:

Month day, year (August 18, 2001)
day Month year (18 August 2001)
mm/dd/yyyy (8/18/2001)
dd/mm/yyyy (18/8/2001)

Discussion You could easily just type the dates in the four formats as string literals in the program, but the purpose of this exercise is to develop and test the string expressions you need for the larger program.

Because you plan to eventually use the same expressions in another program, you decide that this preliminary program should start off with a set of named string constants containing the parts of a date. Then the program can use concatenation expressions to form string variables in the different date formats. In that way, all the date strings can be created before the output statements are executed.

A date consists of three parts, the month, day, and year, but you need to write the month both by name and by number so there must be a total of four string constants. Here is the algorithmic solution:

...........................................................................
Define Constants

 MONTH_NAME = "August"
 MONTH_NUMBER = "8"
 DAY = "18"
 YEAR = "2001"

...........................................................................
Create First Format

 Set first to MONTH_NAME & " " & DAY & ", " & YEAR

...........................................................................
Create Second Format

 Set second to DAY & " " & MONTH_NAME & " " & YEAR

...........................................................................
Create Third Format

 Set third to MONTH_NUMBER & "/" & DAY + "/" & YEAR

...........................................................................
Create Fourth Format

 Set fourth to DAY & "/" & MONTH_NUMBER & "/" + YEAR

...........................................................................
Display the Dates

 Print first
 Print second
 Print third
 Print fourth

From the algorithm, we can create tables of constants and variables that help us write the declarations in the program.

Constants
Name | Value | Description
MONTH_NAME | "August" | The name of the month
MONTH_NUMBER | "8" | The number of the month
DAY | "18" | The day of the month
YEAR | "2001" | The four-digit year number
Variables
Name | Data Type | Description
first | String | Date in Month day, year format
second | String | Date in day Month year format
third | String | Date in mm/dd/yyyy format
fourth | String | Date in dd/mm/yyyy format

Now we're ready to write the program. Let's call it DateFormats. We can take the declarations from the tables and create the executable statements from the algorithm. We also include comments as necessary.

Here is the program, which is all contained within the New() subprocedure:

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form
    ' ***********************************************************
    ' DateFormats Program
    ' This program prints a date in four different formats by
    ' building strings up from the component parts of the date
    ' using string concatenation expressions
    ' ***********************************************************
    Public Sub New()
        MyBase.New
        Form1 = Me

        InitializeComponent()
        Const MONTH_NAME As String = "August"  ' The month name
        Const MONTH_NUMBER As String = "8"     ' The month number
        Const DAY As String = "18"             ' The month day
        Const YEAR As String = "2001"          ' The four-digit year

        Dim first As String                    ' Date in Month day, year
                                               ' format
        Dim second As String                   ' Date in day Month year
                                               ' format
        Dim third As String                    ' Date in mm/dd/yyyy format
        Dim fourth As String                   ' Date in dd/mm/yyyy format
        Dim vbcrlf As String

        vbcrlf = chr(13) & chr(10)             ' Prints a newline

        ' Create Month day, year format
        first = MONTH_NAME & " " & DAY & ", " & YEAR

        ' Create day Month year format
        second = DAY & " " & MONTH_NAME & " " & YEAR

        ' Create mm/dd/yyyy format
        third = MONTH_NUMBER & "/" & DAY & "/" & YEAR

        ' Create dd/mm/yyyy format
        fourth = DAY & "/" & MONTH_NUMBER & "/" & YEAR

        ' Print the dates on the screen
        MsgBox(first & vbcrlf & second & vbcrlf & third & _
               fourth, , "DateFormats")
    End Sub

    ' Form overrides dispose to clean up the component list
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub

#Region " Windows Form Designer generated code "

End Class

The output from the program is

Click To expand
End Sidebar


Testing and Debugging

  1. Every identifier that isn't a Visual Basic reserved word must be declared. If you use a name that hasn't been declared, you get an error message.

  2. If you try to declare an identifier that is the same as a reserved word in Visual Basic, you get an error message from the compiler. See Appendix A for a list of reserved words.

  3. Visual Basic is a case-insensitive language. Two identifiers that are capitalized differently are treated as the same identifier. All Visual Basic reserved words have their first letter capitalized by the editor.

  4. Check for mismatched quotes in Char and String literals. Both Char and String literals start and end with double quotes. Be sure to append the letter "c" to a Char to differentiate it from a string.

  5. To use a double quote within a literal string, use two double quotes "" in a row. If you use just a double quote, it ends the string, and the compiler then sees the remainder of the string as an error.

  6. In an assignment statement, be sure that the identifier to the left of = is a variable and not a named constant.

  7. In assigning a value to a String variable, the expression to the right of = must be a String expression or a literal string.

  8. In a concatenation expression, at least one of the two operands of & must be of class String.

  9. The filename that holds a program must end with the extension .vb.


Summary of Classes

Programming with an object-oriented language depends heavily on the use of class types from the language's standard library. As we progress through this book we steadily introduce you to new classes in the Visual Basic library. In this section we summarize all of the classes and methods that a chapter has added to your repertoire. The summary is presented as a table, organized hierarchically. The major divisions of the table are the namespaces containing the classes (the name you need to include in an Imports declaration). Within a namespace we list the classes and what the class provides.

Namespace Name System
Class Name | Comments
ComponentModel | Called to implement the Dispose method
Drawing | Provides basic graphics functionality
WinForms | Base class for Form1 class
String |


Summary

The syntax (grammar) of the Visual Basic language is defined by a metalanguage. In this text, we use a form of metalanguage called syntax templates. We describe the semantics (meaning) of Visual Basic statements in English.

Identifiers are used in Visual Basic to name things. Some identifiers, called reserved words, have predefined meanings in the language; others are created by the programmer. The identifiers you invent are restricted to those not reserved by the Visual Basic language. Reserved words are listed in Appendix A.

Identifiers are associated with memory locations by declarations. A declaration may give a name to a location whose value does not change (a constant) or to one whose value can change (a variable). Every constant and variable has an associated data type or class. Visual Basic provides many predefined data types and classes. In this chapter we have examined the type Char and the String class. A class contains fields and methods that describe the behavior of an object. An object is an instance of the class that describes it.

The assignment operator is used to change the value of a variable by assigning it the value of an expression. At execution time, the expression is evaluated and the result is stored in the variable. With the String class, the ampersand (&) is an operator that concatenates two strings. A string expression can concatenate any number of strings to form a new String value.

Simple output to the screen is accomplished by using the MsgBox function that is provided in Visual Basic. MsgBox("A string") prints whatever is between the parentheses on the screen.

A Visual Basic application program is a class containing one or more class declarations, which are fields and methods. The first class must be named Form1 for a graphical Visual Basic application. Execution of a program class always begins with the New subprocedure.


Quick Check

Q:

1. Every Visual Basic program consists of at least how many subprocedures? (p. 50)

1. A program must have at least one subprocedure-;the Form1 subprocedure.

Q:

2. Use the following syntax template to decide whether your last name is a valid Visual Basic identifier. (p. 48)

Click To expand

2. Unless your last name is hyphenated, it probably is a valid Visual Basic identifier.

Q:

3. Write a Visual Basic constant identifier that gives the name ZED to the value "Z". (pp. 59-;61)

3. Const ZED As Char = Z c

Q:

4. Which of the following words are reserved words in Visual Basic? (Hint: Look in Appendix B.)

Public  Static  Final  pi  Single  Integer  sqrt

4. Public, Static, Single, Integer

Q:

5. Declare a Char variable named letter and a String variable named street. (pp.57-61)

5. Dim letter As Char Dim street As String

Q:

6. Assign the value "Elm" to the String variable street. (pp. 58-;59)

6. street = Elm

Q:

7. Write an output statement that prints the title of this book (Programming and Problem Solving with VB.Net) on the screen. (pp. 66-;68)

7. MsgBox( Programming and Problem Solving with VB.Net )

Q:

8. What does the following code segment output on the screen?

Dim str As String
str = "Abraham"
MsgBox("The answer is " & str & " Lincoln")

8. The answer is AbrahamLincoln

Q:

9. The following program code is incorrect. Rewrite it, using correct syntax for the comment.

Dim address As String   " Employee's street address
                        " including apartment

9. Dim address As String ' Employee's street address ' Including apartment

Q:

10. Fill in the blanks in this program.

  _____ Class Form1
     Inherits System.WinForms.Form

                    Public Sub ______
                        MyBase.New
                        Form1 = Me
                        ' This call is required by the Win Form Designer.
                        InitializeComponent
                        ______ TITLE As String = "Mr"
                        Dim guest1 As String
                        Dim guest2 As String
                        Dim message As String
                        guest1 ______ TITLE & " " & "Jones"
                        guest2 ______ TITLE & "s. " & "Smith"
                        message = "The guests in attendance were "
                        message = message ______ guest1 & " and " & guest2
                        MsgBox(______)

                    End Sub
                End Class
(pp. 68-;69)

Q:

11. Show precisely the output produced by running the program in Question 10 above. (pp. 68-;72)

11. The guests in attendance were Mr. Jones and Mrs. Smith

Answers

A:

1. A program must have at least one subprocedure-;the Form1 subprocedure.

A:

2. Unless your last name is hyphenated, it probably is a valid Visual Basic identifier.

A:

3. Const ZED As Char = "Z"c

A:

4. Public, Static, Single, Integer

A:

5.

Dim letter As Char
Dim street As String

A:

6. street = "Elm"

A:

7. MsgBox("Programming and Problem Solving with VB.Net")

A:

8. The answer is AbrahamLincoln

A:

9.

Dim address As String   ' Employee's street address
                        ' Including apartment

A:

10.

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form

    Public Sub New()
        MyBase.New
        Form1 = Me
        ' This call is required by the Win Form Designer.
        InitializeComponent
        Dim TITLE As String = "Mr"
        Dim guest1 As String
        Dim guest2 As String
        Dim message As String
        guest1 = TITLE & ". " & "Jones"
        guest2 = TITLE & "s. " & "Smith"
        message = "The guests in attendance were "
        message = message & guest1 & " and " & guest2
        MsgBox(message)

    End Sub
End Class

A:

11. The guests in attendance were Mr. Jones and Mrs. Smith


Exam Preparation Exercises

    Chapter 2

  1. Mark the following identifiers either valid or invalid.

    Valid

    Invalid

    1. item#1

    2. data

    3. y _____

    4. 3Set

    5. PAY_DAY

    6. bin-2

    7. num5

    8. Sq Ft

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

  2. Given the four syntax templates:

    Click To expand

    mark the following "Dwits" either valid or invalid.

    Valid

    Invalid

    1. XYZ

    2. 123

    3. X1

    4. 23Y

    5. XY12

    6. Y2Y

    7. ZY2

    8. XY23X1

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

  3. Match each of the following terms with the correct definition (1 through 15) given below. There is only one correct definition of each term.

    1. _____ program

    2. _____ algorithm

    3. _____ compiler

    4. _____ identifier

    5. _____ compilation phase

    6. _____ execution phase

    7. _____ variable

    8. _____ constant

    9. _____ memory

    10. _____ syntax

    11. _____ semantics

    12. _____ block

    1. A symbolic name made up of letters, digits, underscores, and dollar signs but not beginning with a digit

    2. A place in memory where a data value that cannot be changed is stored

    3. A program that takes a program in a high-level language and translates it into machine code or Bytecode

    4. An input device

    5. The time spent planning a program

    6. Grammar rules

    7. A sequence of statements enclosed in brackets

    8. Meaning

    9. A program that translates assembly language instructions into machine code

    10. When the compiled version of a program is being run

    11. A place in memory where a data value that can be changed is stored

    12. When a program in a high-level language is converted into machine-code or Bytecode

    13. A part of the computer that can hold both program and data

    14. Instructions for solving a problem in a finite amount of time with a finite amount of data

    15. Data type specifications and instructions used by a computer to solve a problem

  4. Which of the following are reserved words and which are programmer-defined identifiers?

    Reserved

    Programmer-defined

    1. Char

    2. sort

    3. Integer

    4. New

    5. Public

    • _____

    • _____

    • _____

    • _____

    • _____

    • __________

    • __________

    • __________

    • __________

    • __________

  5. Reserved words can be used as variable names. (True or False?)

  6. In a Visual Basic program containing just one subprocedure, that method can be named either form1 or Form1. (True or False?)

  7. If s1 and s2 are String variables containing "blue" and "bird", respectively, what does each of the following statements print?

    1. MsgBox("s1 = " & s1 & "s2 = " & s2)

    2. MsgBox("Result:" & s1 & s2)

    3. MsgBox("Result: " & s1 & s2)

    4. MsgBox("Result: " & s1 & " " & s2)

  8. Show precisely what is output by the following statement.

    MsgBox("A rolling" & _
      "stone" & _
      "gathers" & _
      "no" & _
      "moss")

  9. How many characters can be stored into a variable of type Char?

  10. How many characters are in the empty string?

  11. A variable of class String can be assigned to a variable of type Char. (True or False?)

  12. A literal string can be assigned to a variable of class String. (True or False?)

  13. What is the difference between the literal string "computer" and the identifier computer?

  14. What is output by the following code segment? (All variables are of class String.)

    street = "Elm St."
address = "1425B"
city = "Amaryllis"
state = "Iowa"
streetAddress = address & " " & street
MsgBox(streetAddress)
MsgBox(city)
MsgBox(". " & state)

  15. Correct the following program segment so that it displays "Martin Luther King Jr."

    Const FIRST As String : "Martin"
Const MID As String : "Luther"
Const LAST As String : King

Dim name As String
Dim initial As character
name = Martin & Luther & King
initial = MID
LAST = "King Jr."
MsgBox("Name = " & name)
MsgBox(mid


Programming Warm-Up Exercises

    Chapter 2

  1. Write the output statement that prints your name.

  2. Write three consecutive output statements that print the following three lines.

    The moon
is
blue.

  3. Write declaration statements to declare three variables of class String and two variables of type Char. The String variables should be named make, model, and color. The Char variables should be named plateType and classification.

  4. Write a series of output statements that display the values in the variables declared in Exercise 3. Each value should be preceded by an identifying message.

  5. Change the PrintName program (pages 69-71) so that it prints the name in the format

    First-name Middle-initial. Last-name

    Make MIDDLE a String constant rather than a char constant. Define a new String variable to hold the name in the new format. Use the existing named constants, any literal strings that are needed for punctuation and spacing, and concatenation operations to create the new string. Print the string, labeled appropriately.

  6. Print the following groups of text.

    1. Four score
      and seven years ago

    2. Four score
      and seven
      years ago

    3. Four score
      and
      seven
      years ago

    4. Four
      score
      and
      seven
      years
      ago

  7. Enter the following program segment into a new Visual Basic application and run it. Be sure to type it exactly as it appears here.

     ' ************************************************
 ' HelloWorld program
 ' This program displays two simple messages
 ' ************************************************

 Imports System.ComponentModel
 Imports System.Drawing
 Imports System.WinForms

 Public Class Form1
     Inherits System.WinForms.Form
     Const MSG1 As String = "Hello world."

     Dim msg2 As String

     Public Sub New()
         MyBase.New
         Form1 = Me
         ' This call is required by the Win Form Designer.
         InitializeComponent
         MsgBox(MSG1)
         msg2 = MSG1 & " " & MSG1 & " " & MSG1
         MsgBox(msg2)

     End Sub
 End Class


Programming Problems

  1. Write a Visual Basic application that displays a series of Haiku poems. A Haiku poem is written in three phrases. The first phrase has five syllables, the second has seven syllables, and the last phrase again has five syllables. For example:

    Bright flash then silence
    My expensive computer
    Has gone to heaven

    Your application should define string constants with four phrases of five syllables and two phrases of seven syllables. Use string concatenation expressions to construct every possible 5-7-5 permutation of these phrases and display each one. Do not use the same phrase twice in any poem. See if you can create phrases that make sense together in every permutation. Be sure to include appropriate comments in your program, choose meaningful identifiers, and use indentation as we do in the programs in this chapter.

  2. Write an application that simulates the child's game "My Grandmother's Trunk." In this game, the players sit in a circle, and the first player names something that goes in the trunk: "In my grandmother's trunk, I packed a pencil." The next player restates the sentence and adds something new to the trunk: "In my grandmother's trunk, I packed a pencil and a red ball." Each player in turn adds something to the trunk, attempting to keep track of all the items that are already there.

    Your application should simulate just five turns in the game. Starting with the empty string, simulate each player's turn by concatenating a new word or phrase to the existing string, and print the result.

  3. Write a program that prints its own grading form. The program should output the name and number of the class, the name and number of the programming assignment, your name and student number, and labeled spaces for scores reflecting correctness, quality of style, late deduction, and overall score. An example of such a form is the following:

    CS-101 Introduction to Programming and Problem Solving

Programming Assignment 1

Sally A. Student       ID Number 431023877

Grade Summary:

Program Correctness:   Quality of Style:

Late Deduction:        Overall Score:

Comments:


Case Study Follow-Up Exercises

    Chapter 2

  1. Change the DateFormats program so that the four formats are shown in the opposite order on the screen.

  2. In the DateFormats program, explain what takes place in the statement that assigns a value to the string variable third.

  3. Change the DateFormats program so that the third and fourth formats use hyphens (-) instead of slashes (/).

  4. Change the DateFormats program so that your birth date is displayed in the four formats.


Chapter 3: Event-Driven Output

Overview

Goals

  • To be able to display a form on the screen.

  • To be able to construct a code segment that displays a message in a form on the screen.

  • To be able to invoke a function.

  • To be able to understand how a constructor instantiates an object.

  • To be able to determine what is displayed by a given code segment.

  • To be able to construct a code segment that responds to a window-closing event.

In the last chapter, we looked at the components that make up a program: classes, fields, and methods. We described the function that Visual Basic provides for writing simple information on the screen (MsgBox). In this chapter we look at two fundamental capabilities that Visual Basic provides. The first is the ability to create a window on the screen and display information in the window, and the second is the ability to recognize and write code that responds to an asynchronous event-the closing of a window.


3.1 Classes and Methods

In Chapter 1, we defined an interface as a connecting link at a shared boundary that allows independent systems to meet and act on or communicate with each other. In this chapter we use an interface: The independent systems are the computer and you are the programmer. The shared boundary is the screen, and the link is the set of capabilities that Visual Basic provides for writing information on the screen.

Visual Basic supports many different classes of windows and interface components. In fact, entire books have been written just to explain all of the features that Visual Basic supports for output. In this text, we use a very simple subset of Visual Basic capabilities. Once you thoroughly understand the basics, you can easily learn about Visual Basic's other interface features on your own.

Forms

The type of window we use in this text is called a form. It has all of the basic features that you are used to seeing in a window on a personal computer: the ability to change size, to be closed, to be turned into an icon (a small pictorial representation of the window), and so on. Our programs won't support all of these features. When you try to click on one of them, the window may simply do nothing in response. In later chapters, we show you how to handle more of a form's features in your programs.

As you learned in Chapter 2, the main form for a Visual Basic application is called Form1. When you start a new Visual Basic application, the class definition for this form is added to your code automatically. The code placed in your application includes the instructions for instantiating a form object and loading the form on the screen. You, as the programmer, are responsible for placing controls on the form that displays the output from your program. In general, the following steps are required to use a form for output in Visual Basic:

  1. Import the namespace containing the Form1 class from Visual Basic's library.

  2. Ask Visual Basic to create the class Form1 (an object).

  3. Add output to the Form1 object.

In the remainder of this section we examine each of these steps in turn. The first two steps are performed by the Visual Basic code generator, so we review the code that is added to the program. The last step involves adding at least one control to a form and displaying text in the control, which we discuss in detail. Along the way, we also review the syntax and semantics for calling a subprocedure in Visual Basic, and we show you how to use additional types that Visual Basic defines for output.

Namespaces In order to access the tools you need to create and manipulate a form, you must import some classes available in a Visual Basic namespace. Here is the syntax diagram for such a declaration:

Click To expand

As the template shows, an import declaration begins with the keyword Imports, the name of a namespace and a dot (period). Following the period you type the name of the class you want to import.

For example, the Form1 class is contained in the Visual Basic class Winforms, which is part of the System namespace. We import the class into our program with this line:

Imports System.WinForms   ' Supplies Form1 class for output display

Declaring and Instantiating the Form1 Object Once you have imported the Form1 class, you have to declare it and then call something called a constructor, which is a set of instructions the computer follows in instantiating the object. The class Form1 is actually a subclass of the base class Form. This fact is made clear in the declaration heading for the class, which is:

Public Class Form1
   Inherits System.WinForms.Form

This heading indicates that Form1 will inherit the properties and methods that are defined in the class Form.

Following the class heading is the heading for the New() subprocedure:

Public Sub New()

This subprocedure is the constructor. The constructor for an object is called whenever a new object of that class is created. Its purpose is to provide the instructions the computer follows in instantiating the object. We formally define the constructor later in this chapter.

The first line of Form1's constructor is

MyBase.New

This line of code calls the New method of the Form class, which is the base class for Form1. The keyword MyBase signifies that the method called with MyBase uses the definition of the method in the base class. The New method is responsible for the actual creation of the Form1 object.

The next line of code

InitializeComponent()

is a method invocation that executes code written by Visual Studio for building the individual components that make up the graphical user interface. We discuss method invocations in the next section.

Method Invocation Recall that methods are subprograms that are called upon to perform some predefined set of actions. You can use the Show method to print strings on the screen. Using object-oriented terminology, we say that we are sending messages to an object called Messagebox. Technically, we say that we are calling methods associated with the object.

A call to a method is another form of executable statement in Visual Basic. You can write the call statement simply by specifying the name of the method, followed by a list of parameters enclosed in parentheses. The parameter to Show is the string to be printed. We say that the parameters are passed to a method.

Call
A statement that causes a method to be executed. In Visual Basic, a method is called by writing its name, followed by a list of parameters enclosed in parentheses
Parameter
An expression used for communicating values to a method

In a call, the method name may or may not have to be appended to an object name with a dot in between. (We explain these different forms of a call statement shortly.)

The effect of the call is to cause control of the computer to jump to the instructions in the method, which make use of the values given to it as parameters. Control of the computer returns to the statement following the call when the method has completed its task.

Here is the syntax template for a call statement.

Click To expand

In Visual Basic, methods come in three flavors: instance methods, class methods, and helper methods. Instance methods are associated with individual objects. Class methods are associated with classes. Helper or auxiliary methods are subprograms within a class that help other methods in the class. Instance methods are covered here.

When you create an object of a given class, it has a set of instance methods associated with it. Because there can be more than one object of a given class, you must also identify the object when you call the method. In object-oriented terms, you must designate the object to which the message is being sent. The object name is appended in front of the method name with a dot in between. In the following call

Messagebox.Show("Good Morning.")

Messagebox is the name of the object with which the Show method is associated.

As you examine the template, you should note that the parameters in a call are optional, but the parentheses are required. You sometimes write call statements of the form

myObject.MethodName()

in which there are no parameters.

After a method's job is done, control passes to the next statement after the call. This form of method call is used as a separate statement in the code. Methods that are used in this way are called subprocedures. In the next chapter we examine a different form of method call that is written within an expression to compute a value as part of evaluating the expression.

Subprocedure
A method that is called as a separate statement. The method does not return a value.

InitializeComponent() is a subprocedure call. It takes no arguments and does not return a value. The code located inside the subprocedure is generated when you add user-interface components (controls) to a form by the forms system, called WinForms.

For example, if you place a textbox on a form and put a command button under it, the code added to the InitializeComponent() call looks like this:

Private Sub InitializeComponent()
   Me.textBox1 = New System.Windows.Forms.TextBox()
   Me.button1 = New System.Windows.Forms.Button()
   Me.textBox1.Location = New System.Drawing.Point(56, 16)
   Me.textBox1.Size = New System.Drawing.Size(160, 20)
   Me.textBox1.TabIndex = 0
   Me.textBox1.Text = "textBox1"
   Me.button1.Location = New System.Drawing.Point(72, 56)
   Me.button1.Size = New System.Drawing.Size(112, 24)
   Me.button1.TabIndex = 1
   Me.button1.Text = "button1"
   Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
   Me.ClientSize = New System.Drawing.Size(292, 273)
   Me.Controls.AddRange(New System.Windows.Forms.Control() _
                       {Me.button1, Me.textBox1})
   Me.Text = "Form1"
End Sub

Most of the code in this subprocedure is used to locate the controls on the form. In previous versions of Visual Basic, this code was not available to be viewed by the programmer.

Putting Elements in the Window In Chapter 2 we saw how to put elements on a form. Forms are instances of the System.Windows.Form class, which is an example of a container class. A form is called a container class because you can add elements into it.

Container class
A class into which you can add other elements

The standard way of adding elements to a form is to use the Visual Studio editor to place elements such as controls on a form. However, it is possible to do this strictly from code. You can use the example code from the InitializeComponent() method call in the previous section as an example.

Constructors In the InitializeComponent() method call example, you saw how the new operation is used to create an object. For example, the following line of code creates a new instance of a label:

Me.SomeLabel = New System.Windows.Forms.Label()

Once again, because of Visual Basic's use of abstraction, you don't need to know the details of what a Label object contains. You can simply accept that it has been properly defined in the Visual Basic library.

You might notice that the right-hand side of the assignment statement starts with the New keyword, followed by what looks similar to a method call: It is an identifier followed by a pair of parentheses. That is because it is really a call to a special method, known as a constructor, that is part of every class. The constructor for a given class is called whenever a new object of that class is created, and its purpose is to provide the instructions the computer follows in instantiating the object.

Constructor
A method that has the same name as the class containing it; this method is called whenever an object of that class is instantiated

Constructors are class method with the special feature that they have the same name as the class.[1] In the case of the constructor for the Label class, there is no parameter, but many constructors do use parameters when they are called.

Let's now combine all these steps into a program. Let's redo the program that prints Mr. Hermann's name in two formats, but let's use a window based on a form rather than a messagebox.

' ******************************************************************
' PrintName program
' This program prints a name in two different formats
' ******************************************************************

Public Class Form1
    Inherits System.Windows.Forms.Form  ' Contains Form class
    Const FIRST As String = "Herman"    ' Person's first name
    Const LAST As String = "Hermann"    ' Person's last name
    Const MIDDLE As Char = "G"c         ' Person's middle initial
    Dim firstLast As String             ' Name in first-last format
    Dim lastFirst As String             ' Name in last-first format

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        firstLast = FIRST & " " & LAST
        SomeLabel().Text = "Name in first-last format is " & _
                          firstLast & " "
        lastFirst = LAST & ", " & FIRST & ", "
        SomeLabel().Text = SomeLabel().Text & "Name in last-first-initial
                         format is " _
                         & lastFirst & MIDDLE & "."
        ' Add any initialization after the InitializeComponent() call
    End Sub

 End Sub

#End Region

End Class

The output screen from this program is shown below.

Click To expand
Start Sidebar
Background Information
The Origins of Visual Basic

If we were to chart the history of programming languages, we would find several distinct families of languages that have their origins in the early days of computing. When the idea of highlevel languages first arose, it seemed that everybody had their own notion of the ideal form for a programming language. The result is known as the Tower of Babel period of programming languages. As more programs were written, the cost of rewriting them to use the features of a new language increased, and the computing world concentrated on a few languages. These included FORTRAN, Algol, COBOL, Basic, PL/1, Lisp, and BCPL.

The history of Visual Basic.NET begins in the early 1960s when two professors of mathematics at Dartmouth College, John Kemeny and Thomas Kurtz, designed a new computer programming language meant to be easy for college students to learn and use. The language began as a subset of FORTRAN but the professors quickly realized that in the process of adapting FORTRAN they were actually turning it into a new language, and they began working on Basic.

The language Kemeny and Kurtz designed proved to be very popular because of its ease-of-use, especially compared to most of the other programming languages in use at the time. Features that made Basic user-friendly include an English-like syntax, a small number of commands (the first version had just 14 commands), and its use as a general-purpose language. While the language was easy for beginners to learn, it was also a quite powerful language. In fact, General Electric used Basic to design what was at the time the largest time-sharing system in the world for its mainframe computers. By the beginning of 1970 there were over 20 different versions of Basic running on all types of mainframe and minicomputers.

The next major milestone in Basic's history occurred in the mid-1970s when Bill Gates and Paul Allen wrote a version of Basic for one of the first microcomputers, the MITS Altair. This proved that Basic could be implemented in a small computer with limited memory, and soon every new brand of personal computer had its own version of Basic. These languages were often implemented in ROM (read-only memory), meaning that the language was available for use whenever the computer was powered up. The language spread to small businesses and schools, and Thomas Kurtz estimated that in 1977 over five million people had programmed with some version of Basic.

Through the 1970s and 1980s many more versions of Basic were designed for professional programmers and not just for hobbyists. There were literally hundreds of different versions of Basic, each one just different enough from the others that moving a program from one version of Basic to another was a major undertaking. However, Microsoft's Basic, installed on over 50 brands of computers, became the standard for the language.

The introduction of Microsoft's Windows operating system brought about the next major change in the Basic language. Programming applications for Windows was a quite complex task, at first limited to programmers who knew assembly language or C. It became clear to Microsoft, though, that there was a huge demand for a Windows programming language that didn't require the programming expertise that was necessary for using C or assembly. Also, the Windows environment encouraged the use of a new kind of programming language, an object-oriented language where programming involved creating modules of data and operations that are self-contained. To facilitate this type of programming in Windows, Microsoft released Visual Basic in the early 1990s. The language evolved throughout the decade, going from what is called an object-based language to a more complete object-oriented language.

The current version of Visual Basic you are learning about in this book, Visual Basic.NET, represents a significant overhaul of Visual Basic. Many of Visual Basic's object-oriented features were added in a somewhat ad hoc way over the years and Microsoft decided to redesign the language from the ground up, using what the company has learned over the years about designing object-oriented languages. One of the most obvious influences on the design of Visual Basic.NET is the Java language. In fact, many people are saying that this new version is the "Javatization" of Visual Basic. The end result, though, is a version of Visual Basic that incorporates the best of what the computer science community has learned about object-oriented and Windows programming, while retaining the basic "ease-of-use" feel of the original Basic language.

End Sidebar

[1]A class method can have any name, but when its name is the same as the class containing it, then it is also a constructor.


3.2 Formatting Output

To format a program's output means to control how it appears visually on the screen or on a printer. We already know that we can control much of how our output appears on the screen by how we place control on a form. In this section, we discuss a greater degree of output appearance control by learning how to align text within labels.

Alignment of Text within Labels

By default, the text within a label begins at the left edge of the label. Sometimes, however, you'll want to center the text within the label or have it appear as far to the right as possible within the label. Visual Basic allows you to do this in two ways. One way is to set the TextAlign property of the label while you are designing the form. This is called setting the property at design time. You learned how to do this in Chapter 2.

The other way to change the alignment of text in a label is to set the TextAlign property in code, or at run time. The TextAlign property of a label has several constants you can set to determine how the text is placed in a label. The programmer has quite a bit of control over the exact alignment of text, whether it is placed at the left, right, or middle of a label, and whether the text should appear in the top of the label, in the middle of the label, or in the bottom of the label. The constants include:

  • ConstantAlignment.BottomLeft

  • ConstantAlignment.BottomCenter

  • ConstantAlignment.BottomRight

  • ConstantAlignment.MiddleLeft

  • ConstantAlignment.MiddleCenter

  • ConstantAlignment.MiddleRight

  • ConstantAlignment.TopLeft

  • ConstantAlignment.TopCenter

  • ConstantAlignment.TopRight

For example, if you want the column heading "Named" in Figure 3.1 to be centered (both horizontally and vertically), you would write:

Click To expand
Figure 3.1: A form with three labels, all left-justified
NamedLabel.TextAlign = ConstantAlignment.MiddleCenter

If instead you write

NamedLabel.TextAlign = ConstantAlignment.MiddleRight

then the text would be positioned as far to the right as possible within the label. Because the usual position for text is toward the left, you rarely need to use the ConstantAlignment.MiddleLeft value. Figure 3.2 shows the result of centering the headings from the example in Figure 3.1.

Click To expand
Figure 3.2: A form with centered headings in the top labels


3.3 Handing Events

When you run the PrintName program on page 105, the result is a window like that shown here. Compare this window with the one from the version of PrintName in Chapter 2.

Click To expand

If you run this application for yourself, you discover that the window just stays there. You can, however, close the window by pressing the small button marked "x" in the upper right-hand corner of the window. This is the standard way to close a window in Windows. To get this to happen, you have to write code telling the computer what to do when the user closes the window.

Recall from Chapter 1 that one of the control structures in a Visual Basic program is asynchronous control. A mouse click is an action that can trigger the program to perform a set of instructions at any time during its execution. That is why it is called asynchronous, which means not connected with a specific moment in time. The program can be busily working away performing some operation; you can choose a random moment to click the mouse. The program has to stop what it is doing and transfer control to the instructions that take care of the mouse click. Once it has responded to your action, control returns to the program at the point at which it was interrupted. In Visual Basic, a user action of this form is called an event, and responding to it is called event handling. Events are handled by special methods called event handlers.

Asynchronous
Not occurring at the same moment in time as some specific operation of the computer. In other words, not synchronized with the program's actions.
Event
An action, such as a mouse click, that takes place asynchronously with respect to the execution of the program
Event handling
The process of responding to events that can occur at any time during execution of the program
Event handler
A method that is invoked when the delegate receives a corresponding event

The event-handling process sounds very much like calling a method, but there is a basic difference. A method call is a statement in our program that calls the method at a specific point in the program's execution. An event handler, on the other hand, can implicitly be called at any time, and there is no corresponding method call statement anywhere in the program. How then is the event handler called?

The answer is that there are objects in Visual Basic called delegates whose purpose is to call an event's associated method. For example, when you place a button on a Visual Basic form, a delegate is created for handling the button's Click event, a standard event to all buttons. An object that generates an event is called an event source. When an event source generates an event, we say that the source is firing the event. Every event source keeps a list of the delegates that want to be notified when that kind of event occurs.

Delegate
An object that is waiting for one or more events to occur
Event source
An object that generates an event
Firing an event
An event source generating an event

Each delegate has one or more methods that are designed to respond to (handle) events. These delegates are waiting in the background, watching diligently for events to occur. When an event occurs, the appropriate delegates are notified and the appropriate event handlers are invoked. When the event handler returns (method finishes executing), the program continues executing. Figure 3.3 illustrates the process. If there is no delegate that is interested or the interested delegate doesn't have an event handler for that particular type of event, the event is ignored and the program continues uninterrupted.

Click To expand
Figure 3.3: Event handling

In the PrintName program, we were able to close the window by pressing the Close button, but we didn't write the event handler for this particular event. Visual Studio added this code automatically when we first created the form. In the next section, we examine this code and another example to see how Visual Studio helps the programmer handle events.


3.4 Event Wiring

Registering a delegate with an event handling method is known as event wiring. One of the nice things about using Visual Studio for writing Visual Basic programs is that when you create a form in Visual Studio, the system automatically performs event wiring for the objects you place on a form.

A good example of how this works is seen when you add a button to a form. The standard event for a button is the Click event. After a button is placed on a form, you can verify that the button's Click event is "wired" by double-clicking on the button when Visual Studio is in design-view. You are taken to the button's Click method automatically, as shown below.

Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button1.Click
End Sub

This "template" for the Click event is provided by Visual Studio. If you want your program to do something when the user clicks this button, you write code inside the template.

Visual Studio even provides code for some of the standard events that occur in Windows. A good example of this is the code written for closing a window, which we examine in the next section.


3.5 Event-Handler Methods

Event handling only takes place if there is code written inside the event-handler method, as was mentioned before. The code for some standard windows events, such as closing a window, is written automatically by Visual Studio. We can modify this code if we want, or we can simply accept it as is.

A good example is the code Visual Studio writes for the window Close event. When we close a window in Visual Basic, we have to do two things: (1) remove all the objects that are in the window, and (2) remove the window. The method this describes is named Dispose. This method takes one parameter, a Boolean variable that describes the state of closing a window. If the user pressed the Close button, the variable (disposing, in the code below) is True. Otherwise, the variable is False and nothing happens.

Let's look at the code for the Dispose method and then we'll describe what is happening.

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()

            End If
       End If
       MyBase.Dispose(disposing)
End Sub

In the first line of the method (the method heading), there are several modifiers before the keyword Sub. We will explain these modifiers in more detail in Chapter 8. In the parameter list for the method is the Boolean variable disposing. The next line is a selection control statement (covered in Chapter 6) that tests the value of disposing. If disposing is True, then another selection control statement is executed. This "nested" selection control statement checks to see if there are any components (objects) on the form being closed. If there are, these objects are removed. If there are no objects on the form, then control is passed to the last line of the method. This line removes the form itself from the screen. The last line, End Sub, indicates the method is finished.

Figure 3.4 shows the relationship between the method written in the code and the Form1 object.

Click To expand
Figure 3.4: The Dispose method is called when the Close button is clicked.

Let's see how the event handler fits into program PrintName. You need to realize that this code has always been part of the application, but we left it out of the previous listing in order to avoid confusion until we could explain how the method is created.

Public Class Form1
    Inherits System.Windows.Forms.Form  ' Contains Form class
    Const FIRST As String = "Herman"    ' Person's first name
    Const LAST As String = "Hermann"    ' Person's last name
    Const MIDDLE As Char = "G"c         ' Person's middle initial
    Dim firstLast As String             ' Name in first-last format
    Dim lastFirst As String             ' Name in last-first format

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        firstLast = FIRST & " " & LAST
        SomeLabel().Text = "Name in first-last format is " & _
                          firstLast & " "
        lastFirst = LAST & ", " & FIRST & ", "
        SomeLabel().Text = SomeLabel().Text & "Name in last-first-initial
                         format is " _
                         & lastFirst & MIDDLE & "."
        ' Add any initialization after the InitializeComponent() call
    End Sub

    ' Event handler for window closing
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()                 ' Remove components
            End If
       End If
       MyBase.Dispose(disposing)                     ' Remove windows
    End Sub

 End Sub

#End Region

End Class

The output of this version of PrintName looks identical to the earlier version. In fact, the behavior of this version is identical to the behavior of the previous version. We have simply added the code for closing the window to the listing.

Start Sidebar
Problem-Solving Case Study
Display a Date in Multiple Formats

Problem You are beginning to work on a program that needs to output dates in several formats. As a start, you decide to write a short Visual Basic application that takes a single date and displays it in the different formats, so you can be certain that all of your string expressions are correct. Does this sound familiar? It should; this is the beginning of the Case Study for Chapter 2. However, there are added constraints: There should be labels showing how the data is written next to the example. The output should be in two columns, with the first column labeled "Format" and the second labeled "Example."

Output A window showing the date in four formats:

Format | Example
Month, day, year | August 18, 2001
day Month year | 18 August 2001
mm/dd/yyyy | 8/18/2001
dd/mm/yyyy | 18/8/2001

Discussion To organize the screen as this problem requires means that we must use a Form object rather than Messagebox.Show to create the output. The design we used in the original problem can be used to create the date strings, but we must write the labels for each date. Otherwise, the design remains the same until we reach the Display the Dates algorithm.

...........................................................................
Display the Labels

  Add label "Format"
  Add label "Example"

...........................................................................
Display the Dates

  Add label Month day, year
  Add label lblFirst
  Add label day Month year
  Add label lblSecond
  Add label mm/dd/yyyy
  Add label lblThird
  Add label dd/mm/yyyy
  Add label lblFourth

Now here's the program.

Public Class Form1
    Inherits System.Windows.Forms.Form
    Const MONTH_NAME As String = "August" ' The name of the month
    Const MONTH_NUMBER As String = "8"    ' The number of the month
    Const DAY As String = "18"            ' The day of the month
    Const YEAR As String = "2001"         ' The four-digit year
    Dim first As String                   ' Date in Month day, year format
    Dim second As String                  ' Date in day Month year format
    Dim third As String                   ' Date in mm/dd/yyyy format
    Dim fourth As String                  ' Date in dd/mm/yyyy format

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Create date formats and display on screen
        first = MONTH_NAME & " " & DAY & ", " & YEAR
        lblFirst.Text = first
        second = DAY & " " & MONTH_NAME & " " & YEAR
        lblSecond.Text = second
        third = MONTH_NUMBER & "/" & DAY & "/" & YEAR
        lblThird.Text = third
        fourth = DAY & "/" & MONTH_NUMBER & "/" & YEAR
        lblFourth.Text = fourth

    End Sub

    ' Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub
 End Sub

#End Region

End Class

The output from the program is

Click To expand
End Sidebar


Testing and Debugging

  1. Objects to which methods are being applied must have the method appended to the object name with a dot in between.

  2. When writing a Visual Basic Windows-based application, be sure to put your executable code after the InitializeComponents() call.

  3. An event for an object cannot be coded until the object is added to a form.

  4. Use Intellisense to help you code object methods and properties.

  5. Don't use the standard Visual Studio names for objects. Creating your own names will make your application easier to write and debug.


Summary of Classes

Class Name |  |  | Comments
System.Windows.Forms.Form |  |  |
 | Components.Dispose() |  | Proper removal of form objects
 | MyBase.Dispose() |  | Proper removal of form
 | Form1 |  |
Constructor: |  |  |
 |  | MyBase.New() |
 | Label |  |
 |  | Text | Sets and returns text in label


Summary

Window output is accomplished by means of the Form1 class. When we create a new Visual Basic application, Visual Studio provides us with a template for writing code to add objects, methods, and properties to the class.

Visual Studio also provides code templates (and sometimes code) for many of the events that occur during a Windows application. For example, when we create a Visual Basic application, Visual Studio writes a generic method for closing a window. You must use that code for properly closing the window, but you can add your own code to it if you need to.


Quick Check

Q: | 1. What is the name of the form created by Visual Studio when you start a new application? (p. 100) | 1. Form1
Q: | 2. What is the Visual Basic reserved word that provides access to classes in the .Net Framework library? (p. 101) | 2. Imports
Q: | 3. Write the statement that constructs a new instance of the Form1 class. (p. 101) |
Q: | 4. What is an expression used for communicating values to a method? (pp. 102–103) | 4. A parameter
Q: | 5. Give an example of a method call to Messagebox.Show(). (p. 103) | 5. Messagebox.Show( Oh what a beautiful morning. )
Q: | 6. Write a statement that adds the text "This is my answer" to the label outputDisplay. (pp. 102–103) | 6. outputDisplay.Text = This is my answer
Q: | 7. What object is waiting for an event to occur? (pp. 109–110) | 7. The delegate object
Q: | 8. What is an event handler? (p. 109) | 8. An event handler is a method that responds to a delegate object.
Q: | 9. What is event wiring? (p. 111) | 9. Event wiring is the process by which an event handler is connected to its corresponding event.
Q: | 10. What is the name of the method that removes objects from a form? (p. 111) | 10. Dispose()

Answers

A: | 1. Form1
A: | 2. Imports
A: | 3. MyBase.New()
A: | 4. A parameter
A: | 5. Messagebox.Show("Oh what a beautiful morning.")
A: | 6. outputDisplay.Text = "This is my answer"
A: | 7. The delegate object
A: | 8. An event handler is a method that responds to a delegate object.
A: | 9. Event wiring is the process by which an event handler is connected to its corresponding event.
A: | 10. Dispose()


Exam Preparation Exercises

    Chapter 3

  1. Name two operations that can be applied to a Form1 object.

  2. How do we gain access to the Form1 class?

  3. How do you instantiate an object of the Form1 class?

  4. Describe the contents of a namespace.

  5. Name three kinds of methods.

  6. How do you invoke an instance method?

  7. What is the primary method associated with the Messagebox object?

  8. What does the expression "sending a message to" mean?

  9. Describe the role of the parameter list.

  10. What is the function of the New method of class Form1?

  11. What is the difference between a method and a subprogram?

  12. What property is called to assign text to a label?

  13. What is the object whose role in life is to wait for an event to occur?

  14. What is an event handler?

  15. What is an event source?

  16. What is event wiring?

  17. Where is the code for the window closing event contained?

  18. What type of variable is passed to the Dispose() method?

  19. What is output by the following code segment? (All variables are of type String. Assume that a Form1 object has been created and the three labels, lblAddress, lblCity, and lblState have been placed on the form.)

    street = "Elm St."
address = "1425B"
city = "Amaryllis"
state = "Iowa"
streetAddress = address & " " & street
lblAddress.Text = streetAddress
lblCity.Text = city
lblState = ", " & state

  20. Correct the following program segment so that it displays "Martin Luther King Jr.".

    const FIRST As String : "Martin"
const MID As String : "Luther
const LAST As String : King
Dim name As String
name = Martin & Luther & King
initial = MID
LAST = "King Jr."
lblName.Text = "Name = " & name


Programming Warm-Up Exercises

    Chapter 3

  1. Write the output statement that displays your name in a form. Assume the form has been properly created.

  2. Write three consecutive output statements that display the following three lines of text as separate labels. Assume the labels have been placed on a form and properly named.

    The moon
    is
    blue

  3. Write declaration statements to declare three variables of type String and two variables of type Char. The String variables should be named make, model, and color. The Char variables should be named plateType and classification.

  4. Write a series of output statements that display the values declared in Exercise 3 on a form. Each value should be preceded by an identifying message.

  5. Change the PrintName program (page 105) so that it also prints the name in the format

    First-name Middle-initial. Last-name

    Make MIDDLE a String constant rather than a Char constant. Define a new String variable to hold the name in the new format and assign it the string using the existing named constants, any literal strings that are needed for punctuation and spacing, and concatenation operations. Display the string, labeled appropriately.

  6. Display the following groups of text in a form, with each line of text in a separate label.

    1. Four score
      And seven years ago

    2. Four score
      and seven
      years ago

    3. Four score
      and
      seven
      years ago

    4. Four
      score
      and
      seven
      years
      ago

  7. Enter the following program fragments into a new Visual Studio application. Be sure to place the fragments in the correct place in the application. Add two labels to the application, calling them lblMsg1 and lblMsg2. Then run the program to see what it outputs.

    Const MSG1 As String = "Hello, world."
Dim msg2 As String
lblMsg1.Text = MSG1
msg2 = MSG1 & " " & MSG1 & " " & MSG1
lblMsg2.Text = msg2

    1. Modify the labels so that the text appears centered.

    2. Modify the labels so that the text appears right-aligned.

    3. The following output appeared on the screen:

    The dollar amount is 23dollarsand52cents.

    Write the correct output using Messagebox.Show.


Programming Problems

  1. Problem 1 in Chapter 2 asked you to write a Visual Basic application that displayed a series of Haiku poems. Recall that a Haiku poem is written in three phrases. The first phrase has five syllables, the second has seven syllables, and the last phrase again has five syllables. For example:

    Bright flash then silence
My expensive computer
Has gone to heaven

    Your program was to define string constants with four phrases of five syllables and two phrases of seven syllables. Use string concatenation expressions to construct every possible 5-7-5 permutation of these phrases and display each one. Rewrite this application using labels on a form rather than using Messagebox.Show.

  2. Rewrite the solution to Chapter 2, Problem 2 that simulates the child's game "My Grandmother's Trunk." In this game, players sit in a circle, and the first player names something that goes in the trunk: "In my grandmother's trunk, I placed a pencil." The next player restates the sentence and adds something new to the trunk: "In my grandmother's trunk, I placed a pencil and a red ball." Each player in turn adds something to the trunk, attempting to keep track of all the items that are already there.

    Your program should simulate just five turns of the game. Starting with the empty string, simulate each player's turn by concatenating a new word or phrase to the existing string, and add the result to a label on a form.

  3. Write an application that writes your name left-justified on the first label on a form, your address centered in the next label, and your phone number right-justified in the next label.


Case Study Follow-Up Exercises

    Chapter 3

  1. Change the DateFormats program so that the four formats are shown in the opposite order on the screen.

  2. In the DateFormats program, explain what takes place in the statement that assigns a value to the variable third.

  3. Change the DateFormats program so that the third and fourth formats use hyphens (-) instead of slashes (/).

  4. Change the DateFormats program so that your birth date is displayed in the four formats.


Chapter 4: Numeric Types and Expressions

Highlights

Goals

  • To be able to declare named constants and variables of type Integer and Double.

  • To be able to construct simple arithmetic expressions.

  • To be able to evaluate simple arithmetic expressions.

  • To be able to construct and evaluate expressions that include multiple arithmetic operations.

  • To understand implicit type conversion and explicit type conversion functions.

  • To be able to use Visual Basic math functions in expressions.

  • To learn and be able to use additional operations associated with the String type.

  • To be able to format the statements in a program in a clear and readable fashion.

In Chapters 2 and 3, we examined enough Visual Basic syntax to be able to construct simple programs using assignment and output. We focused on the Char and String types and saw how to construct expressions using the concatenation operator. In this chapter we continue to write programs that use assignment and output, but we concentrate on additional built-in data types: Integer, Long, Single, and Double. These numeric types include multiple operators that enable us to construct complex arithmetic expressions. We show how to make expressions even more powerful by using calls to Visual Basic's value-returning math functions.


4.1 Overview of Visual Basic Data Types

The Visual Basic built-in data types are organized into primitive types and reference types (see Figure 4.1).

Click To expand
Figure 4.1: VB.NET data types

You might have noticed that the String data type isn't listed in Figure 4.1. In Chapter 2, we noted that String is an example of a class, which is one of the reference types. In fact, most of the types that we use in this text, other than the primitive types, are classes. In Chapter 3, we used classes such as Form and Label. Recall that our convention is to capitalize the first letter of all classes in this manner, to help you identify them in programs.

The division of Visual Basic's data types into primitive and reference types stems from the way Visual Basic stores each of the types in memory. Recall from Chapter 2 that we said Visual Basic chooses an address in the computer's memory where it stores each value and keeps track of the address so we don't have to remember it. Each primitive type takes up a fixed amount of space in memory, so Visual Basic stores each primitive value at the memory addresses it chooses for it. When you assign a value to a variable of a primitive type, Visual Basic copies the value into the address that has been chosen for the variable. This action is possible because each primitive type takes a specified amount of space.

The reference types are more complex. Because they can contain different numbers of fields, they take up different amounts of the computer's memory. Most are too large to fit into a single memory location, so Visual Basic can't just assign the new object by copying one memory location to another. Instead of storing the object at the chosen location, Visual Basic stores the object in one or more additional locations in another part of memory. If Visual Basic doesn't put the object into the chosen location, then what does it put there? It stores the address of the memory location where the object can be found. That is, the chosen location contains a binary number that tells the computer where the object is stored. When Visual Basic assigns an object to a variable, it copies this address into the variable.

Let's look at a pair of examples that demonstrate the difference between primitive values and reference values.

Dim letter As Char
Dim title As String
Dim bookName As String
letter = "J"c
title = "Programming and Problem Solving in VB.Net"
bookName = title

When you declare variables letter, title, and bookName, locations in memory are chosen for these variables. When you assign the value "J" to the Char variable letter, Visual Basic stores the value "J" into it. When you assign the string "Programming and Problem Solving in VB.Net" to the String variable title, Visual Basic chooses locations into which to store the string, and stores the address of the first location into title. If you assign the value of title to the String variable called bookName, then Visual Basic just copies the value stored in title (the address) to the place it chose for bookName. Figure 4.2 illustrates the difference between primitive and reference types.

Click To expand
Figure 4.2: Primitive types and reference types


4.2 Numeric Data Types

You are already familiar with the basic concepts of integer and real numbers in mathematics. However, as used on a computer, the corresponding data types have certain limitations, which we now examine.

Integral Types

The data types Byte, Short, Integer, and Long are known as integral types (or integer types) because they refer to integer values—whole numbers with no fractional part. In Visual Basic, the simplest form of integer value is a sequence of one or more digits:

22   16   1   498   0   4600

Commas are not allowed.

A minus sign preceding an integer value makes the integer negative:

-378    -912

The data types Byte, Short, Integer, and Long are intended to represent different sizes of integers, from smaller (fewer bits) to larger (more bits), as shown in Figure 4.3.

Click To expand
Figure 4.3: The integral types in Visual Basic

The Visual Basic language specifies the sizes of the integral types to be just as shown in the figure. The more bits there are in the type, the larger the integer value that can be stored.

The Integer type is by far the most common data type for manipulating integer data. Byte and Short types are used far less frequently. In Visual Basic you nearly always use Integer for manipulating integer values, but sometimes you may choose to use Long if your program requires values larger than the maximum Integer value. The range of Integer values is from -2147483648 through +2147483647. As we noted in Chapter 1, numbers like these remind us that the binary number system is working in the background.

When you write a literal integer value, it is automatically assumed by Visual Basic to be of type Integer. To write a literal of type Long, you must follow the last digit of the number with the letter "L". You may also use the lowercase "l", but it looks so much like the digit "1" that it may be impossible for a person to recognize the literal as Long when reading your program. We use only the uppercase "L" in this text. Here are some examples of literals of type Integer and Long.

Literal | Type
0 | Integer
0L | Long
2001 | Integer
1800551212L | Long
18005551212 | invalid (11 digits are too large for type Integer)

If your program tries to compute a value larger than a type's maximum value, the result is integer overflow. The Visual Basic CLR will signal an error if the result of a computation or an assignment results in integer overflow.

Floating-Point Types

Floating-point types (or floating types) are used to represent real numbers. Floating-point numbers have an integer part and a fractional part, with a decimal point in between. Either the integer part or the fractional part, but not both, may be missing. Here are some examples:

18.0   127.54   0.57   4.   193145.8523   .8

Just as the integral types in Visual Basic come in different sizes (Byte, Short, Integer, and Long), so do the floating-point types. In increasing order of size, the floating-point types are Single (meaning single-precision) and Double (meaning double-precision). Type Double gives us a wider range of value and more precision (the number of significant digits in the number) than type Single, but at the expense of twice the memory space to hold the number. In Visual Basic, Integer and Single values take up the same amount of memory space (32 bits), and both Long and Double take up 64 bits of memory space.

Floating-point values also can have an exponent, as in scientific notation. (In scientific notation, a number is written as a value multiplied by 10 to some power.) Instead of writing 3.504 × 1012, in Visual Basic we write 3.504E+12. The E (you can also use e), means exponent of base 10. The number preceding the letter E doesn't need to include a decimal point. Here are some examples of floating-point numbers in scientific notation:

1.74536E-12     3.652442E4     7E20     -8.01994E-23      -6e12

You don't have to type the plus sign when you write a positive exponent, but the Visual Basic editor will add it for you after you finish typing the number.

A Single value can represent seven-digit decimal numbers with an exponent in the range of -45 to 38. A Double value can represent 15-digit decimal numbers with an exponent ranging from -324 to 308.

In Visual Basic, floating-point literals of the form we have shown are automatically assumed by the compiler to be of type Double. Visual Basic, however, will not recognize a floating-point number with 0 as the fractional part as a Double. To make it clear, you should append the letter "R" to the literal, as in

12R

To write a literal of type Single, you must end the number with the letter F. You can type the letter f, but the editor will replace it with F. In the Payroll program, the identifiers MAX_HOURS, OVERTIME, payRate, hours, wages, and total are all of type Double. Here are some examples of floating-point literals.

Literal | Type
0.0 | Double
0.0F | Single
2.001E3 | Double
2.001E3F | Single
1.8E225F | invalid (exponent 225 too great for type Single)

We talk more about floating-point numbers in Chapter 13, but there is one more thing you should know about them now. Computers cannot always represent floating-point numbers exactly. You learned in Chapter 1 that the computer stores all data in binary (base-2) form. Many decimal floating-point values can only be approximated in the binary number system. Don't be surprised if your program prints out the number 4.8 as 4.7999998. In most cases, slight inaccuracies in the rightmost fractional digits are to be expected and are not the result of programmer error.


4.3 Declarations for Numeric Types

Just as with types Char and String, we can declare named constants and variables of type Integer, Long, Float, and Double. Such declarations use the same syntax as before, except that the literals and the names of the data types are different.

Named Constant Declarations

In the case of named constant declarations, the literal values in the declarations are numeric instead of being characters in double quotes. For example, here are some constant declarations that define values of type Integer and Single. For comparison, declarations of Char and String values are included.

Const PI As Double = 3.14159
Const E As Single = 2.71828F
Const MAX_TEMP As Long = 1000000000L
Const MIN_TEMP As Integer = -273
Const LETTER As Char = "W"c
Const NAME As String = "Elizabeth"

Although character and string literals are put in quotes, literal integers and floating-point numbers are not, because there is no chance of confusing them with identifiers. Why? Because identifiers must start with a letter or underscore, and numbers must start with a digit or sign.

Start Sidebar
Software Engineering Tip
Using Named Constants Instead of Literals

It's a good idea to use named constants instead of literals. In addition to making your program more readable, named constants can make your program easier to modify. Suppose you wrote an application last year to compute taxes. In several places you used the literal 0.05, which was the sales tax rate at the time. Now the rate has gone up to 0.06. To change your program, you must locate every literal 0.05 and change it to 0.06. And if 0.05 is used for some other reason—to compute deductions, for example—you need to look at each place where it is used, figure out what it is used for, and then decide whether to change it.

The process is much simpler if you use a named constant. Instead of using a literal constant, suppose you had declared a named constant, TAX_RATE, with a value of 0.05. To change your program, you would simply change the declaration, setting TAX_RATE equal to 0.06. This one modification changes all the tax rate computations without affecting the other places where 0.05 is used.

Visual Basic allows us to declare constants with different names but the same value. If a value has different meanings in different parts of an application, it makes sense to declare and use a constant with an appropriate name for each meaning.

Named constants also are reliable; they protect us from mistakes. If you mistype the name PI as PO, the Visual Basic compiler tells you the name PO has not been declared. On the other hand, even though we recognize that the number 3.14149 is a mistyped version of pi (3.14159), the number is perfectly acceptable to the compiler. It won't warn us anything is wrong.

End Sidebar

Variable Declarations

We declare numeric variables the same way in which we declare Char and String variables, except that we use the names of numeric types. Here are some sample declarations.

Dim studentCount As Integer      ' Number of students
Dim sumOfScores As Integer       ' Sum of their scores
Dim sumOfSquares As Long         ' Sum of squared scores
Dim average As Double            ' Average of the scores
Dim deviation As Single          ' Standard deviation of scores
Dim grade As Char                ' Student's letter grade
Dim stuName As String            ' Student's name

Given these declarations

Dim num As Integer
Dim alpha As Integer
Dim rate As Double
Dim ch As Char

the following are appropriate assignment statements:

Variable | Expression
alpha = | 2856
rate = | 0.36
ch = | "B"c
num = | alpha

In each of these assignment statements, the data type of the expression matches the data type of the variable to which it is assigned. Later in the chapter we see what happens if the data types do not match.


4.4 Simple Arithmetic Expressions

Now that we have looked at declaration and assignment, we consider how to calculate with values of numeric types. Calculations are performed with expressions. We first look at simple expressions that involve at most one operator so that we may examine each operator in detail. Then, we move on to compound expressions that combine multiple operations.

Arithmetic Operators

Expressions are made up of constants, variables, and operators. The following are all valid expressions:

alpha + 2    rate - 6.0   4 - alpha   rate   alpha * num

The operators allowed in an expression depend on the data types of the constants and variables in the expression. The arithmetic operators are

+ | Unary plus
- | Unary minus
+ | Addition
- | Subtraction
* | Multiplication
/ | Division
\ | Integer division (no fractional part)
mod | Modulus (remainder from division)
Unary operator
An operator that has just one operand
Binary operator
An operator that has two operands

The first two operators are unary operators—they take just one operand. The remaining five are binary operators, taking two operands. Unary plus and minus are used as follows:

-54  +259.65  -rate

Programmers rarely use the unary plus. Without any sign, a numeric constant is assumed to be positive anyway.

You may not be familiar with integer division ( \ ) and modulus (mod). Let's look at these operations more closely. Note that mod can be used with both integers and floating-point numbers. When you divide one integer by another, you get an integer quotient and a remainder. Integer division gives only the integer quotient, and mod gives only the remainder.

In Visual Basic, the sign of the remainder is the same as the sign of the dividend. For example,

 3  mod  2 = 1
 3  mod -2 = 1
-3  mod  2 = -1
-3  mod -2 = -1

In contrast to integer division, floating-point division yields a floating-point result. The expression

7.2 / 2.0

yields the value 3.6.

The floating-point remainder operation returns the remainder after dividing the dividend by the divisor a whole number of times. For example,

7.2 mod 2.1

yields the value 0.8999999999999999 because 2.1 goes into 7.2 exactly 3 times (3 * 2.1 = 6.3) with 0.9 remaining. The fact that Visual Basic returns 0.8999999999999999 again illustrates the fact that storing floating-point numbers in binary form will lead to a little imprecision. In fact, because rounding errors such as this can really "add up" when working with monetary values, Visual Basic provides another data type, Decimal, which provides more precision even than the float data types for dealing with dollars and cents.

Here are some expressions using arithmetic operators and their values:

Expression | Value
3 + 6 | 9
3.4 - 6.1 | -2.7
2 * 3 | 6
8 / 2 | 4
8.0 / 2.0 | 4.0
8 / 8 | 1
8 \ 9 | 0
8 \ 7 | 1
8 mod 8 | 0
8 mod 9 | 8
8 mod 7 | 1
0 mod 7 | 0
5.0 mod 2.3 | 0.4

Be careful with division and modulus. The expression 7 / 0 and 7 mod 0 produce an error message. The floating-point expressions 7.0 / 0.0 and 7.0 mod 0.0 also produce an error message. The computer cannot divide integers or floating-point numbers by zero.

Because variables are allowed in expressions, the following are valid assignments:

alpha = num + 6
alpha = num / 2
num = alpha * 2
num = 6 mod alpha
alpha = alpha + 1
num = num + alpha

As we saw with assignment statements involving String expressions, the same variable can appear on both sides of the assignment operator. In the case of

num = num + alpha

the value in num and the value in alpha are added together, and then the sum of the two values are stored back into num, replacing the previous value stored there. This example shows the difference between mathematical equality and assignment. The mathematical equality

num = num + alpha

is true only when alpha equals zero. The assignment statement

num = num + alpha

is valid for any value of alpha.

Here's a simple program that uses arithmetic expressions:

' ******************************************************
' FreezeBoil program
' This program computes the midpoint between
' the freezing point and boiling points of water
' ******************************************************

Imports System.ComponentModel       ' Import component handling
Imports System.Drawing              ' Import graphics
Imports System.WinForms             ' Import forms, event handling types

Public Class Form1                  ' Create form for output
    Inherits System.WinForms.Form
    Const FREEZE_PT As Double = 32  ' Freezing point of water
    Const BOIL_PT As Double = 212   ' Boiling point of water

    Dim avgTemp As Double           ' Holds the result of averaging
                                    ' FREEZE_PT and BOIL_PT
    Public Sub New()
        MyBase.New()

        Form1 = Me

        ' This call is required by the Win Form Designer.
        InitializeComponent()
        lblFreeze.Text = "Water freezes at " & FREEZE_PT
        lblFreeze.text = lblFreeze.Text & " and boils at " & _
                         BOIL_PT & " degrees."
        avgTemp = FREEZE_PT + BOIL_PT
        avgTemp = avgTemp / 2

        lblFreeze.Text = lblFreeze.Text & " Halfway between is " & _
                         avgTemp & " degrees."

        ' TODO: Add any initialization after the InitializeComponent() call
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()                ' Remove the form from the screen
        components.Dispose()            ' Remove other Windows components
    End Sub

#Region " Windows Form Designer generated code "

    ' Required by the Windows Form Designer
    Private components As System.ComponentModel.Container

    Private WithEvents lblFreeze As System.WinForms.Label

    Dim WithEvents Form1 As System.WinForms.Form

    ' NOTE: The following procedure is required by the Windows Form
    '   Designer
    ' It can be modified using the Windows Form Designer.
    ' Do not modify it using the code editor.
    Private Sub InitializeComponent()
        Me.components = New System.ComponentModel.Container()
        Me.lblFreeze = New System.WinForms.Label()

        ' @design Me.TrayHeight = 0
        ' @design Me.TrayLargeIcon = False
        ' @design Me.TrayAutoArrange = True
        lblFreeze.Location = New System.Drawing.Point(16, 32)
        lblFreeze.Size = New System.Drawing.Size(568, 23)
        lblFreeze.Font = New System.Drawing.Font("Tahoma", 10!,
                         System.Drawing.FontStyle.Bold)
        lblFreeze.TabIndex = 0

        Me.Text = "FreezeBoil"
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(600, 85)

        Me.Controls.Add(lblFreeze)
    End Sub
#End Region

End Class

The form design for this program looks like this:

Click To expand

The program begins with a comment that explains what the program does. The body of the Form1 class declaration includes a definition of constants FREEZE_PT and BOIL_PT. The declaration of the variable avgTemp, along with a sequence of executable statements, is placed in the New subprocedure of the Form1 class. These statements display text in a label, add FREEZE_PT and BOIL_PT, divide the sum by 2, display the result, and set up the window close event. Here is the output from the application:

Click To expand

One thing you should notice in the program is that whereas both constants are declared as type Double, the value they are assigned does not include the fractional part .0. This is because the Visual Basic editor does not show the fractional part of a floating-point value when it is 0. To indicate these values are indeed floating-point values, the letter "F" is appended to the value.

Also notice in the code that a function, FormatNumber, is called when displaying the three temperature values. This is because Visual Basic will not display a fractional value that is equal to 0. To get the ".0" part to display, we have to force the issue by calling the function. The function takes two arguments, a value and the number of decimal places you want to display.


4.5 Compound Arithmetic Expressions

The expressions we've used so far have contained at most a single arithmetic operator. We also have been careful not to mix integer and floating-point values in the same expression. Now we look at more complicated expressions—ones that are composed of several operators and ones that contain mixed data types.

Precedence Rules

Arithmetic expressions can be made up of many constants, variables, operators, and parentheses. In what order are the operators performed? For example, in the assignment statement

avgTemp = FREEZE_PT + BOIL_PT / 2.0

Is FREEZE_PT + BOIL_PT calculated first or is BOIL_PT / 2 calculated first?

The five basic Visual Basic operators (+ for addition, - for subtraction, * for multiplication, / for division, and mod for modulus) are ordered the same way mathematical operators are, according to precedence rules:

Highest precedence: | ( ) |  |
 | unary + | unary - |
 | * | / | mod
Lowest precedence: | + | - |

In the example above, we first divide BOIL_PT by 2.0 and then add FREEZE_PT to the result.

You can change the order of evaluation by using parentheses. In the statement

avgTemp = (FREEZE_PT + BOIL_PT) / 2.0

FREEZE_PT and BOIL_PT are added first, and then their sum is divided by 2.0. We evaluate subexpressions in parentheses first and then follow the precedence of the operators.

When there are multiple arithmetic operators with the same precedence, their grouping order (or associativity) is from left to right. The expression

int1 - int2 + int3

means (int1 - int2) + int3, not int1 - (int2 + int3). As another example, we would use the expression

(double1 + double2) / double1 * 3.0

to evaluate the expression in parentheses first, then divide the sum by double1, and then multiply the result by 3.0. Following are some more examples.

Expression | Value
10 / 2 * 3 | 15
10 mod 3 - 4 / 2 | -1
5.0 * 2.0 / 4.0 * 2.0 | 5.0
5.0 * 2.0 / (4.0 * 2.0) | 1.25
5.0 + 2.0 / (4.0 * 2.0) | 5.25

Implicit Type Conversion and Type Conversion Functions

Integer values and floating-point values are stored differently inside a computer's memory. The pattern of bits that represents the constant 2 does not look at all like the pattern of bits representing the constant 2.0. (In Chapter 13, we examine why floating-point numbers need a special representation inside the computer.) What happens if we mix integer and floating-point values together in an assignment statement or an arithmetic expression? In certain situations, the compiler will convert a type to another type. This is called implicit type conversion. In other situations, the compiler won't automatically make the conversion and the programmer will have to make the conversion explicitly, using a type conversion function. Now there are three questions to ask: How do we know when an implicit type conversion will take place?, What happens when a type conversion is performed implicitly?, and What are the type conversion functions and how are they used?

The answer to the question depends on how the Visual Basic environment is set up for handling type conversions. The Visual Basic compiler has several attributes you can set to have it compile your programs in certain ways. One option you can set for type conversions is Option Strict. If Option Strict is on, which it is by default, then the Visual Basic compiler will not implicitly convert data types. When, for example, you try to assign an integer value to a floating-point value, an error will appear in the editor, stating that the conversion is not allowed.

If you turn off Option Strict, the compiler will perform implicit type conversion. Let's look first at what happens when we mix data types in assignment statements.

Assignment Statements If you make the declarations

Dim someInt As Integer
Dim someDouble As Double

then someInt can hold only integer values, and someDouble can hold only double-precision floating-point values. The assignment statement

someDouble = 12

may seem to store the integer value 12 into someDouble, but this is not true. The computer refuses to store anything other than a Double value into someDouble. The compiler inserts extra Bytecode instructions that first convert 12 to 12.0 and then store 12.0 into someDouble. This implicit (automatic) conversion of a value from one data type to another is known in Visual Basic as type conversion.

Type conversion
The implicit (automatic) conversion of a value from one data type to another

The statement

someInt = 4.8

also causes type conversion. When a floating-point value is assigned to an Integer variable, the fractional part is rounded up. As a result, someInt is assigned the value 5. If the statement had been written

someInt = 4.35

someInt would be assigned the value 4, with the value being rounded down.

With both of the assignment statements above, the program would be less confusing for someone to read if we avoided mixing data types:

someDouble = 12R
someInt = 4

More often, it is not just constants but entire expressions that are involved in type conversions. With Option Strict turned off, both of the assignments

someDouble = 3 * someInt + 2
someInt = 5.2 / someDouble - anotherDouble

lead to type conversion. Storing the result of an Integer expression into a Double variable doesn't cause loss of information; a whole number such as 24 can be represented in floating-point form as 24.0. In the Visual Basic language, a type conversion that does not result in a loss of information is known as a widening conversion. Assigning Integer values to Long variables or Single values to Double variables are additional examples of widening conversions.

Widening conversion
A type conversion that does not result in a loss of information

However, storing the result of a floating-point expression into an Integer variable can cause a loss of information because the fractional part is either rounded down or rounded up. Visual Basic refers to such a conversion as a narrowing conversion. It is easy to overlook the assignment of a floating-point expression to an Integer variable, a Double value to a Single variable, or a Long value to an Integer variable when we try to discover why our program is producing the wrong answers.

Narrowing conversion
A type conversion that may result in a loss of some information, as in converting a value of type Double to type Single

One way to keep this problem from happening is to always leave the Option Strict compiler option turned on. With this option on, widening conversions are allowed, but narrowing conversions always cause an error. Turning Option Strict off will allow both conversion types.

To make our programs as clear (and error-free) as possible, we should use Visual Basic's type conversion functions. A type conversion function takes a value of one data type and returns the data type called for by the function. There are type conversion functions for each of the numeric data types (and for other Visual Basic data types as well).

Type conversion function
A function that converts a value from one data type to another

The type conversion functions for numeric types include:

  • CInt—Converts its argument to an Integer type.

  • CLng—Converts its argument to a Long type.

  • CSng—Converts its argument to a Single type.

  • CDbl—Converts its argument to a Double type.

A type conversion function is written with the function name followed by the expression to be converted in parentheses:

someDouble = Cdbl(3 * someInt + 2)
someInt = CInt(5.2 / someDouble - anotherDouble)

Both of the statements

someSingle = someInt + 8
someSingle = CSng(someInt + 8)

produce identical results. The only difference is in clarity of the statements. With the type conversion function, it is perfectly clear to the programmer and to others reading the program that the mixing of types is intentional, not an oversight. Countless errors have resulted from unintentional mixing of types.

Arithmetic Expressions So far we have been talking about mixing data types across the assignment operator ( = ). It's also possible to mix data types within an expression:

someInt * someDouble
4.8 + someInt - 3

Such expressions are called mixed type (or mixed mode) expressions.

Mixed type expressions
An expression that contains operands of different data types; also called a mixed mode expression

Whenever an integer value and a floating-point value are joined by an operator, implicit type conversion occurs as follows.

  1. The integer value is temporarily converted to a floating-point value.

  2. The operation is performed.

  3. The result is a floating-point value.

Let's examine how the machine evaluates the expression 4.8 + someInt - 3, where someInt contains the value 2. First, the operands of the + operator have mixed types, so the value of someInt is converted to 2.0. (This conversion is only temporary; it does not affect the value that is stored in someInt.) The addition takes place, yielding a value of 6.8. Next, the subtraction operator ( - ) joins a floating-point value (6.8) and an integer value (3). The value 3 is converted to 3.0, the subtraction takes place, and the result is the floating-point value 3.8.

Just as with assignment statements, you can use type conversion functions within expressions to lessen the risk of errors. Writing expressions like

CDbl(someInt) * someDouble
4.8 + CDbl(someInt - 3)

makes it clear what your intentions are.

As a final remark about type conversion and type conversion functions, you may have noticed that we have concentrated only on the Integer and Double types. It is also possible to stir Byte, Long, Short, and Single values into the pot. The results can be confusing and unexpected. You should avoid unnecessarily mixing values of these types within an expression. Whenever it is necessary to do so, you should use type conversion functions to clarify your intentions.

String Conversion Just as Visual Basic attempts to convert between numeric types when you mix them in expressions, it also tries to convert numeric values to strings when you mix them into expressions with the string concatenation operator. If you declare a String object called answer, you can write an assignment statement of the following form

answer = "The average is: " & average

If average contains the value 27.65, then the outcome of this assignment is that answer contains the string

"The average is: 27.65"

When one of the operands of the & operator is a string and the other operand is a numeric type, the numeric type is converted to a string prior to concatenation. You can also concatenate arithmetic operations with strings in the same expression. For example,

answer = "The results are " & 27 + 18 & " and " & 9

String conversion is a useful feature of Visual Basic for formatting output in which we mix numeric values with text that explains their meaning.

Start Sidebar
May We Introduce…
Blaise Pascal

One of the great historical figures in the world of computing was the French mathematician and religious philosopher Blaise Pascal (1623–1662), the inventor of one of the earliest known mechanical calculators.

Click To expand

Pascal's father, Etienne, was a noble in the French court, a tax collector, and a mathematician. Pascal's mother died when Pascal was three years old. Five years later, the family moved to Paris and Etienne took over the education of the children. Pascal quickly showed a talent for mathematics. When he was only 17, he published a mathematical essay that earned the jealous envy of Rene Descartes, one of the founders of modern geometry. (Pascal's work actually had been completed before he was 16.) It was based on a theorem, which he called the hexagrammum mysticum, or mystic hexagram, that described the inscription of hexagons in conic sections (parabolas, hyperbolas, and ellipses). In addition to the theorem (now called Pascal's theorem), his essay included over 400 corollaries.

When Pascal was about 20, he constructed a mechanical calculator that performed addition and subtraction of eight-digit numbers. That calculator required the user to dial in the numbers to be added or subtracted; then the sum or difference appeared in a set of windows. It is believed that his motivation for building this machine was to aid his father in collecting taxes. The earliest version of the machine does indeed split the numbers into six decimal digits and two fractional digits, as would be used for calculating sums of money. The machine was hailed by his contemporaries as a great advance in mathematics, and Pascal built several more in different forms. It achieved such popularity that many fake, nonfunctional copies were built by others and displayed as novelties. Several of Pascal's calculators still exist in various museums.

Pascal's box, as it is called, was long believed to be the first mechanical calculator. However, in 1950, a letter from Wilhelm Shickard to Johannes Kepler written in 1624 was discovered. This letter described an even more sophisticated calculator built by Shickard 20 years prior to Pascal's box. Unfortunately, the machine was destroyed by fire and never rebuilt.

During his twenties, Pascal solved several difficult problems related to the cycloid curve, indirectly contributing to the development of differential calculus. Working with Pierre de Fermat, he laid the foundation of the calculus of probabilities and combinatorial analysis. One of the results of this work came to be known as Pascal's triangle, which simplifies the calculation of the coefficients of the expansion of (x + y)2, where n is a positive integer.

Pascal also published a treatise on air pressure and conducted experiments that showed that barometric pressure decreases with altitude, helping to confirm theories that had been proposed by Galileo and Torricelli. His work on fluid dynamics forms a significant part of the foundation of that field. Among the most famous of his contributions is Pascal's law, which states that pressure applied to a fluid in a closed vessel is transmitted uniformly throughout the fluid.

When Pascal was 23, his father became ill, and the family was visited by two disciples of Jansenism, a reform movement in the Catholic Church that had begun six years earlier. The family converted, and five years later one of his sisters entered a convent. Initially, Pascal was not so taken with the new movement, but by the time he was 31, his sister had persuaded him to abandon the world and devote himself to religion.

His religious works are considered no less brilliant than his mathematical and scientific writings. Some consider Provincial Letters, his series of 18 essays on various aspects of religion, as the beginning of modern French prose.

Pascal returned briefly to mathematics when he was 35, but a year later his health, which had always been poor, took a turn for the worse. Unable to perform his usual work, he devoted himself to helping the less fortunate. Three years later, he died while staying with his sister, having given his own house to a poor family.

End Sidebar


4.6 Additional Mathematical Methods

Certain computations, such as taking square roots or finding the absolute value of a number, are very common in programs. It would be an enormous waste of time if every programmer had to start from scratch and create methods to perform these tasks. To help make the programmer's life easier, the Math class provides a number of useful methods. These methods may be called either by preceding the method name with the class name and a dot, or, if you import the Math class into your program with the Imports statement, by just using the method name. We will adopt the second form in this book, which will make our programs easier to read. Therefore, when you plan to use the Math class methods in your programs, include the line

Imports System.Math

at the top of the program, along with the other import lines.

Method | Parameter Type(s) | Result Type | Result
Abs(x) | Integer, Long, Float, or Double | same as parameter | Absolute value of x
Cos(x) | Double | Double | Cosine of x (in radians)
Sin(x) | Double | Double | Sine of x (in radians)
Log(x) | Double | Double | Natural logarithm of x
Pow(x, y) | Double | Double | x raised to power y ( if x = 0.0, y must be positive; if x ≤ 0.0, y must be a whole number
Min(x, y) | Integer, Long, Float, or Double | same as parameter | Smaller of x and y
Max(x, y) | Integer, Long, Float, or Double | same as parameter | Larger of x and y
Sqrt(x) | Double | Double | Square root of x (x ≥ 0.0)

When you call a WinForms class subprocedure like New as a separate statement, it simply does some work and returns control to the next statement. But the calls to the Math methods like those in this table are used in expressions within assignment statements or in the parameter lists of other method calls. A method that is called as part of an expression does some work for you, but it also returns a value that takes its place in the expression. The statement

rootX = Sqrt(x)

calls the Sqrt method, which returns the square root of x that is assigned to rootX. Methods such as Sqrt, which return a value, are called value-returning methods. The third column in the preceding table tells you the type of the value that is returned. Methods are sometimes also called functions. In this book, when a method is defined as part of a class, we will call it a method. However, Visual Basic also includes several built-in value-returning procedures called functions. These procedures, while technically part of a class, will still be called functions to retain their historical names in the Visual Basic nomenclature.

Value-returning method
A method that is called from within an expression. The method returns a value that takes its place in the expression, allowing further operations to be performed on the value; also called a function
Start Sidebar
Matters of Style
Program Formatting

As far as the compiler is concerned, there is very little form you have to follow when writing Visual Basic statements. You can place more than one statement on a line and you can place a statement anywhere on a line. The compiler needs blanks (or comments or new lines) to separate important symbols. However, it is extremely important that your programs be readable, both for your sake and for the sake of anyone else who has to examine them.

When you write an outline for an English paper, you follow certain rules of indentation to make it readable. These same kinds of rules can make your programs easier to read. It is much easier to spot a mistake in a neatly formatted program than in a messy one. Thus you should keep your program neatly formatted while you are working on it. If you've gotten lazy and let your program become messy while making a series of changes, take the time to straighten it up. Often the source of an error becomes obvious during the process of formatting the code.

Take a look at the following program for computing the cost per square foot of a house. Although it compiles and runs correctly, it does not conform to any formatting standards.

' HouseCost program
' This program computes the cost per square foot of
' living space for a house, given the dimensions of
' the house, the number of stories, the size of the
' nonliving space, and the total cost less land
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
Inherits System.WinForms.Form
Const H_WIDTH As Double = 30R
Const H_LENGTH As Double = 40R ' Length of house
Const H_STORIES As Double = 2.5 ' Number of full stories
Const NON_LIVING_SPACE As Double = 825R ' Garages, closets, etc.
Const PRICE As Double = 150000R ' Selling price less land
Dim grossFootage As Double ' Total square footage
Dim livingFootage As Double ' Living area
Dim costPerFoot As Double ' Cost / foot of living area
Public Sub New()
MyBase.New()
Form1 = Me
' This call is required by the Win Form Designer.
InitializeComponent()
   grossFootage = H_LENGTH * H_WIDTH * H_STORIES ' Compute gross footage
livingFootage = grossFootage - NON_LIVING_SPACE ' Compute net footage
costPerFoot = PRICE / livingFootage
lblHouseCost.Text = "Cost per square foot is " & costPerFoot
' TODO: Add any initialization after the InitializeComponent() call
End Sub
' Form overrides dispose to clean up the component list.
Public Overrides Sub Dispose()
MyBase.Dispose()
components.Dispose()
End Sub
#Region " Windows Form Designer generated code"
# End Region
End Class

Now look at the same program with proper formatting:

' *****************************************************
' HouseCost program
' This program computes the cost per square foot of
' living space for a house, given the dimensions of
' the house, the number of stories, the size of the
' nonliving space, and the total cost less land
' *****************************************************

Imports System.ComponentModel               ' Import component handling methods
Imports System.Drawing                      ' Import graphics methods
Imports System.WinForms                     ' Import Form methods, types

Public Class Form1                          ' Create a form for output
    Inherits System.WinForms.Form

    Const H_WIDTH As Double = 30R           ' Width of house
    Const H_LENGTH As Double = 40R          ' Length of house
    Const H_STORIES As Double = 2.5         ' Number of full stories
    Const NON_LIVING_SPACE As Double = 825R ' Garages, closets, etc.
    Const PRICE As Double = 150000R         ' Selling price less land

    Dim grossFootage As Double              ' Total square footage
    Dim livingFootage As Double             ' Living area
    Dim costPerFoot As Double               ' Cost / foot of living area

    Public Sub New()                        ' Instantiate form for output
        MyBase.New()                        ' Instantiate new form
        Form1 = Me                          ' Use Me for easy form
                                            '   reference

        ' This call is required by the Win Form Designer.
        InitializeComponent()                ' Build form

        grossFootage = H_LENGTH * H_WIDTH * H_STORIES   ' Compute gross
                                                        ' footage
        livingFootage = grossFootage - NON_LIVING_SPACE ' Compute net
                                                        ' footage
        costPerFoot = PRICE / livingFootage  ' Compute cost per usable
                                             ' foot
        ' Display result on form
        lblHouseCost.Text = "Cost per square foot is " & costPerFoot
        ' TODO: Add any initialization after the InitializeComponent() call
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()                ' Close the window form
        components.Dispose()            ' Remove all components from memory

    End Sub

#Region " Windows Form Designer generated code "

#End Region

End Class

Need we say more?

End Sidebar


4.7 Additional String Operations

Now that we have introduced numeric types and method calls, we can take advantage of additional features of the String data type. Here we introduce three useful methods that operate on strings: Length, IndexOf, and Substring. The methods are value-returning methods.

The Length Method

The Length method, when applied to a String object, returns an Integer value that equals the number of characters in the string. If myName is a String object, a call to the Length method looks like this:

myName.Length()

The Length method requires no parameters to be passed to it, but you still must use parentheses to signify an empty parameter list. Because Length is a value-returning method, the method call must appear within an expression:

Dim firstName As String        ' Local declarations
Dim fullName As String
Dim len As Integer

firstName = "Alexandra"
len = firstName.Length()   ' Assigns 9 to len
fullName = firstName & " Jones"
len = fullName.Length()    ' Assigns 15 to len

The IndexOf Method

The IndexOf method searches a string to find the first occurrence of a particular substring and returns an Integer value giving the result of the search. The substring, passed as a parameter to the method, can be a literal string or a String expression. If str1 and str2 are of type String, the following are valid method calls with each returning an integer

str1.IndexOf("the")  str1.IndexOf(str2)   str1.IndexOf(str2 & "abc")

In each case above, str1 is searched to see if the specified substring can be found within it. If so, the method returns the position in str1 where the match begins. (Positions are numbered starting at 0, so the first character in a string is in position 0, the second is in position 1, and so on.) For a successful search, the match must be exact, including identical capitalization. If the substring could not be found, the method returns the value -1.

Given the code segment

Dim phrase As String
Dim position As Integer
phrase = "The dog and the cat"

then the statement

position = phrase.IndexOf("the")

assigns to position the value 12, whereas the statement

position = phrase.IndexOf("rat")

assigns to position the value -1, because there was no match.

The parameter to the IndexOf method can also be a Char value. In this case, IndexOf searches for the first occurrence of that character within the string and returns its position (or -1, if the character was not found). For example, the code segment

Dim theString As String

theString = "Abracadabra"
position = theString.IndexOf("a"c)

assigns the value 3 to position, which is the position of the first occurrence of a lowercase a in theString. (Remember the first position is 0.)

Below are some more examples of calls to the IndexOf method, assuming the following code segment has been executed:

Dim str1 As String
Dim str2 As String

str1 = "Programming and Problem Solving"
str2 = "gram"

Method Call | Value Returned by Method
str1.IndexOf("and") | 12
str1.IndexOf("Programming") | 0
str2.IndexOf("and") | -1
str1.IndexOf("Pro") | 0
str1.IndexOf("ro" & str2) | 1
str1.IndexOf("Pr" & str2) | -1
str1.IndexOf(" ") | 11

Notice in the fourth example that there are two copies of the substring "Pro" in str1, but IndexOf returns only the position of the first copy. Also notice that the copies can be either separate words or parts of words—IndexOf merely tries to match the sequence of characters given in the parameter list. The final example demonstrates that the parameter can be as simple as a single character, even a single blank.

The Substring Method

The Substring method returns a particular substring of a string. Assuming myString is of type String, here is a sample method call:

myString.Substring(5, 20)

The parameters are integers that specify positions within the string. The method returns the piece of the string that starts with the position specified by the first parameter and continues for the number of characters specified by the second parameter. Below are some examples, assuming the statement

myString = "Programming and Problem Solving"

Method Call | String Contained in Value Returned by Method
myString.Substring(0, 7) | "Program"
myString.Substring(7, 8) | "ming and"
myString.Substring(11, 1) | ""
myString.Substring(24, 7) | "Solving"
myString.Substring(24, 1) | "S"

In the third example in the preceding list, specifying the second parameter to be the same as the first produces the empty string as the result. The last example illustrates how to obtain a single character from a given position in the string. If either of the parameters specifies a position beyond the end of the string, or if the second parameter is smaller than the first, the call to Substring results in an error message. One way to avoid such errors is to write the call to Substring in the following form. Here, start is an Integer variable containing the starting position, and len is another Integer variable containing the length of the desired substring.

myString.Substring(start, Min(start + len, myString.Length())

Recall from our discussion of Visual Basic's math methods that Min returns the smaller of its two parameters. If start + len is accidentally greater than the length of the string, Min returns the length of myString instead. We have thus ensured that the second parameter in the call to Substring can be no greater than the length of myString. We are assuming start is less than the length of the string, but we can use the same sort of formula as the first parameter if we aren't certain that this assumption is valid.

Because Substring returns a value of type String, you can use it with the concatenation operator (&) to copy pieces of strings and join them together to form new strings. The IndexOf and Length methods can be useful in determining the location of a piece of a string to be passed to Substring as parameters.

Here is a code segment that uses several of the String operations:

fullName = "Jonathan Alexander Peterson"
startPos = fullName.IndexOf("Peterson")
name = "Mr. " & fullName.Substring(startPos, fullName.Length() - startpos)

This code assigns "Mr. Peterson" to name when it is executed. First, it stores a string into the variable fullName, and then it uses IndexOf to locate the start of the name Peterson within the string. Next, it builds a new string by concatenating the literal "Mr. " with the characters Peterson, which are copied from the original string by calculating how many characters are in Peterson. As we see in later chapters, string operations are an important aspect of many computer programs.

Start Sidebar
Software Engineering Tip
Understanding before Changing

When you are in the middle of getting a program to run and you come across an error, it's tempting to start changing parts of the program to try to make it work. Don't! You'll nearly always make things worse. It's essential that you understand what is causing the error and that you carefully think through the solution. The only thing you should try is running the program with different data to determine the pattern of the unexpected behavior.

There is no magic trick that can automatically fix a program. If the compiler tells you that a parenthesis or an End Sub is missing, you need to examine the program in light of the syntax rules and determine precisely what the problem is. Perhaps you accidentally typed a left parenthesis instead of a right parenthesis. Or maybe you accidentally deleted the End Sub.

If the source of the problem isn't immediately obvious, a good rule of thumb is to leave the computer and go somewhere you can quietly look over a printed copy of the program. Studies show that people who do all their debugging away from the computer actually get their programs to work in less time and in the end produce better programs than those who continue to work on the machine—more proof that there is still no mechanical substitute for human thought.[1]

End Sidebar
Start Sidebar
Problem-Solving Case Study
Map Measurements

Problem You're spending a day in the city. You plan to visit the natural history museum, a record store, a gallery, and a bookshop, and then go to a concert. You have a tourist map that shows where these places are located. You want to determine how far apart they are and how far you'll walk during the entire day. Then you can decide when it would be better to take a taxi. According to the map's legend, one inch on the map equals one quarter of a mile on the ground.

Output The distance between each of the places and the total distance, rounded to the nearest tenth of a mile. The values on which the calculations are based also should be displayed for verification purposes.

Discussion You can measure the distances between two points on the map with a ruler. The program must output miles, so you need to multiply the number of inches by 0.25. You then write down the figure, rounded to the nearest tenth of a mile. When you've done this for each pair of places, you add the distances to get the total mileage.

What are the objects in our by-hand solution? Distances. We can represent each distance as a floating-point number. The operations on the objects are the same as those that we did by hand.

The only tricky part is how to round a value to the nearest tenth of a mile. This is easy to do in our head, but how do we calculate it in our program? To round to the nearest tenth, we first multiply the value by 10, round the result to the nearest integer, and then divide by 10 again. For example, if doubleValue contains 5.162, then

CDbl(CInt(5.162 * 10R + 0.5) / 10R)

gives 5.2.

Let's treat all of the quantities as named constants so that it is easier to change the program later. From measuring the map, you know that the distance from the museum to the record store is 1.5 inches, then 2.3 inches to the gallery and 5.9 inches more to the bookshop. From the bookshop to the concert is 4.0 inches. Our distance objects are represented by these constants.

...........................................................................
Define Constants

 DISTANCE1 = 1.5
 DISTANCE2 = 2.3
 DISTANCE3 = 5.9
 DISTANCE4 = 4.0
 SCALE = 0.25

...........................................................................
Initialize the Total Miles

 Set totMiles to 0.0

...........................................................................
Design the Form

 Create a form with four labels to hold the four distances.

...........................................................................
Compute Miles for Each Distance on the Map

 Set miles to CDbl(CInt(DISTANCE1 * 10R + 0.5) / 10R)
 Display DISTANCE1, miles to one decimal place
 Add miles to totMiles
 Set miles to CDbl(CInt(DISTANCE2 * 10R + 0.5) / 10R)
 Display DISTANCE2, miles to one decimal place
 Add miles to totMiles
 Set miles to CDbl(CInt(DISTANCE3 * 10R + 0.5) / 10R)
 Display DISTANCE3, miles to one decimal place
 Add miles to totMiles
 Set miles to CDbl(CInt(DISTANCE4 * 10R + 0.5) / 10R)
 Display DISTANCE4, miles to one decimal place
 Add miles to totMiles

...........................................................................
Display the Total Miles

 Display totMiles miles on the form

From the algorithms, we can create tables of constants and variables that help us write the declarations in the program.

Constants
Name | Value | Description
DISTANCE1 | 1.5 | Measurement for the first distance
DISTANCE2 | 2.3 | Measurement for the second distance
DISTANCE3 | 5.9 | Measurement for the third distance
DISTANCE4 | 4.0 | Measurement for the fourth distance
SCALE | 0.25 | Scale factor of map
Variables
Name | Data Type | Description
out | Form | Form for output display
totMiles | Double | Total of rounded mileage
miles | Double | An individual rounded mileage

Now we're ready to write the program. Let's call it Walk. We take the declarations from the tables and create the executable statements from the algorithm. We have labeled the output with explanatory messages. We've also added comments where needed.

Here is the form design for the program:

Click To expand

The form consists of five labels, named lblDistance1, lblDistance2, lblDistance3, lblDistance4, and lblTotMiles, respectively. The properties you should set for the form and for each label are:

...........................................................................
Form1

 Size = 520, 248
 Text = Walk

...........................................................................
lblDistance1

  Font = Tahoma, Bold, 11 pt.
  Location = 0, 16
  Size = 496, 24

...........................................................................
lblDistance2

  Font = Tahoma, Bold, 11 pt.
  Location = 0, 56
  Size = 496, 24

...........................................................................
lblDistance3

  Font = Tahoma, Bold, 11 pt.
  Location = 0, 96
  Size = 496, 24
...........................................................................
lblDistance4

  Font = Tahoma, Bold, 11 pt.
  Location = 0, 136
  Size = 496, 24

...........................................................................
lblTotMiles

  Font = Tahoma, Bold, 11 pt.
  Location = 0, 176
  Size = 496, 24

Now here's the program:

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form

Const DISTANCE1 As Double = 1.5       ' Measurement for first distance
Const DISTANCE2 As Double = 2.3       ' Measurement for second distance
Const DISTANCE3 As Double = 5.9       ' Measurement for third distance
Const DISTANCE4 As Double = 4R        ' Measurement for fourth distance
Const MAP_SCALE As Double = 0.25      ' Map SCALE

Dim totMiles As Double                ' Total of rounded mileages
Dim miles As Double                   ' An individual rounded mileage

Public Sub New()
    MyBase.New
    Form1 = Me
    InitializeComponent()
    totMiles = 0R

    ' Compute and display miles for each distance on the map
    ' Output will not display .0 amounts
    miles = CDbl(CInt(DISTANCE1 * MAP_SCALE * 10R + 0.5) / 10R)
    lblDistance1.Text = "For a measurement of " & DISTANCE1 & _
                        " the first distance is " & miles & " mile(s) _
                        long."
    totMiles = totMiles + miles
    miles = CDbl(CInt(DISTANCE2 * MAP_SCALE * 10R + 0.5) / 10R)
    lblDistance2.Text = "For a measurement of " & DISTANCE2 & _
                        " the second distance is " & miles & " _
                        mile(s) long."
    totMiles = totMiles + miles
    miles = CDbl(CInt(DISTANCE3 * MAP_SCALE * 10R + 0.5) / 10R)
    lblDistance3.Text = "For a measurement of " & DISTANCE3 & _
                        " the third distance is " & miles & " mile(s) _
                        long."

    totMiles = totMiles + miles
    miles = CDbl(CInt(DISTANCE4 * MAP_SCALE * 10R + 0.5) / 10R)
    lblDistance4.Text = "For a measurement of " & DISTANCE4 & _
                        " the fourth distance is " & miles & " _
                        mile(s) long."
    totMiles = totMiles + miles
    lblTotMiles.Text = "Total mileage for the day is " & totMiles & " _
                       miles."

End Sub

    ' Form overrides dispose to clean up the component list.
    Overrides Public Sub Dispose()
        MyBase.Dispose
        components.Dispose
    End Sub

#Region " Windows Form Designer generated code "

#End Region

End Class

The output from the program is

Click To expand
End Sidebar

[1]Basili, V.R., Selby, R.W., Comparing the Effectiveness of Software Testing Strategies, IEEE Transactions on Software Engineering, Vol SE-13, No. 12, pp. 1278–1296, Dec. 1987.


Testing and Debugging

  1. Watch out for which operator you use for division. The slash (/) is for normal division, and the backslash (\) is for integer division.

  2. Double-check every expression according to the precedence rules to be sure that the operations are performed in the desired order.

  3. Avoid mixing integer and floating-point values in expressions. If you must mix them, consider using type conversion functions to reduce the chances of mistakes.

  4. For each assignment statement, check that the expression result has the same data type as the variable to the left of the assignment operator (=). If not, consider using a type conversion function for clarity and safety. And remember that storing a floating-point value into an Integer variable will either round the integer down or round the integer up to the nearest whole number.

  5. If a program is producing erroneous results and all of its expressions appear to be correct, check to see if any of them can result in integer overflow. Also check to see if they contain any unintentional conversions.

  6. For every class library you use in your program, be sure to use an Imports declaration.

  7. Examine each method call to see that you have the correct number of parameters and that the data types of the parameters are correct.

  8. If the cause of an error in a program is not obvious, leave the computer and study a printed listing. Change your program only after you understand the source of the error.


Summary of Classes

In this chapter we introduced value-returning methods, so we add this form of method to our table that summarizes the classes discussed in the chapter.

Namespace Name
Class Name |  |  | Comments
Math |  |  |
 | Value Returning Class Methods: |  |
 |  | Abs(Integer) | Returns Integer
 |  | Abs(Long) | Returns Long
 |  | Abs(Short) | Returns Short
 |  | Abs(Double) | Returns Double
 |  | Cos(Double) | Returns Double
 |  | Log(Double) | Returns Double
 |  | Max(Integer, Integer) | Returns Integer
 |  | Max(Long, Long) | Returns Long
 |  | Max(Short, Short) | Returns Short
 |  | Max(Double, Double) | Returns Double
 |  | Min(Integer, Integer) | Returns Integer
 |  | Min(Long, Long) | Returns Long
 |  | Min(Short, Short) | Returns Short
 |  | Min(Double, Double) | Returns Double
 |  | Pow(Double, Double) | Returns Double
 |  | Sin(Double) | Returns Double
 |  | Sqrt(Double) | Returns Double
String |  |  |
 | Value-Returning Class Methods: |  |
 |  | Length() | Returns Integer
 |  | IndexOf(String) | Returns Integer
 |  | IndexOf(Char) | Returns Integer
 |  | Substring(Integer, Integer) | Returns String


Summary

Visual Basic provides built-in numeric data types, of which the most commonly used are Integer and Double. The integral types are based on the mathematical integers, but the computer limits the range of integer values that can be represented. The floating-point types are based on the mathematical notion of real numbers. As with integers, the computer limits the range of floating-point numbers that can be represented. In addition, it limits the number of digits of precision in floating-point values. We can write literals of type Double in several forms, including scientific (E) notation. Visual Basic provides the standard mathematical operations to go with these data types: addition (+), subtraction (-), multiplication (*), division (/), and remainder (mod).

Mixing values of the integer and floating-point types in an expression can result in automatic type conversion to achieve compatibility between the operands of all of the operators. This is the case if you turn off the compiler property Option Strict. If you aren't careful, these automatic conversions can have unanticipated results. It is best to use type conversion functions whenever you need to mix types within expressions. With Option Strict on, which is the default setting for the Visual Basic compiler, automatic type conversion does not take place.

Much of the computation of a program is performed in arithmetic expressions. Expressions can contain more than one operator. The order in which the operations are performed is determined by precedence rules. In arithmetic expressions, the unary operations (such as negation) are performed first, then multiplication, division, and modulus (remainder) are performed, and lastly addition and subtraction. Multiple arithmetic operations of the same precedence are grouped from left to right. You can use parentheses to override the precedence rules.

Not only should the output produced by a program be easy to read, but the format of the program itself should be clear and readable. A consistent style that uses indentation, blank lines, and spaces within lines helps you (and other programmers) understand and work with your programs.


Quick Check

Q:

1. Write a local Visual Basic constant declaration that gives the name PI to the value 3.14159. (p. 127)

1. Const PI As Double = 3.14159

Q:

2. Declare an Integer variable named count and a Single variable named sum. (pp. 127–128)

2. Dim count As Integer Dim sum As Single

Q:

3. You want to divide 9 by 5.

  1. How do you write the expression if you want the result to be the floating-point value 1.8? (pp. 129–130)

  2. How do you write it if you want only the integer quotient? (pp. 129–130)

3. 9R / 5R or CDbl(9 / 5)

Q:

4. What is the value of the following Visual Basic expression?

5 mod 2

(pp. 129–130)

4. The value is 1.

Q:

5. What is the result of evaluating the expression

(p. 134)

5. The result is 3.

Q:

6. How would you write the following formula as a Visual Basic expression that produces a floating-point value as a result? (pp. 136–137)

6. 9R / 5R * c + 32R

Q:

7. Add type conversion functions to the following statements to make the type conversions clear and explicit. Your answers should produce the same results as the original statements. (pp. 137–138)

  1. someDouble = 5 + someInt

  2. someInt = 2.5 * someInt / someDouble

7. a. someDouble = CDbl(5 + someInt) b. someInt = CInt(2.5 * CDbl(someInt) / someDouble)

Q:

8. You want to compute the square root and absolute values of some floating-point numbers. Which Visual Basic methods would you use? (pp. 140–141)

8. Math.Sqrt and Math.Abs

Q:

9. If the String variable str contains the string "Now is the time", what is the result of the following expression? (pp. 144–145)

str.Length() & " " & str.Substring(1,2)

9. 15 ow

Q:

10. Reformat the following program to make it clear and readable. (pp. 141–143)

' *************************************************************
'                   SumProd program
'  This program computes the sum and product of two integers
' **************************************************************

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
Public Class Form1
Inherits System.WinForms.Form
Const INT2 As Integer = 8
Const INT1 As Integer = 20
Public Sub New()
        MyBase.New()
     Form1 = Me
       ' This call is required by the Win Form Designer.
 InitializeComponent()
lblOut.Text = "The sum of " & INT1 & " and " _
        & INT2 & " is " & INT1 + INT2 : lblOut.Text = _
        "Their product is " & (INT1 * INT2)
 ' TODO: Add any initialization after the  InitializeComponent()
 ' call
 End Sub
 ' Form overrides dispose to clean up the component list.
 Public Overrides Sub Dispose()

   MyBase.Dispose()
 components.Dispose()
 End Sub
#End Region
End Class

Q:

11. What should you do if a program fails to run correctly and the reason for the error is not immediately obvious? (p. 147)

11. Print out the program, leave the computer, and study the program until you understand the cause of the problem. Then correct the algorithm and the program as necessary before you go back to the computer and make any changes to the program file.

Answers

A:

1. Const PI As Double = 3.14159

A:

2. Dim count As Integer
Dim sum As Single

A:

3. 9R / 5R or CDbl(9 / 5)

A:

4. The value is 1.

A:

5. The result is 3.

A:

6. 9R / 5R * c + 32R

A:

7.

  1. someDouble = CDbl(5 + someInt)

  2. someInt = CInt(2.5 * CDbl(someInt) / someDouble)

A:

8. Math.Sqrt and Math.Abs

A:

9. 15 ow

A:

10.

' *************************************************************
'                    SumProd program
'   This program computes the sum and product of two integers
' **************************************************************

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
     Inherits System.WinForms.Form
     Const INT2 As Integer = 8
     Const INT1 As Integer = 20

     Public Sub New()
          MyBase.New()
          Form1 = Me
          ' This call is required by the Win Form Designer.
          InitializeComponent()
          lblOut.Text = "The sum of " & INT1 & " and " _
                        & INT2 & " is " & INT1 + INT2
     lblOut.Text = "Their product is " & (INT1 * INT2)
     ' TODO: Add any initialization after
     ' InitializeComponent()

     End Sub
     ' Form overrides dispose to clean up the component list.
     Public Overrides Sub Dispose()
          MyBase.Dispose()
          components.Dispose()
     End Sub
     #Begin Region
     #End Region
End Class

A:

11. Print out the program, leave the computer, and study the program until you understand the cause of the problem. Then correct the algorithm and the program as necessary before you go back to the computer and make any changes to the program file.


Exam Preparation Exercises

    Chapter 4

  1. Mark the following constructs either valid or invalid. Assume all variables are of type Integer.

    Valid

    Invalid

    1. x * y = c

    2. y = con

    3. Private Final X As Integer : 10

    4. Dim x As Integer

    5. a = b mod c

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

  2. If alpha and beta are Integer variables with alpha = 4 and beta = 9, what value is stored into alpha in each of the following? Answer each part independently of the others.

    1. alpha = 3 * beta

    2. alpha = alpha + beta

    3. alpha = alpha + 1

    4. alpha = alpha / beta

    5. alpha = alpha - 111

    6. alpha = alpha + alpha

    7. alpha = beta mod 6

  3. Compute the value of each legal expression. Indicate whether the value is an integer or a floating-point value. If the expression is not legal, explain why.

    Integer

    Floating-Point

    1. 10R / 3R + 5 * 2

    2. 10 mod 3 + 5 mod 2

    3. 10 / 3 + 5 / 2

    4. 12.5 + (2.5 / (6.2 / 3.1))

    5. -4 * (-5 + 6)

    6. 13 mod 5 / 3

    7. (10R / 3R mod 2) / 3

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

    • _____

  4. What value is stored into the Integer variable result in each of the following?

    1. result = 15 mod 4

    2. result = 7 / 3 + 2

    3. result = 2 + 7 * 5

    4. result = 45 / 8 * 4 + 2

    5. result = 17 + (21 mod 6) * 2

    6. result = CInt(4.5 + 2.6 + 0.5)

  5. If a and b are Integer variables with a = 5 and b = 2, what output does each of the following statements produce?

    1. lblLabel.Text = "a = " & a & "b = " & b

    2. lblLabel.Text = "Sum:" & a + b

    3. lblLabel.Text = "Sum: " & a + b

    4. lblLabel.Text = a / b & " feet"

  6. Translate the following Visual Basic code into algebraic notation. (All variables are Double variables.)

    y = -b + Sqrt(b * b - 4.0 * a * c)

  7. Given the following program fragment:

    Dim i As Integer
Dim j As Integer
Dim z As Double

i = 4
j = 17
z = 2.6

    determine the value of each expression below. If the result is a floating-point value, include a decimal point in your answer.

    1. i / CDbl(j)

    2. 1R / i + 2

    3. z * j

    4. i + j mod I

    5. (1 / 2) * i

    6. 2 * i * j - i

    7. j / 2

    8. 2 * 3 - 1 mod 3

    9. i mod j / i

    10. CInt(z + 0.5)

  8. Evaluate the following expressions. If the result is a floating-point number, include a decimal point in your answer.

    1. Abs(-9.1)

    2. Sqrt(49.0)

    3. 3 * CInt(7.8) + 3

    4. Math.Pow(4R, 2R)

    5. Sqrt(CDbl(3 * 3 + 4 * 4))

    6. Sqrt(Abs(-4.0) + Sqrt(25.0))

  9. Given the statements

    Dim heading As String
Dim str As String
heading = "Exam Preparation Exercises"

    what is the output of each code segment below?

    1. lblOut.Text = heading.Length()

    2. lblOut.Text = heading.Substring(6,10)

    3. lblOut.Text = heading.IndexOf("Ex")

    4. str = heading.Substring(2, 24)
      lblOut.Text = heading.Substring(str.IndexOf("Ex")

    5. str = heading.Substring(heading.IndexOf("Ex") + 2,
      heading.Length())
      lblOut.Text = str.IndexOf("Ex")

  10. Formatting a program incorrectly causes an error. (True or False?)


Programming Warm-Up Exercises

    Chapter 4

  1. Write an assignment statement to calculate the sum of the numbers from 1 through n using Gauss's formula:

    Store the result into the Integer variable sum.

  2. Given the declarations

    Dim i As Integer
Dim j As Integer
Dim x As Double
Dim y As Double

    write a valid Visual Basic expression for each of the following algebraic expressions.

    2. (x + y)(x - y)

  3. Given the declarations

    Dim i As Integer
Dim n As Long
Dim x As Double
Dim y As Double

    write a valid Visual Basic expression for each of the following algebraic expressions. Use calls to math methods wherever they are useful.

    1. | i | (absolute value)

    2. | n |

    3. | x + y |

    4. | x | + | y |

    5. x3 / x + y

  4. Write expressions to compute both solutions for the quadratic formula. The formula is

    The ± means "plus or minus" and indicates that there are two solutions to the equation, one in which the result of the square root is added to -b and one in which the result is subtracted from -b. Assume all variables are Single variables.

  5. Enter the following program into your computer and run it. In the initial comments, replace the items within parentheses with your own information. (Omit the parentheses.)

    ' Programming Assignment One
' (your name)
' (date program was run)
' (description of the problem)
' ***************************************

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
     Inherits System.WinForms.Form
     Const DEBT As Double = 300R         ' Original value owed
     Const PMT As Double = 22.4          ' Payment
     Const INT_RATE As Double = 0.02     ' Interest rate

     Dim charge As Double                ' Interest times debt
     Dim reduc As Double                 ' Amount debt is reduced
     Dim remaining As Double             ' Remaining balance

     Public Sub New()
         MyBase.New()

         Form1 = Me

         ' This call is required by the Win Form Designer.
         InitializeComponent()
         charge = INT_RATE * DEBT
         reduc = PMT - charge
         remaining = DEBT - reduc
         lblOut.Text = "Payment: " & PMT & " Charge: " & charge & _
                       " Balance owed: " & remaining
         ' TODO: Add any initialization after InitializeComponent()
         ' call
     End Sub

     ' Form overrides dispose to clean up the component list.
     Public Overrides Sub Dispose()
         MyBase.Dispose()
         components.Dispose()
     End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

  6. Enter the following program into your computer and run it. Add comments, using the pattern shown in Exercise 5 above. (Notice how hard it is to tell what the program does without the comments.

    Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form
    Const TOT_COST As Integer = 1376
    Const POUNDS As Integer = 10
    Const OUNCES As Integer = 12

    Dim totOz As Integer
    Dim uCost As Double

    Public Sub New()
        MyBase.New()

        Form1 = Me

        ' This call is required by the Win Form Designer.
        InitializeComponent()

        totOz = 16 * POUNDS
        totOz = totOZ + OUNCES
        uCost = TOT_COST / totOz
        lblOut.Text = "Cost per unit: " & uCost
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

  7. Complete the following Visual Basic program. The program should find and output the perimeter and area of a rectangle, given the length and the width, in a label on Form1. Be sure to label the output, and don't forget to use comments.

    ' ****************************************************
' Rectangle program
' This program finds the perimeter and the area
' of a rectangle, given the length and width
' ****************************************************

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form

    Dim length As Double
    Dim wdth As Double

    Dim perimeter As Double
    Dim area As Double

    Public Sub New()
        MyBase.New()

        Form1 = Me

        ' This call is required by the Win Form Designer.
        InitializeComponent()
        length = 10.7
        wdth = 5.2
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

  8. Write an expression whose result is the position of the first occurrence of the characters "res" in a String variable named sentence. If the variable contains the first sentence of this question, then what is the result? (Look at the sentence carefully.)

  9. Write a sequence of Visual Basic statements to output the positions of the second and third occurrences of the characters "res" in the String variable named sentence. You may assume that there are always at least three occurrences in the variable. (Hint: Use the substring method to create a new string whose contents are the portion of sentence following an occurrence of "res".)


Programming Problems

  1. Visual Basic provides a special data type, called a structure, that can consist of subtypes defined by the user. The structure is primarily used to create new, user-defined data types, but Visual Basic has some fundamental data stored in structures. One such structure that is found in the System class is Int32. Among this structure's members are two that hold values that define the minimum and maximum values for integer variables. These members are System.Int32.MaxValue and System.Int32.MinValue. Write an application to display these values. The output should identify which value is System.Int32.MaxValue and which value is System.Int32.MinValue. Be sure to include appropriate comments in your program, and use indentation as we do in the programs in this chapter. Each of these types defines similar constants, so you may want to extend your program to display System.Long.MinValue, and so on, just to learn what the actual maximum and minimum numbers are for each of the primitive types.

  2. Write an application that outputs three labels as follows:

    7 \ 4 using integer division equals <result>
7 / 4 using floating-point division equals <result>
7 mod 4 equals <result>

    where <result> stands for the result computed by your program. Use named constants for 7 and 4 everywhere in your program (including the output statements) to make the program easy to modify. Be sure to include appropriate comments in your program, choose meaningful identifiers, and use indentation as we do in the programs in this chapter.

  3. Write a Visual Basic application that converts a Celsius temperature to its Fahrenheit equivalent. The formula is:

    Make the Celsius temperature a named constant so that its value can be changed easily. The program should print both the value of the Celsius temperature and its Fahrenheit equivalent, with appropriate identifying messages. Display the output in a single label. Be sure to include appropriate comments in your program, choose meaningful identifiers, and use indentation as we do in the programs in this chapter.

  4. Write an application to calculate the diameter, the circumference, and the area of a circle with a radius of 6.75. Assign the radius to a Single variable, and then output the radius with an appropriate message. Declare a named constant PI with the value 3.14159. The program should output the diameter, the circumference, and the area, each in a separate label, with identifying labels. Print each value to five decimal places using the FormatNumber function. Be sure to include appropriate comments in your program, choose meaningful identifiers, and use indentation as we do in the programs in this chapter.

  5. You have bought a car, taking out a loan with an annual interest rate of 9%. You expect to make 36 monthly payments of $165.25 each. You want to keep track of the remaining balance you owe after each monthly payment. The formula for the remaining balance is:

    where

    • Balk = balance remaining after the kth payment

    • k = payment number (1, 2, 3, …)

    • pmt = amount of the monthly payment

    • i = interest rate per month (annual rate ÷ 12)

    • n = total number of payments to be made

Write an application to calculate and display the balance remaining after the first, second, and third monthly car payments. Before showing these three results, the program should output the values on which the calculations are based (monthly payment, interest rate, and total number of payments). Display an identifying message with each numerical result. Be sure to include appropriate comments in your program, choose meaningful identifiers, and use indentation as we do in the programs in this chapter.


Case Study Follow-Up Exercises

    Chapter 4

  1. What is the advantage of using named constants instead of literal constants in the Case Study Walk program?

  2. Modify the Walk program to include a round-off factor so that the rounding of miles can be modified easily. Currently, the program uses a literal constant (10.0) in several places to round miles to the nearest tenth, requiring us to make multiple changes if we want a different round-off factor.

  3. Should the round-off factor in Question 2 be a constant or a variable? Explain.

  4. In the Walk program, a particular pattern of statements is repeated four times with small variations. Identify the repeating pattern. Next, circle those parts of the statements that vary with each repetition. Having done this, now modify the Walk program to work with a fifth distance measurement.


Chapter 5: Event-Driven Input and Software Design Strategies

Overview

Goals

  • To be able to place data entry fields on a form.

  • To be able to place a button on a form.

  • To be able to write statements to read values from a data entry field into a program when a button is pressed.

  • To know how to convert strings containing numbers into numeric types.

  • To be able to write appropriate prompting labels for interactive programs.

  • To be able to describe the basic principles of object-oriented design and functional decomposition.

  • To be able to apply the object-oriented design strategy to solve a simple problem.

  • To be able to apply the functional decomposition strategy to solve a simple problem.

  • To be able to take a design for a software solution and code it in Visual Basic, using self-documenting code.

A program needs data on which to operate. We have been writing all the data values in the program itself, in literal and named constants. If this were the only way we could enter data, we would have to rewrite a program each time we wanted to apply it to a different set of values. In this chapter, we look at ways of entering data into a program while it is running.

Once we know how to input data, process the data, and output the results, we can begin to think about designing more complicated programs. We have talked about general problem-solving strategies and writing simple programs. For a simple program, it's easy to choose a strategy, write the algorithm, and code the program. But as problems become more complex, we have to use a more organized approach. In the second part of this chapter, we look at two general strategies for developing software: object-oriented design and functional decomposition.


5.1 Getting Data into Programs

One of the biggest advantages of computers is that a program can be used with many different sets of data. To do so, we must keep the data separate from the program until the program is executed. Then instructions in the program copy values from the data set into variables in the program. After storing these values into the variables, the program can perform calculations with them (see Figure 5.1).

Click To expand
Figure 5.1: Separating the data from the program

The process of placing values from an outside data set into variables in a program is called input. In widely used terminology, the computer is said to read outside data into the variables. The data for the program can come from an input device (such as a keyboard or mouse) or from a file on an auxiliary storage device. We look at file input in Chapter 9; here we consider data entered from the keyboard.


5.2 Entering Data Using Text Boxes in a Form

If the Payroll program in Chapter 1 had used interactive input, the form in Figure 5.2 would have been appropriate.

Click To expand
Figure 5.2: Possible data entry form for the Payroll program

This form contains three labels and a button (Done) in the middle of the form. The right side of the form has text boxes.

Button
A component of a form that fires an event (called the button's Click event) when the user clicks on it with the mouse
Text box
A component of a form in which the user can type a value. The user must first place the cursor in the text box by clicking inside the box.

The user clicks within a text box to position the cursor there and then types a value into the box using the keyboard. As shown in Figure 5.2, the program displays each text box with an initial value that the user can delete before entering a value. Such an initial value is called a default value. A program can also be written to display a text box without a default value. Including a default value is one way to show the user how to type data within the text box.

The user can enter the data into the text boxes in any order and can go back to any text box and correct a mistyped value. While you can program Visual Basic to respond to each keystroke and mouse click as the user performs them, you don't have to. These events are accessible to the programmer if so desired, but for many simple data input programs, you will ignore them.

When the user clicks on the button, the button's Click event is raised and the event is handled by executing the code that is placed inside the event. For example, there could be code inside the Click event that stores the text in the text boxes to a set of variables and then clears the text boxes so that more data can be entered into them. Visual Basic then waits for the next event to occur. This process is shown in Figure 5.3.

Click To expand
Figure 5.3: Data entry process

Entering data in the manner that we have just described is typical of modern programs and is called a dialog. In a theatrical play, a dialog is an exchange between two characters. In the case of the computer, a dialog is an exchange between the user and a program. The program initiates the dialog by displaying a form, the user replies by entering data into the text boxes of the form and clicking a button, the program processes the data and responds by updating the form.

Dialog
A style of user interface in which the user enters data and then performs a separate action (such as clicking a button) when the entered values are ready to be processed by the program

If the program needs to have a series of values entered, it can again display the form in its initial state and the user replies again. This data entry cycle is called an event loop. The event loop continues until the user indicates that there is no more data to enter, perhaps by entering a special data value or clicking another button.

Event loop
The repetitive calling of an event handler to respond to a series of events, until some condition causes the program to exit the cycle

Later we consider the finer points of designing a dialog so that it is easy to use. Such considerations are especially important when a dialog involves entering data into multiple text boxes in a form. Until then, we focus our attention on a simple dialog using a form that contains a single text box, a label for the text box, and a button.


5.3 Adding a Text Box to a Form

Adding a text box to a form is very similar to adding a label to a form. The essential steps are the same, with only minor differences. Those steps are:

  1. Select TextBox from the toolbox and place it on a form.

  2. Set the appropriate properties for the text box.

Properties you might set for a text box include the font type and size, any default text that is placed in the text box, and its name. You can also set a property of the text box that will make it be the default control for the form, which means that the text box will have focus when the application is started.


5.4 Extracting a Value from a Text Box

When text is typed into a text box, the text is stored in the text box's Text property. The text is retrieved from the text box by referencing the Text property in a statement, such as an assignment statement. For example, if you declare a String variable called textContents, you can write

textContents = inputBox.Text

to store the characters currently contained in inputBox into textContents.

Note that we have not yet said anything about when or where to write a statement like the one above. We have considered the steps of creating the text box and taking data from it in isolation from the rest of the program. In the following sections, we examine how these steps are related to handling a button event that tells the program when the user is ready to have it look at the data. Before that, however, there is one more technique involving text boxes we would like to introduce.

We mentioned in the last section that default text can be placed in a text box's Text property at design time. However, once the value of a text box has been shown on the screen, it is often useful to be able to change its contents. For example, after processing the contents of the text box, the program may replace them with the default value so that the user knows that he or she can enter a new value. The text in a text box is replaced with an assignment into the text box's Text property. Here is an example:

inputBox.Text = "Replace me"

An assignment to the Text property of a text box immediately changes the contents of the text box on the screen. Figure 5.4 shows a form with one TextBox object.

Click To expand
Figure 5.4: A Form object with one TextBox

You might notice another similarity between a Label object and a TextBox object; a Label object also has a Text property that allows you to change its contents after it has been displayed on the screen. For example, you can write

lblLabel.Text = "Enter more data: "

which changes the text in lblLabel to the string, "Enter more data: ".

Given all of their similarities, it is important to remember the major distinction between a Label object and a TextBox object: The user cannot change what is displayed in a Label, but he or she can change the contents of a TextBox.


5.5 Creating a Button

Before you can handle a button event, you have to put a button on a form. This procedure is again similar to placing other controls on a form. Add a button to a form and then set its properties. The property you set to put a caption on a button is the button's Text property.

When a button is placed on a form, a set of events which can be handled are made available to the programmer. There are close to 40 events for a button, including the Click event. These events are pre-registered in Visual Basic, when using Visual Studio, meaning that when an application is running, the event, if raised, will be recognized by Visual Basic. This is in contrast to how events work in other languages, such as Java, where the programmer is responsible for most of the programming of events. In Visual Basic, all the programmer has to worry about is the code that is executed when the event is raised.

Let's take the form we designed in Section 5.4 and add a button to it. The button is placed below the text box and the caption is set to "Done". The button also needs a name, so we'll call it btnDone, following the convention of providing a control with a three-letter prefix that identifies the type of control

The result looks like this when the application is run:

Click To expand

Of course, when the user clicks on the btnDone button, nothing appears to happen. In fact, something does happen: An event is fired, but we have not written any code to handle the event. Since the button doesn't know what to do, it simply ignores the event. Now let's look at how to create the event-handling code.


5.6 Handling a Button Event

When a running application has a button on the form and the user clicks it, the event handler for the Click event is raised. But what should the event handler do? In this case, the event is a signal from the user that some data has been typed into a text box and is now ready for processing. Because we have just been exploring the mechanics of getting to this point in the program, rather than solving an actual problem, we don't know just yet what the data represents or what should be done with it.

Our discussion up to this point has put the cart before the horse. In solving a real problem, we begin by identifying the input data and what should be done with it. Then we design the user interface to enable the user to enter data and generate the events that cause the program to process it.

Let's look at a trivial problem that we can use to illustrate the design process for coding an event handler. Suppose we want the user to be able to enter a string and have it appear in a label on the form when he or she is finished typing. Here is a formal definition of the problem.

Input: A string that the user enters.

Output: The input string, displayed in a label.

Processing: Copy the input string from the data entry field to the label.

When does the program copy the input string to the label? Let's use a button called "Copy" to generate a Click event that will perform the copying. The Click event is the default event of a button, and a template for entering code to respond to the event is added to the code listing for the application when a button is placed on a form. The template looks like this:

Private Sub button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles button1.Click
End Sub

What happens after the program copies the string? The problem statement doesn't answer this question. We could end the program, but then the form disappears before the user has a chance to see the output. We need a second event to indicate when the user has finished looking at the output. Such an event could be generated by a second button, but we're not quite ready to handle events from multiple buttons. Instead, we can do what we've done in prior programs and let the user close the window to end the program. So we only have to write the code for one event handler—the button's Click event.

The event handler for the button simply copies the data from the input text box to the output label. For example,

textContents = inputField.Text
outputLabel.Text = textContents

We can even shorten these two lines to just one and eliminate the string variable textContents as follows:

outputLabel.Text = inputField.Text

This single line of code carries out the main action that we want to perform on the data. The button event handler that contains it is added by the Forms Manager, so all we have to do is insert this line into the body of the method.

Private Sub button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles button1.Click
   outputLabel().Text = inputField().Text
End Sub

After this method executes, it returns control to the CLR. If the user then closes the window, the window closing event calls the window-closing event handler, which, as you remember, is written automatically by the Forms Manager.

All that remains is to add the form objects to the form, assign the appropriate properties to the objects, and then show the form on the screen. The objects and the properties you need to set are:

Textbox Name: inputField
Textbox Font: 10 point, Bold
Textbox Text: ""

Label Name: outputLabel
Label Font: 10 point, Bold
Label Text: ""

Label Name: entryLabel
Label Font: 10 point, Bold
Label Text: Enter a string:

Button Name: Copy
Copy Text: Copy

Here is the complete program, which we call CopyString.

Public Class Form1
    Inherits System.Windows.Forms.Form
    ' This program displays a form with a data entry field and
    ' copies its contents to a label when the user clicks a
    ' button marked "Copy"

#Region " Windows Form Designer generated code "
    Public Sub New()
        MyBase.New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        If Not (components Is Nothing) Then
            components.Dispose()
        End If
    End Sub
    ' Code added by the system for creating form controls
    Private WithEvents inputField As System.Windows.Forms.TextBox
    Private WithEvents outputLabel As System.Windows.Forms.Label
    Private WithEvents entryLabel As System.Windows.Forms.Label

    Private WithEvents copy As System.Windows.Forms.Button

    ' Required by the Windows Form Designer

    Private Sub button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) _
                              Handles button1.Click
        ' Copies the text from the text box to the label
        outputLabel().Text = inputField().Text
    End Sub

#End Region

End Class

The program initially displays a form that looks like this:

Click To expand

After the user enters a string and clicks the copy button, the form appears as follows:

Click To expand

When the user closes the window, the program removes the form from the screen and quits. But what happens if the user doesn't close the window? What if the user types some more data and clicks the button again? The button sends another event to the event manager, which dutifully calls the event handler again, and the output label is updated with the new input value. The user can keep entering new data values until he or she closes the window. In future programs we see that this pattern is useful as a way to enter a series of data values. For the problem, however, our intent was to allow the user to enter just one value.

How can we prevent the user from entering another value? We can have Visual Basic disable the button so that it can't be clicked a second time. Many form objects have an Enabled property that makes the control unusable on the form when the property is set to False. We can thus write a second line in our event handler that calls this method for the form:

copy.Enabled = False

Now, as soon as the user clicks on the Copy button, the button's caption becomes gray, meaning the button has been disabled and cannot be clicked until the Enabled property is set to True. The only event that the user can then generate is closing the window.


5.7 Interactive Input/Output

In Chapter 1 we defined an interactive program as one in which the user communicates directly with the computer. Many of the programs that we write are interactive. There is a certain "etiquette" involved in writing interactive programs that has to do with instructions for the user to follow.

To get data into an interactive program, we begin with input prompts, labels that explain what the user should enter into an adjacent field. Without these messages, the user has no idea what data values to type. In many cases, a program also should display the data values typed in so that the user can verify that they were entered correctly. Displaying input values is called echo printing.

The amount of information you put into your prompts depends on who is going to be using a program. If you are writing a program for people who are not familiar with computers, the prompts should be more detailed. For example, "Type a four-digit part number, then click the button marked "Enter". If the program is going to be used frequently by the same people, you might shorten the prompt to "Enter PN".

Whether a program echo prints its input or not depends on how experienced the users are and on the task it is to perform. If the users are experienced and the prompts are clear, then echo printing is probably not required. If the users are novices or multiple values can be input at once, echo printing should be used. If the program inputs a large quantity of data and the users are experienced, rather than echo print the data, it may be stored in a separate file that can be checked after all of the data is input. We'll see how to store data into a file later in a later chapter.

When data is not entered in the correct form, a message that indicates the problem should be displayed. For users who haven't worked much with computers, it's important that these messages be informative and friendly. The message

ILLEGAL DATA VALUES!!!

is likely to upset even an experienced user. Moreover, it doesn't offer any constructive information. A much better message would be

That is not a valid part number.
Part numbers must be no more than four digits long.
Please reenter the number in its proper form:

In Chapter 10, we introduce statements that allow us to catch and handle erroneous data.


5.8 Converting Strings to Numeric Values

Many of the problems solved with computers involve the entry of numerical data; values of the types Integer, Long, Single, and Double. A Textbox object enables you to enter a String. How then do you input a number? The answer is that you can't—at least not directly. Visual Basic provides only for the input of strings. You have to enter a number as a string and then convert the string into one of the numeric types using methods from Visual Basic's Framework library.

As noted previously, Visual Basic's standard library includes a set of classes that correspond to the built-in numeric types. These classes provide methods and constants that are useful in working with the numeric types. The following table lists these predefined classes and the built-in type that each one corresponds to.

Built-in Type | Object Type
Integer | Int32
Long | Int64
Single | Single
Double | Double
Char | Char

As you can see, the general rule is that the class name is the same as the name of the built-in data type, except for the two integral data types. The object name Int32 for Integer indicates that the number is stored in 32 bits and the object name Int64 for Long indicates that the number is stored in 64 bits.

Among the methods associated with each of these classes is one called Parse, which takes a string as its parameter and returns a value of the object type. For example, you can write

Dim someDouble As Double
someDouble = Double.Parse("37.89")

with the result that the string "37.89" is converted to a value of type Double.

An important concept is revealed if you look closely at the method call and realize that it is not associated with the name of an object of type Double, but with the name of the type itself. Other than our calls to constructors and the value-returning math methods we saw in Chapter 4, all of our calls have been associated with a specific object. For example, you know that we must write inputField.Text to access a specific text box's Text property rather than write TextBox.Text. We informally introduced the terms instance method and class method. We've now reached the point that we can define them formally.

A method that is called in the familiar way is known as an instance method because an object is an instance of its class. But Visual Basic also provides methods that are associated with the class itself that are called class methods. A class method is called by writing the name of the class, followed by a period, and then the name of the method and its parameter list. Class methods give us a way to perform operations related to a class that are not specific to a particular object of that type.

Instance method
A method that is associated with an object of a given type. It is called by writing the name of the object followed by a period and then the name of the method and its parameter list.
Class method
A method that is associated with a class but not with a specific object. It is called by writing the name of the class followed by a period and then the name of the method and its parameter list.

Let's look at an example of getting an integer value from a text box:

Dim intNumber As Integer
intNumber = Int32.Parse(inputField.Text)

You now have a single statement that reads a numerical value from a data entry field on the screen. What if the user types something other than a number in the field? At this point in our knowledge of Visual Basic, the result is that the program halts and displays a message such as "Number Format Error." In Chapter 10, we see how a program can catch such an error (called an exception) and respond to it without stopping.


5.9 Noninteractive Input/Output

Although we tend to use examples of interactive I/O in this text, many programs are written using noninteractive I/O. A common example of noninteractive I/O on large computer systems is batch processing. In batch processing, the user and the computer do not interact while the program is running. This method is most effective when a program is going to input or output large amounts of data. An example of batch processing is a program that inputs a file containing semester grades for thousands of students and prints grade reports to be mailed out.

When a program must read in many data values, the usual practice is to prepare them ahead of time, storing them into a disk file. This allows the user to go back and make changes or corrections to the data as necessary before running the program. When a program is designed to print lots of data, the output can be sent directly to a highspeed printer or another disk file. After the program has been run, the user can examine the data at leisure.

Most Visual Basic programs are written for interactive use. But the flexibility of the language allows you to write noninteractive programs as well. The biggest difference is in the input/output requirements. Noninteractive programs are generally more rigid about the organization and format of the input and output data.

Programs can also combine interactive input and output with file input and output. In Chapter 9, we discuss input and output with disk files.


5.10 Software Design Strategies

Over the last four chapters and the first part of this one, we have introduced elements of the Visual Basic language that let us input data, perform calculations, and output results. The programs we wrote were short and straightforward because the problems to be solved were simple. We are ready to write programs for more complicated problems, but first we need to step back and look at the overall process of programming.

As we have stressed several times, the programming process consists of a problem-solving phase and an implementation phase. The problem-solving phase includes analysis (analyzing and understanding the problem to be solved) and design (designing a solution to the problem). Given a complex problem—one that results in a 10,000-line program, for example—it's simply not reasonable to skip the design process and go directly to writing Visual Basic code. What we need is a systematic way of designing a solution to a problem, no matter how complicated the problem is.

In the remainder of this chapter, we describe a methodical way of designing solutions to more complex problems: object-oriented design (OOD). This methodology helps you create solutions that can be easily implemented as Visual Basic programs. The resulting programs are readable, understandable, and easy to debug and modify. In the next two sections, we present the essential concepts of OOD, and we expand our treatment of the approach throughout the rest of the book. At times OOD is used in conjunction with a second, older methodology, which we also discuss in this chapter, functional decomposition.

Object-oriented design (OOD)
A technique for developing software in which the solution is expressed in terms of objects — self-contained entities composed of data and operations on that data that interact by sending messages to one another
Functional decomposition
A technique for developing software in which the problem is divided into more easily handled subproblems, the solutions of which create a solution to the overall problem

OOD focuses on the entities (objects) in a problem. In OOD, we solve a problem by identifying the components that make up a solution and how those components interact with each other. The result is a design for a set of objects that cooperate to solve a problem.

In contrast, functional decomposition views the solution to a problem as a task to be accomplished. It focuses on the sequence of operations that are required to complete the task. When the problem requires a sequence of steps that is long or complex, we divide it into subproblems that are easier to solve. Functional decomposition is the method that we have been using to break our simple problems into sequences of steps that the computer can follow. This strategy also can be used to develop algorithms for an object's methods.

In most large problems we naturally find entities that we wish to represent in an application. For example, in designing an application that manages a checking account, you might identify checks, deposits, an account balance, and account statements as entities. These entities interact with each other through messages. For example, a check could send a message to the balance entity that tells it to deduct an amount from itself. We didn't list the amount in our initial set of objects, but it may be another entity that we need to represent.

Our example illustrates a common approach to OOD. You begin by identifying a set of objects that you think are important in a problem. Then you consider some scenarios in which the objects interact to accomplish a task. In the process of envisioning how a scenario happens, you identify additional objects and messages. You keep trying new scenarios until you find that the set of objects and messages is sufficient to accomplish any task that the problem requires.


5.11 What Are Objects and Classes?

Let's review what we have said about objects and see how they work in the context of programming. Then we can more effectively explore how to solve a problem with OOD. We have defined an object three ways: as a collection of data together with associated operations, an entity or thing that is relevant in the context of a problem, and as an instance of a class. What is a class? We have also defined a class three ways: as a description of an object that specifies the types of data values that it can hold and the operations that it can perform, as a description of the behavior of a group of objects with similar properties, and as a pattern for an object.

Although varied, these definitions are complementary rather than contradictory. In the problem-solving phase we look for objects (things) that are relevant to the problem we are solving. We analyze these objects and how they interact with each other. We abstract the common properties and behaviors from these real objects and define classes that describe this behavior. In the implementation phase, we then take these descriptions (classes) and use the syntax of our programming language to define classes (in the Visual Basic sense) that describe the data values an object can have and the operations it can perform. Our program instantiates these classes to give us objects that interact to solve the original problem.

A programming language that supports OOD is called an object-oriented programming language. Examples are Visual Basic, Java, Smalltalk, and C++. In these languages, a class is the construct used to define the pattern that is used when instantiating an object. Let's look first at this construct a little deeper; in the next section we outline a strategy for finding objects and classes in the problem-solving phase.

A class isn't an object, but it specifies a pattern to use in creating a specific kind of object. Think of a class definition as analogous to a set of blueprints. A blueprint isn't a house, but it tells you how to construct a house. A class isn't an object, but it tells the computer how to construct an object. Declaring a variable of a class type is like buying the land on which to build the house. You still haven't built the house, but you have a place where you can build it.

The new operator invokes the constructor method for the class; it is like calling in a construction crew to interpret the blueprints and actually build the house. In Visual Basic, this is like having the house built in a factory (using new), and then delivered to the lot (assigned to the variable). Figure 5.5 illustrates this analogy.

Click To expand
Figure 5.5: An analogy illustrating the relationships among a class, a variable, and an object

We saw in Chapter 2 that a class declaration is a collection of field and method declarations. The declarations in a class are called the members of the class. As we have also seen, members can be Public or Private, and they can also be Static (or not Static) and Const (or not Const). The public members of a class combine to specify its public interface. Here we use interface in the general sense of the word, in contrast to the Visual Basic Interface construct. When a programmer wishes to employ a predefined class in writing an application, he or she looks up its interface to see what fields and methods it makes available for use.

Member
A field or method declaration within a class
Public interface
The members of a class that can be accessed outside of the class, together with the modes of access that are specified by other modifiers

What do we mean by "modes of access" in our definition of interface? Recall our earlier discussion of instance methods and class methods. We said that we invoke a class method by writing the class name, a period, and then the method name. We invoke an instance method by writing the object name, a period, and then the method name. In a class declaration, the two types of methods look exactly the same. Consider this statement:

intNumber = Int32.Parse(inputField.Text())

Parse is a class method and is prefaced by the name of the class Int32. It takes a string and converts it into an instance of class Int32.

The Public and Private modifiers determine whether a member can be accessed outside of a class. The Const modifier specifies whether a member can be changed. There are several combinations of these modifiers, each of which effectively defines a mode of access. However, only the Public members are part of the public interface. From code that is outside of the class definition, we cannot access members that are Private.

For example, the public interface for the class System.Double includes some Const fields such as the maximum and minimum Double values. It provides a set of class methods that includes Parse, and a set of instance methods that includes ToString, which converts a Double number to type String. All of these members are Public. We can also surmise that the definition of Double includes a Private field of type Double that is used to store a numerical value. In Chapters 7 and 8, where we look at the details of how to write new class definitions, we introduce additional access modifiers.

In Figure 5.6 we picture a Double object as having a private part and a public part. The private part includes fields and methods that the user cannot access and doesn't need to know about in order to use the object. The public part, shown as ovals in the side of the object, represent its public interface: values and operations that are available to programmers wishing to use the object.

Click To expand
Figure 5.6: An object and its public interface


5.12 Object-Oriented Design

Now we turn to the process of developing a problem solution using OOD. Our goal is to develop a design that captures the information needed to program a solution to the problem. But our design should not be at the level of detail of an actual program. That is, we want a way to write out a solution without getting distracted by programming language syntax. As we have said before, it is important to think first and code later. In this section we introduce an informal technique for developing object-oriented designs, known as CRC cards.[1]

The first step in OOD is to identify the major objects in the problem. We identity the abstract properties of each object, and use these to define classes that we need. Each class specifies a set of responsibilities, which are the actions that its objects support. Objects collaborate with each other by sending messages. A message is a request for an object to carry out one of its responsibilities. The collection of Classes, Responsibilities, and Collaborations (CRC) combine to solve a problem.

OOD leads to programs that are collections of objects. Each object is responsible for one part of the entire solution, and the objects send messages by accessing each other's members. There are many libraries of prewritten classes, including the Visual Basic .NET Framework library. In many cases, it is possible to browse through a library, choose classes you need for a problem, and assemble them to form a substantial portion of your program. Putting existing pieces together in this fashion is an excellent example of the building-block approach we discussed in Chapter 1.

When there isn't a suitable class available in a library, it is necessary to define a new class. We see how this is done in Chapters 7 and 8. Here we introduce CRC cards for solving problems in an object-oriented manner that leads to an OOD. The resulting design may or may not require the definition of new classes. We use CRC cards in the case studies of this chapter and the next to produce designs that do not require new classes. That way you can gain some experience in using them prior to learning the syntax for defining new classes.

In the discussion that follows, however, we won't be concerned with coding. So we use some examples that would result in new classes. This is meant to illustrate the full range of how CRC cards are used, and to give you a foretaste of how we approach OOD once we have the syntax to implement new classes. Let's briefly return to the more general topic of problem solving before we introduce the CRC cards.

Object-Oriented Problem Solving

As we discuss OOD, keep in mind that there are many different correct solutions to most problems. The techniques we use may seem imprecise, especially in contrast with the precision that is demanded by the computer. But the computer merely demands that we express (code) a particular solution precisely. The process of deciding which particular solution to use is far less precise. It is our human ability to make choices without having complete information that enables us to solve problems. Different choices naturally lead to different solutions to a problem.

For example, in developing a simulation of an air traffic control system, you might decide that airplanes and control towers are objects that communicate with each other. Or you might decide that pilots and controllers are the objects that communicate. This choice affects how you subsequently view the problem and the responsibilities that you assign to the objects. Either choice can lead to a working application. You may simply prefer the one with which you are most familiar (recall the Look for Things That Are Familiar strategy from Chapter 1).

Some of your choices lead to designs that are more or less efficient than others. For example, keeping a list of names in alphabetical rather than random order makes it possible for the computer to find a particular name much faster. However, choosing to leave the list randomly ordered still produces a valid (but slower) solution.

Other choices affect the amount of work that is required to develop the remainder of a problem solution. In creating a program for choreographing ballet movements, you might begin by recognizing the dancers as the important object and then create a class for each dancer. But in doing so, you might then discover that all of the dancers have certain common responsibilities. Rather than repeat the definition of those responsibilities for each class of dancer, you can change your initial choice and define a class for a generic dancer that includes all the common responsibilities and then develop subclasses (the subject of Chapter 8) that add responsibilities specific to each individual.

The point is, don't hesitate to begin solving a problem because you are waiting for some flash of genius that leads you to the perfect solution. There is no such thing. It is better to jump in and try something, step back and see if you like the result, and then either proceed or make changes. The CRC card technique that we look at next is a way to easily explore different design choices and keep track of them.

Identifying the Classes

The first step in solving a problem with OOD is to identify the classes of objects that are found in the problem. There is no foolproof technique for doing this; just start brainstorming ideas and see where they lead. A large problem is typically written by a team of programmers, so the brainstorming process occurs in a team setting. Team members identify whatever objects they see in the problem and then propose classes to represent them. The proposed classes are all written on a board. None of the ideas for classes are discussed or rejected in this first stage.

After the brainstorming, the team goes through a process of filtering the classes. First they eliminate duplicates. Then they discuss whether each class really represents an object in the problem (it's easy to get carried away and include classes, such as "the user," that are beyond the scope of the problem). The team then looks for classes that seem to be related. Perhaps they aren't duplicates, but they have much in common, and so they are grouped together on the board. At the same time, the discussion may reveal some classes that were overlooked.

For each class that survives the filtering stage, a CRC card is created. The CRC card is just an index card with a line drawn down the middle. The name of the class is written at the top and the two columns have the headings, Responsibilities and Collaborations. Figure 5.7 shows a blank CRC card.

Click To expand
Figure 5.7: A blank CRC card

We have added spaces at the top of the CRC card for naming the superclass and subclasses of the class. As an example, the different kinds of dancers would be the subclasses of the generic dancer class. Each specific dancer class would have the generic dancer class listed as its superclass. Filling in these spaces helps us to keep track of these relationships between our classes.

Let's say we want to write an address book application. We take a look at a physical address book and then brainstorm that our application has the following potential objects:

Click To expand

Then we enter the filtering stage. Our application doesn't need to represent the physical parts of an address book, so we can delete Cover and Pages. However, we need something analogous to a page that holds all the same sort of information. Let's call it an Entry. The different telephone numbers can all be represented by the same kind of object. So we can combine Home, Work, Fax, Pager, and Cell Phone into a Phone number class. In consultation with the customer, we find that the electronic address book doesn't need the special pages that are often found in a printed address book, so we delete Calendar, Time zone map, Owner information, and emergency numbers.

Further thought reveals that the User isn't part of the application, although this does point to the need for a User Interface that we did not originally list. A Work address is a specific kind of address that has additional fields, so we can make it a subclass of Address. Company names are just strings, so there is no need to distinguish them, but Names have a first, last, and middle part. Our filtered list of classes now looks like this.

Click To expand

For each of these classes we create a CRC card. In the case of Work Address, we list Address as its Superclass and on the Address card we list Work Address in its Subclasses space.

In doing coursework, you may be asked to work individually rather than in a collaborative team. You can still do your own brainstorming and filtering using a scrap of paper rather than a blackboard. However, we recommend that you take a break after the brainstorming and do the filtering after you have let your initial ideas rest for a while. An idea that seems brilliant in the middle of brainstorming may lose some of its attraction after a day or even a few hours.

Initial Responsibilities

Once you (or your team) have identified the classes and created CRC cards for them, go over each card and write down any responsibilities that are obvious. For example, a Name class has a responsibility to know its first name, its middle name, and its last name. You can list these three responsibilities in the left column of its card as shown in Figure 5.8. In an implementation, they become methods that return the corresponding part of the name. For many classes, the initial responsibilities include knowing some value or set of values.

Click To expand
Figure 5.8: A CRC card with initial responsibilities

A First Scenario Walk-Through

To further expand the responsibilities of the classes and see how they collaborate, we must pretend to carry out various processing scenarios by hand. This kind of role-playing is known as a walk-through. We ask a question such as, "What happens when the user wants to find an address that's in the book?" Then we answer the question by telling how each object is involved in accomplishing this task. In a team setting, the cards are distributed among the team members. When an object of a class is doing something, its card is held in the air to visually signify that it is active.

With this particular question, we might pick up the User Interface card and say, "I have a responsibility to get the person's name from the user." That responsibility gets written down on the card. Once the name is input, the User Interface must collaborate with other objects to look up the name and get the corresponding address. What object should it collaborate with?

We've found a hole in our list of classes! The Entry objects should be organized into a Book object. We quickly write out a Book CRC card. The User Interface card-holder then says, "I'm going to collaborate with the Book class to get the address." The collaboration is written in the right column of the card, and it remains in the air. The owner of the Book card holds it up, saying, "I have a responsibility to find an address in the list of Entry objects that I keep, given a name." That responsibility gets written on the Book card. Then the owner says, "I have to collaborate with each Entry to compare its name with the name sent to me by the User Interface." Figure 5.9 shows a team in the middle of a walk-through.

Click To expand
Figure 5.9: A scenario walk-through in progress

Now comes a decision. What are the responsibilities of Book and Entry for carrying out the comparison? Should Book get the name from Entry and do the comparison, or should it send the name to Entry and receive an answer that indicates whether they are equal? The team decides that Book should do the comparing, so the Entry card is held in the air, and its owner says, "I have a responsibility to provide the full name as a string. To do that I must collaborate with Name." The responsibility and collaboration are recorded and the Name card is raised.

Name says, "I have the responsibilities to know my first, middle, and last names. These are already on my card, so I'm done." And the Name card is lowered. Entry says, "I concatenate the three names into a string with spaces between them, and return the result to Book, so I'm done." The Entry card is lowered.

Book says, "I keep collaborating with Entry until I find the matching name. Then I must collaborate with Entry again to get the address." This collaboration is placed on its card and the Entry card is held up again, saying, "I have a responsibility to provide an address. I'm not going to collaborate with Address, but am just going to return the object to Book." The Entry card has this responsibility added and then goes back on the table. Its CRC card is shown in Figure 5.10.

Click To expand
Figure 5.10: The CRC card for Entry

Now Book can say, "I have the Address, and I return it to User Interface, so I'm done." The Book card is lowered. Figure 5.11 shows its CRC card. Then the holder of User Interface (whose arm is getting very tired) says, "I need to collaborate with Address to get each line of the address as a string that I can place in a pair of labels." Address is raised, saying, "I have responsibilities to know my street, city, state, and zip code, and those are already on my list." User Interface says, "I can concatenate the last three to form the second line, so that satisfies the collaboration." The collaboration is recorded, and User Interface says, "Now I display the labels and provide buttons to either quit or get another address, and I'm done." The last card is lowered and the scenario ends.

Click To expand
Figure 5.11: The CRC card for Book

Reading about the scenario makes it seem longer and more complex than it really is. Once you get used to role playing, the scenarios move quickly and the walk-through becomes more like a game. However, to keep things moving, it is important to avoid becoming bogged down with implementation details. Book should not be concerned with how the Entry objects are organized on the list. Address doesn't need to think about whether the zip code is stored as an Integer or a String. Explore each responsibility only as far as necessary to decide whether a further collaboration is needed or whether it can be solved with the available information.

Subsequent Scenarios

We began the first scenario with a "What happens when…" question for the most obvious case. The next step is to brainstorm some additional questions that produce new scenarios. For example, here is a list of some further scenarios.

Click To expand

We walk through each of the scenarios, adding responsibilities and collaborations to the CRC cards as necessary. After several scenarios have been tried, the number of additions decreases. When one or more scenarios take place without adding to any of the cards, then we brainstorm further to see if we can come up with new scenarios that may not be covered. When all of the scenarios that we can envision seem to be doable with the existing classes, responsibilities, and collaborations, then the design is done.

The next step is to implement the responsibilities for each class. The implementation may reveal details of a collaboration that weren't obvious in the walk-through. But knowing the collaborating classes makes it easy to change their corresponding responsibilities. The implementation phase should also include a search of available class libraries to see if any existing classes can be used. For example, the DateAndTime class represents a date that can be used directly to implement Birthday.

Inheritance

In walking through the scenario in which the user requests a business address, we should notice that the Work Address class is nearly identical with the Address class, except that it also provides a Business Name and a Second Address Line. Rather than copy all of the responsibilities and collaborations from the Address card to this one, we can list Address as the superclass of Work Address.

Because Work Address is a subclass of Address, it inherits all of the responsibilities that are in Address. A subclass is automatically able to use the responsibilities of its superclass and also add its own. In addition, a subclass can replace an inherited responsibility with a new definition of the responsibility. The new definition is a way of retaining the same form of interface but customizing its implementation to reflect the differences between the superclass and the subclass. In OOD this concept is called inheritance, and it allows you to adapt an existing class to satisfy additional responsibilities.

Inheritance
A mechanism that enables us to define a new class by adapting the definition of an existing class

An example of inheritance you have seen in this book already is the header for the Form1 class. This class is actually a subclass of the of the System.Windows.Forms.Form class, which is itself a subclass of the System.Windows.Forms class, which is a subclass of the System.Windows class, which is, finally, a subclass of the System class.

In order for us to use Form1 in the design of a user interface, we need all the properties and methods available from the classes mentioned in the paragraph above, so we have to use the Inherits keyword to indicate the relationship. The code for the class header for Form1 is shown below.

Public Class Form1
    Inherits System.Windows.Forms.Form
Click To expand
Figure 5.12: Form1 inherits events (and more) from the Form class.

Enhancing CRC Cards with Additional Information

The CRC card design is informal. There are many ways that the card can be enhanced. For example, when a responsibility has obvious steps, we can write them below its name. Each step may have specific collaborations, and we write these beside the steps in the right column. We often recognize that certain data must be sent as part of the message that activates a responsibility, and we can record this in parentheses beside the calling collaboration and the responding responsibility. Figure 5.13 shows a CRC card that includes design information in addition to the basic responsibilities and collaborations.

Click To expand
Figure 5.13: A CRC card that is enhanced with additional information

To summarize the CRC card process, first brainstorm the objects in a problem and then abstract them into classes. Then filter the list of classes to eliminate duplicates. For each class, create a CRC card and list any obvious responsibilities that it should support. Then walk through a common scenario, recording responsibilities and collaborations as they are discovered. After that, walk through additional scenarios, moving from common cases to special and exceptional cases. When it appears that all of the scenarios have been covered, brainstorm additional scenarios that may need more responsibilities and collaborations. When all ideas for scenarios are exhausted and all the scenarios are covered by the existing CRC cards, the design is done.

In this section, we have presented only an introduction to OOD. A more complete discussion requires knowledge of topics that we explore in Chapters 6 and 8: flow of control, programmer-written methods, class definitions, and interface design. Until then, our programs are relatively small, and we consider problems that can be solved with object-based programming. Keep in mind that a CRC card design doesn't have to lead to the design of new classes.

[1]Originally published in: Kent Beck and Ward Cunningham, A Laboratory for Teaching Object-Oriented Thinking, OOPSLA `89 Proceedings, SIGPLAN Notices 24(10): 1–6, October, 1989.


5.13 Functional Decomposition

The second design technique we use is functional decomposition (also called structured design, top-down design, stepwise refinement, and modular programming). It allows us to use the divide-and-conquer approach, which we talked about in Chapter 1.

When a responsibility clearly involves a series of major steps, you can break it down (decompose it) into pieces. In the process, you move to a lower level of abstraction—that is, some of the implementation details (but not too many) are now specified. Each of the major steps becomes an independent subproblem that you can work on separately. The process continues until each subproblem cannot be divided further or has an obvious solution.

Concrete step
A step for which the implementation details are fully specified
Abstract step
A step for which some implementation details remain unspecified
Module
A self-contained collection of steps that solves a problem or subproblem; can contain both concrete and abstract steps

By subdividing the problem, you create a hierarchical structure called a tree structure. Each level of the tree is a complete solution to the problem that is less abstract (more detailed) than the level above it. Figure 5.14 shows a generic solution tree for a problem. Steps that are shown in black type have enough implementation details to be translated directly into Visual Basic statements. These are concrete steps. Those in colored type are abstract steps; they reappear as subproblems in the next level down. Each box in the figure represents a module. Modules are the basic building blocks in a functional decomposition. The diagram in Figure 5.14 is also called a module structure chart.

Click To expand
Figure 5.14: Hierarchical solution tree

Writing Modules

Here's one approach to writing modules:

  1. Think about how you would solve the subproblem by hand.

  2. Begin writing down the major steps.

  3. If a step is simple enough so that you can see how to implement it directly in Visual Basic, it is at the concrete level; it doesn't need any further refinement.

  4. If you have to think about implementing a step as a series of smaller steps or as several Visual Basic statements, it is still at an abstract level.

  5. If you are trying to write a series of steps and start to feel overwhelmed by details, you probably are bypassing one or more levels of abstraction. Stand back and look for pieces that you can write as more abstract steps.

We could call this the "procrastinator's technique." If a step is cumbersome or difficult, put it off to a lower level; don't think about it today, think about it tomorrow. Of course, tomorrow does come, but the whole process can be applied again to the sub-problem. A trouble spot often seems much simpler when you can focus on it. And eventually the whole problem is broken up into manageable units.

As you work your way down the solution tree, you make a series of design decisions. If a decision proves awkward or wrong (and many times it does!), you can backtrack (go back up the tree to a higher-level module) and try something else. You don't have to scrap your whole design—only the small part you are working on. There may be many intermediate steps and trial solutions before you reach a final design.

Before OOD was developed, functional decomposition was used to solve entire problems. However, by itself, it results in designs that lack flexibility. Applications developed entirely this way are hierarchical in nature, and often at the bottom of the hierarchy you are implementing multiple versions of the same responsibility. With OOD, you can define a superclass and a set of subclasses to organize these responsibilities and save yourself programming effort. But functional decomposition doesn't allow for this kind of organization.

Today, we typically use functional decomposition as a way to design the algorithm for a complex responsibility. For example, in a class that supports image processing, a morphing responsibility that distorts part of an image might have no need of any other collaboration. Yet there may be many mathematically sophisticated steps in the algorithm that transforms the image data. Functional decomposition provides a way to break such a problem down into simpler pieces that are easier to solve.

Pseudocode

You'll find it easier to implement a design if you write the steps in pseudocode. Pseudocode is a mixture of English statements and Visual Basic-like control structures that can be translated easily into Visual Basic. (We've already been using pseudocode in the algorithms in the Problem-Solving Case Studies.) When a concrete step is written in pseudocode, it should be possible to rewrite it directly as a Visual Basic statement in a program.

Remember that the problem-solving phase of the programming process takes time. If you spend the bulk of your time analyzing and designing a solution, coding (implementing) the program takes relatively little time.

Start Sidebar
Software Engineering Tip
Documentation

As you create your functional decomposition or object-oriented design, you are developing documentation for your program. Documentation includes the written problem specifications, design, development history, and actual code of a program.

Good documentation helps other programmers read and understand a program and is invaluable when software is being debugged and modified (maintained). If you haven't looked at your program for six months and need to change it, you'll be happy that you documented it well. Of course, if someone else has to use and modify your program, documentation is indispensable.

Documentation is both external and internal to the program. External documentation includes the specifications, the development history, and the design documents. Internal documentation includes the program format and self-documenting code—meaningful identifiers and comments. You can use the pseudocode from your design as comments in your programs.

Self-documenting code
Program code containing meaningful identifiers as well as judiciously used clarifying comments

This kind of documentation may be sufficient for someone reading or maintaining your programs. However, if a program is going to be used by people who are not programmers, you must provide a user's manual as well.

Be sure to keep documentation up-to-date. Indicate any changes you make in a program in all of the pertinent documentation. Use self-documenting code to make your programs more readable.

End Sidebar

Now let's look at a case study that demonstrates the design process.

Start Sidebar
Problem-Solving Case Study
Averaging Rainfall Amounts

Problem You are working for a scientific research project that is keeping track of the average rainfall in your area. Observers from different locations call you to report how much rain has fallen at the end of a storm. You need to enter the numbers as they come in and print the running average.

Brainstorming Because this is such a small project, you are the "team." Therefore, you must do a solitary brainstorm. Grady Booch suggested that underlining nouns in a problem statement is a good way to begin.[2] So you begin your brainstorming by looking at the nouns in the problem statement: you, project, average, rainfall, observers, and storm. So, here is your first pass at the classes in the problem.

Click To expand

Filtering Project and storm are not really objects; they just give information about the context of the problem. Average and rainfall are numeric values, so we don't need classes for them. What are observers? They are the people who provide the rainfall values, so they are not a class in our problem solution. However, this reminds us that we must have a window object to input the rainfall values. What does you represent? It represents the processing that must be done. Since you cannot actually do the processing within the machine, you is represented as the application class. Let's call the application class RainFall. Here is the filtered list of classes.

Click To expand

Scenarios The main class (RainFall) is responsible for seeing that the values are input and the running average is calculated and printed. Some of the tasks should be done in the Declarations section of Form1 and some in the button handlers for the input. We can use the built-in classes Form1, Button, Label, String, and Textbox to implement the window. Because the classes we need for the window are built in, we don't need to set up CRC cards for them, but we do for class RainFall.

Click To expand

Responsibility Algorithms At this point, let's switch to a functional decomposition to write the algorithms for implementing the responsibilities. Preparing the window for input (more generally called preparing the user interface) requires us to declare each of the components of the interface, to instantiate each component, and to add each component to the frame. These are abstract steps because they each involve a series of actions. We defer a close examination of them until later. After the frame has been built, we show it. We also need to wire its window-closing event handler. These steps can all be translated into individual Visual Basic statements, so they are concrete.

...........................................................................
Prepare Window for Input                                            Level 1

 Add interface objects to form
 Set interface object properties

The interface for this application will include two labels (one for input and one for output), a textbox for entering data, and a button for the user to click when a value is ready for input.

...........................................................................
Add Interface Objects to Form                                       Level 2

 Add a Label
 Add a Label
 Add a Textbox
 Add a Button

...........................................................................
Set Interface Object Properties

 Name one Label, entryLabel
 Name one Label, outputLabel
 Name the Textbox, inputText
 Name the button, enter

Handling the input events is where the real work of the program is accomplished. When the user clicks the Enter button, we need to get the value from the textbox and convert it to a numerical value. Because we have input a new data value, we need to add 1 to the count of the entries. We also add the input value to the total rainfall. The updated total and count can then be used to compute the average, which we display for the user to see. Lastly, we should clear out the textbox so that the user can enter a new value. Each of these steps is concrete, as shown in the following module, where we continue to set interface object variables.

...........................................................................
Set Interface Object Properties—continued                           Level 2

 Set Text property of entryLabel to "Enter amount here:"
 Set Text property of outputLabel to ""
 Set Text property of inputText to ""
 Set Text property of enter to "Enter"

Now that the user interface is prepared, we are ready to move to the next step, which is handling the input events.

...........................................................................
Handle Input Events                                                 Level 2

 Get the entry string from the text box
 Set rainfall to numerical value (double) of entry string
 Add 1 to the number of entries
 Add rainfall to total
 Set average to total divided by number of entries
 Display average
 Clear input field

Since window closing is handled automatically by Visual Studio, we are finished. We used OOD to determine the objects and their responsibilities. We then used functional decomposition to write the algorithms to implement the responsibilities. Each abstract step in each algorithm has been expanded into concrete steps that are functionally equivalent. All that remains is for us to gather the concrete steps in the proper order to give a complete algorithm consisting of concrete steps. To perform the gathering process, we begin at the lowest level, substituting the concrete steps of each module for the corresponding abstract step in the level above it.

Once all of the substitutions are complete, we have an algorithmic solution consisting entirely of concrete steps. Because each concrete step can be directly translated into a Visual Basic statement, writing the program is simply a matter of converting each line of the algorithm into a corresponding line of code. The resulting algorithm and program are known as a flat or inline implementation because our series of substitutions produces a solution with just a single level. We are flattening the two-dimensional, hierarchical structure of the solution by writing all of the steps as one long sequence. This kind of implementation is adequate when the implementation of a responsibility is short.

From our design we collect a list of all of the variables that our program uses, to help in writing their declarations.

Variables
Name | Data Type | Description
Form1 | Form | A form
entryLabel | Label | Label for input
outputLabel | Label | Label for output
inputText | Text box | Input text box
enter | Button | Enter button
total | Double | Keeps running total
numberEntries | Double | Keeps count of entries
amount | Double | Holds input value
average | Double | Holds computed average

At this stage we should look over this list of variables and ask ourselves if there are any that we have forgotten to initialize. total keeps the running total of the rainfall so far, so it should be initialized to zero. numberEntries is a counter that tells how many values have been read, so it should also be set to zero.

Here is the complete program. We have used the steps in the design as the basis for its comments.

Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim amount As Double            ' Holds input value
    Dim average As Double           ' Holds computed average

    Dim total As Double             ' Keeps running total
    Dim numberEntries As Double     ' Keeps count of entries

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call

    End Sub

    ' Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e _
                           AsSystem.EventArgs) Handles MyBase.Load
        total = 0R
        numberEntries = 0R

    End Sub

    Private Sub enter_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) Handles enter.Click
        ' Convert string in inputText to a double value
        amount = CDbl(inputText.Text)
        numberEntries += 1                  ' Increment entries
        total = total + amount              ' Add value to sum
        average = total / numberEntries     ' Compute average
        outputLabel.Text = average          ' Display average
        inputText.Text = ""                 ' Clear input text box
    End Sub
End Class

When the program executes, the form shown below is displayed. The contents shown in the form are the result of entering the following series of values, with the form appearing as it does just before Enter is clicked to cause the last value to be processed.

1.300
0.800
1.200
2.000
0.950
1.410
Click To expand
End Sidebar
Start Sidebar
Background Information
Programming at Many Scales

To help you put the topics in this book into context, we describe in broad terms the way programming in its many forms is done "in the real world." Obviously, we can't cover every possibility, but we'll try to give you a flavor of the state of the art.

Programming projects range in size from the small scale, in which a student or computer hobbyist writes a short program to try out something new, to large-scale multicompany programming projects involving hundreds of people. Between these two extremes are efforts of many other sizes. There are people who use programming in their professions, even though it isn't their primary job. For example, a scientist might write a special-purpose program to analyze data from a particular experiment.

Even among professional programmers there are many specialized programming areas. An individual might have a specialty in business data processing, in writing compilers or developing word processors (a specialty known as "tool making"), in research and development support, in graphical display development, in writing entertainment software, or in one of many other areas. However, one person can produce only fairly small programs (a few tens of thousands of lines of code at best). Work of this kind is called programming in the small.

A larger application, such as the development of a new operating system, might require hundreds of thousands or even millions of lines of code. Such large-scale projects require teams of programmers, many of them specialists, who must be organized in some manner or they waste valuable time just trying to communicate with one another.

Usually, a hierarchical organization is set up. One person, the chief architect or project director, determines the basic structure of the program and then delegates the responsibility of implementing the major components. The components may be implemented by teams or by individual programmers. This sort of organization is called programming in the large.

Programming languages and software tools can help a great deal in supporting programming in the large. For example, if a programming language lets programmers develop, compile, and test parts of a program independently before they are put together, then it enables several people to work on the program simultaneously. Of course, it is hard to appreciate the complexity of programming in the large when you are writing a small program for a class assignment. However, the experience you gain in this course will be valuable as you begin to develop larger programs.

The following is a classic example of what happens when a large program is developed without careful organization and proper language support. In the 1960s, IBM developed a major new operating system called OS/360, which was one of the first true examples of programming in the large. After the operating system was written, more than 1,000 significant errors were found. Despite years of trying to fix these errors, the programmers never did get the number of errors below 1,000, and sometimes the "fixes" produced far more errors than they eliminated.

What led to this situation? Hindsight analysis showed that the code was badly organized and that different pieces were so interrelated that nobody could keep it all straight. A seemingly simple change in one part of the code caused several other parts of the system to fail. Eventually, at great expense, an entirely new system was created using better organization and tools.

In those early days of computing, everyone expected occasional errors to occur, and it was still possible to get useful work done with a faulty operating system. Today, however, computers are used more and more in critical applications such as medical equipment and aircraft control systems where errors can prove fatal. Many of these applications depend on large-scale programming. If you were stepping onto a modern jetliner right now, you might well pause and wonder, "Just what sort of language and tools did they use when they wrote the programs for this thing?" Fortunately, most large, life-critical, software development efforts today use a combination of good methodology, appropriate language, and extensive organizational tools—an approach known as software engineering.

End Sidebar

[2]Grady Booch, What Is and Isn't Object-Oriented Design. American Programmer, Special Issue on Object Orientation, Vol. 2, No. 7–8, Summer 1989.


Testing and Debugging

An important part of implementing a program is testing it (checking the results). By now you should realize that there is nothing magical about the computer. It is infallible only if the person writing the instructions and entering the data is infallible. Don't trust it to give you the correct answers until you've verified enough of them by hand to convince yourself that the program is working.

From here on, these Testing and Debugging sections offer tips on how to test your programs. We offer suggestions for what to do if a program doesn't work as expected. It's much easier to prevent bugs than to fix them.

With programs that input data from text boxes, the most common error is to try to assign the text box contents directly to an Integer or Double variable. Remember that Visual Basic only inputs String values and that you must convert the input to any other type. Another problem that often occurs is that the user enters a number that causes integer overflow. When you want to input an integer value, limit its size to about nine characters. As we noted before, we are not yet ready to handle errors in which the user enters nonnumeric data into such a text box. Your program will simply crash with an error message if this happens.

In testing programs that perform numeric calculations, be sure to test whether the input values can result in overflow or division by zero. In Chapter 6 we see how to catch these errors before the calculation takes place. Until then you should document the limitations of your program with comments.

By giving you a framework that can help you organize and keep track of the details involved in designing and implementing a program, OOD and functional decomposition should help you avoid errors in the first place.

Testing and Debugging Hints

  1. Remember to label each text box with a prompting message.

  2. The value in a text box is a string. If you need to input a number, you must convert the value in the text box to the appropriate numeric data type, using functions such as Int32.Parse and Double.Parse. Applying these functions to a nonnumeric value causes the program to halt with an error message such as Cast From String to Integer Is Not Valid.

  3. A button's Click event method is activated in Code View when you doubleclick the button. Be sure to do this rather than try to code the Click event yourself since there are some default method parameters added automatically by Visual Studio.

  4. Use the design methodologies presented in this chapter to carefully design a complete algorithmic solution to a problem before you start writing Visual Basic code. The design should be sufficiently detailed that converting it to Visual Basic is almost a mechanical process.


Summary of Classes

Class Name |  |  |  | Comments
System |  |  |  |
 | Double |  |  |
 |  | Public Static Fields: MaxValue |  | Highest Double value
 |  |  | MinValue | Lowest Double value
 |  | Value-Returning Class Method: |  |
 |  |  | Parse | Returns Double
 | Single |  |  |
 |  | Public Static Fields: MaxValue |  | Highest Single value
 |  |  | MinValue | Lowest Single value
 |  | Value-Returning Class Method: |  |
 |  |  | Parse | Returns Single
 | Long |  |  |
 |  | Public Static Fields: MaxValue |  | Highest Long value
 |  |  | MinValue | Lowest Long value
 |  | Value-Returning Class Method: |  |
 |  |  | Parse | Returns Long
 | Integer |  |  |
 |  | Public Static Fields: MaxValue |  | Highest Integer value
 |  |  | MinValue | Lowest Integer value
 |  | Value-Returning Class Method: |  |
 |  |  | Parse | Returns Integer
 | Button |  |  |
 |  | Private Subprogram: Click |  | Handles button Click event
 | Textbox |  |  |
 |  | Value-Returning Instance Method: |  |
 |  |  | Text | Returns String


Summary

Programs operate on data. If data and programs are kept separate, the data is available to use with other programs, and the same program can be run with different sets of input data.

The Textbox class enables us to input data from the keyboard, storing data into the variable specified in a call to its Text method. The Text method inputs the contents of the text box as a string. We must use additional methods such as Parse and the conversion functions (cint, cdbl, etc.) to convert an input string to a numeric value. Reading of the text in a text box is usually triggered by a user event such as closing a window or clicking a button.

Interactive programs prompt the user for each data entry and directly inform the user of results and errors. Designing an interactive dialogue is an exercise in the art of communication. Noninteractive input/output allows data to be prepared before a program is run and allows the program to run again with the same data in the event that a problem crops up during processing.

Object-oriented design (OOD) and functional decomposition are methods for tackling nontrivial programming problems. The two methodologies are often used in combination, and experienced programmers often switch between them in solving a problem. We use OOD to design the overall solution to a complex problem, and then apply functional decomposition to aid in writing the algorithm for the individual responsibilities of each object.

Object-oriented design produces a problem solution by focusing on objects and their associated operations. The first step is to brainstorm the major objects in the problem and create classes that are abstract descriptions of them. We filter the initial classes, eliminating duplicate and inappropriate classes. We then walk through a series of scenarios, working from common cases to exceptional cases to determine the responsibilities and collaborations of the classes. The use of CRC cards helps us to keep track of this information as we explore the scenarios. The result of the design process is a program consisting of self-containing objects that have responsibilities for managing their own data and collaborating with other objects by invoking each other's methods.

Functional decomposition begins with an abstract solution that then is divided into major steps. Each step becomes a subproblem that is analyzed and subdivided further. A concrete step is one that can be translated directly into Visual Basic; those steps that need more refining are abstract steps. A module is a collection of concrete and abstract steps that solves a subproblem.

Careful attention to program design, program formatting, and documentation produces highly structured and readable programs.


Quick Check

Q: | 1. What is one of the biggest advantages of computers? (p. 168) | 1. A program can be run with many different data sets.
Q: | 2. Where is an event sent when a user clicks on a button with a mouse? (p. 168) | 2. The button's Click event.
Q: | 3. What happens when a user clicks a button? (p. 168) | 3. The CLR recognizes the button event and calls the Click event.
Q: | 4. Who or what participates in a computer dialog? (p. 169) | 4. A computer dialog is an exchange between the program and the user.
Q: | 5. Assign a value to a text box named dataText. (p. 171) | 5. dataText = Hello, world
Q: | 6. Assign a default value "Replace Me" to dataText. (p. 171) | 6. In design view, type Replace Me in the Text property of the dataText text box.
Q: | 7. Write the statement that stores the values entered in text box dataText into the String variable dataValue. (p. 174) | 7. dataValue = dataText.Text
Q: | 8. Write the statement that converts the dataValue into doubleValue of type Double. (pp. 178–179) | 8. doubleValue = cdbl(dataValue)
Q: | 9. Write a statement that displays the string "New Text" into the text box dataText when the user clicks a button. (p. 175–176) | 9. dataText.Text = New Text
Q: | 10. Object-oriented design produces a collection of __________. (objects, tasks) (p. 180) | 10. objects
Q: | 11. Functional design produces a hierarchy of __________. (objects, tasks) (p. 180) | 11. tasks

Answers

A: | 1. A program can be run with many different data sets.
A: | 2. The button's Click event.
A: | 3. The CLR recognizes the button event and calls the Click event.
A: | 4. A computer dialog is an exchange between the program and the user.
A: | 5. dataText = "Hello, world"
A: | 6. In design view, type "Replace Me" in the Text property of the dataText text box.
A: | 7. dataValue = dataText.Text
A: | 8. doubleValue = cdbl(dataValue)
A: | 9. dataText.Text = "New Text"
A: | 10. objects
A: | 11. tasks


Exam Preparation Exercises

    Chapter 5

  1. Name three objects that can be contained in a Form object.

  2. What are the steps in adding a data entry text box to a Form object?

  3. What are the steps needed to add a Button to a Form object?

  4. What happens when a user clicks a button and there is no code in the Click event?

  5. What is an interface in Visual Basic?

  6. What is an interface implementation in Visual Basic?

  7. What determines the amount of information in a prompt?

  8. Visual Basic provides input only for strings. How can we enter numeric values into our programs?

  9. What are the object types that correspond with the built-in numeric types?

  10. Distinguish between instance methods and class methods.

  11. If it is natural to think of the solution to a problem in terms of a collection of component parts, which design technique is more appropriate?

  12. Distinguish between an abstract step and a concrete step.

  13. List the steps in performing an object-oriented design.

  14. At which point in an object-oriented design is functional decomposition useful?


Programming Warm-Up Exercises

    Chapter 5

    1. Add a Label object to a form. Name it textLabel.

    2. Set the property that assigns the text "Enter data" to textLabel.

    3. Add a Textbox object to the form. Name it dataText.

    4. Write a statement that stores the value entered into dataField into String variable dataValue.

    5. Write the statement that converts dataValue to an integer value and stores it in intValue.

    6. Write a statement to replace the current contents of dataField with "Next Value".

    1. Add a Button object to a form. Name it stop.

    2. Set the button's Text property to "Stop".

    1. Create a form with one label (exampleLabel), one text box (exampleText), and one button (exampleButton).

    2. In the button's Click event, the contents of exampleText are stored in exampleValue and removed from exampleText.

    1. What is the type of the following expression: Int32.Parse("2001")?

    2. Is Parse an instance method or a class method?

  5. Write the statements that store the number 2,000 into variable data of type Integer.

  6. Write the statement that takes the string representing an integer number in exampleField and stores it into data.

  7. You are to read the three coefficients of a quadratic polynomial and write the two floating-point solutions. Assume that the discriminant (the portion of the formula inside the square root) is nonnegative. You may use the standard method Math.Sqrt. The quadratic formula is

    1. Use object-oriented design to determine the objects in the problem. Discuss the problem in the following phases: brainstorming, filtering, scenarios, CRC cards, and responsibility algorithms.

    2. Use functional decomposition to write an algorithm for the problem.

    3. Compare your object-oriented design and your functional design.


Programming Problems

  1. Design and code a Visual Basic application to read an invoice number, quantity ordered, and unit price (all integers) and compute the total price. The application should write out the invoice number, quantity, unit price, and total price on the screen with identifying phrases. Format your program with consistent indentation; use appropriate comments and meaningful identifiers. The data is prompted for and read from the screen. Use buttons where appropriate.

  2. How tall is a rainbow? Because of the way in which light is refracted by water droplets, the angle between the level of your eye and the top of a rainbow is always the same (the so-called magic angle). If you know the distance to the rainbow, you can multiply it by the tangent of that angle to find the height of the rainbow. The magic angle is 42.3333333 degrees. The Visual Basic Framework library works in radians, however, so you have to convert the angle to radians with this formula:

    where π equals 3.14159265.

    Math.Tan is a value-returning method that takes a floating-point parameter and returns a floating-point result:

    x = Math.Tan(someAngle)

    If you multiply the tangent by the distance to the rainbow, you get the height of the rainbow.

    Design and code a Visual Basic application to read a single floating-point value—the distance to the rainbow—from the screen and compute the height of the rainbow. The program should print the distance to the rainbow and its height with phrases that identify which number is which. Format your program with consistent indentation, and use appropriate comments and meaningful identifiers.

  3. Sometimes you can see a second, fainter rainbow outside a bright rainbow. This second rainbow has a magic angle of 52.25 degrees. Modify the program in Exercise 2 so that it prints the height of the main rainbow, the height of the second rainbow, and the distance to the main rainbow, with a phrase identifying each of the numbers.


Case Study Follow-Up Exercises

    Chapter 5

  1. Does RainFall crash if the window is closed before the user enters any values? Explain.

  2. Outline a testing strategy for application RainFall.

  3. RainFall stops when the user closes the window. What other ways could the program allow the user to quit? Explain.

  4. What happens when a negative value is input?


Chapter 6: Conditions, Logical Expressions, and Selection Control Structures

Overview

Goals

  • To be able to construct a simple logical (Boolean) expression to evaluate a given condition.

  • To be able to construct a complex Boolean expression to evaluate a given condition.

  • To be able to construct an If-Then-Else statement to perform a specific task.

  • To be able to construct an If statement to perform a specific task.

  • To be able to construct a series of nested If statements to perform a specific task.

  • To be able to describe an algorithm walk-through and tell how it is used.

  • To be able to explain the purpose of tracing the execution of Visual Basic code.

  • To be able to test and debug a Visual Basic program.

So far, the statements in our programs have been executed in the same order in which we write them, except when an event occurs. The first statement is executed, then the second, and so on. The method call and the event handler, which execute a separate sequence of statements, provide variations of this ordering. But what if we want the computer to execute the statements in an order other than sequentially? Suppose we want to check the validity of input data and then perform a calculation or display an error message, but not both. To do so, we must be able to ask a question and then, based on the answer, choose one or another course of action.

The If statement allows us to execute statements in an order that is different from their physical order. We can ask a question with it and do one thing if the answer is yes (true) or another if the answer is no (false). In the first part of this chapter, we deal with asking questions; in the second part, we deal with the If statement itself.


6.1 Flow of Control

The order in which statements are executed in a program is called the flow of control. In a sense, the computer is under the control of one statement at a time. When a statement has been executed, control is turned over to the next statement (like a baton being passed in a relay race).

Flow of control
The order in which the computer executes statements in a program
Control structure
A statement used to alter the normal sequential flow of control

Flow of control is normally sequential (see Figure 6.1). That is, when one statement is finished executing, control passes to the next statement in the program. If you want the flow of control to be nonsequential, you can use control structures, special statements that transfer control to a statement other than the one that physically comes next. You have already seen that method calls, especially event handlers, are control structures that alter the flow of control so that a separate sequence of statements can be executed.

Click To expand
Figure 6.1: Sequential control

Selection

You can use a selection (or branching) control structure when you want the computer to choose between alternative actions. You make an assertion, a claim that is either true or false. If the assertion is true, the computer executes one statement. If it is false, it executes another (see Figure 6.2). The computer's ability to solve practical problems is a product of its ability to make decisions and execute different sequences of instructions.

Click To expand
Figure 6.2: Selection (branching) control structure
Click To expand

The Payroll program in Chapter 1 shows the selection process at work. The computer must decide whether or not a worker has earned overtime pay. It does this by testing the assertion that the person has worked over 40 hours. If the assertion is true, the computer follows the instructions for computing overtime pay. If the assertion is false, the computer simply computes the regular pay. Before examining selection control structures in Visual Basic, let's look closely at how we get the computer to make decisions.


6.2 Conditions and Logical Expressions

To ask a question in Visual Basic, we don't phrase it as a question; we state it as an assertion. If the assertion we make is true, the answer to the question is yes. If the assertion is not true, the answer to the question is no. For example, if we want to ask, "Are we having spinach for dinner tonight?" we would say, "We are having spinach for dinner tonight." If the assertion is true, the answer to the question is yes. If not, the answer is no.

So, asking questions in Visual Basic means making an assertion that is either true or false. The computer evaluates the assertion, checking it against some internal condition (the values stored in certain variables, for instance) to see whether it is true or false.

The Boolean Data Type

The Boolean data type consists of just two values, the constants True and False. The reserved word Boolean is pronounced bool-e-un.[1] Boolean data is used for testing conditions in a program so that the computer can make decisions (as in a selection control structure).

We declare variables of type Boolean the same as we declare variables of other standard types, by writing the Dim keyword, followed by the variable name, the As keyword, and the word Boolean:

Dim dataOK As Boolean
Dim done As Boolean
Dim taxable As Boolean

Each variable of type Boolean can contain one of two values: True or False. It's important to understand right from the beginning that True and False are not variable names and they are not strings. They are special constants in Visual Basic and, in fact, are reserved words.

Logical Expressions

In programming languages, assertions take the form of logical expressions (also called Boolean expressions). Just as an arithmetic expression is made up of numeric values and operations, a logical expression is made up of logical values and operations. Every logical expression has one of the two Boolean values: True or False.

Here are some examples of logical expressions:

  • A Boolean variable or constant

  • An arithmetic expression followed by a relational operator followed by an arithmetic expression

  • A logical expression followed by a logical operator followed by a logical expression

Let's look at each of these in detail.

Boolean Variables and Constants As we have seen, a Boolean variable is a variable declared to be of type Boolean, and it can contain either the value True or the value False. For example, if dataOK is a Boolean variable, then

dataOK = True

is a valid assignment statement.

Relational Operators Another way of assigning a value to a Boolean variable is to set it equal to the result of comparing two expressions with a relational operator. Relational operators test a relationship between two values.

Let's look at an example. In the following program fragment, lessThan is a Boolean variable and i and j are Integer variables:

lessThan = (i < j)  ' Compare i and j with the "less than"
                    ' relational operator, and assign the
                    ' value to lessThan

By comparing two values, we assert that a relationship (such as "less than") exists between them. If the relationship does exist, the assertion is true; if not, it is false. These are the relationships we can test for in Visual Basic:

Operator | Relationship Tested
= | Equal to
<> | Not equal to
> | Greater than
< | Less than
>= | Greater than or equal to
<= | Less than or equal to

An expression followed by a relational operator followed by an expression is called a relational expression. The result of a relational expression is of type Boolean. For example, if x is 5 and y is 10, the following expressions all have the value True:

x <> y
y > x
x < y
y >= x
x <= y

If x is the character "M"c and y is the character "R"c, the values of the expressions are still True because the relational operator <, used with letters, means "comes before in the alphabet," or, more properly, "comes before in the collating sequence of the character set." For example, in the ASCII subset of the Unicode character set, all of the uppercase letters are in alphabetical order, as are the lowercase letters, but all of the uppercase letters come before the lowercase letters. So

"M"c < "R"c

and

"m"c < "r"c

have the value True, but

"m"c < "R"c

has the value False.

Of course, we have to be careful about the data types of things we compare. The safest approach is always to compare identical types: Integer with Integer, Double with Double, Char with Char, and so on. If you mix data types in a comparison, implicit type conversion can take place just as in arithmetic expressions. If an Integer value and a Double value are compared, the computer temporarily converts the Integer value to its Double equivalent before making the comparison. As with arithmetic expressions, it's wise to use type conversion functions to make your intentions known:

someDouble >= CDb1(someInt)

If you try to compare a Boolean value with a numeric value (probably by mistake), the compiler gives you an error message. Values of type Boolean cannot be converted to any type other than String. When a Boolean variable is concatenated with a string, its value is automatically converted to either "True" or "False". No type can be converted to Boolean.

Be careful to compare Char values only with other Char values. For example, the comparisons

"0"c < "9"c

and

0 < 9

are appropriate, but comparing a digit in quotes (a character) and a digit such as

"0"c < 9

generates an error by the compiler.

You can use relational operators not only to compare variables or constants, but also to compare the values of arithmetic expressions. The following table shows the results of using the relational operators to compare expressions made up of adding 3 to x and multiplying y by 10 for different values of x and y:

Value of x | Value of y | Expression | Result
12 | 2 | x + 3 <= y * 10 | True
20 | 2 | x + 3 <= y * 10 | False
7 | 1 | x + 3 <> y * 10 | False
17 | 2 | x + 3 = y * 10 | True
100 | 5 | x + 3 > y * 10 | True

Comparing Strings Unlike many other computer languages, you can compare strings with the relational operators in Visual Basic and expect to receive reliable results. We can also compare strings with a set of value-returning instance methods that Visual Basic supplies as part of the String class. Because they are instance methods, the method name is written following a String object, separated by a dot. The string that the method name is appended to is one of the strings in the comparison, and the string in the parameter list is the other. Because there are times we want to compare strings ignoring capitalization, the String class provides methods called ToLower and ToUpper that convert all the characters of a string to lowercase or uppercase, respectively. The two most useful comparison methods are summarized in the following table, along with ToLower and ToUpper.

Method Name | Parameter Type | Returns | Operation Performed
Equals | String | Boolean | Tests for equality of string contents.
CompareTo | String | Integer | Returns 0 if equal, a positive integer if the string in the parameter comes before the string associated with the method, and a negative integer if the parameter comes after it.
ToLower |  | String | Returns a new identical string, except the characters are all lowercase.
ToUpper |  | String | Returns a new identical string, except the characters are all lowercase.

For example, if lastName is a String variable, you can write

lastName.Equals("Olson")  ' Tests whether lastName equals "Olson"

Because every String literal is also a String object, Visual Basic lets you append the method call to a literal, if you so choose.

"Olson".Equals(lastName)   ' Tests whether lastName equals "Olson"

As another example, you might write

0 > lastName.CompareTo("Olson")    ' Tests if lastName comes before "Olson"

Comparison of strings follows the collating sequence of the Unicode character set. When the computer tests a relationship between two strings, it begins with the first character of each, compares them according to the collating sequence, and if they are the same, repeats the comparison with the next character in each string. The character-by-character test proceeds until a mismatch is found, the final characters have been compared and are equal, or one string runs out of characters. If all their characters are equal, then the two strings are equal. If a mismatch is found, then the string with the character that comes before the other is the "lesser" string. If their characters are equal throughout the shorter string, the shorter string comes before the longer string.

For example, given the statements

Dim word1 As String
Dim word2 As String

word1 = "Tremendous"
word2 = "Small"

the following relational expressions have the values shown.

Expression | Value | Reason
word1.Equals(word2) | False | They are unequal in the first character.
0 < word1.CompareTo(word2) | True | "T" comes after "S" in the collating sequence.
0 < word1.CompareTo("small") | False | "T" comes before "s" in the collating sequence.
0 > word1.CompareTo("Tremble") | False | Fifth characters don't match, and "b" comes before "e".
word2.Equals("Small") | True | They are equal.
0 = "cat".CompareTo("dog") | False | They are unequal.

In most cases, the ordering of strings corresponds to alphabetical ordering. But when strings have mixed case letters, you can get nonalphabetical results. For example, in a phone book we expect to see Macauley before MacPherson, but the Unicode collating sequence places all English uppercase letters before the lowercase letters, so the string "MacPherson" compares as less than "Macauley". To compare strings for strict alphabetical ordering, all the characters must be in the same case. Here we see examples in which ToLower and ToUpper are used to perform the comparison correctly.

lowerCaseString = myString.ToLower()
upperCaseString = myString.ToUpper()

You can use these methods directly in a comparison expression. For example, the following expressions convert word1 and word2 to the same case before comparing them. It doesn't matter whether the strings are both converted to uppercase or both are converted to lowercase, as long as they are the same.

0 > word1.ToLower().CompareTo(word2.ToLower())
0 > word1.ToUpper().CompareTo(word2.ToUpper())

If two strings with different lengths are compared and the comparison is equal up to the end of the shorter string, then the shorter string compares as less than the longer string. For example, if word2 contains "Small", the expression

0 > word2.CompareTo("Smaller")

yields True because the strings are equal up to their fifth character position (the end of the string on the left) and the string on the right is longer.

Logical Operators In mathematics, the logical (or Boolean) operators AND, OR, and NOT take logical expressions as operands. Visual Basic uses these same words (And, Or, and Not) for the logical operators. By combining relational operators with logical operators, you can make more complex assertions. For example, suppose you want to determine whether a final score is greater than 90 and a midterm score is greater than 70. In Visual Basic you would write the expression this way:

finalScore > 90 And midtermScore > 70

The And operation requires both relationships to be true in order for the overall result to be true. If either or both of the relationships is False, the entire result is False.

The Or operation takes two logical expressions and combines them. If either or both are true, the result is True. Both values must be False for the result to be False. Now you can determine whether the midterm grade is an A or the final grade is an A. If either the midterm grade or the final grade equals A, the assertion is True. In Visual Basic, you can write the expression like this:

midtermGrade = "A"c Or finalGrade = "A"c

The And and Or operators always appear between two expressions; they are binary (two-operand) operators. The Not operator is a unary (one-operand) operator. It precedes a single logical expression and gives its opposite as the result. If (grade = "A"c) is false, then Not(grade = "A"c) is true. Not gives a convenient way of reversing the meaning of an assertion. For example,

Not(hours > 40)

is the equivalent of

hours <= 40

In some contexts, the first form is clearer; in others, the second makes more sense.

The following pairs of expressions are equivalent:

Expression | Equivalent Expression
Not(a = b) | a <> b
Not(a = b Or a = c) | a <> b And a <> c
Not(a = b And c > d) | a <> b Or c <= d

Take a close look at these expressions to be sure you understand why they are equivalent. Try evaluating them with some values for a, b, c, and d. Notice the pattern: The expression on the left is just the one to its right with Not added and the relational and logical operators reversed (for example, = instead of <> and Or instead of And). Remember this pattern. It allows you to rewrite expressions in the simplest form.[2]

Logical operators can be applied to the results of comparisons. They also can be applied directly to variables of type Boolean. For example, instead of writing

IsElector = (age >= 18 And district = 23)

to assign a value to the Boolean variable isElector, you could use two intermediate Boolean variables, isVoter and isConstituent:

isVoter = (age >= 18)
isConstituent = (district = 23)
isElector = isVoter And isConstituent

The two tables that follow summarize the results of applying And and Or to a pair of logical expressions (represented here by Boolean variables x and y.)

Value of x | Value of y | Value of (x And y)
True | True | True
True | False | False
False | True | False
False | False | False
Value of x | Value of y | Value of (x Or y)
True | True | True
True | False | True
False | True | True
False | False | False

The following table summarizes the results of applying the Not operator to a logical expression (represented by Boolean variable x).

Value of x | Value of Not(x)
True | False
False | True

Short-circuit Evaluation Consider the logical expression

i = 1 And j > 2

Some programming languages use full evaluation of logical expressions. With full evaluation, the computer first evaluates both subexpressions (both i = 1 and j > 2) before applying the And operator to produce the final result.

In contrast, Visual Basic uses short-circuit (or conditional) evaluation of logical expressions. Evaluation proceeds from left to right, and the computer stops evaluating subexpressions as soon as possible—that is, as soon as it knows the Boolean value of the entire expression. How can the computer know if a lengthy logical expression yields True or False if it doesn't examine all the subexpressions? Let's look first at the And operation.

Short-circuit (conditional) evaluation
Evaluation of a logical expression in left-to-right order with evaluation stopping as soon as the final Boolean value can be determined

An And operation yields the value True only if both of its operands are True. In the previous expression, suppose that the value of i happens to be 95. The first subexpression yields False, so it isn't necessary even to look at the second subexpression. The computer stops evaluation and produces the final result of False.

With the Or operation, the left-to-right evaluation stops as soon as a subexpression yielding True is found. Remember that an Or produces a result of True if either one or both of its operands are True. Given this expression:

c <= d Or e = f

if the first subexpression is True, evaluation stops and the entire result is True. The computer doesn't waste time evaluating the second subexpression.

Start Sidebar
Background Information
George Boole

Boolean algebra is named for its inventor, English mathematician George Boole, born in 1815. His father, a tradesman, began teaching him mathematics at an early age. But Boole initially was more interested in classical literature, languages, and religion—interests he maintained throughout his life. By the time he was 20, he had taught himself French, German, and Italian. He was well-versed in the writings of Aristotle, Spinoza, Cicero, and Dante, and wrote several philosophical papers himself.

Click To expand

At 16, to help support his family, he took a position as a teaching assistant in a private school. His work there and a second job left him little time to study. A few years later, he opened a school and began to learn higher mathematics on his own. In spite of his lack of formal training, his first scholarly paper was published in the Cambridge Mathematical Journal when he was just 24. Boole went on to publish over 50 papers and several major works before he died in 1864, at the peak of his career.

Boole's The Mathematical Analysis of Logic was published in 1847. It would eventually form the basis for the development of digital computers. In the book, Boole set forth the formal axioms of logic (much like the axioms of geometry) on which the field of symbolic logic is built.

Boole drew on the symbols and operations of algebra in creating his system of logic. He associated the value 1 with the universal set (the set representing everything in the universe) and the value 0 with the empty set, and restricted his system to these two quantities. He then defined operations that are analogous to subtraction, addition, and multiplication. Variables in the system have symbolic values. For example, if a Boolean variable P represents the set of all plants, then the expression 1 - P refers to the set of all things that are not plants. We can simplify the expression by using - P to mean "not plants." (0 - P is simply 0 because we can't remove elements from the empty set.) The subtraction operator in Boole's system corresponds to the Not operator in Visual Basic. In a Visual Basic program, we might set the value of the Boolean variable plant to True when the name of a plant is entered, and Not(plant) is True when the name of anything else is input.

The expression 0 + P is the same as P. However, 0 + P + F, where F is the set of all foods, is the set of all things that are either plants or foods. So the addition operator in Boole's algebra is the same as the Visual Basic Or operator.

The analogy can be carried to multiplication: 0 x P is 0, and 1 x P is P, but what is P x F? It is the set of things that are both plants and foods. In Boole's system, the multiplication operator is the same as the And operator.

In 1854, Boole published An Investigation of the Laws of Thought, on Which Are Founded the Mathematical Theories of Logic and Probabilities. In the book, he described theorems built on his axioms of logic and extended the algebra to show how probabilities could be computed in a logical system. Five years later, Boole published Treatise on Differential Equations, then Treatise on the Calculus of Finite Differences. The latter is one of the cornerstones of numerical analysis, which deals with the accuracy of computations. (In Chapter 13, we examine the important role numerical analysis plays in computer programming.)

Boole received little recognition and few honors for his work. Given the importance of Boolean algebra in modern technology, it is hard to believe that his system of logic was not taken seriously until the early twentieth century. George Boole is truly one of the founders of computer science.

End Sidebar

Precedence of Operators

In Chapter 4 we discussed the rules of precedence, the rules that govern the evaluation of complex arithmetic expressions. Visual Basic's rules of precedence also govern relational and logical operators. Here's a list showing the order of precedence for the arithmetic, relational, and logical operators:

Click To expand

Operators on the same line in the list have the same precedence. If an expression contains several operators with the same precedence, most of the operators group (or associate) from left to right. For example, the expression

a / b * c

means (a / b) * c, not a / (b * c). However, the Not operator groups from right to left. Although you'd never have occasion to use this expression:

Not Not badData

the meaning is Not(Not(badData)) rather than the meaningless (Not Not)badData. Appendix B, Operator Precedence, lists the order of precedence for all operators in Visual Basic. In skimming the appendix, you can see that a few of the operators associate from right to left (for the same reason we just described for the Not operator).

Parentheses are used to override the order of evaluation in an expression. If you're not sure whether parentheses are necessary, use them anyway. The compiler disregards unnecessary parentheses. So if they clarify an expression, use them. Some programmers like to include extra parentheses when assigning a relational expression to a Boolean variable:

dataInvalid = (inputVal = 0)

The parentheses are not needed in the example above. The compiler understands that since the first equal sign is meant to be an assignment operator, the second equal sign is being used as the equality operator. So we could write the statement as

dataInvalid = inputVal = 0

but the parenthesized version is more readable. Without the parentheses, it looks as if we are trying to assign the value 0 to both inputVal and dataInvalid.

One final comment about parentheses: Visual Basic, like other programming languages, requires that parentheses always be used in pairs. Whenever you write a complicated expression, take a minute to go through and pair up all of the opening parentheses with their closing counterparts.

Click To expand
Start Sidebar
Software Engineering Tip
Changing English Statements into Logical Expressions

In most cases, you can write a logical expression directly from an English statement or mathematical term in an algorithm. But you have to watch out for some tricky situations. Remember our sample logical expression:

midtermGrade = "A"c Or finalGrade = "A"c

In English, you would be tempted to write this expression: "Midterm grade or final grade equals A." In Visual Basic, you can't write the expression as you would in English. That is,

midtermGrade Or finalGrade = "A"c

won't work because the Or operator is connecting a Char value (midtermGrade) and a logical expression (finalGrade = "A"c). The two operands of Or must be logical expressions. This example generates a syntax error message.

A variation of this mistake is to express the English assertion "i equals 3 or 4" as

i = 3 Or 4

But the syntax is incorrect. In the second expression, 4 is an Integer rather than a Boolean value. The Or operator (and the And operator) can only connect two Boolean expressions. Here's what we want:

i = 3 Or i = 4

In math books, you might see a notation like this:

12 < y < 24

which means "y is between 12 and 24." This expression is illegal in Visual Basic. First, the relation 12 < y is evaluated, giving a Boolean result. The computer then tries to compare it with the number 24. Because a Boolean value cannot be converted to any type other than String, the expression is invalid. To write this expression correctly in Visual Basic, you must use the And operator as follows:

12 < y And y < 24
End Sidebar

Relational Operators with Floating-Point Types

So far, we've talked only about comparing Integer, Char, and String values. Here we look at Float and Double values.

Do not compare floating-point numbers for equality. Because small errors in the rightmost decimal places are likely to arise when calculations are performed on floating-point numbers, two Float or Double values rarely are exactly equal. For example, consider the following code that uses two Double variables named oneThird and x:

oneThird = 1.0 / 3.0
x = oneThird + oneThird + oneThird

We would expect x to contain the value 1.0, but it may not. The first assignment statement stores an approximation of 1/3 into oneThird, perhaps 0.333333. The second statement stores a value like 0.999999 into x. If we now ask the computer to compare x with 1.0, the comparison may yield False.

Instead of testing floating-point numbers for equality, we test for near equality. To do so, we compute the difference between the two numbers and test to see if the result is less than some maximum allowable difference. For example, we often use comparisons like this:

Math.Abs(r - s) < 0.00001

where Math.Abs is the absolute value method from the Visual Basic Math class. The expression Math.Abc(r - s) computes the absolute value of the difference between two variables r and s. If the difference is less than 0.00001, the two numbers are close enough to call them equal. We discuss this problem with floating-point accuracy in more detail in Chapter 13.

[1]The name boolean is a tribute to George Boole, a nineteenth-century English mathematician who described a system of logic using variables with just two values, True and False. (See the Background Information box on page 222.)

[2]In Boolean algebra, the pattern is formalized by a theorem called DeMorgan's law.


6.3 The If Statement

Now that we've seen how to write logical expressions, let's use them to alter the normal flow of control in a program. The If statement is the fundamental control structure that allows branches in the flow of control. With it, we can ask a question and choose a course of action: if a certain condition exits (the assertion is true), perform one action, else perform a different action.

The computer performs just one of the two actions under any given set of circumstances. Yet we must write both actions into the program. Why? Because, depending on the circumstances, the computer can choose to execute either of them. The If statement gives us a way of including both actions in a program and gives the computer a way of deciding which action to take.

The If-Else Form

In Visual Basic, the If statement comes in two forms, the If-Else form and the If form. Let's look first at the If-Else. Here is its syntax template:

Click To expand

The expression in parentheses must produce a Boolean result. At run time, the computer evaluates the expression. If the value is True, the computer executes Statement1A. If the value of the expression is False, Statement1B is executed. Statement1A is often called the Then-clause; Statement1B, the Else-clause. Figure 6.3 illustrates the flow-of-control of the If-Else. In the figure, Statement2 is the next statement in the program after the entire If statement.

Click To expand
Figure 6.3: If-Else flow of control

The code fragment below shows how to write an If statement in a program. Observe the indentation of the Then-clause and the Else-clause, which makes the statement easier to read. Also notice the placement of the statement following the If statement.

If (hours <= 40R) Then
     pay = rate * hours
Else
     pay = rate * (40R + (hours - 40R) * 1.5)
End If
lblOut.Text = "Pay is " & pay

In terms of instructions to the computer, the previous code fragment says, "If hours is less than or equal to 40.0, compute the regular pay and then go on to execute the output statements. But if hours is greater than 40, compute the regular pay and the overtime pay, and then go on to execute the output statement." Figure 6.4 shows the flow of control of this If statement.

Click To expand
Figure 6.4: Flow of control for calculating pay

If-Else statements are often used to check the validity of input. For example, before we ask the computer to divide by a data value, we should be sure that the value is not zero. (Even computers can't divide something by zero.) If the divisor is zero, our program should thus display an error message. Here's an example that adds an error message to a label on a form.

Dim errorMsg As String
errorMsg = "Division by zero is not allowed. " & _
           "Enter a new value for divisor and press Done button."
If (divisor <> 0) Then
     result = dividend / divisor
Else
     lblOut.Text = errorMsg
End If

If this code fragment is within the handler for a button-controlling screen input, the program waits until another event occurs, either the pressing of the button or the closing of the input window.

Statement Sequences (Compound Statements)

In our division-by-zero example, suppose that when the divisor is equal to zero we want to do two things: add the error message to the form and set the variable named result equal to a special value like Integer.MaxValue. We would need two statements in the same branch, but the syntax template seems to limit us to one.

What we really want to do is turn the Else-clause into a sequence of statements. This is easy. Remember from Chapter 2 that the compiler treats the statement sequence (compound statement) like a single statement. If you put the sequence of statements inside a program construct such as an If statement, the statements become a statement sequence. For example:

If (divisor <> 0) Then
     result = dividend / divisor
Else
     lblOut.Text = errorMsg
     result = Integer.MaxValue
End If

If the divisor is zero, the computer both displays the error message and sets the value of result to Integer.MaxValue before continuing with whatever statement follows the If statement.

Statement sequences can be used in both branches of an If-Else statement. For example:

Dim errorMsg As String
Dim okMsg As String
errorMsg = "Division by zero is not allowed."
okMsg = "Division performed."
If (divisor <> 0) Then
     result = dividend / divisor
     lblOut.Text = okMsg
Else
     lblOut.Text = errorMsg
     result = Integer.MaxValue
End If

The If Form

Sometimes you run into a situation where you want to say, "If a certain condition exists, perform some action; otherwise, don't do anything." In other words, you want the computer to skip a sequence of instructions if a certain condition isn't met. You could do this by leaving the Else branch empty, using only the null statement:

If (a <= b) Then
     c = 20
Else
     ⋮

Better yet, you could simply leave off the Else part. The resulting statement is the If form of the If statement. This is its syntax template:

Click To expand

Here's an example of an If form.

If (age < 18) Then
     lblOut.Text = "Not an eligible "
End If
lblOut.Text = lblText.Out & "voter."

This statement means that if age is less than 18, first add the string "Not an eligible " to label lblOut and then add the string "voter." to the label. If age is not less than 18, skip the first statement and go directly to adding the string "voter." to the label. Figure 6.5 shows the flow of control for an If form.

Click To expand
Figure 6.5: If flow of control

Like the two branches in an If-Else form, the one branch in an If can be a block. For example, let's say you are writing a program to compute income taxes. One of the lines on the tax form says, "Subtract line 23 from line 27 and enter result on line 24; if result is less than zero, enter zero and check box 24A." You can use an If statement to do this in Visual Basic:

Dim checkMsg As String
checkMsg = "Check box 24A"
result = line17 - line23
If (result < OR) Then
     lblOut.Text = checkMsg
     result = OR
End If
line24 = result

This code does exactly what the tax form says it should. It computes the result of subtracting line 23 from line 17. Then it looks to see if result is less than zero. If it is, the fragment displays a message telling the user to check box 24A and then sets result to zero. Finally, the calculated result (or zero, if the result is less than zero) is stored into a variable named line24.

You can also write the If form without the End If at the end of the form. When you do this, the Then-clause of the If form is restricted to a single statement, which immediately follows the word Then. For example:

Dim checkMsg As String
checkMsg = "Check box 24A"
result = line17 - line23
If (result < 0.0) Then lblOut.Text = checkMsg

The statement that follows Then is executed if the assertion is true. It is the only statement that is executed based on the value of the assertion, however, because there is no statement sequence. Any statement that follows this line will, of course, be executed as part of the normal control flow of the program. If you want a statement sequence for the Then-clause, then you must use the keyword Then and put the statements below the clause.

Nested If Statements

There are no restrictions on what the statements in an If form can be. Therefore, an If within an If is okay. In fact, an If within an If within an If is legal. The only limitation here is that people cannot follow a structure that is too involved, and readability is one of the marks of a good program.

When we place an If within an If, we are creating a nested control structure. Control structures nest much like mixing bowls do, with smaller ones tucked inside larger ones. Here's an example written in pseudocode:

Click To expand

In general, any problem that involves a multiway branch (more than two alternative courses of action) can be coded using nested If statements. For example, to store the name of a month into a string variable, we could use a sequence of If statements (unnested):

If (month = 1) Then
     monthName = "January"
End If
If (month = 2) Then
     monthName = "February"
End If
If (month = 3) Then
     monthName = "March"
End If
⋮
If (month = 12) Then
     monthName = "December"
End If

But the equivalent nested If structure:

If (month = 1) Then
     monthName = "January"
Else
     If (month = 2) Then
          monthName = "February"
     Else
          If (month = 3) Then
               monthName = "March"
     Else
               If (month = 4)
⋮

is more efficient because it makes fewer comparisons. The first version—the sequence of independent If statements—always tests every condition (all 12 of them), even if the first one is satisfied. In contrast, the nested If solution skips all remaining comparisons after one alternative has been selected. As fast as modern computers are, many applications require so much computation that an inefficient algorithm can waste hours of computer time. Always be on the lookout for ways to make your programs more efficient, as long as doing so doesn't make them difficult for other programmers to understand. It's usually better to sacrifice a little efficiency for the sake of readability.

In the last example, notice how the indentation of the Then- and Else-clauses causes the statements to move continually to the right. Instead, we can use a special indentation style with deeply nested If-Else statements to indicate that the complex structure is just choosing one of a set of alternatives. This general multiway branch is known as an If-ElseIf control structure:

If (month = 1) Then
     monthName = "January"
ElseIf (month = 2) Then
     monthName = "February"
ElseIf (month = 3) Then
     monthName = "March"
ElseIf (month = 4) Then
⋮
Else
     monthName = "December"
End If

Notice that ElseIf is one word. This style prevents the indentation from marching continuously to the right. But, more importantly, it visually conveys the idea that we are using a 12-way branch based on the variable month.

Another particularly helpful use of the nested If is when you want to select from a series of consecutive ranges of values. For example, suppose that you want to display a message indicating an appropriate activity for the outdoor temperature, given the following table.

Activity | Temperature
Swimming | Temperature > 85
Tennis | 70 < temperature <= 85
Golf | 32 < temperature <= 70
Skiing | 0 < temperature <= 32
Dancing | Temperature <= 0

At first glance, you may be tempted to write a separate If statement for each range of temperatures. On closer examination, however, it is clear that these conditions are interdependent. That is, if one of the statements is executed, none of the others should be executed. We really are selecting one alternative from a set of possibilities—just the sort of situation in which we can use a nested If structure as a multiway branch. The only difference between this problem and our earlier example of printing the month name from its number is that we must check ranges of numbers in the If expressions of the branches.

When the ranges are consecutive, we can take advantage of that fact to make our code more efficient. We arrange the branches in consecutive order by range. Then, if a particular branch has been reached, we know that the preceding ranges have been eliminated from consideration. Thus, the If expressions must compare the temperature to only the lowest value of each range.

message = "The recommended activity is "
If (temperature > 85) Then
     message = message & "swimming."
ElseIf (temperature > 70) Then
     message = message & "tennis."
ElseIf (temperature > 32) Then
     message = message & "golf."
ElseIf (temperature > 0) Then
     message = message & "skiing."
Else
     message = message & "dancing."
End if

To see how this If-ElseIf structure works, consider the branch that tests for temperature greater than 70. If it has been reached, you know that temperature must be less than or equal to 85 because that condition causes this particular ElseIf branch to be taken. Thus, you need to test only whether temperature is above the bottom of this range (> 70). If that test fails, then you enter the next ElseIf-clause knowing that temperature must be less than or equal to 70. Each successive branch checks the bottom of its range until you reach the final Else, which takes care of all the remaining possibilities.

Note that if the ranges aren't consecutive, then you must test the data value against both the highest and lowest value of each range. You still use an If-ElseIf structure because that is best for selecting a single branch from multiple possibilities, and you can arrange the ranges in consecutive order to make them easier for a human reader to follow. But there is no way to reduce the number of comparisons when there are gaps between the ranges.

Start Sidebar
Problem-Solving Case Study
A Simple Calculator

Problem It's useful to have a calculator that you can display on your computer screen when you look around and can't find your hand-held one. Let's write a Visual Basic application that simulates a simple calculator. Once we have the basic design it is easy to extend it to provide other functions.

Brainstorming You pick up your pocket calculator. After all, this problem is one of simulating the pocket calculator. What are the objects that you see? A register on the top that shows what you are entering and displays the answer, an ON button, buttons for each of the ten decimal digits, buttons for each of the four basic operations, a CLEAR button, and a button marked with an equal sign. There are buttons for percent and square root, but you do not use them often enough to add them to the simulation. So the first list of proposed classes is as follows:

Click To expand

Filtering We certainly need a register to show the output. Do we need an ON button? No, running the program is the equivalent to the ON button. However, we do need some way of quitting the program. We could have a quit button or we could let the window closing end the program. We need an object to represent the calculator itself; that is, we need a form to hold all the other objects. What about the buttons for each digit? No, we can let the user input a number into a text field rather than pushing individual buttons. Do we need an ENTER button? No, we can let the button for the operation signal that the value is ready to be read. We do need buttons for each operation. We certainly need a CLEAR button to set the register value to zero. What about the equal button? On the calculator it signals that we want to see the results so far in the register. Instead of entering an equal button, let's just display the result of each operation in the register as we go along.

Here is our second pass at the classes for our application:

Click To expand

Scenarios The register is simply a label that displays what is put into it, so it actually has no further responsibility. The form object is a container into which labels, text boxes, and buttons are placed, so it collaborates with each of these. The text box for the input has no responsibilities; it just collaborates with the appropriate button handler. The label for the text box has only one responsibility: to display the input prompt. What happens when the operation buttons are pressed? The four operator buttons are event sources; they have no responsibility, but their handlers do. When one is pressed, Visual Basic calls the appropriate button handler, which collaborates with the text box to input a value, performs a calculation, and send the value to the register. The CLEAR button is different in function from the others but its only responsibility is to be an event source. However, its event handler must collaborate with the register to set it to zero. It makes sense to use one button handler for the four arithmetic buttons, but a separate one for the CLEAR button.

The application has responsibility for setting up the window, handling the button events through the inclusion of the two event subprocedures, and handling the window-closing event.

CRC Cards All of the objects are instances of classes provided in the Visual Basic Winforms namespace. So the only CRC card is for class Form1.

Click To expand

Responsibility Algorithms Do you remember in Chapter 1, after the long first program, that we said that much of the code dealt with input and output, which would soon become second nature to you? Well the first responsibility should sound comfortingly familiar. It is exactly the same as the first responsibility for Chapter 5's Case Study. Does this mean that we can just cut and paste the algorithms? No, the objects in the window are not the same, but the steps used to create them are the same, so we can reuse the top-level module.

...........................................................................
Prepare Form for Input                                              Level 1

 Place controls on form
 Set properties for controls, including names and any text

The first step in creating a user interface is to place the controls that are going to make up the interface on the form. The form will include two labels (one for input and one for the register), a text box for entering a data value, four buttons for the user to click to indicate the operation when a value is ready for input, and a button for the user to click to clear the register.

Before we go on, let's simplify the problem somewhat. Let's first create a calculator with only plus and minus functions. After we verify this simplified version of the program, we can come back and add division and multiplication (see Case Study Follow-Up Question 2). We also set the TextAlign property of the register and the text box to Right to make the numbers look better in the calculator.

...........................................................................
Set Control Properties                                              Level 2

 Set Form1's Text property to "Calculator"
 Name one label lblResult, set its Text property to "Result"
 Name one label lblRegister, set its Text property to "0.0"
 Name text box txtInput, set its Text property to ""
 Name one button btnAdd, set its Text property to "+"
 Name one button btnSubtract, set its Text property to "-"
 Name one button btnClear, set its Text property to "Clear"
 Set txtInput TextAlign property to Right
 Set lblRegister TextAlign property to Right

The form, in design view, should look like this:

Click To expand

We have now completed the first responsibility for the Calculator program. The second responsibility is to handle the numeric buttons; that is, write the Click event code for the buttons. What should the Click event do when an add or subtract button is pushed? First, it should get the value to be used in the calculation from the text box and convert it to a real number. If the user enters a string that does not represent a legitimate real number, a FormatException may be thrown. In Chapter 10 we show you how to handle this exception; here we assume correct input.

The next step is to determine how the arithmetic operations (addition and subtraction) should be performed. Because we only have one text box to store a number, we have to move each entry in the text box to a variable. This simply requires setting a variable, result, to 0, adding or subtracting the value in the text box to the value in result, and then clearing the text box so another number can be entered. Finally, we need to put the focus back in the text box so that the user can enter another value without having to move the cursor to the text box.

...........................................................................
Handle Addition Button Click Event                                  Level 1

 Get the entry string from text box
 Set operand to numerical value (Double) of entry string
 Set result to result + operand
 Display result in register
 Clear text box
 Set focus in text box

...........................................................................
Handle Subtraction Button Click Event                               Level 1

 Get the entry string from text box
 Set operand to numerical value (Double) of entry string
 Set result to result — operand
 Display result in register
 Clear text box
 Set focus in text box

The third responsibility is even simpler; all we have to do is set the register and text box to zero as well as the variable that contains the same value, and put the focus back in the text box. These actions are performed in the Clear button's Click event.

...........................................................................
Handle Clear Button Click Event                                     Level 1

 Set result to zero
 Display result in register
 Clear text box
 Set focus

The fourth responsibility, handling window closing, is taken care of automatically by Visual Basic.

Objects and Variables
Name | Type | Description
Form1 | Form | Form to display on screen
lblResultText | Label | Indicates output area
lblResult | Label | Label to show output
lblEntry | Label | Label indicating input text box
btnAddition | Button | Addition button
btnSubtraction | Button | Subtraction button
btnClear | Button | Clear button
result | Double | Holds current value
operand | Double | Holds input value

The coding of the program into Visual Basic is now easy and straightforward. Here is the result.

' Import user interface classes
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms

Public Class Form1
    Inherits System.WinForms.Form
    Dim result As Double         ' Holds current value
    Dim operand As Double        ' Holds input value
    Public Sub New()
        MyBase.New()
        Form1 = Me
        InitializeComponent()
        result = 0R              ' Initialize result to 0.0
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()         ' Close the frame
        components.Dispose()     ' Exit the program
    End Sub

#Region " Windows Form Designer generated code "
#End Region

Protected Sub btnSubtraction_Click(ByVal sender As Object, _
                                   ByVal e As System.EventArgs)

    operand = CDbl(txtInput.Text)   ' Get value from text box and convert
    result = result - operand       ' Subtract from result
    lblRegister.Text = CStr(result) ' Convert and store in register
    txtInput.Clear()                ' Clear text box for input
    txtInput.Focus()                ' Put focus back in text box
End Sub

Protected Sub btnAddition_Click(ByVal sender As Object, _
                                ByVal e As System.EventArgs)
    operand = CDbl(txtInput.Text)   ' Get value from text box and convert
    result = result + operand       ' Add to result
    lblRegister.Text = CStr(result) ' Convert and store in register
    txtInput.Clear()                ' Clear text box for input
    txtInput.Focus()                ' Put focus back in text box
End Sub

Protected Sub btnClear_Click(ByVal sender As Object, _
                             ByVal e As System.EventArgs)
    txtInput.Clear()                ' Clear text box for input
    lblRegister.Text = ""           ' Clear register
    result = 0R                     ' Set register back to zero
    lblRegister.Text = "0.0"        ' Display 0.0 in register
    txtInput.Focus()                ' Put focus back in text box
End Sub

End Class

When we run the program, it displays a window like the one shown below.

Click To expand
End Sidebar


Testing and Debugging

In Chapter 1, we discussed the problem-solving and implementation phases of computer programming. Testing is an integral part of both phases. Testing in the problem-solving phase is done after the solution is developed but before it is implemented. In the implementation phase, we test after the algorithm is translated into a program, and again after the program has compiled successfully. The compilation itself constitutes another stage of testing that is performed automatically.

Testing Strategies

When an individual programmer is designing and implementing a program, he or she can find many software errors with pencil and paper. Desk checking the design solution is a very common method of manually verifying a design or program. The programmer writes down essential data (variables, input values, parameters of subprograms, and so on) and walks through the design, manually simulating the actions of the computer and recording changes in the data on paper. Portions of the design or code that are complex or that are a source of concern should be double-checked.

Desk checking
Tracing the execution of a design or program on paper

Desk checking can be done by an individual, but most sizable computer programs are developed by teams of programmers. Two extensions of desk checking that are used effectively by programming teams are design or code walk-throughs and inspections. These are formal team activities, the intention of which is to move the responsibility for uncovering bugs from the individual programmer to the group. Because testing is time-consuming and errors cost more the later they are discovered, the goal is to identify errors before testing begins.

Walk-through
A verification method in which a team performs a manual simulation or design
Inspection
A verification method in which one member of a team reads the program or design line by line and the others point out errors

In a walk-through the team performs a manual simulation of the design or program with sample test inputs, keeping track of the program's data by hand. Unlike thorough program testing, the walk-through is not intended to simulate all possible test cases. Instead, its purpose is to stimulate discussion about the way the programmer chose to design or implement the program's requirements.

At an inspection, a reader (not necessarily the program's author) goes through the design or code line by line. Inspection participants point out errors, which are recorded on an inspection report. Some errors are uncovered just by the process of reading aloud. Others may have been noted by team members during their pre-inspection preparation. As with the walk-through, the chief benefit of the team meeting is the discussion that takes place among team members. This interaction among programmers, testers, and other team members can uncover many program errors long before the testing stage begins.

At the high-level design stage, the design should be compared to the application requirements to make sure that all required responsibilities have been included and that this application or class correctly interfaces with other software in the system. At the low-level design stage, when the design has been filled out with more details, it should be reinspected before it is implemented.

After the code is written, you should go over it line by line to be sure that you've faithfully reproduced the algorithm in a code walk-through. In a team programming situation, you ask other team members to walk through the algorithm and code with you, to double-check the design and code.

You also should take some actual values and hand-calculate what the output should be by doing an execution trace (or hand trace). When the program is executed, you can use these same values as input and then check the results. The computer is a very literal device—it does exactly what we tell it to do, which may or may not be what we want it to do. We try to make sure that a program does what we want by tracing the execution of the statements.

Execution trace (hand trace)
The process of going through the program with actual values recording the state of the variables

When a program contains branches, it's a good idea to retrace its execution with different input data so that each branch is traced at least once. In the next section, we describe how to develop data sets that test each of a program's branches.

To test an application with functions, we need to execute each function. If a function contains branches, we need to execute each branch at least once and verify the results. For example, in the Problem-Solving Case Study, there are three Click methods (a method is similar to a function). Therefore, we need at least three data sets to test the methods. The following combination of buttons and input values cause all the methods to be executed:

 | Button | Value
Set 1 | + | 12.5
Set 2 | - | 2.5
Set 3 | Clear |

Every event in the program is executed at least once through this series of test runs, which provide what is called minimum complete coverage of the program's branching structure. Whenever you test a program with branches in it, you should design a series of tests that covers all of the branches. Because an action in one branch of a program often affects processing in a later branch, it is critical to test as many combinations of branches, or paths, through a program as possible. By doing so, you can be sure there are no interdependencies that could cause problems. Should you try all possible paths? Yes, in theory you should. However, the number of paths in even a small program can be very large.

The approach to testing that we've used here is called code coverage because the test data is designed by looking at the code of the program. Code coverage is also called white-box (or clear-box) testing because you are allowed to see the program code while designing the tests. Another approach to testing, data coverage, attempts to test as many allowable data values as possible without regard to the program code. Because you need not see the code, it is also called black-box testing—the same set of tests would be used even if the code were hidden in a black box. Complete data coverage is as impractical as complete code coverage for many programs. For example, the btnAddition_Click event reads one Double value and thus has an immense number of possible input values.

Often, testing is a combination of these two strategies. Instead of trying every possible data value (data coverage), the idea is to examine the code (code coverage) and look for ranges of values for which processing is identical. Then one tests the values at the boundaries and, sometimes, a value in the middle of each range. For example, a simple condition such as

alpha < 0

divides the integers into two ranges:

  1. Integer.MinValue through -1

  2. 0 through Integer.MaxValue

Thus, you should test the four values Integer.MinValue, -1, 0, and Integer.Max-Value. A compound condition such as

alpha >= 0 And alpha <= 100

divides the integers into three ranges:

  1. Integer.MinValue through -1

  2. 0 through 100

  3. 101 through Integer.MaxValue

Thus, you have six values to test. In addition, to verify that the relational operators are correct, you should test for values of 1 (> 0) and 99 (< 100).

Conditional branches are only one factor in developing a testing strategy. We consider more of these factors in later chapters.

The Test Plan

We've discussed strategies and techniques for testing programs, but how do you approach the testing of a specific program? You do it by designing and implementing a test plan—a document that specifies the test cases that should be tried, the reason for each test case, and the expected output. Test plan implementation involves running the program using the data specified by the test cases in the plan and checking and recording the results.

Test plan
A document that specifies how a program is to be tested
Test plan implementation
Using the test cases specified in a test plan to verify that a program outputs the predicted results

The test plan should be developed together with the design. The following table shows a partial test plan for the Calculator application. The first test case assumes that the calculator has an initial value of 0.0. Successive test cases use the preceding result as one of the two operands for a calculation. To be more thorough, we should check that the program properly handles closing the window, and we should include some negative as well as positive values.

Implementing a test plan does not guarantee that a program is completely correct. It means only that a careful, systematic test of the program has not demonstrated any bugs. The situation shown in Figure 6.6 is analogous to trying to test a program without a plan—depending only on luck, you may completely miss the fact that a program contains numerous errors. Developing and implementing a written test plan, on the other hand, casts a wide net that is much more likely to find errors.

Click To expand
Figure 6.6: When you test a program without a plan, you never know what you might be missing

Test Plan for the Calculator Application

Reason for Test Case | Input Values | Expected Output | Observed Output
Test addition command | 12.5, + | 12.5 |
Test subtraction command | 2.5, - | 10 |
Test clear command | none, Clear | 0.0 |
Test window closing | none | window disappears |

Tests Performed Automatically during Compilation and Execution

Once a program is coded and test data has been prepared, it is ready for compiling. The compiler has two responsibilities: to report any errors and (if there are no errors) to translate the program into object code or Bytecode.

Errors can be syntactic or semantic. The compiler finds syntactic errors. For example, the compiler warns you when reserved words are misspelled, identifiers are undeclared, and operand types are mismatched. These errors are marked even before the program is compiled. When the compiler finds an error while you are entering source code, it will underline the error with a blue squiggly line. This gives you a visual clue that you have an error, and you must fix the error (and any others) before the program will run. The compiler won't find all of your typing errors. If you type > instead of <, you won't get an error message; instead, you get erroneous results when you test the program. It's up to you to design a test plan and carefully check the code to detect errors of this type.

Semantic errors (also called logic errors) are mistakes that give you the wrong answer. They are more difficult to locate than syntactic errors and usually surface when a program is executing. Visual Basic detects only the most obvious semantic errors—those that result in an invalid operation (integer division by zero, for example). Although typing errors sometimes cause semantic errors, they are more often a product of a faulty algorithm design.

By walking through the algorithm and the code, tracing the execution of the program, and developing a thorough test strategy, you should be able to avoid, or at least quickly locate, semantic errors in your programs.

Figure 6.7 illustrates the testing process we've been discussing. The figure shows where syntax and semantic errors occur and in which phase they can be corrected.

Click To expand
Figure 6.7: Testing process

Testing and Debugging Hints

  1. The equal sign (=) can be used for assignment and for testing equality. Be careful how you use it.

  2. If you use extra parentheses for clarity, be sure that the opening and closing parentheses match up. To verify that parentheses are properly paired, start with the innermost pair and draw a line connecting them. Do the same for the others, working your way out to the outermost pair. For example,

    Click To expand

  3. Here is a quick way to tell whether you have an equal number of opening and closing parentheses. The scheme uses a single number (the "magic number"), whose value initially is 0. Scan the expression from left to right. At each opening parenthesis, add 1 to the magic number; at each closing parenthesis, subtract 1. At the final closing parenthesis, the magic number should be 0. In the following example the digits indicate the total number so far, working from left to right.

    If ( ( (total/scores) > 50) And ( (total/(scores-1) ) < 100) ) Then
0  1 2 3            2     1     2 3      4        3 2         1 0

  4. Don't use =< to mean "less than or equal to"; only the symbol <= works. Likewise, => is invalid for "greater than or equal to"; you must use >= for this operation.

  5. Don't compare strings with the = operator. Use the associated instance methods such as Equal and CompareTo. When testing for alphabetical order, remember to convert the strings to the same case before comparison.

  6. When comparing values of different types, use type conversion functions to convert the values before comparison.

  7. Don't compare floating-point types for exact equality. Check that the difference between the values is less than some small amount.

  8. Test for bad data. If a data value must be positive, use an If statement to test the value. If the value is negative or 0, an error message should be displayed; otherwise, processing should continue.

  9. Take some sample values and try them by hand. Develop a test plan before you start testing your program.

  10. If your program produces an answer that does not agree with a value you've calculated by hand, try these suggestions:

    • Redo your arithmetic.

    • Recheck your input data.

    • Carefully go over the section of code that does the calculation. If you're in doubt about the order in which the operations are performed, insert clarifying parentheses.

    • Check for integer overflow. The value of an Integer variable may have exceeded Integer.MaxValue in the middle of a calculation. Visual Basic doesn't give an error message when this happens.

    • Check the conditions in branching statements to be sure that the correct branch is taken under all circumstances.


Summary of Classes

In this chapter we introduced additional instance methods for the String class that enable us to compare the values contained in strings.

Namespace Name
Class Name |  |  | Comments
String |  |  |
 | Value-Returning Instance Methods: |  |
 |  | Equals(String) | True if string contents match parameter
 |  | CompareTo(String) | 0 if equal, positive Integer if parameter comes before string, negative Integer if parameter comes after string
 |  | ToUpper | Returns an identical string, with all characters uppercase
 |  | ToLower | Returns an identical string, with all characters lowercase


Summary

Using logical expressions is a way of asking questions while a program is running. The program evaluates each logical expression, producing the value True if the expression is true or the value False if the expression is not true.

The If statement allows you to take different paths through a program based on the value of a logical expression. The If-Else statement is used to choose between two courses of action; the If statement is used to choose whether or not to take a particular course of action. The branches of an If or If-Else can be any statement, simple or compound. They can even be another If statement.

The algorithm walk-through is a manual simulation of the algorithm at the design phase. By testing our design in the problem-solving phase, we can eliminate errors that can be more difficult to detect in the implementation phase.

An execution trace is a way of finding program errors once we've entered the implementation phase. It's a good idea to trace a program before you run it, so that you have some sample results against which to check the program's output. A written test plan is an essential part of any program development effort.


Quick Check

Q: | 1. Write a Visual Basic expression that compares the variable letter to the constant "Z" and yields True if letter is less than "Z". (pp. 217–218) | 1. letter - Z c
Q: | 2. Write a Visual Basic expression that yields True if letter is between "A" and "Z" inclusive. (pp. 217–218) | 2. letter -= A c And letter -= Z c
Q: | 3. What form of the If statement would you use to make a Visual Basic program print out "Is an uppercase letter" if the value in letter is between "A" and "Z" inclusive, and print out "Is not an uppercase letter" if the value in letter is outside that range? (pp. 227–228) | 3. The If-Else form
Q: | 4. What form of the If statement would you use to make a Visual Basic program print out "Is a digit" only if the value in the variable someChar is between "0" and "9" inclusive? (pp. 229–230) | 4. The If form
Q: | 5. On the telephone, each of the digits 2 through 9 has a segment of the alphabet associated with it. What kind of control structure would you use to decide which segment a given letter falls into and to print out the corresponding digit? (pp. 231–232) | 5. A nested If statement
Q: | 6. If operands of a relational expression are characters rather than numeric values, what do the relational operators measure? | 6. The relative position of the characters within the collating sequence of the character set
Q: | 7. What is the event that is triggered when a user pushes a button on a form? (pp. 237–239) | 7. Click
Q: | 8. In what phase of the program development process should you carry out an execution trace? (pp. 241–242) | 8. The implementation phase
Q: | 9. You've written an application that displays the corresponding digit on a phone, given a letter of the alphabet. Everything seems to work right except that you can't get the digit "5" to display; you keep getting the digit "6". What steps would you take to find and fix this bug? (pp. 243–245) | 9. Carefully review the section of code that should print out 5 . Check the branching condition and the output statement there. Try some sample values by hand.

Answers

A: | 1. letter < "Z"c
A: | 2. letter >= "A"c And letter <= "Z"c
A: | 3. The If-Else form
A: | 4. The If form
A: | 5. A nested If statement
A: | 6. The relative position of the characters within the collating sequence of the character set
A: | 7. Click
A: | 8. The implementation phase
A: | 9. Carefully review the section of code that should print out "5". Check the branching condition and the output statement there. Try some sample values by hand.


Exam Preparation Exercises

    Chapter 6

  1. What is the purpose of a control structure?

  2. What is a logical expression?

  3. Given the following relational expressions, state in English what they say.

    Expression

    Meaning in English

    one = two

    one <> two

    one > two

    one < two

    one >= two

    one <= two

  4. Given the values for the Boolean variables x, y, and z:

    x = True, y = False, z = True

    evaluate the following logical expressions. In the blank next to each expression, write a T if the result is True or an F if the result is False.

    • _____ a. x And y Or x And z

    • _____ b. (x Or Not(y)) And (Not(x) Or z)

    • _____ c. x Or y And z

    • _____ d. Not(x Or y) And z

  5. Given these values for variables i, j, p, and q:

    i = 10, j = 19, p = True, q = False

    add parentheses (if necessary) to the expressions below so that they evaluate to True.

    • _____ a. i = j Or p

    • _____ b. i >= j Or i <= j And p

    • _____ c. Not(p) Or p

    • _____ d. Not(q) And q

  6. Given these values for the Integer variables j, k, m, and n:

    j = 6, k = 7, m = 11, n = 11

    What is the value of s in the following code?

    s = "Madam"
If (j < k) Then
    If (m <> n) Then
       s = s & "How"
   Else
       s = s & "Now"
   End If
End If
If (j >= m) Then
    s = s & "Cow"
Else
    s = s & "Adam"
End If

  7. Given the Integer variables x, y, and z, where x contains 3, y contains 7, and z contains 6, what is the value of s in each of the following code fragments?

    1. If (x <= 3) Then
    s = "x" & "y"
End If

    2. If (x <> -1) Then
    s = "The value of x is " & x
Else
    s = "The value of y is " & y
End If

    3. If (x <> -1) Then
    s = x
    s = y
    s = z
Else
    s = "y"
    s = "z"
End If

  8. Given this code fragment:

    If (height >= minHeight) Then
   If (weight >= minWeight) Then
      Messagebox.Show("Eligible to serve.")
   Else
      Messagebox.Show("Too light to serve.")
   End If
Else
   If (weight >= minWeight) Then
      Messagebox.Show("Too short to serve.")
   Else
      Messagebox.Show("Too short and too light to serve.")
   End If
End If

  9. Match each logical expression in the left column with the logical expression in the right column that tests for the same condition.

    _____ a. x < y And y < z

    (1) Not(x <> y) And y = z

    _____ b. x > y And y >= z

    (2) Not(x <= y Or y < z

    _____ c. x <> y Or y = z

    (3) (y < z Or y = z) Or x = y

    _____ d. x = y Or y <= z

    (4) Not(x >= y) And Not(y >= z)

    _____ e. x = y And y = z

    (5) Not(x = y And y <> z)

  10. The following expressions make sense but are invalid according to Visual Basic's rules of syntax. Rewrite them so that they are valid logical expressions. (All the variables are of type Integer.)

    1. x < y <= z

    2. x, y, and z are greater than 0

    3. x is equal to neither y nor z

    4. x is equal to y and z

  11. Given these values for the Boolean variables x, y, and z:

    x = True, y = True, z = False

    Indicate whether each expression is True (T) or False (F).

    • _____ a. Not(y Or z) Or x

    • _____ b. z And x And y

    • _____ c. Not y Or (z Or Not(x))

    • _____ d. z Or (x And (y Or z))

    • _____ e. x Or x And z

  12. For each of the following problems, decide which is more appropriate, an If-Else or an If. Explain your answers.

    1. Students who are candidates for admission to a college submit their SAT scores. If a student's score is equal to or above a certain value, print a letter of acceptance for the student. Otherwise, print a rejection notice.

    2. For employees who work more than 40 hours a week, calculate overtime pay and add it to their regular pay.

    3. In solving a quadratic equation, whenever the value of the discriminant (the quantity under the square root sign) is negative, print out a message noting that the roots are complex (imaginary) numbers.

    4. In a computer-controlled sawmill, if a cross section of a log is greater than certain dimensions, adjust the saw to cut four-inch by eight-inch beams; otherwise, adjust the saw to cut two-inch by four-inch studs.

  13. What causes the error message "Else Without If" when this code is written?

    If (mileage < 24R) Then
     lblOut.Text = "Gas"
     lblOut.Text = lblOut.Text & " guzzler"
End If
Else
lblOut.Text = "Fuel efficient"

  14. The following code fragment is supposed to print "Type AB" when Boolean variable typeA and typeB are both True, and print "Type O" when both variables are False. Instead, it prints "Type O" whenever just one of the variables is False. Rewrite the code to make the code segment work the way it should.

    If (typeA Or typeB) Then
    If (typeA And typeB) Then msgbox("Type AB")
Else
    msgbox("Type O")
End If

  15. The nested If structure below has five possible branches depending on the values read into Char variables ch1, ch2, and ch3. To test the structure, you need five sets of data, each set using a different branch. Create the five data sets.

    If (ch1 = ch2) Then
    If (ch2 = ch3) Then
        Messagebox.Show("All initials are the same.")
    Else
       Messagebox.Show("First two are the same.")
    End If
ElseIf (ch2 = ch3) Then
    Messagebox.Show("Last two are the same.")
ElseIf (ch1 = ch3) Then
    Messagebox.Show("First and last are the same.")
Else
    Messagebox.Show("All initials are the same.")
End If

    1. Test data set 1: ch1 = _____ ch2 = _____ ch3 = _____

    2. Test data set 2: ch1 = _____ ch2 = _____ ch3 = _____

    3. Test data set 3: ch1 = _____ ch2 = _____ ch3 = _____

    4. Test data set 4: ch1 = _____ ch2 = _____ ch3 = _____

    5. Test data set 5: ch1 = _____ ch2 = _____ ch3 = _____

  16. If x and y are Boolean variables, do the following two expressions test the same condition?

    x <> y
(x Or y) And Not(x And y)

  17. The following If condition is made up of three relational expressions:

    If (i >= 10 And i <= 20 And i <> 16) Then
    j = 4
End If

    If i contains the value 25 when this If statement is executed, which relational expression(s) does the computer evaluate? (Remember that Visual Basic uses short-circuit evaluation.)

    1. If strings cannot be compared using the relational operators in Visual Basic, how can you compare two strings?

    2. Fill in the following table that describes methods that can be applied to string objects.

      Method Name

      Parameter

      Returns

      English Description

      Equals

      CompareTo

      ToUpper

      ToLower

  19. What is the name of the event that is called when a button is pushed?


Programming Warm-Up Exercises

    Chapter 6

  1. Declare eligible to be a Boolean variable, and assign it the value True.

  2. Write a statement that sets the Boolean variable available to True if numberOrdered is less than or equal to numberOnHand minus numberReserved.

  3. Write a statement containing a logical expression that assigns True to the Boolean variable isCandidate if satScore is greater than or equal to 1100, gpa is not less than 2.5, and age is greater than 15. Otherwise, isCandidate should be False.

  4. Given the declarations

    Dim leftPage As Boolean
Dim pageNumber As Integer

    write a statement that sets leftPage to True if pageNumber is even. (Hint: Consider what the remainders are when you divide different integers by two.)

  5. Write an If statement (or a series of If statements) that assigns to the variable biggest the greatest value contained in variables m, n, and o. Assume the three values are distinct.

  6. Rewrite the following sequence of If statements as a single If-Else statement.

    If (year Mod 4 = 0) Then
    Messagebox.Show(year & " is a leap year.")
End If
If (year Mod 4 <> 0) Then
    year = year + 4 - year Mod 4
    Messagebox.Show(year & " is the next leap year.")
End If

  7. Simplify the following program segment, taking out unnecessary comparisons. Assume that age is an Integer variable.

    If (age > 64) Then
    Messagebox.Show("Senior voter")
End If
If (age < 18) Then
    Messagebox.Show("Under age")
End If
If (age >= 18 And age < 65) Then
    Messagebox.Show("Regular Voter")
End If

  8. The following program fragment is supposed to print out the values 25, 60, and 8, in that order. Instead, it prints out 50, 60, and 4. Why?

    length = 25
width = 60
If (length = 50) Then
    height = 4
Else
    height = 8
End If
Messagebox.Show(length & " " & width & " " & height)

  9. The following Visual Basic program segment is almost unreadable because of the lack of indentation. Fix the indentation to make the program more readable.

    ' This is a nonsense program segment
If (a > 0) Then
If (a < 20) Then
Messagebox.Show("A is in range.")
b = 5
Else
Messagebox.Show("A is too large.")
End If
b = 3
Else
Messagebox.Show("A is too small.")
Messagebox.Show("All done.")
End If

  10. Given the Single variables x1, x2, y1, y2, and m, write a program segment to find the slope of a line through the two points (x1, y1) and (x2, y2). Use the formula:

    to determine the slope of the line. If x1 equals x2, the line is vertical and the slope is undefined. The segment should write the slope with an appropriate label. If the slope is undefined, it should write the message "Slope undefined."

  11. Given the Single variables a, b, c, root1, root2, and discriminant, write a program segment to determine whether the roots of a quadratic polynomial are real or complex (imaginary). If the roots are real, find them and assign them to root1 and root2. If they are complex, write the message "No real roots."

    The formula for the solution to the quadratic equation is

    The ± means "plus or minus" and indicates that there are two solutions to the equation: one in which the result of the square root is added to -b and one in which the result is subtracted from -b. The roots are real if the discriminant (the quantity under the square root sign) is not negative.

  12. Provide a user interface with two buttons, Enter and Quit.

    1. Add the buttons to a form.

    2. Name the buttons.

    3. Write the statements that access a button's name from within the Click event.

    4. Write the statements that display each button's name on the form when the button is pushed.


Programming Problems

  1. Design and write a Visual Basic application that inputs a single letter and prints out the corresponding digit on the telephone. The letters and digits on a telephone are grouped this way:

    2 = ABC

    4 = GHI

    6 = MNO

    8 = TUV

    3 = DEF

    5 = JKL

    7 = PRS

    9 = WXY

    No digit corresponds to either Q or Z. For these two letters, your application should print a message indicating that they are not used on a telephone.

    The application should have two buttons, one to enter input and one to quit. The screen dialog might look like this:

    Enter a single letter, and I will tell you what the corresponding
digit is on the telephone.

R

The digit 7 corresponds to the letter R on the telephone.

    Here's another example:

    Enter a single letter, and I will tell you what the corresponding
digit is on the telephone.

Q

There is no digit on the telephone that corresponds to Q.

    Your program should print a message indicating that there is no matching digit for any nonalphabetic character the user enters. Also, the program should recognize only uppercase letters. Include the lowercase letters with the invalid characters. Prompt the user with an informative message for the input value, as shown above.

    Use proper indentation, appropriate comments, and meaningful identifiers throughout the program.

  2. People who deal with historical dates use a number called the Julian day to calculate the number of days between two events. The Julian day is the number of days that have elapsed since January 1, 4713 B.C. For example, the Julian day for October 16, 1956 is 2,435,763. There are formulas for computing the Julian Day from a given date and vice versa.

    One very simple formula computes the day of the week from a given Julian day:

    where Mod is the Visual Basic modulus operator. This formula gives a result of 0 for Sunday, 1 for Monday, and so on up to 6 for Saturday. For Julian day 2,435,763, the result is 2 (a Tuesday). Your job is to write a Visual Basic application that requests and prints out the name of the day that corresponds to that number. Use two buttons, Done and Quit. The application continues until the Quit button is pushed.

    Your screen dialog might look like this:

    Enter a Julian day number and press Done. Press Quit to quit.
2451545
Julian day number 2451545 is a Saturday.
2451547
Julian day number 2451547 is a Monday.

  3. You can compute the date for any Easter Sunday from 1982 to 2048 as follows (all variables are of type Integer):

    a is year Mod 19
b is year Mod 4
c is year Mod 7
d is (19 * a + 24) Mod 30
e is (2 * b + 4 * c + 6 * d + 5) Mod 7

    Easter Sunday is March (22 + d + e).[3]

    For example, Easter Sunday in 1985 is April 7.

    Write an application that inputs the year and outputs the date (month and day) of Easter Sunday for that year. Use two buttons.

  4. The algorithm for computing the date of Easter can be extended easily to work with any year from 1900 to 2099. There are four years—1954, 1981, 2049, and 2076—for which the algorithm gives a date that is seven days later than it should be. Modify the application for Exercise 3 to check for these years and subtract 7 from the day of the month. This correction does not cause the month to change. Be sure to change the documentation for the program to reflect its broadened capabilities.

  5. Write a Visual Basic application that calculates and prints the diameter, the circumference, or the area of a circle, given the radius. The application should have three buttons, one for diameter, one for circumference, and one for area. The user should be prompted to enter the radius in floating-point form and press the appropriate button. The output should be labeled appropriately. For example, if the input is 6.75 and the area button is pressed, your program should print something like this:

    The area of a circle with radius 6.75 is 143.14

    Here are the formulas you need:

    where r is the radius. Use 3.14159265 for π.

  6. The factorial of a number n is n * (n - 1) * (n - 2) * ··· * 2 * 1. Stirling's formula approximates the factorial for large values of n:

    where π = 3.14159265 and e = 2.718282.

    Write a Visual Basic program that inputs an integer value (that stores it into a Double variable n), calculates the factorial of n using Stirling's formula, assigns the (rounded) result to a Long integer variable, and then prints the result appropriately labeled.

    Depending on the value of n, you should obtain one of these results: A numerical result.

    • If n equals 0, the factorial is defined to be 1.

    • If n is less than 0, the factorial is undefined.

    • If n is too large, the result exceeds Long.MaxValue

    Because Stirling's formula is used to calculate the factorial of very large numbers, the factorial approaches Long.MaxValue quickly. If the factorial exceeds Long.MaxValue, it causes an arithmetic overflow in the computer, in which case the program continues with a strange-looking integer result, perhaps negative. Before you write the program, then, you first must write a small program that lets you determine, by trial and error, the largest value of n for which you can compute a factorial using Stirling's formula. After you've determined this value, you can write the program using nested Ifs that print different messages depending on the value of n. If n is within the acceptable range for your computer system, output the number and the result with an appropriate message. If n is 0, write the message, "The number is 0. The factorial is 1." If the number is less than 0, write: "The number is less than 0. The factorial is undefined." If the number is greater than the largest value of n for which your computer system can compute a factorial, write: "The number is too large."

    Suggestion: Don't compute Stirling's formula directly. The values of nn and en can be huge, even in floating-point form. Take the natural logarithm of the formula and manipulate it algebraically to work with more reasonable floating-point values. If r is the result of these intermediate calculations, the final result is er. Make use of the Math class methods Log and Exp. These methods compute the natural logarithm and natural exponentiation, respectively.

[3]Notice that this formula can give a date in April.


Case Study Follow-Up Exercises

    Chapter 6

  1. Using the source code from the Calculator program, identify the statements that go with Click events for the Addition button, the Subtraction button, and the Clear button.

  2. Complete the original Calculator program by adding buttons for multiplication and division.

  3. Write a test plan to test the final project and implement the test plan.


Chapter 7: Classes and Methods

Overview

Goals

  • To be able to apply the concepts of abstraction and encapsulation in object-oriented design.

  • To be able to design the public interface for a class.

  • To be able to implement a class responsibility as a method.

  • To be able to design and implement a constructor for a class.

  • To be able to distinguish between immutable and mutable objects.

  • To be able to implement a new class.

  • To be able to determine the lifetime of an object or a class field.

  • To be able to recognize the conditions under which an object is garbage collected.

  • To be able to collect a set of classes together in a namespace.

  • To be able to test classes in a namespace.

Prior to this chapter we have written Visual Basic application classes in which we have occasionally nested other class definitions. These classes have followed specific patterns that enabled us to write them without learning all the rules and options that Visual Basic provides for implementing a class. As we have seen from our use of CRC cards, however, it is common to create new classes that don't follow an existing formula.

In this chapter we consider the principles that result in a well-designed class implementation. Primary among these is the concept of encapsulation. We'll also see how to bundle up one or more classes into a namespace that can be imported by applications.

Up to this point we've also been writing just a few specific methods. It's time to see how to write methods (also called functions) that can implement an arbitrary class responsibility.


7.1 Encapsulation

The dictionary provides several definitions of the word capsule. For example, it can be a sealed gelatin case that holds a dose of medication. Early spacecraft were called capsules because they were sealed containers that carried passengers through space. A capsule protects its contents from outside contaminants or harsh conditions. To encapsulate something is to place it inside a capsule.

What does encapsulation have to do with classes and object-oriented programming? One goal in designing a class is to protect its contents from being damaged by the actions of external code. If the contents of a class can be changed only through a well-defined interface, then it is much easier to use the class and to debug errors in an application.

Encapsulation
Designing a class so that its implementation is protected from the actions of external code except through the formal interface

Why is it important to encapsulate a class? Encapsulation is the basis for abstraction in programming. Recall, for example, that it is abstraction that lets us use Form1 without having to know about the details of its implementation.

Abstraction
The separation of the logical properties of an object from its implementation

We simplify the design of a large program through abstraction: We design classes that can be described in simple terms. There are implementation details of each class that are irrelevant to using it. Those details are hidden by encapsulation. The programmer who implements the class doesn't have to understand how the larger application uses it, and the programmer who uses the class doesn't have to think about how it is implemented.

Even when you are the programmer in both cases, abstraction simplifies your job because it allows you to focus on different parts of the program in isolation from each other. What seems like a huge programming problem at first is much more manageable when you break it into little pieces that you can separate (the divide-and-conquer strategy from Chapter 1).

Separating a class into a logical description (an interface) and an encapsulated implementation has two additional benefits: modifiability and reuse.

Modifiability
The property of an encapsulated class definition that allows the implementation to be changed without having an effect on code that uses it (except in terms of speed or memory space)
Reuse
The ability to import a class into any program without additional modification to either the class or the program; the ability to extend the definition of a class

Encapsulation enables us to modify the implementation of a class after its initial development. Perhaps we are rushing to meet a deadline, so we create a simple but inefficient implementation. Later, we can replace the implementation with one that is more efficient. The modification is undetectable by users of the class with the exception that their programs run faster and/or require less memory.

If we write a class in a manner that exposes implementation details, user code may try to exploit some of those details. If we later change the implementation, the user code would stop working. Figure 7.1 illustrates an encapsulated implementation versus one that is exposed to external code.

Click To expand
Figure 7.1: Encapsulated versus exposed implementation

Encapsulation also allows us to use a class in other applications. An encapsulated class is self-sufficient so that it doesn't depend on declarations in the application. It can thus be imported into different applications without requiring changes to either the class or the application.

As we see in Chapter 8, reuse also means that an encapsulated class can be easily extended to form new related classes. For example, suppose you are working for a utility company developing software to manage their fleet of vehicles. As shown in Figure 7.2, an encapsulated class that describes a vehicle could be used in the applications that schedule its use and keep track of maintenance as well as the tax accounting application that computes its operating cost and depreciation. Each of those applications could add extensions to the vehicle class to suit its particular requirements.

Click To expand
Figure 7.2: Reuse

Reuse is a way to save programming effort and also ensures that objects have the same behavior every place that they are used. Consistent behavior helps us to avoid and detect programming errors.

It is important to understand that encapsulation isn't a Visual Basic language construct. We achieve encapsulation by carefully designing the class interface. Once that's done, we take advantage of Visual Basic features that simplify implementation of an encapsulated class.

Abstraction

How do we design a class with an encapsulated implementation? We begin with the abstraction for the object, as written on our CRC card. That way, we focus on the class interface independently of how we may implement it. Then the implementation is automatically separated from the interface.

We thus begin with a discussion of abstraction in programming, then turn to class interface design, and finally look at implementation. Along the way we'll take a short detour to look at method implementation in Visual Basic.

Data and Control Abstraction Creation of a new class begins when there is a need for a new object type. At the conclusion of the brainstorming and filtering steps in a design, if we find a CRC card that isn't in the .NET framework, we create a new class from the responsibilities on the card. Some responsibilities require us to supply data to the class (for example, constructors), while others provide data (such as Know First Name). Each responsibility has a control component (what it does) and a data component (the information it needs or returns). Encapsulation allows us to create an abstraction of either or both of these components.

Data abstraction is the separation of the external representation of an object's values from their internal representation. For example, the external representation of a date might be integer values for the day and year, and a string that specifies the name of the month. But we might implement the date within the class using a standard value that calendar makers call the Julian day, which is the number of days since January 1, 4713 B.C.

Data abstraction
The separation of the logical representation of an object's range of values from their implementation

The advantage of using the Julian day is that it simplifies arithmetic on dates, such as computing the number of days between dates. All of the complexity of dealing with leap years and the different number of days in the months is captured in formulas that convert between the conventional representation of a date and the Julian day. From the user's perspective, however, the responsibilities of a Date object receive and return a date as two integers and a string.[1]

In many cases, the external representation and the implementation of the values are identical. However, we won't tell that to the user, in case we decide to change the implementation in the future.

Control abstraction is the separation of the logical properties of the actions of a responsibility from their implementation. For example, the documentation for the Date class says that it takes into account all of the special leap-year rules. In the implementation, only the conversion formulas handle those rules, and the other responsibilities merely perform simple arithmetic on the Julian day number.

Control abstraction
The separation of the logical properties of the operations on an object from their implementation

A user can simply imagine that every Date responsibility is separately computing leap years. But control abstraction lets us program a more efficient implementation and hide that complexity from the user.

Object State Any object that stores information is said to have state. The object's state is the current set of values that it contains. Some of those values may be represented in the data abstraction and some may be hidden. An object's state can also contribute to its control abstraction. For example, an object that simulates a traffic light may only return its current color, but internally it keeps track of the time so that it knows when to change color. The state of an object, its data and control abstractions, and its collaborations combine to precisely determine its behavior.

State
The current values contained within an object

An object is instantiated with an initial state. If any of its methods can subsequently change its state, then the object is said to be mutable. A control, such as a text box, is an example of a mutable object; there are many properties of each control we can change that will change the state of the control. If an object doesn't have any methods that can change its state, it is immutable. A String is an immutable object; we can only retrieve its contents.

Mutable
Describing an object whose state can be changed after it is created
Immutable
Describing an object whose state cannot be changed once it is created
Start Sidebar
Theoretical Foundations
Categories of Instance Responsibilities

Class instance responsibilities generally fall into three categories: constructors, transformers, and observers.

Constructor
An operation that creates a new instance of a class
Transformer
An operation that changes the internal state of an object
Observer
An operation that allows us to observe the state of an object without changing it

A constructor is an operation that creates a new instance of a class. Operators that modify the state of an object are transformers. For example, an operation that changes the year of a Date object is a transformer. Observers are operations that allow us to observe the state of an object. The knowFirstName method of a Name class is an example of an observer.

Some operations are combinations of observers and constructors. An operation that takes a Date object and an integer value and returns a new Date object that is the original data plus that number of days is an example of an observer (of the original Date object) and a constructor (of the new Date object). This particular case is an example of a copy constructor.

Copy constructor
An operation that creates a new instance of a class by copying an existing instance, possibly altering some or all of its state in the process

In addition to the three basic categories of responsibility, there is a fourth category that is less common: iterators.

Iterator
An operation that allows us to process-one at a time-all the components in an object

In later chapters we examine classes that are made up of multiple values of all the same type. An iterator allows us to observe each of these components one at a time. For example, given a class representing a list of names, an iterator would enable us to go through the list, observing or transforming each of the names one by one.

End Sidebar

For example, examine the following class shell that represents a traffic light.

Public Class TrafficLight
    ' Property variables
    Public Property currentColor() As String
        Get
        End Get
        Set
        End Set
    End Property
    Public Property timeRemaining() As Integer
        Get
        End Get
        Set
        End Set
    End Property
    ' Constructor
    Public Sub New()

        MyBase.New()
    End Sub
    ' Observer methods
    Public Sub knowCurrentColor() As String
    End Sub
    Public Sub knowTimeRemaining() As Integer
    End Sub
    ' Transformers
    Public Sub changeColor()
    End Sub
    Public Sub decrementTimeRemaining()
    End Sub
End Class

The values stored in currentColor and timeRemaining are the state of the object. We know that any object of class TrafficLight is a mutable object because transformer methods are defined.

[1]The .NET framework includes a Date class. However, the familiar properties of dates make them a natural example to use in explaining the concepts of class interface design. So we ignore the existence of the framework class in the discussion that follows, as if we must design our own Date class.


7.2 Class Interface Design

Let's begin our discussion of designing the class interface by looking at the CRC card for a new class, Name, with three fields, First, Middle, and Last.

Click To expand

As you can see, a Name has eight responsibilities. As we discussed in Chapter 5, we have extended the basic CRC card to include notes about the data that a responsibility needs and that it returns. This additional information arises naturally in the scenario discussions as we play out the collaborations, and we simply write it on the CRC card to remember it.

From the preceding discussion we can classify the eight responsibilities as follows:

Responsibility | Classification
Create itself | Constructor
Know its first name | Observer
Know its middle name | Observer
Know its last name | Observer
Name in firstMidLast form | Observer
Name in lastFirstMid form | Observer
Is this name equal to (anotherName?) | Observer
Compare this name to (anotherName?) | Observer

We know that the first responsibility (Create itself) must be a constructor that uses New() to instantiate itself. The remaining responsibilities all return information about a name, so they are observers. There are no transformers among the responsibilities. Thus an instance of this class is an immutable object; once it is created it doesn't change. None of the responsibilities are iterators. This information can be added to the CRC card for future reference.

Now let's look at how we turn this CRC card into a design for a class. Note that at this stage, we begin to convert the responsibilities from phrases or sentences to names that represent the responsibility.

Public Interface Design

There are two ways that the data abstraction of an object can be provided to the user, the fields that it makes publicly accessible and the parameters of its methods. We briefly consider Public variable values, and then look at methods and parameters.

Public Field Values One way to implement the Name class would be to keep each part of the name in a String declared as a variable:

Dim first As String
Dim middle As String
Dim last As String

The Know first, Know middle, and Know last responsibilities return these values, so we might consider simply declaring the corresponding variables Public and letting the user access them directly. However, this defeats the encapsulation of the class by revealing its internal representation to the user.

We can achieve encapsulation by making each of these responsibilities a method. If we later change the internal representation, the methods can be rewritten to convert the new internal representation into the existing external form.

Would we ever want to declare a Public field in a class? Yes. Sometimes, it's useful to provide constants that a collaborator can pass to responsibilities. For example, the names of the primary colors for a Spectrum object could be represented by Public constants.

The constant RED could be an Integer with a value of 1 or a String with a value of "RED". The collaborator doesn't know how the class represents the constant. It merely passes the class-supplied constant as a parameter to a method, as in this example:

colorObject.mixWith(Spectrum.RED)

In Visual Basic, such constants are declared Static; that is, they belong to the class rather than to an instance. We want every object of a given class to use the same set of constants, so they should be kept centrally located in the class.

The Name class doesn't have any such constants on its CRC card. Normally, they would be identified during the scenario phase, and it would be noted that responsibilities receive one value from among a small set. Here's part of the CRC card for Spectrum that illustrates the need for constants.

Click To expand

Responsibilities as Methods Each responsibility on the CRC card can be implemented by a method in the class. If we know the information that is needed and returned by each one, then the design is straightforward.

An observer naturally returns a value, so we implement it as a Visual Basic value-returning method. Constructors have a special syntax in Visual Basic that we review later in this chapter. A transformer is typically a method that doesn't return a value. Iterators may either not return a value or be value-returning, but we won't consider them until we work with arrays in Chapter 12.

If a responsibility implicitly refers to an object, then it should be implemented as an instance method. Constructors, observers, transformers, and iterators are always instance methods. They implicitly take their associated object as a parameter and have access to its fields. In addition they can receive values through the parameter list, as noted on the CRC card.

Class methods are used to implement responsibilities that are not associated with a particular object, such as the Math.Abs method. Class methods may also affect all the instances of a class-for example, setting the maximum score for all TestScore objects so that they can be checked for errors. None of the responsibilities of the Name class are appropriate for a class method implementation.

Let's add this information to the CRC card for the class. We abbreviate value-returning as V-R on the card.

Click To expand


7.3 Internal Data Representation

The first implementation step for a class is to decide on the internal representation of data. Then we can use the means-ends analysis technique from Chapter 1 to design the algorithms for translating between the abstract and internal representations. These algorithms form the basis for the constructor and observer methods, and parts of some transformer and iterator methods.

As we saw with our example of a Date object, the internal representation (Julian day) and the abstract representation (month, day, year) can be quite different. In other cases, such as class Name, the internal representation may be exactly the same as the abstract form of the data. We choose the internal form to be useful for the programmer rather than the user.

Our goal in selecting an internal representation should be to simplify our task and to make the object efficient in terms of storage space and execution time. These goals are sometimes in conflict, and we must balance simplicity against efficiency.

Two problem-solving strategies that are valuable in designing the internal data representation are looking for things that are familiar and solving by analogy. In the case of the Date class, you can go to the library and research calendars. For the Name class, you already know how to write down a name.

It's rare that you have to invent an entirely new data representation. Most programs are written to solve problems that people have dealt with in the past. Consider who would normally use such data, and consult those people or look in texts that they would use (Figure 7.3). For example, astronomers use dates in computing the positions of planets over many years. You can find the formulas for computing the Julian day in some astronomy texts.

Click To expand
Figure 7.3: Who would use similar data?

As another example, a geography professor gave an assignment to draw a map showing nearby airports with their elevations and the lengths of their runways. Most students spent hours poring through almanacs and topographic maps to gather the data. Even so, their maps were inaccurate. One student, however, got a perfect grade because she stopped to consider who would use such data. She went to a pilot supply shop and bought a precise map of nearby airports.

While there, she noticed a book that lists information for airports using an especially efficient representation. If she ever had to develop a computerized version of the map, she would start with that representation.

From the preceding discussion it should be clear that we can't give you a set of rules that lead you to an internal data representation for a class. Each situation you encounter is different. Be prepared to give this part of your design some careful thought, to go to a library and do some research, to consult with other people, and to trade off issues of efficiency and complexity.

In some cases you may discover that there is no one representation that is always best. Or there may not be enough information to choose among several options. In those cases, you simply have to pick a representation and use it. In the end, it may not turn out to be the best choice. But remember that the beauty of encapsulation is that you can go back later and change to a better representation!

Data Lifetime

There are three categories of data that we use in implementing a class, instance data, class data, and local data. We tend to focus more on the instance data because it is the internal representation of an object. However, the other two categories are also important. Class data provides information common to all objects of a class. Local data is specific to a given call of a method.

Instance data
Data that is associated with a specific object
Class data
Data that is associated with a class and accessible by all objects of that class
Local data
Data that is associated with a specific call to a method

Storage space for data in a Visual Basic application is automatically supplied by the CLR for as long as the data is needed. Depending on the category of the data, it is needed at different stages in the execution of the application. The period during which storage space is assigned to data is called its lifetime.

Lifetime
For a variable, constant, or object, the portion of an application's execution time during which it is assigned storage space in the computer's memory

As you implement the internal representation for a class, you need to choose one of the categories for each variable, constant, or object, and that choice largely depends upon the desired lifetime of the data. Let's look at each of the three categories in turn.

Instance Data Each class instance contains data that distinguishes it from other instances. For example, a Name object contains three strings that hold the first, middle, and last names.

We've said many times that the CLR instantiates an object when we use New. To instantiate an object, the CLR must provide memory space for it. The CLR manages an area of memory called the free pool or heap for storing objects.

Free pool (heap)
An area of memory, managed by the CLR, that is used to provide storage space for objects

When an object is instantiated, we say that New allocates memory space from the free pool to the object. An object is destroyed when the CLR detects that no variable refers to it. If no variable contains the address of the object, there is no longer any way to access its contents.

Allocate
To assign memory space at run time for use by an object
Garbage
The set of currently unreachable objects
Garbage collection
The process of finding all unreachable objects and destroying them by deallocating their storage space
Deallocate
To return the storage space for an object to the pool of free memory so that it can be reallocated to new objects

When an object is unreachable, the CLR marks it as garbage. Periodically, the CLR performs an operation known as garbage collection, in which it finds all of the unreachable objects and deallocates their storage space, making it once again available in the free pool for the creation of new objects.[2]

This approach of creating and destroying objects at different points in the application by allocating and deallocating space in the free pool is called dynamic memory management. Without it, the computer would be much more likely to run out of storage space for data.

Dynamic memory management
The allocation and deallocation of storage space as needed while an application is executing

The point of the preceding discussion is to impress upon you that the lifetime of instance data is shorter than the execution time of the application and is the same as the lifetime of its object. It is thus a way of storing object-specific information between method calls.

Instance data thus records the object's state for as long as the object exists. We implement a field for an instance value by omitting the Static modifier in its declaration. It is often helpful to gather together the private variables into one section of the class declaration for easy reference-for example,

' Private instance variables
Private first As String
Private middle As String
Private last As String

To identify the parts of the internal representation that are instance data, answer this question:

Which parts of the internal representation does each object of this class need to store independently for its lifetime?

Class Data Fields declared as Static belong to the class, rather than to a specific instance. Space for class data is allocated at the start of the application and exists until it is finished. That is, its lifetime is the whole time that the application is running. It provides a way for us to record information about a class that survives across the creation and destruction of objects.

A common use for class data is to provide constants that are available to all instances of the class. For example, a Measurement class might declare constants for conversion between metric and English units.

Public Class Measurement
    ' Class Constants
    Private Const INCHES_TO_CENTIMETERS As Double = 2.54
    Private Const METERS_TO_YARDS = 1.1
    ...

As you can see from these examples, class data has many uses. It is a way of storing information that is separate from any particular object. In implementing the class, ask yourself:

What information needs to be kept that is independent of any instance or that must remain throughout the execution of the application?

Local Data Local data exists only within a method call. As we have seen with Form1 and with event methods, we can declare local constants and variables within the body of a method that are accessible only to statements within the method.

Public Sub actionPerformed()
    ' Local variable declarations
    Dim secondOperand As Double
    Dim whichButton As String
    ...

The CLR allocates space for this data when the method is called and deallocates it as soon as the method returns. Local data has the most limited lifetime of the three categories.

We use local data whenever we need to store information that is specific to a particular method. For example, computing the Julian day number requires intermediate variables in which to keep these values. The values aren't needed by any other methods, so they do not have to be kept in private variables.

As you develop the algorithm for each method, keep a list of the values that are needed. Those that are not parameters, instance values, or class values are probably local. To be certain, for each value ask yourself:

Is this information needed anywhere outside of the method?

If the answer is no, then you know the value is local. The following table summarizes the lifetime of each category of data.

Category | Lifetime
Class | Execution of application program
Instance | From creation to destruction of its object
Local | From method call until return

Internal Representation Example

Our Name class needs to represent a first, middle, and last name for each of its instances. Clearly, these are private variables because they are distinct values for each object. What other information does Name need to carry out its responsibilities?

The lastFirstMid responsibility must provide punctuation to separate the names. We could write this punctuation directly in the necessary string expression, or we could declare a named constant with the punctuation string. The constant could be associated either with the instance or with the class. Because the same punctuation characters are used in every instance, let's make it a class constant.

Here is the Visual Basic code that declares the internal representation for Name.

Private Const PUNCT As String = ",  "
Private first As String
Private middle As String
Private last As String

When the following expression is executed:

New Name("Martha", "Bette", "Casey")

The CLR allocates space to the three string variables in the new Name object. For each additional Name object that we instantiate, the CLR allocates space for three more strings. But there is just one copy of PUNCT, and it exists for the lifetime of the application.

[2]The term garbage collection dates back to programming languages of the 1960s. If the technique had been invented more recently, it would probably be called recyclable collection.


7.4 Class Syntax

The syntax for writing a new class is exactly like that of our Form1 class. Here we show a syntax template for a class. It consists of a heading, followed by a block containing class declarations:

Click To expand

The following template for a ClassDeclaration shows that it can be either a field declaration (such as a variable, constant, or object) or a method declaration:

Click To expand

Here's an example class declaration.

Public Class Name
   Const PUNCT As String = ",  "   ' Class constant
   Private first As String
   Private middle As String
   Private last As String
   ' Declare methods that implement responsibilities of Name
End Class

We already know how to declare fields in a class from our experience with writing fields in the Form1 class. However, we have not examined the process of declaring methods. The mechanics of writing a method are quite simple: We just write its heading and then follow it with the block of code that does the work. In the next section we see how to write a method declaration, and we focus extensively on its heading and especially its parameters.


7.5 Declaring Methods

Up to now, we've restricted our method declarations to a small set of recipes. We have seen declarations for the Form1 class, and various event handler declarations, all of which have their headings predefined for us. In this section we look at how to write headings for arbitrary methods. Here we repeat the syntax templates from Chapter 2 that show the form of a method declaration.

Click To expand
Click To expand

In Chapter 2 we left Heading undefined. Now we can provide the syntax template that defines it.

Click To expand

From this template we see that a method heading can optionally begin with modifiers such as Public, Private, Overloads, and Overridable. Following the modifiers is the name of the type of method being declared. A method that returns a value is called a Function, while a method that does not return a value is called a Sub (for sub-procedure). Next in the heading is the name that we are giving the method. Then, enclosed in parentheses is an optional list of the parameters accepted by the method, with commas separating them. Each parameter declaration consists of an identifier and a type (or class) name. Notice that the parentheses are required. Following the parameter list is the As keyword and the result type of the method if the method is a Function. Here are some examples of method headings.

Sub doNothing()                                   ' The simplest heading
Public Sub skipValue()                            ' Example with a modifier
Function trimax(num1 As Integer, _                ' Shows multiple parameters
                num2 As Integer, _
                num3 As Integer) As Integer
Private Function Square(number As Integer) _      ' One parameter,
                        As Integer                ' returns Integer
Public Function cube(number As Double)            ' A public class method
As Double

The first example is the simplest form of instance method heading, and illustrates that the parentheses are required even when there are no parameters. When we omit any access modifiers from the heading, it is said to have namespace accessibility. We explain namespace accessibility later in this chapter when we look at how to create namespaces. The second example shows a similar heading with the addition of the Public access modifier. The third example shows a heading for a value-returning instance method that has three parameters, all of type Integer.

The fourth case shows a heading for a Private value-returning class method that takes one parameter of type Integer. A Private class method can only be called from other methods within the class (either class or instance methods). The fifth example is a Public value-returning class method that can be called by users of the class.

The parameter list in the method heading provides one mechanism by which the method call and the method exchange information. Identifiers declared in the method heading are called parameters and expressions in the method call are arguments.

Parameter
An identifier declared between the parentheses in a method heading
Argument
An expression listed in the call to a method

Arguments are matched to parameters by their relative position within the two lists. Here is an example that illustrates the matching between the lists in a method heading and a call.

Click To expand

When the CLR executes a call to a method, it first copies the values of the arguments into their corresponding parameters. We refer to this step as parameter passing. Then it transfers control to the first statement in the method's block. As soon as the last statement in the block has been executed, the values in the parameters are destroyed and control returns to the point following the call.

Parameter passing
The transfer of data between the arguments and parameters in a method call

The parameters are just like local variables declared within the method's block. The only difference is that they are automatically given initial values when the method is called, because the values of the arguments are copied into the parameters. If the method is called again with different arguments, then the method starts executing with these new values in its parameters.

Parameters

When a method is executed, it uses the values of the arguments passed to it in the call. But because of the distinction between simple and reference types, the effect of passing different types of arguments varies.

With simple types, such as Integer, Double, and Boolean, the parameter receives a copy of the value of the argument. The method can use and change the value in the parameter. Keep in mind that the argument can be any expression that computes a value of the appropriate type. Frequently, however, our arguments are just individual variables.

When we pass a variable as an argument to a parameter, it is easy to think that the two identifiers are connected in some way, as if the parameter becomes a synonym for the argument. But their only connection is at the moment when the value of the argument is copied to the parameter. Otherwise, they are independent of each other. That is, operations on the parameter do not affect the argument.

When the method returns, whatever value is in a parameter is discarded. It is not copied back into a variable in the argument. Thus, values of simple types may be passed into methods, but changes to those values aren't returned. Figure 7.4 illustrates the passing of simple types as parameters.

Click To expand
Figure 7.4: Passing a simple type as a parameter

With reference types such as a class object, a variable contains the address of where an object's fields are stored in memory. This address is the value copied from the argument to the parameter. There is only one copy of the object's fields, which is used by both the calling code and the method. If the method changes the values in those locations, then the calling code sees those changes when the method returns.

Therefore, you must be careful when using reference type parameters because changes made to their fields affect the value of the argument. Figure 7.5 illustrates the difference between passing simple and reference types to a method.

Click To expand
Figure 7.5: Passing simple and reference types

Each argument must have the same type or class as the parameter in the same position. You sometimes see Visual Basic code that seems to violate this rule, for example, in passing an Integer argument to a Single parameter. But in this case, Visual Basic automatically inserts a widening conversion that forces the argument to have the same type as the parameter. Proper programming style would use an explicit conversion of the argument. There must also be the same number of arguments in a call as there are parameters in the method heading, or a compiler error message results.

When a method has several parameters of the same type, be careful that the arguments are in the correct order. For example, given the following heading for a method that computes the amount of water resulting from snow melting during one day at a specified temperature.

Public Function meltEstimate(temperature As Integer, _
                             snowDepth As Integer)

the following call is syntactically correct, but the arguments are clearly reversed and produce a meaningless result.

runOff = meltEstimate(237, -12)

This example also demonstrates that we can pass a literal constant as an argument.

Changes to the parameter do not affect the literal constant argument when a simple type is being used. But what about literals of type String, which is a reference type? Wouldn't a change to the parameter change the argument? For many reference types the answer would be yes, but String is an immutable class.

There is no way to change a string object. We can only assign a new value to a String variable. Because each string value occupies a different place in memory, the effect of assigning a new value to a String variable is to store a different address in it. A parameter of type String is initially given a copy of the address of the string literal, and when we assign it a new value, the address is replaced by the address of the new value. The argument is left unchanged. Figure 7.6 illustrates what happens when we change the value of a parameter of type String.

Click To expand
Figure 7.6: The effect of assigning a new value to a reference type parameter

In contrast to the simple types and immutable classes like String, there are classes that provide methods for directly changing instance fields. For example, Visual Basic allows us to write a method that takes a text box as a parameter, and changes the Text property of the text box. The method's parameter receives the address where the text box is stored. Calling the method with the parameter directly changes the same text box that the argument refers to.

Figure 7.7 shows this process, and you should carefully compare it to Figure 7.6 to be sure that you understand the difference. Assigning a new value (a different object) to a reference type parameter does not change the object to which the argument refers. But changes to the fields of the object referred to by the parameter are made to the argument object.

Click To expand
Figure 7.7: The effects of changing the field of a reference type parameter

We recommend that you avoid modifying the contents of a reference parameter within a method. Our example of passing a text box to a method that changes its Text property is poor programming practice. We used this example only to show that a reference parameter could be changed. However, reference parameters should not be changed. Although Visual Basic permits us to write a method that changes its parameter, when another programmer reads a call statement, his or her expectation is that the object referred to by the argument is unchanged.

Because it is poor practice to do so, Visual Basic programmers have adapted the convention that a reference value passed as an argument to a method should not be changed. In Visual Basic, arguments should be used only for sending data into a method. A method that changes its arguments is said to have a side effect. That is, it makes a change to values outside of itself, in a manner that isn't expected.

Side effect
Any change made by a method to values that are declared outside of its block that isn't explicit in its interface

A Parameter-Passing Analogy

To help you remember the behavior of simple and reference parameters, we offer another analogy. Suppose you have in your hand a slip of paper and you are talking to someone who has another slip of paper. You are like the call and the other person is like the method. You read to her what's written on your paper and she writes it down. If your slip contains a simple type, such as a number representing the current temperature, she can use her copy of that number to perform some task. She could also replace the number with a different one. When she is done, she throws away her paper and your paper remains unchanged (see Figure 7.8).

Click To expand
Figure 7.8

Now suppose your slip of paper contains the address of a house (a reference type). The other person can use a copy of the address to go look at the house. If the person changes the address on the slip of paper (assigns a new value to the reference type parameter), then that person can go to a different house, but she would no longer have the address of the first house. When done, the person throws away her paper. Your slip of paper still has the original address and so you can go and look at the house, and you find it unchanged.

Lastly, suppose that you again have an address on your slip of paper, but this time the person goes to the house and, instead of just looking at it, she goes inside and rearranges the furniture. When she is done, she throws away her paper, but your paper still has the address of the house. When you go to the house, you find that its contents have changed.

Note that in every case, your slip of paper remained unchanged. In Visual Basic, the argument is never affected by a method call. But in the last situation, the method made changes to the object at the address it was given. So even though the address in the argument was untouched, the object it refers to has been altered. Whenever you are writing a method that changes a reference parameter, and you are unsure about how the operation is going to behave, stop and ask yourself, "Am I going inside the house, or am I going to a different address?"

Implementing a Responsibility as a Method

Once you have the design for the responsibilities listed on the CRC card, you need to turn each into Visual Basic code that implements it. Observers return information, transformers change information, and observers, constructors, and transformers may all receive information. We have already seen that parameters enable methods to receive information. How do we return information, and how do we change values? The mechanisms vary somewhat depending on whether the information is a simple type or a reference type. Figure 7.9 shows the mechanisms that we need and the ones we now know how to achieve.

Click To expand
Figure 7.9: Method mechanisms for implementing a responsibility

As you might expect, one way of returning information from a method is to write it as a value-returning method. Recall that a value-returning method is called from within an expression and returns a value that takes its place in further computations. In our discussion of the syntax for method headings we saw that a value-returning method includes the type or class of its return value at the end of the heading. For example, if we want to return a String from the knowFirstName instance method, we write the following heading (our CRC card indicates that it has no parameters).

Public Function knowFirstName() As String

The heading tells Visual Basic that the method returns a value. The class name allows Visual Basic to determine whether the value that it returns is compatible with the expression that contains the call. For example, we can't call knowFirstName in an expression that tries to assign its return value to a Boolean variable. But how do we actually return a value? We use a Return statement. Here is the syntax template for this new statement.

Click To expand

We write the Return statement at the point in the method where we are ready to return control to the calling expression. The expression in the Return statement computes the value that is returned. The result of the expression must be compatible with the class or type specified in the method's heading.

Note that a value-returning method enables us to return just one value. What if we need to return multiple values? Then we bind them together into an object that is returned. Our scenario discussions should reveal when a responsibility must return an object rather than a simple value, so this should never come as a surprise. Figure 7.10 shows the interface implementation mechanisms we have seen.

Click To expand
Figure 7.10: Method mechanisms for implementing a responsibility

Now we consider mechanisms for changing values. Changing simple types can't be done directly in Visual Basic. We use a value-returning method and simply replace the value that was passed to it. For example,

value = Math.Abs(value)

As we saw earlier, changing a reference type parameter within a method is poor practice. Instead of passing the object we wish to change as a parameter, we define an instance method that has direct access to the fields of the object. You might be tempted to think that the instance fields are like extra parameters to the method, but there is a big difference: They are not copied when the method is called. Remember that they are declared in the same block as the method.

Class methods also have access to fields, but as you might expect, they can access only the class fields. Notice that when a method changes a class field, it is not changing a specific instance-it is changing a value accessible to all instances of that class.

Now we have seen the basic mechanisms that Visual Basic provides for implementing a method interface, and we can fill in the blanks from Figure 7.10 as shown in Figure 7.11.

Click To expand
Figure 7.11: Method mechanisms for implementing a responsibility

Here is the implementation of the knowFirstName observer, within the declaration of class Name. We examine the implementation of the remaining observer responsibilities of Name in the case study at the end of the chapter.

Public Class Name
   Const PUNCT As String = ", "  ' Class constant
   ' Private variables
   Private first As String
   Private middle As String
   Private last As String
   ' Declare methods that implement responsibilities of Name
   Public Function knowFirstName() As String
      Return first
   End Function
End Class

There is one last bit of Visual Basic that we need for implementing the responsibilities of Name. We must still see how to write a constructor.

Constructors

Now that we know how to write a method, writing a constructor is easy. Syntactically, a constructor heading looks just like other method headings except that its name is New, and it doesn't have a Return type, which means it is defined as a Sub. Just as with a normal method, the heading is followed by a statement sequence.

Click To expand

For example, we can write the constructor for the Name class as follows:

Public Sub New(firstName As String, middleName As String, lastName As String)
   first = firstName
   middle = middleName
   last = lastName
End Sub

The constructor might be invoked with a statement such as

someName = New Name("Martha", "Bette", "Casey")

A constructor is a special kind of method that is called when New instantiates the object. Like an instance method, it has access to all of the instance fields of the object. But because the object has not yet been given a name, its call can't be written in the usual manner with an object name and a period preceding the method name.

You might think that constructor New returns a value of class Name, but that's the job of the New operator. When you use New (the operator), it looks at the type of the constructor and creates an empty object of that type. It then calls the constructor, which has access to its fields, and thus can give them initial values. When the constructor returns, New returns the initialized object for use in the expression. One way to think of a constructor invocation is that New takes the place of the object name and the period until the object value is given a name (by assigning it to an object identifier). In our example, the expression assigns the initialized object value to an identifier of class Name called someName.

Because a constructor is called when an object is instantiated, its primary function is to initialize fields within the object. Some of these values may be supplied through parameters whereas others may be constants (called default values). In some cases, a constructor may perform more complex work, such as calculating a value for a field from several parameters, accessing a file for its initial values, and so on. The majority of constructors that we write in this text, however, simply initialize fields from parameters and constants.


7.6 Namespaces

As we noted previously, Visual Basic lets us group related classes together into a unit called a namespace. Namespaces allow the programmer to create hierarchical groups of classes that are named in a consistent manner. Namespaces can be compiled separately and imported into our programs.

Namespaces provide means for implementing encapsulation because they allow us to distribute our class files as DLL files. A DLL (dynamic link library) file is a Visual Basic program that is compiled into Bytecode and can then be linked into a Visual Basic program at both design time and compile time. The unreadable nature of a DLL file prevents users from seeing the implementation details.

Namespace Syntax

The syntax for a namespace is extremely simple. We've been writing our applications as unnamed namespaces all along, so all we have to do is to specify the namespace name at the start of the application. The first line of a namespace is the keyword Namespace followed by an identifier.

Namespace someName

After this we can write our Imports declarations and then one or more declarations of classes. Namespaces are closed with the End keyword.

End Namespace

The syntax template for a namespace is shown here.

Click To expand

As you can see from the syntax diagram, we can write a series of Class declarations following the Imports declarations. Those classes are members of the namespace. A program that imports a namespace has access to all the classes declared in the namespace.

Classes that are imported into the namespace can be used by any of the classes declared in the namespace. From the perspective of the imported classes, the declared classes are user code and thus can only access their Public members. Note that imported classes are not part of the namespace.

Even though it is common to place several classes within a single namespace, it is also possible to spread a namespace definition out among multiple Visual Basic programs. We discuss how to do this in the next section.

Namespaces in Multiple DLLs

Namespaces are used as organizational units for storing one or more class definitions. Often, a namespace will consist of just a single file that is compiled to a DLL file. However, it is possible to split a namespace definition among multiple files.

Using multiple files provides us with more flexibility in developing the classes of a namespace. Team programming projects would be very cumbersome if Visual Basic made multiple programmers share a single namespace file.

We split a namespace among multiple files simply by placing its members into separate files that contain the same namespace name. For example, we can create one file containing the following code (the ... following the class name represents the code for each class):

Namespace someName
Public Class One ...
Public Class Two ...

and a second file containing:

Namespace someName
Public Class Three ...
Public Class Four ...

with the result that the namespace someName contains four classes, all of which have access to each other's nonprivate members. All of the classes are available to be imported by user code.

Many programmers simply place every class into its own file. Others gather the nonpublic classes into one file, separate from the public classes. How you organize your namespaces is up to you, but you should be consistent to make it easy to find the members of the namespace among all of its files.

Splitting a namespace among multiple files has one other benefit. Each compilation unit can have its own set of Imports declarations. Thus, if the classes in a namespace need to use different sets of imported classes, you can place them in separate compilation units, each with just the Imports declarations that are required.

Resolving Class Names in a Namespace

In Visual Basic, when you import a namespace with one or more class definitions into an application, you must reference both the namespace and the class name when you declare a class object and when you initialize a class object. For example, if the namespace is named someName and the class is named One, you can create an object of class One with the following line:

Dim oneObject As someName.One

The name someName.One is called the fully qualified name for the object and the dot operator between the namespace name and the class name is used to signify that the class is found within the namespace.

Start Sidebar
Problem-Solving Case Study
Implementing the Name Class

Earlier in the chapter we showed the CRC card design for a class called Name. Here we complete its implementation. Here is the CRC card for the class.

Click To expand

We need to develop a method algorithm for each of the responsibilities. We've already done this for the first two responsibilities, but here we want to take a moment to revisit our choice of internal representation.

Internal Representation

Because we already have the interface design, we can skip ahead to specifying the internal representation for the class, after which we apply our algorithmic problem-solving techniques to each of the methods.

Our initial approach was to use three separate strings to represent the parts of the name. But we could conserve storage space by combining all of the parts of a name into one String variable. Then the CLR would have to store just one address instead of an address for the start of each string that holds a part of a name.

Along with the single string, we would use a set of Integer variables to record the character position where the middle and last parts of the name begin in the string. Retrieving a part of a name is then a matter of calling the Substring method using the Integer variables as parameters. For example, if the start of the middle name is stored in the string at a position recorded by the Integer variable midStart and is followed by the last name as position lastStart, then we would retrieve the middle name with the following call:

middleName = wholeName.Substring(midStart, (lastStart - midStart) -1)

This approach sounds attractive, but we've actually started down a dead-end path to illustrate a point. This representation has some merit, but it also has some disadvantages. Before you read the next paragraph, stop and think about what the disadvantages might be. Then read on and see if you've spotted them all.

The most obvious disadvantage is that we are making the computer perform extra operations to retrieve the parts of a name. We have traded processing speed for some saving in memory space. Secondly, the more complex the implementation, the more likely it is to have errors. And lastly, we may not really be saving any memory space. Let's consider this last point more closely.

Each part of the name (except the first name, which starts at zero) has a corresponding Integer variable, which in many systems takes up the same space as the address of a String object. So, as Figure 7.12 shows, we still use the same amount of space! Have we gone to all this trouble for nothing? Not quite.

Click To expand
Figure 7.12: Two possible implementations of a name object

In Visual Basic, a string takes up more space than an Integer, so we do save some space. And Visual Basic also allocates more memory for a string than is required to hold the exact number of characters. Combining all the strings into one saves that wasted space.

By storing all the parts of the name in one string, we have increased the processing time and the likelihood of errors in the code. The amount of space we save may be significant if we are storing millions of names, but for a small number it is a tiny portion of the computer's memory.

The point we set out to make is that sometimes the simplest implementation is the best. Although it is good to explore other possibilities, be sure to weigh the benefits against the costs, keeping in mind how the object may be used.

Responsibility Algorithms

We've chosen the simpler implementation of the Name class with a separate string for each part of the name. The strings are private variables. With this representation the algorithm for the constructor is trivial. We just assign the parameters to the corresponding private variables.

Here is the algorithm for the constructor (which we coded earlier).

...........................................................................
Public Sub New(firstName As String, middleName As String, lastName As String)

 Set first to firstName
 Set middle to middleName
 Set last to lastName

This implementation does not violate the encapsulation because the variables first, middle, and last are private and are not directly accessed by user code.

The algorithms for the basic observers also are simple. They just return the corresponding private variable. Here is the algorithm for the observer that returns the first name. The other basic observers are equally simple.

...........................................................................
Public Function knowFirstName() As String

 Return first

...........................................................................
Public Function knowMiddleName() As String

 Return middle

...........................................................................
Public Function knowLastName() As String

 Return last

The preceding algorithms illustrate a particular aspect of object-oriented programming. Many classes contain large numbers of trivial methods whose purpose is simply to ensure encapsulation of instances. Once an interface has been designed, it is possible to code much of its implementation with a minimum of algorithmic problem solving. There may then be only a few methods left that require additional thought.

The next two responsibilities on the CRC card return the name in different formats. These algorithms merely have to concatenate the parts of the name in different orders. In the case of lastFirstMid, we use the constant called PUNCT, which we defined earlier.

...........................................................................
Public Function firstMidLast() As String

Return first & " " & middle & " " & last

...........................................................................
Public Function lastFirstMid() As String

  Return last & PUNCT & first & "" & middle

The next responsibility on the CRC card is Equal. Objects of type String have an Equal method, so we can use this to compare all of the parts of a Name object to another name that's supplied as a parameter. Here's the algorithm for Equal.

...........................................................................
Public Function Equal(Name otherName) As Boolean

Return
   first.Equals(otherName.first) and
   middle.Equals(otherName.middle) and
   last.Equals(otherName.last)

The last responsibility of Name is compareTo. We can use the compareTo method of class String to compare last names. If the last names are equal, we compare first names. If the first names are equal, we compare middle names. Only if all three are equal can we say that the names are equal. Is that all we have to do? Not quite. As we've seen before, names should be converted to all upper- or lowercase before they are compared.

...........................................................................
Public Function compareTo(Name otherName) As Integer

  Set result to last.toUpper.compareTo(otherName.last.toUpper)
  If result is not zero Then
      Return result
  Else
      Set result to
          first.toUpper.compareTo(otherName.first.toUpper)
      If result is not zero Then
          Return result
      Else
          Return
             middle.ToUpper.compareTo(otherName.middle.ToUpper)

Notice that in the preceding two algorithms we directly access the fields of otherName rather than use its observer methods to retrieve their values. Because these are instances of the same class, their methods are allowed to access each other's fields. The accesses are all occurring inside of the capsule, so there is no violation of the encapsulation.

Our design is now complete and can be coded in Visual Basic. Here is the complete code for the Name namespace. (From this case study on we omit the list of variables in order to save space.)

Namespace

Namespace Name
    Public Class Name
        ' This class defines a name consisting of three parts

        ' Punctuation for formatting
        Private Const PUNCT As String = ", "   ' Class constant

        ' Private variables
        Private first As String
        Private middle As String
        Private last As String

        ' Constructors
        Public Sub new(ByVal firstName As String, ByVal _
                       middleName As String, ByVal _
                       lastName As String)
            ' Initializes a Name object with first, middle, and
            ' last name
            first = firstName
            middle = middleName
            last = lastName
        End Sub

        ' Basic observers that return the value of each field

        Public Function knowFirstName() As String
            Return first
        End Function

        Public Function knowMiddleName() As String
            Return middle
        End Function

        Public Function knowLastName() As String
            Return last
        End Function

        ' Additional observers

        Public Function firstMidLast() As String
            Return first & " " & middle & " " & last
        End Function

        Public Function lastFirstMid() As String
            Return last & PUNCT & first & " " & middle
        End Function

        Public Function Equal(ByVal othername As Name) As Boolean
            Return first.Equals(othername.first) And _
                   middle.Equals(othername.middle) And _
                   last.Equals(othername.last)
        End Function

        Public Function compareTo(ByVal otherName As Name) As Integer
            Dim result As Integer
            result = last.ToUpper().CompareTo(otherName.last.ToUpper)
            If (result <> 0) Then
                Return result
            Else
                Return _
                        middle.ToUpper.CompareTo(otherName.middle.ToUpper())
            End If
        End Function
    End Class
End Namespace

Testing

The Name class contains eight methods. Each one should be tested. As you can see, designing a class and writing its code is just the beginning of the process. Testing can be as much work or more.

In order to test the methods of a class, we need to write an application that can call each method. We call such an application a driver.

Driver
A simple application that is used to call methods being tested. The use of a driver permits direct control of the testing process.

The class we have just written is contained in the Name package and so we can simply import it into the driver with a statement like the following:

Imports Name

Once the package has been imported, our driver has access to all of the public interface components, and we simply need to write statements that call each of the methods with the necessary arguments to test that their responsibilities are met. In principle this sounds straightforward. But with eight methods, we must carefully write out a plan that lists every test to ensure that we don't miss any cases.

Because testing a namespace requires some new organizational concepts, we leave the development of the driver for the Name class to the Testing and Debugging section that follows.

End Sidebar


Testing and Debugging

Now that our code is becoming more complex, we need to extend our formal test plan to help us organize the larger number of tests. In Chapter 6 we introduced the test plan as a table with four columns: Reason for Test Case, Input Values, Expected Output, and Observed Output. Our test plan was written for a single method in a class.

With the large number of methods in a typical package, it's more convenient to combine multiple tests into a single driver. Thus, to help us write this driver, we expand the test plan to include Class, Method, and Test Number columns. Together with the class and method names, the test number uniquely identifies what's being tested.

The simplest approach to developing a namespace test plan is to create an empty table with seven columns and as many rows as there are methods. Insert the class and method names in the order that they appear in the code. Then, for each method, determine the number of different tests that are needed and add the necessary rows to the table, filling them in before moving on to the next method.

For many methods, such as basic observers, there may be just a single test. Methods with branches have multiple tests. Once all of the tests of a method are listed, you can number them. As an example, here is a test plan for our Name class. We omit the empty Observed Output column here to save space.

Class | Method | Test # | Reason for Test | Input Values | Expected Output
Name | New | 1 | Constructor | "John", "Kirk", "Herrel" | Name object with three fields
Name | knowFirstName | 1 | Observer | instance from constructor test | "John"
Name | knowMiddleName | 1 | Observer | instance from constructor test | "Kirk"
Name | knowLastName | 1 | Observer | instance from constructor test | "Herrel"
Name | firstMidLast | 1 | Observer | instance from constructor test | "John Kirk Herrel"
Name | lastFirstMid | 1 | Observer | instance from constructor test | "Herrel, John Kirk"
Name | Equal | 1 | Equality test | instance, second object with same values | True
Name | Equal | 1 | Inequality test | instance, second object with same values | False
Name | compareTo | 1 | Equality test | instance, second object with same values | 0
Name | compareTo | 2 | Greater than test | instance, second object with greater value | Positive integer
Name | compareTo | 3 | Less than test | instance, second object with lesser value | Negative integer

Once we have a plan of all the tests, it is simply a matter of writing a driver to perform them. Below is the code for the driver to test the Name class in the Name package.

Imports System
Imports NameClass

Module NameTest
   Sub Main()
      Dim testName As Name
      Dim otherName As Name
      ' Output headings
      Console.WriteLine("Test Results for Namespace Name")
      Console.WriteLine()
      Console.WriteLine("Class     Method      Test#  " & _
                     " Expected Output        Observed Output")
      ' Constructor test
      testName = New Name("John", "Kirk", "Herrell")
      If (testName.knowFirstName().Equals("John") And _
          testName.knowMiddleName().Equals("Kirk") And _
          testName.knowLastName().Equals("Herrell")) Then
         Console.WriteLine("Name      Name         1      " & _
           "3-field Name           3-field Name")
      Else
         Console.WriteLine("Name      Name         1      " & _
           "3-field Name           Fields don't match")
      End If
      ' Basic observer tests
      Console.WriteLine("Name      first         1   " & _
           "  John                   " & testName.knowFirstName())
      Console.WriteLine("Name      middle        1   " & _
           "  Kirk                   " & testName.knowMiddleName())
      Console.WriteLine("Name      last          1   " & _
           "  Herrell                " & testName.knowLastName())
      ' Additional observer tests
      otherName = New Name("John", "Kirk", "Herrell")

      Console.WriteLine("Name      Equals        1   " & _
           "  True                   " & testName.Equal(otherName))
      otherName = New Name("John", "Patrick", "Herrell")
      Console.WriteLine("Name      Equals        2   " & _
           "  False                  " & testName.Equal(otherName))
      otherName = New Name("John", "Kirk", "Herrell")
      Console.WriteLine("Name      compareTo     1   " & _
           "  0                      " & testName.compareTo(otherName))
      otherName = New Name("John", "Kirk", "Altman")
      Console.WriteLine("Name      compareTo     1   " & _
           "  Positive Integer       " & testName.compareTo(otherName))
      otherName = New Name("John", "Kirk", "Zigman")
      Console.WriteLine("Name      compareTo     1   " & _
           "  Negative Integer       " & testName.compareTo(otherName))

   End Sub
End Module

Testing and Debugging Hints

  1. Make sure your class designs are encapsulated to avoid the possibility of side-effect errors.

  2. Use the CRC card process to design the user interface before you begin to think about the implementation.

  3. Make classes immutable unless the abstraction truly requires its objects to change without being copied. Immutable objects are less prone to side effects.

  4. When choosing an internal representation, carefully consider the tradeoff between saving space or improving speed and the complexity of the required code. One benefit of encapsulation is that you can start with a simple implementation and then go back and make improvements after you have the class working.

  5. Consciously consider whether data should be associated with a class, an instance (object) of the class, or a particular method call.

  6. When writing methods, be careful not to use parameter names, local names, or method names that duplicate class or instance identifiers. Parameters and local identifiers hide class and instance identifiers with the same name. (More on this in the next chapter.)

  7. A method heading should always have a pair of parentheses, even when there are no parameters.

  8. Remember that arguments and parameters are matched by their position, and the types of each argument must be compatible (in the sense of the assignment operator) with its corresponding parameter.

  9. Assigning a new value to a parameter of a reference type causes it to refer to a new place in memory and leaves the arguments unchanged. Modifying the contents of the object at the location specified by the parameter modifies the same object that the argument refers to. Such side effects are considered poor programming practice.

  10. Only one value (simple or reference type) can be returned with a Return statement. Every value returning method must have at least one Return statement.

  11. A constructor must begin with the keyword New. Its heading does not include a Return type. Constructors typically take the Public modifier.

  12. When classes are defined inside a namespace, they must be imported using their fully qualified name. When an imported class from a namespace is declared, you must also use its fully qualified name; otherwise, an error will result.

  13. Make a test plan and use it to write your driver(s).


Summary of Classes

Package Name
Class Name |  |  |  | Comments
Name |  |  |  |
 | Name |  |  |
 |  | Constructors: New(first, middle, last) |  |
 |  | Value-Returning Instance Methods: |  |
 |  |  | knowFirstName() | All return String unless noted otherwise
 |  |  | knowMiddleName() |
 |  |  | knowLastName() |
 |  |  | firstMidLast() |
 |  |  | lastFirstMid() |
 |  |  | Equal(otherName) | Returns Boolean
 |  |  | CompareTo(otherName) | Returns Integer


Summary

Encapsulation is the separation of the implementation of a class from external code except through a formal interface. Abstraction is the separation of the logical properties of an object from its implementation. Abstraction helps us to design a well-encapsulated class. Together they lead to classes that are immune to side-effects, that are easily modified and reused.

Abstraction can be divided into data abstraction (separating the internal and external representations of the data stored in a class) and control abstraction (separating the logical properties of operations from their implementations).

Class responsibilities can be divided into four categories: constructors, observers, transformers, and iterators. Constructors create objects, observers return the abstract contents of objects, and transformers directly change the contents of objects. When an object has multiple parts that can be accessed in some sequence, an iterator is used to step through the parts.

Objects can be mutable or immutable. Immutable objects do not have transformer operations.

Visual Basic allocates memory space for objects from a free pool in memory. When an object no longer has any identifiers that refer to it, the garbage collector returns its space to the free pool. Because instance data is associated with an object, its lifetime starts with allocation and ends when it becomes garbage (is unreachable). The lifetime of class data is for the entire execution of an application. Local data exists only for the lifetime of an individual method call.

Methods provide the means for implementing the responsibilities of a class. We design the method interface from the CRC card specification of a responsibility. The interface includes the parameters that receive the arguments that are passed to the method, the method's return value (if any), and access to class and instance fields of an object. We categorize the information that flows through the interface as receives, returns, and changes.

A method heading defines the parameters that receive the corresponding arguments from a call to the method. In Visual Basic a copy of the argument is given to the method. Simple type arguments that are passed to a method cannot be modified by it. Assigning a new value to a reference type parameter doesn't change the argument because the assignment causes the address stored in the parameter to refer to a different object. However, the object associated with a reference type argument can be changed by a method if it assigns new values to the fields of the object, but this is bad practice.

The Return statement in combination with a value-returning method provides a mechanism to return a value from a method. If a method's responsibility is to change the values in an object, the method should be an instance method applied to the object and not a method that takes the object as a parameter. An instance method has the option to receive, return, or change any object field.

A constructor is a special type of method that is called with the New operator to initialize a newly created object. The constructor has access to all the fields of the new object. The heading for a constructor doesn't include a Return type. The name of a constructor is always New.

A namespace collects a set of related object classes into a common structure that can be imported by other classes and applications.

Test plans are especially important for verifying the correct operation of a new class or namespace. Because there may be many methods to test, we need to take an organized and methodical approach to testing each one with no case omitted. We can often combine multiple tests into a single driver to save work.


Quick Check

Q: | 1. What are the benefits of a well-designed interface? (p. 260) | 1. A well-designed interface makes a class easy to use and debug.
Q: | 2. What is abstraction? (p. 262) | 2. The separation of the logical properties of an object from its implementation.
Q: | 3. Which do you design first, the class interface or the implementation? (p. 262) | 3. Abstraction dictates that the interface must be designed first.
Q: | 4. What are the four categories of responsibility? (p. 264) | 4. Constructor, observer, transformer, iterator.
Q: | 5. (True or False?) Transformer operations may be defined on an immutable object. (pp. 263-264) | 5. False.
Q: | 6. What is the default modifier for a declaration? (p. 264) | 6. Namespace.
Q: | 7. What is the purpose of a constructor for a class? (pp. 264) | 7. A constructor is a method that initializes the data fields of a new class instance.
Q: | 8. What is an iterator? (p. 264) | 8. An iterator is an operation that allows the user to view members in a collection one at a time.
Q: | 9. What do we call the transfer of data between the arguments and parameters in a method call? (pp. 276-277) | 9. Parameter passing.
Q: | 10. Distinguish between simple and reference types. (pp. 277-280) | 10. A variable of a simple type contains a value. A variable of a reference type contains the address of a value.
Q: | 11. When a method is invoked, how does the system match up arguments and parameters? (p. 277) | 11. Arguments and parameters are matched by position: The first argument is copied into the first parameter, the second argument is copied into the second parameter, etc.
Q: | 12. What does "lifetime of a variable or constant" mean? (p. 270) | 12. Lifetime refers to the portion of a program's execution during which a variable or constant is assigned storage space in memory.
Q: | 13. What is the Visual Basic syntactic construct that bundles related classes? (p. 285) | 13. Namespace.

Answers

A: | 1. A well-designed interface makes a class easy to use and debug.
A: | 2. The separation of the logical properties of an object from its implementation.
A: | 3. Abstraction dictates that the interface must be designed first.
A: | 4. Constructor, observer, transformer, iterator.
A: | 5. False.
A: | 6. Namespace.
A: | 7. A constructor is a method that initializes the data fields of a new class instance.
A: | 8. An iterator is an operation that allows the user to view members in a collection one at a time.
A: | 9. Parameter passing.
A: | 10. A variable of a simple type contains a value. A variable of a reference type contains the address of a value.
A: | 11. Arguments and parameters are matched by position: The first argument is copied into the first parameter, the second argument is copied into the second parameter, etc.
A: | 12. "Lifetime" refers to the portion of a program's execution during which a variable or constant is assigned storage space in memory.
A: | 13. Namespace.


Exam Preparation Exercises

    Chapter 7

  1. What is the goal of designing a new class?

  2. Why is it important to encapsulate a new class?

  3. Name two benefits of encapsulating a class's implementation.

  4. Explain the advantages of modifiability.

  5. Explain the advantages of reuse.

  6. How can exposing implementation details cause an error in a user program?

  7. What are the five basic control structures?

  8. Constructors have a strange syntax. Explain.

  9. Can one object have another object as a part?

    1. What is data abstraction?

    2. What is control abstraction?

    3. Differentiate between data abstraction and control abstraction within an object.

  11. Distinguish between class and instance methods at the logical level.

  12. Distinguish between class and instance methods at the syntactical level.

  13. Distinguish between a parameter and an argument.

    1. If a parameter is of a simple type, what does the parameter receive?

    2. Can the method change the value in the parameter?

    3. Is the argument changed?

    1. If a parameter is not a simple type, what does the parameter receive?

    2. Can the method change the value in the parameter?

    3. Is the argument changed?

  16. Why doesn't changing a parameter of type String change the argument?

  17. What does a constructor do? Give an example.

    1. What does a transformer do? Give an example.

    2. What does an observer do? Give an example.

    1. What does an iterator do?

    2. Distinguish between a transformer iterator and an observer iterator.

  20. Fill in the following table showing three categories of data, specifying the lifetime of each and what code can access each.

    Types of Data

    Lifetime of Data

    From Where Data Is Accessible

  21. How does the Visual Basic compiler distinguish between class data and instance data?

    1. From where does the CLR get the space it needs to create an object?

    2. Define allocate.

    3. Define deallocate.

    4. Define dynamic memory management.

    1. What does it mean when we say an object is unreachable?

    2. What is the term used for unreachable objects?

    3. What is the process of going through memory and reclaiming the memory assigned to unreachable objects?

    1. What is a DLL file?

    2. What does a DLL file hold?

    1. What are the two places in which the data abstraction is visible to the user?

    2. Should the public constants be initialized in their declarations or in assignment statements?

    3. Can variables be marked Public in Visual Basic?

  26. Distinguish between an immutable and a mutable object.

  27. (True or False?) Transformer operations may be defined on a mutable object.

    1. How can you change a value in an immutable object?

    2. How do you create a new object with the same values as another object?


Programming Warm-Up Exercises

    Chapter 7

  1. Declare the following instance variables for a class called AddressLabel: name, address, state, zipCode, city, and country. zipCode is a long; the others are strings.

  2. Write the heading for a copy constructor for class AddressLabel that changes the address field.

  3. Write the heading for a constructor for class AddressLabel that has four String parameters, name, address, city, and state, and one long parameter, zipCode. The country is set by default to the United States.

  4. What basic observer operations should our AddressLabel class have?

  5. Declare a namespace named someNamespace with headings for Public class AddressLabel and Public class PrintLabel.

  6. How does Visual Basic manage to put all the pieces of a namespace together?

  7. Write the code for a class method call to class Integer, followed by an instance method call to the newly created object.

  8. Write a Visual Basic statement that declares an integer-returning Public method named myExample.

  9. Write a Visual Basic statement that assigns 1066 as the return value of myExample.

  10. Design and declare an integer method that inputs an integer value from a TextBox and returns it.

  11. Design and declare a Single method that inputs a Single value from a TextBox.


Programming Problems

  1. Design and implement an address book object that contains a person's name, home address, and phone number, and numbers for their fax machine, cellular phone, and pager. Write a test plan for the object and implement a driver that tests it.

  2. Design, implement, and test a namespace of one or more classes to represent a catalog entry for a music library. The objects in the library are albums, which can be in the form of tapes, records, compact disks, DVDs, and electronic files. Each album has a title, name of the recording group, name of the record company, the catalog number or URL used to order the recording, number of songs, total playing time, and an index used to identify the album in the library.

  3. Design, implement, and test a namespace of one or more classes to represent a car. The information associated with a car object is its make, model, production year, color, vehicle identification number, registration number, and owner's name and address.


Case Study Follow-Up Exercises

    Chapter 7

  1. Add an iterator method to the Name class that returns each of the parts of a name in the order that they are listed in the class. That is, the first time the iterator is called, it returns the first field. The second time it is called, it returns the middle field, etc. After it returns the last field, it should reset to the first field for the next call.

  2. Modify the test plan and extend the driver program as necessary to test the iterator developed for Exercise 1.


Chapter 8: Inheritance, Polymorphism, and Scope

Overview

Goals

  • To be able to visualize the hierarchical nature of classes in object-oriented programming.

  • To be able to demonstrate the concept of inheritance in a class hierarchy.

  • To be able to distinguish between overriding and hiding in Visual Basic.

  • To be able to define polymorphism.

  • To be able to differentiate between deep and shallow copying of objects.

  • To be able to use the access rules for Visual Basic classes.

  • To be able to apply the assignment capability rules for objects.

  • To be able to identify the interface components of a class in a hierarchy.

  • To be able to design a derived class to extend an existing class hierarchy.

  • To be able to implement a derived class using inheritance.

  • To be able to explain the concept of overloading in Visual Basic.

In Chapter 7 you saw how to create a new class from a CRC card design by choosing an internal representation and implementing the responsibilities as methods. Using the CRC card technique naturally results in a design that is encapsulated. An encapsulated class has the advantage of being reusable in different programs. We also mentioned that another form of reuse is to create additional classes that are variations of the base class. In this chapter we look at how to design and implement such derived classes.

The relationship between derived classes (subclasses) and their base class is defined by Visual Basic's rules of inheritance, which tell us what parts of the base class are automatically included in the derived classes. Another aspect of Visual Basic that is similar to the inheritance rules is called scope of access. Visual Basic provides a set of rules that determine the fields and methods of a class that can be accessed by derived classes, classes in the same namespace, user code, and so on. Understanding these rules helps us to preserve encapsulation as the relationships between classes become more sophisticated.


8.1 Inheritance

Let's look at an analogy between the work of an architect and the work of a programmer. The way that an architect handles the complexity of a large building design sheds some light on how we can organize our programming work. The analogy lets us consider the same concepts but without the distraction of Visual Basic syntax and semantics.

An Analogy

The architect begins by considering the overall requirements for a building: square footage, number of occupants, types of usage, size of the building lot, height limits, and so on. Once some initial decisions are made, the architect is faced with a basic aspect of any design: the building is composed of floors. In many buildings the floors all have common characteristics: the same size and shape, the same number and location for elevator shafts, stairways, and utility trunks, and so on. The architect could then begin by designing a basic empty floor with all of the common elements in place. Once she installs this plan in the library of the computer-aided design (CAD) program, she can use it repeatedly as the starting point for designing each floor of the building.

The architect may further decide that there are two main types of floors: office floors and mechanical equipment floors. The office floors might be of two types: executive office space and standard office space. Starting from the basic empty floor design, she adds components such as lavatories and hallways to make an empty office floor. She can then add offices and conference rooms to the empty space. Each of the four types of floors is thus derived from the basic empty floor plan and is added to the library (see Figure 8.1). Drawing the entire building is then simply a matter of creating an instance of one of these four floor plans for each story.

Click To expand
Figure 8.1: A hierarchy of floor types

The architect uses the same process to design the components that make up the floors. She might design a basic type of office with a door, windows, lights, heating, wiring, and so forth and then derive several types of offices from that one design. From a given type of office, such as secretarial, she might further refine the design into subtypes such as general secretarial, secretary/receptionist, and executive secretary.

Creating hierarchies of designs simplifies the architect's job. She begins each hierarchy with the most general form of a building component, such as the basic empty floor, and then derives new designs by adding details to the more general forms. The new designs inherit all of the characteristics of the general form, saving the architect from having to redraw those pieces they have in common. In some cases she replaces existing parts of a design, as when she substitutes a wider door for a reception area than is in the basic secretarial office. The replacement part overrides what was originally specified in the more general form.

In addition to the components of individual floors, the architect can specify characteristics that are common to all floors, such as a color scheme. Each floor inherits these general properties. Sometimes she hides or deletes portions of the general properties, as in customizing the color scheme for a particular floor that has been rented in advance by a company with its own corporate colors. We see later that inheritance, overriding, and hiding are formally defined mechanisms in Visual Basic.


8.2 Inheritance and the Object-Oriented Design Process

Now let's consider how a class hierarchy originates in the CRC card-design process. At the end of the filtering phase we sometimes discover that there are several classes that are similar. For example, in the Address Book example of Chapter 5, we noted the similarity between the Address and Work Address classes. Let's look at the CRC cards for some different address objects. There are no class members, so we don't bother to write "instance" by each member field.

Click To expand
Click To expand
Base class
A class that is extended by one or more derived classes (its subclasses)
Subclass
A class that is derived from another class (its base class)

These CRC cards might originate in the brainstorming phase or they could result from a series of scenarios. No matter how they arise, it is quite obvious that they have several responsibilities in common: Know Its Name, Know Its City, Know Its State, and Know Its Zip Code. We can save ourselves duplicate effort by defining a base class, Address, that has the common responsibilities. In addition, two of the classes share the "Know its Company" responsibility. We can thus define a subclass of Address called Company Address that becomes the base class of these two. The third class directly extends Address with its specific responsibilities. Here are the revised CRC cards:

Click To expand
Click To expand

Figure 8.2 shows the relationships between these classes. Address is the most general form and is thus at the top of its hierarchy, just below VB .NET's most general class, which is called Object.

Click To expand
Figure 8.2: Address class hierarchy

The ability of a programming language to support the creation of base classes and subclasses is known as inheritance. Each of the subclasses inherits the responsibilities that are defined by its base class, including all of the responsibilities that it has inherited. In addition, subclasses are assignment compatible with the base classes above them in the hierarchy. That is, we can assign a WorkAddress object to a WorkAddress variable, a CompanyAddress variable, an Address variable, or an Object variable.

Inheritance
A mechanism by which one class acquires the properties—the data fields and methods—of another class

Periodically in the CRC card-design process it is useful to look for similarities among the cards and decide whether a base class should be created. Another situation where inheritance is used can be seen when we start to create a new class and realize that it is just a variation of an existing class. For example, a field for input of numeric values is just an extension of the familiar Textbox. We simply have to add getInteger and getDouble methods that read the field and convert it to a numeric value. The derived class could be called NumericField. There is no need to define an entirely new class when we can simply extend one that already exists. As you look for things that are familiar in solving a problem, keep in mind the existing classes.

In our Address example, we collected responsibilities from similar classes to form a base class. In the Textbox case, we recognize the existence of a class with the properties that make it suitable as a base class. Whenever you encounter either of these situations, you should immediately consider how you can take advantage of it through inheritance.


8.3 How to Read a Class Hierarchy

In Visual Basic, all classes can eventually trace their roots back to the System.Object class, which is so general that it does almost nothing; objects of type System.Object are nearly useless by themselves. But System.Object does define several basic methods—comparison for equality, conversion to a string, and so on.

The Visual Basic library defines numerous classes that directly extend the System.Object class and thus inherit all of its methods. For example, there is a class called Control that extends System.Object with the basic operations needed to display something in a Form. Returning to our analogy, the architect would never include the basic empty floor in a building plan, but use it instead as the basis for designing floors that are complete. Similarly, we would never instantiate an object of an abstract class in a program, but we use it as the base class for defining new subclasses that are complete.

Abstract
A modifier of a class or field that indicates that it is incomplete and must be fully defined in a derived class
Derived class
A class that is created as an extension of another class in the hierarchy

The familiar Button and Label classes are subclasses of the System.ComponentModel.Component class. The TextBox class is derived from a class called TextBoxBase, which is in turn derived from System.ComponentModel.Component. As you can see, the hierarchy of classes can be multiple levels in depth, so it can become difficult to keep track of who's descended from whom. Figure 8.3 shows how these classes are related. We use boxes with their corners cut out to represent the incomplete nature of the abstract class.

Click To expand
Figure 8.3: Hierarchy of component classes

When the architect looks at a floor plan with her CAD program, she sees all of its parts, including those that are derived from the basic empty floor. We can get much the same view of a Visual Basic class when we view its documentation.

For example, let's look at part of the documentation for the TextBox class. This table is called the class's member list.

Public Instance Constructors

TextBox Constructor

Initializes a new instance of the TextBox class.

Public Instance Properties
Tag (inherited from Control) | Gets or sets the object that contains data about the control.
Text (inherited from TextBoxBase) | Overridden. Gets or sets the current text in the text box.
TextAlign | Gets or sets how text is aligned in a TextBox control.
TextLength (inherited from TextBoxBase) | Gets the length of text in the control.
Top (inherited from Control) | Gets or sets the top coordinate of the control.
Public Instance Methods
AppendText (inherited from TextBoxBase) | Appends text to the current text of text box.
BeginInvoke (inherited from Control) | Overloaded. Executes a delegate on the thread that owns the control's underlying handle.
BringToFront (inherited from Control) | Brings the control to the front of the z-order.
Clear (inherited from TextBoxBase) | Clears all text from the text box control.
Public Instance Events
Click (inherited from TextBoxBase) | Occurs when the text box is clicked.
ContextMenuChanged (inherited from Control) | Occurs when the ContextMenu property value has changed.
ControlAdded (inherited from Control) | Occurs when a new control is added.
ControlRemoved (inherited from Control) | Occurs when a control is removed.
CursorChanged (inherited from Control) | Occurs when the Cursor property value has changed.
Disposed (inherited from Component) | Represents the method that will handle the Disposed event of a Component.

This documentation, which is explained in more detail in Appendix F, details all of the objects associated with the TextBox class. You can see that most, but not all, of the methods, properties, and events of the TextBox class are inherited from other classes. If we look back up the class hierarchy, the closest of these classes is the TextboxBase class.

If we look at the documentation for this class, we'll find the definitions for many of the properties, methods, and events found in the TextBox class. We can also see in the documentation a note stating that we should not inherit from the TextBoxBase class. This is because the class is what is known as an abstract class, which means that the class cannot be instantiated and you should inherit from one of its derived classes. There are two derived classes for TextBoxBase—TextBox and Rich-TextBox.

Next up in the hierarchy is the Control class. This class provides the basic functionality for the objects you place on a form. It handles input through the keyboard and the mouse, and it defines the boundaries of a control (where a control fits on a form). Although it is not an abstract class, you do not typically inherit from this class.

The Control class inherits from the Component class (also called the System.ComponentModel.Component) class. This class is used exclusively for the creation, containment, and disposal of a component. A control on a form is an example of a component. Non-form object examples of components include any object-oriented programming object that is reusable and can interact with other objects.

Overriding

If you look at the definition of the Text property in the TextBox class definition, you see the line

Overrides Public Property Text As String

which means that the TextBox class redefines how the Text property works from its definition in the TextBoxBase class. This means that the TextBoxBase class defines a Text property and the TextBox class uses the same form of heading. We don't have to create a new definition of the Text property, we can just override the definition from its base class.

Override
To provide an instance method in a derived class that has the same form of heading as an instance method in its base class. The method in the derived class redefines (overrides) the method in its base class. We cannot override class methods.

As we look further at the definition of the TextBox class, each row in the table tells us every property, method, and event that is available in the class. There are more than 100 of these for the TextBox class alone. If we move up the hierarchy to include the Component class and the Control class, the number will grow much higher.

This example illustrates the power of using inheritance in a hierarchy of classes. Just as the architect saves effort by defining a hierarchy of building parts, we can save ourselves a lot of work by using inheritance.

Hiding

When a derived class defines a property with the same name as one in its base class, the field in the derived class hides the one in the base class. The same applies to class methods. Hiding a class method may seem like overriding, but technically, overriding can only apply to instance methods.

Hide
To provide a property in a derived class that has the same name as a property in its base class. To provide a class method that has the same form of heading as a class method in its base class. The field or class method is said to hide the corresponding component of the base class.

If you look at the TextBox member list carefully, you may notice that nothing is deleted as a result of inheritance (except constructors). In Visual Basic, there is no way to remove a member that is inherited. We can cover over a member with a replacement member, but we can't delete it. This is an aspect of the philosophy of object-oriented design: As we go deeper in the hierarchy, we add or change functionality, but we do not lose functionality. In object-oriented design, a derived class is always an extension of its base class.

For example, we would say that a TextBox is-a TextBoxBase. In object-oriented design, a fundamental concept is the is-a relationship between a derived class and its base class. A derived class is-a form of its base class. The closest we can get to deleting a member of a base class in a derived class is to override it or hide it with a member of the same name that does nothing. However, this would be considered poor programming practice.

Polymorphism

Let's take a closer look at the mechanism that allows Text to have two different implementations, an ability called polymorphism. Object-oriented languages, such as Visual Basic, give us the ability to implement subclasses with methods that override base class methods. Our definition of Text in class TextBox that overrides the method in base class TextBoxBase is an example. Polymorphism further enables the compiler and CLR to decide which version of Text is appropriate to call depending on the class of the object to which it is applied. For example, when Text is applied to a TextBox, it has one implementation, but when applied to another control, say a label, it has another. In object-oriented terms, we say that Text is polymorphic. That is, it has multiple forms. Visual Basic decides which form to use depending on the class of the object.

Polymorphism
The ability of a language to have duplicate method names in an inheritance hierarchy and to apply the method that is appropriate for the object to which the method is applied

Together with inheritance and encapsulation, polymorphism gives us the ability to flexibly implement a hierarchy of objects. We use polymorphism to substitute different implementations of a responsibility as required by variations in the internal representation of classes at different levels of the hierarchy. For example, Address might represent the zip code as a single string, but then we might redefine its representation in the CompanyAddress class so that the four-digit extension is kept separately. Thus, the Know Zip Code responsibility would be implemented differently in CompanyAddress than in Address.


8.4 Derived Class Syntax

The declaration of a derived class looks very much like the declaration of any other class. Here is the syntax template for a class declaration.

Click To expand

The only difference between this template and the one in Chapter 7 is that we've added the optional Inherits clause that allows us to indicate the base class that our new class is derived from. Here's an example declaration.

Public Class NumericField
       Inherits TextBox
       ' Declare new methods that add numeric input to TextBox
End Class

This new class inherits all of the fields and methods of TextBox (including everything TextBox has inherited) and then adds some of its own. Is that all there is to implementing a derived class? In terms of syntax, yes. But in terms of semantics, there is more we need to know.

In Chapter 7 we learned how to create individual classes that are fully encapsulated. With inheritance and polymorphism we are beginning to create related sets of classes. Visual Basic lets us group related classes in a namespace so that encapsulation protects the whole collection. But within the namespace, Visual Basic allows the related classes to communicate directly. Direct access is more efficient than converting back and forth between external and internal representations when classes share a common internal representation.

Also, when a derived class overrides or hides members of its base class, it can no longer refer to them directly. Visual Basic provides additional syntax and semantics that enable a subclass to access such base class members when the need arises.

In the next section we examine the Visual Basic semantics for accessing members from different places. Then we can return to the specifics of implementing a hierarchy of derived classes.

Start Sidebar
May We Introduce
Ada Lovelace

On December 10, 1815 (the same year that George Boole was born), a daughter—Augusta Ada Byron—was born to Anna Isabella (Annabella) Byron and George Gordon, Lord Byron. In England at that time, Byron's fame derived not only from his poetry but also from his wild and scandalous behavior. The marriage was strained from the beginning, and Annabella left Byron shortly after Ada's birth. By April of 1816, the two had signed separation papers. Byron left England, never to return. Throughout the rest of his life he regretted that he was unable to see his daughter. At one point he wrote of her,

Click To expand

I see thee not. I hear thee not.
But none can be so wrapt in thee.

Before he died in Greece, at age 36, he exclaimed,

Oh my poor dear child! My dear Ada!
My God, could I but have seen her!

Meanwhile, Annabella, who eventually was to become a baroness in her own right, and who was educated as both a mathematician and a poet, carried on with Ada's upbringing and education. Annabella gave Ada her first instruction in mathematics, but it soon became clear that Ada was gifted in the subject and should receive more extensive tutoring. Ada received further training from Augustus DeMorgan, today famous for one of the basic theorems of Boolean Algebra. By age eight, Ada had demonstrated an interest in mechanical devices and was building detailed model boats.

When she was 18, Ada visited the Mechanics Institute to hear Dr. Dionysius Lardner's lectures on the "Difference Engine," a mechanical calculating machine being built by Charles Babbage. She became so interested in the device that she arranged to be introduced to Babbage. It was said that, upon seeing Babbage's machine, Ada was the only person in the room to understand immediately how it worked and to recognize its significance. Ada and Charles Babbage became lifelong friends. She worked with him, helping to document his designs, translating writings about his work, and developing programs for his machines. In fact, Ada today is recognized as the first computer programmer in history.

When Babbage designed his Analytical Engine, Ada foresaw that it could go beyond arithmetic computations and become a general manipulator of symbols, and thus would have farreaching capabilities. She even suggested that such a device eventually could be programmed with rules of harmony and composition so that it could produce "scientific" music. In effect, Ada foresaw the field of artificial intelligence more than 150 years ago.

In 1842, Babbage gave a series of lectures in Turin, Italy, on his Analytical Engine. One of the attendees was Luigi Menabrea, who was so impressed that he wrote an account of Babbage's lectures. At age 27, Ada decided to translate the account into English, with the intent to add a few of her own notes about the machine. In the end, her notes were twice as long as the original material, and the document, "The Sketch of the Analytical Engine," became the definitive work on the subject.

It is obvious from Ada's letters that her "notes" were entirely her own and that Babbage was acting as a sometimes unappreciated editor. At one point, Ada wrote to him,

I am much annoyed at your having altered my Note. You know I am always willing to make any required alterations myself, but that I cannot endure another person to meddle with my sentences.

Ada gained the title Countess of Lovelace when she married Lord William Lovelace. The couple had three children, whose upbringing was left to Ada's mother while Ada pursued her work in mathematics. Her husband was supportive of her work, but for a woman of that day, such behavior was considered almost as scandalous as some of her father's exploits.

Ada died in 1852, just one year before a working Difference Engine was built in Sweden from one of Babbage's designs. Like her father, Ada lived only to age 36, and even though they led very different lives, she undoubtedly had admired him and took inspiration from his unconventional and rebellious nature. In the end, Ada asked to be buried beside him at the family's estate.

End Sidebar


8.5 Scope of Access

In writing a Visual Basic class we declare variables, constants, methods, and inner classes that are given identifiers. Visual Basic defines a set of rules that specify where those identifiers can then be used, both inside and outside of the class. We say that the rules determine the scope of access of an identifier. The term is usually shortened to scope, and the rules are thus called the scope rules of the language.

Scope of access (scope)
The region of program code where it is legal to reference (use) an identifier
Scope rules
The rules that determine where in a program an identifier may be referenced, given the point where the identifier is declared and its specific access modifiers

The scope rules for access within a class are straightforward and do not depend on the access modifiers attached to an identifier. External access scope rules determine where an identifier can be used outside of a class and depend on both the access modifiers and where the access takes place. We look first at internal scope, then at external scope.

Internal Scope

Any identifier declared as a constant or instance member of a class can be used anywhere within the class with two exceptions. You can't use one class variable to initialize another before the first one has been defined. And, within its block (statement sequence), a local identifier overrides a constant or instance member of the same name. Let's take a closer look at each of these exceptions.

Order of Definition Suppose you are defining a Circle class and you want to provide class variables that are initially set to PI and PI times two. The first of the following two declarations is illegal because its initialization expression uses the second identifier before it has been given a value.

Const twoPI As Double = PI * 2    ' PI isn't defined yet
Const PI As Double = 3.14159265358979323846

Reversing the order of the statements makes them both legal.

Const PI As Double = 3.14159265358979323846
Const twoPI As Double = PI * 2    ' PI already has a value

The scope rule that requires us to define a variable's value before it is used applies only to references in expressions that initialize other variables as part of their declaration.

It's otherwise legal to refer to variables before they are defined. For example, the following declarations are legal.

Public Function  circumference(anyCircle as Circle) As Integer
   Return anyCircle.Radius  *  twoPI
End Function
Const PI As Double = 3.14159265358979323846
Const twoPI As Double = PI * 2

The method is allowed to refer to the variable twoPI before it is initialized. Why is this case different? The CLR performs all variable declaration initializations before it starts executing statements in the main program, and it does them in the order that they are written in the code. So an initialization expression can only use variables that have already been given values.

The method circumference isn't executed until it is called from some point in the program, which can happen only after all of the variable declaration initializations are complete. In terms of the order of execution, all initialization expressions are executed before any other use of a variable.

Note that regular assignment statements that initialize variables in methods are distinct from declaration initialization expressions. They are executed in the normal flow of control, which starts after all of the declaration initializations.

Constant declarations do not follow this rule because the compiler computes constant values at compile time. The compiler first searches through our code to find all the constant declarations before it computes their values. So it doesn't require us to define a constant before using it. The CLR isn't able to search through the Bytecode in the same way, so it requires us to initialize variables before their use.

To make life easier for human readers of your code, it's good form to define constants ahead of any references to them. Some programmers even make a point of writing all constant declarations preceding the variable declarations, just as a reminder that constants are given their values first.

Name Precedence The scope rule that says local identifiers hide members of the class with the same name is called name precedence.

Name precedence
A scope rule specifying that a local identifier declaration hides the declaration of an identifier in the class with the same name (also called name hiding)

In Visual Basic, the scope of a local identifier is the entire block (statement sequence) in which it is declared. The block includes all of the statements between a method heading and the closing of the method definition. For example:

Public Class someClass
   Public var As Integer
   Public Const CONSTANT As Integer = 5
   Public Sub someMethod(param As Integer)
      Dim var As Integer
      Const Constant As Integer = 10
      Var = param * CONSTANT
      Labell.Text = var
   End Sub
End Class

Notice in this example that the scope of the local declarations extends to the entire block. Thus, if we use Const to initialize var, the initialization would refer to the local version of Const. Of course, the compiler would report this as an error because we're using Const before we've defined it.

External Scope

The external access scope rules for members control their use by code that is outside of the class. There are three external places from which Visual Basic allows class members to be accessed: derived classes, members of the same namespace, and code external to the namespace.

A namespace contains a set of related classes, and sometimes we want to make members of those classes accessible to each other, even if they aren't Public. For example, suppose that a Date class is part of a Calendar namespace. There is another class, called ShowWeek, in the namespace that displays a week surrounding a given date. Both of the classes use the Julian day as their internal representation. It is thus more efficient for a Date object to make its Julian day member directly accessible to ShowWeek than to require conversion of the date to its external form and back to a Julian day in the other object. The user is unaware of this shortcut, so the encapsulation is preserved.

Classes naturally belong together in a namespace if they have common internal representations. In that case, they can bypass each other's encapsulations because the common details of their implementations are already known to anyone who's working with them.

Visual Basic defines five levels of access for class members, four of which enable direct access by other namespace members. The five levels of access are Public, Protected, Friend, Protected Friend, and Private. These are also the keywords we use for access modifiers for each of the levels.

Public Access A Public member can be accessed from anywhere outside of the class. User code, derived classes, and other classes in the same namespace can all access a Public member. The member may still be hidden by a declaration of the same name in another class, in which case references to it must be qualified with its class or instance name.

Here's an example of using qualified names. If class ShowWeek defines a julianDay member and Date also defines julianDay as a class member, then ShowWeek would need to refer to

Date.julianDay

to access the julianDay field of Date. If julianDay is an instance member of Date and the particular object is called instanceName, then ShowWeek must refer to it as:

instanceName.julianDay

Protected Access A Protected member is accessible from a derived class and it can be inherited by a class that is further derived from that derived class.

The following code segment shows the definition of two classes. The second class, DerivedClass, is derived from the first class, SomeClass, and it inherits the Protected field someInt from SomeClass. Notice that DerivedClass does not have its own declaration of someInt.

Public Class SomeClass
   Protected someInt As Integer
End Class

Public Class DerivedClass(param1 as SomeClass)
                     Inherits SomeClass
    param1.someInt = 1
End Class

Public Sub someProg()
   Dim param2 As SomeClass
   param2.someInt = 2      ' Generates compiler error
End Sub

As long as the Protected member is referenced in a derived class, all is well. However, the subprogram someProg creates a variable of type SomeClass, but since this subprogram is defined outside the derived class, any calls to the Protected member will generate an error.

Friend Access A Friend member is accessible from anywhere within the program entity in which the Friend member is declared. The following program illustrates the use of the Friend modifier.

Imports System
Public Class someName
   Private nameValue As String
   Friend nameLen As Integer

   Friend Property Name() As String
      Get
            Return nameValue
      End Get
      Set (Byval value As String)
         nameValue = value
      End Set
   End Property
End Class

Public Class anotherName
       Inherits someName
End Class

Module FriendText
   Sub Main
      Dim m As New someName
      Dim n As New anotherName
      m.Name = "Mike"
      n.Name = "Mason"
      n.nameLen = n.Name.length()
      Console.Write(m.Name & " " & n.Name)
   End Sub
End Module

The first class, someName, declares a Private field, nameValue, for storing a name. A single property, Name, is created, with its access set to Friend. Class anotherName is then defined as essentially an empty shell that inherits from class someName. The main program then instantiates an object of each class, sets the Name property, and then sets the nameLen property using the object instantiated from anotherName. We can access this field because it was declared as Friend in the someClass definition.

Protected Friend Access A Friend member is accessible from anywhere within the program entity in which the Friend member is declared, and from within derived classes.

Private Access Lastly, a Private member cuts off all external access, even by classes in the same namespace or program assembly. A Private member of a class can be referred to only by other members of the class, and only the internal scope rules apply to it. It isn't even permissible for a derived class in the same namespace to access a Private member of its base class.

Instances of a class can refer to each other's Private members. A member is Private to its class, which includes all instances of the class. Thus, two objects, someObj and otherObj, that have Private Integer fields called someInt can refer to each other's fields. That is, someObj can refer to otherObj.someInt and otherObj can refer to someObj.someInt.

Thus far we have primarily used the Public level of access, making most classes and methods Public. This simple scheme provides encapsulation and a consistent user interface that is strictly a set of method calls. However, the scheme is inflexible and limits our ability to provide for either extension or the construction of related classes that are grouped into a program.

Now that we have a better understanding of the Visual Basic access rules, we must consider which access modifier is most appropriate for each member of a class. Once you have identified all of its members, take a second look at each one and ask the following question.

Do I want to access the member from other classes in the same namespace, from derived classes, or from user code?

Based on your answer to each part of this question, you can use the preceding access rules to decide which access modifier is most appropriate.


8.6 Implementing a Derived Class

Given the CRC card design for a subclass and its base class, you can determine what it is that you must include in the subclass to implement its responsibilities. You may find that you simply need to change the operation of an inherited method, which means that you override or hide it with a new version. Because a derived class doesn't inherit any constructors, it probably needs one or more new constructors. We often implement new constructors by calling the old ones to do much of the work, and then adding a few statements to take care of initializing any fields that we've added. In some cases you may find it necessary to change the internal representation of the subclass.

After you have designed the subclass interface and its internal representation, you implement it by writing the necessary field and method declarations within a class declaration that inherits the base class. Here are the steps in the form of a checklist.

  1. Study the interfaces of the base class and the subclass, identifying the members that are inherited.

  2. Determine whether the internal representation must change in the subclass.

  3. Provide constructors as needed.

  4. Add fields and methods to those that are inherited, as necessary.

  5. Hide any inherited fields or class methods that you wish to replace.

  6. Override any instance methods that you wish to replace.

We have already seen how to read an existing class hierarchy to determine what is inherited by a subclass. When we are creating a new hierarchy we start from a set of CRC cards. During the enactment of the scenarios, it may become clear that certain responsibilities are Public, Private, or Namespace in their access requirements and this should be written on the cards. As we've noted, it is rare for a CRC card design to result in a Protected responsibility.

As we design the internal representation for a class, we may also notice that certain fields need an access level other than Namespace. For example, we might define some Public class constants. Once we have this information, we can list the members that a subclass inherits.

Next we reconsider the choice of internal representation given the responsibilities of the subclass. Unless there is a good reason for making the representation different from the base class, it should remain the same. Thus, we begin with the inherited representation and look for any omissions. We can easily add extra fields.

Sometimes an inherited field is inappropriate for the subclass. Perhaps the base class uses an Integer field for part of its representation, and the purpose of the subclass is to extend its range by using Long. Then we must hide the inherited field in the subclass.

Once we have the interface and internal representation, we can begin to implement the constructors. Visual Basic has some special rules regarding constructors in derived classes that we examine next.

Constructors in Derived Classes

Visual Basic requires every constructor in a derived class to first call a special method, MyBase.New. This method actually calls the constructor of the base class and must be the first statement in the constructor, even before any declarations.

In fact, Visual Basic requires every constructor in a derived class to call a constructor in its base class. This is normally accomplished by including the line

MyBase.New

as the first line in the constructor of a derived class. If the statement isn't included in the constructor, it is added implicitly by CLR.

The reason that Visual Basic requires us to call a base class constructor is that every derived class is a specialized form of its base class. An object of the subclass can be assigned to a variable of both its own class and of its base class (and by extension, any of the classes above it in the hierarchy). The base class may perform initialization operations to create a valid object.

Of course, the base class constructor must call a constructor for its base class, and so on, until the top-level object constructor is called. Thus, the process of instantiating an object calls a chain of constructors that provides essential initialization all the way up. Next we examine how Visual Basic identifies methods and the Visual Basic syntax for calling a constructor in the base class.

Overloading and Method Signatures

If you were to examine the interface of the String class, you would discover that it has not one but three constructors.

Public Sub New(Char())
Public Sub New(Char, Integer)
Public Sub New(Char(), Integer, Integer)

We've always been careful to avoid declaring duplicate identifiers in our programs, yet the String class has three constructors all with the same name. How is this possible? In the case of methods, Visual Basic uses more than just the name to identify them; it also uses the parameter list. A method's name, the number and type of parameters that are passed to it, and the arrangement of the different parameter types within the list, combine into what Visual Basic calls the signature of the method.

Signature
The distinguishing features of a method heading. The combination of the method name with the number and type(s) of its parameters in their given order

Visual Basic allows us to use the name of a method as many times as we wish, as long as each one has a different signature. When we use a method name more than once, we are overloading its identifier. The Visual Basic compiler needs to be able to look at a method call and determine which version of the method to invoke. The three constructors in the String class all have different signatures; one takes a Char array as an argument, the second takes a single Char and an Integer, and the third version takes a Char array and two integers. Visual Basic decides which version to call according to the arguments in the statement that instantiates a String object.

Overloading
The repeated use of a method name with a different signature

The following method headings have different signatures and thus overload each other.

Public Sub someName(param1 As Integer, param2 As Integer, param3 As Integer)
Public Sub someName(param1 As Integer, param2 As Double, param3 As Integer)
Public Sub someName(param1 As Double, param2 As Integer, param3 As Integer)
Public Sub someName(param1 As Integer, param2 As Integer, param3 As String)

Even though the parameters all have the same names, the differences in their types enable Visual Basic to distinguish between them. For example, the statement

someName(1, 2R, 3)

calls the second version because it has an Integer value, a Double value, and another Integer value as its arguments. If we write the call as

someName(1R, 2, 3)

then the Double, Integer, Integer pattern of its arguments identifies the third version of the method as the target of the call.

The following method headings all have the same signature, and cannot be declared together in a class. Their signature is the method name (aName) and the presence of three parameters of type Integer, Double, and String in that order.

Public Sub aName(param1 As Integer, param2 As Double, param3 As String)
Public Sub aName(large As Integer, medium As Double, small As String)
Friend Sub aName(red As Integer, green As Double, blue As String)
Public Function aName(thing1 As Integer, thing2 As Double, _
                      hatCat As String) As Integer

Keep in mind that the types of the parameters determine the signature of a method. The names of the parameters, the return type, and the modifiers of a method are not part of its signature.

Overloading is related to but different from hiding and overriding. Hiding and overriding are mechanisms whereby a name is replaced when the same name is declared in a new context. For example, an instance method identifier overrides a method with the same name in its base class if it has the same signature. The declaration in the derived class results in an identifier that duplicates the one in the base class. But because they are in different classes, the duplication is acceptable. When we use the identifiers, we indicate which one we mean. An instance identifier is associated with a specific object of one of the other class types, and a class identifier is preceded with the name of the class and a period.

If we declare a method with the same name as an inherited base class method, but their signatures are different, then the new method overloads the name of the base class method. The two identifiers are the same, but the different signatures allow them to be distinguished. Different signatures also serve to distinguish overloaded methods within the same class. The Visual Basic compiler decides which version to call by comparing the types of the arguments with the types of the parameters in each signature.

To summarize, overloading allows us to add new versions of a name that can exist together in a single context, while hiding and overriding provide a way to replace a name with a new declaration in a different context.

Accessing Overridden and Hidden Methods and Fields

Suppose you need to extend the Form1 class so that, when a window is closed, a confirmation is requested of the user. The interface of this derived class is the same as the base class, except that you are overriding the Dispose instance method with a new version. Within your version of Dispose, you present the message to the user, handle the event that signals the user has acknowledged the message, and then Dispose of the form if the user has confirmed the closing.

There's just one problem with this approach. To actually Dispose of the form, you need to call the Dispose method that's defined by the base class. But you just overrode Dispose with a local definition that has the same signature. If it were a class method, you could access the hidden form by writing the full name of the method: the base class name, a period, and the method name. That is, if Dispose were a class method, you could write

Form1.Dispose()

to access its hidden form. But Dispose is an instance method, and there's no instance (object) of class Form1 to refer to.

Visual Basic solves this problem by providing the keyword MyBase, which refers to the base class (also called the superclass) of a derived class. We write

MyBase.Dispose()

to refer to the Dispose method in Form1 from within a derived class that overrides Dispose. We can also use MyBase as an alternative to naming the base class when referring to a hidden method or field.

The reference to MyBase.Dispose() invokes the method from the base class just like any other instance method inherited by the derived class. Our example illustrates an important point regarding hiding and overriding: All the fields and methods from the inheritance interface of the base class are inherited, but some of them are covered up by new declarations in the derived class. In effect, they are still there, and MyBase simply provides a way to uncover them for a particular reference.

Using MyBase followed by a parameter list refers to the constructor in the base class with the same signature.

For example, in a class derived from String, we can write:

myValue = MyBase(Letters, 25)

to call the version of the String constructor that has a character array and an integer as its two parameters. We would write

myValue = MyBase()

to explicitly call the default constructor for the base class. Recall that the constructor is not inherited. Thus, even though it has not been overridden, we can't call it by name. The statement

myValue = new String(Letters, 25) ' Invalid reference

is invalid if written in a class derived from String.

Keep in mind that there are only two cases where you have to use MyBase or the name of the base class:

  1. To access a method or field that has been overridden or hidden

  2. To access a base class constructor

Otherwise, the name has been inherited and you can refer to it directly.

There is one other situation in which hiding can occur. A local declaration that takes name precedence over a class definition is said to hide the class declaration. As we have already seen, in this situation we use the keyword Me to refer to the class declaration.

Before we move on to a case study, there is one more technique that we would like to introduce. In Chapter 7 we mentioned that it is sometimes useful to create constructors that take an existing instance and use its contents to build a new instance. These are called copy constructors and we use them to illustrate the difference between shallow and deep copying of objects.


8.7 Copy Constructors

One way to simplify the creation of a new instance of an immutable object from an old one is to provide a constructor that takes an existing object as a parameter. It may also have other parameters that take values to substitute in creating the new object. Here's an example of the heading and documentation for such a constructor.

Public Sub New(oldAcct As SavingsAccount, address As String)
' oldAcct must contain valid account information
' A SavingsAccount object is created with its
' contents equal to those of the old account except that
' the address is set equal to the address parameter

Here's an example of how it would be called, where oldAccount is an existing object of type SavingsAccount and address is a string holding a new address value for the account.

Dim account as new SavingsAccount(oldAccount, address)

The body of the constructor would copy every field from oldAccount to its own equivalent fields, substituting the value in its address parameter for the one in the address field of oldAccount. Note that if any of the fields in the object are themselves objects, it is necessary to use their observers to copy the actual values from the fields of the nested objects. Otherwise, the new object simply refers to the same places in memory as oldAccount.

Deep copy
An operation that copies one class instance to another, using observer methods as necessary to eliminate nested references and copy only the simple types they refer to. The result is that the two instances do not contain any duplicate references.
Shallow copy
An operation that copies a source class instance to a destination class instance, simply copying all references so that the destination instance contains duplicate references to values that are also referred to by the source.

This copy constructor is said to perform a deep copy of the object. A deep copy copies what a reference refers to, rather than the reference itself. A shallow copy ignores the presence of nested objects, treating them like fields of simple types. Figure 8.4 illustrates shallow copying and Figure 8.5 shows deep copying. In these figures, black arrows represent assignment operations and colored arrows are references.

Click To expand
Figure 8.4: Shallow copying
Click To expand
Figure 8.5: Deep copying
Start Sidebar
Problem-Solving Case Study
Extending TextBox for Numeric Input and Output

Problem Reading a numeric value from a field requires us to perform a type conversion to the numeric data type from String, which is the data type of a TextBox. It would be much nicer to have a TextBox object that specifically handles numeric input, automatically doing the conversion for us. The nearest base class is type TextBox. We can extend it with a set of methods, each of which reads the TextBox and returns a value of a specific numeric type.

Brainstorming The Visual Basic .NET Framework library documentation tells us that TextBox extends TextBoxBase, which extends System.ComponentModel.Component, which extends System.Object. To study the interface of our base class, we thus work our way upward through this hierarchy. We showed this process earlier in the chapter, so we won't repeat the analysis here. Recall that we found that TextBox has access to over 100 methods.

Filtering Of all the methods and properties in the TextBox hierarchy, there are two properties that we need to examine closely because they deal with input and output: Text and SelectedText. We have used the Text property before; SelectedText returns the part of the string (or the complete string) that the user has highlighted within the textbox. We should add equivalent methods that handle numeric values. The remaining methods in the class are not related to our extension of the class.

For numeric input, we would like our derived class to have responsibilities similar to these three for each of the numeric types—for example, getInt, setInt, and getSelectedInt. Rather than add responsibilities for every numeric type, here we simply add the ones for the Integer and Double types. We leave it as an exercise to develop the responsibilities for Visual Basic.NET's remaining numeric types. Thus, we have six responsibilities in our Numeric-Field class. The responsibilities that get values are observers, and those that set the value in the field are transformers.

We also must provide constructors for our new class. The base class has just the default constructor. Because we want the textbox to contain only numbers, we should supply a constructor for creating the textbox with an initial value of the appropriate numeric types.

Because we are extending an existing class in a specified manner, we don't need to walk through any scenarios. However, if we were creating this extension for real application, then we would need to consider its use. We can identify its collaborations simply by noting that each of our responsibilities needs to use the corresponding methods in TextBox. Here's the CRC card for our new class:

Click To expand

Responsibility Algorithms Now that we've designed the method interfaces, let's design their algorithms. We begin with the constructors. The first form of our constructor takes no parameters and relies on the default size established by TextBox. We thus create it by calling the corresponding constructor for TextBox, by calling MyBase.New, as shown below. Note that this satisfies Visual Basic.NET's requirement that each constructor call a constructor for its base class before doing anything else.

...........................................................................
Public Sub New
 MyBase.New

The next constructor takes an integer value and stores the value in the textbox's Text property. Me refers to the current, instantiated object.

...........................................................................
Public Sub New(byval value As Integer)
 Me.Text = value

The final constructor does the same thing as the last constructor, this time with a double value.

...........................................................................
Public Sub New(byval value As Double)

 Me.Text = value

The remaining methods use the corresponding methods from the base class or get or set the text in the field, but with appropriate conversion to or from a numeric type.

...........................................................................
getInteger

 Return Me.Text converted to Integer

 ...........................................................................
setInteger

  Me.Text = value

 ...........................................................................
getSelectedInteger

  Return Me.SelectedText() converted to Integer

 ...........................................................................
getDouble

  Return Me.Text converted to Double

...........................................................................
setDouble

  Me.Text = value

 ...........................................................................
getSelectedDouble

 Return Me.SelectedText() converted to Double

Now we're ready to code the implementation of our class. In the comments, we note that if the user enters a string that's an invalid number, an error occurs. In Chapter 10, we show how you can handle this error.

Public Class numericField
   Inherits TextBox
   ' This class provides a set of methods for directly getting
   ' and setting numeric values in a TextBox derived object.
   ' It does not check for number format exceptions. No TextBox
   ' methods are overridden. Only Integer and Double are supported.

   ' Constructors
   Public Sub New()        ' Default constructor
      MyBase.New()
   End Sub

   Public Sub New(ByVal value As Integer) ' TextBox with Integer value
      Me.Text = value
   End Sub

   ' TextBox with Double value
   Public Sub New(ByVal value As Double)
      Me.Text = value
   End Sub

   ' Get Integer from TextBox
   Public Function getInteger() As Integer
      Return CInt(Me.Text)
   End Function

   ' Change Integer value in TextBox
   Public Sub setInteger(ByVal value As Integer)
      Me.Text = value
   End Sub

   ' Get Integer from user selection
   Public Function getSelectedInteger() As Integer
      Return CInt(Me.SelectedText())
   End Function

   ' Get Double from TextBox
   Public Function getDouble() As Double
      Return CDbl(Me.Text)
   End Function

   ' Change Double in TextBox
   Public Sub setDouble(ByVal value As Double)
      Me.Text = value
   End Sub

   ' Get Double from user selection
   Public Function getSelectedDouble() As Double
      Return CDbl(Me.SelectedText())
   End Function

End Class

To test this class, we must import it into a driver that can call its methods.

End Sidebar


Testing and Debugging

As we noted in our discussion of the driver for class Name in Chapter 7, when we test a class we can usually test multiple methods with one driver to reduce the amount of user interface code that we have to write. For the NumericField class, there are three constructors and six methods to test. Because each of the methods is independent of the others, there are a total of 10 tests to be performed. When a class has methods that can affect each other's behavior, then we need to test the combinations of methods as well. For example, if one method stores a value in a textbox of the class that another method then uses, we have to test the combination of these two methods.

Each method is tested to verify that it satisfies its responsibilities if it is called with the proper inputs. We may also test a method to verify its operation when its inputs are erroneous. For example, we may check that a method throws an appropriate exception when it is given invalid data. When a method contains a branch, then we must apply the testing strategies for that control structure to the method just as we would apply them to an application.

The driver is responsible for setting up the proper conditions prior to each method call and reporting the conditions after it returns. It supplies arguments or initializes an object as necessary, and after the call it outputs the results from the method. In some cases, the output is simply a statement that the responsibilities were satisfied. In other cases it is more useful to display the actual return value or fields of the object. It is up to you to decide what to output, but in general the more information that you have, the easier it is to identify errors.

To test the NumericField class, we need a driver that first declares and initializes an object of that class. We must also provide a form to display the textbox and some labels to provide instructions to the user and to show the results of the method calls. Each of the methods has a single line of code in its body, so there are no control structures to test. We simply call each one to either display a value in the textbox or to get a value from the textbox.

We can create a separate NumericField object for each test case or we can create a single object and use it in multiple cases. We chose the latter here because it simplifies the user interface to just have one data entry textbox in the form. The textbox is placed on the form using code because we don't have all the procedures necessary for creating a custom control that is accessible from the toolbox. That code is described separately below. We will perform the testing in a button's Click event in order to allow the user to signal that the textbox's contents are ready.

We want to perform a series of tests that together cover all of its methods. Each test involves handling a user event. We use the same event (pressing a button) for all of the tests, so within the button's Click event we need a way to determine which test is being performed. We can declare an Integer variable, testNumber, in the program that keeps track of the test number between events. The variable is initialized to 1 when the form is initialized, and at the end of each test we assign it the number for the next test. The Click event then contains an If-Then-ElseIf control structure that branches to the appropriate test depending on testNumber.

Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button1.Click

   If (testNumber = 1) Then
      ' Perform first test
   ElseIf (testNumber = 2) Then
      ' Perform second test
   ElseIf (testNumber = 3) Then
      ' Perform third test
   ' And so on

End Sub

We can combine testing of more than one method into a single branch in this control structure. For example, we can use setInteger to place a number in the textbox in preparation for testing getSelectedInteger. We would thus combine testing of getInteger and setInteger in a single branch, followed by a separate test of getSelectedInteger.

The typical form of an individual test is getting a value from the textbox, displaying it in a label, and setting testNumber to the next test. The different tests vary this pattern slightly but are all very similar. Testing NumericField is very straightforward because we have added methods that are very similar to each other, which is fairly common in extending a class.

You can place a control on a form through code very simply. The first thing to do is to determine where to place the textbox on the form. Do this by actually placing a textbox on the form and copying down the coordinates found in the Location property. Then, add the following lines of code to your program:

testField.Location = New Point(184, 8)
Me.Controls.Add(testField)

The Point object defines a row and column coordinate on the form. The second line adds the control to the collection of controls that are part of the form and allows the control to be displayed on the form.

Here is the complete driver for testing class NumericField.

Public Class Form1
   Inherits System.Windows.Forms.Form
   Dim intAmount As Integer
   Dim doubleAmount As Double
   Dim testField As New numericField()
   Friend WithEvents entryLabel As System.Windows.Forms.Label
   Friend WithEvents outputLabel As System.Windows.Forms.Label

#Region " Windows Form Designer generated code "

   Public Sub New()
      MyBase.New()

      ' This call is required by the Windows Form Designer.
      InitializeComponent()
      ' Position testField on Form
      testField.Location = New Point(275, 8)
      Me.Controls.Add(testField) ' Display testField

      ' Add any initialization after the InitializeComponent() call

   End Sub

   ' Form overrides dispose to clean up the component list.
   Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
         If Not (components Is Nothing) Then
            components.Dispose()

         End If
      End If
      MyBase.Dispose(disposing)
   End Sub
   Friend WithEvents Button1 As System.Windows.Forms.Button

   ' Required by the Windows Form Designer
   Private components As System.ComponentModel.Container

   ' NOTE: The following procedure is required by the Windows Form Designer
   ' It can be modified using the Windows Form Designer.
   ' Do not modify it using the code editor.
   <System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
      Me.Button1 = New System.Windows.Forms.Button()
      Me.outputLabel = New System.Windows.Forms.Label()
      Me.entryLabel = New System.Windows.Forms.Label()
      Me.SuspendLayout()
      '
      ' Button1
      '
      Me.Button1.Font = New System.Drawing.Font("Microsoft Sans Serif", 9.75!,
System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
CType(0, Byte))
      Me.Button1.Location = New System.Drawing.Point(16, 48)
      Me.Button1.Name = "Button1"
      Me.Button1.TabIndex = 0
      Me.Button1.Text = "Enter"
      '
      ' outputLabel
      '
      Me.outputLabel.Font = New System.Drawing.Font("Microsoft Sans Serif",
9.75!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
CType(0, Byte))
      Me.outputLabel.Location = New System.Drawing.Point(120, 48)
      Me.outputLabel.Name = "outputLabel"
      Me.outputLabel.Size = New System.Drawing.Size(264, 24)
      Me.outputLabel.TabIndex = 3
      Me.outputLabel.Text = "Press Enter when input is ready"
      '
      ' entryLabel
      '
      Me.entryLabel.Font = New System.Drawing.Font("Microsoft Sans Serif",
9.75!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
CType(0, Byte))
      Me.entryLabel.Location = New System.Drawing.Point(8, 8)

      Me.entryLabel.Name = "entryLabel"
      Me.entryLabel.Size = New System.Drawing.Size(264, 24)
      Me.entryLabel.TabIndex = 1
      Me.entryLabel.Text = "Enter a real number here:"
      '
      ' Form1
      '
      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
      Me.ClientSize = New System.Drawing.Size(432, 101)
      Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.outputLabel,
Me.entryLabel, Me.Button1})
      Me.Name = "Form1"
      Me.Text = "NumericField"
      Me.ResumeLayout(False)

   End Sub

#End Region

   Private Sub Button1_Click(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles Button1.Click
      Static testNumber As Integer = 1
      If (testNumber = 1) Then
         ' Convert string in testField to a double value and
         ' display
         doubleAmount = testField.getDouble()  ' Test getDouble
         outputLabel.Text = "You input: " & doubleAmount ' Show results
         testField.setDouble(doubleAmount)     ' Test setDouble
         ' Show instructions
         entryLabel.Text = "Select part of the field " & _
                           "and press Enter"
         ' Set up for test 2
         testNumber = 2
      ElseIf (testNumber = 2) Then
         ' Convert selected string in testField to a double
         ' value and display
         ' Test getSelectedDouble
         doubleAmount = testField.getSelectedDouble()
         outputLabel.Text = "You input: " & doubleAmount ' Show results
         testField.Text = "" ' Clear field
         ' Show instructions
         entryLabel.Text = "Type an integer and press Enter"
         ' Set up for test 3
         testNumber = 3
      ElseIf (testNumber = 3) Then
         ' Convert string in testField to an Integer value

         ' and display
         intAmount = testField.getInteger() ' Test getInteger
         outputLabel.Text = "You input: " & intAmount ' Show results
         testField.setInteger(intAmount)
         ' Show instructions
         entryLabel.Text = "Select part of the field and " & _
                           "press Enter"
         ' Set up for test 4
         testNumber = 4
      ElseIf (testNumber = 4) Then
         ' Convert selected string in testField to an Integer
         ' value and display
         ' Test getSelectedInt
         intAmount = testField.getSelectedInteger()
         entryLabel.Text = "You input: " & intAmount ' Show results
         ' Show instructions
         testField.Visible = False
         outputLabel.Text = "End of test. Please close the window."
      End If
   End Sub
End Class
Public Class numericField
   Inherits TextBox
   ' This class provides a set of methods for directly getting
   ' and setting numeric values in a TextBox derived object.
   ' It does not check for number format exceptions. No TextBox
   ' methods are overridden. Only Integer and Double are supported.

   ' Constructors
   Public Sub New()        ' Default constructor
      MyBase.New()
   End Sub

   Public Sub New(ByVal value As Integer) ' TextBox with Integer value
      Me.Text = value
   End Sub

   ' TextBox with Double value
   Public Sub New(ByVal value As Double)
      Me.Text = value
   End Sub

   ' Get Integer from TextBox
   Public Function getInteger() As Integer
      Return CInt(Me.Text)
   End Function

   ' Change Integer value in TextBox
   Public Sub setInteger(ByVal value As Integer)
      Me.Text = value
   End Sub

   ' Get Integer from user selection
   Public Function getSelectedInteger() As Integer
      Return CInt(Me.SelectedText())
   End Function

   ' Get Double from TextBox
   Public Function getDouble() As Double
      Return CDbl(Me.Text)
   End Function

   ' Change Double in TextBox
   Public Sub setDouble(ByVal value As Double)
      Me.Text = value
   End Sub

   ' Get Double from user selection
   Public Function getSelectedDouble() As Double
      Return CDbl(Me.SelectedText())
   End Function

End Class

Here is the series of dialog boxes that appears on the screen when the test is executed.

Click To expand
Click To expand

Note that we tested just one of the constructors in our driver program. You can test the other constructors by modifying the statement

Dim testField As New NumericField(10)

in the program to call a different constructor and recompile and run the program. Then repeat the process for each of the constructors. An alternative would be to create a separate driver that simply declares four different numeric text boxes, each created by a different constructor. Here's how the statements could be written.

Dim testField1 As New NumericField()
Dim testField2 As New NumericField(10)
Dim testField3 As New NumericField(1234)
Dim testField4 As New NumericField(12.34)

Once a class has been thoroughly tested, it can be used in many different programming projects. One of the major goals of object-oriented design is to enable the reuse of code in multiple programs. But if a class has an error in it, then the error gets transferred to every program that uses it. You would be very upset if you built a program on top of a class that has written by someone else, only to discover that the class has bugs that make your program fail. Be sure to test your classes thoroughly so that you don't cause similar problems for other programmers!

Testing and Debugging Hints

  1. Study the entire hierarchy for a class before you try to use or extend it. Watch out for cases of overriding and hiding that can change the semantics of a class member.

  2. An Abstract class cannot be instantiated as an object; a derived class must be written to use it.

  3. Overriding applies to instance methods, hiding applies to class methods. You cannot hide an instance method with a class method or override a class method with an instance method.

  4. When you write a class that extends a base class, be sure that the compilation unit (program) imports the base class if it isn't one of the standard ones that is always imported (such as System).

  5. The access modifiers for a method that overrides or hides a method in a base class must grant the same level of access or greater. The most restrictive form of access is Private, then Protected, then Public. You can override a Private method with a Public one, but you can't use Private to override a Public method.

  6. Be sure that the first statement of every constructor is a call to one of the base class constructors. If you omit the call, Visual Basic.NET automatically inserts a call to MyBase.New().

  7. Overloading a method requires that the two methods have a different signature. The signature is the name of the method plus the types of its parameters in their specific order. The return type and modifiers are not part of the signature.

  8. Use Me to access fields or methods of a base class that have been hidden or overridden by a derived class.

  9. Use Me to access class fields that have been hidden by a local declaration.

  10. Thoroughly document the interface of a class to facilitate its proper use, ensure its correct design, and to simplify testing.


Summary of Classes

Package Name
Class Name |  |  | Comments
numericField |  |  |
 | NumericField extends TextBox |  |
 | Constructors: |  |
 |  | New() |
 |  | New(Integer) |
 |  | New(Double) |
 | Value-Returning Instance Methods: |  |
 |  | getInteger() | Returns Integer
 |  | setInteger() |
 |  | getSelectedInteger() | Returns Integer
 |  | getDouble() | Returns Double
 |  | setDouble() |
 |  | getSelectedDouble() | Returns Double


Summary

Object-oriented languages such as Visual Basic organize class data types into a hierarchy. At the top of Visual Basic's hierarchy is a class called System.Object that provides a few basic operations. Using inheritance, other classes extend System.Object and are said to be derived from it. Derived classes inherit all of the public and protected fields and methods of their base class except for its constructors. We must explore the entire inheritance hierarchy for a class to determine its full interface.

Instance methods can override base class instance methods, and class methods and fields can hide base class methods and fields. Overriding and hiding provide ways for us to change the meaning of a method or field when extending a base class with a derived class. The derived class can thus retain the same form of interface, but operate in a different manner.

Constructors are not inherited by derived classes. The first statement in a constructor must be a call to a constructor for the base class; otherwise, Visual Basic calls the base class constructor implicitly.

Visual Basic allows us to declare multiple methods with the same name as long as they have different signatures. The method name is then said to be overloaded. The signature is the combination of the method name and the types of its parameters in their given order. The return type and modifiers are not part of the signature. Visual Basic determines which version of an overloaded method to call by examining the types in the argument list and selecting the method with the matching parameter list.

Sometimes we need to access a method or field in a base class that has been overridden or hidden. We can use the MyBase keyword to refer to the base-class version of a field or method instead of the local version. Similarly, a method can define a local variable or parameter with the same name as a field in the class and we can use Me to refer to the class version instead of the local version.

Scope rules determine the range of code that has access to an identifier. Internal scope rules specify where class members can be accessed within a class. In Visual Basic, members can be used anywhere within a class except that class variables must be defined before they are used to initialize other variables, and local variables can override class variables (name precedence).

External scope specifies where a member can be accessed outside of a class. Visual Basic provides five levels of external access. The default level is Namespace, and extends access to all classes in the same namespace. With Protected access, derived classes are also able to access a member. A member with Public access can be used by any code that imports the class. The Private access level restricts access to only the class containing the member.

In this chapter we have learned how to declare derived classes. The advantage of a derived class is that we have the interface from the base class as the model for our design. We benefit from inheriting many fields and methods that give our derived class powerful capabilities with no extra work on our part. In some cases, however, a new object is sufficiently different from anything else that exists in the standard hierarchy that we have to design it entirely from the ground up. Now that you know how to design and implement both derived classes and top-level classes, you are prepared to explore a wide range of useful and general abstractions that computer scientists have identified as fundamental to the development of more advanced algorithms. The remainder of this text primarily examines some of these abstractions and their implementation.


Quick Check

Q: | 1. What is the most general class in Visual Basic? (p. 310) |
Q: | 2. What is the mechanism that allows one class to extend another class? (pp. 309–310) | 2. inheritance
Q: | 3. What do you call a class that is an extension of another class in the hierarchy? (pp. 310–311) | 3. derived class
Q: | 4. Overriding refers to __________ methods; hiding refers to fields and __________ methods. (pp. 313–314) | 4. instance, class
Q: | 5. What kinds of members are not accessible to a derived class? (p. 315) | 5. Private
Q: | 6. What is an operation that has multiple meanings depending on the object to which it is applied? (p. 324) | 6. a polymorphic operation
Q: | 7. Are constructors inherited? (p. 323) | 7. Class constructors are not inherited.
Q: | 8. An object of a __________ class can be assigned to an object of its __________ class. (p. 323) | 8. derived, MyBase
Q: | 9. Explain the meaning of the keyword Inherits. (pp. 314–315) | 9. Inherits tells the compiler the class from which this class is being derived.
Q: | 10. What do we call the rules that determine where in a program that an identifier can be recognized? (pp. 316–317) | 10. scope rules
Q: | 11. What keyword refers to a class's base class? (pp. 323–324) | 11. MyBase
Q: | 12. What keyword refers to hidden class members (pp. 323–324) | 12. Me

Answers

A: | 1. System.Object
A: | 2. inheritance
A: | 3. derived class
A: | 4. instance, class
A: | 5. Private
A: | 6. a polymorphic operation
A: | 7. Class constructors are not inherited.
A: | 8. derived, MyBase
A: | 9. Inherits tells the compiler the class from which this class is being derived.
A: | 10. scope rules
A: | 11. MyBase
A: | 12. Me


Exam Preparation Exercises

    Chapter 8

    1. Name two kinds of scope.

    2. Do both internal and external scope depend on the access modifiers of an identifier?

    3. Define internal scope.

    4. What are the two exceptions in 1c?

    5. What is name precedence?

    6. Is it legal to define local variables with the same identifier in nested blocks?

  2. Name the three external places from which Visual Basic allows class members to be accessed.

    1. List the four levels of access for class members.

    2. Which of the four is the default access?

    3. From where can a Public member be accessed?

    4. From where can a member with no access modifier be accessed?

    5. From where can a Private member be accessed?

    6. From where can a Protected member be accessed?

    1. What nonpublic members should be part of the interface?

    2. How do you make them part of the interface?

  5. What happens if we forget to include a constructor in our new class?

    1. Distinguish between a deep copy and a shallow copy.

    2. Under what conditions are a deep copy and a shallow copy the same?

  7. To what class can all Visual Basic objects trace themselves back?

  8. What is the modifier of a class or field that indicates that it is incomplete?

  9. What does the inheritance mechanism allow one class to acquire from another?

  10. What do you call the class that is extended by a derived class?

  11. When we examine a derived class, we have access to more than just the methods and fields defined in the class. Explain.

  12. What happens if a derived class defines an instance method with the same name as a data field in the superclass?

  13. What happens if a derived class defines a data field with the same name as a data field in the base class?

  14. Distinguish between overriding and hiding.

  15. Is it possible to remove a member that is inherited?

  16. Overloading, overriding, and hiding are similar, yet different. Fill in the following table showing whether the sentence describes overloading, overriding, or hiding.

    Situation

    Hiding

    Overriding

    Overloading

    A class method has the same name and signature of a base class method.

    An instance method has the same name and signature of a base class instance method.

    A class has two methods with the same name but different signatures.

    A field in a derived class has the same name as a field in its base class.

    An instance method has the same name but a different signature of a base class instance method.

    A method declares a field with the same name as a field in the class.

    A method has a formal parameter with the same name as field in the class.

  17. What parts of a base class's interface cannot be inherited?


Programming Warm-Up Exercises

    Chapter 8

  1. Declare three constructors for class MyClass.

  2. Fill in the blanks in the documentation in the following code segment.

    Public Class MyName
            Inherits YourName
   Public myField As Integer     ' myField is a __________ field.
   Public Sub New(myField As Integer)
      ' __________ with a parameter that hides the __________ __________
      ' Assign the __________ to the __________ field.
      Me.myField = myField
   End Sub
End Class

    1. How does the syntax of a constructor differ from that of an ordinary method?

    2. How is a constructor invoked?

    3. How many constructors can a class have?

    4. What is the signature of a method?

    5. What happens if a class does not have a constructor?

    6. What must be the first statement in every constructor?

    1. Declare a Public class SomeClass.

    2. Write the heading for a Public class method someMethod.

    3. Write the heading for an integer class method someMethod that should be accessible by the classes in the namespace but not to derived classes.

    4. Write the heading for an integer class method someMethod that should be accessible only to other methods in the class.

    5. Write the heading for a character class method someMethod that should be accessible by classes in the namespace and any derived classes.

  5. Is the following code segment correct? If so, to what does each reference to var refer?

    Public Class someClass
   Public var As Integer                 ' Class member var
   Public const final As Integer         ' Class member final
   Public Sub someMethod(param As Integer)
      Dim var As Integer
      var = param * final;
      const final As Integer = 10
      var = 5
      Label1.Text = "" & Me.var
   End Sub
End Class

  6. Examine the following constructor headings and list the signature for each.

    Public Class someClass
   Public Sub New()
   Public Sub New(Integer)
   Public Sub New(Double)
   Public Sub New(String, Integer, Double)
End Class

  7. Examine the following method headings and list the signature for each. Can this set of signatures be declared in the same class?

    Public Function someMethod() As Integer
Public Sub someMethod()
Public Function someMethod() As Double
Public Function someMethod(Integer) As Double
Public Function someMethod(String) As Double
Public Function someMethod(Integer, Integer) As Double

  8. Are these code segments correct? If not, why not?

    1. Public taxRate As Double = 29.3
Public myRate As Double = taxRate * 1.1

    2. Public myRate As Double = taxRate * 1.1
Public taxRate as Double = 29.3


Programming Problems

  1. Take the CRC cards used to design the Address hierarchy and complete the design and implementation of a namespace that contains the five classes. Design and implement a test plan for the namespace.

  2. Design and implement an application that creates an electronic address book. Use the Name class from Chapter 7 and the HomeAddress class from this chapter. The application prompts the user to enter a name and an address. When the name has been entered and verified by the user, it is written to a file. Design and implement a test plan for the application.

  3. Design, implement, and test a class that represents a telephone number. There should be fields for the local number, the area code, and the country code.


Case Study Follow-Up Exercises

    Chapter 8

  1. To extend class NumericField to handle values of type Single, what new methods would you need to declare?

  2. Would you need to add a constructor?

  3. Write the interface for your new methods.

  4. Implement and test your new methods.


Chapter 9: File I/O and Looping

Overview

Goals

  • To be able to recognize when noninteractive input/output is appropriate and describe how it differs from interactive input/output.

  • To be able to write applications that use data files for input and output.

  • To be able to construct syntactically correct While loops.

  • To be able to construct count-controlled loops with a While statement.

  • To be able to use the end-of-file condition to control the input of data.

  • To be able to use sentinels to control the execution of a While statement.

  • To be able to construct counting loops with a While statement.

  • To be able to construct summing loops with a While statement.

  • To be able to choose the correct type of loop for a given problem.

  • To be able to choose data sets that test a looping application comprehensively.

In Chapters 1 through 8, we have used interactive input. We have written our programs so that the user could enter more than one set of data values by continuing to push a button indicating input data is ready to be read; that is, we have used events to control data input. This strategy works well when the data sets are small. However, there are times when the data sets are too large to be entered in this fashion, so they are stored on a file. In this chapter we examine how to read data into our program that has been previously stored in a file.

Entering data from a file leads to the need for an additional control structure that allows us to explicitly repeat a process rather than relying on a user-initiated event to control the processing.


9.1 File Input and Output

In everything we've done so far, we've assumed that input is via the user interface on the screen. We have used both the MsgBox function and the user interface to send messages to the screen. We look now at input/output (I/O) with files.

Files

Earlier we defined a file as a named area in secondary storage that holds a collection of information (for example, the program code we have typed into the editor). The information in a file usually is stored on an auxiliary storage device, such as a disk (see Figure 9.1).

Click To expand
Figure 9.1: Disks used for file storage

Reading and writing data in files is a different process than input and output on the screen. A file contains a sequence of values and has a distinct beginning and end, that is, a first value and a last value. You may think of reading a file as analogous to reading a book in that it is read from the front to the back. Just as you might use a bookmark to keep track of where you are in a book, Visual Basic uses something called a file pointer to remember its place in a file. Each time some data is read from the file, the file pointer advances to the point where reading should next resume. Each read operation copies a value from the file into a variable in the program and advances the file pointer. A series of read operations eventually reads the last value in the file, and the pointer is said to be at end-of-file (EOF).

Writing data to a file is like writing in an empty notebook. At the beginning, the file is empty, and then data is written into it from front to back. The file pointer always indicates the end of the last value written so that writing can resume after that point. In effect, the file pointer is always at EOF when a file is being written. The size of the file increases with each write operation. The only limit to the size of a file is the amount of disk space available to the program.

Why would we want a program to read data from a file instead of the user interface? If a program is going to read a large quantity of data, it is easier to enter the data into a file with an editor than to enter it while the program is running. With the editor, we can go back and correct mistakes. Also, we do not have to enter the data all at once; we can take a break and come back later. And if we want to rerun the program, having the data stored in a file allows us to do so without retyping the data.

Why would we want the output from a program to be written to a disk file? The contents of a file can be displayed on a screen or printed. This gives us the option of looking at the output over and over again without having to rerun the program. Also, the output stored in a file can be read into another program as input. For example, the Payroll program writes its output to a file named payFile. We can take payFile and read it into another program, perhaps one that prints out paychecks.

Using Files

If we want a program to use file I/O, we have to do five things:

  1. Import the namespace System.IO.

  2. Use declaration statements to declare the file variable identifiers we are going to use.

  3. Associate a file object with each variable.

  4. Use methods associated with the file objects to read or write data.

  5. Call a method to close the files when we are through with them.

Import the Namespace System.IO The first thing we must do any time we want to read from or write to a file is import the namespace containing the classes that define files.

Import System.IO

Through the namespace System.IO, Visual Basic defines many classes for different forms of I/O. In the case of our programs we use just three of these classes—File, StreamReader, and StreamWriter. The File class provides methods for opening files. The StreamReader class represents streams of characters coming from an input file. StreamWriter represents streams of characters going to an output file.

Declare the File Identifiers In a program, you declare the file identifiers the same way that you declare any variable:

Dim theFile As File
Dim outFile As StreamWriter
Dim inFile As StreamReader

Note that StreamReader is for input files only, and StreamWriter is for output files only. With these classes, you cannot read from and write to the same file. The classes StreamReader and StreamWriter allow us to write both individual characters and whole lines of text to files. Being able to read or write individual characters is useful when we need to control every aspect of input or output on a character by character basis.

The data in character stream files are organized into lines. A line is a string of characters that ends with a special character called an end-of-line (EOL) mark. When you examine such a file with an editor, each line in the file appears as a line of text on the screen. The editor doesn't display the EOL mark. The EOL mark simply tells the editor when to go to the next line as it places the characters on the screen.

Associate a File Object with Each File Variable The third thing we have to do is to associate file objects with each file variable. This involves telling the operating system the name of the file on the disk. Thus, we pass the file's name on the disk to a method that creates a StreamReader or StreamWriter object. This way, Visual Basic and the operating system know which disk file to use when the program performs an operation on the file.

For example, we can write these statements:

outFile = theFile.CreateText("outfile.dat")
inFile = theFile.OpenText("infile.dat")

Exactly what do these statements do? They create file objects for use in your program and associate them with physical files on your disk. The object is then assigned to a file variable that you can reference in other statements in your program. The first statement creates a connection between the file variable outFile and the disk file named outfile.dat. (Names of file variables must be Visual Basic identifiers. But many computer systems do not use Visual Basic syntax for file names on disk. For example, file names can include dots and slashes (/) but file variable identifiers cannot.) Similarly, the second statement associates the identifier inFile with the disk file infile.dat.

Each method you use performs additional operations depending on whether the file is an input file or an output file. With an input file, the OpenText method places the file pointer at the first piece of data in the file. (Each input file has its own file pointer.)

With an output file, the CreateText method checks to see whether the file already exists. If the file doesn't exist, it creates a new, empty file for you. If the file does exist, it erases the old contents of the file. Then the file pointer is placed at the beginning of the empty file (see Figure 9.2). As output proceeds, each successive output operation advances the file pointer to add data to the end of the file.

Click To expand
Figure 9.2: The effect of calling a constructor for a character stream file object

Because calling these methods creates a file object and prepares the file for reading or writing, you must call them before you can use any input or output methods that refer to the file. It's a good habit to call the methods early in the program to be sure that the files are prepared before any attempts to perform file I/O.

Public Class Form1
    Inherits System.WinForms.Form

    Dim inFile As StreamReader
    Dim outFile As StreamWriter
    Dim theFile As File
    Dim line As String

    Public Sub New()
        MyBase.New

        Form1 = Me

        ' This call is required by the Win Form Designer.
        InitializeComponent
        inFile = theFile.OpenText("c:\myfile.dat")
        outFile = theFile.CreateText("c:\outfile.dat")
⋮

Use Methods Associated with the File Object To Read or Write Data Once a file has been declared and instantiated, we are ready to use it. The main operation that we can perform on a file of class StreamWriter is to write a value onto it. The Write method can be passed any data type, including characters that are part of the Unicode character set. These characters have a numerical equivalent that can be accessed through the use of a function, Chr. This function, when passed an integer, will return that integer's equivalent Unicode character. (See Appendix D for a partial listing of the Unicode characters and their numerical values.)

Here are some examples of calls to Write. Recall from Chapter 2 that a literal of type Char is enclosed in quotes and is followed by the letter "c".

outFile.Write("X"c)
outFile.Write(Chr(88))     ' Code 88 = "X"c
outFile.Write("This is a string value.")

These statements write

XXThis is a string value.

into outFile. To separate values from each other in the output, we can explicitly insert blanks. For example, the statements

outFile.Write("X"c)
outFile.Write(" ")
outFile.Write(Chr(88))                    ' Chr(88) = "X"
outFile.Write(" ")
outFile.Write("This is a string value.")

produce the following output in the file:

X X This is a string value.

We can also separate values by explicitly inserting the EOL or new line character codes (Chr(13) & Chr(10)) as follows:

outFile.Write("X"c)
outFile.Write(Chr(13) & Chr(10))
outFile.Write(Chr(88))           ' Chr(88) = "X"
outFile.Write(Chr(13) & Chr(10))
outFile.Write("This is a string value.")

Recall from Chapter 2 that we said that when we want the computer to write out a new line, we pass it the character sequence Chr(13) & Chr(10), which corresponds to the EOL mark. The output from this code segment is:

X
X
This is a string value.

Some operating systems do not immediately write data to a file after each call to Write. Instead, they keep the data in an area of main memory called a buffer and write the data to the disk only when the buffer is full. The reason that this is done is to make the program run faster. A disk access is about 100,000 times slower than copying data between locations in memory. The operating system might save the data from 1,000 write operations in main memory before transferring the data to the disk, making the program run nearly 1,000 times faster.

Occasionally, we need to force the operating system to write a partially filled buffer to the disk. The StreamWriter class has a method called Flush that does this for us. For example, if a user is entering data slowly, it could take an hour to fill the buffer, and a power failure would lead to the loss of all the data in the buffer. We could write our program so that every 10 minutes it calls Flush to save the buffer's contents on the disk and thus reduce the potential for lost data. When we close the file, as discussed in the next section, the buffer is automatically flushed.

Now we turn our attention to file input. The StreamReader class has several methods we can use for input. The first of these is, reasonably enough, the Read method. Unlike the Write method, Read is a value-returning method. It is called with no parameters and returns an Integer value containing the code for a single Unicode character. If the file contains no more data when Read is called (the file pointer is at EOF), then Read returns the value -1, which is not a valid character code. As we will see later in this chapter, detecting when there is no more data to be read is an important aspect of working with files. Assuming that the variable datum is of type Integer, we can write a call to Read from the StreamReader object inFile as follows:

datum = inFile.Read()

When this statement executes, a single character is read from the disk file associated with inFile, converted to the Integer value in the Unicode character set that represents it, and then stored in datum. In addition, the file pointer advances to the next character in the file. If the character "M" had been written in the file, datum would contain the integer 77, because 77 is the character code for the letter M. If the digit 8 had been written in the file, datum would contain 56, the character code for an 8. If the value in datum is -1, then the file pointer is at EOF. If the value in datum is not -1, then it can be converted to type Char with an explicit type conversion. For example, if letter is of type Char, we can write:

letter = Chr(datum)

If datum contained 77, letter would contain "M". If datum contained 56, letter would contain "8".

A value that has been input with Read can be output to another file with Write. For example,

outFile.Write(datum)

places the character associated with datum on the disk file associated with outFile. That is, the value in datum is automatically converted to the character that it represents before writing it in the file.

Reading data from a file does not destroy the data in the file. But because the file pointer moves in just one direction, we cannot go back and reread data that has been read from the disk file unless we return the pointer to the beginning of the file and start over. As we see in the next section, closing a file and reassigning it to a file object causes the file pointer to return to the start of the file.

When you want to skip over characters in a file and not read them into your program, you can just issue a Read, assign the data to a variable, and immediately issue another Read. There is no method available for actually skipping over a data file element in Visual Basic.

Call a Method To Close the File After we have finished reading from or writing to a file, it must be closed. Closing a file tells the operating system that we no longer need the file, and makes it available for use by another program. Once a file is closed, it is no longer associated with the corresponding identifier in our program. Each of the file classes that we have discussed in this chapter has a void method called Close associated with it. The Close method does not take any parameters. The following code segment closes the files that we have been using in our discussions.

inFile.Close()
outFile.Close()

Although Visual Basic automatically closes every file when the program exits, it is good programming practice to explicitly close each file. Also, once a program has finished using a file, it should be closed immediately. You don't have to wait until the end of a program to close a file.

Once a file is closed, it can again be assigned a file object. For example, if the file infile.dat is associated with the file object inFile, and we have a File object called theFile, we can write:

inFile.Close()
inFile = theFile.OpenText("infile.dat")

The effect of these two statements is to temporarily break the association between the disk file (infile.dat) and the file identifier (inFile), and then restore their connection. A side effect of these operations is that the file pointer is reset to the start of the file as part of the assignment that associates the physical disk file with the inFile file object. After the call to Close(), it would be possible to assign a different disk file to inFile, or to assign infile.dat to a different file identifier. Here's an example that does both:

inFile.Close()
inFile = theFile.OpenText("somefile.dat")
differentFile = theFile.OpenText("infile.dat")

Extending File I/O with the ReadLine and WriteLine Methods

Visual Basic provides two methods with the StreamReader and StreamWriter classes that allow you to read or write an entire line at once. These are generally more convenient than single character I/O.

The WriteLine method, unlike the Write method, automatically adds the EOL (Chr(13) & Chr(10)) mark at the end of whatever it writes. For example, the following statements do exactly the same thing.

PayFile.Write("Rate = " & rate & Chr(13) & Chr(10)
PayFile.WriteLine("Rate = " & rate)

Using WriteLine merely saves a small amount of extra typing. This example also illustrates the typical usage of the string concatenation operator to combine several values in a single output statement. The value of the Double variable rate is automatically converted to class String before concatenation takes place.

We can also call WriteLine with no parameters as follows:

payFile.WriteLine()

What do you suppose this call does? Even when there is nothing for WriteLine to output, it still writes an EOL mark to the file. This call is equivalent to writing

payFile.Write(Chr(13) & Chr(10))

In both cases, the result is that an EOL is written at the end of the file, indicating that any subsequent output starts on a new line. If you need to write multiple values on one output line, you can use a series of calls to Write, followed by a call to WriteLine:

payFile.Write("Rate = ")
payFile.Write(rate)
payFile.WriteLine()

The preceding three statements are equivalent to the following single statement:

payFile.WriteLine("Rate = " & rate)

Why would we ever want to write three different statements when we can use just one? Sometimes values are computed in different sections of a program, yet you want them to all appear together on one line. As each value is computed, you can write it to the file with a call to Write, and after all of the values have been written, you can start a new line using a call to WriteLine.

A series of calls to WriteLine can be used to produce blank lines in the output. For example, if we run the following code segment:

payFile.WriteLine("X")
payFile.WriteLine()
payFile.WriteLine(88)
payFile.WriteLine()
payFile.WriteLine("This is a string value.")

the output is:

X

88

This is a string value.

We can rewrite these five statements as a single call to WriteLine:

payFile.WriteLine("X" & Chr(13) & Chr(10) & Chr(13) & Chr(10) & 88 _
                  & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "This is a
                  string value.")
but notice that this statement does not look much like the actual output. It is easier for a human reader to determine what is output by the longer version. Calling WriteLine five times in a row with simple values as parameters obviously writes five lines to the file. With the shorter version, you must count the number of Chr(13) & Chr(10) calls and add 1 for the EOL that WriteLine automatically inserts, to know how many lines are output. As you gain experience in reading programs written by other programmers, you grow to appreciate every effort that makes the code easier to understand.

If you write a floating-point value to a file (or a text box, for that matter) that has been calculated within the program, you have no control over the number of digits printed. For example, in the extended Calculator program, if you divide 1 by 3, you get .33333333…. You can use the FormatNumber function to print only as many decimal digits as you wish. We ask you to explore this function in the exercises.

The StreamReader class also has a value-returning method for reading data from files called ReadLine that returns an object of type String. The returned string contains a line of input from the file, but without the EOL mark at the end of the line. The ReadLine method actually reads the EOL mark but then discards it instead of storing it in the string. The file pointer thus advances to the start of the next line. If we read a line from one file we can write it out to another file with WriteLine. For example, if dataLine is a String object, we can write

dataLine = dataFile.ReadLine()
payFile.WriteLine(dataLine)

When WriteLine outputs the string in dataLine, it appends an EOL mark that replaces the one discarded by ReadLine. The advantage of having ReadLine discard the EOL mark is that it is easier to work with the input string. For example, if we want to add some characters to the end of the string before we write it to payFile, we can simply use string concatenation:

dataLine = dataFile.ReadLine()
dataLine = dataLine & "***"
payFile.WriteLine(dataLine)

If ReadLine did not discard the EOL mark, then after the concatenation there would be an EOL mark between the last character from the input line and the appended stars. The call to WriteLine would then output two lines in payFile.

What happens when we call ReadLine and the file pointer is at EOF? The answer is similar to what happens with Read. Recall the Read returns an Integer value (-1) when there is no more data in a file. ReadLine also returns this value when the program reaches EOF. There is a special method you can use (Peek) for determining when the data file is at EOF, and this method is discussed later in the chapter.

If ReadLine returns a string, how do we get numbers into our program? We do it exactly the same way we did when using TextBox variables. The data from a file comes into a program as a string and we must convert it to a numeric value using one of the numeric conversion functions. Here is an example we used when getting a floatingpoint value from a form:

number = CDbl(inputBox.Text)

Using this method as a model, we see that we can input a floating-point value from the file as follows:

Dim floatNumber As Double
floatNumber = CDbl(dataFile.ReadLine())

Likewise, an integer value can be read as:

Dim intNumber As Integer
intNumber = CInt(dataFile.ReadLine())

We return to the subject of reading data from files later in this chapter. Except for some trivial cases, we must combine reading operations with loops to read through all of the data in a file.

Call a Method To Close the File The last step is the same for both the StreamReader and StreamWriter classes. Here's an example:

dataFile.Close()
payFile.Close()

Exceptions with Input and Output

There are situations when reading and writing data to a file can cause the program to halt with an error message. If we try to assign a value to an input file and the file doesn't exist on the disk, the program will halt with an error message. If we try to read a line of data from a file and there is no more data in the file, the program also halts with an error message. These kinds of errors are representative of an unusual situation that Visual Basic calls an exception.

Exception
An unusual situation that is detected while a program is running. An exception halts the normal execution of the method.

When a method signals that one of these unusual errors has occurred, it is said to throw an exception. We shall see in Chapter 10 how a Visual Basic program can in turn catch an exception. If an exception isn't caught by the program, then it is subsequently caught by the operating system, which halts the program and displays an error message. The process of throwing and catching errors is structured in Visual Basic within a Try…Catch…Finally statement.

Throw
The act of signaling that an exception has occurred. Throwing an exception is said to abnormally terminate execution of a method.
Catch
The processing of a thrown exception by a section of code called an exception handler
Try
Identifies a block of Visual Basic code where an error can occur
Finally
Identifies a block of Visual Basic code that executes after all error processing has ended

The Try…Catch…Finally statement provides a structured means of performing error checking. The statements that include methods that might throw an error are placed inside a Try. If one of these statements causes an exception to be thrown, then the statements within the Catch are executed. After all error processing is complete, the statements inside the Finally are executed. Here is the syntax template for the Try…Catch…Finally statement:

Click To expand

An Example Program Using Files

Now let's put all our discussion of file I/O together by writing a simple application. The application should read a single line from an input file and display it in a text box on the screen so that the user can edit it. When the user closes the form, the contents of the text box are then written to an output file. Our user interface needs a label to prompt the user, and a text box to display the data, but there is no need for a button. Thus, the only event handler we need is the one built-in for closing the form. However, this event handler is also where we write the contents of the text box to the output file and then close the file.

We use the StreamReader and StreamWriter classes for input and output of the line. They are given values that associate them with files called infile.dat and outfile.dat.

When the form-closing event handler is called, it retrieves the contents of the text box. The string associated with the text box's Text property is passed directly to WriteLine, which writes it to the file. After closing the output file, the event handler removes the frame from the screen and halts the program.

Imports System.ComponentModel           ' Namespace for components
Imports System.Drawing                  ' Namespace for graphics
Imports System.WinForms                 ' Namespace for user interface
Imports System.IO                       ' Namespace for File I/O

Public Class Form1
    Inherits System.WinForms.Form
    Dim theFile As File                 ' Prepare a File object
    Dim outFile As StreamWriter         ' Prepare output file
    Dim inFile As StreamReader          ' Prepare input file

    Public Sub New()
        MyBase.New()
        Form1 = Me
        ' This call is required by the Win Form Designer.
        InitializeComponent()
        inFile = theFile.OpenText("c:\infile.dat")     ' Open input
                                                       ' file
        outFile = theFile.CreateText("c:\outfile.dat") ' Open output
                                                       ' file
        txtLine.Text = inFile.ReadLine()               ' Read a line
        inFile.Close()                                 ' Close input
                                                       ' file
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        outFile.WriteLine(txtLine.Text) ' Write text box to file
        outFile.Close()                 ' Close outFile
        MyBase.Dispose()                ' Close form
        components.Dispose()            ' Clear components
    End Sub

#Region " Windows Form Designer generated code "
 #End Region

End Class

If infile.dat contains the string "27.000", then here is how the form initially appears on the screen.

Click To expand

Here is the form after being edited by the user.

Click To expand

After the window is closed, examination of outfile.dat with a text editor reveals that it contains the single line:

This is a line of data to output to a file.


9.2 Looping

In Chapter 6, we said that the flow of control in a program can differ from the physical order of the statements. The physical order is the order in which the statements appear in a program; the order in which we want the statements to be executed is called the logical order.

Loop
A control structure that causes a statement or group of statements to be executed repeatedly

The If statement is one way of making the logical order different from the physical order. Looping control structures are another. A loop executes the same statement (simple or compound) over and over, as long as a condition or set of conditions is satisfied.

Click To expand

In this chapter, we discuss different kinds of loops and how they are constructed using the While statement. We also discuss nested loops (loops that contain other loops) and introduce a notation for comparing the amount of work done by different algorithms.

The While Statement

The While statement, like the If statement, tests a condition. Here is the syntax template for the While statement:

Click To expand

and this is an example of one:

count = 1
While (count <= 25)
     count = count + 1
End While

The While statement is a looping control structure. The statement to be executed each time through the loop is called the body of the loop. In the example above, the body of the loop is a statement that adds 1 to the value of count. This While statement says to execute the body repeatedly as long as count is less than or equal to 25. The While statement is completed (hence, the loop stops) when count is greater than 25. The effect of this loop, then, is to count through the Integer values from 1 to 25.

Just like the condition in an If statement, the condition in a While statement must be an expression of type Boolean. The While statement says, "If the value of the expression is True, execute the body and go back and test the expression again. If the expression's value is False, skip the body." So the loop body is executed over and over as long as the expression is True when it is tested. When the expression is False, the program skips the body and execution continues at the statement immediately following the loop. Of course, if the expression is False to begin with, the body is not even executed. Figure 9.3 shows the flow of control of the While statement, where Statement1 is the body of the loop and Statement2 is the statement following the loop.

Click To expand
Figure 9.3: While statement flow of control

The body of a loop can be a block of statements (as the syntax template indicates), which allows us to execute any group of statements repeatedly. Most often we use While loops in the following form:

While (Expression)
      ⋮
End While

In this structure, if the expression is True, the entire sequence of statements in the block is executed, and then the expression is checked again. If it is still True, the statements are executed again. The cycle continues until the expression becomes False.

Although in some ways the If and While statements are alike, there are fundamental differences between them (see Figure 9.4). In the If structure, Statement1 is either skipped or executed exactly once. In the While structure, Statement1 can be skipped, executed once, or executed over and over. The If is used to choose a course of action; the While is used to repeat a course of action.

Click To expand
Figure 9.4: A comparison of If and While

Phases of Loop Execution

The body of a loop is executed in several phases:

  • The moment that the flow of control reaches the first statement inside the loop body is the loop entry.

    Loop entry
    The point at which the flow of control reaches the first statement inside a loop

  • Each time the body of the loop is executed, a pass is made through the loop. This pass is called an iteration.

    Iteration
    An individual pass through, or repetition of, the body of a loop

  • Before each iteration, control is transferred to the loop test at the beginning of the loop.

    Loop test
    The point at which the While expression is evaluated and the decision is made either to begin a new iteration or skip to the statement immediately following the loop

  • When the last iteration is complete and the flow of control has passed to the first statement following the loop, the loop exit has been reached. The condition that causes a loop to be exited is the termination condition. In the case of a While loop, the termination condition is that the While expression becomes False.

    Loop exit
    The point at which the repetition of the loop body ends and control passes to the first statement following the loop
    Termination condition
    The condition that causes a loop to be exited

Notice that the loop exit occurs only at one point: when the loop test is performed. Even though the termination condition may become satisfied midway through the execution of the loop, the current iteration is completed before the computer checks the While expression again.

The concept of looping is fundamental to programming. In this chapter, we spend some time looking at typical kinds of loops and ways of implementing them with the While statement. These looping situations come up again and again when you are analyzing problems and designing algorithms.

Loops Using the While Statement

In solving problems, you will come across two major types of loops: count-controlled loops, which repeat a specified number of times, and event-controlled loops, which repeat until something happens within the loop. In the context of a loop, we use the word event to mean a specific condition that we expect to occur during some iteration of the loop and that can be tested by a Boolean expression. Don't confuse looping events with those that are caused by asynchronous processes such as buttons. The latter cause an asynchronous transfer of control to an event handler, while looping events are merely Boolean conditions that are checked in the loop test.

Count-controlled loop
A loop that executes a specified number of times
Event-controlled loop
A loop that terminates when something happens inside the loop body to signal that the loop should be exited

If you are making an angel food cake and the recipe reads, "Beat the mixture 300 strokes," you are executing a count-controlled loop. If you are making a pie crust and the recipe reads, "Cut with a pastry blender until the mixture resembles coarse meal," you are executing an event-controlled loop; you don't know ahead of time the exact number of loop iterations.

Count-Controlled Loops

A count-controlled loop uses a variable we call the loop control variable in the loop test. Before we enter a count-controlled loop, we have to initialize (set the initial value of) the loop control variable and then test it. Then, as part of each iteration of the loop, we must increment (increase by 1) the loop control variable. Here's an example:

loopCount = 1                      ' Initialization
While (loopCount <= 10)            ' Test
    ⋮                         ' Repeated actions

    loopCount = loopCount + 1      ' Incrementation
End While

Here loopCount is the loop-control variable. It is set to 1 before loop entry. The While statement tests the expression

loopCount <= 10

and executes the loop body as long as the expression is True. The dots inside the compound statement represent a sequence of statements to be repeated. The last statement in the loop body increments loopCount by adding 1 to it.

Look at the statement in which we increment the loop-control variable. Notice its form:

variable = variable + 1

This statement adds 1 to the current value of the variable, and the result replaces the old value. Variables that are used this way are called counters. In our example, loopCount is incremented with each iteration of the loop—we use it to count iterations. The loop control variable of a count-controlled loop is always a counter.

There is a shorter way to write the increment statement in Visual Basic. The += operator allows you to only use the counter variable name once. The statement

loopCount += 1

has precisely the same effect as the assignment statement

loopCount = loopCount + 1

From here on, we typically use the += operator.

In designing loops, it is the programmer's responsibility to see that the condition to be tested is set correctly (initialized) before the While statement begins. The programmer also must make sure the condition changes within the loop so that it eventually becomes False; otherwise, the loop is never exited.

loopCount = 1               ←  Variable loopCount must be initialized
While (loopCount <= 10)
    ⋮
    loopCount += 1          ←  loopCount must be incremented
End While

A loop that never exits is called an infinite loop because, in theory, the loop executes forever. In the code above, omitting the incrementation of loopCount at the bottom of the loop leads to an infinite loop; the While expression is always True because the value of loopCount is forever 1. If your program goes on running for much longer than you expect it to, chances are you've created an infinite loop. You may have to issue an operating system command to stop the program.

How many times does the loop in our example execute—9 or 10? To determine this, we have to look at the initial value of the loop-control variable and then at the test to see what its final value is. Here we've initialized loopCount to 1, and the test indicates that the loop body is executed for each value of loopCount up through 10. If loop-Count starts out at 1 and runs through 10, the loop body is executed 10 times. If we want the loop to execute 11 times, we have to either initialize loopCount to 0 or change the test to

loopCount <= 11

Event-Controlled Loops

There are several kinds of event-controlled loops: sentinel-controlled, end-of-line-controlled, and flag-controlled. In all of these loops, the termination condition depends on some event occurring while the loop body is executing.

Sentinel-Controlled Loops Loops often are used to read in and process long lists of data. Each time the loop body is executed, a new piece of data is read and processed. Often a special data value, called a sentinel or trailer value, is used to signal the program that there is no more data to be processed. Looping continues as long as the data value read is not the sentinel; the loop stops when the program recognizes the sentinel. In other words, reading the sentinel value is the event that controls the looping process.

A sentinel value must be something that never shows up in the normal input to a program. For example, if a program reads calendar dates, we could use February 31 as a sentinel value:

' This code is incorrect
While ( Not(date.Equals("0231") )
    date = dataFile.ReadLine()     ' Get a date
    ⋮                         ' Process it
End While

There is a problem in the loop in the example above. The value of date is not defined before the first pass through the loop. Somehow we have to initialize this string. We could assign it an arbitrary value, but then we would run the risk that the first value input is the sentinel value, which would then be processed as data. Also, it's inefficient to initialize a variable with a value that is never used.

We can solve the problem by reading the first data value before entering the loop. This is called a priming read. (The idea is similar to priming a pump by pouring a bucket of water into the mechanism before starting it.) Let's add the priming read to the loop:

' This is still incorrect
date = dataFile.ReadLine()         ' Get a date --- priming read
While ( Not(date.Equals("0231") )
    date = dataFile.ReadLine()     ' Get a date
    ⋮                        ' Process it
End While

With the priming read, if the first value input is the sentinel value, then the loop correctly does not process it. We've solved one problem, but now there is a problem when the first value is valid data. Notice that the first thing the program does inside the loop is to get a date, destroying the value obtained by the priming read. Thus, the first date in the data list is never processed. Given the priming read, the first thing that the loop body should do is process the data that's already been read. But then at what point do we read the next input value? We do this last in the loop. In this way, the While condition is applied to the next input before it gets processed. Here's how it looks:

' This version is correct
date = dataFile.ReadLine()         ' Get a date --- priming read
While ( Not(date.Equals("0231") )
    ⋮                         ' Process it
    date = dataFile.ReadLine()     ' Get a date
End While

This segment works just fine. The first value is read in; if it is not the sentinel, it gets processed. At the end of the loop, the next value is read in, and we go back to the beginning of the loop. If the new value is not the sentinel, it gets processed just like the first. When the sentinel value is read, the While expression becomes False, and the loop exits (without processing the sentinel).

Many times the problem dictates the value of the sentinel. For example, if the problem does not allow data values of 0, then the sentinel value should be 0. Sometimes a combination of values is invalid. The combination of February and 31 as a date is such a case. Sometimes a range of values (negative numbers, for example) is the sentinel. And when you process Char data, one line of input at a time, the newline character (Chr(13) & Chr(10)) often serves as the sentinel. The problem here is that the newline character is stored as String, not as Char. Therefore, we need to convert the data we read to String from Char in order to find the sentinel. We do that with the ChrW function. Another problem we have is that the newline character is actually two string characters: Chr(13), which stands for "Carriage Return" and Chr(10), which stands for "Linefeed". Therefore, we need to test for both of these in our While loop. We can do this by allowing the loop to process as long as the character (string) read is not a carriage return or a linefeed. Here's the code segment:

inChar = ChrW(inFile.Read())
While Not (lastChar = Chr(13) And inChar = Chr(10))
   outFile.Write(inChar)

   lastChar = inChar
   inChar = ChrW(inFile.Read())
End While

(Notice that for this particular task we use the Read method, not the ReadLine method, to input a single character.)

When you are choosing a value to use as a sentinel, what happens if there aren't any invalid data values? Then you may have to input an extra value in each iteration, a value whose only purpose is to signal the end of the data. For example, look at this code segment:

data = dataFile.ReadLine()         ' Get first data line
sentinel = data.Substring(0,1)     ' Extract sentinel character
While (sentinel.Equals("Y"))
  value = CDbl(data.Substring(1, _
       data.Length() - 1))
  ⋮                          ' Process data value
  data = dataFile.ReadLine()      ' Get next data line
  sentinel = data.Substring(0,1)  ' Extract sentinel character
End While

The first value on each line of the following data set is used to indicate whether or not there are more lines of data. In this data set, when the sentinel value is anything other than "Y", there is no more data; when it is "Y", there is more data.

Sentinel Values | Data Values
Y | 12.78
Y | -47.90
Y | 5.33
Y | 21.83
Y | -99.01
N | 59.99

What happens if you forget to include the sentinel value? Once all the data has been read from the file, the loop body is executed again. However, there isn't any data left because the computer has reached the end of the file. In the next section, we see what happens in this situation and describe a way to use the end-of-file situation as an alternative to using a sentinel.

End-of-File-Controlled Loops After a program has read the last piece of data from an input file, the computer is at the end of the file (EOF, for short). The next time that we attempt to read from the file, there is nothing for the Read or ReadLine method to return. What happens? In Visual Basic, if you try to read a line or a character from a file and the file is at EOF, an exception is thrown. To keep this from happening in the normal course of your program, Visual Basic provides a special method, Peek, that reads the next line or next character from a file and returns -1 if to be read.

Thus, to read and process lines from a file, we can write a sentinel-controlled loop like the following:

line = dataFile.ReadLine()         ' Get a line - priming read
While (dataFile.Peek() <> -1)
    ⋮                         ' Process it
    line = dataFile.ReadLine()     ' Get the next line
End While

Looping Subtasks

We have been looking at ways to use loops to affect the flow of control in programs. But looping by itself does nothing. The loop body must perform a task in order for the loop to accomplish something. In this section, we look at two tasks—counting and summing—that are often used in loops.

Counting A common task in a loop is to keep track of the number of times the loop has been executed. For example, the following program fragment reads and counts input characters until it comes to a period. The loop in this example has a counter variable; but the loop is not a count-controlled loop because the variable is not being used as a loop control variable.

count = 0                            ' Initialize counter
inChar = CChar(inFile.Read())        ' Read a character
While (inChar <> ".")                ' Test the character
    count += 1                       ' Increment counter
    inChar = CChar(inFile.Read())    ' Read a character
End While

The loop continues until a period is read. After the loop is finished, count contains one less that the number of characters read. That is, it counts the number of characters up to, but not including, the sentinel value (the period). Notice that if a period is the first character, the loop body is not entered and count contains a zero, as it should. We use a priming read here because the loop is sentinel-controlled.

The counter variable in this example is called an iteration counter because its value equals the number of iterations through the loop. According to our definition, the loop control variable of a count-controlled loop is an iteration counter. However, as you've just seen, not all iteration counters are loop control variables.

Iteration counter
A counter variable that is incremented with each iteration of a loop

What happens if our example loop encounters EOF before it reads a period? It runs forever (or at least until we terminate the program with an operating system command). The reason is that Read returns -1 when the file pointer is at EOF. Since -1 is not equal to a period, the test will always be True and the loop continues executing forever. The test in the While statement is thus more properly written as follows:

While (inFile.Peek() <> -1 And inChar <> ".")  ' Test character

Summing Another common looping task is to sum a set of data values. Notice in the following example that the summing operation is written the same way, regardless of how the loop is controlled.

sum = 0                                 ' Initialize sum
count = 1
While (count <= 10)
    number = CInt(dataFile.ReadLine())  ' Input a value
    sum = sum + number                  ' Add value to sum
    count += 1
End While

We initialize sum to 0 before the loop starts so that the first time the loop body executes, the statement

sum = sum + number

adds the current value of sum (0) to number to form the new value of sum. After the entire code fragment has executed, sum contains the total of the 10 values read, count contains 11, and number contains the last value read.

Here count is incremented in each iteration. For each new value of count, there is a new value for number. Does this mean we could decrement count by 1 and inspect the previous value of number? No. Once a new value has been read into number, the previous value is gone forever, unless we've saved it in another variable.

Let's look at another example. This time, let's also check for EOF. We want to count and sum the first 10 odd numbers in a data set. We need to test each number to see if it is even or odd. (We can use the modulus operator to find out. If number mod 2 equals 1, number is odd; otherwise, it's even.) If the input value is even, we do nothing. If it is odd, we increment the counter and add the value to our sum. We use a flag to control the loop because this is not a normal count-controlled loop. In the following code segment, all variables are of type Integer except the Boolean flag, notDone.

count = 0                                  ' Initialize event counter
sum = 0                                    ' Initialize sum
notDone = True                             ' Initialize loop control flag
While (notDone)
    If (dataFile.Peek() <> -1) Then        ' Check for EOF
        number = CInt(dataFile.ReadLine()) ' Get and convert a line
        If (number Mod 2 = 1) Then         ' Is the value odd?
            count += 1                     ' Yes - increment counter
            sum = sum + number             ' Add value to sum
            notDone = (count < 10)         ' Update loop counter flag
        End If
    Else                                   ' Hit EOF unexpectedly
        msgbox("EOF reached before ten odd values read.")

        notDone = False                    ' Update loop counter flag
    End If
End While

We control the loop with the flag notDone, because the loop exits when either of two events occur: reading and processing 10 odd values or reaching EOF. Because we use a Boolean flag to control the loop, this type of loop is often called a flag-controlled loop.

In this example, there is no relationship between the value of the counter variable and the number of times the loop is executed. Note that count is incremented only when an odd number is read; it is an event counter We initialize an event counter to 0 and increment it only when a certain event occurs. The counter in the previous example was an iteration counter; it was initialized to 1 and incremented during each iteration of the loop.

Event counter
A variable that is incremented each time a particular event occurs

How to Design Loops

It's one thing to understand how a loop works when you look at it and something else again to design a loop that solves a given problem. In this section, we look at how to design loops. We can divide the design process into two tasks: designing the control flow and designing the processing that takes place in the loop. And we can break each task into three phases: the task itself, initialization, and update. It's also important to specify the state of the program when it exits the loop, because a loop that leaves variables and files in a mess is not well designed.

There are seven different points to consider in designing a loop:

  1. What is the condition that ends the loop?

  2. How should the loop be initialized?

  3. How should the condition be updated?

  4. What is the process being repeated?

  5. How should the process be initialized?

  6. How should the process be updated?

  7. What is the state of the program on exiting the loop?

We use these questions as a checklist. The first three help us design the parts of the loop that control its execution. The next three help us design the processing within the loop. The last question reminds us to make sure that the loop exits in an appropriate manner.

Designing the Flow of Control

The most important step in loop design is deciding what should make the loop stop. If the termination condition isn't well thought out, there's the potential for infinite loops and other mistakes. So here is our first question:

  • What is the condition that ends the loop?

This question usually can be answered through a close examination of the problem statement. For example:

Key Phrase in Problem Statement | Termination Condition
"Sum 365 temperatures" | The loop ends when a counter reaches 366 (count-controlled loop).
"Process all the data in the file" | The loop ends when EOF occurs (EOF-controlled loop).
"Process until 10 odd integers have been read" | The loop ends when 10 odd numbers have been input (event counter).
"The end of the data is indicated by a negative test score" | The loop ends when a negative input value is encountered (sentinel-controlled loop).

Now we need statements that make sure the loop gets started correctly and statements that allow the loop to reach the termination condition. So we have to ask the next two questions:

  • How should the condition be initialized?

  • How should the condition be updated?

The answers to these questions depend on the type of termination condition.

Count-Controlled Loops If the loop is count-controlled, we initialize the condition by giving the loop control variable an initial value. For count-controlled loops in which the loop control variable is also an iteration counter, the initial value is usually 1. If the process requires the counter to run through a specific range of values, the initial value should be the lowest value in that range.

The condition is updated by increasing the value of the counter by 1 for each iteration. (Occasionally, you may come across a problem that requires a counter to count down from some higher value to a lower value. In this case, the initial value is the greater value, and the counter is decremented by 1 for each iteration.) So, for countcontrolled loops that use an iteration counter, here are the answers to the questions:

  • Initialize the iteration counter to 1.

  • Increment the iteration counter at the end of each iteration.

If the loop is controlled by a variable that is counting an event within the loop, the control variable is usually initialized to 0 and is incremented each time the event occurs. For count-controlled loops that use an event counter, these are the answers to the questions:

  • Initialize the event counter to 0.

  • Increment the event counter each time the event occurs.

Sentinel-Controlled Loops In sentinel-controlled loops, a priming read may be the only initialization necessary. It also may be necessary to open the file in preparation for reading. To update the condition, a new value is read at the end of each iteration. So, for sentinel-controlled loops, we answer our questions this way:

  • Open the file, if necessary, and input a value before entering the loop (priming read).

  • Input a new value for processing at the end of each iteration.

EOF-Controlled Loops EOF-controlled loops require the same initialization as sentinelcontrolled loops. You must open the file, if necessary, and perform a priming read. Updating the loop condition happens implicitly; the input value reflects the success or failure of the operation. However, if the loop doesn't read any data, it never reaches EOF, so updating the loop condition means the loop must keep reading data.

Flag-Controlled Loops In flag-controlled loops, the Boolean flag variable must be initialized to True or False and then updated when the condition changes.

  • Initialize the flag variable to True or False, as appropriate.

  • Update the flag variable as soon as the condition changes.

In a flag-controlled loop, the flag variable essentially remains unchanged until it is time for the loop to end. Then the code detects some condition within the process being repeated that changes the value of the flag (through an assignment statement). Because the update depends on what the process does, at times we have to design the process before we can decide how to update the condition.

Designing the Process within the Loop

Once we've determined the looping structure itself, we can fill in the details of the process. In designing the process, we first must decide what we want a single iteration to do. Assume for a moment that the process is going to execute only once. What tasks must the process perform?

  • What is the process being repeated?

To answer this question, we have to take another look at the problem statement. The definition of the problem may require the process to sum up data values or to keep a count of data values that satisfy some test. For example:

Count the number of integers in the file howMany.

This statement tells us that the process to be repeated is a counting operation.

Here's another example:

Read a stock price for each business day in a week and compute the average price.

In this case, part of the process involves reading a data value. We have to conclude from our knowledge of how an average is computed that the process also involves summing the data values.

In addition to counting and summing, another common loop process is reading data, performing a calculation, and writing out the result. Many other operations can appear in looping processes. We've mentioned only the simplest here; we look at some other processes later on.

After we've determined the operations to be performed if the process is executed only once, we design the parts of the process that are necessary for it to be repeated correctly. We often have to add some steps to take into account the fact that the loop executes more than once. This part of the design typically involves initializing certain variables before the loop and then reinitializing or updating them before each subsequent iteration.

  • How should the process be initialized?

  • How should the process be updated?

For example, if the process within a loop requires that several different counts and sums be performed, each must have its own statements to initialize variables, increment counting variables, or add values to sums. Just deal with each counting or summing operation by itself—that is, first write the initialization statement, and then write the incrementing or summing statement. After you've done this for one operation, you go on to the next.

The Loop Exit

When the termination condition occurs and the flow of control passes to the statement following the loop, the variables used in the loop still contain values. And if an input file has been used, the reading marker has been left at some position in the file. Or maybe an output file has new contents. If these variables or files are used later in the program, the loop must leave them in an appropriate state. So, the final step in designing a loop is answering this question:

  • What is the state of the program on exiting the loop?

Now we have to consider the consequences of our design and double-check its validity.

For example, suppose we've used an event counter and later processing depends on the number of events. It's important to be sure (with an algorithm walk-through) that the value left in the counter is the exact number of events—that it is not offset by 1.

Look at this code segment:

commaCount = 1
while (inChar <> -1)
   inChar = inFile.Read()
   If (chrw(inChar) = ","c) then
      commaCount += 1
   end if
end while
outLabel.Text = commaCount

This loops reads characters from an input line and counts the number of commas in the line. However, when the loop terminates, commaCount equals the actual number of commas plus 1 because the loop initializes the loop counter to 1 before any events take place. By determining the state of commaCount at loop exit, we've detected a flaw in the initialization. commaCount should be initialized to zero.

Designing correct loops depends as much on experience as it does on the application of design methodology. At this point, you may want to read through the Problem-Solving Case Study at the end of the chapter to see how the loop-design process is applied to a real problem.

Nested Loops

In Chapter 6, we described the nested If statement. It's also possible to nest While statements. Both While and If statements contain statements and are themselves statements. So the body of a While statement or the branch of an If statement can contain other While and If statements. By nesting, we can create complex control structures.

Suppose we want to extend our code for counting commas on one line, repeating it for all the lines in a file. We put an EOF-controlled loop around it:

inChar = inFile.Read()                         ' Initialize outer loop
while (inFile.Peek() <> -1)                    ' Outer-loop test for EOF
   count = 0                                   ' Initialize inner loop
      while (inChar <> -1)                     ' Inner-loop test
         if (chrw(inChar) = ","c) then
            count += 1
         end if
         ' Update inner-termination condition
         inChar = inFile.Read()
      end while
   ' Update outer-termination condition
   inChar = inFile.Read()
end while

In this code, notice that we have omitted the priming read for the inner loop. The priming read for the outer loop has already "primed the pump." It would be a mistake to include another priming read just before the inner loop; the character read by the outer priming read would be destroyed before we could test it. Our inner-loop test checks for both end of line inFile.Peek <> -1 and for EOF (-1), just in case the last line in the file doesn't end with the newline character.

General Pattern

Let's examine the general pattern of a simple nested loop. The dots represent places where the processing and update may take place in the outer loop.

Click To expand

Notice that each loop has its own initialization, test, and update. It's possible for an outer loop to do no processing other than to execute the inner loop repeatedly. On the other hand, the inner loop might be just a small part of the processing done by the outer loop; there could be many statements preceding or following the inner loop.

Let's look at another example. For nested count-controlled loops, the pattern looks like this (where outCount is the counter for the outer loop, inCount is the counter for the inner loop, and limit1 and limit2 are the number of times each loop should be executed):

outCount = 1                              ' Initialize outer-loop counter
While (outCount <= limit1)
   ⋮
  inCount = 1                             ' Initialize inner-loop counter
  While (inCount <= limit2)
   ⋮
    inCount+=1                            ' Increment inner-loop counter
  End While
   ⋮
  outCount+=1                             ' Increment outer-loop counter
End While

Here, both the inner and outer loops are count-controlled loops, but the pattern can be used with any combination of loops. The following program fragment shows a count-controlled loop nested within an EOF-controlled loop. The outer loop inputs an integer value telling how many asterisks to print on each line of an output file. (We use the numbers to the right of the code to trace the execution of the program.)

line = dataFile.readLine()                  1
While (line <> "")                          2
  starCount = CInt(line)                    3
  loopCount = 1                             4
  While (loopCount <= starCount)            5
    outFile.Write("*")                      6
    loopCount+=1                            7
  End While                                 8
  outFile.WriteLine()                       9
  line = dataFile.ReadLine()               10
End While                                  11
outFile.WriteLine("End")                   12

To see how this code works, let's trace its execution with these data values (<EOF> denotes end-of-file):

3
1
<EOF>

We'll keep track of the variables line, starCount, and loopCount, as well as the logical expressions in Table 9.1. To do this, we've numbered each line. As we trace the program, we indicate the first execution of line 3 by 3.1, the second by 3.2, and so on. Each loop iteration is enclosed by a large brace, and True and False are abbreviated as T and F.

Click To expand
Table 9.1 Code trace

Here's the output in outFile from the program, given the input used for our trace.

***
*
End

Because starCount and loopCount are variables, their values remain the same until they are explicitly changed, as indicated by the repeating values in Table 9.1. The values of the logical expressions line <> -1 and loopCount <= starCount exist only when the test is made. We indicate this fact in those columns at all other times.

Designing Nested Loops

To design a nested loop, we begin with the outer loop. The process being repeated includes the nested loop as one of its steps. Because that step is more complex than a single statement, our functional decomposition methodology tells us to make it a separate module. We can come back to it later and design the nested loop just as we would any other loop.

For example, here's the design process for the outer loop for the preceding code segment:

  1. What is the condition that ends the loop? EOF is reached in the input.

  2. How should the condition be initialized? A priming read should be performed before the loop starts.

  3. How should the condition be updated? An input statement should occur at the end of each iteration.

  4. What is the process being repeated? Using the value of the current input integer starCount, the code should print that many asterisks across one output line.

  5. How should the process be initialized? No initialization is necessary.

  6. How should the process be updated? A sequence of asterisks is output and then a newline character is output. There are no counter variables or sums to update.

  7. What is the state of the program on exiting the loop? File datafile is at EOF, starCount contains the last integer read from the input stream, and the rows of asterisks have been printed along with a concluding message.

From the answers to these questions, we can write this much of the algorithm:

Click To expand

After designing the outer loop, it's obvious that the process in its body (printing a sequence of asterisks) is a complex step that requires us to design an inner loop. So we repeat the methodology for the corresponding lower-level module:

  1. What is the condition that ends the loop? An iteration counter exceeds the value of starCount.

  2. How should the condition be initialized? The iteration counter should be initialized to 1.

  3. How should the condition be updated? The iteration counter is incremented at the end of the loop.

  4. What is the process being repeated? The code should print a single asterisk in the output file.

  5. How should the process be initialized? No initialization is necessary.

  6. How should the process be updated? No update is needed.

  7. What is the state of the program on exiting the loop? A single row of asterisks has been printed, the writing marker is at the end of the current output line, and loop-Count contains a value one greater than the current value of starCount.

Now we can write the algorithm:

Click To expand

Of course, nested loops themselves can contain nested loops (called doubly nested loops), which can contain nested loops (triply nested loops), and so on. You can use this design process for any number of levels of nesting. The trick is to defer details using the functional decomposition methodology—that is, focus on the outermost loop first, and treat each new level of nested loop as a module within the loop that contains it.

It's also possible for the process within a loop to include more than one loop. For example, below is an algorithm that reads people's names from a file and prints them in another file, omitting the middle name in the output. The steps for reading the first name, middle name, and last name require us to design three separate loops. All of these loops are sentinel-controlled. Here is the algorithm:

Click To expand

This kind of complex control structure would be difficult to read if written out in full. There are simply too many variables, conditions, and steps to remember at one time. When a responsibility algorithm becomes this complex, it is an indication that we have missed some objects in the brainstorming and scenario phases. In this case, we should extend StreamReader with new methods that get the first, middle, and last names. We could call the new class NameFileReader.

Start Sidebar
Problem-Solving Case Study
Average Income by Gender

Problem You've been hired by a law firm that is working on a sex discrimination case. Your firm has obtained a file of incomes, gender.dat, which contains the salaries for every employee in the company being sued. Each salary amount is preceded by "F" for female or "M" for male. As a first pass in the analysis of this data, you've been asked to compute the average income for females and the average income for males. The output should be saved in a file to be reviewed later.

Brainstorming Most of the nouns in this problem statement relate to the reason for the problem, not the problem itself. There are two file objects, one for input and one for output. The result of the application is a pair of averages, one for males and one for females. These averages are Double values, not classes. Therefore, we only have three classes.

Click To expand

Filtering No filtering is necessary in a problem this simple.

Scenarios What happens when we see an "F"? We add the corresponding salary to the female sum. Otherwise, we add the salary to the male sum. When all of the figures have been read and summed, we calculate the average female's salary and the average male's salary. Calculate the average—we must not forget to keep track of how many males and females there are. All the responsibility for this application lies in the application class, which by default is named Form1. Because there is no event processing in this application and there is only one class, all the processing is done in class Form1.

Click To expand

Responsibility Algorithms The algorithms for preparing the input and output files are very straightforward.

...........................................................................
Prepare File for Input                                              Level 1

 Declare StreamReader, inFile
 Instantiate inFile, "incomes.dat"

...........................................................................
Prepare File for Output                                             Level 1

 Declare StreamWriter, outFile
 Instantiate outFile, "results.dat"

Calculating the averages is more difficult. We remember the basic algorithm from the application RainFall. To calculate a single average we sum the values and divide by the number of values. In that problem the processing was done in the event handler for the input button. Here we must explicitly write the loop, which involves a loop with several subtasks, because we must calculate two averages instead of just one. We use our checklist of questions to develop these subtasks in detail.

  1. What is the condition that ends the loop? The termination condition is EOF on the file inFile. It leads to the following loop test (in pseudocode):

    While NOT EOF in inFile

  2. How should the condition be initialized? A priming read must take place to enter a gender code and amount.

  3. How should the condition be updated? We must input a new data line with a gender code and amount at the end of each iteration.

    We must input each data line as a string. Then we need to extract the gender code and amount from the string.

    While Reading inLine from inFile does not return EOF
    Extract gender code and amount from inLine
    (Process is repeated)

  4. What is the process being repeated? From our knowledge of how to compute an average, we know that we have to count the number of amounts and divide this number into the sum of the amounts. Because we have to do this separately for females and males, the process consists of four parts: counting the females and summing their incomes, and then counting the males and summing their incomes. We develop each of these in turn.

  5. How should the process be initialized? femaleCount and femaleSum should be set to zero. maleCount and maleSum should also be set to zero.

  6. How should the process be updated? When a female income is input, femaleCount is incremented, and the income is added to femaleSum. Otherwise, an income is assumed to be for a male, so maleCount is incremented, and the amount is added to maleSum.

  7. What is the state of the program on exiting the loop? The file inFile is at EOF; femaleCount contains the number of input values preceded by "F"; femaleSum contains the sum of the values preceded by "F"; maleCount contains the number of values not preceded by "F"; and maleSum holds the sum of those values.

From the description of how the process is updated, we can see that the loop must contain an If structure, with one branch for female incomes and the other for male incomes. Each branch must increment the correct event counter and add the income amount to the correct total. After the loop has exited, we have enough information to compute the averages, dividing each total by the corresponding count.

Now we're ready to write the complete algorithm:

...........................................................................
Calculate the Averages                                              Level 1

 Initialize variables
 While Reading inLine from inFile does not return EOF
     Extract gender code and amount from inLine
     Update process
 Compute average incomes

...........................................................................
Initialize Variables                                                Level 2

  Set femaleCount to 0
  Set femaleSum to 0.0
  Set maleCount to 0
  Set maleSum to 0.0
...........................................................................
 Extract Gender Code and Amount from inLine

  Set gender to Char at position 0 of inLine
  Set amountString to substring of inLine starting at position 2, through end of inLine
  Set amount to amountString converted to Double

We haven't discussed the operation of getting a single character from a string. The String class provides a value-returning instance method called Chars that takes an Integer value as its parameter. The Integer specifies the position of the character to be copied from the string and the method returns the character as a value of type Char. We can thus implement the first line of this algorithm with the statement:

gender = inLine.Chars(0);
...........................................................................
Update Process

 If gender is "F"
      Increment femaleCount
      Add amount to femaleSum
 Else
      Increment maleCount
      Add amount to maleSum

...........................................................................
Compute Average Incomes

 Set femaleAverage to femaleSum / Cdbl(femaleCount)
 Set maleAverage to maleSum / Cdbl(maleCount)

...........................................................................
Write Results to File                                               Level 1

WriteLine "For" & femaleCount & " females, the average income is " & femaleAvg & "."
WriteLine "For" & maleCount & " males, the average income is " & maleAvg & "."

All of these algorithms depend on the file being created in a certain way. These assumptions should be stated in a special section of the design.

Assumptions There is at least one male and one female among all the data sets. The data sets are all entered properly in the input file, with the gender code in the first character position in each line and a floating-point number starting in the third character position. The only gender codes in the file are "M" and "F"—any other codes are counted as "M". (This last assumption invalidates the results if there are any illegal codes in the data. Case Study Follow-Up Exercise 1 asks you to change the program as necessary to address this problem.)

Before we write the program, we need to look back over the algorithm and see if there are repeating processes that should be encapsulated into helper methods. The summing and counting are the same for both males and females. There could be a summing and counting method, but it would need three parameters for a two-statement method body. Let's just code it inline.

This application is different than the other case study applications we have developed in the book so far because we don't need a user interface. The program reads directly from inFile and writes data directly to outFile. No interactivity with the user is required. When you have applications like this in Visual Basic, you can develop your program without using Visual Studio.

To do this, simply use your favorite text editor to create the source code. When you are finished writing the code, save the file with a .vb extension, such as incomes.vb. Then you can compile the program using the command-line compiler that is part of .NET. To do this, simply invoke the compiler with the file name after it, like this: vbc incomes.vb. If there are no errors, you can then run the file by just invoking the program name, without an extension.

Before we examine the code for this application, let's look at what other code you have to include in your application to create a command-line application. First, we have to physically import any classes we need into our program. For this application, we need the System class and the System.IO class. Next, we have to create a module, which is an organizational unit that contains our code. You can name a module with any name. We will call this module incomes. Then we have to create a main subprogram, which is much like Public Class Form1, to contain our source code. This subprogram is always called sub main. We then close the subprogram and the module. A template for a command-line application will always look something like this, depending on the classes you import:

imports system
imports system.io

module incomes
   sub main
      ' Place code here
   end sub
end module

Now, finally, we're ready to see the code for the Incomes application:

imports system
imports system.io
imports microsoft.visualbasic

module RainFall
   ' This program reads a file of income amounts classified by
   ' gender and computes the average income for each gender

  sub main
     dim femaleCount as integer       ' Keeps count of females
     dim femaleSum as double          ' Keeps total of salaries
     dim femaleAvg as double          ' Holds average salary
     dim maleCount as integer         ' Keeps count of males
     dim maleSum as double            ' Keeps total of salaries
     dim maleAvg as double            ' Holds average salary
     dim inLine as string             ' A line from the file
     dim gender as char               ' Indicate gender
     dim amountString as string       ' Amount part of input line

     dim amount as double             ' Salary amount
     dim theFile as file              ' Generic file object
     dim inFile as StreamReader       ' Input data file
     dim outFile as StreamWriter      ' Output data file

     ' Prepare files for reading and writing
     inFile = theFile.OpenText("gender.dat")
     outFile = theFile.CreateText("results.dat")

     ' Calculate averages
     ' Initialize process
     femaleCount = 0
     femaleSum = 0.0
     malecount = 0
     maleSum = 0.0
     While (inFile.Peek() <> -1)

        ' Read line of data, update process
        ' Extract gender code and amount from input line
        inLine = inFile.ReadLine()
        gender = inLIne.Chars(0)
        amountString = inLine.Substring(2)
        amount = Cdbl(amountString)

        ' Process amount based on gender code
        If (gender = "F"c) then
           ' female
           femaleCount += 1
           femaleSum += amount
        Else
           ' male
           maleCount += 1
           maleSum += amount
        end If
     end While

     ' Compute average incomes
     femaleAvg = femaleSum / Cdbl(femaleCount)
     maleAvg = maleSum / Cdbl(maleCount)

     ' Write results
     outFile.WriteLine("For " & femaleCount & " females, the average income is " _
                        & femaleAvg & ".")
     outFile.WriteLine("For " & maleCount & " males, the average income is " _
                         & maleAvg & ".")
     inFile.Close()

      outFile.Close()
   end sub
end module

Testing Given the following data in file gender.dat:

F 24345.23
F 17812.99
F 59207.34
M 38119.29
M 29228.99
M 89101.73
F 47237.83
M 31820.04
F 32933.73
F 45910.86

program Incomes writes the following in file results.dat:

For 6 females, the average income is 37907.9966666667.
For 4 males, the average income is 47067.5125.

With an EOF-controlled loop, the obvious test cases are a file with data (as shown above) and an empty file. We should test input values of both "F" and "M" for the gender, and try some typical data (so we can compare the results with our hand-calculated values) and some atypical data (to see how the process behaves). The application assumed that the data file included at least one woman and one man, but in general, an atypical data set for testing a counting operation is an empty file, which should result in a count of zero. Any other result for the count indicates an error. For a summing operation, atypical data might include negative or zero values.

The Incomes program is not designed to handle empty files or negative income values; the assumption that the file was not empty and contained at least one male and one female was written in the design. An empty file causes both femaleCount and maleCount to equal zero at the end of the loop. Although this is correct, the statements that compute average income cause the program to produce invalid results (infinity) because they divide by zero. And a negative income would be treated like any other value, even though it is probably a mistake.

To correct these problems, we should insert If statements to test for the error conditions at the appropriate points in the program. When an error is detected, the program should either display an error message or write an error report to a file instead of carrying out the usual computation. This prevents a crash and allows the program to keep running. We call a program that can recover from erroneous input and keep running a robust program.

End Sidebar


Testing and Debugging

Loop-Testing Strategy

Even if a loop has been properly designed, it is still important to test it rigorously, because the chance of an error creeping in during the implementation phase is always present. To test a loop thoroughly, we must check for the proper execution of both a single iteration and multiple iterations.

Remember that a loop has seven parts (corresponding to the seven questions in our checklist). A test strategy must test each part. Although all seven parts aren't implemented separately in every loop, the checklist reminds us that some loop operations serve multiple purposes, each of which should be tested. For example, the incrementing statement in a count-controlled loop may be updating both the process and the ending condition. So it's important to verify that it performs both actions properly with respect to the rest of the loop.

Consider what the acceptable ranges of variables are and what sorts of I/O operations you expect to see in the loop. Try to devise data sets that could cause the variables to go out of range or leave the files in unexpected states.

It's also good practice to test a loop for four special cases: (1) when the loop is skipped entirely, (2) when the loop body is executed just once, (3) when the loop executes some normal number of times, and (4) when the loop fails to exit.

Statements following a loop often depend on its processing. If a loop can be skipped, those statements may not execute correctly. If it's possible to execute a single iteration of a loop, the results can show whether the body performs correctly in the absence of the effects of previous iterations, which can be very helpful when you're trying to isolate the source of an error. Obviously, it's important to test a loop under normal conditions, with a wide variety of inputs. If possible, you should test the loop with real data in addition to mock data sets. Count-controlled loops should be tested to be sure they execute exactly the right number of times. And finally, if there is any chance that a loop might never exit, your test data should try to make that happen.

Testing a program can be as challenging as writing it. To test a program, you need to step back, take a fresh look at what you've written, and then attack it in every way possible to make it fail. This isn't always easy to do, but it's necessary if your programs are going to be reliable. (A reliable program is one that works consistently and without errors, regardless of whether the input data is valid or invalid.)

Test Plans Involving Loops

In Chapter 6, we introduced formal test plans and discussed the testing of branches. Those guidelines still apply to programs with loops, but here we provide some additional guidelines that are specific to loops. In general, the goal of testing a loop is to verify that it behaves as expected.

Unfortunately, when a loop is embedded in a larger program, it sometimes is difficult to control and observe the conditions under which the loop executes using test data and output alone. In some cases we must use indirect tests. For example, if a loop reads floating-point values from a file and prints their average without echo-printing them, you cannot tell directly that the loop processes all the data. If the data values in the file are all the same, then the average appears correct as long as even one of them is processed. You must construct the input file so that the average is a unique value that can be arrived at only by processing all the data.

What we would like to do to simplify our testing of such loops is to observe the values of the variables involved in the loop at the start of each iteration. How can we observe the values of variables while a program is running? Two common techniques are the use of the system's debugger program and the use of extra output statements designed solely for debugging purposes. We discuss these techniques in the section Testing and Debugging Hints.

Now let's look at some test cases that are specific to the different types of loops that we've seen in this chapter.

Count-Controlled Loops When a loop is count-controlled, you should include a test case that specifies the output for all the iterations. It may help to add an extra column to the test plan that lists the iteration number. If the loop reads data and outputs a result, then each input value should produce a different output to make it easier to spot errors. For example, in a loop that is supposed to read and print 100 data values, it is easier to tell that the loop executes the correct number of iterations when the values are 1, 2, 3, …, 100 than if they are all the same.

If the program inputs the iteration count for the loop, you need to test the cases in which the count is invalid. For example, when a negative number is input, an error message should be output and the loop should be skipped. You should also test various valid cases. When a count of 0 is input, the loop should be skipped; when a count of 1 is input, the loop should execute once; and when some typical number of iterations is input, the loop should execute the specified number of times.

Event-Controlled Loops In an event-controlled loop, you should test the situation in which the event occurs before the loop, in the first iteration, and in a typical number of iterations. For example, if the event is that EOF occurs, then try an empty file, a file with one data set, and another with several data sets. If your testing involves reading from test files, you should attach printed copies of the files to the test plan and identify each in some way so that the plan can refer to them. It also helps to identify where each iteration begins in the Input and Expected Output columns of the test plan.

When the event is the input of a sentinel value, you need the following test cases: The sentinel is the only data set, the sentinel follows one data set, and the sentinel follows a typical number of data sets. Given that sentinel-controlled loops involve a priming read, it is especially important to verify that the first and last data sets are processed properly.

Testing and Debugging Hints

  1. For each file that a program uses, check that all five of the required steps are performed: import the package System.IO, declare an object of the given file class, instantiate the file object, use the methods associated with the file object to perform input or output operations, and close the file when you are done using it.

  2. Remember that you must use a File object to create StreamReader and StreamWriter objects.

  3. Plan your test data carefully to test all sections of a program.

  4. Beware of infinite loops, where the expression in the While statement never becomes False. The symptom: The program doesn't stop. If you have created an infinite loop, the most likely error is in your logic. In a count-controlled loop, make sure the loop control variable is incremented within the loop. In a flag-controlled loop, make sure the flag eventually changes.

  5. Check the loop-termination condition carefully, and be sure that something in the loop causes it to be met. Watch closely for values that cause one iteration too many or too few (the "off-by—1" syndrome).

  6. Write out the consistent, predictable part of a loop's behavior in each iteration. Look for patterns that this establishes. Are they just what you want? Perform an algorithm walk-through to verify that all of the appropriate conditions occur in the right places.

  7. Trace the execution of the loop by hand with a code walk-through. Simulate the first few passes and the last few passes very carefully to see how the loop really behaves.

  8. Use a debugger if your system provides one. A debugger is a program that runs your program in "slow motion," allowing you to execute one instruction at a time and to examine the contents of variables as they change. If you haven't already done so, check to see if a debugger is available on your system.

  9. If all else fails, use debug output statements—output statements inserted into a program to help debug it. They output a message to a separate file that indicates the flow of execution in the program or reports the values of variables at certain points in the program.

    For example, if you want to know the value of variable beta at a certain point in a program, you could insert this statement:

    logFile.WriteLine("beta = " & beta)

    If this output statement is in a loop, you will get as many values of beta in the file associated with logFile as there are iterations of the body of the loop. When you are finished debugging your program, you can remove the debug output statements or just precede them with a ' so that they'll be treated as comments. (This practice is referred to as commenting out a piece of code.) You can remove the apostrophe if you need to use the statements again.

  10. An ounce of prevention is worth a pound of debugging. Use the checklist questions and design your loop correctly at the outset. It may seem like extra work, but it pays off in the long run.


Summary of Classes

Class Name |  |  | Comments
System.IO |  |  |
 | File |  |
 | StreamReader |  |
 | Value-Returning Instance Methods: |  |
 |  | Read() | Returns Int
 |  | ReadLine() | Returns String
 | Public Instance Method: |  |
 |  | Close() |
 |  | StreamWriter |
 | Public Instance Methods: |  |
 |  | Close() |
 |  | Write(Char) |
 |  | Write(Integer) |
 |  | Write(Long) |
 |  | Write(Single) |
 |  | Write(Double) |
 |  | WriteLine(Char) |
 |  | WriteLine(Integer) |
 |  | WriteLine(Long) |
 |  | WriteLine(Single) |
 |  | WriteLine(Double) |


Summary

Programs operate on data. If data and programs are kept separate, the data is available to use with other programs, and the same program can be run with different sets of data. Noninteractive input/output allows data to be prepared before a program is run and allows the program to run again with the same data in the event that a problem crops up during processing.

Data files are often used for noninteractive processing and to permit the output from one program to be used as input to another program. In Visual Basic we use three types of file classes: File, StreamReader, and StreamWriter. The File class is used strictly to create objects of the other two class types. The StreamReader class is used to read characters and lines (as strings) from data files, using the Read and ReadLine methods. The StreamWriter class is used to write characters and lines (as strings) to data files, using the Write and WriteLine methods that enable the output of all the standard Visual Basic data types and classes.

To use files, you must do five things: (1) import the library System.IO, (2) declare the file variables along with your other variable declarations, (3) instantiate each file object, (4) use methods associated with each file object to read or write it, and (5) call the Close() method with each file object when you are done with it.

The While statement is a looping construct that allows the program to repeat a statement as long as the value of the expression is True. When the value of the expression becomes False, the statement is skipped, and execution continues with the first statement following the loop.

With the While statement you can construct several types of loops that you use again and again. These types of loops fall into two categories: count-controlled loops and event-controlled loops.

In a count-controlled loop, the loop body is repeated a specified number of times. You initialize a counter variable right before the While statement. This variable is a loop control variable. The control variable is tested against the limit in the While expression. The last statement in the loop body increments the control variable.

Event-controlled loops continue executing until something inside the body signals that the looping process should stop. Event-controlled loops include those that test for a sentinel value in the data, end-of-file, or a change in a flag variable.

Sentinel-controlled loops are input loops that use a special data value as a signal to stop reading. EOF-controlled loops are loops that continue to input (and process) data values until there is no more data. To implement them with a While statement, you must test the value returned by the input method. The Read and ReadLine methods return -1 when they reach EOF. Sentinel-controlled loops usually require an updating read at the end of the loop.

A flag is a variable that is set in one part of the program and tested in another. In a flag-controlled loop, you must set the flag before the loop begins, test it in the While expression, and change it somewhere in the body of the loop.

Counting is a looping operation that keeps track of how many times a loop is repeated or how many times some event occurs. This count can be used in computations or to control the loop. A counter is a variable that is used for counting. It may be the loop control variable in a count-controlled loop, an iteration counter in a counting loop, or an event counter that counts the number of times a particular condition occurs in a loop.

Summing is a looping operation that keeps a running total of certain values. It is like counting in that the variable that holds the sum is initialized outside the loop. The summing operation, however, adds up unknown values; the counting operation adds a constant (1) to the counter each time.

When you design a loop, there are seven points to consider: how the termination condition is initialized, tested, and updated; how the process in the loop is initialized, performed, and updated; and the state of the program upon loop exit. By answering the checklist questions, you can bring each of these points into focus.

To design a nested loop structure, begin with the outermost loop. When you get to where the inner loop must appear, make it a separate module and come back to its design later.


Quick Check

Q: | 1. If a program is going to input 1,000 integer numbers, is interactive input appropriate? (p. 349) | 1. No. File input is more appropriate for programs that input large amounts of data.
Q: | 2. What happens when you associate an input file with an input file object? (p. 350) | 2. The variable is associated with the name of the disk file, and places the file pointer at the first piece of data in the file.
Q: | 3. What is the statement that reads in a string and stores the integer equivalent into number? (p. 357) | 3. number = Cint(infile.ReadLine())
Q: | 4. Write the first line of a While statement that loops until the value of the Boolean variable done becomes True. (p. 361) | 4. While (Not done)
Q: | 5. What are the four parts of a count-controlled loop? (pp. 363–364) | 5. The process being repeated, plus initializing, testing, and incrementing the loop control variable.
Q: | 6. Should you use a priming read with an EOF-controlled loop? (pp. 367–368) | 6. No, it is not necessary.
Q: | 7. How is a sentinel used to control a loop? (pp. 365–366) | 7. A sentinel is a value that is used to signal the end of the data. The loop expression compares each data value to the sentinel and the loop continues until they are equal.
Q: | 8. What is the difference between a counting operation in a loop and a summing operation in a loop? (pp. 368–369) | 8. A counting operation increments by a fixed value with each iteration of the loop; a summing operation adds unknown values to the total.
Q: | 9. What is difference between a loop-control variable and an event counter? (pp. 369–370) | 9. A loop-control variable controls the loop; an event counter simply counts certain events during execution of the loop.
Q: | 10. What kind of loop would you use in a program that reads the closing price of a stock for each day of the week? (pp. 370–372) | 10. Because there are five days in a business week, you would use a count-controlled loop that runs from 1 to 5.
Q: | 11. How would you extend the loop in Question 10 to make it read prices for 52 weeks? (pp. 370–372) | 11. Nest the original loop inside a count-controlled loop that runs from 1 to 52.

Answers

A: | 1. No. File input is more appropriate for programs that input large amounts of data.
A: | 2. The variable is associated with the name of the disk file, and places the file pointer at the first piece of data in the file.
A: | 3. number = Cint(infile.ReadLine())
A: | 4. While (Not done)
A: | 5. The process being repeated, plus initializing, testing, and incrementing the loop control variable.
A: | 6. No, it is not necessary.
A: | 7. A sentinel is a value that is used to signal the end of the data. The loop expression compares each data value to the sentinel and the loop continues until they are equal.
A: | 8. A counting operation increments by a fixed value with each iteration of the loop; a summing operation adds unknown values to the total.
A: | 9. A loop-control variable controls the loop; an event counter simply counts certain events during execution of the loop.
A: | 10. Because there are five days in a business week, you would use a count-controlled loop that runs from 1 to 5.
A: | 11. Nest the original loop inside a count-controlled loop that runs from 1 to 52.


Exam Preparation Exercises

    Chapter 9

  1. What are the five steps in using file input?

  2. Why do you need to use a File object when creating StreamReader and StreamWriter objects?

  3. Explain the difference between the CreateText and the OpenText methods.

  4. What does the following statement do?

    fileOut.Flush()

  5. What does the Read method for class StreamReader return?

    1. What value does the Read method return?

    2. What value does the ReadLine method return?

    3. Distinguish between a null string and an empty string.

  7. Explain the difference between a loop and a branch.

  8. What does the following loop print out? (number is of type Integer.)

    number = 1
While (number < 11)
  number +=
  out.WriteLine(number)
End While

  9. By rearranging the order of the statements (don't change the way they are written), make the loop in Exercise 8 print the numbers from 1 through 10.

  10. When the following code is executed, how many iterations of the loop are performed?

    number = 2
done = False
While (Not done)
   number = number * 2
   If (number > 64) Then
      done = True
   End If
End While

  11. What is the output of this nested-loop structure?

    i = 4
While (i >= 1)
   j = 2
   While (j >= 1)
      out.Write(j & " ")
      j -= 1
   End While
   out.WriteLine(i)
End While

  12. The following code segment is supposed to write out the even numbers between 1 and 15. (n is an Integer variable.) The code has two flaws in it.

    n = 2
While (n <> 15)(
   n = n + 2
   out.Write(n & " ")
End While

    1. What is the output of the code as written?

    2. Correct the code so that it works as intended.

  13. The following code segment is supposed to copy one line from file inFile to file outFile.

    inChar = inFile.Read()
While (inChar <> Constants.vbCrLf)
   inChar = inFile.Read()
   outFile.Write(inChar)
End While

    1. What is the output if the input line consists of the characters ABCDE?

    2. Rewrite the code so that it works properly.

  14. What sentinel value would you choose for a program that reads telephone numbers as integers?

  15. Consider the following code segment:

    sum = 0
i = 1
limit = 8
finished = False
While (i <= limit And Not(finished))
   number = Cint(dataFile.ReadLine())
   If (number > 0) then
      sum = sum + number
   ElseIf (number = 0)
      finished = True
   End If
   i += 1
End While
outLabel.Text = "End of test. " & sum & " " & number

    and these data values (written one per line):

    5   6   -3   7   -4   0   5   8   9

    1. What are the contents of sum and number after exit from the loop?

    2. Does the data fully test the program? Explain your answer.

  16. What is the output of the following program segment? (All variables are of type Integer.)

    i = 1
While (i <= 5)
   sum = 0
   j = 1
   While (j <= 1)
      sum = sum + j
      j += 1
   End While
   console.write(sum & " ")
   i += 1
End While

  17. The physical order of a program is the order in which the statements are __________ (written, executed).

  18. The logical order of a program is the order in which the statements are __________ (written, executed).

    1. What are the two major types of loops?

    2. Distinguish between a count-controlled loop and an event-controlled loop.

    3. What happens if you forget to increment the loop-control variable in a count-controlled loop?

    4. What happens if you forget to change the event within the body of an event-controlled loop?

    5. Name three kinds of event-controlled loops.

  20. Distinguish between an iteration counter and an event counter.

    1. What is an assignment expression?

    2. Write an assignment expression that can be used to control a reading loop with the Read method.

    3. Write an assignment expression that can be used to control a reading loop with the ReadLine method.


Programming Warm-Up Exercises

    Chapter 9

  1. Write the statement to declare a File object.

  2. Write the statements that associate an object of class StreamReader with file infile.dat.

  3. Write the statements that associate an object of class StreamWriter with file infile.dat.

  4. What does the following series of statements write in the file fileOut?

    fileOut.Write("W")
fileOut.Write(Constants.vbCrLf)
fileOut.Write(chr(88))
fileOut.Write(Constants.vbCrLf)
fileOut.Write(" What letter comes next?")

  5. What is printed by the following series of statements?

    fileOutWl.WriteLine("W")
fileOutWl.WriteLine(88)
fileOutWl.WriteLine(" What letter comes next?")

  6. Write a code fragment that reads two characters from fileIn and stores them in two Char variables, first and second. (fileIn, of class StreamReader, has been declared and given a value.)

  7. Write a code fragment that reads a line from fileInBuf and prints it in fileOut with blank lines before and after it.

  8. Write the statements that close fileOut and associate it with file dataOut.dat.

  9. Write a program segment that sets a Boolean variable dangerous to True and stops reading data if pressure (a Single variable being read in) exceeds 510.0. Use dangerous as a flag to control the loop.

  10. Here is a simple count-controlled loop:

    count = 1
While (count < 20)
   count += 1
End While

    1. List three ways of changing the loop so that it executes 20 times instead of 19.

    2. Which of those changes makes the value of count range from 1 through 21.

  11. Write a program segment that counts the number of times the integer 28 occurs in a file of 100 integers.

  12. Write a nested loop code segment that produces this output:

    1
1   2
1   2   3
1   2   3   4

  13. Write a program segment that reads a file of student scores for a class (any size) and finds the class average.

  14. Write a program segment that reads in integers and then counts and prints out the number of positive integers and the number of negative integers. If a value is zero, it should not be counted. The process should continue until end-of-file occurs.

  15. Write a program segment that adds up the even integers from 16 through 26, inclusive.

  16. Write a statement(s) that increments count by 1 and sets it back to 0 when it reaches 13.

  17. Write a program segment that prints out the sequence of all hour and minute combinations in a day, starting with 1:00 P.M. and ending with 12:50 P.M.

  18. Rewrite the code segment in Exercise 17 so that it prints the times in 10-minute intervals.

  19. Write a loop to count the number of not-equal operators (<>) in a file that contains a Visual Basic program. Your algorithm should count the number of times a less-than mark (<) followed by a greater-than mark (>) appears in the input. Read the input file one character at a time, keeping track of the two most recent characters, the current value, and the previous value. In each iteration of the loop, a new current value is read and the old current value becomes the previous value. When EOF is reached, the loop is finished.


Programming Problems

  1. Design and write a Visual Basic application that inputs an integer and a character from the screen. The file output should be a diamond composed of the character and extending the width specified by the integer. For example, if the integer is 11 and the character is an asterisk (*), the diamond would look like this:

    Click To expand

    If the input integer is an even number, it should be increased to the next odd number.

    Use meaningful variable names, proper indentation, appropriate comments, and good prompting messages. Use a Form object for input and a StreamWriter object for output.

  2. Write and design and a Visual Basic application that inputs an integer larger than 1 and calculates the sum of the squares from 1 to that integer. For example, if the integer equals 4, the sum of the squares is 30 (1 + 4 + 9 + 16). The output should be the value of the integer and the sum, properly labeled on the screen. The program should repeat this process for several input values. Use buttons and screen input and output.

  3. You are putting together a music CD for a party. You've arranged a list of songs in the order in which you want to play them. However, you would like to minimize the empty space (the CD plays for 67 minutes). So you want to figure out the total time for a group of songs and see how well they fit. Write and design and a Visual Basic application to help you do this. For example, if a song takes 7 minutes and 42 seconds to play, the data entered for that song would be

    462

    After all the data has been read, the program should print a message indicating the time remaining on the CD. The output should be in the form of a table with columns and heading written to a file. For example,

    Song Number

    Song Time

    Total Time

    Minutes

    Seconds

    Minutes

    Seconds

    1

    5

    10

    5

    10

    2

    7

    42

    12

    52

    5

    4

    19

    17

    11

    3

    4

    33

    21

    44

    4

    10

    27

    32

    11

    6

    8

    55

    41

    6

    There are 35 minutes and 54 seconds of space left on the 67 minute CD.

    Note that the output converts the input from seconds to minutes and seconds.

  4. Design and write an application that prints out the approximate number of words in a file of text. For our purposes, this is the same as the number of gaps following words. A gap is defined as one or more spaces in a row, so a sequence of spaces counts as just one gap. The newline character (Constants.vbCrLf) also counts as a gap. Anything other than a space or newline is considered to be part of a word. For example, there are 13 words in this sentence, according to our definition. The program should echo-print the data.

    Solve this problem with two different programs.

    1. Use a String object into which you input each line as a string. This approach is quite straightforward.

    2. Assume the String class does not exist, and input the data one character at a time. This approach is more complicated. (Hint: Count a space as a gap only if the previous character read is something other than a space.)

    Use meaningful variable names, proper indentation, and appropriate comments. Thoroughly test the programs with your own data sets.


Case Study Follow-Up Exercises

    Chapter 9

  1. Change the Incomes program in the following ways.

    1. The program prints an error message when a negative income value is input and then goes on processing any remaining data. Do not include the erroneous data in any of the calculations. Thoroughly test the modified program with your own data sets.

    2. The program does not crash when there are no males in the input file or no females (or the file is empty). Instead, it prints an appropriate error message. Test the revised program with your own data sets.

    3. The program rejects data sets that are coded with a letter other than "F" or "M" and prints an error message before continuing to process the remaining data. The program also prints a message indicating the number of erroneous data sets encountered in the file.

  2. Develop a thorough set of test data for the Incomes program as modified in Exercise 1.

  3. Rather than put the responsibility for reading in the application in Exercise 1, redesign this problem to extend StreamReader with two new methods, readGender and readSalary. Code and test your redesign.

  4. Use the Cstr function and the String methods IndexOf and Substring to print only two decimal places for all floating-point values. To accomplish this conversion, you must do the following tasks:

    • Convert the floating-point value to a string using the Cstr function.

    • Find the location of the decimal point using the IndexOf method.

    • Create a new string that is a substring of the original with only two characters following the decimal point using the Substring method.


Chapter 10: Additional Control Structures and Exceptions

Overview

Goals

  • To be able to write a Select Case statement for a multiway branching problem.

  • To be able to write a Do statement and contrast it with a While statement.

  • To be able to write a For statement as an alternative to a While statement.

  • To be able to choose the most appropriate looping statement for a given problem.

  • To be aware of Visual Basic's additional operators and their place in the precedence hierarchy with respect to each other.

  • To be able to use the Visual Basic exception-handling mechanism using the Try-Catch-Finally statement.

  • To be able to define an exception class and throw an exception.

In Chapters 6 and 9, we introduced Visual Basic statements for the selection and loopcontrol structures. In some cases, we introduced more than one way of implementing these structures. For example, selection may be implemented by an If structure. The If-(then) is sufficient to implement any selection structure, but Visual Basic provides the If-Else for convenience because the two-way branch is frequently used in programming.

This chapter introduces four new statements that are also nonessential to, but nonetheless convenient for, programming. One, the Select Case statement, makes it easier to write selection structures that have many branches. Two new looping statements, For and Do, make it easier to program certain types of loops.

This chapter also covers the Try-Catch-Finally statement used for exception-handling in Visual Basic. We also show you how to generate exceptions of your own.

Finally, we examine the remaining operators in Visual Basic and their place in the precedence hierarchy.


10.1 Additional Control Structures

The Select Case Statement

Test expression
The expression whose value determines which Case statement is selected. The expression may be a string or numeric expression.

The Select Case statement is a selection control structure for multiway branches. A Select Case statement is similar to nested If statements. The value of a test expression—any string or numeric expression—determines which of the branches is executed. Look at the following example Select Case statement:

Select Case (digit) ' The Case expression is (digit)
    Case 1
        Statement1  ' Statement1 is executed if digit is 1, then go
                    ' to Statement5

    Case 2, 3
        Statement2  ' Statement2 is executed if digit is 2 or 3, then go
                    ' to Statement5

    Case 4
        Statement3  ' Statement3 is executed if digit is 4, then go
                    ' to Statement5

    Case Else
       Statement4   ' Statement4 is executed if digit is any other value,
                    ' then go to Statement5
End Select
Statement5

In this example, digit is the test expression. The statement means "If digit is 1, execute Statement1 and break out of the Select Case statement and continue with Statement5. If digit is 2 or 3, execute Statement2 and continue with Statement5. If digit is 4, execute Statement3 and continue with Statement5. If digit is none of the values previously mentioned, execute Statement4 and continue with Statement5." Figure 10.1 shows the flow of control through this statement.

Click To expand
Figure 10.1: Flow of control in the example Select Case statement

Let's look at the syntax template for the Select Case statement; then we look at what actually happens when it is executed.

Click To expand

IntegralExpression is an expression of one of the types Char, Byte, Short, or Integer. StringExpression is, of course, any string. The CaseLabel in front of a statement is a label that begins with the keyword Case:

Click To expand

The To and Is keywords allow you to write more complex expressions in a Select Case statement. We will look at them a little later in this section.

In a case label, Expression is an expression of any numeric type or of string type. In our opening example that tests the value of digit, the following are the case labels:

Case 1
Case 2,3
Case 4
Case Else

As that example shows, there can be more than one value in a case label.

The value resulting from each expression in a case label does not have to be unique. However, if they are not, it is an indication of a logic error in the program. For example,

Select Case (digit)
    Case 1
        Msgbox(1)
    Case 2
        MsgBox(2)
    Case 2
        MsgBox(3)
    Case Else
        MsgBox(0)
End Select

There are two expressions that could execute when the value of digit is 2. Clearly, the first Case 2 is the correct case label, but having two case expressions that evaluate to 2 does not cause a syntax error.

Be careful: Case 1 does not mean the first case. We've listed the values in order because that makes the statement easier to read. But Visual Basic allows us to place them in any order. The following Select Case statement behaves in exactly the same way as our earlier example:

Select Case (digit)
    Case 2, 3
        Statement2

    Case 4
        Statement3
    Case 1
        Statement1
    Case Else
        Statement4
End Select

The flow of control through a Select Case statement goes like this. First, the Select Case expression is evaluated. Then each expression beside the reserved word Case is tested to see if it matches the Select Case expression. If the values match, control branches to the statement associated with that case label (the statement under the case label). From there, control proceeds to the statement following the end of the Select Case statement. If the value of the Select Case expression doesn't match any case value, then one of two things happens. If there is a Case Else label, control branches to the associated statement. If there is no Case Else label, the statements in the Select Case are skipped and control simply proceeds to the statement following the entire Select Case statement.

The following Select Case statement prints an appropriate comment based on a student's grade (grade is of type Char), though it could just as easily be of type String.

Select Case (grade)
    Case "A", "B"
        MsgBox("Good Work")
    Case "C"
        MsgBox("Average Work")
    Case "D", "F"
        MsgBox("Poor Work")
End Select

If grade does not contain one of the specified characters, none of the statements within the Select Case is executed. It would be wise to add a Case Else label to account for an invalid grade:

Select Case (grade)
    Case "A", "B"
        MsgBox("Good Work")
    Case "C"
        MsgBox("Average Work")
    Case "D", "F"
        MsgBox("Poor Work")
    Case Else
        MsgBox(grade & " is not a valid letter grade.")
End Select

A Select Case statement behaves exactly like an If-Then-ElseIf control structure. For example, our Select Case statement is equivalent to the following code:

If (grade = "A" Or grade = "B") Then
    MsgBox("Good Work")
ElseIf (grade = "C") Then
    MsgBox("Average Work")
ElseIf (grade = "D" Or grade = "F") Then
    MsgBox("Poor Work")
Else
    MsgBox(grade & " is not a valid letter grade.")
End If

Is either of these two versions better than the other? There is no absolute answer to this question. For this particular example, our opinion is that the Select Case statement is easier to understand because of its table-like form. When you are implementing a multiway branching structure, our advice is to use the one that you feel is easiest to read. Keep in mind that Visual Basic provides the Select Case statement as a matter of convenience. Don't feel obligated to use a Select Case statement for every multiway branch.

Earlier in this section we mentioned that the case label can be modified with the To and Is keywords. These keywords allow us to have more complicated expressions inside a case label. Let's start by looking at the To keyword.

To is used to specify a range in a case label expression. For example, in the example above where a student's grade is evaluated with a Select Case statement, we can use a range of numeric scores instead of letter grades to determine a student's progress.

Select Case (grade) ' grade is a numeric value
   Case 80 to 100
        MsgBox("Good Work")
    Case 70 to 79
        MsgBox("Average Work")
    Case 0 to 69
        MsgBox("Poor Work")
    Case Else
        MsgBox(grade & " is not a valid grade.")
End Select

A value of grade that is in the range 80 through 100 will trigger the message "Good Work". A score from 70 through 79, such as 73, will trigger the message "Average Work". Any grade ranging from 0 through 69 will trigger the message "Poor Work".

Yet another way to write a Select Case statement of this type is to use the Is keyword. Use Is when you want to test a relational expression in a case label. The following example demonstrates the use of Is with our student grade program segment:

Select Case (grade) ' grade is a numeric value
    Case Is > 79
        MsgBox("Good Work")
    Case Is >= 70
        MsgBox("Average Work")
    Case Is >= 0
        MsgBox("Poor Work")
    Case Else
        MsgBox(grade & " is not a valid grade.")
End Select

Let's trace two values of grade through this Select Case statement to see how Is works. First, let's assign the value 85 to grade. The first case label has the expression Is > 79. Since 85 is greater than 79, then the statement that follows the label is triggered, and the message "Good Work" is displayed. Now let's assign the value 73 to grade. Since 73 is less than 79, the first case label is skipped. However, 73 is greater than or equal to 70, so the message inside the second case label, "Average Work", is displayed.

You can use all of the relational operators (>, <, >=, <=, <>) with Is. You would not use the equal sign (=) with Is because that is the expression tested with the Case keyword alone.

Start Sidebar
May We Introduce
Admiral Grace Murray Hopper

From 1943 to her death on New Year's Day in 1992, Admiral Grace Murray Hopper was intimately involved with computing. In 1991, she was awarded the National Medal of Technology "for her pioneering accomplishments in the development of computer programming languages that simplified computer technology and opened the door to a significantly larger universe of users."

Click To expand

Admiral Hopper was born Grace Brewer Murray in New York City on December 9, 1906. She attended Vassar and received a Ph.D. in mathematics from Yale. For the next 10 years, she taught mathematics at Vassar.

In 1943, Admiral Hopper joined the U.S. Navy and was assigned to the Bureau of Ordnance Computation Project at Harvard University as a programmer on the Mark I. After the war, she remained at Harvard as a faculty member and continued work on the Navy's Mark II and Mark III computers. In 1949, she joined Eckerly-Mauchly Computer Corporation and worked on the UNIVAC I. It was there that she made a legendary contribution to computing: She discovered the first computer "bug"—a moth caught in the hardware.

Admiral Hopper had a working compiler in 1952, a time when the conventional wisdom was that computers could do only arithmetic. Although not on the committee that designed the computer language COBOL, she was active in its design, implementation, and use. COBOL (which stands for Common Business-Oriented Language) was developed in the early 1960s and is still widely used in business data processing.

Admiral Hopper retired from the Navy in 1966, only to be recalled within a year to full-time active duty. Her mission was to oversee the Navy's efforts to maintain uniformity in programming languages. It has been said that just as Admiral Hyman Rickover was the father of the nuclear navy, Rear Admiral Hopper was the mother of computerized data automation in the Navy. She served with the Navy Data Automation Command until she retired again in 1986 with the rank of rear admiral. At the time of her death, she was a senior consultant at Digital Equipment Corporation.

During her lifetime, Admiral Hopper received honorary degrees from more than 40 colleges and universities. She was honored by her peers on several occasions, including the first Computer Sciences Man of the Year award given by the Data Processing Management Association, and the Contributions to Computer Science Education Award given by the Special Interest Group for Computer Science Education, which is part of the ACM (Association for Computing Machinery).

Admiral Hopper loved young people and enjoyed giving talks on college and university campuses. She often handed out colored wires, which she called nanoseconds because they were cut to a length of about one foot—the distance that light travels in a nanosecond (billionth of a second). Her advice to the young was, "You manage things, you lead people. We went overboard on management and forgot about leadership."

When asked which of her many accomplishments she was most proud of, she answered, "All the young people I have trained over the years."

End Sidebar

The Do While Statement

The Do While statement is one of the four forms of Visual Basic's Do control structures. The Do While statement looks very similar to the While statement. The syntax template for the Do While statement is this:

Click To expand

As usual in Visual Basic, Statement is either a single statement or a block. The Do While statement

Do While (Expression)
    Statement1
    Statement2
    :
    StatementN
Loop

means execute the statements between Do While and Loop "while" the expression remains True. In this way, the Do While statement executes just like the While statement.

Here is a summing loop from Chapter 9 written with a While statement:

sum = 0
count = 1
While (count <= 10)
    number = CInt(dataFile.ReadLine())
    sum = sum + number
    count += 1
End While

Here is the same program written with a Do While statement:

sum = 0
count = 1
Do While (count <= 10)
    number = CInt(dataFile.ReadLine())
    sum = sum + number
    count += 1
Loop

As you can see, the only difference between these two programs is in the syntax of the different statement types.

Looking at the similarities between the While statement and the Do While statement, you may be wondering why Visual Basic includes a Do control structure. The reason is to provide three other loop types that are not like the While statement. We will look at the first of these next—the Do-Loop While statement.

The Do-Loop While Statement

The Do-Loop While statement is a looping control structure in which the loop condition is tested at the end (bottom) of the loop. This format guarantees that the loop body executes at least once. The syntax template for the Do-Loop While is this:

Click To expand

As with the Do While statement, Statement is either a single statement or a block.

The Do-Loop While statement

Do
     Statement1
     Statement2
     :
     StatementN
Loop While (Expression)

means "Execute the statements between Do and Loop While as long as Expression still has the value True at the end of the loop." This means that you execute the statements before you test the expression.

Let's compare a While loop and a Do-Loop While loop that do the same task: They find the first period in a file of data. Assume that there is at least one period in the file.

While Solution

inputChar = Chrw(dataFile.Read())
While (inputchar <> ".")
    inputChar = Chrw(dataFile.Read())
End While

Do-Loop While Solution

Do
    inputChar = Chrw(dataFile.Read())
Loop While (inputChar <> ".")

The While solution requires a priming read so that inputChar has a value before the loop is entered. This isn't required for the Do-Loop While solution because the input statement within the loop is executed before the loop condition is evaluated.

We can also use the Do-Loop While to implement a count-controlled loop if we know in advance that the loop body should always execute at least once. Below are two versions of a loop to sum the integers from 1 through n.

While Solution

sum = 0
counter = 1
While (counter <= n)
    sum = sum + counter
    counter += 1
End While

Do-Loop While Solution

sum = 0
counter = 1
Do
    sum = sum + counter
    counter += 1
Loop While (counter <= n)

If n is a positive number, both of these versions are equivalent. But if n is 0 or negative, the two loops give different results. In the While version, the final value of sum is 0 because the loop body is never entered. In the Do-Loop While version, the final value of sum is 1 because the body executes once and then the loop test is made.

Because the While statement tests the condition before executing the body of the loop, it is called a pretest loop. The Do-Loop While statement does the opposite and thus is known as a posttest loop. Figure 10.2 compares the flow of control in the While and the Do-Loop While loops.

Click To expand
Figure 10.2: Flow control of White and Do-Loop While

The Do Until and the Do-Loop Until Statements

In the Do While and Do-Loop While statements, the statements inside the body of the loop are executed as long as the expression is True. There are times when you want to iterate over a body statement as long as the expression tested is False. You can do this with a Do Until statement.

The syntax template for the Do Until statement looks like this:

Click To expand

In general, the Do Until statement is not used very often because the logic used to develop most loops is based on iterating over a series of statements while the condition is True. Still, most programs written with a Do While statement can also be written with a Do Until statement. As an example, let's look at a count-controlled loop that sums the integers from 1 through n written with a Do Until statement.

sum = 0
counter = 1
Do Until (counter > n)
    sum = sum + counter
    counter += 1
Loop

Notice that, in this example, the main difference between the Do While statement and the Do Until statement is in the logic of the relational condition. In the Do While statement, the test is counter <= n; in the Do Until statement, the test is counter > n.

The Do-Loop Until statement works like the Do Until statement, but with the test at the bottom of the loop. The syntax template for the Do-Loop Until statement is

Click To expand

Here is the program that sums the integers from 1 through n with the Do-Loop Until statement.

sum = 0
counter = 1
Do
   sum = sum + counter
   counter += 1
Loop Until (counter > n)

When we finish introducing new looping constructs, we offer some guidelines for determining when to use each type of loop.

The For Statement

The For statement is designed to simplify the writing of count-controlled loops. The following statement prints out the integers from 1 through n:

For count = 1 To n
    outFile.WriteLine(count)
Next count

This For statement means "Initialize the loop control variable count to 1. While count is less than or equal to n, execute the output statement and increment count by 1. Stop the loop after count has been incremented to n + 1."

The syntax template for a For statement is

Click To expand

Start is the initial value of the loop control variable and can be any expression that yields a numeric value. End is the final value of the loop control variable and is used to determine when to stop the loop. In other words, a For statement will loop while the counter variable is less than or equal to the End value.

Step is enclosed in shading to indicate that including this keyword is optional. Without Step, a For statement automatically increments the counter variable by 1. If you want to alter the increment, you use Step followed by the amount of the increment. For example, to increment the counter variable by 2, you would write

For counter = 1 to 10 Step 2

Step can also be used to decrement a For statement by specifying a negative value after Step. To make a For statement of this type work, be sure the Start value is larger than the End value. To illustrate the concepts of incrementing and decrementing For statements, here are two loops that execute the same number of times (50):

For loopCount = 1 To 50
    ⋮
Next loopCount

For loopCount = 50 To 1 Step -1
    ⋮
Next loopCount

Just like other types of loops, For loops may be nested. For example, the nested For structure

For lastNum = 1 To 7
    For numToPrint = 1 to lastNum
        outFile.Write(numToPrint)
    Next numToPrint
    outFile.WriteLine()
    Next lastNum

prints the following triangle of numbers.

1
12
123
1234
12345
123456
1234567

To use For loops intelligently, you should know the following facts.

  1. The Start, End, and increment values may be positive or negative, but the loop will not be executed if the Start value is greater than the End value and the increment is positive, or if the Start value is less than the End value and the increment is negative.

  2. If the increment is 0, the result will be an infinite loop.


10.2 Guidelines for Choosing a Looping Statement

Here are some guidelines to help you decide when to use each of the three looping statements (While, Do (all types), and For).

  1. If the loop is a simple count-controlled loop, the For statement is a natural. Concentrating the three loop-control actions—initialize, test, and increment/decrement—into one location (the first line of the For statement) reduces the chances of forgetting to include one of them.

  2. If the loop is an event-controlled loop whose body should execute at least once, a Do-Loop While or Do-Loop Until statement is appropriate.

  3. If the loop is an event-controlled loop and nothing is known about the first execution, use a While statement or a Do While statement.

  4. When in doubt, use a While statement.


10.3 Exception-Handling Mechanism

In the last chapter, we defined an exception as an unusual situation that is detected while a program is running. An exception halts the normal execution of a method. There are three parts to an exception-handling mechanism: defining the exception, raising or generating the exception, and handling the exception. We look first at handling exceptions and then at defining and raising them.

The Try-Catch-Finally Statement

As part of introducing files in Chapter 9, we mentioned that if you tried to open a file that didn't exist, Visual Basic will report an exception and the program will stop. However, if we include the right code, via the Try-Catch-Finally statement, we can handle the exception so that the program won't quit. This is called catching an exception, and because it involves a branch in control flow, it's natural to discuss it here.

When an error occurs in a method call, it isn't always possible for the method itself to take care of it. For example, suppose we ask the user for a file name in a dialog, get the name from a text box, and then we attempt to open the file (prepare it for reading by calling the StreamReader constructor). The constructor discovers that the file doesn't exist and is thus unable to open it. Perhaps the file has been deleted, or maybe the user just mistyped the name. The constructor has no way of knowing that the proper response to the error is to ask the user to reenter the name. Because the constructor can't appropriately deal with this error, it doesn't even try. It simply passes the problem back to Visual Basic.

When a call returns with an exception, normal execution ends and Visual Basic looks to see if there is code to take care of the problem. That code is called an exception handler and is part of the Try-Catch-Finally statement.

Exception handler
A section of a program that is executed when an exception occurs. In Visual Basic, an exception handler appears within a Catch clause of a Try-Catch-Finally control structure.

The syntax diagram for a Try-Catch-Finally statement is

Click To expand

As the diagram shows, the statement has three main parts. The first part is the keyword Try and a statement sequence. The second part is an optional series of Catch clauses, each consisting of the keyword Catch, a single parameter declaration enclosed in parentheses, and a statement sequence. The last part is also optional and consists of the keyword Finally and a statement sequence.

When a statement or series of statements in a program might result in an exception, we enclose them in the statement sequence following Try. For each type of exception that can be produced by the statements, we write a Catch clause. Here's an example:

Try
    …    ' Statements that try to open a file
Catch except As System.IO.IOException
    …    ' Statements that execute if the file can't be opened
Finally
    …    ' Statements that are always executed
End Try

The Try statement is meant to sound something like the coach telling the gymnast, "Go ahead and try this move that you're unsure of, and I'll catch you if you fall." We are telling the computer to try executing some operations that might fail, and then we're providing code to catch the potential exceptions. The Finally clause provides an opportunity to clean up, regardless of what happens in the Try and Catch blocks. We focus on the execution of a Try statement without a Finally clause (a Try-Catch statement) and briefly describe what happens when we add the Finally clause.

Execution of Try-Catch If none of the statements in the Try block throws an exception, then control transfers to the statement following the entire Try-Catch statement. That is, we try some statements, and if everything goes according to plan, we just continue on with the succeeding statements.

When an exception occurs, control is immediately transferred to the statement sequence associated with the appropriate Catch. It is important to realize that control jumps directly from whatever statement caused the exception to the Catch clause. If there are statements in the Try clause following the one that caused the exception, they are skipped. If the Catch statement sequence completes without causing any new exceptions, then control transfers to the next statement outside of the Try-Catch structure.

How does the computer know which Catch is appropriate? It looks at the class of the parameter declared in each one and selects the first one with a class that matches the thrown exception. Given how Visual Basic uses objects for just about everything, it should come as no surprise that an exception is an object and has a class. We've already seen one such class, IOException. This is a general class you can use when performing file I/O. There are more specific classes for special situations you can use in your programs. Two of these are FileNotFoundException and DirectoryNotFoundException. FileNotFoundException is thrown when a specified file cannot be found or opened for some reason. DirectoryNotFoundException is thrown when a file system directory called for cannot be found. If we use these two exception classes in a program, we can check to see if both the directory and file we specify are found on the system. Let's look at an example of a Try-Catch statement to illustrate this process.

Try
    ' Some statements
Catch badDir As DirectoryNotFoundException
    ' Statements to handle directory not found errors
Catch badFile As FileNotFoundException
    ' Statements to handle file not found errors
End Try

The computer begins by executing the statements within the Try statement sequence. If one of them causes a DirectoryNotFoundException, then control jumps to the first Catch clause. If instead a statement causes a FileNotFoundException, then control jumps to the second Catch clause.

What if a statement throws an exception that isn't among the Catch clauses. In that case, the Try statement fails to catch the error, and its enclosing method throws the exception to Visual Basic, which will normally cause the program to quit and display an error message.

The exception object that is passed to the Catch clause through its parameter has a value-returning method associated with it called Message. The Message method returns a string containing a message about the exception. For example, it might contain the name of the file that could not be opened, as well as the methods that threw the exception. Thus, you could write the following statement in a Catch clause to display a message.

Catch except As IOException
    Messagebox.Show(except.Message)

Let's look at an actual example. Suppose we have prompted the user to enter a file name into a text box, and upon clicking a button, the button's Click event is called to open that file as a StreamWriter. We could use the following code in the button's Click event to try to open the file. If the file can't be opened, we display an error message, and clear the text box so that the user can try again.

fileName = txtFile.Text
Try
    outFile = theFile.CreateText(filename)
Catch except As IOException
    errorLabel.Text = "Unable to open file " & fileName
    txtFile.Text = ""
    txtFile.Focus
End Try

Execution of Try-Catch-Finally When a Finally clause is present in a Try statement, the statement sequence following Finally is always executed no matter what happens in the Try and Catch clauses. Thus, even when a Catch causes a new exception, the Finally clause is executed. The Finally clause gives us an opportunity to clean up after a failed Catch. In writing the algorithm for the Finally clause, however, it is important to realize that it is always executed, even if the Try succeeds.

In this text we use only Try-Catch statements, and we keep our exception handlers simple so that they won't produce additional exceptions. The Finally clause is really only needed when a Catch contains statements that might generate a new exception and we need to undo some of its processing before the exception is thrown.

Generating an Exception with Throw

The .NET Framework classes are not the only classes that can generate exceptions. Here we introduce the Throw statement, which is the statement that we use when raising or generating an exception.

All exceptions are thrown by a Throw statement. The syntax of the Throw statement is quite simple.

Click To expand

The ObjectExpression must be either a variable or a value that inherits from the Exception class. When an exception is thrown, Visual Basic goes looking for a Catch statement that can handle that specific type of exception.

The Throw statement may be written within a Try statement that is intended to catch it. In that case, control is transferred to the Catch clause with the corresponding type.

More often, the Throw occurs inside a method that is called from within a Try statement, as shown in Figure 10.3. Visual Basic first looks for a Catch within the method. When it fails to find one, it causes the method to return. Visual Basic then looks around the point where the method was called, and finds an appropriate Catch clause. The Catch is executed and control proceeds to the statement following the Try-Catch.

Click To expand
Figure 10.3: Throwing an exception to be caught in a calling method

If Visual Basic can't find a matching Catch, it causes the method containing the call to also return. The series of returns can lead all the way back to the main program. If the exception isn't caught by this time, the program stops and an error message is displayed.

We can throw any of the standard exceptions that Visual Basic provides. For example,

Throw New FileNotFoundException

It's actually quite rare to throw one of the predefined exceptions. One situation where we might do so is when we've caught such an exception in a Catch clause but there is some aspect of handling it that must be passed to a higher level method.

Instead of throwing a predefined exception, we typically want to define and throw a new exception type that is unique to a class. A user of the class must then handle that type of exception. For example, in our Address class from Chapter 8, we could have validated the zip code. Basic zip codes are five digits or less. If a zip code is greater than 99,999 we can throw a ZipCodeInvalidException in the constructor for an Address object as follows.

Imports System
Public Class Address
    Private aName As String
    Private aZip As Integer
    Dim except As String
    Dim inExcept As Exception
    Public Sub new()
        MyBase.New()
    End Sub
    Property name() As String
        Set
            aName = value
        End Set
        Get
            Return aName
        End Get
    End Property
    Property zip() As Integer
        Set
            If (aZip <= 99999) Then

            aZip = value
        Else
            Throw New ZipCodeInvalidException(e, ie)
        End If
    End Set
    Get
        Return aZip
    End Get
End Property
End Class

We would call this method within a Try-Catch statement.

myAddress = New Address()
Try
    myAddress.Name = "Nell Dale"
    myAddress.Zip = 7344
Catch zipCode As ZipCodeInvalidException
    ' Exception handler
    ' Print bad zip code
    Messagebox.Show(zipCode.Message)
End Try

Whatever method is trying to create an Address object with an invalid zip code is then responsible for handling the error. But, of course, we also need to define the class called ZipCodeInvalidException.

Exception Classes

Notice that Throw must have an exception object to throw. Exception objects are very simple to create. Their class name conveys the basic information that tells Visual Basic what sort of exception is being thrown. All we typically need to add is an error message or some other piece of information that helps the Catch clause handle the error.

The predefined type Exception provides the mechanism for throwing an exception and provides properties and methods for displaying an error message. Thus, all we have to do is inherit the Exception class within our own class name and supply a constructor for displaying the error message. Here is how we define ZipCodeInvalidException.

Public Class ZipCodeInvalidException
    Inherits Exception
    Public Sub New(ByVal message As String, ByVal InnerException As Exception)
        message = "Invalid zip code"
    End Sub
End Class

The second parameter in the constructor, InnerException, is a required parameter for all Exception classes. This parameter allows you to store an exception in order to call another, more descriptive error, if available. Our Exception classes will not utilize this parameter.

Now look at the Try-Catch in the previous section.

myAddress = New Address()
Try
    myAddress.Name = "Nell Dale"
    myAddress.Zip = 7344
Catch zipCode As ZipCodeInvalidException
    ' Exception handler
    ' Print bad zip code
    Messagebox.Show(zipCode.Message)
End Try

In the Catch clause, we use Message to retrieve the same string that was used to instantiate the object of class ZipCodeInvalidException.

Whenever you design a class, you should consider whether there are error conditions that cannot be handled strictly within the class. Exceptions are, as their name implies, meant to be used for exceptional situations. We recommend using an exception only when there is no simple way to handle an error.


10.4 Additional Visual Basic Operators

Visual Basic has a rich variety of operators that allow you to manipulate values of the primitive data types. Operators you have learned about so far include the assignment operator (=), the arithmetic operators (+, -, *, /, Mod), the relational operators (=, <>, <, <=, >, >=), the string concatenation operator (&), and the conditional logical operators (Not, And, Or).

Visual Basic also has many specialized operators. Here is a table of these additional operators. As you inspect the table, don't panic—a quick scan will do.

Operator | Remarks
Combined-assignment operators |
+= | Add and assign
-= | Subtract and assign
*= | Multiply and assign
/= | Divide and assign
^= | Raise to a power and assign
\= | Integer divide and assign
&= | Concatenate and assign
Other Comparison Operators |
Is | Compares an object to a data type
Like | Compares a string to a pattern
Bitwise Operators | Numeric operands only
BitAnd | Bitwise And comparison
BitOr | Bitwise Or comparison
BitNot | Bitwise Not comparison
BitXor | Bitwise Xor comparison
Miscellaneous Data Types |
AddressOf | Creates a reference to a procedure
GetType | Returns the type within a class object

The operators in this table, along with those you already know, comprise all of the Visual Basic operators.

Assignment Operators and Assignment Expressions

Visual Basic has several assignment operators. The equal sign (=) is the basic assignment operator. When combined with its two operands, it forms an assignment expression (not an assignment statement). Every assignment expression has a value and a side effect, namely, that the value is stored into the object denoted by the left-hand side. For example, the expression

Assignment expression
A Visual Basic expression with (1) a value and (2) the side effect of storing the expression value into a memory location
delta = 2 * 12

has the value 24 and the side effect of storing this value into delta.

In addition to the = operator, Visual Basic has several combined assignment operators (+=, *=, and the others listed in our table of operators). These operators have the following semantics:

Statement | Statement Equivalent
i += 5 | i = i + 5
pivotPoint *= n + 3 | pivotPoint = pivotPoint * (n + 3)

The combined assignment operators are sometimes convenient for writing a line of code more compactly, but you can do just fine without them. We only use them in this text for incrementing a variable by 1, as in:

k += 1      ' Instead of k = k + 1

Bitwise Operators

The bitwise operators listed in the operator table (BitAnd, BitOr, and so forth) are used for manipulating individual bits within a memory cell. This book does not explore the use of these operators; the topic of bit-level operations is most often covered in a course on computer organization and assembly-language programming.

Operator Precedence

Following is a summary of operator precedence for the Visual Basic operators we have encountered so far, excluding the bitwise operators. (Appendix B contains the complete list.) Table 10.1 lists the operators in their order of precedence, from highest to lowest.

Table 10.1: Precedence (highest to lowest)
Operator | Associativity | Remarks
^ | Left to right |
Unary +, Unary - | Left to right |
* / | Left to right |
+ - & | Left to right |
= <> < > >= <= Is Like | Left to right | Relational operators
Not | Right to left |
And | Left to right |
Or | Left to right |
= += -= /= ^= *= | Right to left | Assignment operators

The column labeled Associativity describes grouping order. Within a precedence level, most operators group from left to right. For example,

a - b + c

means

(a - b) + c

and not

a - (b + c)

Certain operators, though, group from right to left—specifically, the unary operators and the assignment operators. Look at the unary -operator, for example. The expression

sum = - -1

means

sum = -(-1)

instead of the meaningless

sum = (- -)1

This associativity makes sense because the unary -operation is naturally a right-to-left operation.

Start Sidebar
Problem-Solving Case Study
Monthly Rainfall Averages

Statement of the Problem Meteorologists have recorded monthly rainfall amounts at several sites throughout a region of the country. You have been asked to write an application that reads one year's rainfall amounts from sites within the region from a file and prints out the average of the 12 values for each of the sites in a separate file. The first line of a data set is the name of the site, and the 12 values for the site follow on the next line with exactly one blank in between each value. The data file is named "rainData.dat".

Brainstorming This problem sounds familiar. We wrote an application in Chapter 5 that took rainfall amounts from observers and calculated the running average. In that problem we used a form for input and output; this program asks for file input and output. Calculating an average should be the same, but where the calculation takes place is different. In the previous problem the calculation took place in the button's Click event. There are no buttons in this problem.

There are other nouns in the problem, but they describe the context like region, country, and sites, so they are not objects in the solution. So, what are the objects? The application and the file objects.

Click To expand

Filtering This looks like a very sparse set of classes for a problem that includes calculating a series of averages. This problem is one that has a process repeated over different data, but does not have any objects other than numeric values and files. What about error conditions? Well, we can't determine what they might be at this stage other than to say that the data might have been entered incorrectly in the file. Let's add an exception class to our list of classes. Before we look at the responsibilities of each class, let's name them. Let's call the application class RainFall and the exception class DataSetException.

Click To expand

Scenarios The processing takes place within the application class RainFall, processing a data set, writing the average, repeating the process until there are no more data sites. What happens if an input error occurs while reading the data file? What happens if a data value contains a non-numeric character? What happens if there is a negative rainfall amount? These are all error conditions we must handle. For the moment, let's just say that RainFall has the responsibility to throw exceptions if need be.

CRC Cards On the CRC card, we summarize RainFall's responsibility as Process data.

Click To expand

Responsibility Algorithms Preparing the file for input and output has become so routine that we do not need to write the algorithms for these. The third responsibility, Processing data, is the heart of the problem. At the topmost level of the design, we need a loop to process the data from all the sites. Each iteration must process one site's data, then check for a new site name. The program does not know in advance how many recording sites there are, so the loop cannot be a count-controlled loop. Although we can make any of For, While, or Do-Loop While work correctly, we use a Do-Loop While under the assumption that the file definitely contains at least one site's data. Therefore, we can set up the loop so that it processes the data from a recording site and then, at the bottom of the loop, decides whether to iterate again.

...........................................................................
Process Data                                                        Level 1

 Prepare input file, rainData.dat
 Prepare output file, outFile.dat
 Read data set name
 Do
   Process one site
   Read data set name
 Loop While (more data)
 Close files

Processing one site requires another loop to input 12 monthly rainfall amounts and form their sum. Using the summing technique we are familiar with now, we initialize the sum to 0 before starting the loop, and each loop iteration reads another number and adds it to the accumulating sum. A For loop is appropriate for this task, because we know exactly 12 iterations must occur. How are we going to handle the error conditions brought up in the scenarios? We had better put the For loop in a Try-Catch statement. Once we have the value we should throw an exception if it is negative.

...........................................................................
Process One Site                                                    Level 2

 Set sum to 0.0
 Try to Get a line of values
    For months going from 1 to 12
       Get rainfall amount
       If amount is negative
            throw DataSetException("negative value found")
       Else
       Set sum to sum + amount
    Write "Average for" & data set name & "is" & sum / 12
 Catch and handle exceptions

Because all of the input values are on one line with a blank in between, we have to extract the characters that make up one value so we can convert it. We can use the String methods IndexOf and Substring to extract a string that represents exactly one floating-point value.

...........................................................................
Get Rainfall Amount                                                 Level 3

 Set index to line.IndexOf(" ")
 Set currentValue to line.Substring(0,index)
 Set line to substring(index+1, line.Length() - (index+1))
 Set amount to CDbl(currentValue)

Before we go on, we had better hand-simulate this algorithm. Let's apply it to the following input string.

"23.5 5.6 5.44"

index | currentValue | line | amount
4 | "23.5" | "5.6 5.44" | 23.5
3 | "5.6" | "5.44" | 5.6
error | "5.44" |  |

There is an error in the logic. Unless there is a blank line after the last value rather than an end of line, the IndexOf method returns a -1, indicating there isn't another blank. We then use the index in the next statement, producing an error in method Substring. We need to check to be sure that a blank is found. If not, the remaining string is the last value in the line.

...........................................................................
Get Rainfall amount (revised)                                       Level 3

 Set index to line.IndexOf(" ")
 If index > 0 Then
    Set currentValue to line.Substring(0, index)
    Set line to dataLine.Substring(index+1, dataLine.Length() - (index+1))
 Else
    Set currentValue to line
 Set amount to CDbl(currentValue)

We now need to decide what other errors might occur, how to catch them, and how to handle them. The file can throw an IOException exception and our own code can throw a DataSetException. Let's catch the IOException, print out the site name, and end the program. For the DataSetException, let's print out the site name with an error message, and continue processing with the next site.

...........................................................................
Catch and Handle Exceptions

 Catch IOException
   Write "I/O Exception with site " & data set name
   exit program
 Catch DataSetException
   Write except.Message() & data set name

The only abstract step left is to determine when the outer loop finishes. If we read in a new site name and there is no more data, the value of inFile.Peek will be -1.

...........................................................................
More Data

 inFile.Peek <> -1

Assumptions The file contains data for at least one site.

End Sidebar


Testing and Debugging

The same testing techniques we used with While loops apply to Do and For loops. There are, however, a few additional considerations with these loops.

The body of a Do loop always executes at least once. Thus, you should try data sets that show the result of executing a Do loop the minimal number of times.

With a data-dependent For loop, it is important to test for proper results when the loop executes zero times. This occurs when the starting value is greater than the ending value (or less than the ending value if the loop control variable is being decremented).

When a program contains a Select Case statement, you should test it with enough different data sets to ensure that each branch is selected and executed correctly. You should also test the program with a Select Case expression whose value is not in any of the case labels.

A program that handles exceptions must be tested to ensure that the exceptions are generated appropriately and then properly handled. Test cases must be included to cause exceptions to occur and to specify the expected results from handling them.

Testing and Debugging Hints

  1. Case labels in a Select Case statement are made up of values, not variables. They may, however, include named constants and expressions involving only constants.

  2. If there is a possibility that the value of a Select Case expression might not match one of the case constants, you should provide a Case Else alternative.

  3. Double-check long Select Case statements to make sure that you haven't omitted any branches.

  4. The Do Loop While loop is a posttest loop. If there is a possibility that the loop body should be skipped entirely, use a While loop or a For statement.

  5. When the loop control variable of a For statement is supposed to be incremented, be sure the Step value is positive; if the loop control variable is supposed to be decremented, be sure the Step value is negative.

  6. Make sure that all exceptions are either caught or forwarded as appropriate.


Summary of Classes

We have not introduced any new classes in the Visual Basic framework in this chapter, nor have we designed any classes that should go into a library to use later.


Summary

The Select Case is a multiway selection statement. It allows the program to choose among a set of branches. A Select Case statement can always be simulated by an If-Then-ElseIf structure. If a Select Case can be used, however, it often makes the code easier to read and understand.

The Do loops are general-purpose looping statements. The Do While is just like the While statement; the Do-Loop While is also like the While statement, except that its test occurs at the end of the loop, guaranteeing at least one execution of the loop body. As with a While loop, a Do-Loop While continues as long as the loop condition is True. The Do Until and the Do-Loop Until loops continue until the condition becomes True, or while the condition is False. These loops are used less often than While-type loops, but their use is sometimes warranted by the logic of the program. The Do-Loop While and the Do-Loop Until are convenient for loops that test input values and repeat if the input is not correct.

The For statement is also a looping statement and is commonly used to implement count-controlled loops. The initialization, testing, and incrementation (or decrementation) of the loop-control are centralized in one location, the first line of the For statement.

An exception occurs when an error condition is encountered in a method and the method cannot directly resolve the problem. The method is said to throw an exception, which we can catch using a Try-Catch statement. Catching an exception and handling it properly enables the program to continue executing, rather than allowing the error to be passed to Visual Basic, which halts the program with an error message. Typical exceptions include IOException and ArithmeticException.

The Throw statement gives us the ability to throw exceptions when we detect them. When we create new classes derived from Exception, we can Throw exceptions that are specific to our classes and methods. Those exceptions can then be caught and handled in ways that are more appropriate than would be possible if we were restricted to using the exception types in the Visual Basic framework.

The For statement, Do loops, and Throw statements are the ice cream and cake of Visual Basic. We can live without them if we absolutely must, but they are very nice to have.

Similarly, the additional operators that Visual Basic supplies, such as += and *=, are sometimes convenient shortcuts. But we can program effectively without them. Often, the use of the less common operators results in code that is harder to understand and so we recommend that you avoid them. Even so, you must be aware of their meaning so that you can interpret them when you encounter code written by a programmer who values compact syntax over clarity.


Quick Check

Q: | 1. Given a Select Case expression that is the Integer variable nameVal, write a Select Case statement that writes the following to StreamWriter file outData: your first name if nameVal = 1, your middle name if nameVal = 2, and your last name if nameVal = 3. (pp. 402–405) | 1. Select Case (nameVal)Case 1outData.WriteLine(`Mary`)Case 2outData.WriteLine(`Lynn`)Case 3outData.WriteLine(`Smith`) End Select
Q: | 2. How would you change the answer to Question 1 so that it writes an error message if the value is not 1, 2, or 3? (pp. 405–406) | 2. Select Case (nameVal)Case 1 outData.WriteLine(`Mary`)Case 2 outData.WriteLine(`Lynn`)Case 3 outData.WriteLine(`Smith`)Case Else outData.WriteLine(`Invalid name value`) End Select
Q: | 3. What is the primary difference between a While loop and a Do While loop? (pp. 408–409). | 3. The body of a Do While always execute at least once; the body of a While may not execute at all.
Q: | 4. A certain problem requires a count-controlled loop that starts at 10 and counts down to 1. Write the heading (first line) of a For statement that controls this loop. (pp. 413–414) | 4. For count = 10 to 1 Step -1
Q: | 5. What Visual Basic looping statement would you choose for a loop that is both count-controlled and event-controlled and whose body might not execute even once? (p. 415) | 5. A While (or perhaps a For ) statement
Q: | 6. What Boolean value does the condition of a Do Until loop have to become in order for the loop to stop? (p. 412) | 6. True
Q: | 7. Write a statement that tries to divide a number by 0 and writes out an error message if an ArithmeticException occurs. (p. 417) |
Q: | 8. What is the super class of most exceptions? (p. 416) | 8. Exception

Answers

A:

1.

Select Case (nameVal)
       Case 1
           outData.WriteLine("Mary")
       Case 2
           outData.WriteLine("Lynn")
       Case 3
           outData.WriteLine("Smith")
End Select

A:

2.

Select Case (nameVal)
       Case 1
            outData.WriteLine("Mary")
       Case 2
            outData.WriteLine("Lynn")
       Case 3
            outData.WriteLine("Smith")
       Case Else
            outData.WriteLine("Invalid name value")
End Select

A:

3. The body of a Do While always execute at least once; the body of a While may not execute at all.

A:

4. For count = 10 to 1 Step -1

A:

5. A While (or perhaps a For) statement

A:

6. True

A:

7.

Try
   intValue = 12 \ 0
   Catch except as ArithmeticException
   Messagebox.Show(except.Message)
End Try

A:

8. Exception


Exam Preparation Exercises

    Chapter 10

  1. Define the following terms:

    • Select Case expression

    • Pretest loop

    • Posttest loop

  2. (True or False?) A Select Case expression may be an expression that results in a value of type Integer, Double, and String.

  3. (True or False?) The values in case labels may appear in any order, but duplicate case labels are not allowed within a given Select Case statement.

  4. (True or False?) All possible values for the Select Case expression must be included among the case labels for a given Select Case statement.

  5. Rewrite the following code fragment using a Select Case statement.

    If (n = 3) Then
    alpha += 1
ElseIf (n = 7) Then
    beta += 1
ElseIf (n = 10) Then
    gamma += 1
End If

  6. What is written by the following code fragment if n equals 3? (Be careful here.)

    Select Case (n + 1)
    Case 2
        outData.WriteLine("Bill")
    Case 4
        outData.WriteLIne("Mary")
    Case 7
        outData.WriteLine("Joe")
    Case 8
        outData.WriteLine("Anne")
    Case Else
        outData.WriteLine("Whoops!")
End Select

  7. (True or False?) If a While loop whose condition is delta <= alpha is converted into a Do-Loop While loop, the loop condition of the Do-Loop While loop is delta > alpha.

  8. (True or False?) The default increment of a For statement is 1.

  9. What is written by the following program fragment? (All variables are of type Integer.)

    n = 0
i = 1
Do
    outData.Write(i)
    i += 1
Loop While (i <= n)

  10. What is written by the following program fragment? (All variables are of type Integer.)

    n = 0
For i = 1 To n
   outData.Write(i)
Next I

  11. What is written by the following program fragment? (All variables are of type Integer.)

    For i = 4 To 1 Step -1
    For j = i To 1 Step -1
        outData.Write(j & " ")
    Next j
    outData.WriteLine(i)
Next I

  12. What is written by the following program fragment? (All variables are of type Integer.)

    For row = 1 To 10
    For col = 1 To 10
       outData.Write("*")
    Next col
    For col = 1 To 2*row - 1
        outData.Write(" ")
    Next col
    For col = 1 To 10 - row
        outData.Write("*")
    Next col
    outData.WriteLine()
Next row

  13. (True or False?) The statement inside the following For statement executes at least one time.

    For x = 1 To 10 Step -1
    outData.Write(x)
Next x

  14. (True or False?) The equal sign (=) in the statement If (a = 12) Then is used for assignment?

  15. Rewrite each statement as described.

    1. Using the += operator, rewrite the statement

      sumOfSquares = sumOfSquares + x * x

    2. Using the -= operator, rewrite the statement

      count = count - 1

    3. Using the Select Case statement, rewrite the statement

      If (n > 8) Then
    k = 32
Else
    k = 15 * n
End If

  16. What is the control structure to use if you think an operation might throw an exception?

  17. What is the statement that raises an exception?

  18. What part of the Try-Catch must have a parameter of an exception object?

  19. (True or False?) Our code can catch exceptions that it throws, but not exceptions that the system throws.

  20. Mark the following statements True or False. If a statement is false, explain why.

    1. There can only be one Catch for each Try.

    2. The exception handler is within the Catch clause.

    3. The Finally clause is optional.

    4. The Finally clause is rarely used.


Programming Warm-Up Exercises

    Chapter 10

  1. Write a Select Case statement that does the following:

    If the value of grade is

    • "A", add 4 to sum.

    • "B", add 3 to sum.

    • "C", add 2 to sum.

    • "D", add 1 to sum.

    • "F", print "Student is on probation" to StreamWriter file outData.

  2. Modify the code for Exercise 1 so that an error message is printed if grade does not equal one of the five possible grades.

  3. Write a program segment that reads and sums integer values from a file until it has summed 10 data values or until a negative value is read, whichever comes first. Use a Do-Loop While loop for your solution.

  4. Rewrite the following code segment using a Do-Loop While loop instead of a While loop.

    response = inData.Read()
While (response >= 0 And response <= 127)
    response = inData.Read()
End While

  5. Rewrite the following code segment using a While loop.

    inInt = inData.Read()
If (inInt >= 0) Then
    Do
        outData.Write(inInt)
        inInt = inData.Read()
    While (inInt >= 0)
End If

  6. Rewrite the following code segment using a For loop.

    sum = 0
count = 1
While (count <= 1000)
    sum = sum + count
    count += 1
End While

  7. Rewrite the following For loop as a While loop.

    For m = 93 To 5 Step -1
    outData.WriteLine( m & " " & m * m)
Next m

  8. Rewrite the following For loop using a Do Loop While loop.

    For k = 9 To 21
    outData.WriteLine( k & " " & 3 * k)
Next k

  9. Write a value-returning method that accepts two Integer parameters, base and exponent, and returns the value of base raised to the exponent power. Use a For loop in your solution.

    1. Declare an exception of class MyException.

    2. Write class MyException.

    3. Write the statement that throws an exception of class MyException.

  11. Write a Try-Catch statement that attempts to open file "data.in" for reading and writes an error message if an exception is thrown.


Programming Problems

  1. Develop a Visual Basic application that inputs a two-letter abbreviation for one of the 50 states from a text box and displays the full name of the state in a label. If the abbreviation isn't valid, the program should display an error message and ask for an abbreviation again. The names of the 50 states and their abbreviations are given in the following table. Use two buttons: one to enter an abbreviation and one to quit.

    State

    Abbreviation

    State

    Abbreviation

    Alabama

    AL

    Montana

    MT

    Alaska

    AK

    Nebraska

    NE

    Arizona

    AZ

    Nevada

    NV

    Arkansas

    AR

    New Hampshire

    NH

    California

    CA

    New Jersey

    NJ

    Colorado

    CO

    New Mexico

    NM

    Connecticut

    CT

    New York

    NY

    Delaware

    DE

    North Carolina

    NC

    Florida

    FL

    North Dakota

    ND

    Georgia

    GA

    Ohio

    OH

    Hawaii

    HI

    Oklahoma

    OK

    Idaho

    ID

    Oregon

    OR

    Illinois

    IL

    Pennsylvania

    PA

    Indiana

    IN

    Rhode Island

    RI

    Iowa

    IA

    South Carolina

    SC

    Kansas

    KS

    South Dakota

    SD

    Kentucky

    KY

    Tennessee

    TN

    Louisiana

    LA

    Texas

    TX

    Maine

    ME

    Utah

    UT

    Maryland

    MD

    Vermont

    VT

    Massachusetts

    MA

    Virginia

    VA

    Michigan

    MI

    Washington

    WA

    Minnesota

    MN

    West Virginia

    WV

    Mississippi

    MS

    Wisconsin

    WI

    Missouri

    MO

    Wyoming

    WY

    (Hint: Use nested Select Case statements, where the outer statement uses the first letter of the abbreviation as its Select Case expression.)

  2. Design and write a Visual Basic application that reads a date in numeric form from a set of three text boxes and display it in English within a label. Use appropriate buttons. For example:

    Given the data:

    10       27      1942

    The program displays:

    October    twenty-seventh,   nineteen hundred forty-two.

    Here is another example:

    Given the date:

    12       10      2010

    The program displays:

    December      tenth,       two thousand ten.

    The program should display an error message for any invalid data, such as 2 29 1883 (1883 wasn't a leap year).

  3. Write a Visual Basic application that reads full names from an input file and writes the initials for the names to an output file named initials. For example, the input

    John James Henry

    should produce the output

    JJH

    The names are stored in the input file first name first, then middle name, then last name, separated by an arbitrary number of blanks. There is only one name per line. The first name or the middle name could be just an initial.

  4. Write a Visual Basic application that converts letters of the alphabet into their corresponding digits on the telephone. The program should let the user enter letters repeatedly until a "Q" or a "Z" is entered. (Q and Z are the two letters that are not on the telephone.) An error message should be printed for any nonalphabetic character that is entered.

    The letters and digits on the telephone have the following correspondence.

    ABC = 2

    DEF = 3

    GHI = 4

    JKL = 5

    MNO = 6

    PRS = 7

    TUV = 8

    WXY = 9

    Here is an example:

    When the user enters P, the program displays:

    The letter P corresponds to 7 on the telephone.

    When the user enters A, the program displays:

    The letter A corresponds to 2 on the telephone.

    When the user enters D, the program displays:

    The letter D corresponds to 3 on the telephone.

    When the user enters 2, the program displays:

    Invalid letter. Enter Q or Z to quit.

    When the user enters Z, the program quits.

  5. Rewrite the Rainfall program so that it asks the user for the input file name using a Form, and catches the FileNotFoundException if the name is invalid. In the case of an invalid file name, the user should be asked to re-enter the file name until a valid name is entered or they close the Form (signaling that they want to stop the program).


Case Study Follow-Up Exercises

    Chapter 10

  1. Rewrite the processOneSite function in the Rainfall application, replacing the For loop with a Do-Loop While loop.

  2. Rewrite the processOneSite function in the Rainfall application, replacing the For loop with a While loop.

  3. Change the Do-Loop While loop in the Rainfall application that calls the processOneSite function to be a While loop.

  4. Could module Get Rainfall Amount be made a function? Explain.


Chapter 11: One-Dimensional Arrays

Overview

Goals

  • To be able to differentiate between atomic and composite data types.

  • To be able to differentiate between unstructured and structured composite data types.

  • To be able to declare and instantiate a one-dimensional array object.

  • To be able to access the individual components in a one-dimensional array object.

  • To be able to use an initializer list to instantiate a one-dimensional array object.

  • To be able to pass a one-dimensional array object as a parameter.

  • To be able to manipulate the elements in a one-dimensional array object in which the elements are atomic types.

  • To be able to manipulate the elements in a one-dimensional array object in which the elements are composite types.

  • To be able to declare, instantiate, and use a one-dimensional array object with index values that have semantic content.

  • To be able to declare, instantiate, and manipulate components in a one-dimensional array object that is only partially filled with data.

  • To be able to declare, instantiate, and manipulate dynamic arrays that can grow in size.

In Chapter 4, we showed a diagram of Visual Basic data types (repeated above as Figure 11.1). Visual Basic data types are divided into primitive and reference types. Recall that the Visual Basic stores primitive data types directly into the named location, but reference types are stored into the place whose address is stored in the named location. We have covered classes and primitive data types in previous chapters. We extend the discussion of the Visual Basic data types in this chapter by examining the array. However, before we do, we step back and look at data types from a general perspective rather than from a Visual Basic perspective.

Click To expand
Figure 11.1: Visual Basic data types


11.1 Atomic Data Types

Recall that a data type is a set of values, along with a set of operations on those values. The definitions of integer and real numbers come from mathematics. Integer numbers are the set of whole numbers from negative to positive infinity, and the operations defined for them are the arithmetic operations—defined by +, -, /, \ (for integer-specific division), *, and modulus—assignment, and the relational operations.

Real numbers are the set of all numbers from negative to positive infinity, and the operations are the same as those for the integers except that integer division and modulus are excluded. Because computers are finite, programming languages put limits on the range of integers and the range and precision of real numbers. Visual Basic provides four different types of integer numbers that differ only in the range of values that they can represent in memory: Byte, Short, Integer, and Long. Visual Basic has two real types (called floating-point) that differ only in the range and precision that they can represent: Single and Double.

Alphanumeric characters, another simple type, are the symbols that we use in written language. These symbols vary from one natural language to another. The same is true of characters used to represent textual data in a computer. For many years, there were two main character sets: ASCII and EBCDIC. In these character sets, each character occupies one byte in memory, giving 256 possible characters. Some of the characters are nonprintable control characters used by the computer's hardware. Unicode, a character set that uses two bytes to represent each character, was developed to include characters for writing text in many natural languages. Unicode, which Visual Basic uses, contains ASCII as a subset.

The operations defined on alphanumeric characters are the relational operations and assignment. The ordering used by the relational operators is the collating sequence of the character set. Although the collating sequence is different for different character sets, the letters and digits are ordered as we would expect: that is, "A" < "B" < "C" …and "1" < "2" < "3" ….

Characters are treated differently in different programming languages. In Pascal, the data type for a character is distinct. Arithmetic operations, applied to character data, cause a compile-time error. In C++, there is no real distinction between characters and numbers. Arithmetic operations may be applied to character data and often are. The only difference is that, when character data is printed, the character itself is printed rather than the numeric representation of the character. In Visual Basic, arithmetic operations can be performed on character data only if the data is converted to a numeric data type.

Boolean values are another simple data type comprised of the two literals, True and False. The operations allowed on these values are the logical operators And, Or, Not, the relational operations, and assignment operations. Although many languages consider Boolean values to be ordered, Visual Basic does not, so only equal and not equal can be applied to Boolean values in Visual Basic. Visual Basic calls the Boolean type Boolean and the literals True and False.

Integers, reals, and characters have two properties in common. Each is made up of indivisible, or atomic, elements, and each is ordered. Data types with these properties are called scalar data types. (Visual Basic's type Boolean is not scalar because they aren't ordered.)

Scalar data types
A data type in which the values are ordered and each value is atomic (indivisible)
Ordinal data type
A data type in which each value (except the first) has a unique predecessor and each value (except the last) has a unique successor

When we say that a value is atomic, we mean that is has no component parts that can be accessed independently. For example, a single character is atomic, but the string "Good Morning" is not because it is composed of 12 characters. When we say that the values are ordered, we mean that exactly one of the relations of less than, greater than, or equal is true for any pair of values. Some examples of ordered relationships are

1 < 2 "C" > "A" 3.562 < 106.22

Integers, characters, and Booleans have an additional property: Each value (except the first) has a unique predecessor and each value (except the last) has a unique successor. A type with this property is called an ordinal data type.

Real numbers are not ordinal because a real value has no unique predecessor or successor. If one more digit of precision is added, the predecessor and successor change; that is 0.52 and 0.520 are the same, but the predecessor of 0.52 is 0.51 and the predecessor of 0.520 is 0.519. Because Visual Basic's type Boolean is not ordered, it is not ordinal.

Although the ordinals are a subset of the scalars, they are quite different. Mathematicians make this same distinction; they talk about continuous values versus discrete values. There are many real-life analogies that demonstrate this distinction as well: the continuous spectrum of colors in a real rainbow versus the discrete colors in a child's crayon drawing of a rainbow or the continuous tone of a violin sliding up the scale versus the discrete tones of a piano.

Visual Basic classifies the scalar types that it represents as primitive. Simple is another name for scalar types.


11.2 Composite Data Types

There are times when it is necessary to show a relationship among variables or to store and reference collections of variables as a group. For this reason we need a way to associate an identifier with a collection of values. A data type made up of a collection of values is called a composite data type.

Composite data type
A data type that allows a collection of values to be associated with an identifier of that type

Composite data types come in two forms: unstructured and structured. An unstructured data type is one in which no relationship exists among the values in the data type other than that they are members of the same collection. A structured data type, on the other hand, is an organized collection of components, one in which a relationship exists among the items in the collection. We use this relationship to access individual items within the collection as well as manipulate the collection as a whole.

Unstructured data type
A collection of components that are not organized with respect to one another
Structured data type
An organized collection of components; the organization determines the method used to access individual components

A value in a simple type is a single data item; it cannot be broken down into component parts. For example, in Visual Basic each Integer value is a single integer number and cannot be further decomposed. In contrast, a composite data type is one in which each value is a collection of component items. The entire collection is given a single name, yet each component can still be accessed individually.

The class is an example of a composite data type. A class has a name and is composed of named data members and methods. An instance of a class, including the data members and methods, can be passed as a parameter. The data members and methods can be accessed individually by name. A class is unstructured because the meaning is not dependent on the ordering of the data members or the methods within the source code. That is, the order in which the members of the class are listed can be changed without changing the function of the class.

In Visual Basic, all composite types are either classes, interfaces, or arrays. Rather than talking about the type of a composite object, we talk about its class. An example of a composite object in Visual Basic is an instance of the String class, used for creating and manipulating strings. When you declare a variable myString to be of class String, myString does not represent just one atomic data value; it represents an entire collection of characters and the methods that manipulate the characters. But each of the components in the string can be accessed individually by using an expression such as myString.Chars(0), which accesses the Char value at position 0 (the first position) in the string. Therefore, the characters within a string are organized according to their relative positions.

Simple data types are the building blocks for composite types. A composite type gathers together a set of component values and usually imposes a specific arrangement on them (see Figure 11.2). If the composite type is a built-in type, the accessing mechanism is provided in the syntax of the language. If the composite type is a user-defined type, the accessing mechanism is built into the methods provided with the class.

Click To expand
Figure 11.2: Atomic (simple) and composite data types

In Chapters 1 through 10, we have discussed control structures and the Class, an unstructured composite type. In the next three chapters we focus on structured composite data types. Of course, we do not abandon the class, but we focus on having a structured composite type as a field in a class.


11.3 One-Dimensional Arrays

How we organize our data plays an important role in the design process. If the internal data representation for a class is a composite type, that is, if it contains more than single atomic member, we call the internal representation a data structure. The choice of data structure directly affects the design because it determines the algorithms used to process the data. The class gives us the ability to refer to an entire group of components by one name. This simplifies the design of many programs.

Data structure
The implementation of a composite data member in a class

Many problems, however, have so many components that it is difficult to process them if each one must have a unique field name. For example, if we use individually named values to read and print a file in reverse order, all the values must be read and saved before the last one can be printed. If there are 1,000 values, we must define 1,000 individual variables to hold the values, and input and output each variable separately—an incredibly tedious task! An array—the last of Visual Basic's built-in reference types—is the data type that allows us to program operations of this kind with ease.

Let's look at how we would have to solve this problem with simple variables.

' Read 1,000 numbers and print them in reverse order
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
Imports System.IO

Public Class Form1
    Inherits System.WinForms.Form
    Dim theFile As File
    Dim inFile As StreamReader
    Dim outFile As StreamWriter
    ' Declare 1,000 variables
    Dim value0 As Integer
    Dim value1 As Integer
    Dim value2 As Integer
    .
    .
    .
    Dim value999 As Integer
    Public Sub New()
        MyBase.New
        ' This call is required by the Win Form Designer.
        InitializeComponent()
        ' Open input and output files
        inFile = theFile.OpenText("infile.dat")
        outFile = theFile.CreateText("outfile.dat")
        ' Read 1,000 integer values
        value0 = CInt(inFile.ReadLine())
        value1 = CInt(inFile.ReadLine())
        value2 = CInt(inFile.ReadLine())
        .
        .
        .
        value999 = CInt(inFile.ReadLine())
        ' Write out 1,000 values
        outFile.WriteLine(value999)
        outFile.WriteLine(value998)
        outFile.WriteLine(value997)
        .
        .
        .

        outFile.WriteLine(value0)
        inFile.Close()
        outFile.Close()
        End
    End Sub

    ' Form overrides dispose to clean up the component list.
    Overrides Public Sub Dispose()
        MyBase.Dispose
        components.Dispose
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

This program is over 3,000 lines long, and we have to use 1,000 separate variables. Note that all the variables have the same name except for an appended number that distinguishes them. Wouldn't it be convenient if we could put the number into a counter variable and use For loops to go from 0 through 999, and then from 999 back down to 0? For example, if the counter variable were number, we could replace the 2,000 input/output statements with the following six lines of code (we enclose number in parentheses to set it apart from value):

For number = 0 to 999
    value(number) = CInt(inFile.ReadLine())
Next number
For number = 999 to 0 Step -1
    outFile.WriteLine(value(number))
Next number

This code fragment is correct in Visual Basic if we declare value to be a one-dimensional array, which is a collection of variables—all of the same type—where the first part of each variable name is the same, and the last part is an index value enclosed in parentheses. In our example, the value stored in number is called the index.

The declaration of a one-dimensional array is similar to the declaration of a simple variable (a variable of a simple data type), with one exception. You must indicate that it is an array by putting parentheses next to the type. In Visual Basic, you normally instantiate the array when you declare it, by specifying how large the array is to be.

Dim value(1000) As Integer

value is an array with 1000 components, all of type Integer. The first component has index value 0, the second component has index value 1, and the last component has index value 999.

Here is the program to print out numbers in reverse order using an array. This is certainly much shorter than our first version of the program.

' Read 1,000 numbers and print them in reverse order
Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
Imports System.IO

Public Class Form1
    Inherits System.WinForms.Form
    Dim theFile As File
    Dim inFile As StreamReader
    Dim outFile As StreamWriter
    Dim value(1000) As Integer
    Dim number As Integer
    Public Sub New()
        MyBase.New
        ' This call is required by the Win Form Designer.
        InitializeComponent()
        ' Open files
        inFile = theFile.OpenText("infile.dat")
        outFile = theFile.CreateText("outfile.dat")
        ' Read 1,000 values
        For number = 0 To 999
            value(number) = CInt(inFile.ReadLine())
        Next number
        ' Write 1,000 values
        For number = 999 To 0 Step -1
            outFile.WriteLine(value(number))
        Next number
        ' Close files and exit program
        inFile.Close()
        outFile.Close()
        End
    End Sub

    'Form overrides dispose to clean up the component list.
    Overrides Public Sub Dispose()
        MyBase.Dispose
        components.Dispose
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

In general terminology, an array differs from a class in three fundamental ways:

  1. An array is a homogeneous data structure (all components in the structure are of the same data type), whereas classes are heterogeneous types (their components may be of different types).

  2. A component of an array is accessed by its position in the structure, whereas a component of a class is accessed by an identifier (the data member name).

  3. Because array components are accessed by position, an array is a structured data type.

Let's now define Visual Basic arrays formally and look at the rules for accessing individual components.

Declaring and Creating an Array

A one-dimensional array is a structured collection of components (often called array elements) that can be accessed individually by specifying the position of a component with a single index value.

One-dimensional array
A structured collection of components, all of the same type, which is given a single name. Each component (array element) is accessed by an index that indicates the component's position within the collection.

Here is a syntax template describing the simplest form of one-dimensional array declaration and creation:

Click To expand

ArrayName is a location in memory that holds the address of the array. DataType describes what is stored in each component of the array. Array components can be of almost any type, but for now we limit our discussion to atomic components.

IntExpression is an integer expression that specifies the number of components in the array. This expression must have a value greater than or equal to 0. If the value is n, the range of index values is 0 through n - 1, not 1 through n. For example, the declarations:

Dim angle(4) As Single
Dim testScore(10) As Integer

instantiate the arrays shown in Figure 11.3. The angle array has four components, each capable of holding one Single value. The testScore array has a total of 10 components, all of type Integer.

Click To expand
Figure 11.3: angle and testScore arrays

Because arrays are reference types in Visual Basic, they are instantiated at run time, not at compile time. Therefore, the IntExpression used to instantiate an array object does not have to be a literal constant. It can be a value that you have read into the program. For example, if you have read the value of dataSize from a file, the following declaration is legal:

Dim data(dataSize) As Integer

Once instantiated, data always has dataSize components (except in the case of a dynamic array, which we discuss later in the chapter). For example, if dataSize is 10 when the array object data is instantiated but is later changed to 15, data still has 10 components.

An alternative array declaration and creation method is

Dim data() As New Integer(dataSize) {}

This alternative method of array declaration and creation is included in the language to provide compatibility with other languages utilizing the .NET framework. However, because it seems somewhat awkward and clumsy, we will not use this method in this text.

Declaring and Creating an Array with an Initializer List

Visual Basic provides an alternative way to instantiate an array. You learned previously that Visual Basic allows you to initialize a variable in its declaration:

Dim delta As Integer = 25

The value 25 is called an initializer. You can also initialize an array in its declaration, using a special syntax for the initializer. You specify a list of initial values for the array elements, separate them with commas, and enclose the list within braces:

Dim age() As Integer = {23, 10, 16, 37, 12}

In this declaration, age(0) is initialized to 23, age(1) is initialized to 10, and so on. There is something interesting to notice about this syntax—the number of components is not specified. When the compiler sees an initializer list, it determines the size by the number of items in the list, instantiates an array of that size, and stores the values into their proper places. Of course, the types of the values in the initializer list must match the type of the array.

Accessing Individual Components

Recall that to access an individual data member of a class, we use dot notation—the name of the class object, followed by a period, followed by the member name. In contrast, to access an individual array component, we write the array name, followed by an expression enclosed in parentheses. The expression specifies which component to access. The syntax template for accessing an array component is

Click To expand

The IndexExpression may be as simple as a constant or a variable name or as complex as a combination of variables, operators, and method calls. Whatever the form of the expression, it must result in an integer value. Index expressions can be of type Byte, Char, Short, or Integer.[1] Using an index expression of type Long causes a design-time error. The simplest form of index expression is a literal constant. Using our angle array, the sequence of assignment statements:

angle(0) = 4.93
angle(1) = -15.2
angle(2) = 0.5
angle(3) = 1.67

fills the array components one at a time (see Figure 11.4).

Click To expand
Figure 11.4: angle array with values

Each array component—angle(2), for instance—can be treated exactly the same as any simple variable of type Single. For example, we can do the following to the individual component angle(2):

' Assign it a value
angle(2) = 9.6

' Read a value into it
angle(2) = CDb1(inFile.ReadLine())

' Write its contents
outFile.WriteLine(angle(2))

' Pass it as an argument
y = Math.Sqrt(angle(2))

' Use it in an expression
x = 6.8 * angle(2) + 7.5

Let's look at a more complicated index expression. Suppose we declare a 1,000-element array of Integer values with the statement

Dim value(1000) As Integer

and execute the following statement:

value(counter) = 5

In this statement, 5 is stored into an array component. If counter is 0, 5 is stored into the first component of the array. If counter is 1, 5 is stored into the second place in the array, and so forth. If instead we execute this statement:

If (value(number+1)) Mod 10 <> 0)

then the expression number+1 selects an array component. The specific array component accessed is divided by 10 and checked to see if the remainder is nonzero. If number+1 is 0, we are testing the value in the first component; if number+1 is 1, we are testing the second place; and so on. Figure 11.5 shows the index expression as a constant, a variable, and a more complex expression.

Click To expand
Figure 11.5: An index as a constant, a variable, and an arbitrary expression

Out-of-Bounds Array Indexes

Given the declaration

Dim alpha(100) As Single

the valid range of index values is 0 through 99. Starting at 0 seems awkward, because we are accustomed to numbering things beginning with 1. However, you should not be surprised; the positions in a string begin with 0. What happens if we try to execute the statement

alpha(i) = 62.444

when i is less than 0 or when i is greater than 99? The result is that a memory location outside the array would be accessed, which causes an error. This error is called an out-of-bounds error. Some languages, C++ for instance, do not check for this error, but Visual Basic does. If your program attempts to use an index that is not within the bounds of the array, an IndexOutOfRangeException is thrown. Rather than try to catch this error, you should write your code to prevent it.

Out-of-bounds error
An error caused by an index value that is either less than 0 or greater than the array size minus 1

In Visual Basic, each array that is instantiated has a Public instance property, called Length, associated with it that contains the number of components in the array. Length can be used when processing the components in the array to keep from having an out-of-bounds error. Here's how. Array-processing algorithms often use For loops to step through the array elements one at a time. Here is a loop to zero out our 100-element alpha array:

For n = 0 to alpha.Length-1
    alpha(n) = 0
Next n

Use this pattern—set your loop range from 0 to one less than the number of elements in the array—and you can ensure that your loop will stay within the bounds of the array. If your program does crash with an IndexOutOfRangeException, immediately check to be sure you subtracted 1 from the Length property.

Aggregate Array Operations

We can assign one array to another and we can compare two arrays for equality, but we might not get the answer we expect. Arrays, like classes, are reference types. This means that the value stored in the location assigned to the object name is not the object itself but the address of where the object is stored. Let's see what happens when we test two arrays for equality and assign one array to another.

Dim numbers() As Integer = {2, 4, 6}
Dim values(3) As Integer
values(0) = 2
values(1) = 4
values(2) = 6

If (numbers = values) Then
    ...

End If
numbers = values
If (numbers = values) Then
    ...
End If

The first If expression is false because numbers and values occupy different places in memory. (See Figure 11.6a.) The next statement takes the contents of values (the address of where the array is stored) and stores it into numbers. Now the If expression is true. numbers contains the address in memory of where the array referenced by values is stored. (See Figure 11.6b.)

Click To expand
Figure 11.6: Comparison of array variables

You should not be surprised at this example. Assignment for reference types is a shallow assignment; an equality test for reference types is a shallow test. If you want to have a deep test, you must write a method to do the comparison, element by element:

Dim numbers() As Integer = {2, 4, 6, 9}
Dim index As Integer
Dim sameNumbers(numbers.Length) As Integer
For index = 0 To numbers.Length-1
    sameNumbers(index) = numbers(index)
Next index

Public Function compareArrays(ByRef one() As Integer, ByRef two() As _
                               Integer) As Boolean
    If (one.length <> two.length) Then
        Return False
    End If

    Dim result As Boolean = True
    Dim index As Integer = 0
    While (index < one.length And result)
        If (one(index) = two(index)) Then
            index += 1
        Else
            result = False
        End If
    End While
        Return result
End Function

Now examine these comparisons:

If (numbers = sameNumbers) Then
    ...
End If

If (compareArrays(numbers, sameNumbers) Then
    ...
End If

The first expression is false. The array objects sameNumbers and numbers contain the same values, but the equality test is for addresses, not values. The second expression is true because compareArrays is a function that performs a deep comparison.

[1]Visual Basic inherits the notion that type Char is a numeric type from C and C++. The Visual Basic language specifications say that arrays must be indexed by Integer values but that values of type Short, Byte, or Char may also be used because they are subjected to unary numeric promotion and become Integer values. However, for clarity we convert Char values to Integer when using them as an index.


11.4 Examples of Declaring and Processing Arrays

We now look in detail at some specific examples of declaring and accessing arrays. These examples demonstrate different applications of arrays in programs.

Occupancy Rates

Here are some declarations that a program might use to analyze occupancy rates in an apartment building:

Const BUILDING_SIZE As Integer = 350  ' Number of apartments

Dim occupants(BUILDING_SIZE) As Integer

' occupants(i) is the number of occupants in apartment i
Dim totalOccupants As Integer

occupants is a 350-element array of integers (see Figure 11.7); occupants(0) = 3 if the first apartment has 3 occupants; occupants(1) = 5 if the second apartment has five occupants; and so on. If values have been stored into the array object, then the following code totals the number of occupants in the building.

Click To expand
Figure 11.7: occupants array
totalOccupants = 0
For counter = 0 To occupants.Length-1
    totalOccupants = totalOccupants + occupants(counter)
Next counter

The first time through the loop, counter is 0. We add the contents of totalOccupants (that is, 0) and the contents of occupants(0), storing the result into totalOccupants. Next, counter becomes 1 and the loop test occurs. The second loop iteration adds the contents of totalOccupants and the contents of occupants(1), storing the result into totalOccupants. Now counter becomes 2 and the loop test is made. Eventually, the loop adds the contents of occupants(349) and the sum and increments counter to 350. At this point, counter is outside the outer range of the loop and control exits the loop.

Note how we used the named constant BUILDING_SIZE in the array declaration and occupants.Length in the For loop. When a constant is used in this manner, changes are easy to make. If the number of apartments changes from 350 to 400, we just need to change the declaration of BUILDING_SIZE. We could also have written

For counter = 0 To BUILDING_SIZE-1

but we prefer to use the Length property because it is specifically associated with the array. In the future, the program might be changed to use a different constant to set the size of occupants. Then BUILDING_SIZE would no longer be the correct value to terminate the loop, but occupants.Length is still correct.

Sales Figures

Now let's look at an example where the values stored in the array are sales figures and the indexes are the product numbers. (The products are gourmet hamburgers.) The product numbers range from 1 through 5. We can make the array object contain six components and just ignore the 0th position, or we can set up five components and make sure that we add (or subtract) 1 from the product number to get the proper slot. Let's use the latter strategy.

' Declare and instantiate an array with 5 real components
Dim gourmetBurgers(5) As Double

The data for this example are (hamburger number, day's sales) pairs. The data file contains a week's worth of such pairs. The first value is a integer between 1 and 5 that represents one of the gourmet hamburgers. The next value is the sales amount for that hamburger for the day. Each value is on a line by itself. The following code segment reads in a (hamburger number, sales figure) pair.

InFile = theFile.OpenText("infile.dat")
outFile = theFile.CreateText("outfile.dat")
Dim salesAmount As Double
Dim burgerNumber As Integer
    ...
burgerNumber = CInt(inFile.ReadLine())
salesAmount = CDb1(inFile.ReadLine())

To add the sales amount to the value in the appropriate slot in the array, we use burgerNumber-1 as the index into the array gourmetBurgers.

gourmetBurgers(burgerNumber-1) = _
    salesAmount + gourmetBurgers(burgerNumber-1)

If we put our input and processing within a loop, we can process a week's worth of sales figures. We can then write the totals out to a file with the following loop.

For burgerNumber = 0 To gourmetBurgers.Length-1
    outFile.Write("Gourmet Burger # " & (burgerNumber+1))
    outFile.WriteLine(": " & gourmetBurgers(burgerNumber))
Next burgerNumber

Figure 11.8 shows the contents of array object gourmetBurgers after the data shown has been processed. Note that the data is shown all on one line with commas between pairs to save space.

Click To expand
Figure 11.8: groumetBurgers array

This example, where the index into the array is one less than the number assigned to a type of hamburger, is a type of problem where the index has semantic content. That is, the index has meaning within the problem itself.

Letter Counts

As a final example in this section, let's use an array to count the number of times each letter in the English alphabet is used in text, either uppercase or lowercase. We declare an array of 26 integers, one for each letter.

Dim letterCount(26) As Integer

letterCount(0) is the counter for the number of times we see an "a", letterCount(1) is the counter for the number of times we see a "b", and letterCount(25) is the number of times we see a "z". How do we convert a letter to its position in the array? Well, we read a character and see if it is a letter. If so, we convert it to uppercase. Then we take its ASCII (numeric) value using the Asc function, convert that number from a Short (the data type for the return value of the Asc function) to an Integer, and subtract it from the ASCII value of "A". This gives us the letter's place in the array and we can increment the occurrence of that particular letter by 1. The following code fragment accomplishes this conversion and incrementation:

letter = CChar(inFile.Read())
If ((letter >= "A"c And letter <= "Z"c) Or _
    (letter >= "a"c And letter <= "z"c)) Then
    index = CInt(asc(letter.ToUpper(letter)) - asc("A"))
    letterCount(index) = letterCount(index) + 1
End If

All we need to do now is put this code in a file-reading loop:

While(inFile.Peek <> -1)
     letter = CChar(inFile.Read())
   If ((letter >= "A"c And letter <= "Z"c) Or _
     (letter >= "a"c And letter <= "z"c)) Then
     index = CInt(Asc(letter.ToUpper(letter)) - Asc("A"))
     letterCount(index) = letterCount(index) + 1
   End If
End While

To end the program, we need a loop that prints out the values to a file:

For index = 0 To letterCount.Length - 1
outFile.WriteLine("The number of all " & _
                   Chr(index + CInt(Asc("A"))) & _
                   " " & letterCount(index))
Next index

This loop uses the Chr function to convert a number to its ASCII character representation.


11.5 Arrays of Objects

Although arrays with atomic components are very common, many applications require a collection of composite objects. For example, a business needs a list of parts records, and a teacher needs a list of students in a class. Arrays are ideal for these applications. We simply define an array whose components are class objects.

Arrays of Strings

Let's define an array of strings, each of which is a grocery item. Declaring and creating the array of objects is exactly like declaring and creating an array where the components are atomic types.

Dim groceryItems(10) As String ' Array of strings

groceryItems is an array of 10 strings. How many characters are there in each string? We don't know yet. The array of strings has been instantiated, but the string objects themselves have not. Another way of saying this is that groceryItems is an array of references to string objects, which are set to "" (the empty string) when the array is instantiated. The string objects must be instantiated separately. The following code segment reads and stores 10 strings into groceryItems.

Dim index As Integer
Dim groceryItems(10) As String
...
inFile = theFile.OpenText("infile.dat")
outFile = theFile.CreateText("outfile.dat")

For index = 0 To groceryItems.Length-1
    groceryItems(index) = inFile.ReadLine()
Next index

The ReadLine method is a value-returning method, which instantiates the string, stores values into it, and returns it. That is, the reference to the string is returned and stored into groceryItems. Figure 11.9 shows what the array looks like with values in it.

Click To expand
Figure 11.9: groceryItems array

An array name with no brackets is the array object. An array name with brackets is a component. The component can be manipulated just like any other variable of that type.

Expression | Class/Type
groceryItems | An array
groceryItems(0) | A string
groceryItems(0).Chars(0) | A character

How would you read in grocery items if you didn't know how many you had? You know that there are no more than 10, but you don't know exactly how many. You would have to use a While loop that reads in a grocery item and stores it in the first place. If there were another item, it would be stored in the second place, and so on. This means that you must keep a counter of how many items you read in. Here is a code fragment that would read and store grocery items until 10 had been read or the file is empty.

Dim index As Integer = 0
...
While (index < groceryItems.Length And inFile.Peek <> -1)
    groceryItems(index) = inFile.ReadLine()
    index += 1
End While
Messagebox.Show(index & " grocery items were read and stored.")

Look carefully at Figures 11.9 and 11.10. In Figure 11.9, every slot in the array is filled with grocery items. In Figure 11.10, index items have been read in and stored. If index is equal to 10, then the two figures are the same. To process the items in Figure 11.10, you use a loop that goes from 0 through index-1.

Click To expand
Figure 11.10: Partially filled array

Throughout this chapter we have drawn array variables with an arrow to the object structure to which they refer. An array type is a reference type. When we declare an array variable with the name groceryItems, one location in memory is assigned to that name. When the array is instantiated, the address of the place in memory where the actual structure begins is stored into location groceryItems. This address is called the base address of the array.

Base address
The memory address of the first element of an array

If the component type is an atomic type, the values are actually stored in the memory locations beginning at the base address. If the component type is a reference type, a reference to the first component is stored in the base address. We have used the arrow in our drawings as a visual reminder that the contents of a reference variable is the address of where the object can be found.

Clearly, strings and arrays are related. You can visualize a string as an array of characters. You can visualize an array as a string of values. Because of this similarity, the String class has methods that transform a string into an array of Char and any array of Char into a string. Method ToCharArray in class String converts a string value into an array of characters. Char arrays can be converted to strings using the Char method ToString.

Arrays of User-Defined Objects

In the last example, the components of an array were strings. Now let's look at an array of user-defined objects. In Chapter 7, we used the Date data type as an example. The following code declares and instantiates an array of elements of Dates:

Dim bigEvents(10) As Date

and the following shows the types involved and how to access the various components.

Expression | Class/Type
bigEvents | An array
bigEvents(0) | A Date object
bigEvents(0).Month | An integer
bigEvents(0).Day | An integer
bigEvents(0).Year | An integer


11.6 Arrays and Methods

When an array is used as a field in a class, we may need to pass an array object as a parameter to a method or pass a component of an array object to a method. Recall from Chapter 7 that, by default, parameters are passed by value in Visual Basic. That is, a copy of each parameter is sent to the method. Because an array, like a class, is a reference type, what is passed to the method is the address of where the object is stored.

Suppose we define a Public class method (say, in class Accounting) that takes an array as an argument and returns the sum of the components in the array:

Public Function sumSales(ByRef data() As Integer) As Integer
    Dim sum As Double = 0R
    Dim index As Integer
    For index = 0 To data.Length-1
        sum = sum + data(index)
    Next index
    return sum
End Function

The following statement uses method sumSales to sum the week's gourmet hamburger sales.

outFile.Write("This week's sales of gourmet hamburgers: ")
outFile.WriteLine(Accounting.sumSales(gourmetBurgers))

What is passed as a parameter to sumSales? It is the base address of gourmetBurgers (the arrow in Figure 11.8).

There are two cases to consider when passing array components as arguments to a method: the component is a primitive type, or the component is a reference type. If the component is a primitive type, the method cannot change the value of its parameter. If the component is a reference type, the method can change the value of its parameter but it should not. Changing the value of a parameter is poor style.


11.7 Special Kinds of Array Processing

Two types of problems occur frequently that use arrays as part of the internal data structure for a class. One problem uses only part of the defined array in which to store data and the other problem is one in which the index values have specific meaning within the problem.

Partial (or Sub) Array Processing

The size of an array is the declared number of array components—the number of slots set aside for the array object in memory. Visual Basic has an instance property Length associated with each array that contains this value. In many problems, however, we do not know how many data values we actually have, so we declare the array to be as big as it would ever need to be. This means that we may not fill all of the array components with values. In order to avoid processing slots into which we have not stored valid data, we must keep track of how many components are actually filled.

As values are put into the array, we keep a count of how many components are filled. We then use this count to process only components that have values stored in them. Any remaining places are not processed. For example, if there are 250 students in a class, a program to analyze test grades would set aside 250 locations for the grades. However, some students may be absent on the day of the test. So the number of test grades must be counted, and that number, rather than 250, is used to control the processing of the array. This number becomes part of the internal representation of the class being defined. Figure 11.10 on p. 461 visualizes this type of processing.

Chapter 12 examines subarray processing in detail.

Indexes with Semantic Content

In some problems, an array index has meaning beyond simple position; that is, the index has semantic content. An example is the gourmetBurgers array we showed earlier. This array was indexed by the number of the type of hamburger minus 1. That is, the sales for the hamburger that the company called #1 occupied the 0th position in the array; hamburger #2 occupied the 1st position in the array, etc.


11.8 Dynamic Arrays

Visual Basic arrays do not have to be limited to the number of components you specify when you declare the array. You can declare a Visual Basic array to be a dynamic array and then you can resize the array whenever you need more elements in the array.

Dynamic array
An array whose size can grow or shrink during the course of a program

A dynamic array is declared by giving it an empty dimension list when it is declared:

Dim angle()

After this statement, you can allocate a number of elements to the array by using the ReDim statement:

ReDim angle(4)

The ReDim statement can only be issued in places where you normally place executable statements in Visual Basic, for the following reason. Even though the statement seems to be a declaration statement, it is really an executable statement, like an assignment statement. The following statements illustrate different uses of the ReDim statement:

Dim angle()                      ' Declaring angle as dynamic array
Dim numElements As Integer       ' Variable used for array sizing
numElements = 9                  ' Setting initial number of elements
ReDim angle(numElements)         ' Allocating 10 elements to the array
:

ReDim angle(numElements * 1.25)  ' Adding 25% more elements to
                                 ' the array and setting lower
                                 ' bound to 1

You can also resize an array to make it smaller. Just specify a smaller number than the previous ReDim statement.

Each time you execute the ReDim statement, all the elements currently stored in an array are lost. This is useful when you want to prepare the array for new data, or you are trying to preserve memory by deleting some unused array space, but most of the time you will want to resize an array without losing all the data stored in the array. You can do this by issuing the ReDim statement with the Preserve keyword.

You can increase an array's size by 25% and keep the contents of the array with a statement like this:

ReDim Preserve (numElements * 1.25)

Determining the amount of space to add to an array when resizing is more of an art than a science. Part of your analysis of the program before you begin coding should include determining when and by how much you will resize your dynamic arrays.

Start Sidebar
Problem-Solving Case Study
Comparison of Two Lists

Statement of the Problem You are writing an application that does not tolerate erroneous input data. Therefore, the data values are prepared by entering them twice into one file. The file contains two lists of positive integer numbers, separated by a negative number. These two lists of numbers should be identical; if they are not, then a data-entry error has occurred. For example, if the input file contains the sequence of numbers 17, 14, 8, -5, 17, 14, 8, then the two lists of three numbers are identical. However, the sequence 17, 14, 8, -5, 17, 12, 8 shows a data-entry error.

You decide to write a separate application to compare the lists and print out any pairs of numbers that are not the same. The exact number of integers in each list is unknown, but each list has no more than 500.

Brainstorming In all previous problems involving files, we just read and/or wrote them, value by value. Here we have to compare the first value on the file with the first value following a negative number, the second value with the second value following a negative number, and so on. The comparison cannot begin until the values have been read in. We need two container objects: one to hold the first list and one to hold the second list.

Click To expand

Filtering Do we really need two container objects? If we were checking lists by hand, we would write the numbers from the first list on a pad of paper, one per line. The line number would correspond to the number's position in the list; that is, the first number would be on the first line, the second number on the second line, and so on. The first number in the second list would then be compared to the number on the first line, the second number to the number on the second line, and so forth. We wouldn't write the second sequence on a sheet of paper in order to compare the values. So we need only one container object.

Click To expand

Scenarios Let's call our container class FirstList. What are its responsibilities? Its constructor must set the number of values read so far to zero. FirstList is responsible for reading values from the file and storing them into the list. And finally, it is responsible for determining if the rest of the file matches the items in the list.

The application class must prepare the files for input and output, send a message to FirstList to read in values, and send a message to FirstList to compare itself with the rest of the file.

Who is responsible for letting the user know the results of the comparison? Since FirstList is responsible for making the comparison, let's assign the responsibility of informing the user of the result to FirstList.

Click To expand
Click To expand

Data Representation The by-hand algorithm tells us exactly what the container list must look like: an array, where each component represents a line on the paper. The only difference is that humans normally count from 1 and Visual Basic counts from zero. We also need a place to record how many values have been read in.

Responsibility Algorithms for FirstList This class is both reading and writing to files. Which files? Let's let the driver pass the files as parameters to the constructor. Now we are ready to design the algorithms for our class methods.

       ...........................................................................
       Public FirstList(in, out)

        Set numItems to 0
        Set dataFile to in
        Set outFile to out

       ...........................................................................
       Public readFirstList()

        Get first value from dataFile
        While value greater than 0
         Set firstList(numItems) = value
         Increment numItems
         Get next value from dataFile

       ...........................................................................
       Public compareListWithFile()

        Set valuesTheSame to True
        For counter going from 0 through numItems-1
           Get next value from dataFile
           If values are not the same
             Set valuesTheSame to False
             Print both values
        If valuesTheSame
           Print "The two lists are identical"

       ...........................................................................
       Values are not the same                                             Level 2

        value <> firstList(counter)

       ...........................................................................
       Print both values                                                   Level 2

        Print "Values at position " & (counter + 1) & firstList(counter) &
                  " <> " & value

       Responsibility Algorithm for DataValidation
       ...........................................................................
       Public Class Form1

        Prepare dataFile for reading
        Prepare outFile for output
        Declare and instantiate check of type FirstList(dataFile, outFile)
        checker.readFirstList()
        checker.compareListWithFile()
        Close files

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
Imports System.IO

Public Class FirstList
    ' Instance variables
    Private numItems As Integer = 0
    Private firstList(500) As Integer  ' Default value for number of items

    Private dataFile As StreamReader
    Private outFile As StreamWriter
    ' Constructor
    Public Sub new(ByRef inF As StreamReader, _
                   ByRef outF As StreamWriter)
        dataFile = inF
        outFile = outF
    End Sub
    ' Transformer
    Public Function readFirstList() As Integer
        ' Reads in firstList(0) ...firstList(numItems - 1)
        Dim value As Integer
        value = CInt(dataFile.ReadLine())
        While (value > 0)
            firstList(numItems) = value
            numItems += 1
            value = CInt(dataFile.ReadLine())
        End While
    End Function
    ' Observer
    Public Function compareListWithFile() As Integer
        ' Compares the two lists and prints the results
        ' in outFile
        Dim value As Integer
        Dim counter As Integer
        Dim valuesTheSame As Boolean = True
        For counter = 0 To numItems - 1
            value = CInt(dataFile.ReadLine())
            If (value <> firstList(counter)) Then
                outFile.WriteLine("Values at position " & (counter + 1) _
                                   & ": " & firstList(counter) & " <> " _
                                   & value)
                valuesTheSame = False
            End If
        Next counter
        If (valuesTheSame) Then
            outFile.WriteLine("The two lists are identical")
        End If
    End Function
End Class

Public Class Form1
    ' This application compares two positive integer data sets
    ' in file dataFile, separated by a negative number.

    ' If the data sets are not identical, the pairs that do not
    ' match are printed in outFile.

    Inherits System.WinForms.Form
    Dim theFile As File           ' File object for file I/O
    Dim outFile As StreamWriter   ' Output data file
    Dim dataFile As StreamReader  ' Input data file

    Public Sub New()
        MyBase.New()
        Form1 = Me
        InitializeComponent()
        dataFile = theFile.OpenText("c:\datafile.dat")  ' Prepare input file
        outFile = theFile.CreateText("c:\outfile.dat")  ' Prepare output file

        ' Declare an object of type FirstList
        Dim checker As New FirstList(dataFile, outFile)
        checker.readFirstList()
        checker.compareListWithFile()
        dataFile.Close()
        outFile.Close()
        Messagebox.Show("Finished checking lists.")
        End

End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

Testing The program is run with two sets of data, one in which the two lists are identical and one in which there are errors. The data and the results from each are shown below.

Data Set 1 | Data Set 2
21 | 21
32 | 32
76 | 76
22 | 22
21 | 21
-4 | -4
21 | 21
32 | 32
76 | 176
22 | 12
21 | 21
Output | Output
The two lists are identical. | Position 2: 76 <> 176
 | Position 3: 22 <> 12
End Sidebar
Start Sidebar
Problem-Solving Case Study
Grading True/False Tests

Statement of the Problem Your History teacher gives True/False exams. Knowing that you are studying computer science, she asks you to write a program that grades True/False questions. The key to the exam is on the first line of data, followed by a line with the student's name and a line with the student's answers. She gives you the following example of the file to use as a guideline.

TFTFTFTFTTTFFFT
Joe Jones
TFTFTFTFTTTFFTT
Janet Jerome
TFTFTFTFTTTFFFF
Jeff Jubilee
TFTFTFTFTTTFFFT
...

As output, she wants the student's name followed by the number answered correctly written in a file, one student per line.

Brainstorming How would you grade these exams by hand? If the student's answers were written as a sequence of Ts and Fs, you would probably take a sheet of paper, write down the Ts and Fs from the key, fold the paper so that the Ts and Fs were on the top, and line up the paper under a student's answer. You could then compare them one at a time marking through the student's answer that did not agree with the key. So, what are the objects in the problem?

We are working with an exam key and a student's answer, where the student's answer has two parts: the name and a series of Ts and Fs. We need a container to hold the key, and a container to hold a student's answer. We also need file objects.

Click To expand

Filtering Let's look at the key object first. An exam is made up of questions, the answers to which are either T or F. Therefore, the key is an ordered collection of Ts and Fs that represent the correct answers to the questions. Let's call this class TheKey. The student answer contains the student's name and a sequence of Ts and Fs. Yes, this set of objects looks complete. Oh, we forgot the class that contains Form1, the application class.

Click To expand

Scenarios What are the responsibilities of TheKey class? That is, what operations must the class perform on the Ts and Fs? The class must create itself, initialize the Ts and Fs to the correct pattern, and grade a student's answer.

The second object is a student's answer. This object contains a student's name and an ordered collection of Ts and Fs representing the student's answers to the exam questions. Let's call this class AStudentAnswer. What are the responsibilities of this class? It must create itself, input a name and a sequence of Ts and Fs, pass the Ts and Fs to the key to be graded, and print out the student name and the number of correct answers.

At this stage you should step back and see if there is anything missing from the design. You notice that the sample data your teacher gives you has 15 questions. The test you took last week had 20 questions. This means that the number of questions varies, so the number of questions on the exam must be input to the constructor for class TheKey to determine the number of Ts and Fs. Class AStudentAnswer also needs to know the number of questions.

Click To expand

Data Representation The key and the answer are an ordered collection of Ts and Fs. The array is the obvious choice for holding the Ts and Fs. The index of an individual answer is the question number minus 1. Now we are ready to design the algorithms for the three classes.

Responsibility Algorithms for TheKey In our by-hand algorithm, we wrote the key so that we could hold it next to the student answers and compare them, position by position. The operational verb is the discussion is "compare." To grade a student's answer, the operation must compare its ordered collections of Ts and Fs with the ordered collection of Ts and Fs representing a student's answer. The return value is the number of places where the values match.

...........................................................................
Public TheKey(number As Integer)                                    Level 1

 Create key as an array of number characters

...........................................................................
Public Sub readKey(inFile As StreamReader)

 For counter going from 0 through key.Length - 1
    Get next answer
    Set key(counter) to next answer

...........................................................................
Public Sub numberCorrect(answer As Char)

 Set noCorrect to 0
 For counter going from 0 through key.Length - 1
   If (key(counter) = answer.(counter))
       increment noCorrect
 return noCorrect

Responsibility Algorithms for AStudentAnswer
...........................................................................
Public Sub AStudentAnswer(number As Integer)

 Create an answer as an array of number characters

...........................................................................
Public Sub readStudentName(inFile As StreamReader)

 Read a student name from inFile

...........................................................................
Public Sub readStudentAnswer(inFile As StreamReader)

 For counter going from 0 through answer.Length - 1
    Get next answer
    Set answer(counter) to next answer

...........................................................................
Public Sub writeStudentName(outFile As StreamWriter)

 Write name

...........................................................................
Public Sub writeNumberCorrect(outFile As StreamWriter, key As TheKey)

 Set score to key.numberCorrect(answer)
 Write " number correct " + score

As is usually true in an object-oriented design, the work is done in the classes. The final algorithm that pulls the pieces together is in Form1.

...........................................................................
Public Class Form1

 Import TheKey class
 Import AStudentAnswer class
 Declare key of class TheKey
 Declare studentExam of class AStudentAnswer
 Prepare file inFile for input
 Prepare file outFile for output
 key.readKey(inFile)
 While more students
  studentExam.readStudentName(inFile)
  studentExam.readStudentAnswer(inFile)
  studentExam.writeStudentName(outFile)
  studentExam.writeNumberCorrect(outFile, key)
 Close files

"Declare…". We have forgotten something. How do we know how many questions there are on this exam? The user could enter it from the keyboard as an event or the user could enter the number of questions in the file on the line immediately before the key. Because this program does not need the user to be present to enter any other data, putting the number of questions in the data file is a better choice.

How should the loop be implemented? In the past the looping structure has been within the method that does the reading. Here the reading is done within a method invoked within the loop. When reading the student name, readStudentName can determine that there is no more data, but the algorithm did not do so. What is more, how does that information get back into the calling program to control the loop? We need to add one more observer, moreData, to class AStudentAnswer, which checks to see if there was another student's name in the file.

...........................................................................
Public Sub moreData() As Boolean

 return name <> -1

...........................................................................
Public Class Form1

 Import TheKey class
 Import AStudentAnswer class
 Prepare file for input
 Prepare file for output
 Get noQuestions
 Declare key(noQuestions) of class TheKey
 Declare studentExam(noQuestions) of class AStudentAnswer
 key.readKey(inFile)
 studentExam.readStudentName(inFile)

 While studentExam.moreData()
   studentExam.readStudentAnswer(inFile)
   studentExam.writeStudentName(outFile)
   studentExam.writeNumberCorrect(outFile, key)
   studentExam.readStudentName(inFile)
 Close files

Now we are ready to code our solution. Let's implement the classes first and put them into a namespace called Grader.

Imports System.ComponentModel
Imports System.Drawing
Imports System.WinForms
Imports System.IO

Namespace Grader                    ' Namespace declaration
    Public Class TheKey
        ' Private data members
        Private key() As Char
        ' Class constructor
        Public Sub new(ByVal number As Integer)
            MyBase.new()
            ReDim key(number)       ' Instantiate array object
        End Sub
        ' Methods
        Public Sub readKey(ByRef inFile As StreamReader, _
                           ByRef outFile As StreamWriter)

        ' Transformer: Input Ts and Fs
        Dim datum As Integer
        Dim counter As Integer
        For counter = 0 To (key.Length - 1)
            datum = inFile.Read()
            key(counter) = CChar(datum)
        Next counter
        inFile.ReadLine()       ' Read past end of line
    End Sub

    Public Function numberCorrect(ByRef answer() As Char) As Integer
        ' Observer: Returns the number of correct answers
        Dim noCorrect As Integer = 0
        Dim counter As Integer
        For counter = 0 To key.Length - 1
            If (key(counter) = answer(counter)) Then
                noCorrect += 1
            End If
        Next counter
        Return noCorrect
    End Function
End Class               ' End of class TheKey
Public Class AStudentAnswer
    ' Private data members
    Private answer() As Char
    Private name As String
    ' Constructor
    Public Sub New(ByRef number As Integer)
        ReDim answer(number)
    End Sub
    ' Methods
    Public Sub readStudentName(ByRef inFile As StreamReader)
        ' Transformer: Read in a student's name
        name = inFile.ReadLine()
    End Sub
    Public Sub writeStudentName(ByRef outFile As StreamWriter)
        ' Observer: Output a name
        outfile.Write(name)
    End Sub
    Public Sub readStudentAnswer(ByRef inFile As StreamReader)
        ' Transformer: Read in a student's answers
        Dim datum As Integer
        Dim counter As Integer

        For counter = 0 To answer.Length - 1
            datum = inFile.Read()
            answer(counter) = CChar(datum)
        Next
        inFile.ReadLine()       ' Read past end of line
    End Sub
    Public Function moreData(ByVal inFile As StreamReader) As Boolean
        ' Observer: Returns True if there is more data
        Return (inFile.Peek <> -1)
    End Function
    Public Sub writeNumberCorrect(ByRef outFile As StreamWriter, _
                                  ByVal key As TheKey)
        ' Observer: Write out the student name and score
        Dim score As Integer
        score = key.numberCorrect(answer)
        outFile.WriteLine(" number correct " & score)
    End Sub
End Class           ' End of class AStudentAnswer

End Namespace

' Driver for classes in namespace Grader
Public Class Form1
    Inherits System.WinForms.Form
    ' Student True/False exams are graded
    ' Variable declarations
    Dim theFile As File
    Dim inFile As StreamReader
    Dim outFile As StreamWriter
    Dim key As Grader.TheKey
    Dim noQuestions As Integer               ' Number of questions
    Dim studentExam As Grader.AStudentAnswer

    Public Sub New()
        MyBase.New()
        Form1 = Me
        InitializeComponent()
        ' Prepare files for input and output
        inFile = theFile.OpenText("c:\datafile.dat")
        outFile = theFile.CreateText("c:\outfile.dat")
        ' Set up key and student answer arrays
        noQuestions = CInt(inFile.ReadLine())
        key = New Grader.TheKey(noQuestions)
        studentExam = New Grader.AStudentAnswer(noQuestions)
        key.readKey(inFile, outFile)

        ' Priming read
        studentExam.readStudentName(inFile)
        While (studentExam.moreData(inFile))
            ' Process exams until end of file
            studentExam.readStudentAnswer(inFile)
            studentExam.writeStudentName(outFile)
            studentExam.writeNumberCorrect(outFile, key)
            studentExam.readStudentName(infile)
        End While
        inFile.Close()
        outFile.Close()
        Messagebox.Show("Application finished.")
        End          ' Exits system without showing blank Form1
    End Sub

    ' Form overrides dispose to clean up the component list.
    Public Overrides Sub Dispose()
        MyBase.Dispose()
        components.Dispose()
    End Sub

#Region " Windows Form Designer generated code "
#End Region

End Class

Testing This program has two classes and a driver. We have been using the word driver to refer to a simple program class that is used to test a method. Here we are using it in its other context: the main class in an object-oriented design. The driver in this case study is class Form1. Actually, these two definitions are not dissimilar. In both cases, the driver starts the process. In a regular application, the driver implements the top-level algorithm, which might start any process. In a test driver, the process is always the same because its role is strictly to test one or more methods.

Our application program serves as both the main application and the test driver by carefully choosing data sets to test the application. That is, we can use a black-box testing strategy.

What are the inputs to the program? There are three: an integer that specifies the number of questions, a sequence of Ts and Fs that represent the key and a student's answers, and a string that represents a student's name. For the moment, let's assume that the data is correct in the file, and concentrate on the main processing: the comparison of the key and a student exam. The following cases come immediately to mind.

  1. The key and the student response match completely (all are correct).

  2. The key and the student response do not match at all (all are wrong).

  3. The key and the student response partially match (some are correct).

Depending on the number of questions, there could be thousands of cases in the third category. How many do we have to try to convince ourselves that the program is correct? The end cases here would be that they agree in the first and last positions, they do not agree in the first and last position, they agree somewhere in the middle, and they disagree somewhere in the middle.

As the algorithm is not dependent on the number of questions, let's use 5 for our test cases.

Test # | Reason for Test | Input | Expected Output
 |  | 5 TTTTT |
1 | All Correct (Also tests same in first and last position) | Jones TTTTT | Jones number correct 5
2 | All wrong (Also tests different in first and last position) | Janes FFFFF | Janes number correct 0
3 | Partially correct | Julian TFTFT | Julian number correct 3
4 | Test ending condition | EOF in data file | Program ends

Provided our input is correct, if the implemented test plan produces the results expected, we should be fairly confident that the program is correct. Provided our input is correct? We have built in no error checking into these classes. This is a serious flaw in our design. The Case Study Follow-Up Exercises ask you to make these classes more robust by adding error checking.

End Sidebar


Testing and Debugging

The most common error in processing arrays is an out-of-bounds array index. That is, the program attempts to access a component using an index that is either less than 0 or greater than the array size minus 1. For example, given the declarations:

Dim line(100) As Char
Dim counter As Integer

the following For statement would print the 100 elements of the line array and then try to print a 101st value.

For counter = 0 To counter.Length
    outFile.Write(line(counter))
Next counter

This error is easy to detect, because your program will halt with an IndexOutOfRangeException. The loop upper range should be counter.Length-1. But you won't always use a simple For statement when accessing arrays. Suppose we read data into the line array in another part of the program. Let's use a While statement that reads to the end of the file:

counter = 0
While (inFile.Peek <> -1)
    inFile.Read(ch)
    line(counter) = ch
    counter += 1
End While

This code seems reasonable enough, but what if the input line has more than 100 characters? After the hundredth character is read and stored into the array, the loop executes one more time and the IndexOutOfRangeException is thrown, causing the program to crash.

The moral is: When processing arrays, give special attention to the design of loop termination conditions. Always ask yourself if the loop could possibly keep running after the last array component has been processed.

Whenever an array index goes out of bounds, the first suspicion should be a loop that fails to terminate properly. The second thing to check is any array access involving an index that is based on an input value or a calculation. When an array index is input as data, a data validation check is an absolute necessity.

As we have demonstrated in many examples in the last several chapters, it is possible to combine data structures in various ways: classes whose components are objects, classes whose components are arrays, arrays whose components are objects, arrays whose components are strings, and so forth. When arrays of objects are used, there can be confusion about precisely where to place the operators for array element selection (()) and class member selection (.).

To summarize the correct placement of these operators, let's use a StudentRec class, where the data members are defined as:

Private Class StudentRec
    Private stuName As String
    Private gpa As Single
    Private examScores As Integer
    Private courseGrade As Char
    ...
End Class

If we declare a variable of class StudentRec and an array of class StudentRec components

Dim student As StudentRec
Dim members(100) As StudentRec

the following chart show how to access the fields of student. Recall that the dot operator is a binary (two-operand) operator; its left operand denotes a class variable or class name, and its right operand is a member. The () operator is a unary (one-operand) operator; it comes immediately after an expression denoting an array.

Expression | Class/Type | Meaning
student | A StudentRec object | A single student
student.stuName | A string | A name
student.gpa | A real number | A gpa
student.examScores | An array of integers |
student.examScores(0) | An integer | The first exam score
student.examScores(4) | Crash! Index out of range |
student.courseGrade | A character |
members(0).student | A StudentRec object | The first student
members(0).stuName | A string | The name of the first student
members(0).gpa | A real number | The gpa of the first student
members(0).examScores | An array of integers | The exam scores of the first student
members(0).examScores(1) | An integer | The second score for the first student

Testing and Debugging Hints

  1. When an individual component of a one-dimensional array is accessed, the index must be within the range 0 through the array size minus 1. Attempting to use an index value outside this range causes your program to crash.

  2. The individual components of an array are themselves variables of the component type. When values are stored into an array, they should either be of the component type or be explicitly converted to the component type; otherwise, an implicit conversion error occurs.

  3. Unlike other Visual Basic composite types, arrays can be declared and instantiated in the same statement. However, if you are going to create a dynamic array, you cannot specify the size of the array in the declaration statement.

  4. When an array is a parameter, the reference to the array object is passed to the method. The reference cannot be changed by the method, but the elements in the array can be changed.

  5. An individual array component can be passed as an argument. If the component is a reference type, it can be changed within the method if it is a mutable object. If the component is an atomic type, it cannot be changed within the method.

  6. Although reference types passed as a parameter can be changed if the type is mutable, it is bad style to do so.

  7. Subarray processing is used to process array components when the actual number of data items is not known until the program is executing. The Length property of the array object contains the number of slots in the array; the number of data values stored into the array may differ.

  8. When methods perform subarray processing on a one-dimensional array, the array name and the number of data items actually stored in the array should be encapsulated together into a class.

  9. When a one-dimensional array is instantiated, the constructor for the component class is called.

  10. When processing the components in a one-dimensional array, use a loop that begins at zero and stops when the counter is equal to one minus the Length property associated with the array object.

  11. You can increase the size of a dynamic array without losing any data in the array, but if you decrease the size of a dynamic array, you will lose any data stored in the elements removed from the array.


Summary

The one-dimensional array is a homogeneous data structure that gives a name to a sequential group of like components. Each component is accessed by its relative position within the group (rather than by name, as in a class), and each component is a variable of the component type. To access a particular component, we give the name of the array and an index that specifies which component of the group we want. The index can be an expression of any type that can be implicitly converted to Integer. Array components can be accessed in random order directly, or they can be accessed sequentially by stepping through the index values one at a time.


Quick Check

Q:

1. Declare and instantiate a one-dimensional array named quizAnswer that contains 12 components indexed by the integers 0 through 11. The component type is Boolean. (pp. 447–448).

1. Dim quizAnswer(12) As Boolean

Q:

2. Given the declarations

Const SIZE As Integer = 30
Dim firstName(SIZE) as Char

  1. Write an assignment statement that stores "A" into the first component of array firstName. (pp. 449–450)

  2. Write an output statement that prints the value of the 14th component of array firstName. (pp. 449–450)

  3. Write a For statement that fills array firstName with blanks. (p. 452)

2. a. firstname(0) = A c b. outFile.WriteLine(firstName(13)) c. For index = 0 To firstName.Length-1 firstName(index) = ` ` Next index

Q:

3. Declare and instantiate a five-element one-dimensional Integer array named oddNums by using an initializer list to contain the first five odd integers, starting with 1. (pp. 448–449).

3. Dim oddNums() As Integer = {1, 3, 5, 7, 9}

Q:

4. Give the heading for a void function named someFunc, where someFunc has a single parameter, a one-dimensional Single array, values. (p. 462).

4. Public Function someFunc(ByRef values() As Single)

Q:

5. Given the declaration

Dim gradebook(150) as StudentRec

where StudentRec is the class type defined in this chapter, do the following.

  1. Write an assignment statement that records the fact that the tenth student has a grade point average of 3.25. (pp. 458–461)

  2. Write an assignment statement that records the fact that the fourth student scored 78 on the third exam. (pp. 458–461)

5. a. gradeBook(9) .gpa = 3.25 b. gradeBook(3) .examScores(2) = 78

Q:

6. Given the declarations in Question 2 and the following program fragment that reads characters into array firstName until a blank is encountered, write a For statement that prints out the portion of the array that is filled with input data. (pp. 462–463)

n = 0
letter = CChar(inFile.Read())
While (letter <> " ")
    firstName(n) = letter
    n += 1
    letter = CChar(inFile.Read())
End While

6. For index = 0 To n-1 outFile.Write(firstName(index)) Next index

Answers

A:

1. Dim quizAnswer(12) As Boolean

A:

2.

  1. firstname(0) = "A"c

  2. outFile.WriteLine(firstName(13))

  3. For index = 0 To firstName.Length-1
    firstName(index) = " "
Next index

A:

3. Dim oddNums() As Integer = {1, 3, 5, 7, 9}

A:

4. Public Function someFunc(ByRef values() As Single)

A:

5.

  1. gradeBook(9) .gpa = 3.25

  2. gradeBook(3) .examScores(2) = 78

A:

6.

For index = 0 To n-1
    outFile.Write(firstName(index))
Next index


Exam Preparation Exercises

    Chapter 11

  1. (True or False?) Every component in an array must have the same type, and the number of components is fixed at creation time.

  2. (True or False?) The components of an array must be of a primitive type.

  3. Declare and instantiate one-dimensional arrays according to the following descriptions.

    1. A 24-element Single array

    2. A 500-element Integer array

    3. A 50-element double-precision array

    4. A 10-element Char array

  4. Write a code fragment to do the following tasks:

    1. Declare a constant named CLASS_SIZE representing the number of students in a class.

    2. Declare a one-dimensional array quizAvg whose components will contain floating-point quiz score averages.

    3. Instantiate quizAvg of size CLASS_SIZE.

  5. Write a code fragment to do the following tasks:

    1. Declare a one-dimensional Integer array birdSightings.

    2. Instantiate the array with 20 components.

  6. Given the declarations

    Const SIZE As Integer = 100
dim count(SIZE) As Integer

    write code fragments to do the following tasks:

    1. Set count to all zeros.

    2. Read values into the array.

    3. Sum the values in the array.

  7. What is the output of the following code fragment? The data for the program is given below it.

    Dim a(100) As Integer
Dim b(100) As Integer
Dim j As Integer

Dim m As Integer
Dim sumA As Integer = 0
Dim sumB As Integer = 0
Dim sumDiff As Integer = 0
m = CInt(inFile.ReadLine())
For j = 0 To m-1
    a(j) = CInt(inFile.ReadLine())
    b(j) = CInt(inFile.ReadLine())
    sumA = sumA + a(j)
    sumB = sumB + b(j)
Next j
For j = (m-1) To 0 Step -1
    outFile.WriteLine(a(j) & " " & b(j) & " " & (a(j) - b(j))
Next j
outFile.WriteLine()
outFile.WriteLine(sumA & " " & sumB & " " & sumDiff)
End

    Data

     5
11
15
19
14
 4
 2
17
 6
 1
 3

  8. A person wrote the following code fragment, intending to print 10 20 30 40.

    Dim arr() As Integer = {10, 20, 30, 40}
Dim index As Integer
For index = 1 to 4
  outFile.WriteLine(" " & arr(index))
Next index

    Instead, the program halted with a built-in exception. Explain the reason for this output.

  9. Given the declaration

    int sample(8) As Integer
Dim j As Integer
Dim k As Integer

    show the contents of the array sample after the following code segment is executed. Use a question mark to indicate any undefined values in the array.

    For k = 0 To 7
    sample(k) = 10 - k
Next k

  10. Using the same declarations and contents given for Exercise 9, show the contents of the array sample after the following code segment is executed.

    For j = 0 To 7
    If (j <= 3) Then
        sample(j) = 1
    Else
        sample(j) = -1
    End If
Next j

  11. Using the same declarations and contents given for Exercise 9 and the content stored in Exercise 10, show the contents of the array sample after the following code segment is executed.

    For k = 0 To 7
    If (k mod 2 = 0) Then
        sample(k) = k
    Else
        sample(k) = k + 100
Next k

  12. What are the two basic differences between a class and an array?

  13. If an array is passed as a parameter, can the method change the array?

  14. For each of the following descriptions of data, determine which general type of data structure (array of primitive types, class, array of classes, class containing classes) is appropriate.

    1. A payroll entry with a name, address, and pay rate

    2. A person's address

    3. An inventory entry for a part

    4. A list of addresses

    5. A list of hourly temperatures

    6. A list of passengers on an airliner, including names, addresses, fare class, and seat assignment

    7. A departmental telephone directory with last name and extension number

  15. What happens in Visual Basic if you try to access an element that is outside the dimensions of the array?

  16. What are the array components initialized to when the array is instantiated?

  17. What are the array components initialized to when the array is instantiated using an initializer list?


Programming Warm-Up Exercises

Use the following declarations in Exercises 1–7. You may declare any other variables that you need.

   Const NUM_STUDENTS As Integer = 100    ' Number of students
   Dim failing(NUM_STUDENTS) As Boolean
   Dim passing(NUM_STUDENTS) As Boolean
   Dim grade As Integer
   Dim score(NUM_STUDENTS) As Integer
    Chapter 11

  1. Write a Visual Basic function that initializes all components of failing to False.

  2. Write a Visual Basic function that takes score as a parameter. Set the components of failing to True whenever the corresponding value in score is less than 60.

  3. Write a Visual Basic function that has score as a parameter. Set the components of passing to True whenever the corresponding value in score is greater than or equal to 60.

  4. Write a Visual Basic value-returning function passTally that reports how many components in passing are True.

  5. Write a Visual Basic value-returning function error that takes passing and failing as parameters. error returns True if any corresponding components in passing and failing are the same.

  6. Write a Visual Basic value-returning function that takes grade as a parameter. The function reports how many values in score are greater than or equal to grade.

  7. Write a Visual Basic function that takes score as a parameter and reverses the order of the components in score; that is, score(0) goes into score(score.Length-1), score(1) goes into score(score.Length-2), and so on.

  8. Write a program segment to read in a set of part numbers and associated unit costs. Use an array of classes with two data members, number and cost, to represent each pair of input values. Assume the end-of-file condition terminates the input.


Programming Problems

  1. The local baseball team is computerizing its records. There are 20 players on the team, identified by the numbers 1 through 20. Their batting records are coded in a file as follows. Each line contains four numbers: the player's identification number and the number of hits, walks, and outs he or she made in a particular game. Here is a sample
    3 2 1 1

    The example above indicates that during a game, player number 3 was at bat four times and made 2 hits, 1 walk, and 1 out. For each player there are several lines in the file. Each player's batting average is computed by adding the player's total number of hits and dividing by the total number of times at bat. A walk does not count as either a hit or a time at bat when the batting average is being calculated.

    Design and implement an application that prints a table showing each player's identification number, batting average, and number of walks. (Be careful: The player's identification numbers are 1 through 20, but Visual Basic array indexes start at 0.)

  2. Design, implement, and test a class that calculates the mean and standard deviation of integers stored in a file. The output should be of type Single and should be properly labeled and formatted to two decimal places. The formula for calculating the mean of a series of integers is to add all the numbers, then divide by the number of integers. Expressed in mathematical terms, the mean [image] of N numbers X1, X2, …, XN is

    To calculate the standard deviation of a series of integers, subtract the mean from each integer (you may get a negative number) and square the result, add all these squared differences, divide by the number of integers minus 1, then take the square root of the result. Expressed in mathematical terms, the standard deviation S is

    The methods of the class that access the input data should take a file as a parameter.

  3. One of the local banks is gearing up for a big advertising campaign and would like to see how long its customers are waiting for service at drive-up windows. Several employees have been asked to keep accurate records for the 12-hour drive-up service. The collected information, which is read from a file, consists of the time the customer arrived in hours, minutes, and seconds; the time the customer actually was served; and the ID number of the teller. Design and implement a class with the following responsibilities:

    1. Reads in the wait data.

    2. Computes the wait time in seconds.

    3. Calculates the mean, standard deviation (defined in Programming Problem 2), and range.

    4. Prints a single-page summary showing the values calculated in part c.

    Input

    • The first data line contains a title.

    • The remaining lines each contain a teller ID, an arrival time, and a service time.

    • The times are broken up into integer hours, minutes, and seconds according to a 24-hour clock.

    Processing

    • Calculate the mean and the standard deviation.

    • Locate the shortest wait time and the longest wait time for any number of records up to 100.

    Output

    • The input data (echo print).

    • The title.

    • The following values, all properly labeled: number of records, mean, standard deviation, and range (minimum and maximum).

  4. Your history professor has so many students in her class that she has trouble determining how well the class does on exams. She has discovered that you are a computer whiz and has asked you to write a program to perform some simple statistical analysis on exam scores. Your program must work for any class size up to 100. Write and test a computer program that does the following:

    1. Reads the test grades from file inData.

    2. Calculates the class mean, standard deviation (defined in Programming Problem 2), and percentage of the test scores falling in the ranges <10, 10-19, 20-29, 30-39, …, 80-89, and greater than or equal to 90.

    3. Prints a summary showing the mean and the standard deviation, as well as a histogram showing the percentage distribution of test scores.

    Input

    • The first line contains the number of exams to be analyzed and a title for the report.

    • The remaining lines have 10 test scores on each line until the last line, and from one to 10 scores on the last. The scores are all integers.

    Output

    • The input data as they are read.

    • A report consisting of the title that was read from the data, the number of scores, the mean, the standard deviation (all clearly labeled), and the histogram.

  5. Write an application that reads an apartment number and the number of occupants in the apartment. The apartment number is to be used as an index into an array of apartments. The components in the array represent the number of people who live in the apartment. Use the data structure described in the chapter. Use window input and print the number of people in the building when the user pushes the Quit button.


Case Study Follow-Up Exercises

    Chapter 11

  1. The DataValidation application compares two lists of integers. Exactly what changes would be necessary for the program to compare two lists of Single values?

  2. Modify the DataValidation application so that it works even if the lists do not have the same number of elements. Print an appropriate error message and stop the comparison if the two lists do not have the same number of elements.

  3. The exam grading application contains no error checking.

    1. What happens if a letter other than a T or F is entered in the key?

    2. What happens if a letter other than a T or F is entered for a student?

    3. It is easy to put a check in to be sure that the input values are a T or F, but what should the program do if an error occurs?

  4. Redesign and rewrite the exam grading application to incorporate error checking based on the answers in Exercise 3.

  5. When the authors ran the test plan originally, the names didn't print and a format exception error occurred. In the original version of the application, the following statement was not included after reading in a series of Ts and Fs:

    inFile.ReadLine()

    When this statement was added in two places, the application worked correctly. Explain.


Chapter 12: Array-Based Lists

Overview

Goals

  • To be able to distinguish between an array and a list.

  • To be able to insert an item into a list.

  • To be able to delete an item from a list.

  • To be able to search for an item in a list.

  • To be able to sort the items in a list into ascending or descending order.

  • To be able to build a list in sorted order.

  • To be able to search for an item using a binary search.

  • To be able to use the Visual Basic Comparable interface.

Chapter 11 introduced the array, a structured data type that holds a collection of components of the same type or class given a single name. In general, a one-dimensional array is a structure used to hold a list of items. We all know intuitively what a "list" is; in our everyday lives we use lists all the time-grocery lists, lists of things to do, lists of addresses, lists of party guests. In computer programs, lists are very useful and common ways to organize the data. In this chapter, we examine algorithms that build and manipulate lists implemented using a one-dimensional array to hold the items.


12.1 Lists

From a logical point of view, a list is a homogenous collection of elements, with a linear relationship between elements. Linear means that, at the logical level, each element in the list except the first one has a unique predecessor, and each element except the last one has a unique successor.[1] The number of items in the list, which we call the length of the list, is a property of a list. That is, every list has a length.

Linear relationship
Each element except the first has a unique predecessor, and each element except the last has a unique successor
Length
The number of items in a list; the length can vary over time.
Unsorted list
A list in which data items are placed in no particular order with respect to their content; the only relationship between data elements is the list predecessor and successor relationships.
Sorted list
A list with predecessor and successor relationships determined by the contents of the keys of the items in the list; there is a semantic relationship among the keys of the items in the list
Key
A member of a class whose value is used to determine the logical and/or physical order of the items in a list

Lists can be unsorted—their elements may be placed into the list in no particular order—or they can be sorted in a variety of ways. For instance, a list of numbers can be sorted by value, a list of strings can be sorted alphabetically, and a list of addresses can be sorted by zip code. When the elements in a sorted list are composite types, their logical (and often physical) order is determined by one of the members of the structure, the key member. For example, a list of students on a class roll can be sorted alphabetically by name or numerically by student identification number. In the first case, the name is the key; in the second case, the identification number is the key (see Figure 12.1).

Click To expand
Figure 12.1: List sorted by two different keys

If a list cannot contain items with duplicate keys, it is said to have unique keys (see Figure 12.2). This chapter deals with both unsorted lists and lists of elements with unique keys, sorted from smallest to largest key value. The items on the list can be of any type, atomic or composite. In the following discussion, "item," "element," and "component" are synonyms; they are what is stored in the list.

Click To expand
Figure 12.2: (a) List with duplicate keys and (b) List with unique keys

[1]At the implementation level, there is also a relationship between the elements but the physical relationship may not be same as the logical one.


12.2 List Class

In this section, we design and implement a general-purpose class that represents a list of items. Let's think in terms of a to-do list. But before we begin to brainstorm, we must ask an important question: For whom are we designing the class? We may be designing it for ourselves to keep in our library of classes. We may be designing it for others to use in a team project. When we design a class, the software that uses it is called the client of the class. In our discussion, we use the terms client and user interchangeably, thinking of the client as referring to the people writing the software that uses the class, rather than the software itself.

Client
Software that declares and manipulates objects of a particular class

Brainstorming the List Class

Because we are designing a general-purpose class, our brainstorming must be more speculative. We don't have a specific problem to solve; we have to think in terms of what we do with our to-do lists and what other things we might like to do if we could. Hopefully, we start with an empty list each morning and add things to it. As we accomplish a task on the list, we cross it off. We check to see if an item is on the list. We check to see if the list is empty (we wish!). We go through the list one item at a time. Let's translate these observations into responsibilities for the list with their responsibility type.

Click To expand

Although we have designed our CRC card for a to-do list, the responsibilities are valid for any kind of list. For example, if we are creating a list of people to invite to a wedding, all of these operations are valid. We add names to the list, check whether a name is on the list, count names on the list, check to see if the list if full (i.e., the length is equal to the number of invitations bought), delete names, and review the names one at a time.

To make the rest of the discussion more concrete, let's assume that the items on the list are strings. We then show how the items can be made even more general.

Refining the Responsibilities

Let's go back through the responsibilities, refining them and converting them into method headings. Because we are designing a general-purpose class, we do not have any specific scenarios that we can use. Instead, we consider a variety of simplified scenarios that are examples of how we believe the class might be employed. Because the class is intended for widespread use, we should pay special attention to the documentation right from the design stage.

The observers, testing for full and empty, returning the number of items, and checking to see if an item is in the list, need no further discussion. Here are their method headings.

Public Function isFull() As Boolean
' Returns True if no room to add a component, False otherwise

Public Function isEmpty() As Boolean
' Returns True if no components in the list, False otherwise

Public Function Length() As Integer
' Returns the number of components in the list

Public isThere(item As String) As Boolean
' Returns True if item is in the list, False otherwise

In designing the transformers, we have to make some decisions. For example, do we allow duplicates in our list? This has implications for deleting items as well as inserting items. If we allow duplicates, what do we mean by removing an item? Do we delete just one or all of them? Because the focus of this chapter is on algorithms, we just make a decision and design our algorithms to fit. We examine the effect of other choices in the exercises. Let's allow only one copy of an item in the list. This decision means that delete just removes one copy. However, do we assume that the item to be removed is in the list? Is it an error if it is not? Or does the delete operation mean "Delete, if there"? Let's use the last meaning.

We now incorporate these decisions into the documentation for the method headings.

Public Sub insert(item As String)
' Adds item to the list
' Assumption: item is not already in the list

Public Sub delete(item As String)
' Item is removed from the list if it is in the list

The iterator allows the user to see each item in the list one at a time. Let's call the method that implements the Know Each Item responsibility getNextItem. The list must keep track of the next item to return when the iterator is called so it uses a state variable to record the position. The constructor initializes this position to 0, and it is incremented in getNextItem. A client can use the length of the list to control a loop that asks to see each item in turn. As a precaution, the current position should be reset to 0 after the last item has been accessed. In an application, we might need a transformer iterator that goes through the list applying an operation to each item; however, for our general discussion we provide only an observer iterator.

What happens if a user inserts or deletes an item in the middle of an iteration? Nothing good you can be sure! Adding and deleting items changes the length of the list, making the termination condition of our iteration-counting loop invalid. Depending on whether an addition or deletion occurs before or after the iteration point, our iteration loop could end up skipping or repeating items.

We have several choices of how to handle this possibly dangerous situation. The list can throw an exception, the list can reset the current position when inserting or deleting, or the list can disallow transformer operations while an iteration is taking place. We choose the latter here by way of an assumption in the documentation. In case the user wants to restart an iteration, let's provide a resetMethod that reinitializes the current position.

Public Sub resetList()
' The current position is reset

Public Sub getNextItem() As String
' Assumption: No transformers have been called since the iteration began

Before we go to the implementation phase, let's look at how we might use getNextItem. Suppose the client program wants to print out the items in the list. The client program cannot directly access the list items, but it can use getNextItem to iterate through the list. The following code fragment prints the string values in list.

Dim next As String
Dim index As Integer
For index = 1 to list.Length()
   next = list.getNextItem()
   Console.WriteLine(next & " is still in the list.")
Next index

Now is also the time to look back over the list and see if we need to add any responsibilities. For example, do we need to provide an equals test? If we want to perform a deep comparison of two lists, we must provide equals; however, comparing lists is not a particularly useful operation, and we provide the client the tools to write a comparison operation if necessary. If fact, here is the client code to compare two lists. The algorithm determines if the lengths are the same and, if so, iterates through the lists checking to see if corresponding items are the same.

Click To expand

We can implement this algorithm without having to know anything about the list. We just use the instance methods supplied in the interface.

Public Function isDuplicate(list1 As List, list2 As List) As Boolean
   If (list1.Length() <> list2.Length()) Then
      return False
   Else
      Dim next1 As String
      Dim next2 As String
      Dim counter As Integer = 1
      Dim same As Boolean = True
      Dim limit As Integer = list1.Length()
      list1.resetList()
      list2.resetList()
      While (same And (counter <= limit))
         next1 = list1.getNextItem()
         next2 = list2.getNextItem()
         same = (next1.compareTo(next2) = 0)

         counter += 1
      End While
   End If
   return same
End Function

Internal Data Representation

How are we going to represent the items in the list? An array of strings is the obvious answer. What other data fields do we need? We have to keep track of the number of items in our list, and we need a state variable that tells us where we are in the list during an iteration.

Public Class List
   Protected listItems() As String
   Protected numItems As Integer
   Protected currentPos As Integer
   ...
End Class

We introduced the concept of subarray processing in the last chapter and pointed out that every Visual Basic array object has a field called length that contains the number of components defined for the array object. The literature for lists uses the identifier "length" to refer to the number of items that have been put into the list. Faced with this ambiguity in terminology, we still talk about the length of the list, but we refer to the field that contains the number of items in the list as numItems.

It is very important to understand the distinction between the array object that contains the list items and the list itself. The array object is listItems(0) .. listItems(listItems.Length-1); the items in the list are listItems(0)..list-Items(numItems-1). This distinction is illustrated in Figure 12.3. Six items have been stored into the list, which is instantiated with the following statement:

Click To expand
Figure 12.3: An instance of class List
Dim myList = new List(10)

Responsibility Algorithms for Class List

As Figure 12.3 shows, the list exists in the array elements listItems(0) through listItems(numItems-1). To create an empty list, it is sufficient to set the numItems field to 0. We do not need to store any special values into the data array to make the list empty, because only those values in listItems(0) through listItems(numItems-1) are processed by the list algorithms. We explain why currentPos is set to 0 when we look more closely at the interator.

Public Sub New(maxItems As Integer)
' Instantiates an empty list object with room for maxItems items
   numItems = 0
   Dim listItems(maxItems) As String
   currentPos = 0
End Sub

Should the class provide a default constructor? Let's do so as a precaution.

Public Sub New()
' Instantiates an empty list object with room for 100 items
   numItems = 0
   Dim listItems(100) As String
   currentPos = 0
End Sub

The observers isFull, isEmpty, and Length are very straightforward. Each is only one line long, as is so often the case in methods within the object-oriented paradigm.

Public Function isFull() As Boolean
' Returns True if no room to add a component, False otherwise
   Return (listItems.Length() = numItems)
End Sub

Public Function isEmpty() As Boolean
' Returns True if no components are in the list, False otherwise
   Return (numItems = 0)
End Sub

Public Function Length() As Integer
' Returns the number of components in the list
   Return numItems
End Sub

We have one more observer to implement: isThere. Because isThere is an instance method, it has direct access to the items in the list. We just loop through the items in the list looking for the one specified on the parameter list. The loop ends when we find the matching item or have looked at all the items in the list. Our loop expression has two conditions: (1) the index is within the list and (2) the corresponding list item is not equal to the one for which we are searching. After the loop is exited, we return the assertion that the index is still within the list. If this assertion is true, then the search item was found.

Click To expand

This algorithm can be coded directly into Visual Basic, using the compareTo method of String.

Public Function isThere(item As String) As Boolean
' Returns True if item is in the list
   Dim index As Integer = 0
   While((index < numItems) And (listItems(index).compareTo(item) <> 0))
      index += 1
   End While
   Return (index < numItems)
End Sub

This algorithm is called a sequential or linear search because we start at the beginning of the list and look at each item in sequence. We stop the search as soon as we find the item we are looking for (or when we reach the end of the list, concluding that the desired item is not present in the list).

We can use this algorithm in any program requiring a list search. In the form shown, it searches a list of String components, but the algorithm works for any class that has a compareTo method.

Let's look again at the heading for the operation that puts an item into the list.

Public Sub insert (item As String)
' Adds item to the list
' Assumption: item is not already in the list

Is there anything in the documentation that says where each new item should go? No, this is an unsorted list. Where we put each new item is up to the implementer. In this case, let's put each new item in the easiest place to reach: the next free slot in the array. Therefore, we can store a new item into listItems(numItems)—and then increment numItems.

This algorithm brings up a question: Do we need to check that there is no room in the list for the new item? We have two choices. The insert method can test numItems against listItems.Length and throw an exception if there isn't room, or we can let the client code make the test before calling insert. Our documentation is incomplete because it does not specify what occurs in this situation. Let's make the client code responsible for checking the isFull operation before an insertion. If the client attempts to insert an item into a full list, the operation fails and the list is unchanged.

This algorithm is so simple, we just go directly to code.

Public Sub insert(item As String)
' If the list is not full, puts item in the last position in the list:
' otherwise list is unchanged.
   If (Not listItems.IsFull())
      listItems(numItems) = item
      numItems += 1
   End If
End Sub

Deleting a component from a list consists of two parts: finding the item and removing it from the list. We can use the same algorithm we used for isThere to look for the item. We know from the documentation that the item may or may not be in the list. If we find it, how do we remove it? We shift every item after it up one array slot.

Click To expand

We can implement this algorithm using a For loop.

Public Sub Delete(item As String)
' Removes item from the list if it is there
   Dim index As Integer = 0
   Dim found As Boolean = False
   While (index < numItems And Not(found))
      If (listItems(index).compareTo(item) = 0) Then
         found = True
      Else
         index += 1

      End If
   End While
   If (found) Then
   ' Shift remainder of list up to delete item
      Dim count As Integer
      For count = index To numItems-1
         listItems(count) = listItems(count+1)
      Next count
      numItems -= 1
   End If
End Sub

The resetList method is analogous to the open operation for a file in which the file pointer is positioned at the beginning of the file, so that the first input operation accesses the first component of the file. Each successive call to an input operation gets the next item in the file. Therefore, resetList must initialize currentPos to the first item in the list. Where is the first item in an array-based list? In the 0th position. The getNextItem operation is analogous to an input operation; it accesses the current item and then increments currentPos. When currentPos reaches numItems, we reset it to 0.

Public Sub resetList() As String
' The iteration is initialized by setting currentPos to 0
   currentPos = 0
End Sub

Public Sub getNextItem() As String
' Returns the item at the currentPos position; increments currentPos;
' resets current position to first item after the last item is returned
' Assumption: No transformers have been invoked since last call
   Dim next As String = listItems(currentPos)
   If (currentPos = numItems-1) Then
      currentPos = 0
   Else
      currentPos += 1
   End If
   Return next
End Sub

Both of the methods change currentPos. Shouldn't we consider them transformers? We could certainly argue that they are, but their intention is to set up an iteration through the items in the list, returning one item at a time to the client.

Test Plan

The documentation for the methods in class List helps us identify the tests necessary for a black-box testing strategy. The code of the methods determines a clear-box testing strategy. To test the List class implementation, we use a combination of black-box and clear-box strategies. We first test the constructor by testing to see if the list is empty (a call to Length returns 0).

The methods Length, Insert, and Delete must be tested together. That is, we insert several items and delete several items. How do we know that the Insert and Delete work correctly? We must write an auxiliary method printList that iterates through the list using Length and getNextItem to print out the values. We call printList to check the status of the list after a series of insertions and deletions. To test the isFull operation, we must test it when the list is full and when it is not. We must also call Insert when the list is full to see that the list is returned unchanged.

Are there any special cases that we need to test for Delete and isThere? We look at the end cases. What are the end cases in a list? The item is in the first position in the list, the item is in the last position in the list, and the item is the only one in the list. So we must be sure that our Delete can correctly delete items in these positions. We must check that isThere can find items in these same positions and correctly determine that values are not in the list.

These operations are summarized in the following test plan. The tests are shown in the order in which they should be performed.

Operation to be Tested |  |  |  |
 | Description of Action | Input Values | Expected Output | Observed Output
Constructor (4) |  |  | 0 |
 | print Length |  |  |
Insert |  |  |  |
 | Insert four items and print | mary, john, betty, ann | mary, john, betty, ann |
 | Insert item and print |  | mary, john, betty, ann |
isThere |  |  |  |
 | isThere susy and print whether found |  | item is not found |
 | isThere mary and print whether found |  | item is found |
 | isThere ann and print whether found |  | item is found |
 | isThere betty and print whether found |  | item is found |
isFull |  |  |  |
 | invoke (list is full) |  | list is full |
 | delete ann and invoke |  | list is not full |
Delete |  |  |  |
 | Print |  | mary, john, betty |
 | Delete betty and print |  | mary, john, |
 | Delete mary and print |  | john |
 | Delete john and print |  | (empty) |
isEmpty |  |  | yes |

But what about testing resetList and getNextItem? They do not appear explicitly in the test plan, but they are tested each time the auxiliary method printList is called to print the contents of the list. We do however have to add one additional test involving the iterator. We must print out length plus 1 items to test whether the current position is reset after the last item is returned.

To implement this test plan, we must construct a test driver that carries out the tasks outlined in the first column of the plan. We can make the test plan a document separate from the driver, with the last column filled in and initialed by a person running the program and observing the screen output. Or we can incorporate the test plan into the driver as comments and have the output go to a file. The key to properly testing any software is in the plan: It must be carefully thought out and it must be written.


12.3 Sorting the List Items

getNextItem presents the items to the user in the order in which they were inserted. Depending on what we are using the list for, there might be times when we want to rearrange the list components into a certain order before an iteration. For example, if we are using the list for wedding invitations, we might want to see the names in alphabetical order. Arranging list items in order is a very common operation and is known in software terminology as sorting.

Sorting
Arranging the components of a list in order (for instance, words into alphabetical order or numbers into ascending or descending order)

If you were given a sheet of paper with a column of 20 names on it and asked to write the numbers in ascending order, you would probably do the following:

  1. Make a pass through the list, looking for the lowest name (the one that comes first alphabetically).

  2. Write it on the paper in a second column.

  3. Cross the name off the original list.

  4. Repeat the process, always looking for the lowest name remaining in the original list.

  5. Stop when all the names have been crossed off.

We could implement this algorithm as a client code, using getNextItem to go through the list searching for the lowest. When we find it, we could insert it into another list and delete it from the original. However, we would need two lists—one for the original list and a second for the sorted list. In addition, the client would have destroyed the original list. If the list is large, we might not have enough memory for two copies even if one is empty. A better solution is to derive a class from List that adds a method that sorts the values in the list. Because the data fields in List are protected, we can inherit them and access them directly. By accessing the values directly, we can keep from having to have two lists.

Click To expand

Responsibility Algorithms for Class ListWithSort

The constructor takes the maximum number of items and calls the list's constructor. None of the other methods need to be overridden.

Going back to our by-hand algorithm, we can search listItems for the smallest value, but how do we "cross off" a list component? We could simulate crossing off a value by replacing it with an empty string. We thus set the value of the crossed-off item to something that doesn't interfere with the processing of the rest of the components. However, a slight variation of our hand-done algorithm allows us to sort the components in place. We do not have to use a second list; we can put a value into its proper place in the list by having it swap places with the component currently in that list position.

We can state the algorithm as follows. We search for the smallest value in the array and exchange it with the component in the first position in the array. We search for the next-smallest value in the array and exchange it with the component in the second position in the array. This process continues until all the components are in their proper places. Figure 12.4 illustrates how this algorithm works.

Click To expand
Figure 12.4: Straight selection sort
Click To expand

Observe that we perform numItems-1 passes through the list because count runs from 0 through numItems-2. The loop does not need to be executed when count equals numItems-1 because the last value, listItems(numItems-1), is in its proper place after the preceding components have been sorted.

This sort, known as the straight selection sort, belongs to a class of sorts called selection sorts. There are many types of sorting algorithms. Selection sorts are characterized by finding the smallest (or largest) value left in the unsorted portion at each iteration and swapping it with the value indexed by the iteration counter. Swapping the contents of two variables requires a temporary variable so that no values are lost (see Figure 12.5).

Click To expand
Figure 12.5: Swapping the contents of two variables, x and y

Class ListWithSort

We are now ready to code our derived class. We need to include in the documentation that the alphabetic order may be lost with future insertions.

Public Class ListWithSort
Inherits List
   ' The items in the list are rearranged into ascending order
   ' This order is not preserved in future insertions

  Public Sub New(maxItems As Integer)
  ' An empty list object has been instantiated with room for maxItems items

    numItems = 0
    Dim listItems(maxItems) As String
    currentPos = 0
  End Sub

  Public Sub New()
  ' An empty list object has been instantiated with room for 100 items
    numItems = 0
    Dim listItems(100) As String
    currentPos = 0
  End Sub

  Public Sub selectSort()
  ' Arranges list items in ascending order
  ' Selection sort algorithm is used.
    Dim temp As Integer
    Dim passCount As Integer
    Dim searchIndex As Integer
    Dim minIndex As Integer
    For passCount = 0 To numItems - 1
       minIndex = passCount
       For searchIndex = passCount + 1 To numItems - 1
         If (listItems(searchIndex).compareTo(listItems(minIndex) < 0) Then
            minIndex = searchIndex
         End If
       Next searchIndex
       ' Swap listItems(minIndex) and listItems(passCount)
       temp = listItems(minIndex)
       listItems(minIndex) = listItems(passCount)
       listItems(passCount) = temp
    Next passCount
  End Sub

End Class

Note that with each pass through the outer loop in selectSort, we are looking for the minimum value in the rest of the array (listItems(passCount) through listItems(numItems-1)). Therefore, minIndex is initialized to passCount and the inner loop runs from searchIndex equal to passCount+1 through numItems-1. Upon exit from the inner loop, minIndex contains the position of the smallest value. (Note that the If statement is the only statement in the loop.)

This method may swap a component with itself, which occurs if no value in the remaining list is smaller than listItems(passCount). We could avoid this unnecessary swap by checking to see if minIndex is equal to passCount. Because this comparison is made in each iteration of the outer loop, it is more efficient not to check for this possibility and just to swap something with itself occasionally.

This algorithm sorts the components into ascending order. To sort them into descending order, we must scan for the maximum value instead of the minimum value. Simply changing the test in the inner loop from less than to greater than can accomplished this. Of course, minIndex would no longer be an appropriate identifier and should be changed to maxIndex.


12.4 Sorted List

It is important to note that ListWithSort does not provide the user with a sorted list class. The Insert and Delete algorithms do not preserve ordering by value. The Insert operation places a new item at the end of the list, regardless of its value. After selectSort has been executed, the list items remain in sorted order only until the next insertion or deletion takes place. Of course, the client could sort the list after every insertion, but this is inefficient. Let's now look at a sorted list design in which all the list operations cooperate to preserve the sorted order of the list components.

Brainstorming Sorted List

There is nothing about order in the design for class List. If we want the list items kept in sorted order, we need to specify this. Let's go back to the CRC card design for class List and indicate that we want the list to be sorted.

Click To expand

The first thing to notice is that the observers do not change. They are the same whether the list sorted by value or not. The transformers Insert and Delete and the iterator now have additional constraints. Rather than designing an entirely new class, we can derive SortedList from class List, overriding those methods whose implementations need changing.

Click To expand

Responsibility Algorithms for Class SortedList

Let's look first at Insert. Figure 12.6 illustrates how it should work.

Click To expand
Figure 12.6: All of the different places where an item can be inserted into a sorted list, starting with 0, 1, or 2 items already in the list.

The first item inserted into the list can go into the first position. If there is only one item, the list is sorted. If a second item being inserted is less than the first item, the first item must be moved into the second position and the new item put into the first position. If the second item is larger, it goes into the second position. If we add a third item that is smaller than the first item, the other two are shifted down one and the third item goes into the first position. If the third item is greater than the first item but less than the second, the second is shifted down and the third item goes into the second position. If the third item is greater than the second item, it goes into the third position.

To generalize, we start at the beginning of the list and scan until we find an item greater than the one we are inserting. We shift that item and the rest of the items in the list down one position to make room for the new item. The new item goes in the list at that point.

Click To expand
Click To expand

If we assume that index is the place where item is to be inserted, the algorithm for shifting the remainder down is as follows.

Click To expand

This algorithm is illustrated in Figure 12.7. Like the shiftUp algorithm, shiftDown can be implemented using a For loop.

Click To expand
Figure 12.7: Inserting into a sorted list

This algorithm is based on how we would accomplish this task by hand. Often, such an adaptation is the best way to solve a problem. However, in this case, further thought reveals a slightly better way. Notice that we search from the front of the list (people always do), and we shift down from the end of the list upward. We can combine the searching and shifting by beginning at the end of the list.

If item is the new item to be inserted, compare item to the value in listItems(numItems-1). If item is less, put listItems(numItems-1) into listItems(numItems) and compare item to the value in listItems(numItems-2). This process continues until you find the place where item is greater than (or equal to, if duplicates are allowed) the list item. Store item directly after it. Here is the algorithm:

Click To expand

Notice that this algorithm works even if the list is empty. When the list is empty, numItems is 0 and the body of the While loop is not entered. So item is stored into listItems(0), and numItems is incremented to 1. Does the algorithm work if item is the smallest? The largest? Let's see. If item is the smallest, the loop body is executed numItems times, and index is -1. Thus, item is stored into position 0, where it belongs. If item is the largest, the loop body is not entered. The value of index is still numItems -1, so item is stored into listItems(numItems), where it belongs.

Are you surprised that the general case also takes care of the special cases? This situation does not happen all the time, but it occurs sufficiently often that we find it is good programming practice to start with the general case. If we begin with the special cases, we may still generate a correct solution, but we may not realize that we don't need to handle the special cases separately. So begin with the general case, then treat as special cases only those situations that the general case does not handle correctly.

The methods Delete and getNextItem must maintain the sorted order—but they already do! An item is deleted by removing it and shifting all of the items larger than the one deleted up one position, and getNextItem only returns a copy of an item, it does not change an item. Only Insert needs to be overridden in derived class SortedList.

Public Class SortedList
Inherits List
Public Sub New(maxItems As Integer)
' An empty list object has been instantiated with room for maxItems item
   Dim numItems = 0
   Dim listItems(maxItems) As String

   currentPos = 0
End Sub
   Public Sub New()
  ' An empty list object has been instantiated with room for 100 items
  numItems = 0
  Dim listItems(100) As String
  currentPos = 0
  End Sub

   Public Sub Insert(item As String)
  ' If the list is not full, puts item in its proper place in the
  ' list, otherwise list is unchanged
     If (Not listItems.isFull()) Then
        Dim index As Integer = numItems - 1
        while (index >= 0 And (item.compareTo.(listItems(index)) < 0))
           listItems(index+1) = listItems(index)
           index -= 1
        end while
        listItems(index+1) = item
        numItems += 1
     End If
  End Sub
End Class

Test Plan

The same test plan can be used for the sorted list that we used for the unsorted version. The only difference is that in the expected output column, the list items should appear in sorted order.


12.5 The List Class Hierarchy and Abstract Classes

We have create a hierarchy with class List at the top and two derived classes. We can visualize the hierarchy as follows:

Click To expand

ListWithSort is a List. SortedList is a List. ListWithSort is not a SortedList, and SortedList is not a ListWithSort.

We could have organized the hierarchy using an abstract class. Recall from Chapter 8 that an abstract class is a class that is headed by the word MustInherit and leaves one or more methods incomplete. An abstract class cannot be instantiated. Another class must extend the abstract class and implement all of the abstract methods. We could have implemented the observers and iterator in the abstract class and left the implementation of the transformers to the derived class. Then the unsorted list and the sorted version could both inherit from the abstract class. Methods that are to be implemented by the derived class begin with the MustOverride keyword. An abstract method that is derived from the abstract class must include the Overrides modifier. The class and method headings would be as follows:

MustInherit Class list
   Public Sub New()
   End Sub

   Public Sub New(ByVal maxItems As Integer)
   End Sub

   Public Function isFull() As Boolean
   End Function

   Public Function isEmpty() As Boolean
   End Function

   Public Function Length() As Integer
   End Function

   Public Function isThere(ByVal item As String) As Boolean
   End Function

   Public Sub resetList()
   End Sub

   Public Function getNextItem() As String
   End Function

   MustOverride Sub Delete(ByVal item As String)
   MustOverride Sub Insert(ByVal item As String)

End Class

Class UnsortedList
   Inherits List

   Public Sub New()
   End Sub

   Public Sub New(ByVal maxItems As String)
   End Sub

   Public Overrides Sub Delete(ByVal item As String)
   End Sub

   Public Overrides Sub Insert(ByVal item As String)
   End Sub

End Class

Class SortedList
   Inherits List
   Public Sub New()
   End Sub

   Public Sub New(ByVal maxItems As Integer)
   End Sub

   Public Overrides Sub Delete(ByVal item As String)
   End Sub

   Public Overrides Sub Insert(ByVal item As String)
   End Sub

End Class

Class ListWithSort
   Inherits UnsortedList

   Public Sub New()
   End Sub

   Public Sub New(ByVal maxItems As Integer)
   End Sub

   Public Sub selectSort()
   End Sub

End Class

Notice that the classes that inherit from an abstract class cannot be declared as Public.

Under these new conditions, the class hierarchy looks like this:

Click To expand


12.6 Searching

In our SortedList class we overrode the Insert method, the only method that had to be rewritten to keep the list in sorted form. However, if the list is in sorted form, we can perform a more efficient search. In this section, we look at two searching algorithms that depend on the list items being in sorted order.

Sequential Search

The isThere algorithm assumes that the list to be searched is unsorted. A drawback to searching an unsorted list is that we must scan the entire list to discover that the search item is not there. Think what it would be like if your city telephone book contained people's names in random rather than alphabetical order. To look up Marcus Anthony's phone number, you would have to start with the first name in the phone book and scan sequentially, page after page, until you found it. In the worst case, you might have to examine tens of thousands of names only to find out that Marcus's name is not in the book.

Of course, telephone books are alphabetized, and the alphabetical ordering makes searching easier. If Marcus Anthony's name is not in the book, you discover this fact quickly by starting with the A's and stopping the search as soon as you have passed the place where his name should be. Although the sequential search algorithm in isThere works in a sorted list, we can make the algorithm more efficient by taking advantage of the fact that the items are sorted.

How does searching in a sorted list differ from searching in an unsorted list? When we search for an item in an unsorted list, we won't discover that the item is missing until we reach the end of the list. If the list is already sorted, we know that an item is missing when we pass the place where it should be in the list. For example, if a list contains the values

Click To expand

and we are looking for judy, we need only compare judy with becca, bobby, and june to know that judy is not in the list.

If the search item is greater than the current list component, we move on to the next component. If the item is equal to the current component, we have found what we are looking for. If the item is less than the current component, then we know that it is not in the list. In either of the last two cases, we stop looking. In our original algorithm, the loop conditions were that the index was within the list and the corresponding list item was not the one searched for. In this algorithm, the second condition must be that the item being searched for is less than the corresponding list item. However, determining whether the item is found is a little more complex. We must first assert that the index is within the list and, if that is true, assert that the search item is equal to the corresponding list item.

Click To expand

Why can't we just test to see if item is equal to listItems(index) at the end of the loop? This works on all cases but one. What happens if item is larger than the last element in the list? We would exit the loop with index equal to numItems. Trying to access listItems(index) would then cause the program to crash with the index out of range error. Therefore, we must check on the value of index first.

Public Sub isThere(item As String) As Boolean
' Returns True if item is in the list, False otherwise
' Assumption: List items are in ascending order
   Dim index As Integer = 0
   while (index < numItems And item.compareTo(listItems(index)) > 0) Then
      index += 1
   end while
   return (index < numItems And item.compareTo(listItems(index)) = 0)
End Sub

On average, searching a sorted list in this way takes the same number of iterations to find an item as searching an unsorted list. The advantage of this new algorithm is that we find out sooner if an item is missing. Thus, it is slightly more efficient. There is another search algorithm that works only on a sorted list, but it is more complex: a binary search. However, the complexity is worth it.

Binary Search

The binary search algorithm on a sorted list is considerably faster both for finding an item and for discovering that an item is missing. A binary search is based on the principle of successive approximation. The algorithm divides the list in half (divides by 2—that's why it's called a binary search) and decides which half to look in next. Division of the selected portion of the list is repeated until the item is found or it is determined that the item is not in the list.

This method is analogous to the way in which we look up a name in a phone book (or word in a dictionary). We open the phone book in the middle and compare the name with one on the page that we turned to. If the name we're looking for comes before this name, we continue our search in the left-hand section of the phone book. Otherwise, we continue in the right-hand section of the phone book. We repeat this process until we find the name. If it is not there, we realize that either we have misspelled the name or our phone book isn't complete. See Figure 12.8.

Click To expand
Figure 12.8: A binary search of the phone book

We start with the whole list (indexes 0 through numItems-1) and compare our search value to the middle list item. If the search item is less than the middle list item, we continue the search in the first half of the list. If the search item is greater than the middle list item, we continue the search in the second half of the list. Otherwise, we have found a match. We keep comparing and redefining the part of the list in which to look (the search area) until we find the item or the search area is empty. Let's write the algorithm bounding the search area by the indexes first and last. See Figure 12.9.

Click To expand
Figure 12.9: Binary search
Click To expand

This algorithm should make sense. With each comparison, at best, we find the item for which we are searching; at worst, we eliminate half of the remaining list from consideration. Before we code this algorithm, we need to determine when the search area is empty. If the search area is between listItems(first) and listItems(last), then this area is empty if last is less than first.

Let's do a walk-through of the binary search algorithm. The item being searched for is "bat". Figure 12.10a shows the values of first, last, and middle during the first iteration. In this iteration, "bat" is compared with "dog", the value in listItems(middle).

Click To expand
Figure 12.10: Walk-through of binary search algorithm

Because "bat" is less than (comes before) "dog", last becomes middle-1 and first stays the same. Figure 12.10b shows the situation during the second iteration. This time, "bat" is compared with "chicken", the value in listItems(middle). Because "bat" is less than (comes before) "chicken", last becomes middle-1 and first again stays the same.

In the third iteration (Figure 12.10c), middle and first are both 0. The item "bat" is compared with "ant", the item in listItems(middle). Because "bat" is greater than (comes after) "ant", first becomes middle+1. In the fourth iteration (Figure 12.10d), first, last, and middle are all the same. Again, "bat" is compared with the item in listItems(middle). Because "bat" is less than "cat", last becomes middle-1. Now that last is less than first, the process stops; found is False.

The binary search is the most complex algorithm that we have examined so far. The following table shows first, last, middle, and listItems(middle) for searches of the items "fish", "snake", and "zebra", using the same data as in the previous example. Examine the results in this table carefully.

Iteration |  | first | last | middle | listItems[middle] | Terminating Condition
item: fish |  |  |  |  |  |
 | First | 0 | 10 | 5 | dog |
 | Second | 6 | 10 | 8 | horse |
 | Third | 6 | 7 | 6 | fish | found is True
item: snake |  |  |  |  |  |
 | First | 0 | 10 | 5 | dog |
 | Second | 6 | 10 | 8 | horse |
 | Third | 9 | 10 | 9 | rat |
 | Fourth | 10 | 10 | 10 | snake | found is True
item: zebra |  |  |  |  |  |
 | First | 0 | 10 | 5 | dog |
 | Second | 6 | 10 | 8 | horse |
 | Third | 9 | 10 | 9 | rat |
 | Fourth | 10 | 10 | 10 | snake |
 | Fifth | 11 | 10 |  |  | last < first

Notice in the table that whether we search for "fish", "snake", or "zebra", the loop never executed more than four times. It never executes more than four times in a list of 11 components because the list is being cut in half each time through the loop.

The table below compares a sequential search and a binary search in terms of the average number of iterations needed to find an item.

 | Average Number of Iterations
Length | Sequential Search | Binary Search
10 | 5.5 | 2.9
100 | 50.5 | 5.8
1,000 | 500.5 | 9.0
10,000 | 5000.5 | 12.0

If the binary search is so much faster, why not use it all the time? It certainly is faster in terms of the number of times through the loop, but more computations are performed within the binary search loop than in the other search algorithms. So if the number of components in the list is small (say, less than 20), the sequential search algorithms are faster because they perform less work at each iteration. As the number of components in the list increases, the binary search algorithm becomes relatively more efficient.

Here is the code for isThere that uses the binary search algorithm.

Public Sub isThere(item As String)
' Returns True if item is in the list. False otherwise
' Binary search algorithm is used
' Assumption: List items are in ascending order
   Dim first As Integer = 0
   Dim last As Integer = numItems - 1
   Dim middle As Integer
   Dim found As Boolean = False
   While (last >= first And (Not Found))
      middle = (first + last) / 2
      If (item.compareTo(listItems(middle)) = 0) then
         found = True
      ElseIf (item.compareTo(listItems(middle)) < 0) then
         ' Item not in listItems(middle) … listItems(last)
         last = middle - 1
      Else
         ' Item not in listItems(first) … listItems(middle)
         first = middle + 1
   End While
   Return found
End Sub


12.7 Generic Lists

Generic lists are lists where the operations are defined, but the objects in the list are not. Although we called the components of our lists "items," they are of type String. Is it possible to construct a truly general-purpose list where the items can be anything? For example, could we have a list of Name objects as defined in Chapter 7 or Address objects as defined in Chapter 8? Yes, we can. All we have to do is declare the objects on the list to be Object. What is Object? It is the base data type for all the other data types we have used in this book and can be used to define a generic object. An object declared as type Object can be any legal Visual Basic.NET data type. Now, let's see how we can use it to make our lists generic.

The Object Type

All data types in Visual Basic.NET inherit from the Object type. Normally, when we declare an object, we pick one of the inherited data types for the object. We can, however, declare an object to be of type Object. An object of type Object can then be assigned any data type. Look at the following example.

Dim item As Object
item = 12000
item = "Mike"

The two assignment statements above are perfectly legal when the variable is of type Object. You should use the Object data type only when you don't know in advance what data type a variable will receive.

To make our List class as generic as possible, we replace String with Object throughout the class. This means that any object can be passed as a parameter to Insert, Delete, or isThere. This change also means that the array must hold Object objects, and getNextItem must return an Object object. Here is the complete abstract class List.

MustInherit Class List
   Protected listItems() As Object
   Protected numItems As Integer
   Protected currentPos As Integer

   Public Sub New(ByVal maxItems As Integer)
      ' Instantiates an empty List object
      ' with room for maxItems items
      numItems = 0
      Dim listItems(maxItems) As Object
      currentPos = 0
   End Sub

   Public Sub New()
      numItems = 0
      Dim listItems(100) As Object
      currentPos = 0
   End Sub

   Public Function isFull() As Boolean
      ' Returns True if there is not room for
      ' another component; False otherwise

      Return (listItems.Length = numItems)
   End Function

   Public Function isEmpty() As Boolean
      ' Returns True if there are no components in
      ' the list; False otherwise

      Return (numItems = 0)
   End Function

   Public Function Length() As Integer
      ' Returns the number of components in the list

      Return numItems
   End Function

   MustOverride Function isThere(ByVal item As Object) As Boolean
   ' Returns True if item is in the list; False otherwise

   ' Transformers
   MustOverride Sub Insert(ByVal item As Object)
   ' If list is not full, insert item into list;
   ' otherwise, list is unchanged.
   ' Assumption: item is not already in the list

   MustOverride Sub Delete(ByVal item As Object)
   ' Removes item from the list if it is there

   ' Iterator Pair
   Public Sub resetList() ' Prepare for iteration
      currentPos = 0
   End Sub

   Public Function getNextItem() As Object
      ' Returns the item at the currentPos position;
      ' resets current position to first item after
      ' the last item is returned
      ' Assumption: No transformers have been invoked since
      ' last call
      Dim nextItem As Object = listItems(currentPos)
      If (currentPos = numItems - 1) Then
         currentPos = 0
      Else
         currentPos += 1
      End If
      Return nextItem
   End Function

End Class

Notice that we have made the isThere method abstract. This way the derived class can determine which searching algorithm to use.

Polymorphism

We have discussed polymorphism several times, as it is one of the major features of object-oriented programming. In a hierarchy of classes, polymorphism enables us to override a method name with a different implementation in a derived class. Thus, there can be multiple forms of a given method within the hierarchy (literally, polymorphism means having multiple forms.)

The Visual Basic.NET compiler decides which form of a polymorphic instance method to use by looking at the class of its associated instance. For example, if compareTo is associated with a String variable, then the version of compareTo defined in class String is called.

Thus far, this is all straightforward. But consider the case in which compareTo is applied to an object that has been passed as a parameter declared to be Object. The abstract Insert method that we defined in the last section is precisely the example we have in mind.

MustOverride Sub Insert(byval Item As Object)

An instance of any class can be passed as an argument to this parameter. The class of the argument object determines which form of compareTo is actually called within Insert. At compile time, however, there is no way for the Visual Basic.NET compiler to determine the class of the argument object. Instead, it must insert Bytecode that identifies the argument's class at run time and then calls the appropriate method. Programming language designers call this dynamic binding. When the compiler can identify the appropriate form of the method to use, it is called a static binding.

Dynamic binding
Determining at run time which form of a polymorphic method to call
Static binding
Determining at compile time which form of a polymorphic method to call
Start Sidebar
Problem-Solving Case Study
Exam Attendance

Problem You are the grader for a U.S. government class of 200 students. The instructor has asked you to prepare two lists: an alphabetical list of the students taking an exam and an alphabetical list of the students who have missed it. The catch is that she wants the lists before the exam is over. You decide to write an application for your notebook computer that takes each student's name as the student enters the exam room and prints the lists of absentees and attendees for your instructor.

Brainstorming The words student, name, and list are sprinkled throughout the problem statement. The fundamental objects, then, are lists of students, each represented by his or her name. We need a form object to hold the student names as they enter the exam. We also need an object that represents the person checking in the students. This object is the driver class for the application.

Click To expand

Filtering None of these classes overlap, so there is no consolidation that we can do. We may be missing a class, but we can't tell yet.

Scenarios Let's look at a by-hand algorithm to get a better picture of the processing. You could stand by the door, ask the students their names as they enter, and check off the names from a master list. After all of the students have entered, you make a list of those that were not checked off and a list of those that were. Of course! You must have a master list of all the students in the class.

How do you simulate "Ask the students their names"? You type each name on the keyboard of your laptop. How do you simulate "Check off"? Your program reads the name and deletes it from the master list. If you then insert the name into a list of those present, you have both lists ready for printing as soon as the last student signs in.

You check with your instructor, and she says that the master class list is available in file "students.dat". You must add an input file object for reading the master list of students and a list to store them in. What about an output file? Yes, you need that too. You can write the two lists to a file and take it down the hall to be printed while the students are taking the exam. What about the student names? You have class Name that you can use from Chapter 7.

Click To expand

CRC Cards This application makes use of two lists: the master list and the list of the attendees. We do not make a CRC card for this class because we can use one of the list classes developed in this chapter. Here is the CRC card for the application class. Let's call it class ExamAttendance.

Click To expand

Internal Data Representation Your application must prepare the initial list of students from the class-roster file, which is ordered by social security number. If you use an unsorted list for the list of all students, the list must be sorted before printing (class ListWithSort). You decide to use class SortedList instead. So the internal representation is two sorted lists of Name objects. The interface for class Name is repeated here.

Interface Name
   ' This interface defines a name consisting of three parts
   ' Constructors are not implemented in an interface but are
   ' included in comments here
   ' Public Sub New(firstName As String, middleName As String,
   '                lastName As String)
   ' Initializes a Name object with first, middle, and last
   ' name

   Function knowFirstName() As String
   Function knowMiddleName() As String
   Function knowLastName() As String

   ' Additional observers
   Function firstLastMid() As String
   Function lastFirstMid() As String
   Function Equal(ByVal otherName As Name) As Boolean
   Function compareTo(ByVal otherName As Name) As Integer

End Interface

Responsibility Algorithms Preparing the window object involves creating a form for the students to enter their names. The names are to be stored in a Name object, which require a first name, a middle name, and a last name. This means that the form must have three labels and three textboxes. There must be a button to signal the name is ready for input and a button to signal that the last student has entered the exam.

...........................................................................
Prepare Window Objects

 Add three labels to form
 Add three textboxes to form
 Add "Enter" button to form
 Add "Quit" button to form

Creating the master list of students requires preparing the file for input, reading the names, storing them into a Name object, and inserting the object into the list. Reading the names requires that you know how the data is written in file "students.dat". The file format is first name, middle name or initial, last name, and social security number. There is exactly one space between each part of the name. If there is an initial rather than a middle name, it is not followed by a period. We should extend StreamReader to input this information and return a Name object. We design this class later.

...........................................................................
Create MasterList

 Prepare file
 While more data
  Set name to datafile.getName()
  Insert name into masterList

The next three responsibilities, getting a student name, deleting the name from the master list, and inserting the name in the present list, take place in the event handler for the "Enter" button.

............................................................................
Get a Student Name

 Set firstName to firstField.getText()
 Set middleName to middleField.getText()
 Set lastName to lastField.getText()
 Set name to new Name(firstName, middleName, lastName)

............................................................................
Delete Name from Master List

 presentList.Insert(name)

The last two responsibilities, printing the list of those present and printing the list of those absent, take place within the event handler for the "Quit" button. Because the algorithm for printing a list is identical in both cases, you write it as a helper module that takes the file name and the list. The event handler can also close the window.

...........................................................................
Print List(outFile, list)

 List.resetList()
 For index going from 1 to List.Length()
   Set name to List.getNextItem()
   outFile.WriteLine(Name.firstLastMid())

Now you need to design the class that does the file input. It is derived from StreamReader. It must create itself, input a string, and break the string into the first name, middle name, and last name. It should also have a Boolean method that tells when the file is empty. Here is its CRC card for class ExamDataReader.

Click To expand

Because the fields of the name are delimited by blanks, you can use the ReadLine method in StreamReader to input the entire line. In order to separate out the first, middle, and last names of a student, you can use the methods provided in the String class. The indexOf method finds the spaces. The first name begins at the 0th position and ends at the position before the space. The original line is replaced by the line with the first name removed. This process is repeated to get the middle name and the last name. The social security number can just be ignored. This problem doesn't need it.

We must be sure to read the first line of data in the constructor and read at the end of getName. The More Data responsibility can then check to see if the last read was an empty string.

...........................................................................
Get Name

 Set index to dataLine.indexOf(" ")
 Set firstName to dataLine.Substring(0, index)
 Set dataLine to dataLine.Substring(index+1, dataLine.Length())
 Set index to dataLine.indexOf(" ")
 Set middleName to dataLine.Substring(0, index)
 Set dataLine to dataLine.Substring(index+1, dataLine.Length())
 Set index to dataLine.indexOf(" ")
 Set lastName to dataLine.Substring(0, index)
 Set name to new Name(firstName, middleName, lastName)
 Set dataLine to Me.ReadLine()

 Return name

Imports CName
Imports System.IO
Imports ListClass
Imports WindowsApplication6.ExamDataReader          ' Imports program class

Public Class ExamDataReader
   Dim dataLine As String
   Dim inFile As StreamReader
   Public Sub New(ByVal dataFile As String)
      Dim theFile As File
      inFile = theFile.OpenText(dataFile)
      dataLine = inFile.ReadLine()
   End Sub
   Public Function getName() As Name
      ' Data fields
      Dim firstName As String
      Dim middleName As String
      Dim lastName As String

      Dim name As Name
      Dim index As Integer
      ' Extract first name
      index = dataLine.IndexOf(" ")
      firstName = dataLine.Substring(0, index)
      dataLine = dataLine.Substring(index + 1, dataLine.Length())
      ' Extract middle name
      index = dataLine.IndexOf(" ")
      middleName = dataLine.Substring(0, index)
      dataLine = dataLine.Substring(index + 1, dataLine.Length())
      ' Extract last name
      index = dataLine.IndexOf(" ")
      lastName = dataLine.Substring(0, index)
      name = New Name(firstName, middleName, lastName)
      dataLine = Me.inFile.ReadLine()
      Return name
   End Function

   Public Function moreData() As Boolean
      Return (inFile.Peek <> -1)
   End Function

End Class

Public Class Form1
   Inherits System.Windows.Forms.Form
   Dim firstName As String          ' First name field
   Dim middleName As String         ' Middle name field
   Dim lastName As String           ' Last name field
   Dim aName As Name                ' A name
   Dim masterList As SortedList     ' List of students
   Dim presentList As SortedList    ' List of those present
   Dim theFile As File              ' File object
   Dim dataFile As StreamReader     ' Master file of students
   Dim outFile As StreamWriter      ' File for printing
   ' Dim dataFile As New ExamDataReader("students.dat")

#Region " Windows Form Designer generated code "

   Public Sub New()
      MyBase.New()

      ' This call is required by the Windows Form Designer.
      InitializeComponent()

      masterList = New SortedList(200)
      presentList = New SortedList(200)

' Get the master list of students
      While (dataFile.moreData())
         aName = dataFile.getName()
         masterList.insert(Name)
      End While
      Add any initialization after the InitializeComponent() call

   End Sub

   Form overrides dispose to clean up the component list.
   Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
         If Not (components Is Nothing) Then
            components.Dispose()
         End If
      End If
      MyBase.Dispose(disposing)
   End Sub
      ' Required by the Windows Form Designer
   Private components As System.ComponentModel.Container

   NOTE: The following procedure is required by the Windows Form Designer
   It can be modified using the Windows Form Designer.
   Do not modify it using the code editor.
   Private Sub InitializeComponent()

   Private Sub Button1_Click(ByVal sender As System.Object, _
               ByVal e As System.EventArgs) Handles Button1.Click
      firstName = firstText.Text
      middleName = middleText.Text
      lastName = lastText.Text
      aName = New Name(firstName, middleName, lastName)

   End Sub

   Private Function printList(outFile As StreamWriter, list As SortedList)
   ' Helper method to print a list
       Dim name As Name
       Dim limit As Integer = list.length()
       Dim index As Integer
       list.resetList()
       For index = 1 To limit
           Name = list.getNextItem()

           outFile.WriteLine(name.firstLastMid())
       Next index
   End Function
End Class

Here is a screen shot of the user interface.

Click To expand
End Sidebar


Summary

This chapter has provided practice in working with lists where the items are stored in a one-dimensional array. We have examined algorithms that insert, delete, and search data stored in an array-based unsorted list, and we have written methods to implement these algorithms. We have also examined an algorithm that takes the array in which the list items are stored and sorts them into ascending order.

We have examined several search algorithms: sequential search in an unsorted list, sequential search in a sorted list, and binary search. The sequential search in an unsorted list compares each item in the list to the one being searched for. All items must be examined before it can be determined that the search item is not in the list. The sequential search in a sorted list can determine that the search item is not in the list when the place where the item belongs has been passed. The binary search looks for the search item in the middle of the list. If it is not there, then the search continues in the half where the item should be. This process continues to cut the search area in half until either the item is found or the search area is empty.

We have examined the insertion algorithm that keeps the items in the list sorted by value. We generalized the list in an abstract class List, leaving the Insert, Delete, and isThere methods abstract. We demonstrated using the Object data type as a way to make the list items generic.


Quick Check

Q:

1. What is the difference between a list and an array? (p. 492)

1. A list is a variable-sized structured data type; an array is a built-in type often used to implement a list.

Q:

2. If the list is unsorted, does it matter where a new item is inserted? (p. 492)

2. No.

Q:

3. The following code fragment implements the "Delete, if it's there" meaning for the Delete operation in an unsorted list. Change it so that the other meaning is "Implement"; that is, there is an assumption that the item is in the list. (pp. 502–503)

While (index < numItems And Not(found))
   Dim count As Integer
   If (listItems(index) = item) Then
      found = True
   Else
      index += 1
   End If
End While
If (found) Then
   For count = index To numItems-2
      listItems(count) = listItems(count+1)
   Next count
   numItems -= 1
End If

3. index = 0 While (item -- listItems(index)index += 1 End While Dim count As Integer For count = index To numItems - 2listItems(count) = listItems(count+1) Next count numItems -= 1

Q:

4. In a sequential search of an unsorted array of 1,000 values, what is the average number of loop iterations required to find a value? What is the maximum number of iterations? (pp. 505–506)

4. The average number is 500 iterations. The maximum is 1,000 iterations.

Q:

5. The following program fragment sorts list items into ascending order. Change it to sort into descending order. (pp. 507–508)

For passCount = 0 To length - 1
   minIndex = passCount
   For searchIndex = passCount + 1 To numItems - 1
      If (listItems(searchIndex.compareTo(listItems(minIndex))_
                    < 0) Then
         minIndex = searchIndex
      End If
   Next searchIndex
   temp = listItems(minIndex)
   listItems(minIndex) = listItems(passCount)
   listItems(passCount) = temp
Next passCount

5. The only required change is to replace the less than with greater than in the inner loop. As a matter of style, the name minIndex should be changed to maxIndex .

Q:

6. Describe how the Insert operation can be used to build a sorted list from unsorted input data. (pp. 512–514)

6. The list initially has a length of 0. Each time a value is read, Insert adds the value to the list in its correct position. When all the data values have been read, they are in the array in sorted order.

Q:

7. Describe the basic principle behind the binary search algorithm. (pp. 519–523)

Answers

A:

1. A list is a variable-sized structured data type; an array is a built-in type often used to implement a list.

A:

2. No.

A:

3.

index = 0
While (item <> listItems(index)
   index += 1
End While
Dim count As Integer
For count = index To numItems - 2
   listItems(count) = listItems(count+1)
Next count
numItems -= 1

A:

4. The average number is 500 iterations. The maximum is 1,000 iterations.

A:

5. The only required change is to replace the less than with greater than in the inner loop. As a matter of style, the name minIndex should be changed to maxIndex.

A:

6. The list initially has a length of 0. Each time a value is read, Insert adds the value to the list in its correct position. When all the data values have been read, they are in the array in sorted order.

A:

7. The binary search takes advantage of sorted list values, looking at a component in the middle of the list and deciding whether the search value precedes or follows the midpoint. The search is then repeated on the appropriate half, quarter, eighth, and so on, of the list until the value is located.


Exam Preparation Exercises

    Chapter 12

  1. The following values are stored in an array in ascending order.

    28 45 97 103 107 162 196 202 257

    Applying the unsorted linear search algorithm to this array, search for the following values and indicate how many comparisons are required to either find the number or find that it is not in the list.

    1. 28

    2. 32

    3. 196

    4. 194

  2. Repeat Exercise 1, applying the algorithm for a sequential search in a sorted list.

  3. The following values are stored in an array in ascending order.

    29 57 63 72 79 83 96 104 114 136

    Apply the binary search algorithm looking for 114 in this list and trace the values of first, last, and middle. Indicate any undefined values with a U.

  4. (True or False?) A binary search is always better to use than a sequential search.

  5. If resetList initializes currentPos to -1 rather than 0, what corresponding change would have to be made in getNextItem?

  6. We have said that arrays are homogeneous structures, yet Visual Basic.NET implements them with an associated integer. Explain.

  7. Why does the outer loop of the sorting method run from 0 through numItems-2 rather than numItems-1?

  8. A method that returns the number of days in a month is an example of

    1. a constructor

    2. an observer

    3. an iterator

    4. a transformer

  9. A method that adds a constant to the salary of everyone in a list is an example of

    1. a constructor

    2. an observer

    3. an iterator

    4. a transformer

  10. A method that stores values into a list is an example of

    1. a constructor

    2. an observer

    3. an iterator

    4. a transformer

  11. What kind of class cannot be instantiated?

  12. Class List assumes that there are no duplicate items in the list.

    1. Which method algorithms would have to be changed to allow duplicates?

    2. Would there still be options for the Delete operation? Explain.


Programming Warm-Up Exercises

    Chapter 12

  1. Complete the implementation of UnsortedList as a derived class from abstract class List.

  2. Complete the implementation of ListWithSort as a class derived from UnsortedList.

  3. Derive a subclass of UnsortedList that has the following additional methods:

    1. A value-returning instance method named Occurrences that receives a single parameter, item, and returns the number of times item occurs in the list

    2. A Boolean instance method named greaterFound that receives a single parameter, item, and searches the list for a value greater than item. If such a value is found, the method returns True; otherwise, it returns False.

    3. An instance method named Component that returns a component of the list given a position number (pos). The position number must be within the range 0 through numItems-1.

    4. A copy constructor for the List class that takes a parameter that specifies how much to expand the array holding the items. Implement the copy constructor by creating a larger array and copying all of the items in the list into the new array.

  4. Complete the implementation of SortedList as a derived class from abstract class List.

  5. Derive a subclass of SortedList that has the additional methods outlined in Exercise 3.

  6. Write a Visual Basic.NET method named Exclusive that has three parameters: item, list1, and list2 (both of class List as defined in this chapter). The method returns True if item is present in either list1 or list2 but not both.

  7. The Insert method in class SortedList returns items into the list in ascending order. Derive a new class from List that sorts the items in descending order.

  8. Exam Preparation Exercise 12 asked you to examine the implication of a list with duplicates.

    1. Design an abstract class ListWithDuplicates that allows duplicate keys.

    2. How does your design differ from List?

    3. Implement your design where the items are unsorted and Delete deletes all of the duplicate items.

    4. Implement your design where the items are sorted and Delete deletes all of the duplicate items.

    5. Did you use a binary search in part (d)? If not, why not?

  9. Rewrite method Insert in class SortedList so that it implements the first insertion algorithm discussed for sorted lists. That is, the place where the item should be inserted is found by searching from the beginning of the list. When the place is found, all the items from the insertion point to the end of the list are shifted down one position.


Programming Problems

  1. A company wants to know the percentages of total sales and total expenses attributable to each salesperson. Each person has a pair of data lines in the input file. The first line contains his or her name, last name first. The second line contains his or her sales (Integer) and expenses (Single). Write an application that produces a report with a header line containing the total sales and total expenses. Following this header should be a table with each salesperson's name, percentage of total sales, and percentage of total expenses, sorted by salesperson's name. Use one of the list classes developed in this chapter.

  2. Only authorized shareholders are allowed to attend a stockholder's meeting. Write an application to read a person's name from the keyboard, check it against a list of shareholders, and print a message on the screen saying whether or not the person may attend the meeting. The list of shareholders is in a file owners.dat in the following format: first name, blank, last name. Use the end-of-file condition to stop reading the file. The maximum number of shareholders is 1,000.

  3. Enhance the program in Problem 2 as follows:

    1. Print a report file showing the number of stockholders at the time of the meeting, how many were present at the meeting, and how many people who tried to enter were denied permission to attend.

    2. Follow this summary report with a list of the names of the stockholders, with either Present or Absent after each name.

  4. An advertising company wants to send a letter to all its clients announcing a new fee schedule. The clients' names are on several different lists in the company. The various lists are merged to form one file, "clients," but obviously, the company does not want to send a letter twice to anyone.

    Write an application that removes any names appearing on the list more than once. On each data line there is a four-digit code number, followed by a blank and then the client's name. For example, Amalgamated Steel is listed as

    0231 Amalgamated Steel

    Your program is to output each client's code and name, but no duplicates should be printed. Use one of the list classes developed in this chapter.


Case Study Follow-Up Exercises

    Chapter 12

  1. Write a test plan for application ExamAttendance.

  2. If the event handler does not delete the name from the class list when a student arrives, what other algorithm could be used to determine the names of those students that did not attend the exam?

  3. Redesign the solution to this problem to use ListWithSort.

  4. Implement the design in Question 3.


Chapter 13: Multidimensional Arrays and Numeric Computation

Overview

Goals

  • To be able to declare a two-dimensional array.

  • To be able to perform fundamental operations on a two-dimensional array:

    • Access a component of the array.

    • Initialize the array.

    • Print the values in the array.

    • Process the array by rows.

    • Process the array by columns.

  • To be able to declare a two-dimensional array as a parameter.

  • To be able to declare and process a multidimensional array.

  • To be able to describe how floating-point numbers are represented in the computer.

  • To be able to avoid errors caused by the limited numeric precision of the computer.

The structures that we choose to hold a collection of data objects play an important role in the design process. In the last two chapters, we have discussed the one-dimensional array and its use in problems in which the logical representation of data is a list of objects. The choice of data structure directly affects the design because it determines the algorithms used to process the data objects. For example, if data items are stored in sorted order within the array, we can apply a binary search algorithm rather than a linear search.

In many problems, however, the relationships between data items are more complex than can be represented in a simple list. Examples of complex relationships can be found in simulations of board games such as chess, Tic-Tac-Toe, or Scrabble; in computer graphics, where points on the screen are arranged as a two-dimensional object; or in matrix operations in mathematics. In this chapter we examine the two-dimensional array, which is useful when we need to organize data in two dimensions. We usually call these dimensions rows and columns.

Then we extend the definition of an array to allow arrays with any number of dimensions, called multidimensional arrays. Each dimension represents a different feature of the data objects in the structure. For example, we might use a three-dimensional array to store objects representing sales figures by (1) store number, (2) month, and (3) item number.

Finally, we take a closer look at the limitations of the computer in doing calculations, how these limitations can cause numerical errors, and how to avoid such errors. This discussion relates directly to our case study, where we examine arrays as implementation structures for the mathematical matrix object.


13.1 Two-Dimensional Arrays

A one-dimensional array is used to represent items in a list or a sequence of values. A two-dimensional array is used to represent items in a table with rows and columns. A component in a two-dimensional array is accessed by specifying the row and column indexes of the item in the array. This is a familiar task. For example, if you want to find a street on a map, you look up the street name on the back of the map to find the coordinates of the street, usually a number and a letter. The number specifies a row, and the letter specifies a column. You find the street where the row and column meet.

Two-dimensional array
A collection of components, all of the same type or class, structured in two dimensions. Each component is accessed by a pair of indexes that represent the component's position in each dimension.

Figure 13.1 shows a two-dimensional array with 100 rows and 9 columns. The rows are accessed by an integer ranging from 0 through 99; the columns are accessed by an integer ranging from 0 through 8. Each component is accessed by a row-column pair—for example, (0,5).

Click To expand
Figure 13.1: alpha array

Array Declaration and Instantiation

A two-dimensional array variable is declared in exactly the same way as a one-dimensional array variable, except that there are two values within the parentheses. A two-dimensional array object is instantiated in exactly the same way, except that sizes must be specified for two dimensions. Below is the syntax template for declaring an array with two dimensions, along with an example.

Click To expand

The following code fragment would create the array shown in Figure 13.1, where the data in the table are floating-point numbers.

Dim alpha(99,8) As Double
Dim beta(9,9) As String

The first dimension specifies the last element number for rows, and the second dimension specifies the last element number for columns. Just remember that these numbers are always 1 less than the actual size of the row or column. Once the two-dimensional arrays have been created, alpha.Length() and beta.Length() give the number of elements in each array.

Accessing Individual Components

To access an individual component of the alpha array, one expression is used to specify its position (in the same way we specify the position in a single dimension array). However, the expression consists of two numbers separated by a comma. The first number specifies the row and the second number specifies the column.

Click To expand

The syntax template for accessing an array component is

Click To expand

As with one-dimensional arrays, each index expression must result in an integer value between 0 and the number of slots in that dimension minus 1.

Let's look now at some examples. Here is the declaration of a two-dimensional array with 364 integer components (52 × 7 = 364):

Dim hiTemp(51,6) As Integer

hiTemp is an array with 52 rows (indexed from 0 to 51) and 7 (indexed from 0 to 6) columns. Each place in the array (each component) can contain any Integer value. Our intention is that the array contains high temperatures for each day in a year. Each row represents one of the 52 weeks in a year, and each column represents one of the 7 days in a week. (To keep the example simple, we ignore the fact that there are 365—and sometimes 366—days in a year.) The expression hiTemp(2,6) refers to the Integer value in the third row and the seventh column. Semantically, hiTemp(2,6) is the temperature for the seventh day of the third week. The code fragment shown in Figure 13.2 would print the temperature values for the third week.

Click To expand
Figure 13.2: hiTemp array

To obtain the number of columns in a row of an array, we access the length field for the specific row with the GetLength() method, which takes the specified dimension number as an argument. For example, the statement

midYear = hiTemp.GetLength(1)

stores the length of the columns, which is 7, into the Integer variable midYear.

Using Initializer Lists

Just as a one-dimensional array can be created with a list of values, a two-dimensional array can be created with a list of a list of values. For example, the following statement instantiates a two-dimensional array of hits. This array represents the hits for a five-day period for your four favorite baseball players.

Dim hits() As Integer = {{2, 1, 0, 3, 2}, {1, 1, 2, 3, 4}, {1, 0, 0, 0, 0} _
                         , {0, 1, 2, 1, 1}}

When you initialize a two-dimensional array in this way, each dimension must have the same number of elements. For example, the following initializer will not compile:

Dim hits() As Integer = {{2, 1, 0, 3, 2}, {1, 1, 2, 3, 4}, {1, 0} _
                          , {0, 1, 2, 1, 1}} ' Error

The third row only has two elements in it, instead of five like the other rows. Each row in the array must have the same number of elements. What if you want to represent the fact that the third baseball player only had two at-bats? Perhaps you could use a special number to represent a "no bat", such as - 1. Of course, your code will then have to test for this value when processing the array, but this data representation allows your array dimensions to be complete, which is necessary for your program to compile and run.


13.2 Processing Two-Dimensional Arrays

Processing data in a two-dimensional array generally means accessing the array in one of four patterns—randomly, along rows, along columns, or throughout the entire array. Each of these may also involve subarray processing.

The simplest way to access a component is to look directly in a given location. For example, a user enters map coordinates that we use as indexes into an array of street names to access the desired name at those coordinates. This process is referred to as random access because the user may enter any set of coordinates at random.

There are many cases in which we might wish to perform an operation on all the elements of a particular row or column in an array. Consider the hiTemp array defined previously, in which the rows represent weeks of the year and the columns represent days of the week. If we wanted the average high temperature for a given week, we would sum the values in that row and divide by 7. If we wanted the average for a given day of the week, we would sum the values in that column and divide by 52. The former case shows access by row; the latter case shows access by column.

Now suppose that we wish to determine the average for the year. We must access every element in the array, sum them, and divide by 364. In this case, the order of access—by row or by column—is not important. (The same is true if we initialize every element of an array to a constant.) This is access throughout the array.

There are times when we must access every array element in a particular order, either by rows or by columns. For example, if we wanted the average for every week, we would run through the entire array, taking each row in turn. However, if we wanted the average for each day of the week, we would run through the array a column at a time.

Let's take a closer look at these patterns of access by considering three common examples of array processing.

  1. Sum the rows.

  2. Sum the columns.

  3. Initialize the array to some special value.

In the following discussion, we use the generic identifiers row and col, rather than problem-dependent identifiers, and we look at each algorithm in terms of generalized two-dimensional array processing. The array that we use is declared and instantiated by the following statement:

Dim data(49,49) as Integer     ' A two-dimensional array

In what follows, we assume that data contains valid information.

Sum the Rows

Suppose we want to sum row number 2 (the third row) in the array and print the result. We can do this easily with a For loop:

Dim total As Integer = 0
Dim col As Integer = 0
For col = 0 To data. GetLength(0)
   total += data(2, col)
Next col
Console.WriteLine("Row sum: " & total)

This For loop runs through each column of data while keeping the row index fixed at 2, the third row. Every value in row 2 is added to total.

Now suppose we want to sum and print two rows—row 2 and row 3. We can use a nested loop and make the row index a variable:

Dim row As Integer
Dim col As Integer
For row = 1 To 2
   total = 0
   For col = 0 to data.GetLength(1)
      Total = total + data(row, col)
   Next col
   Console.WriteLine("Row sum: " & total
Next row

The outer loop controls the rows, and the inner loop controls the columns. For each value of row, every column is processed; then the outer loop moves to the next row. In the first iteration of the outer loop, row is held at 1 and col goes from 0 through data.GetLength(1). Therefore, the array is accessed in the following order:

data(1,0)   (1,1)   (1,2)  (1,3)   …   (1,29)

In the second iteration of the outer loop, row is incremented to 2, and the array is accessed as follows:

data(2,0)   (2,1)    (2,2)  (2,3)   …   (2,29)

We can generalize this row processing to run through every row of the array by having the outer loop run from 0 to data.GetLength(1). However, if we want to access only part of the array (subarray processing), given variables declared as

Dim rowsFilled As Integer       ' Data is in 0…rowsFilled-1
Dim colsFilled As Integer       ' Data is in 0…colsFilled-1

then we write the code fragment as follows:

Dim row As Integer
Dim col As Integer

For row = 0 To rowsFilled-1
   ' Array does not have empty values
   For col = 0 To colsFilled-1
      total = total + data(row, col)
   Next col
   Console.WriteLine("Row sum: " & total)
Next row

This is an example of subarray processing by row. Figure 13.3 illustrates subarray processing by row.

Click To expand
Figure 13.3: Partial array processing by row

Sum the Columns

Suppose we want to sum and print each column. The code to perform this task follows. Again, we have generalized the code to sum only the portion of the array that contains valid data.

Dim row, col As Integer
For col = 0 To colsFilled-1
   ' Array does not have empty values
   For row = 0 To rowsFilled-1
      total = total + data(row, col)

   Next row
   Console.WriteLine("Column sum: " & total)
Next col

In this case, the outer loop controls the column, and the inner loop controls the row. All the components in the first column are accessed and summed before the outer loop index changes and the components in the second column are accessed. Figure 13.4 illustrates subarray processing by column.

Click To expand
Figure 13.4: Partial array processing by column

Initialize the Array

Instantiating an array with initializer lists is impractical if the array is large. For a 100-row by 100-column array, you don't want to list 10,000 values. If the values are all different, you should store them in a file and input them into the array at run time. If the values are all the same, the usual approach is to use nested For loops and an assignment statement. Here is a general-purpose code segment that sets every item in the array to -1:

Dim row, col As Integer
For row = 0 to data.GetLength(0)-1
   For col = 0 to data.GetLength(1)-1
      data(row, col) = -1
   Next col
Next row

In this case, we initialized the array a row at a time, but we could just as easily have run through each column instead. The order doesn't matter as long as we access every element.

Almost all processing of data stored in a two-dimensional array involves either processing by row or processing by column. The looping patterns for row processing and column processing are so useful that we summarize them on the following page. To make them more general, we use minRow for the first row number and minCol for the first column number. Remember that row processing has the row index in the outer loop, and column processing has the column index in the outer loop.

Row Processing

Dim row, col, minRow, minCol As Integer
For row = minRow To rowsFilled
   For col = minCol To colsFilled
      ' Whatever processing is required
   Next col
Next row

Note: rowsFilled is equal to the GetLength method applied to the row dimension minus 1, and colsFilled is equal to the GetLength method applied to the column dimension minus 1.

Column Processing

Dim row, col, minRow, minCol As Integer
For col = minCol To colsFilled
   For row = minRow To rowsFilled
      ' Whatever processing is required
   Next row
Next col

Note: rowsFilled is equal to the GetLength method applied to the row dimension minus 1 and colsFilled is equal to the GetLength method applied to the column dimension minus 1.

Two-Dimensional Arrays and Methods

Two-dimensional arrays can be parameters to methods, and they can be the return value type for a method. The syntax and semantics are identical to those for one-dimensional arrays. Let's enclose the array initialization code fragment within a method.

Public Sub initialize(data(,) as integer)
   dim row, col As Integer
   For row = 0 to data.GetLength(0)-1
      For col = 0 to data.GetLength(1)-1
         a(row, col) = 0
      Next col
   Next row
End Sub

Because Visual Basic has a field associated with each array that contains the number of slots defined for the array, we do not have to pass this information as a parameter as we do in many other languages. This is a consequence of the object orientation of the language. The array is an object and the information about the object is encapsulated with it.


13.3 Multidimensional Arrays

Visual Basic places a limit of 32 on the number of dimensions that an array can have. Most problems will not require an array of up to 32 dimensions. How many should you have in a particular case? Use as many as there are features that describe the components in the array.

Array
A collection of components, all of the same type or class, ordered on N dimensions (N >= 1). Each component is accessed by N indexes, each of which represents the component's position within that dimension.

Take, for example, a chain of department stores. Monthly sales figures must be kept for each item by store. There are three important pieces of information about each item: the month in which it was sold, the store from which it was purchased, and the item number. We can define an array type to summarize this data as follows:

Dim sales(100,12,10) As Integer    ' Declare array of sales figures
' First dimension represents number of stores:
' second dimension represents months;
' third dimension represents items

A graphic representation of the sales array is shown in Figure 13.5.

Click To expand
Figure 13.5: sales array

The number of components in sales is 12,000 (10 × 12 × 100). If sales figures are available only for January through June, then half the array is empty. If we want to process the data in the array, we must use subarray processing. The following program fragment sums and prints the total number of each item sold this year to date by all stores.

Dim currentMonth As Integer = 6   ' Range: 1…12
Dim item, numberSold, store, month As Integer
For item = 0 To sales.GetLength(2)-1
   numberSold = 0
   For store = 0 To sales.GetLength(0)-1
      For month = 0 To currentMonth-1
         numberSold = numberSold + sales(store, month, item)
      Next month
   Next store
   Console.WriteLine("Item #: " & item & " Sales to date = " & numberSold)
Next item

Because item controls the outer For loop, we are summing each item's sales by month and store. If we want to find the total sales for each store, we use store to control the outer For loop, summing its sales by month and item with the inner loops.

Dim numberSold As Integer
Dim item, month, store, currentMonth As Integer
For store = 0 To sales.GetLength(0)-1
   numberSold = 0
   For item = 0 To sales.GetLength(2)-1
      For month = 0 To sales.GetLength(1)-1
         numberSold = numberSold + sales(store, month, item)
      Next month
   Next item
Console.WriteLine("Store #: " & store & " Sales to date = " & _
                  numberSold)
Next store

It takes two loops to access each component in a two-dimensional array; it takes three loops to access each component in a three-dimensional array. The task to be accomplished determines which index controls the outer loop, the middle loop, and the inner loop. If we want to calculate monthly sales by store, then month controls the outer loop and store controls the middle loop. If we want to calculate monthly sales by item, then month controls the outer loop and item controls the middle loop.

Multidimensional arrays can be passed as parameters to methods. Make sure that you have the right number of commas in your method call. (The number of commas is equal to the number of dimensions in the array minus 1.)

ArrayList Class

We cannot leave the discussion of arrays without mentioning a class that is available in the Framework library: the ArrayList class. The functionality of the ArrayList class is similar to that of the array. In fact, the array uses the same underlying implementation structure (the IList interface) used in the ArrayList class. In contrast to an array, however, an ArrayList can grow and shrink using special methods (ReDim). The class provides methods to manipulate items at specified index positions. In many ways, the ArrayList is like the general-purpose list classes that we designed in the last chapter.


13.4 Floating-Point Numbers

We have used floating-point numbers off and on since we introduced them in Chapter 2, but we have not examined them in depth. Floating-point numbers have special properties when used on the computer. Thus far, we've almost ignored these properties, but now it's time to consider them in detail.

Representation of Floating-Point Numbers

As we know, Visual Basic represents numbers in the binary number system and its different numeric types use different numbers of bits. But let's assume, to simplify the following discussion, that we have a computer in which each memory location is the same size and is divided into a sign plus five decimal digits. When a variable or constant is defined, the location assigned to it consists of five digits and a sign. When an integral variable or constant is defined, the interpretation of the number stored in that place is straightforward. When a floating-point variable or constant is defined, the number stored there has both a whole number part and a fractional part, so it must be coded to represent both parts.

Let's see what such coded numbers might look like. The range of whole numbers we can represent with five digits is -99,999 through +99,999:

Click To expand

Our precision (the number of digits we can represent) is five digits, and each number within that range can be represented exactly.

Precision
The maximum number of significant digits

What happens if we allow one of those digits (the leftmost one, for example) to represent an exponent?

Click To expand

Then +82345 represents the number +2345 × 108. The range of numbers we now can represent is much greater:

or

Significant digits
Those digits from the first nonzero digit on the left to the last nonzero digit on the right (plus any 0 digits that are exact)

However, our precision is now only four digits; that is, only four-digit numbers can be represented exactly in our system. What happens to numbers with more digits? The four leftmost digits are represented correctly, and the rightmost digits, or least significant digits, are lost (assumed to be 0). Figure 13.6 shows what happens. Note that 1,000,000 can be represented exactly but -4,932,416 cannot, because our coding scheme limits us to four significant digits.

Click To expand
Figure 13.6: Coding using positive exponents

To extend our coding scheme to represent floating-point numbers, we must be able to represent negative exponents. Examples are

and

Because our scheme does not include a sign for the exponent, let's change it slightly. The existing sign becomes the sign of the exponent, and we add a sign to the far left to represent the sign of the number itself (see Figure 13.7).

Click To expand
Figure 13.7: Coding using positive and negative exponents

All the numbers between -9999 × 109 and 9999 × 109 can now be represented accurately to four digits. Adding negative exponents to our scheme allows us to represent fractional numbers as small as 1 × 10-9.

Figure 13.8 shows how we would encode some floating-point numbers. Note that our precision is still only four digits. The numbers 0.1032, -5.406, and 1,000,000 can be represented exactly. The number 476.0321, however, with seven significant digits, is represented as 476.0; the 321 cannot be represented. (We should point out that some computer systems, including the CLR, perform rounding rather than simple truncation when discarding excess digits. Using our assumption of four significant digits, rounding would store 476.0321 as 476.0 but would store 476.0823 as 476.1. We continue our discussion assuming simple truncation rather than rounding.)

Click To expand
Figure 13.8: Coding of some floating-point numbers

Arithmetic with Floating-Point Numbers

When we use integer arithmetic, our results are exact. Floating-point arithmetic, however, is seldom exact. To understand why, let's add three floating-point numbers x, y, and z using our coding scheme.

First, we add x to y and then we add z to the result. Next, we perform the operations in a different order, adding y to z and then adding x to that result. The associative law of arithmetic says that the two answers should be the same—but are they? Let's use the following values for x, y, and z:

Here is the result of adding z to the sum of x and y:

Now here is the result of adding x to the sum of y and z:

These two answers are the same in the thousands place but are different thereafter. The error behind this discrepancy is called representational error.

Representational error
Arithmetic error that occurs when the precision of the true result of an arithmetic operation is greater than the precision of the machine

Because of representational errors, it is unwise to use a floating-point variable as a loop-control variable. Because precision may be lost in calculations involving floating-point numbers, it is difficult to predict when (or even if) a loop-control variable of type Single (or Double) equals the termination value. A count-controlled loop with a floating-point control variable can behave unpredictably.

Also because of representational errors, you should never compare floating-point numbers for exact equality. Rarely are two floating-point numbers exactly equal, and thus you should compare them only for near equality. If the difference between the two numbers is less than some acceptable small value, you can consider them equal for the purposes of the given problem.

Implementation of Floating-Point Numbers in the Computer

All computers limit the precision of floating-point numbers, although modern machines use binary rather than decimal arithmetic. In our representation, we use only five digits to simplify the examples, and some computers really are limited to only four or five digits of precision. Some systems provide six significant digits, 15 significant digits, and 19 significant digits, respectively, for three sizes of floating-point types. We have shown only a single-digit exponent, but most systems allow two digits for the smaller floating-point type and up to four-digit exponents for a longer type.

Some languages leave the range and precision of floating-point types to each individual compiler, but Visual Basic states the range and precision in the language specification in the following formula:

where s is +1 or -1, m is a positive integer less than 224, and e is between -149 and 104, inclusive, for values of type Single. For values of type Double, m is less than 253 and e is between -1075 and 970. No, we don't expect you to calculate this. Each Visual Basic numeric type provides constants MaxValue and MinValue. One of the exercises asks you to print out these values for each type.

When you declare a floating-point variable, part of the memory location contains the exponent, and the number itself (called the mantissa) is assumed to be in the balance of the location. The system is called floating-point representation because the number of significant digits is fixed, and the decimal point conceptually is allowed to float (move to different positions as necessary). In our coding scheme, every number is stored as four digits, with the leftmost digit being nonzero and the exponent adjusted accordingly. Numbers in this form are said to be normalized. The number 1,000,000 is stored as

Click To expand

and 0.1032 is stored as

Click To expand

Normalization provides the maximum precision possible.

In Visual Basic, values of type Single use 32 bits with an approximate range of ±1.4E - 45 to ± 3.4E + 38 with seven significant digits. Values of type Double use 64 bits with an approximate range of ± 4.9E - 324 to 1.7E + 308 with 15 significant digits.

Model Numbers Any real number that can be represented exactly as a floating-point number in the computer is called a model number. A real number whose value cannot be represented exactly is approximated by the model number closest to it. In our system with four digits of precision, 0.3021 is a model number. The values 0.3021409, 0.3021222, and 0.30209999999 are examples of real numbers that are represented in the computer by the same model number. The following table shows all of the model numbers for an even simpler floating-point system that has one digit in the mantissa and an exponent that can be -1, 0, or 1.

0.1 × 10-1 | 0.1 × 10+0 | 0.1 × 10+1
0.2 × 10-1 | 0.2 × 10+0 | 0.2 × 10+1
0.3 × 10-1 | 0.3 × 10+0 | 0.3 × 10+1
0.4 × 10-1 | 0.4 × 10+0 | 0.4 × 10+1
0.5 × 10-1 | 0.5 × 10+0 | 0.5 × 10+1
0.6 × 10-1 | 0.6 × 10+0 | 0.6 × 10+1
0.7 × 10-1 | 0.7 × 10+0 | 0.7 × 10+1
0.8 × 10-1 | 0.8 × 10+0 | 0.8 × 10+1
0.9 × 10-1 | 0.9 × 10+0 | 0.9 × 10+1

The difference between a real number and the model number that represents it is a form of representational error called rounding error. We can measure rounding error in two ways. The absolute error is the difference between the real number and the model number. For example, the absolute error in representing 0.3021409 by the model number 0.3201 is 0.0000409. The relative error is the absolute error divided by the real number and is sometimes stated as a percentage. For example, 0.0000409 divided by 0.3021409 is 0.000135, or 0.0135%.

The maximum absolute error depends on the model interval—the difference between two adjacent model numbers. In our example, the interval between 0.3201 and 0.3022 is 0.0001. The maximum absolute error in this system, for this interval, is less than 0.0001. Adding digits of precision makes the model interval (and thus the maximum absolute error) smaller.

The model interval is not a fixed number; it varies with the exponent. To see why the interval varies, consider that the interval between 3021.0 and 3.022.0 is 1.0, which is 104 times larger than the interval between 0.3021 and 0.3022. This makes sense, because 3021.0 is simply 0.3021 times 104. Thus, a change in the exponent of the model numbers adjacent to the interval has an equivalent effect on the size of the interval. In practical terms, this means that we give up significant digits in the fractional part in order to represent numbers with large integer parts. Figure 13.9 illustrates this by graphing all of the model numbers listed in the preceding table.

Click To expand
Figure 13.9: A graphical representation of model numbers

We also can use relative and absolute error to measure the rounding error resulting from calculations. For example, suppose we multiply 1.0005 by 1,000. The correct result is 1000.5, but because of rounding error, our four-digit computer produces 1000.0 as its results. The absolute error of the computed result is 0.5, and the relative error is 0.05%. Now suppose we multiply 100,050.0 by 1,000. The correct result is 100,500,000, but the computer produces 100,000,000 as its result. If we look at the relative error, it is still a modest 0.05%, but the absolute error has grown to 50,000. Notice that this example is another case of changing the size of the model interval.

Whether it is more important to consider the absolute error or the relative error depends on the situation. It is unacceptable for an audit of a company to discover a $50,000 accounting error; the relative error is acceptable in representing prehistoric dates because the error in measurement techniques increases with age. That is, if we are talking about a date roughly 10,000 years ago, an absolute error of 5 years is acceptable; is the date is 100,000,000 years ago, then an absolute error of 50,000 years is equally acceptable.

Comparing Floating-Point Numbers We have cautioned against comparing floating-point numbers for exact equality. Our exploration of representational errors in this chapter reveals why calculations may not produce the expected results even though it appears that they should. In Chapter 10, we wrote an expression that compares two floating-point variables r and s for near equality using the floating-point absolute value method Abs:

Math.Abs(r - s) < 0.00001

From our discussion of model numbers, you now can recognize that the constant 0.00001 in this expression represents a maximum absolute error. We can generalize this expression as

Math.Abs(r - s) < ERROR_TERM

where ERROR_TERM is a value that must be determined for each programming problem.

What if we want to compare floating-point numbers with a relative error measure? We must multiply the error term by the value in the problem that the error is relative to. For example, if we want to test whether r and s are "equal" within 0.05% of s, we write the following expression:

Math.Abs(r - s) < 0.0005 * s

Keep in mind that the choice of an acceptable error and whether it should be absolute or relative depends on the problem being solved. The error terms we have shown in our example expressions are completely arbitrary and may not be appropriate for most problems. In solving a problem that involves the comparison of floating-point numbers, you typically want an error term that is as small as possible. Sometimes the choice is specified in the problem description or is reasonably obvious. Some cases require careful analysis of both the mathematics of the problem and the representational limits of the particular computer. Such analyses are the domain of a branch of mathematics called numerical analysis and are beyond the scope of this text.

Underflow and Overflow In addition to representational errors, there are two other problems to watch for in floating-point arithmetic: underflow and overflow.

Underflow is the condition that arises when the value of a calculation is too small to be represented. Going back to our decimal representation, let's look at a calculation involving small numbers:

This value cannot be represented in our scheme because the exponent -13 is too small. Our minimum is -9. One way to resolve the problem is to set the result of the calculation to 0.0. Obviously, any answer depending on this calculation will not be exact.

Overflow is a more serious problem because there is no logical recourse when it occurs. For example, the result of the calculation

cannot be stored, so what should we do? To be consistent with our response to overflow, we could set the result to 9999 × 109 (the minimum representable value in this case). Yet this seems intuitively wrong. The alternative is to stop with an error message.

In Visual Basic, if an overflow occurs, the result is set to signed infinity. If an underflow occurs, the result is set to a signed zero. No exception is thrown in either case.

Although we are discussing problems with floating-point numbers, integer numbers can also overflow both negatively and positively. When overflow or underflow occurs with integers, an overflow or underflow exception is thrown.

Sometimes you can avoid overflow by arranging computations carefully. Suppose you want to know how many different five-card poker hands can be dealt from a deck of cards. What we are looking for is the number of combinations of 52 cards taken 5 at a time. The standard mathematical formula for the number of combinations of n things taken r at a time is

We could write a method Factorial and write this formula in an assignment statement:

hands = Factorial(52) / Factorial(5) * Factorial(47))

The only problem is that 52! is a very large number (approximately 8.0658 × 1067). And 47! is also large (approximately 2.5862 × 1059). Both of these numbers are well beyond the capacity of the CLR to represent exactly (52! requires 68 digits of precision). Even though these numbers can be represented as floating-point numbers, most of the precision is still lost. By rearranging the calculations, however, we can achieve an exact result with any integral type with nine or more digits of precision (with Integer in Visual Basic). How? Consider that most of the multiplications in computing 52! are canceled when the product is divided by 47!

So we really only have to compute

hands = 52 * 51 * 50 * 49 * 48 / Factorial(5)

which means the numerator is 311,875,200 and the denominator is 120. If we have nine or more digits of precision, we get an exact answer: 2,598,960 poker hands.

Cancellation Error Another type of error that can happen with floating-point numbers is called a cancellation error, a form of representational error that occurs when numbers of widely differing magnitudes are added or subtracted. Let's look at an example:

The laws of arithmetic say this equation should be true. But is it true if the computer does the arithmetic?

To four digits, the sum is 1000 × 10-3. Now the computer subtracts 1:

The result is 0, not .00001234.

Sometimes you can avoid adding two floating-point numbers that are drastically different in size by carefully arranging the calculations. Suppose a problem requires many small floating-point numbers to be added to a large floating-point number. The result is more accurate if the program first sums the smaller numbers to obtain a larger number and then adds the sum to the large number.

Start Sidebar
Background Information
Practical Implications of Limited Precision

A discussion of representational, overflow, underflow, and cancellation errors may seem purely academic. In fact, these errors have serious practical implications in many problems. We close this section with three examples illustrating how limited precision can have costly or even disastrous effects.

During the Mercury space program, several of the spacecraft splashed down a considerable distance from their computed landing points. This delayed the recovery of the spacecraft and the astronaut, putting both in some danger. Eventually, the problem was traced to an imprecise representation of the Earth's rotation period in the program that calculated the landing point.

As part of the construction of a hydroelectric dam, a long set of high-tension cables had to be constructed to link the dam to the nearest power-distribution point. The cables were to be several miles long, and each one was to be a continuous unit. (Because of the high power output from the dam, shorter cables couldn't be spliced together.) The cables were constructed at great expense and strung between the two points. It turned out that they were too short, however, so another set had to be manufactured. The problem was traced to errors of precision in calculating the length of the catenary curve (the curve that a cable forms when hanging between two points).

An audit of a bank turned up a mysterious account with a large sum of money in it. The account was traced to an unscrupulous programmer who had used limited precision to his advantage. The bank computed interest on its accounts to a precision of a tenth of a cent. The hundredths of cents were not added to the customers' accounts, so the programmer had the extra hundredths for all the accounts summed and deposited into an account in his name. Because the bank had thousands of accounts, these tiny amounts added up to a large amount of money. And because the rest of the bank's programs did not use as much precision in their calculations, the scheme went undetected for many months.

The moral of these stories is twofold: (1) The results of floating-point calculations are often imprecise, and these errors can have serious consequences; and (2) if you are working with extremely large numbers or extremely small numbers, you need more information than this book provides and should consult a numerical analysis text.

End Sidebar
Start Sidebar
Software Engineering Tip
Choose a Numeric Data Type

A first encounter with all the numeric data types of Visual Basic may leave you feeling overwhelmed. To help in choosing an alternative, you may even feel tempted to toss a coin. You should resist this temptation because each data type exists for a reason. Here are some guidelines:

  1. In general, Integer is preferable. As a rule, you should use floating-point types only when absolutely necessary—that is, when you definitely need fractional values. Not only is floating-point arithmetic subject to representational errors, it also is significantly slower than integer arithmetic on most computers.

  2. For ordinary integer data, use Integer instead of Byte or Short. It's easy to make overflow errors with these smaller data types.

  3. Use Long only if the range of Integer value is too restrictive. Compared to Integer, the Long type requires twice the memory space.

  4. Double is the default floating-point type in Visual Basic and should be used unless you are certain that a problem can be solved with the lower precision of the Single type.

By following these guidelines, you'll find that the simple types you use most often are Integer and Double, along with String for string data and Boolean for Boolean data. Only rarely do you need the longer or shorter variations of these fundamental types.

End Sidebar
Start Sidebar
Problem-Solving Case Study
Matrix Manipulation

Problem Many mathematical problems, such as rotations in graphics, require the addition, subtraction, and multiplication of two matrices. Design and implement a general-purpose Matrix class that provides the operations of addition, subtraction, and multiplication for real matrices.

Brainstorming You reach for your algebra book to refresh your memory on what matrices are and how matrix addition, subtraction, and multiplication are defined. You find that a matrix is just like an array data type—well, not exactly. A matrix is a mathematical object; an array is a structured data type. A more accurate statement is that an array is a perfect structure to implement a matrix.

Before we start to design the user interface for the Matrix class, we review what the operations on matrices mean. To add two matrices, you add the values in the corresponding positions: result(i,j) = A(i,j) + B(i,j). To subtract one matrix from another, you subtract the values in the corresponding positions: result(i,j) = A(i,j) - B(i,j). A + B and A - B are defined only on matrices with the same dimensions.

Matrix multiplication is slightly more complex. If matrix E is the result of multiplying matrix C and matrix D, then

Why didn't we use the same matrices, A and B, for multiplication that we used for addition and subtraction? Well, matrices A and B cannot be multiplied. Look carefully at the formula: The first row of C is being multiplied item by item by the first column of D and the values summed. Therefore, the number of columns in C must be equal to the number of rows in D in the preceding formula. Here is an example.

The sum of multiplying a row by a column is called the dot product. Another way of stating multiplications is that for any position i, j in E

Now that we understand the semantics of the operations, we are ready to determine the responsibilities.

Scenario If we were the users of the class, what facilities would we need? First, of course, we would need to create the matrix itself by telling it how many rows and columns there should be. Next, we would need a way to put values into the slots of the matrix. We would probably want to print out the matrix after it is constructed to be sure the values were correct. At that point, we would be ready to apply one of the binary operations, say, addition. We send the message to one matrix to add itself to the matrix in the message parameter and return the result to us. We follow the same process for subtraction and multiplication.

Are there any states of the matrix object we might want to know about? Well, it might be useful to view the value at a particular matrix position, so let's add that to the list of responsibilities. We also might want to access the number of rows and columns in a matrix.

What about error conditions? Matrix addition and subtraction require that the matrices have the same dimensions, and matrix multiplication requires that the number of columns in the first matrix must be equal to the number of rows in the second matrix. It makes sense for the matrix that is being told to perform an operation to check to be sure the operation is legal before complying with the request. If the operation is not legal, the matrix can throw an exception.

CRC Card We can summarize our observations in a CRC card.

Click To expand

Internal Data Representation We said earlier that an array is the ideal representation structure for a matrix. In many languages, we would have to include the number of rows and number of columns as data fields in the Matrix class, but Visual Basic provides them for us as instance fields in the array object. Let's represent the numeric values in the matrices as Double; this allows us to handle the largest range of values.

Public Class Matrix
   ' Private data field
   Dim matrix(,) As Double
   …
End Class

Responsibility Algorithms To create itself, we need a constructor that takes the number of rows and the number of columns as parameters and creates the array.

Public Sub New(rows As Integer, cols As Integer)
   ' Create empty matrix
   Dim matrix(row, cols) As Double
End Sub

The next method simply asks the object to return a copy of an item in a particular slot in the array.

Public Function knowValueAt(row As Integer, col As Integer) As Double
   ' Returns the value at matrix(row, col)
   return matrix(row, col)
End Function

The next two observer methods return the number of rows and the number of columns. The GetLength method of the two-dimensional array gives the number of rows; the GetLength method applied to the second dimension gives the number of columns in that row.

Public Function knowRows() As Integer
   ' Returns the number of rows in matrix
   return.matrix.GetLength(0)
End Function

Public Function knowColumns() As Integer
   ' Returns the number of columns in matrix
   return matrix.GetLength(1)
End Function

The main transformer method takes a value and a row and column number. The value is stored in the matrix at the (row, col) position.

Public Sub setValue(dataItem As Double, row As Integer, col As Integer)
   ' Sets matrix(row, col) to dataItem
   matrix(row, col) = dataItem
End Sub

The remaining observer is printMatrix. The matrix is to be printed by row. We have a pattern that we can follow exactly for our general discussion about arrays. Because we don't know how many columns the matrix has, we should print a blank line between rows.

Public Sub printMatrix()
   ' Matrix is written to display by row
   Dim row, col As Integer
   For row = 0 To matrix.GetLength(0)
      For col = 0 to matrix.GetLength(1)
         console.write(matrix(row, col) & " ")
      Next col
      console.writeline()
   Next row
End Sub

The last three methods are what this problem is all about: addition, subtraction, and multiplication of matrices. One matrix is the one to which the method is applied and the other matrix is a parameter.

...........................................................................
add (two)

 If the addition is not legal
    throw MatException
 Else
    Create result matrix with same dimensions as this
    For row going from 0 through matrix.GetLength(0)-1
      For col going from 0 through matrix.GetLength(1)-1
        Set result(row, col) to matrix(row, col) + two(row, col)
 Return result

Each of these steps is concrete. Determining if the addition is legal is a matter of checking the dimensions of this against the dimensions of the parameter. The string that goes with the exception can simply state that the addition is not legal.

Public Sub add(two As Matrix) As Matrix
   ' Returns the sum of the current matrix object and the two matrix objects
   Dim row, col As Integer
   If (matrix.GetLength(0) <> two.GetLength(0) Or _
      (matrix.GetLength(1) <> two.GetLength(1)) Then
      Throw New MatException(Nothing, "Illegal matrix addition.")

   Else
      Dim result As New Matrix(matrix.GetLength(0), matrix.GetLength(1))
      For row = 0 To matrix.GetLength(0)-1
         For col = 0 To matrix.GetLength(1)-1
            result(row, col) = matrix(row, col) + two(row, col)
         Next col
      Next row
      Return result
   End If
End Sub

      ...........................................................................
      Subtract (two)

       If the subtraction is not legal
          throw MatException
       Else
          Create result matrix with the same dimension as this
       For row going from 0 through matrix.GetLength(0)-1
          For col going from 0 through matrix.GetLength(1)-1
            Set result(row, col) to matrix(row, col) - two(row, col)
       Return result

Public Function Subtract(two As Matrix) As Matrix
   ' Returns two subtracted from current matrix
   Dim row, col As Integer
   If (matrix.GetLength(0) <> two.GetLength(0)) Or _
      (matrix.GetLength(0) <> two.GetLength(1)) Then
      Throw new MatException(Nothing, "Illegal matrix subtraction.")
   Else
      Dim result As Matrix(matrix.GetLength(0), matrix.GetLength(1))
      For row = 0 To matrix.GetLength(0)-1
         For col = 0 To matrix.GetLength(1)-1
            result(row, col) = matrix(row, col) - two(row, col)
         Next col
      Next row
      Return result
   End If
End Function

       ...........................................................................
       Multiply (two)

        If multiplication is not legal
           throw MatException
        Else
           Create result matrix with number of rows in current matrix and number of
               columns in two
           For row going from 0 through matrix.GetLength(0)-1
             For col going from 0 through matrix.GetLength(1)-1
               Set result(row, col) to dot product of row of current matrix and column
                of two matrix
        Return result

       ...........................................................................
       dotProduct (row, col)

        Set total to 0
        For index going from 0 through number of columns of matrix
          Set total to matrix(row, col) * two(row, col) + total

           Let's make dotProduct a helper function.

Public Function Multiply(two As Matrix) As Matrix
   'Returns current matrix times two
   Dim row, col As Integer
   If (matrix.GetLength(0) <> two.GetLength(1)) Then
      Throw new MatException(Nothing, "Illegal matrix multiplication.")
   Else
      Dim result As new Matrix(matrix.GetLength(0), matrix.GetLength(1))
      For row = 0 To matrix.GetLength(0)-1
         For col = 0 To matrix.GetLength(1)-1
            result(row, col) = dotProduct(row, col, two)
         Next col
      Next row
      Return result
   End If
End Function

Private Function dotProduct(row As Integer, col As Integer, two As Matrix)
   'Returns the dot product of row of current matrix and column of two
   Dim total As Integer = 0
   Dim index As Integer
   For index = 0 To two.GetLength(0)-1

      total = total + matrix(row, index) * two(index, col)
   Next index
   Return total
End Function

Before we collect these methods into a complete class, have we forgotten anything? The class creates itself, has three observer methods that return its state variables, has a transformer that sets a value in a specified row and column—what happens if the specified row and column are not within the bounds of the matrix? The class should check for this error and throw an exception. The same is true for the operation that returns a value at a specified position. What about the binary matrix operations? We know that these numeric operations can cause underflow and overflow. If there is underflow, the values are automatically set to zero, but if there is overflow, the values are set to a signed infinity. If overflow occurs, the operation should throw an exception. The Double class has a boolean class method isInfinite that we can use to determine if there has been overflow. In the following class, overflow is checked, but the other error conditions are left to a Case Study Follow-Up Exercise.

namespace MatrixClass
    Public Class Matrix

       Private matrix(,) As Double

       Public Sub New(rows as integer, cols as integer)
       ' Create empty matrix
          Redim matrix(rows, cols)
       End Sub

       Public Function knowValueAt(row as integer, col as integer) As Double
       ' Returns the value at matrix(row, col)
          Return matrix(row, col)
       End Function

       Public Function knowRows() As Integer
       ' Returns the number of rows in matrix
          Return matrix.GetLength(0)
       End Function

       Public Function knowCols() As Integer
       ' Returns the number of columns in matrix
          Return matrix.GetLength(1)
       End Function

       Public Sub setValue(dataItem As Double, row As Integer, col As Integer)
       ' Sets matrix(row, col) to dataItem
          matrix(row, col) = dataItem
       End Sub

       Public Sub printMatrix()
       ' Matrix is displayed on screen
          Dim row, col As Integer
          For row = 0 To matrix.GetLength(0)-1
             For col = 0 To matrix.GetLength(1)-1
                Console.Write(matrix(row, col) & " ")
             Next col
             Console.WriteLine()
          Next row
       End Sub

       Public Function add(two As Matrix) As Matrix
       ' Returns the sum of me and two
       ' Throws MatException if the matrices cannot be added or overflow occurs
          Dim row, col As Integer
          If (matrix.GetLength(0) <> two.GetLength(0) Or _
             (matrix.GetLength(1) <> two.GetLength(1)) Then
             Throw New MatException("Illegal matrix addition.")
          Else
             Dim result(matrix.GetLength(0), matrix.GetLength(1)) as Matrix
             For row = 0 To matrix.GetLength(0)-1
                For col = 0 To matrix.GetLength(1)-1
                   result(row, col) = matrix(row, col) + two(row, col)
                   If Double.IsInfinity(result(row, col)) Then
                      Throw New MatException("Addition overflow.")
                   End If
                Next col
             Next row
             Return result
          End If
       End Function

       Public Function sub(two As Matrix) As Matrix
       ' Returns two subtracted from me
       ' Throws MatException if the matrices cannot be subtracted
       ' or overflow occurs
          Dim row, col As Integer
          If (matrix.GetLength(0) <> two.GetLength(0) Or _
             (matrix.GetLength(1) <> two.GetLength(1)) Then
             Throw New MatException("Illegal matrix subtraction.")
          Else
             Dim result(matrix.GetLength(0), matrix.GetLength(1)) As Matrix
             For row = 0 To matrix.GetLength(0)-1
               For col = 0 To matrix.GetLength(1)-1
                  result(row, col) = matrix(row, col) - two(row, col)

                  If Double.IsInfinity(result(row, col)) Then
                     Throw New MatException("Subtraction overflow.")
                  End If
               Next col
             Next row
             Return result
         End If
      End Function

      Public Function Multiply(two As Matrix) As Matrix
      ' Returns me times two
      ' Throws MatException if the matrices cannot be multiplied
      ' or overflow occurs
         Dim row, col As Integer
         If (matrix.GetLength(1) <> matrix.GetLength(0)) Then
            Throw New MatException("Illegal matrix multiplication.")
         Else
            Dim result(matrix.GetLength(1), two.GetLength(0)) As Matrix
            For row = 0 To matrix.GetLength(0)-1
               For col = 0 To matrix.GetLength(1)-1
                  result(row, col) = dotProduct(row, col, two)
                  If Double.IsInfinity(result(row, col)) Then
                     Throw New MatException("Multiplication overflow.")
                  End If
               Next col
            Next row
            Return result
         End If
      End Function

      Private Function dotProduct(row As Integer, col As Integer, _
                                  two As Matrix) As Double
      ' Returns the dot product of row of this and column of two
         Dim total As Double = 0
         Dim index As Integer
         For index = 0 To two.GetLength(0)-1
            total = total + matrix(row, index) * two(index, col)
         Next index
         Return total
      End Function

   End Class
End Namespace

Testing Because the branching statements check for errors and throw exceptions only if they occur, a clear or white-box testing strategy is appropriate. The end cases for addition and subtraction would be for dimensions of one by one and something larger. For multiplication, the outer dimensions should be one and the inner dimensions something greater than one, the inner dimensions should be one and the outer dimensions something greater than one. Then the error conditions must all be checked. A complete test plan is left as a Case Study Follow-Up Exercise.

End Sidebar


Testing and Debugging

Errors with multidimensional arrays usually fall into two major categories: index expressions that are out of order and index range errors. We have been very careful to use an array object's own GetLength value in loop expressions to minimize range errors. However, inadvertent switching of indexes can cause index range errors. Take a look at the code for dotProduct. What happens if the indices are reversed in the following statement?

total = total + matrix(row, index) * two(index, col)

That is, what happens if the statement is coded as follows?

total = total + matrix(index, row) * two(col, index)

If the first matrix is a 3 × 5 and the second is a 5 × 2, index goes from 0 through 4 while row and col remain at 0. matrix(0,0) and two(0,0) are accessed; then matrix(1,0) and two(0,1) are accessed; then matrix(2,0) and two(0,2) are accessed. This last access causes an IndexOutOfBounds exception to be thrown: two(0,2) doesn't exist.

How can you avoid such errors? There is no simple answer. You just have to be careful and thoroughly test your code.

Testing and Debugging Hints

  1. With multidimensional arrays, use the proper number of indexes when referencing an array component and make sure the indexes are in the correct order.

  2. In loops that process multidimensional arrays, double-check the upper and lower bounds on each index variable to be sure they are correct for that dimension of the array.

  3. When passing an array object as an argument, be sure that is has the same number of dimensions as the parameter of the method to which it is being passed.

  4. Be aware of representational, cancellation, overflow, and underflow errors. If possible, try to arrange calculations in your program to keep floating-point numbers from becoming too large or too small.

  5. If your program increases the value of a positive integer and the result suddenly becomes a negative number, you should suspect integer overflow.

  6. Avoid mixing data types in expressions, assignment operations, argument passing, and the return of a method value. If you must mix types, explicit conversions can prevent unwelcome surprises (and errors) caused by implicit type conversion.


Summary of Classes

Package Name |  |  |  | Comments
Class Name |  |  |  |
MatrixClass |  |  |  |
 | Matrix |  |  |
 |  | Constructor: | New(Integer, Integer) | Number of rows, number of columns
 |  |  | Add(Matrix) As Matrix | Returns result of matrix addition
 |  |  | Sub(Matrix) As Matrix | Returns result of matrix subtraction
 |  |  | Multiply(Matrix) As Matrix | Returns result of matrix multiplication
 |  | Transformer: | setValue(Double, | Sets (row, col) to Double value
 |  |  | Integer, Integer) |
 |  | Observers: | knowValue(Integer, | Returns value at (row, col)
 |  |  | Integer) As Double |
 |  |  | knowRows() As Integer | Returns the number of rows
 |  |  | knowColumns() As Integer | Returns the number of columns
 |  |  | printMatrix() | Prints matrix by row on display


Summary

Two-dimensional arrays are useful for processing information that is represented naturally in tabular form. Processing data in two-dimensional arrays usually takes one of two forms: processing by row or processing by column. Associated with each two-dimensional array is an instance method GetLength(0) that contains the number of rows. Associated with each row of the table is an instance method GetLength(1) that contains the number of items in the row (the column length). The number of items in a row is usually the same for each row, but does not need to be. If the rows are uneven, the array is called a ragged array.

A multidimensional array is a collection of like components that are ordered on more than two dimensions. Each component is accessed by a set of indexes, one for each dimension, which represents the component's position on the various dimensions. Each index may be thought of as describing a feature of a given array component.

The floating-point types built into the Visual Basic language are Single and Double. Floating-point numbers are represented in the computer with a fraction and an exponent. This representation permits numbers that are much larger or much smaller than those that can be represented with integral types. Floating-point representation also allows us to perform calculations on numbers with fractional parts.

However, there are drawbacks to using floating-point numbers in arithmetic calculations. Representational errors, for example, can affect the accuracy of a program's computations. When using floating-point numbers, keep in mind that if two numbers are vastly different from each other in size, adding or subtracting them can produce the wrong answer. Remember, also, that the computer has a limited range of numbers that it can represent. If a program tries to compute a value that is too large or too small, the result is unusual or unexpected values.


Quick Check

Q:

1. Declare a two-dimensional array named plan and create an array object with 30 rows and 10 columns. The component type of the array is Single. (pp. 453–454)

1. Dim plan(30,10) As Single

Q:

2. Given the array created in Question 1, answer the following questions.

Q:

a. Assign the value 27.3 to the component in row 13, column 7 of the array plan from Question 1. (pp. 493–494)

a. plan(13,7) = 27.3

Q:

b. Nested For loops can be used to sum the values in each row of array plan. What range of values would the outer For loop count through to do this? (pp. 496–497)

b. For row = 0 To 29

Q:

c. Nested For loops can be used to sum the values in each column of array plan. What range of values would the outer For loop count through to do this? (p. 498)

c. For col = 0 To 9

Q:

d. Write a program fragment that initializes array plan to all 1s. (p. 499)

d. For row = 0 To 29 For col = 0 to 9 plan(row, col) = 0R Next col Next row

Q:

e. Write a program fragment that prints the contents of array plan, one row per line of output. (pp. 499–500)

e. For row = 0 To 29 For col = 0 To 9 outFile.Write(plan(row, col)) Next col outFile.WriteLine() Next row

Q:

3. Suppose array plan is passed as an argument to a method in which corresponding parameter is named someArray. What would the declaration of someArray look like in the parameter list? (p. 500)

3. Dim someArray(,) As Single

Q:

4. Given the declarations

const SIZE As Integer = 10
Dim quick(SIZE, SIZE, SIZE, SIZE-1) As Integer

Q:

a. How many components does array quick contain? (p. 501)

a. Nine thousand (10 10 10 9)

Q:

b. Write a program fragment that fills array quick with blanks. (pp. 501–502)

b. For dim1 = 0 To SIZE-1 For dim2 = 0 To SIZE-1 For dim3 = 0 To SIZE-1 For dim4 = 0 To SIZE-2 quick(dim1, dim2, dim3, dim4) = ` ` Next dim4 Next dim3 Next dim2 Next dim1

Q:

5. Why is it inappropriate to use a variable of a floating-point type as a loop-control variable? (p. 506)

5. Because representational errors can cause the loop-termination condition to be evaluated with unpredictable results.

Q:

6. If a computer has four digits of precision, what would be the result of the following addition operation? (pp. 506–507)

6. 400500.00 (Actually, 4.005E+5)

Answers

A:

1. Dim plan(30,10) As Single

A:

a. plan(13,7) = 27.3

A:

b. For row = 0 To 29

A:

c. For col = 0 To 9

A:

d.

For row = 0 To 29
  For col = 0 to 9
    plan(row, col) = 0R
  Next col
Next row

A:

e.

For row = 0 To 29
  For col = 0 To 9
    outFile.Write(plan(row, col))
  Next col
  outFile.WriteLine()
Next row

A:

3. Dim someArray(,) As Single

A:

a. Nine thousand (10 × 10 × 10 × 9)

A:

b.

For dim1 = 0 To SIZE-1
  For dim2 = 0 To SIZE-1
    For dim3 = 0 To SIZE-1
      For dim4 = 0 To SIZE-2
        quick(dim1, dim2, dim3, dim4) = " "
      Next dim4
    Next dim3
  Next dim2
Next dim1

A:

5. Because representational errors can cause the loop-termination condition to be evaluated with unpredictable results.

A:

6. 400500.00 (Actually, 4.005E+5)


Exam Preparation Exercises

    Chapter 13

  1. Given the declarations

    Const NUM_SCHOOLS As Integer = 10
Const NUM_SPORTS As Integer = 3
Dim kidsInSports(NUM_SCHOOLS, NUM_SPORTS) As Integer
Dim costOfSports(NUM_SPORTS, NUM_SCHOOLS) As Double

    answer the following questions:

    1. What is the number of rows in kidsInSports?

    2. What is the number of columns in kidsInSports?

    3. What is the number of rows in costOfSports?

    4. What is the number of columns in costOfSports?

    5. How many components does kidsInSports have?

    6. How many components does costOfSports have?

    7. What kind of processing (row or column) would be needed to total the amount of money spend on each sport?

    8. What kind of processing (row or column) would be needed to total the amount of children participating in sports at a particular school?

  2. Given the following code segments, draw the arrays and their contents after the code is executed. Indicate any undefined values with the letter U.

    1. Dim exampleA(4,3) As Integer
Dim i, j As Integer
For i = 0 To 3
   For j = 0 To 2
      exampleA(i,j) = i* j
   Next j
Next i

    2. Dim exampleB(4,3) As Integer
Dim i, j As Integer
For i = 0 To 3
   For j = 0 To 2
      exampleB(i,j) = (i + j) mod 3
   Next j
Next i

    3. Dim exampleC(8,2) As Integer
Dim i, j As Integer
exampleC(7,0) = 4
exampleC(7,1) = 5
For i = 0 To 6
   exampleC(i,0) = 2
   exampleC(i,1) = 3
Next i

    1. Define an Integer variable teamType and an Integer variable result-Type.

    2. Define a two-dimensional array variable outcome.

    3. This array is used to keep track of the wins and losses for the baseball season. teamType represents the classes, freshman (0), sophomore (1), junior (2), and senior (3). resultType represents whether a class won (0), tied (1), or lost (2). Instantiate the array object referenced by outCome.

  4. (True or False?) The number of rows in the array must be specified on the parameter list of a method that takes the array as a parameter.

  5. Declare and instantiate the two-dimensional arrays described below.

    1. An array with five rows and six columns, which contains Boolean values

    2. An array, indexed from 0 through 39 and 0 through 199, which contains Double values

    3. An array, indexed from 0 through 3 and 0 through 2, which contains Char values

  6. A logging operation keeps records of 37 loggers' monthly production for purposes of analysis, using the following array structure:

    Const NUM_LOGGERS As Integer = 37
Dim logsCut(NUM_LOGGERS) As Integer
Dim monthlyHigh As Integer
Dim monthlyTotal As Integer
Dim yearlyTotal As Integer
Dim high As Integer
Dim month As Integer
Dim bestMonth As Integer
Dim logger As Integer
Dim bestLogger As Integer

    1. (True or False?) The following statement assigns the January log total for logger number 7 to monthlyTotal.

      monthlyTotal = logsCut(7,0)

    2. (True or False?) The following statements compute the yearly total for logger number 11.

      yearlyTotal = 0
For month = 0 to NUM_LOGGERS-1
   yearlyTotal = yearlyTotal + logsCut(month, 10)
Next month

    3. (True or False?) The following statements find the best logger (most logs cut) in March.

      monthlyHigh = 0
For logger = 0 to NUM_LOGGERS-1
   If (logsCut(logger, 2) > monthlyHigh) Then
      bestLogger = logger
      monthlyHigh = logsCut(logger, 2)
   End If
Next Logger

    4. (True or False?) The following statements find the logger with the highest monthly production and the logger's best month.

      high = -1
For month = 0 To 11
   For logger = 0 To NUM_LOGGERS-1
      If logsCut(logger, month) > high) Then
         high = logsCut(logger, month)
         bestLogger = logger
         bestMonth = month
      End If
   Next logger
Next month

  7. Declare and instantiate the Double array variables described below.

    1. A three-dimensional array in which the first dimension is indexed from 0 through 9, the second dimension is indexed from 0 through 6 representing the days of the week, and the third dimension is indexed from 0 through 20

    2. A four-dimensional array in which the first two dimensions are indexed from 0 through 49, and the third and fourth have 20 and 30 slots, respectively

  8. If a system supports 10 digits of precision for floating-point numbers, what are the results of the following computations.

    1. 1.4E + 12 + 100.0

    2. 4.2E - 8 + 100.0

    3. 3.2E - 5 + 3.2E+5

  9. Define the following terms:

    • significant digits

    • exponent

    • representational error

    • overflow

    • underflow


Programming Warm-Up Exercises

    Chapter 13

  1. Using the declarations in Exam Preparation Exercise 1, write code fragments to do the following tasks.

    1. Determine which school spent the most money on football.

    2. Determine which sport the last school spent the most money on.

    3. Determine which school had the most students playing basketball.

    4. Determine in which sport the third school had the most students participating.

    5. Determine the total amount spent by all schools on volleyball.

    6. Determine the total number of students who played any sport. (Assume that each student played only one sport.)

    7. Determine which school had the most students participating in sports.

    8. Determine which was the most popular sport in terms of money spent.

    9. Determine which was the most popular sport in terms of student participation.

  2. Examine the following class declaration.

    Public Class TwoDimensions
' Private Data
   Private data(,) As Integer
   Private rowsUsed As Integer      ' Number of rows that contain
                                    ' data

   Private columnsUsed As Integer   ' Number of columns that
                                    ' contain data
   ' Methods
   Public Sub New(maxRows As Integer, maxColumns As Integer)
   ' Constructor : Creates a maxRows _ maxColumns array
   Public Sub inputData(inFile As StreamReader)
   ' Reads data into the array
   ' Data is in the file as follows:
   ' First line: number of rows (rowsUsed)
   ' Second line: number of columns (columnsUsed)
   ' The data is stored one value per line in row order. That is,
   ' the first columnsUsed values go into row 0; the next
   ' columnsUsed values go into row 1; etc.
   Public Print()
   ' Prints the values in the array on display
   Public Function maxInRow(row As Integer) As Integer
   ' Returns the maximum value in the specified row
   Public Function maxInCol(col As Integer) As Integer
   ' Returns the maximum value in the specified column
   Public Function maxInArray() As Integer
   ' Returns the maximum value in the array
   Public Function Sum() As Integer
   ' Returns the sum of the values in the array
   Public Function sumInRow(row As Integer) As Integer
   ' Returns the sum of the values in the specified row
   Public Function sumInCol(column As Integer) As Integer
   ' Returns the sum of the values in the specified column
   Public Function allPlus() As Boolean
   ' Returns True if all the values are positive; False otherwise

    1. Write the code for method TwoDimensions.

    2. Write the code for method inputData.

    3. Write the code for method print.

    4. Write the code for method maxInRow.

    5. Write the code for method maxInCol.

    6. Write the code for method maxInArray.

    7. Write the code for method sum.

    8. Write the code for method sumInRow.

    9. Write the code for method sumInCol.

    10. Write the code for method allPlus.

  3. Write a code segment that finds the largest value in a two-dimensional Double array of 50 rows and 50 columns.

  4. Given the following declarations

    Const NUM_DEPTS As Integer = 100
Const NUM_STORES As Integer = 10
Const NUM_MONTHS As Integer = 12

    1. Declare an array variable sales that is indexed by number of departments, number of stores, and number of months and contains Double values.

    2. Instantiate the array object referenced by sales.

    3. What values do the variables in sales have after it is created?

    4. Write a code segment to calculate the sum of the sales for January.

    5. Write a code segment to calculate the sum of the sales for store 2.

    6. Write a code segment to calculate the sum of the sales for department 33.

  5. In a program you are writing, a Double variable beta potentially contains a very large number. Before multiplying beta by 100.0, you want the program to test whether it is safe to do so. Write an If statement that tests for a possible overflow before multiplying by 100.0. Specifically, if the multiplication would lead to overflow, print a message and don't perform the multiplication; otherwise, go ahead with the multiplication.

  6. Write an application to print out MAX_VALUE and MIN_VALUE for types Single and Double.


Programming Problems

  1. Write an application that plays Tic-Tac-Toe. Represent the board as a 3 × 3 Char array. The array is initialized to blanks and each player is asked to turn to input a position. The first player's position is marked on the board with an O, and the second player's position is marked with an X. Continue the process until a player wins or the game is a draw. To win, a player must have three marks in a row, in a column, or on a diagonal. A draw occurs when the board is full and no one has won.

    Each player's position should be input as indexes into the Tic-Tac-Toe board—that is, a row number and a column number. Make the program user-friendly.

    After each game, print out a diagram of the board showing the ending position. Keep a count of the number of games each player has won and the number of draws. Before the beginning of each game, ask each player if he or she wishes to continue. If either player wishes to quit, print out the statistics and stop. Use buttons as appropriate.

  2. Photos taken in space by the Galileo spacecraft are sent back to Earth as a stream of numbers. Each number represents a level of brightness. A large number represents a high brightness level, and a small number represents a low level. Your job is to take a matrix (a two-dimensional array) of the numbers and print it as a picture.

    One approach to generating a picture is to print a dark character (such as a $) when the brightness level is low, and to print a light character (such as a blank or a period) when the level is high. Unfortunately, errors in transmission sometimes occur. Thus, your program should first attempt to find and correct these errors. Assume a value is in error if it differs by more than 1 from each of its four neighboring values. Correct the erroneous value by giving it the average of its neighboring values, rounded to the nearest integer.

    Example:

    Click To expand

    Note that values on the corners or boundaries of the matrix have to be processed differently that the values on the interior. Your application should print an image of the uncorrected picture and then an image of the corrected picture.

  3. The following diagram represents an island surrounded by water (shaded area).

    Click To expand

    Two bridges lead off of the island. A mouse is placed on the small blue square. Write a program to make the mouse take a walk across the island. The mouse is allowed to travel one square at a time, either horizontally or vertically. A random number from 1 through 4 should be used to decide which direction the mouse is to take. The mouse drowns when he hits the water; he escapes when he enters a bridge. You may generate a random number up to 100 times. If the mouse does not find his way by the hundredth try, he will die of starvation. Restart the mouse in a reinitialized array and go back and repeat the whole process. Count the number of times he escapes, drowns, and starves.

    Input

    • First input line—the size of the array, including border of water and bridges (not larger than 20 × 20)

    • Next N input lines—the rows of the two-dimensional array, where the positions containing negative numbers represent the water, the positions in the edge containing a 0 represent the bridges, the position containing a 1 represents the starting position of the mouse, and all other positions contain 0s

    Output

    • A line stating whether the mouse escaped, drowned, or starved

    • A line showing the mouse's starting position and the position of the two bridges

    • A map showing the frequency of the mouse's visits to each position

    • You should print the items above (double-spaced between trips) for each trip by the mouse.

  4. In competitive diving, each diver makes three dives of varying degrees of difficulty. Nine judges score each dive from 0 through 10 in steps of 0.5. The total score is obtained by discarding the lowest and highest of the judges' scores, adding the remaining scores, and then multiplying the scores by the degree of difficulty. The divers take turns, and when the competition is finished, they are ranked according to score. Write a program to calculate the outcome of a competition, using the following input and output specifications.

    Input

    • Number of divers

    • Divers names (10 characters), difficulty (Double), and judges' ratings (nine Doubles)

    • There is a line of data for each dive. All the data for Dive 1 are grouped together, then all for Dive 2, then all for Dive 3.

    Output

    • The input data, echo printed in tabular form with appropriate headings—for example,

      Name

      Difficulty

      Judge's number (1–9)

    A table that contains the following information:

    Name

    Dive 1

    Dive 2

    Dive 3

    Total

    where Name is the diver's name; Dive 1, Dive 2, and Dive 3 are the total points received for a single dive, as described above; and Total is the overall total.

  5. You are to test what happens when overflow occurs. Write a simple application that does the following tasks:

    • Calculates the factorial of an integer number.

    • Embeds the factorial in a loop that goes from 1 to 100.

    • Prints out the result of each factorial within the loop.

    • Run your application with the factorial calculated using each of the integer types.

    • Record the results of each run.

    • Redo the experiment taking the factorial of a real number. Record the results of each run.

    • Write a report describing what you have learned about overflow and Visual Basic.


Case Study Follow-Up Exercises

  1. There are no checks for row and column parameters being in bounds in the Matrix Manipulation Case Study. List the methods in which this error might occur.

  2. Implement checks for this error and recode the solution.

  3. Design a test plan for class Matrix as modified in Case Study Follow-Up Exercise 2.


Appendix A: Database Programming in Visual Basic. NET

An Overview of Database Technology

When people first started writing computer programs, data was stored in an external file created expressly for the program that was to use the file. Each time a new program was called for, a new file was created to store the data the program needed. In larger companies, it soon became clear that many of the external files created for data processing in several different departments contained the same data. Although data storage costs are quite cheap today, in the early days of data processing data storage was very expensive.

Expensive data storage was one problem, with duplicate data, but another problem was that the values of the data were not the same throughout the company. In a typical company, for example, employee data might be stored in one file in Personnel, in a separate file in the employee's department, and in yet a third file in Payroll. If the employee changed his address, the change might be made only in the department's file and not in the Personnel file. When it came time to mail the employee his or her paycheck, the Personnel department might send it to the wrong address. Trying to keep all this data up to date when it was located in several different files became a nightmare.

Both of these reasons, as well as others, influenced the development of database technology, especially relational database technology. A database itself is usually made up of several tables. In the following section, we'll discuss the organization of relational databases in more detail.


Relational Database Organization

To be able to use a database, we need to understand the organization of a database. When we talk about databases, we are usually talking about relational databases. A relational database is a set of related, organized data partitioned into what are called tables. A relational database is usually made up of many tables, each related to the other in some way. We will show you an example of a relational database and its tables later.

Basic Relational Database Terminology

The data in a table is divided into records. A record is a collection of fields that define the different parts of the record. For example, in a mailing list table, the fields might be a name, a street address, a city, a state, and a zip code.

Each record in a table is called a row. Each field in a record is called a column. A table is said to be a collection of rows and columns, in much the same way that a two-dimensional array is a collection of rows and columns. For a database to work properly, each row of a table must be unique. This is accomplished by creating a key field, which is a single field or combination of fields that uniquely identifies the record.

To make sense out of these terms, and to begin to learn how to use Visual Basic .NET with relational databases, let's look at how we can use Visual Basic .NET to explore the structure of a relational database.


Exploring the Structure of a Relational Database

On your CD is a copy of a relational database named Biblio. Biblio is made up of a set of tables that define a set of computer-related books. The tables in the database include the Titles table, the Publishers table, the Authors table, and the Title Author table. This database was defined using Microsoft Access, but we can explore its structure using Visual Basic .NET.

To begin, start a new Visual Basic .NET application. The first thing we need to do is create a connection to the Biblio database. We do that by selecting Connect to Database… from the Tools menu on the menu bar, as shown below.

Click To expand

The next thing we have to do is select a provider for the database connection. A provider is a program that establishes the connection between our Visual Basic .NET application and a database. Providers are also called drivers. To select a provider, click on the Providers tab of the DataLink Properties window and select Microsoft Jet 4.0 OLE DB Provider from the list of different providers, and then click the Next button:

Click To expand

You are then asked to enter a database name. The button to the right of the prompt allows you to browse your hard disk or other disks to find the Biblio database. Once you find the database, click on it so that the path to the database appears in the textbox:

Click To expand

You can click on the Test Connection button to make sure you are properly connected to the database. Otherwise, press the OK button and your database connection is complete.

A new panel now appears in the Visual Studio. NET IDE. The IDE is shown below:

Click To expand

The database connection is listed under Data Connections in the Server Explorer panel on the left side of the IDE. To examine the structure of the database in more detail, click on the plus button to the left of the listing for the Biblio database. Then click on the plus button next to the Tables listing to display all the tables in the database, as shown below:

Click To expand

There are other structures you can view in the Server Explorer, such as Views and Stored Procedures, but we'll concentrate just on tables in this chapter.

The next step we can take is to view the fields that make up the table. By expanding the view by a particular table, we can see a listing of all the fields in the table. The fields for the Titles table are shown below:

Click To expand

Each field has its own set of properties that you can view using the IDE. Just highlight the field and its properties will be displayed in the Property window:

Click To expand

The primary property we're interested in when working with a database is the DataType property. For example, the Year Published field is of data type adSmallInt, which translates to an Integer variable in Visual Basic .NET. We need to be aware of the data types of the fields in database tables when we perform operations on the data in those fields.

The next step we can take is to actually display the data in the table. To do this, just double-click on the table name. A spreadsheet-like display of the table appears where a form normally resides, as shown below:

Click To expand

The Solutions Explorer window and the Properties window have been narrowed to provide more room for the table view. You can easily understand the structure of the table just by looking at it. The field names are displayed along the top of the grid, with data for the field in the cells below.

You can scroll through the table several different ways. To move from one field to the next, you can either use the Tab key or the right arrow. If you move right beyond the last field, you are taken to the next record on the row below. You can scroll up and down between rows using the up and down arrows.

Being able to view the contents (and the structure) of a database and its tables can help you in writing code to access the database. In the next section, we discuss how to write Visual Basic .NET code to connect to a database table and access its data using ADO.NET, the database access technology that is built into Visual Basic .NET.


Why You Need ADO.NET

ADO.NET is the latest in a line of data models that allow a Visual Basic .NET application to access a database. The ADO.NET model provides a set of objects (classes, actually) for connecting to a database, retrieving, displaying, updating, and deleting data from a database. The ADO.NET model was designed to provide an easy-to-use set of methods and properties for performing database applications in Visual Basic .NET.

We can't just directly connect our Visual Basic .NET program to a database. There has to be another program that provides the connectivity to the database for our program. The technology that provides a Visual Basic .NET program with the lowest level of connectivity to a database is called OLEDB. OLEDB is a systems-level programming interface to a data store, which is just another name for a file that contains data of some type. Data stores can be relational databases, but they can also be an e-mail system, the directory services of an operating system, a plain text file, or some other source of data.

The Visual Basic .NET programmer can program to the OLEDB layer, but doing so is extremely complex and time-consuming. To make things easier, a second layer of technology, ADO.NET, is provided. We say that ADO.NET provides a wrapper for the functionality of OLEDB. ADO.NET is considered an applications programming interface (API), and is the interface we directly use when writing Visual Basic .NET programs with database access.

A diagram that depicts the database access hierarchy, from the database to a Visual Basic .NET program, is displayed in Figure A-1.

Click To expand
Figure A-1: Application-Provider-Data hierarchy


The ADO.NET Object Model

ADO.NET is a set of objects you use to add database programmability to your Visual Basic .NET applications. These objects (classes) are arranged in a hierarchy often referred to as the ADO.NET Object Model. Before we explore the object model, though, let's see how ADO.NET fits into the total architecture of a Visual Basic .NET application.

The Visual Basic .NET and ADO.NET Application Architecture

When we want to write a Visual Basic .NET application to access a database, we break the problem up into different components, sometimes called tiers or levels. There is the data component, which consists of the database and tables we want to access. The next component is called the adapter component because it consists of the tools that adapt the data from the data component to be used in our Visual Basic .NET application. The third level is called the application level. This architecture is depicted graphically in Figure A-2.

Click To expand
Figure A-2: Client/server model of database access

The architecture shown in Figure A-2 is the "classic" three-tiered client/server architecture for database programming. ADO.NET actually adds a fourth component to the architecture—the XML component. XML is a mark-up language that is somewhat similar to HTML, which is used for designing web pages, but XML is much more powerful than HTML. XML actually allows you to describe how you want the data stored within the XML document to be displayed, while with HTML you are consigned to use the tags and rules already built into the language.

ADO.NET uses XML as a layer between the adapter component and the application component. One advantage to doing this is that practically any data can be accessed using ADO.NET. The data that comes from the database is filtered through the adapter to an XML file, which is then parsed into a form the presentation layer (a Visual Basic .NET application) can use. This new architecture is shown in Figure A-3.

Click To expand
Figure A-3: Application-XML-ADO.NET-Provider hierarchy

As you can see by these figures, a key member of ADO.NET is the Dataset. The Dataset is an object that represents a database table or other configuration of database data. The Dataset is important to your application because it resides in memory while your application is actually disconnected from the original database. This helps improve performance because constantly accessing a database over a network or even on a local computer can use a good amount of your computer's resources, slowing down your application.

Let's look now at other benefits to ADO.NET.

The Benefits of ADO.NET

There are several benefits to using ADO.NET for database programming. Besides performance, which we've already mentioned, the benefits include interoperability, maintainability, programmability, and scalability.

Although this book has concentrated on teaching you how to program in Visual Basic .NET, one of the great advantages of the .NET platform is the ability to create applications using more than one language. For example, you can write the adapter layer of an application in C# and the presentation layer in Visual Basic .NET, or the other way around, depending on your needs. Since the data is transferred as an XML file between the layers, it doesn't really matter what language the layer is programmed in.

By providing different layers of access, applications written using ADO.NET are easier to maintain. Each layer is an independent unit, meaning there isn't a lot of complex code that ties the presentation layer to the adapter layer and to the data layer. The benefits of modular programming can be realized in spades with ADO.NET.

Because ADO.NET is inherently object-oriented, applications written in ADO.NET are easier to program. For example, in a banking application, each account is a separate object. We can then more easily access the properties of an individual account using dot notation. The primary objects of the application can then be referenced throughout the program without having to resort to a more complex syntax that involves naming lower-level programming objects, such as field types and value types.

We already talked about how ADO.NET disconnects from the physical data source, making your application run more efficiently. Another way ADO.NET speeds up performance is by using XML as the underlying data type for all database data. In previous versions of ADO.NET, the data in an application had to be converted from a Visual Basic data type to a data type recognized by the database, slowing down performance quite a bit.

ADO.NET also allows your applications to perform well when more users are added to the system (this is called scalability). Because the Dataset is disconnected from the database, adding more users to the program won't mean more people are tying up network resources. The Dataset resides in memory, meaning that each user can have his or her own copy of the data, or a system can be designed in which several users share data on a server that is local to that group of users. This is much better than trying to get a large group of users to work with data from a single server at the same time.

The ADO.NET Architecture

ADO.NET is a collection of objects that are based on the Dataset. A hierarchical view of the ADO.NET object model is shown in Figure A-4.

Click To expand
Figure A-4: The ADO.NET data model

The primary object in the object model is the Dataset. Next in importance after the Dataset is the DataTable. The DataTable object contains other objects: rows and columns. The row object, DataRow, contains a row (or record) of data from the original data source. The column object, DataColumn, contains the database table's schema, which is essentially a list of all the fields in the table, along with other structural information about the table. We'll describe these objects in more detail later as we work through the details of creating a database-aware Visual Basic .NET application, but first we need to discuss how to connect the adapter layer to the database layer.


Programming the Adapter Level

The adapter level is the first component of a Visual Basic .NET database application we have to code. There are four components to the adapter level we have to pay attention to:

  • Connection—The Connection object is used to establish a link between the Visual Basic .NET application and a data source.

  • Command—The Command object is used to represent an SQL statement for retrieving data from a data source.

  • DataReader—The DataReader object creates a read-only, read-forward stream of records from a data source.

  • DataAdapter—The DataAdapter object collects the connection object and command object into one set that is used to fill a Dataset with data.

Creating and Instantiating a Data Provider

We discussed earlier that a data provider is a driver program for connecting a Visual Basic .NET application to a data source. Microsoft provides two providers for connecting to data sources with ADO.NET—the SQL provider and the OLEDB provider.

The SQL provider is used to connect only with SQL Server databases. Since SQL Server is also a Microsoft product, this provider is optimized to provide the fastest application performance in comparison with other databases. However, this provider is somewhat limited since you can only use it to connect to a SQL Server database.

The other provider, OLEDB, can be used to connect to many different databases, from other Microsoft databases, such as Access, to databases provided by other vendors, such as Oracle and Informix. We will concentrate on using the OLEDB provider in this appendix, since most people running some version of the Windows operating system have Microsoft Access running on their computer also.

To create an OLEDB data provider, you must first import the System.Data.Oledb namespace. This namespace is required for all ADO.NET programming you will do with the OLEDB data provider. The next step is to declare a connection object. The class you use is OleDbConnection. The declaration of a Connection object looks like this:

Dim dbConnection As OleDbConnection

The next step is to instantiate the Connection object with information about the connection you want to make. This is known as the connection string. The connection string includes the name of the actual database driver you want to use and the complete path to the database you are connecting to. Here's an example:

dbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; " & _
                                   "Data Source = c:\databases\biblio.mdb")

In the line of code above, the connection string specifies the proper driver program for an Access database as the provider, and it specifies the Biblio.mdb database located in the \databases subdirectory of the local computer's c: drive.

The next step is to create a Command object for returning data from the database. The Command object usually holds an SQL statement along with the Connection object name. Here are the lines of code we need:

Dim dbCommand = OleDbCommand
dbCommand = New OleDbCommand("Select * From Titles", dbConnection)

Once you have a Connection object and a Command object, you can open the connection between your Visual Basic .NET application and the database you specified in the connection string. You do this by calling the Open method of the Connection object:

dbConnection.Open()

Now that you know how to create a data adapter, we need to look at how we can actually use it to populate our application with data. We'll look at two different ways to do this—the DataReader object, which provides us with a memory-efficient, read-only set of data, and the DataAdapter object, which provides us with a more conventional Dataset we can use for manipulating data from a data source.

Using the DataReader Object

The DataReader object is used to create a type of dataset that is read-only and only allows us to scroll forward through the records returned from the data source. We create a DataReader object by first declaring the object and instantiating it via a call to the ExecuteReader method of a Command object. For the sake of convenience, we perform both of these operations in one statement. Here is the line of code:

Dim dbReader As OleDbDataReader = dbCommand.ExecuteReader()

However, before we execute this statement, we need to declare a few more objects we'll need. Here are the declarations; we'll explain their use a little later:

Dim colValues() As Object
Dim numVals, x As Integer

The Command object, dbCommand, contains an SQL statement that is executed on the object dbConnection when the statement above is executed. The set of records returned by the SQL statement is stored in the dbReader object.

Before we can access the data, we need to learn a little bit about the data stored in each row. The colValues array we declared above is used to store data about the column values (fields) in the records returned by the ExecuteReader method. To store data in the array, however, we need to re-dimension it with the number of fields in each row. We can do that by using the FieldCount property of the DataReader object, like this:

ReDim colValues(dbReader.FieldCount)

Now we're ready to store the number of columns in a row in the variable numVals. We'll use this value to loop through the columns when we return the data stored in each row. But first, we have to actually read some data from the dbReader object. To do this, we call the Read method of the dbReader object.

dbReader.Read()

We'll discuss the Read method in more detail below.

The next line after we read data returns the number of fields in the row. The method that returns this value is GetValues. It is called with the colValues array as its argument.

numVals = dbReader.GetValues(colValues)

At this point, we're ready to loop through the records returned to the program. We can do this using a While loop. The condition we'll use for the test is the value returned by the Read method. This method returns True if the method is positioned at the next row of data to be read, and returns False if there is no more data to read.

We need to do two things to read the data from a returned row. We need to return the field (column) name and we need to return the value stored in the field. The method for returning a field name is GetName(n), where n is the column number. The method for returning a field value is GetValue(n), where n is again the column number. The following lines of code display a column name and the column value to the console (the column returned is the first column in the row):

Console.Write(dbReader.GetName(0) & ": ")
Console.WriteLine(dbReader.GetValue(0))

Of course, we want to take out the literal number as an argument to the methods and replace it with a variable.

We now have all the parts necessary to write a complete program to read and display all the records in a database. The following Console application returns all the records from the Titles table of the Biblio.mdb database:

Imports System.Data.OleDb

Module Module1

   Sub Main()
      Dim dbConnection As OleDbConnection
      Dim dbAdapter As OleDbDataAdapter
      Dim dbCommand As OleDbCommand
      Dim numVals, x As Integer
      dbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; " _
                & "Data Source = c:\databases\biblio.mdb")
      dbCommand = New OleDbCommand("select * from titles", dbConnection)
      Dim colValues() As Object
      dbConnection.Open()
      Dim dbReader As OleDbDataReader = dbCommand.ExecuteReader()
      ReDim colValues(dbReader.FieldCount)
      While (dbReader.Read())
         numVals = dbReader.GetValues(colValues)
         For x = 0 To numVals - 1
             Console.Write(dbReader.GetName(x) & ": ")
             Console.WriteLine(dbReader.GetValue(x))
         Next
      End While
      Console.Write("Press any key to quit")
      Console.Read()
   End Sub

End Module

The While loop loops through the records of the dbReader object while the For loop loops through the columns in each row.

The DataReader object provides us with a low-memory, fairly efficient technique for accessing a data source for the purposes of displaying the data in the data source. However, if we want to make changes to the data in the data source, we cannot use the DataReader method; we need to use the DataAdapter method instead.

Using the DataAdapter Object to Access Data in a Table

The DataReader object can only be used to scroll through the data returned from a data source. This is acceptable only in the most basic programs that need data access. For most your database programs in Visual Basic .NET, you will want more access to the data in a data source than read-only access. This means you need to use the DataAdapter object.

When you use the DataAdapter object, you use the object to connect to a data source. The DataAdapter object is then used to fill a dataset with data from the data source. After the dataset is filled, you use a DataTable object to access the individual rows in the dataset. The following paragraphs describe the coding for this scenario.

The first step, of course, is to declare the objects we'll need for our program. We need object variables for the following objects: Connection, Command, DataSet, DataAdapter, DataTable, DataRow, and DataSetCommand. We need string variables to hold our connection string and our SQL statement, and we'll need an integer variable to hold the number of rows returned when we fill the dataset. Here are the declarations:

Dim dbConnection As OleDbConnection
Dim dbCommand As OleDbCommand
Dim ConnString As String = "Provider = Microsoft.Jet.OLEDB.4.0; " & _
                           "Data Source = c:\databases\biblio.mdb"
Dim CmdString As String = "Select * From Titles"
Dim dbDataset As DataSet
Dim dbAdpt As OleDb.OleDbDataAdapter
Dim dbTable As DataTable
Dim dsCommand As OleDb.OleDbCommand
Dim dRow As DataRow
Dim numRows As Integer

The next step is to instantiate our objects:

dbConnection = New OleDbConnection(ConnString)
dsCommand = New OleDbCommand(CmdString, dbConnection)
bAdpt = New OleDbDataAdapter(CmdString, dbConnection)

Now we're ready to use the DataAdapter object to create a dataset. We first have to tell the DataAdapter object what command to use for filling the dataset, and then we can call the Fill method to create the dataset. The call to the Fill method looks different because the first argument to the method is the dataset name we are filling. When you call the Fill method, you can optionally store its return value, the number of rows in the dataset, in an Integer variable. Always do this because you can use that number when you want to write code that loops through the dataset.

Here's the code for filling a dataset:

dbAdpt.SelectCommand = dsCommand
dbDataset = New DataSet()
numRows = dbAdpt.Fill(dbDataset, "Titles")

Now we have a dataset, but we can't really do anything with it until we can access an actual row of the data. To do this, we need a DataTable object that we can use to build a DataRow object, which is where the data is stored. Here are the lines of code we need:

dbTable = dbDataset.Tables("Titles")
dRow = dbTable.Rows(0)

Finally, let's just make sure we actually have some data in the dataset. The following line returns the data from the first column in the row:

Console.WriteLine(dRow(0))

Using the DataAdapter Object to Add Data to a Table

Adding data to a database table is more commonly called inserting data into a table. This is because the SQL command that performs this operation is the Insert command. Inserting data into a table is much more involved than displaying data.

There are several steps to take when writing a program for inserting data into a table. Let's cover the procedure step by step.

The first step is to declare the variables you'll need. When you insert new data into a table, you have to create a new dataset, so we'll need a dataset variable. You also have to store the new data in a new row to insert it, so we'll also need a datarow variable. We also need an OledbCommand object. Finally, we need a string that stores the SQL statement we're going to use for the insertion. This string will need some explanation, but let's first show you the variable declarations:

Dim Changes as DataSet
Dim AuthorInsert As OleDb.OleDbCommand
Dim NewAuthor As DataRow
Const INSERT As String = "INSERT INTO Authors(Au_ID, " & _
                         "Author, YearBorn) VALUES(@Au_ID, " & _
                         "@Author, @YearBorn)"

In this example, we're using the Authors table of the Biblio database. The table has three fields: Au_ID, Author, and YearBorn. (Note: this field is modified from the original field, which is named Year Born; this is a problem because a lot of coding constructs don't like spaces, so we renamed the field using Microsoft Access.)

There are three parts to the Insert command in SQL. The first part indicates what table you're inserting into:

INSERT INTO Authors

The second part is a list of the fields into which you're inserting data:

INSERT INTO Authors (Au_ID, Author, YearBorn)

The last part includes the actual values you are inserting. However, when we declare the string we don't know what the values are going to be, so we use parameters in place of the actual values. Think of these parameters just like the parameters you use with function and subprocedure calls. To differentiate between a parameter and an actual field value, a parameter always starts with an ampersand (@). Here's the whole statement:

INSERT INTO Authors (Au_ID, Author, YearBorn) VALUES(@Au_ID, @Author,
@YearBorn)

The next step is to add an insertion command to the DataAdapter object:

dbAdpt.InsertCommand = AuthorInsert

This object not only has to store the command, it also has to know about the parameters that will be passed to it. You do this by calling the Parameter.Add method of the DataAdapter object, passing the method the name of the parameter, its data type, the size of the field, and the field the parameter corresponds to in the table. The code for this example is:

AuthorInsert.Parameters.Add("@Au_ID", OleDbType.BigInt, 10, "Au_ID")
AuthorInsert.Parameters.Add("@Author", OleDbType.VarChar, 50, "Author")
AuthorInsert.Parameters.Add("@YearBorn", OleDbType.Integer, 4, "YearBorn")

To insert data into a table, you actually first insert a new row into the table and then add the data. This step involves calling the NewRow method of the DataTable object, like this:

NewAuthor = dbDataset.Tables("Authors").NewRow()

Now we need to add data to the row object we're going to add to the table. To do this, we make separate calls to each field of the row, like this:

dNewAuthor("Au_ID") = numRows + 1
dNewAuthor("Author") = "Mike McMillan"
dNewAuthor("YearBorn") = 1957

We are finally ready to add this row to the Table object:

dbDataset.Tables("Authors").Rows.Add(dNewAuthor)

But this isn't the last step. The last step is to actually update the physical table with the new data. The object we call to make the change to the physical table is the DataAdapter object we used to create the data set. We also need to make sure there aren't any errors in the changed data set where we wrote the new row. We perform this task by calling the GetChanges method of the new data set, creating yet another data set, dstChanges in the sample below. If there are errors in the data set, which we determine by calling the HasErrors method, we won't update the table. If there are no errors, then we go ahead with the update. Here's the code fragment:

dstChanges = dbDataset.GetChanges()
If dstChanges.HasErrors() Then
    Console.WriteLine("errors in updated data set")
Else
    Try
        dbAdpt.Update(dstChanges, "Authors")
    Catch e As Exception
        Console.WriteLine(e.ToString)
    End Try
    Console.WriteLine("Row added")
End If

An exception is thrown if the update doesn't work. Otherwise, a simple message is written that a new row has been added to the table and the program continues.

Deleting a Row From a Table

Deleting a row from a table is comparatively easy. You simply call the Delete method on the particular row you want to delete. The following code deletes the fifth row of the Authors table, using the objects from the example above:

dbDataSet.Tables("Authors").Rows(4).Delete()

As with an addition, a deletion is not made permanent in a table until the Update method is called.


Appendix B: Visual Basic Reserved Words

AddHandler | AddressOf | Alias | And
AndAlso | Ansi | As | Assembly
Auto | Boolean | ByRef | Byte
ByVal | Call | Case | Catch
CBool | CByte | CChar | CDate
CDec | CDbl | Char | CInt
Class | CLng | CObj | Const
CShort | CSng | CStr | CType
Date | Decimal | Declare | Default
Delegate | Dim | Do | Double
Each | Else | ElseIf | End
Enum | Erase | Error | Event
Exit | ExternalSource | False | Finally
For | Friend | Function | Get
GetType | GoTo | Handles | If
Implements | Imports | In | Inherits
Integer | Interface | Is | Let
Lib | Like | Long | Loop
Me | Mod | Module | MustInherit
MustOverride | MyBase | MyClass | Namespace
New | Next | Not | Nothing
NotInheritable | NotOverridable | Object | On
Option | Optional | Or | OrElse
Overloads | Overridable | Overrides | ParamArray
Preserve | Private | Property | Protected
Public | RaiseEvent | ReadOnly | ReDim
Region | Rem | RemoveHandler | Resume
Return | Select | Set | Shadows
Shared | Short | Single | Static
Step | Stop | String | Structure
Sub | SyncLock | Then | Throw
To | True | Try | TypeOf
Unicode | Until | Variant | When
While | With | WithEvents | WriteOnly
Xor | #Const | #If...Then...#Else |


Appendix C: Operator Precedence

When several operations occur in an expression, each part is evaluated and resolved in a predetermined order called operator precedence.

When expressions contain operators from more than one category, they are evaluated according to the following rules. The arithmetic and concatenation operators have an order of precedence that is described below, and all have higher precedence than the comparison and logical operators. Comparison operators have higher precedence than the logical operators, but lower precedence than the arithmetic and concatenation operators. All comparison operators have equal precedence; that is, they are evaluated in the order, left to right, in which they appear. Arithmetic, concatenation and logical/bitwise Operators are evaluated in the following order of precedence:

  • Arithmetic/Concatenation Operators

    • Exponentiation (^)

    • Negation (-)

    • Multiplication and division (*, /)

    • Integer division (\)

    • Modulus arithmetic (Mod)

    • Addition and subtraction (+, -), String concatenation (+)

    • String concatenation (&)

  • Comparison Operators

    • Equality (=)

    • Inequality (<>)

    • Less than, greater than (<,>)

    • Greater than or equal to (>=)

    • Less than or equal to (<=)

  • Logical/Bitwise Operators

    • Negation (Not)

    • Conjunction (And)

    • Disjunction (Or)

When multiplication and division occur together in an expression, each operation is evaluated as it occurs from left to right. When addition and subtraction occur together in an expression, each operation is evaluated in order of appearance from left to right. Parentheses can be used to override the order of precedence and force some parts of an expression to be evaluated before others. Operations within parentheses are always performed before those outside. Within parentheses, however, operator precedence is maintained.

The string concatenation operator (&) is not an arithmetic operator, but in precedence, it does follow all arithmetic operators and precede all comparison operators.


Appendix D: Primitive Data Types

Type | Value Stored | Default Value | Size | Range of Values
Char | Unicode character | Character code 0 | 2 bytes | 0 to 65535
Byte | Integer value | 0 | 1 byte | 0 to 255
Short | Integer value | 0 | 2 bytes | -32768 to 32767
Integer | Integer value | 0 | 4 bytes | -2147483648 to 2147483647
Long | Integer value | 0 | 8 bytes | -9223372036854775808 to 9223372036854775807
Single | Real value | 0.0 | 4 bytes | ± 1.401298E-45 to ± 3.402823E+38
Double | Real value | 0.0 | 8 bytes | ± 1.79769313486231E+308 to ± 4.9406564584247E-324


Appendix E: Formatting Numbers

To give more precise control over the formatting of numbers for output, Visual Basic .NET provides several methods that are part of the Microsoft.VisualBasic namespace. These methods are:

FormatNumber
FormatCurrency
FormatPercent

The FormatNumber method allows up to five arguments. The first argument is the number you want to format. The number can be in the form of a literal value, a variable, or an expression. The second argument, which is optional, is the number of digits you want to display after the decimal point. The third through fifth arguments, all Booleans, are also optional. The third argument determines whether or not a leading zero is displayed in a number less than zero. The fourth argument determines whether a negative number is displayed in parentheses, and the fifth argument determines whether commas are used to group digits. If you don't supply any of the optional arguments to the method, it will display a number with grouped digits and any fractional part rounded to two decimal places.

Let's examine some examples of the FormatNumber method.

Dim num As Double = 130000.1256565
Console.WriteLine(FormatNumber(num))  ' Displays 130,000.13

This is the most basic form of the method and the way you will use the method most often. Here's another example.

Dim num As Double = -130000.1256565
Console.WriteLine(FormatNumber(num))  ' Displays -130,000.13

If we want to display a negative number with parentheses instead of a negative sign, as we would if we were writing an accounting application, we can modify the arguments to the method like this:

Dim num As Double = -.75
console.write(FormatNumber(num,,,Tristate.True))  ' Displays (.75)

Setting parentheses to display a negative number is the third argument in the method, so to skip over the other two methods we use commas. To set this argument, we have to use a special value called a Tristate value. We can set a Tristate value to True or False, with the third value (hence the "Tri" in Tristate) being the system default. The system default is set to the current locale's setting. In the United States, it is customary to display negative numbers with a negative sign, but in other countries it may be customary to use parentheses.

Another argument in the FormatNumber method lets us display leading zeros in fractional numbers. Using the example above, let's display -.75 as (0.75). Here's how we do it:

Dim num As Double = -.75
console.write(FormatNumber(num,,Tristate.True,Tristate.True)) ' Displays (0.75)

Finally, we can control how many decimal places we display in a floating-point number by manipulating the second argument. The default, as you've seen, is two digits, but we can specify other numbers. The example below displays the constant pi to 6 digits:

dim pi as double = 3.14159265358979
console.write(formatnumber(pi, 6))   ' Displays 3.141593

The FormatCurrency method has the same arguments as the FormatNumber method. The number it formats, however, is displayed as a currency value by displaying whatever currency symbol is defined in the system control panel. For most of you, this will be the dollar sign.

Here are some examples using the FormatCurrency method.

Console.WriteLine(FormatCurrency(123.45))
Console.WriteLine(FormatCurrency(-56.75))
Console.WriteLine(FormatCurrency(-2.34456, 3,,Tristate.True))
Console.WriteLine(FormatCurrency(.25,,Tristate.True))

The output from this code is:

$123.45
($56.75)
($2.345)
$0.25

The last method we can use for formatting is the FormatPercent method for percentage values. This method also takes the same number and type of arguments as the other two methods. The first argument (the number, numeric variable or numeric expression) is multiplied by 100 in order to convert it to a percentage.

The following examples demonstrate how to use the FormatPercent method:

Console.WriteLine(FormatPercent(.1))
Console.WriteLine(FormatPercent(1, Tristate.False))
Console.WriteLine(FormatPercent(.2359,2))

The output is:

10.00%
100%
23.60%

The second number demonstrates how you can format a percentage without displaying any digits after the decimal point. The third number illustrates how the method rounds numbers when you specify the number of digits to display after the decimal point.


Appendix F: Program Style, Formatting, and Documentation

Useful programs have very long lifetimes, during which they must be modified and updated. Good style and documentation are essential if another programmer is to understand and work with your program.

General Guidelines

Style is of benefit only for a human reader of your program — differences in style make no difference to the computer. Good style includes the use of meaningful identifiers, comments, and indentation of control structures, all of which help others to understand and work with your program. Perhaps the most important aspect of program style is consistency. If the style within a program is not consistent, it then becomes misleading and confusing.


Comments

Comments are extra information included to make a program easier to understand. You should include a comment anywhere code is difficult to understand. However, don't overuse comments. Too many comments in a program can obscure the code and be a source of distraction.

In our style, there are four basic types of comments: headers, declarations, in-line, and sidebar.

Header comments appear at the top of a class, method, or package and should include your name, the date that the code was written, and its purpose. It is also useful to include sections describing the input, output, and assumptions that form the basis for the design of the code. The header comments serve as the reader's introduction to your program. Here is an example:

' This method determines whether a given year is a leap
' year
'
' Written by: Your Name
'
' Date completed: 4/8/2002
'
' Input: System.Calendar for the IsLeapYear method
'
' Output: True if the year is a leap year; False otherwise
'
' Assumptions: Year is provided in DateTime format

Declaration comments accompany the field declarations in a class. Anywhere that an identifier is declared, it is helpful to include a comment that explains its purpose. For example:

' Class Constants
Shared Const FIRST As String = "Herman" ' Person's first name
Shared Const LAST As String = "Hermann" ' Person's last name
Shared Const MIDDLE As String = "G"     ' Person's middle initial
' Instance variables
Dim firstLast As String                 ' Name in first-last format
Dim lastFirst As String                 ' Name in last-first format
Dim studentCount As Integer             ' Number of students
Dim sumOfScores As Integer              ' Sum of their scores
Dim sumOfSquares As Long                ' Sum of squared scores
Dim average As Double                   ' Average of the scores
Dim deviation As Single                 ' Standard deviation of scores
Dim grade As String                     ' Student's letter grade
Dim stuName As String                   ' Student's name

Notice that aligning the comments gives the code a neater appearance and is less distracting.

In-line comments are used to break long sections of code into shorter, more comprehensible fragments. It is generally a good idea to surround in-line comments with blank lines to make them stand out. Some editors, such as Visual Studio. NET, also color comments automatically, which makes it easier to spot them on the screen. However, blank lines are still helpful because code is often printed on paper in black and white. Here is an example:

' Set label properties

resultLabel.Text = "Result: "
register.Text = "0.0"
entryLabel.Text = "Enter #:"
inputField.Text = ""

' Set button properties

add.Text = "+"
subtract.Text = "-"
clear.Text = "Clear"

Even if comments aren't needed, blank lines can be inserted wherever there is a logical break in the code that you would like to emphasize.

Sidebar comments appear to the right of executable statements and are used to shed light on the purpose of the statement. Sidebar comments are often just pseudocode statements from your responsibility algorithms. If a complicated Visual Basic .NET statement requires some explanations, the pseudocode statement should be written to its right. For example:

While (dataFile.Peek <> -1)  ' Loop while there is data to read

Because the page of a textbook has a fixed width, it is sometimes difficult to fit a sidebar comment next to a long line of code. In those cases, we place the sidebar comment before the statement to which it refers. Most computer screens can now display more characters on a line than fit across a page, so this situation is less common in practice. However, if lines of code become too long, they are hard to read. It is then better to place the sidebar comment before the line of code.

In addition to the four main types of comments that we have discussed, there are some miscellaneous comments that we should mention. Although we do not do this in the text, to conserve space, we recommend that classes and methods be separated in a compilation unit by a row of asterisks.

' ***********************************************************************

Programmers also sometimes place a comment at the end of a control structure to indicate which control structure is ending.

   Return noCorrect
End While    ' Goes with While (inFile.Peek<> -1)


Identifiers

The most important consideration in choosing a name for a field or method is that the name convey as much information as possible about what the field is or what the method does. The name should also be readable in the context in which it is used. For example, the following names convey the same information, but one is more readable that the other:

datOfInvc  invoiceDate

Although an identifier may be a series of words, very long identifiers can become quite tedious and can make the program harder to read. The best approach to designing an identifier is to try writing out different names until you reach an acceptable compromise—and then write an especially informative declaration comment next to the declaration.


Formatting Lines and Expressions

Visual Basic .NET allows you to break a long statement in the middle and continue onto the next line by ending the line with an underscore ( _ ). The split can occur at any point where it would be possible to insert an underscore without affecting the behavior of the code. When a line is so long that it must be split, it's important to choose a breaking point that is logical and reasonable. Compare the readability of the following code fragments:

outFile.WriteLine(" for a radius of " & radius & " the diameter of the cir" & _
                  "cle is " & diameter)
outFile.WriteLine(" for a radius of " & radius & " the diameter of the " & __
                  "circle is " & diameter)

When writing expressions, keep in mind that spaces improve readability. Usually you should include one space on either side of the = operation as well as most other operators. Occasionally, spaces are left out to emphasize the order in which operations are performed. Here are some examples:

If (x+y > y+z) Then
  maximum = x + y
Else
  maximum = y + z
End If
hypotenuse = Math.Sqrt(a*a + b*b)


Indentation

The purpose of indenting statements in a program is to provide visual cues to the reader and to make the program easier to debug. When a program is properly indented, the way the statements are grouped is immediate obvious. Compare the following two program fragments:

While (count <= 10)
num = CInt(in.ReadLine())
If (num = 0) Then
count += 1
num = 1
End If
out.WriteLine(num)
out.WriteLine(count)
End While

While (count <= 10)
   num = CInt(in.ReadLine())
   If (num = 0) Then
      count += 1
      num = 1
   End If
   out.WriteLine(num)
   out.WriteLine(count)
End While

As a basic rule in this text, each nested or lower level item is idented by two or three spaces. Exceptions to this rule are parameter declarations and statements that are split across two or more lines. The number of spaces you indent is a matter of personal preference. Many programmers indent two spaces; many more indent three or more spaces.

As we noted at the beginning of this appendix, the most important aspect of code formatting is consistency. You may frequently find it necessary to adopt the style of another programmer in order to update their code in a consistent manner. Even when you believe your own favorite style to be superior, resist the temptation to mix your own style with a different, existing style. The mixture is likely to be more confusing than either style alone.

Previous Section


Glossary

A

abstract
a modifier of a class or field that indicates that it is incomplete and must be fully defined in a derived class
abstract data type (ADT)
a class of data objects with a defined set of properties and a set of operations that process the data objects while maintaining the properties
abstract step
an algorithmic step for which some implementation details remain unspecified
abstraction
a model of a complex system that includes only the details essential to the perspective of the viewer of the system; the separation of the logical properties of data or actions from their implementation details; the separation of the logical properties of an object from its implementation
abstraction (in OOD)
the essential characteristics of an object from the viewpoint of the user
aggregate operation
an operation on a data structure as a whole, as opposed to an operation on an individual component of the data structure
algorithm
a logical sequence of discrete steps that describes a complete solution to a given problem computable in a finite amount of time; instructions for solving a problem or subproblem in a finite amount of time using a finite amount of data; a verbal or written description of a logical sequence of actions
allocate
to assign memory space at run time for use by an object
ALU
see arithmetic/logic unit
anonymous class
a class that does not have an identifier (a name) associated with it
argument
a variable, constant, or expression listed in the call to a method
arithmetic/logic unit (ALU)
the component of the central processing unit that performs arithmetic and logical operations
array
a collection of components, all of the same type, ordered on n dimensions (n >= 1); each component is accessed by n indices, each of which represents the component's position within that dimension
assembler
a program that translates an assembly language program into machine code
assembly language
a low-level programming language in which a mnemonic represents each of the machine language instructions for a particular computer
assertion
a logical proposition that is either true or false
assignment expression
a Visual Basic .NET expression with (1) a value and (2) the side effect of storing the expression value into a memory location
assignment statement
a statement that stores the value of an expression into a variable
asynchronous
not occurring at the same moment in time as some specific operation of the computer; in other words, not synchronized with the program's actions
atomic data type
a data type that allows only a single value to be associated with an identifier of that type
auxiliary storage device
a device that stores data in encoded form outside the computer's memory