Programming tips and some time saving features of the bash shell
Lab 4
CSCI 112
Goals:
Learn some programming tips that will make
implementing the programs easier.
Learn some simple features of the bash shell that will save you lots of
time typing
Program 2 clarification:
In lecture on Monday I used the Movie
class as an example of creating a class.
Unfortunately, because the course has not covered dynamic memory enough
to use in this assignment, you have to do something different then what
I presented.
Once you know about dynamic memory (the next assignment) you can call
the constructor for a Movie class after you read in the field values
(title, year, rating). However, in this assignment you need to
construct the Movie objects
before
you read in the field values.
The Movie_db class contains an array of 100 Movie objects. This
array has to be initialized before the ratings are read. The
bottom line: Movie should have a constructor that takes no
arguments. Instead of initializing the fields in a constructor
for class Movie, write a non-constructor function that initializes the
fields:
from Movie_db.h
Movie m_movies[MAX]; //
declare an array of MAX (100) Movie objects that are not initialized.
When a movie is read, it must be
inserted
into this array. But since this is already an array of Movie
objects, you have to update one of the non-used objects. Here is
the pseudo-code:
bool Movie_db::insert_rating(string
title, int year, int rating)
{
look through the non-empty movies in
the movie array
if you find one that has the same title and year as passed into
insert_rating() update its rating
m_movies[match_index].update_rating(rating);
if you don't find it, and there is an unused Movie in the array,
initialize that movie to the title, year, and rating passed in
m_movies[num_movies].initialize(title, year, rating);
update num_movies
}
Preventing files being
included multiple times
Consider the following files:
main.cpp
#include "bar.h"
#include "foo.h"
bar.h
#include "foo.h"
foo.h
class foo {...};
The preprocessor replaces each
#include
with the contents of the file being included. In this example,
when the file main.cpp is preprocessed, the contents of foo.h will
replace the #include "foo.h" in both main.cpp and bar.h. And the
#include "bar.h" in main.cpp will be replaced by the contents of
foo.h. The files generated by the preprocessor will look like
this:
bar.h
class foo {...}; // this came from replacing the #include "foo.h" with
the contents of foo.h
main.cpp
class foo {...}; // this came from expanding the #include "bar.h"
class foo{...}; // this came from expanding the #include "foo.h"
Now when main.cpp is compiled, there will be a compilation error
because class foo is multiply defined. The problem is that foo.h
was included multiple times.
You can prevent this problem by putting the following in EVERY .h file
you write (substitute the filename
of the current file for FOO):
#ifndef FOO_H // this
must
go on the first line of your file
#define FOO_H
contents of file
#endif // this must go on the last line of your file
How this works.
Many students have a hard time
understanding how the above mechanism works, and since it can be used
without understanding how it works, I'm not going to spend time
lecturing on how it works. I provide a brief description here for
those of you who are sill curious, and I will be happy to discuss how
it works anyone who is interested.
The directive ifndef stands for "if not defined." We can read the
above directives like this:
if the variable FOO_H is not
defined
this is the #ifndef FOO_H
{
define the variable
FOO_h
this is the #define FOO_H
rest of file goes here
}
this is the #endif
When the preprocessor starts to
process
a new .cpp file, there are no variables defined (its table of defined
variables is empty). Each time it encounters a #define directive,
it put that variable in its table.
The first time a .h file is read, the variable (by convention we use
FILENAME_H, for the file filename.h) associated with that file is not
in the table of defined variables, and thus the preprocessor enters the
body of the if-not-defined statement and reads everything in the file.
The next time that .h file is read, the variable associated with that
file IS in the table of defined variables and thus the preprocessor
goes to the end of the if-not-defined statement (that is, the #endif)
without reading everything in between.
The result is that the preprocessor will only ever read each .h file
once and you won't get multiply defined errors.
Incremental Programming
When learning to program in a new language, the
compiler
can provide the most help if you write your programs
incrementally. In other words, write a few lines of code and then
compile, and make sure your program works. If you have only
written a few lines of code, it will be much easier to figure out the
errors the compiler generated, and it will much easier to track down
logic errors.
Here is a game plan for writing program 3 incrementally:
- Create a makefile as describe in lab
3 or copy my Makefile.
- Create the Movie and Movie_db classes with
nothing in them,
just empty classes.
- Add the code described above to prevent multiple includes.
- Put in all the #include directives
(movie_db.h needs to include movie.h, main.cpp needs to include
movie_db.h)
- Write a simple dummy main() function (I
usually just have my
dummy main print out "hello
world")
- Compile all 5 files. Fix all errors
until your program
prints out "hello world."
- Write the code in main.cpp to read in the
data. Write
temporary code to write out the data so you can be sure your program
reads the data correctly.
- Compile and test your program with several
different data sets,
fix all problems now.
- Write, compile, and test class Movie
(implement all the functions and call them from main()).
- Write, compile, and test class Movie_db
- You should be done at this point.
Programming incrementally will save you lots and lots of time even
though you have to write some code you eventually will delete (e.g.
printing hello words, and printing out the input from main
Making bash your default shell
On the department's computers,
bash is not the default shell.
The choice of shells is a matter of personal preference. bash is
currently one of the most popular shells, and is available on many
platforms including Windows (most varieties), Linux (all varieties),
UNIX (all varieties), OSX.
If you make bash your default shell, you can use the features described
below. Chances are that bash will work just like your current
shell, and you won't notice any difference, except that bash allows you
to use the features described below.
To make bash your default shell:
Log on to cougar using a
secure-shell (ssh) (note: ssh is similar to telnet but encrypts
your password so no one can steal it)
$ ssh cougar.ecst.csuchico.edu
Now execute the command chsh (change shell)
$ chsh /bin/bash
It will ask you for your password, and then change your shell.
After about an hour, every time you log onto a department machine, your
default shell will be bash.
In the mean time, you can start
the bash shell manually by typing:
$ /usr/bin/bash
bash profile
When bash starts, it executes the
file .bash_profile in your home directory (unless something is screwed
up
with your preferences, see below).
You can customize bash so it behaves the way you like it by putting
commands in .bash_profile. There is an example of such a command
below
in the section of command line editing.
Another thing you can add to .bash_profile is called an alias. An
alias is another name for a command. For example, if your 112
directory is:
~/classes/f05/112
to go to this directory you would have to type:
cd ~/classes/f05/112
if you do this a lot, you can create an alias in .bash_profile that looks
like this
alias 112="cd ~/classes/f05/112"
now when you type 112 at the command prompt you will go to your 112
directory. I set up dozens of alias to make it easy to navigate
my file structure.
Another very helpful alias is:
alias ls="ls -F"
The -F causes ls to show all directories with a "/" at the end and all
executables with a "*" at the end. Now when you type "ls"
you will get "ls -F"
I usually add the
following line to the top of my .bash_profile
echo
"hello from ~tyson/.bash_profile"
This will be executed
every time the bash_profile is read. It simple
writes the screen to the terminal. This way I can make sure that
my .bash_profile is read when I expect it to be read.
Turns out the above is a bad idea.
While it is handy in that it makes it clear when your .bash_profile is
read, it causes sftp and scp to fail.
If your .bash_profile is not being executed automatically, the problem is
probably with your windowing system settings. You can manually
run your .bash_profile by typing
$ source .bash_profile
Command history
bash keeps track of all the
commands you type in. You can see a list of your recent commands
using the history command
$ history
each command has a number:
...
401 cd lab6_files
402 ls -l
403 pico hello.cpp
...
you can execute one of the commands again by
typing ! followed by the number. For example,
$ !402
would be the same as typing "ls -l"
Command line editing
Commands in the command history
can also be edited
Use the up/down arrows to move up and down through the list, use the
left/right arrows to edit a command
For example, assume that the last command was "pico hello.cpp" if you
press up-arrow, "pico hello.cpp" will appear on the command line.
You can edit this to be "pico hello.h" by deleting the cpp and
inserting an h
If you are a vi or vim user, you can set the mode of command line
editing to use all the vi edit keys (e.g. i for insert, k for
up). Put the following in your .bash_profile (ONLY DO THIS IF YOU ARE
A VI USER)
set -o vi
There is also an emacs command line edit mode (ONLY DO THIS IF YOU ARE
AN EMACS USER)
set -o emacs
File completion
bash has a feature to save typing
filenames. When you press the <tab> key, bash matches what
you have typed to all the files in the currently directory. For
example:
$ pico m<tab>
will automatically fill in
$ pico material_list.cpp
IF material_list.cpp is the only file in that directory that starts
with m. Assume that is another, material_list.h. then we
would get
$ pico material_list.
and you would have to fill in the "h" or type "c<tab" to get the
.cpp.
This is a real time savor, it is worth learning how to use.
More about shells
The bash shell (and other shells)
can do a whole lot more. There is an entire programming language
at your finger tips. I recommend you buy the O'Reilly book, Learning the bash shell to learn
more about bash.
Exercise 1:
Try the above bash features.
Exercise 2:
Work on program
2