See reading notes for Chapter 1 for discussion of the purpose of these reading notes.
As with all the
remaining chapters in the book, it is divided into Lessons where you
study features of the C programming language one topic at a time, and Application
Programs where you put together what you have learned into complete, useful
programs.
From now on, I will refer to the lessons by numbers preceded with
"L" (for example, L3.1 L3.2, L3.3, etc.) and application programs
by numbers preceded with AP (for example, "AP3.1, AP3.2, etc.")
Like Chapter 2, most Chapter 3 is central to C Programming. However, we will
defer one section until later in the course: we will skip over L3.9, and cover
it after Chapter 6. The book itself suggests this as an option.
So, you are not responsible for any material in Chapter 3.9 for now (and we
might
skip
this material altogether.)
The remainder of Chapter 3
contains very basic and useful information about
getting started with C programming. It is
pretty
well
written
and easy
to follow,
and
you should
read it through the other sections in their entirety.
You may want to have a computer terminal handy as you read, so that you can
try out the programs that are given in the text as you read about them. You
are more likely to succeed if you "play around" with the programs,
experimenting with them as you learn about the C language. You can find the
source code for these programs on the floppy disk that came with the textbook.
You might make a separate directory under ~/cisc181 called ~/cisc181/td99,
and then make a separate directory for each chapter, where you can play with
the code examples, for example, using the commands:
mkdir ~/cisc181/td99
mkdir ~/cisc181/td99/ch03
Note that to use these
programs on strauss, you may have to do two things:
^M characters
that seem to show up in files copied from DOS or Windows disks. You can do
this with the command:
dos2unix
L3_1.C L3_1.c
Note that this creates a new copy of the file with a lowercase .c extension;
this new copy is in the Unix file format.Fortunately, I have already done most of that for you: you can find a copy of the code from the book where I have already done the dos2unix stuff in the following directory on strauss:
~pconrad/public_html/cisc105/td99/code
For example, consider the subdirectory:
~pconrad/public_html/cisc105/td99/code/Chapter3/Lessons
When logged into strauss, you can use the following sequence of commands to go to that directory, look at the files there, and copy one of the files into your ~/cisc181/td99/ch03 subdirectory. I made one typing error, and I left it in to show that this is "normal". Note that I also use the "mv" command as a way to "rename" the file to a name that is more suitable for Unix. (I already did the copy to strauss and the dos2unix command, but I didn't rename the files—this wasn't as easy to do from a script—so you'll have to do that part yourself.) In addition to changing the .C to a .c (which is required by the C compiler), I chose a name without underscores, because I find the period more convenient to type than the underscore; otherwise, there is no special reason I got rid of the underscore.
|
These files are also available at the following URL:
http://udel.edu/~pconrad/public_html/cisc105/td99
Crucial Material: The following sections are crucial to your success in this course. Material from these sections will be useful not only in this course, but also in later courses as well. It definitely be on assignments and exams.
Sections 3.3 and 3.4.
Important Material: The following sections contain concepts and terminology that are important enough that they might appear on a quiz or exam, but are not as important as the information in the "Crucial Material" list.
Everything else in Chapter 3 except 3.9 (we are deferring 3.9 until later in the semester, or perhaps skipping it altogether).
This is an excellent treatment of this topic. Some key concepts you need to get from this lesson:
Of particular importance are point #3 on page 78-79, and point #6 on page 80. Please read these items over a couple of times slowly. If you really study these two paragraphs and understand them, it will help you immensely in understanding what is going on with C and C++ programs.
A comment about Tan and D'Orazio's rules for variables in Table 3.1. Most of what is in Table 3.1 is exactly right; the table gives you the C language rules for naming variables. They slipped in one "editorial comment" however, and its one that I think needs some clarification. The authors suggest that while use of "uppercase and mixed-case characters" is:
"allowed; however many programmers use lowercase characters for variable names and uppercase for constant names. Differentiate your identifiers by using different characters rather than different cases"
For the most part, I agree with this comment. For example, all of the following variables are legal, but are not examples of good programming style:
int wIdTh, HEIGHT, dEPTH, Volume;
The problem with these variables is that there is a "convention" that variables
are in lowercase. When you violate that convention, you make programs that
are more difficult to maintain for future programmers.
However, there is one exception to the rule of "all lowercase variables" that is worth mentioning: camel notation. As an alternative to using the underscore, as in:
int building_height, building_depth, building_width;
camel notation using mixed case, where the variable starts with a lowercase letter, but each new word uses uppercase, as in:
int buildingHeight, buildingDepth, buildingWidth;
Many programmers consider this perfectly acceptable style and some prefer it to the user of underscores. It is advisable, though, to either always use underscores, or always use camel notation within a given program; mixing the two makes the code harder to maintain.
Lesson 3.2 focuses on two topics:
Here are some observations about each of these topics:
Constant Macros (also known as "pound defines")
A key point in this lesson is the use of #define for defining what are called "constant macros". Constant macros help you avoid "magic numbers" in your program. A "magic number" is some value that appears in your code without explanation and just somehow "makes the code work".
A good example of a magic number can be found in the scoring of Diving at the Athens 2004 Olympics. The web page for the 2004 games indicates that the final score for a dive is determined from the judges scores by the following algorithm, taken from the official Olympic web site at: http://www.athens2004.com/en/DivingRules
During the Olympic Games, World Championships and World Cups, the judging panel consists of seven judges. When the seven judges give their scores, the highest and the lowest scores are cancelled. The five remaining scores are added up and the total is then multiplied by the difficulty factor for each dive and then by 0.6 in order to arrive at the final result. If two competitors get the same total score they are considered tied.
Notice that the "magic number" 0.6 appears in this formula without explanation. There is probably some historical reason for this magic number 0.6, however to a casual observer, it appears to be an arbitrary choice. Presumably, at some point in the future, if judging criteria are changed, this number might be changed. As a result, we do not want to "hard code" the number in our program.
Therefore, to make this number (which might change later) stand out in the code, we instead put it at the very top of our program. We also give it a name, to indicate where it came from, and help the reader of the code understand what it represents.
/* from rules on Athens 2004 Olympic web page, http://www.athens2004.com/en/DivingRules */ #define DIVING_SCORE_SCALING_FACTOR 0.6
Then, in our formula, we refer to the number by this name:
/* from rules on Athens 2004 Olympic web page, http://www.athens2004.com/en/DivingRules */ finalScore = DIVING_SCORE_SCALING_FACTOR * degreeOfDifficulty * total;
Another good example is a tax rate. The sales tax varies from state to state. In Delaware, as of this writing (2004) it is zero! However, in Pennsylvania, it is currently 6%. If we want to write software that will work in both states, we can put a definition like the following at the top of our program:
#define SALES_TAX_RATE 0.00
In a formula, we would write:
total = price + (price * SALES_TAX_RATE);
(Here, the parentheses are unnecessary because of order of operations, but it is good style to include them anyway; it makes the formula easier to read.)
In Pennsylvania, we would then only have to change the one line of the program to:
#define SALES_TAX_RATE 0.06
The real advantage of this becomes clear when you have a large piece of software where SALES_TAX_RATE appears not just once, but several times in the code. Then, when you need to change the sales tax, you can just change it in one place, rather than in many different places.
Details of Format Specifiers (e.g. "%8.3f", "%+5d", etc.)
A key point is that one on p. 93: "Does all this need to be memorized? No."
The key word there is all. There is, in fact, a short list of facts you should memorize, and here it is:
Later on, we'll learn some fun facts about %c and %s, which are used with
the char data type, and the char * data type, respectively, but that's for
a later chapter.
This lesson is very important stuff! Some things to particularly get out of this are:
Items 7 through 10 and Table 3.4 are particularly helpful in learning how to debug a certain type of error. I recommend that you read over them.
However, one thing to note: the textbook
suggests that a division by zero error will likely produce an error message
about an "overflow". On strauss, that is not necessarily the case. Instead,
division by zero with integers will likely give a "floating exception" (strangely
enough), while division by zero with the double data type produces a value
that prints out as "Inf" (i.e., infinity.) Here is some sample code, and a
sample script:
> cat divByZero.c
/* divByZero.c P. Conrad 9/15/04 */
int main(void)
{
int a=5;
int b=0;
int c;
c = a/b;
printf("Done; c=%d\n",c);
}
> cc divByZero.c
> ./a.out
Floating exception
> cat doubleDivByZero.c
/* divByZero.c P. Conrad 9/15/04 */
int main(void)
{
double a=5.0;
double b=0.0;
double c;
c = a/b;
printf("Done: c=%lf\n",c);
}
> cc doubleDivByZero.c
> ./a.out
Done: c=Inf
>
|
This is another excellent writeup by the textbook authors about some very important topics. Please read this!
Some key topics:
j = i++; vs. j
= ++i;)j = i++;)
One place where the textbook is slightly inconsistent
One slight inconsitency that I should point out though, so you won't get confused:
item 16 on page 109 says that "if all arithmetic operators are of equal precedence
in an arithmetic expression, the leftmost operator is executed first". That
is true most of the time. However, if you look at the details in item 19 on
page 110, you get the full story. Look just above table 3.6, where it explains
that "The associativity specifies the direction of evaluation of operators
with the same precedence." Note that while most operators associate L to R
(meaning that item 16 is correct), a few associate R to L, including:
unary + and - (positive and negative operations) |
This section covers the details of the scanf statement. Some things to recall about the scanf operator:
I love the fact that this section comes early in the text. Most textbooks
put this topic very late in the book. The fact that this topic comes so early
in Tan and D'Orazio is one of the things that led me to select this book for
our course.
Reading data from files allows us to process large volumes of data in the programs
we write this semester—much
more than would be reasonable for us to type in from the keyboard. For now,
we will only read in a small number of items from the data files we work with.
However, once we learn while loops and for loops (Chapter 4), we can process
files containing hundreds, thousands, or even millions of data items with only
a few lines of code.
However, I do have one quibble with the approach that Tan and D'Orazio take: they introduce files before they introduce the if statement. As a result, they omit a very important aspect of the discussion of working with files: the fact that after you do an fopen() operation, you should always check the result of that operation before proceeding.
For example, the example in Lesson 3.6 shows the following source code. See what happens if the file C3_6.IN doesn't exist (I rename C3_6.IN to the name FOO just to make sure the file doesn't exist when we run the program the second time.)
> ls
C3_6.IN L3_1.c L3_6.c L3_6a.c a.out
> cat L3_6.c
/* Lesson for 3_6 */
#include <stdio.h>
void main(void)
{
double xx ;
int ii, kk;
FILE *inptr;
inptr=fopen ("C3_6.IN","r");
fscanf(inptr,"%d",&ii);
fscanf(inptr,"%d %lf",&kk,&xx);
fclose(inptr);
printf("ii=%5d\nkk=%5d\nxx=%9.3lf\n",ii, kk, xx);
}
> cc L3_6.c
> ./a.out
ii= 36
kk= 123
xx= 456.780
> mv C3_6.IN FOO
> ./a.out
Segmentation fault
>
|
The "segmentation fault" results because the variable "inptr" is a pointer variable. If the fopen() call is not successful in opening the file, then using "inptr" in a subsequent fscan operation results in a segmentation fault (access through a bad pointer.) We'll learn more about segmentation faults in a later chapter, but for now, even if we are not sure what a segmentation fault is, we can see that it is not a very "user-friendly" error to run into.
Here's a better approach. The changed code is in bold face type. We need to use an "if" statement to check whether "inptr" is equal to the value NULL (this is how fopen() tells us that the file open operation failed.) This allows us to make a much nicer error message when the file doesn't exist (or for whatever other reason, cannot be opened.)
> ls
C3_6.IN L3_1.c L3_6.c L3_6a.c a.out
> cat L3_6a.c
/* Lesson for 3_6 */
#include <stdio.h>
void main(void)
{
double xx ;
int ii, kk;
FILE *inptr;
inptr=fopen ("C3_6.IN","r");
if (inptr==NULL)
{
printf("Error: could not open file\n");
exit(-1);
}
fscanf(inptr,"%d",&ii);
fscanf(inptr,"%d %lf",&kk,&xx);
fclose(inptr);
printf("ii=%5d\nkk=%5d\nxx=%9.3lf\n",ii, kk, xx);
}
> cc L3_6a.c
> ./a.out
ii= 36
kk= 123
xx= 456.780
> mv C3_6.IN FOO
> ./a.out
Error: could not open file
> mv FOO C3_6.IN
> ./a.out
ii= 36
kk= 123
xx= 456.780
>
|
This section covers use of the math library (e.g. functions such as sqrt() for square root, fabs() for floating point absolute value, sin(), cos(), for sine and cosine, etc.
It also covers the range of various numeric data types other than plain old int, float and double.
Using the math library
When you use the math library on strauss, when you compile, you need to include "-lm" on the cc compile statement. For example, consider the example program in Lesson 3.7. Here is what happens when you try to compile that without the -lm option on the cc compile line, and then with the -lm option on the compile line:
> cc L3_7.c Undefined first referenced symbol in file exp L3_7.o log10 L3_7.o sqrt L3_7.o sin L3_7.o log L3_7.o pow L3_7.o ld: fatal: Symbol referencing errors. No output written to a.out > cc L3_7.c -lm > ./a.out x= 3.0 y= 4.0 a=sin(x) = 0.1411 b=exp(x) = 20.0855 c=log(x) = 1.098612289 d=sqrt(x) = 1.7321 e=pow(x,y) = 81.0000 f=sin(y)+exp(y)log10(y)*sqrt(y)/pow(3.2,4.4)= 53.8341 g=log(x) = 1.098612309 > |
Without the -lm on the command line, the math library is not linked in, and the linking phase of the compiler can't find any machine language code for the routines such as sin(), exp(), etc. Hence, the error message tells us that these are "undefined symbols". When we include -lm, then everything works fine.
Other numeric data types (other than int, float and double)
You don't need to memorize all of the ranges on p. 129, especially since they are system dependent: they may be different on strauss from on any other computer you may encounter in "later life".
However, you should memorize the following facts:
Also, in addition to short int, int, and long int types, there is are two other integer data types in C not covered in your textbook (at least that I have found): "signed char" and "unsigned char", which can be used to store an 8 bit integer value (signed or unsigned.) This type is often used in programs that process multimedia data (e.g. GIF, JPEG, MP3, MPEG, WAV, etc.) to process binary data one byte (8-bits) at a time. We probably won't cover that topic in this course, but I want you to at least have "heard" of it.
This section gives a great overview of why we would want to write data directly to a file. I'll add only a few comments:
printf("Error: some
bad thing happened\n");
it is considered better style to write :
fprintf(stderr,"Error: some
bad thing happened\n");
We are omitting this section until later in the semester. Look for notes on this chapter in the document that goes along with chapter 6.
This is a very good program to look over. One thing to note however: the source code shows:
printf (" \n\
First triangle area = %6.2f \n\
Second triangle area = %6.2f \n\
Third triangle area = %6.2f \n\
Fourth triangle area = %6.2f \n",
area1, area2, area3, area4);
|
This won't compile on strauss with either gcc or cc. Instead, change it to:
printf (" \n"
"First triangle area = %6.2f \n"
"Second triangle area = %6.2f \n"
"Third triangle area = %6.2f \n"
"Fourth triangle area = %6.2f \n",
area1, area2, area3, area4);
|
Note that in the corrected version, you don't see any commas after any of the character strings except for the last one. In C, when you have two character strings that are adjacent (separated only by "white space" like spaces and newlines) the compiler concatenates these together (attaches them to each other). The comma after the last string is the one that separates the format string (the concatenation of all the strings) from the list of variables.
This is another good program to look over. One thing to note: this program repeats the formula for converting Celsius to Fahrenheit several times. At this stage in the semester, that's ok. Later on, however, after we've covered functions in more detail (i.e. following the treatment of Chapter 5), it would be considered a major style error to not create a "function" for this formula so that you don't need to repeat this formula over and over. In fact, if your instructor has already covered functions, you might consider how a program like this could be rewritten to look much simpler by using a function to do the conversion.
This is another good example program to look over. Again, there is one small change needed to make this program work on strauss. Instead of:
printf ("\n\
Block number Force required for movement(KN)\n\n");
|
You should use the following:
printf ("\nBlock number Force required for movement(KN)\n\n");
|
The following exercises are definitely recommended, and are certainly fair game for exam questions:
float cisc105_expectedGrade, phys207_expectedGrade;
...
cisc105_expectedGrade = 3.7;
phys207_expectedGrade=3.3;
You are not responsible (at this time) for exercises 1-5 on page 148 unless/until told otherwise by your instructor.
All of the application exercises are good practice for you. A few of them may show up i your labs/projects. You are advised to look over the others, as they might make an appearance (in simplified form) as exam questions.
(end of reading notes for Chapter 3).
These reading notes are based on C Programming for Engineering and Computer Science, by H. H. Tan and T. B. D'Orazio, Copyright 1999 by WCB McGraw-Hill. The notes themselves are Copyright 2004, by Phillip T. Conrad, All Rights Reserved.
Prof. Conrad takes sole responsibility for any views or opinions expressed in these notes. Such view and opinions do necessarily reflect those of the University of Delaware, its Department of Computer and Information Sciences, or any other organization.