Review
- Pointers
- Pointer value, pointer variable
- Semantics: reference to a value; address in memory.
- Syntax: Declare pointer variable, address-of variable, dereference pointer.
- Memory
- Lifetime of variable.
- Global
- Stack
- Heap
- Dynamic variables (on heap)
-
new
anddelete
- Dynamic structures work the same as variables of
scalar types:
new_struct.cpp
. - Dynamically-allocated array
storage:
new type[size]
anddelete []
(Note: We used dynamically allocated arrays to implement array lists. An array list is not an array; it's really an object. An array list contains the data storage + a current size + current capacity + associated operations.)
- For every
new
that executes, there must be adelete
. - Allocating large contiguous blocks of memory can be a problem. How can we run out of memory?
Remember the Big Ideas.
-
- Abstraction: client,
implementer, interface
(abstraction barrier).
- The bigger and more complicated the problem, the more important this is!
- You've learned to do this with functions. The caller of a function is not supposed to know the details of how a function works; the implementation of a function does not know the details of what its callers are up to.
- You've been using data abstraction as a client for built-in data types: you don't actually know how integers and floating point numbers are stored, but you use them just fine. A data abstraction has a (secret) representation for some kind of data.
- You implemented data abstaction with classes.
Linked Structures
Allow us to represent relationships. Think LinkedIn, Facebook friends, family trees, etc.Solving an immediate problem
- Storing a list of items whose length is not known in advance.
- Dynamic arrays are great, but there's all the copying,
and usually you end up with wasted space. (You could solve
the extra space problem by copying again, but …)
- Can we have a data structure that uses exactly the amount of memory we need? At all times. Yes!
Linked lists
- What are they?
- How can I get one?
- How can I store one?
- How can I use one?
A linked list of some data type is an ordered collection of data elements. A list is eitherAbstractly, we can have a list of 0 integers, 1 integer value, 200,000 integer values. Similarly, we could have a list of strings or snakes or fish or any other data type.
- the empty list or
- a non-empty list node, which is a pair of two things:
- A data element of the appropriate type, the first element of the list; and
- A linked list of the rest of the elements, the rest.
Above definition is recursive, i.e., it refers to itself!
Metaphors:
- Balloon and tow trucks
Source - Anchor and boats
Exercise:
You and a group of friends pretend you're tow trucks, get a balloon. One friend is just a hook (they hold on to the beginning of a list). Build:
- An empty list.
- A list with just the number 3 in it.
- A list with 2 and 3 in it.
- A list with -1, 2, and 3 in it.
Devise an algorithm for searching the list. First search for 2. Then search for 42.
Be careful: Tow trucks have minds of their own, and if they aren't hooked up to something, they just drive away in search of adventure.
Box and pointer diagrams
Because we don't always have friends around.We can represent the empty list as ∅
A variable that holds a non-empty list would be drawn like this:
A C++ StringList
Interface
A new game:Below is a simple string list API (application programming interface) consisting of a new type, called
We're going to approach this new data structure a little differently. For dynamic arrays, we showed you how to make one, and then how to use it. For linked lists, we're going to work the other way.This time, we'll say “What if we had something that behaves in such-and-such a way? What could we do with it? How would we use it?”
StringList
(whose precise definition is left
abstract), and a set of six functions client code can use to
make an manipulate lists. There is also some documentation in
this file.
When programming a real project (and your own programming projects would certainly qualify), it is common to break the problem up into pieces (Big Idea #3) that represent reusable functionality (Big Idea #2). For example, an application that needed to represent lists of strings (maybe it's a math game) could benefit from a general string list package that could be used throughout the application and in other applications, too. To accomplish this, we design an abstraction (Big Idea #1) that has an interface all client code can use.
We want the interface to be reasonably general to foster reuse, so it doesn't contain anything specific to a particular application, for example, the game probably has a GUI, but the list code doesn't know anything about that.
An abstraction that represents a data structure and its operations is called a data abstraction. That is, a data abstraction consists of one or more abstract types and a set of operations (functions) on values of the abstract type(s).
An StringList
Client
In the real world, picking up existing code and figuring out how
to use it based on its API is a common activity: it's much more
common than programming everything yourself — in fact, you
almost never write a whole application from scratch. Also, when
designing an abstraction, it's usually a good idea to think
through and sketch some simple use cases.
What kinds of things can we do with a list? What have you done with sequences of values before?
- Reducers: Take a sequence of values and produce a single value. Add up numbers. Find average number, max, min, concatenate all strings. Length. Printing a list takes a list and prints it, producing no value.
- Transducers: Take a list and produce a new list (often called mapping). Convert each element to lower case. Double each element. Copy. Concatenate two lists element by element.
- Filters: Take a list and produce a list with
only some of the original elements. For numbers: Even values,
odd values, values over some value (over average). For
strings: strings over some length, words begining
with
'r'
. - Generators: produce a list from scalar values. Numbers between 1 and n, factors of n, powers of 2 less than 1,000,000. Read in a list from a file. Names of all the files in a directory.
Let's start with reading in a list, printing a list, and summing up the members of a list. To build a list, you start with an empty list, and then you prepend things onto it:
Results in:StringListNode *family = empty(); numbers = prepend("ma", numbers); numbers = prepend("pa", numbers);
+------+--+ +------+--+ | "pa" | .+---->| "ma" | .+---->@ +------+- + +------+- +
Notice that you build a list up back to front, because whenever you add a new element, it goes on the front.
Reading a list of arbitrary length is extremely easy! Suppose we want to read a list of strings from the keyboard:
string name; StringListNode *nameList = empty(); for (cin >> name; !cin.eof(); cin >> name) ageList = prepend(name, nameList);
How would you write a readFile
function that takes
a string giving the file name and returns a list of the string
values in the file (assume the file only contains strings)?
Print the list. Sum the elements of the list. Copy a list. Produce a list of strings from a to b.
Here is a client of string lists that one might make to test your list functions. The idea would be that for each list function you add, you could add a command to your interactive test loop to run that function.
Exercises:
For these tasks, you are writing client code. You will not manipulate any pointers or structs directly. Notice the interface deals only with strings and string lists.For each problem, begin by writing a stub, that is, write a function definition whose body is the most trivial thing possible. If you were writing code, you could compile and even begin some testing just using the stub. It's also good practice for exams (on an exam, you wouldn't write the stub, but you should be very fast at getting the function header right).
Here are some solutions for your reference:
- Using the above interface, write a function that takes an
StringList
and prints its elements.Write it with a loop. But then try to see if you can think of a way to to do it without a loop.
List reducers:
- Write a function called
concatList()
that takes an string list as a parameter and returns the concatenation of the elements in the list. Use recursion.- [For IntLists] Write a function called
sumList()
that takes an integer list as a parameter and returns the sum of the elements in the list. Use recursion.- Write a function called
length()
that takes an string list and returns the number of (non-empty) elements in the list. Use recursion.List transducers:
- Write a function called
copy()
that takes an string list and returns a new string list that is a copy of the input list.- Write a function called
mapPluralize()
that takes an input list and returns a new string list each of whose elements is the correpsonding element of the input list with the letter's'
attached to the end. Use recursion.- [For IntLists] Write a function called
mapIncrement()
-- that takes input list and returns a new string list -- each of whose elements is the corresponding element of the -- input list plus one. Use recursion. --- Write a function called
mapSuffix()
that takes an input list and a string suffix and returns a new string list each of whose elements is the corresponding element of the input list with the given suffix attached. Use recursion.- [For IntLists] Write a function called
mapIncrementBy()
-- that takes input list and an integer increment vale -- returns a new integer list each of whose elements is the -- corresponding element of the input list plus the given -- increment value. Use recursion. --- Write a function called
pairwiseConcat()
that takes two string lists and returns a new string list each of whose elements is the concatenation of the corresponding elements of the list parameters. If the two input lists are not the same length, treat the shorter list as if it were padded out with empty strings to be the same length as the longer list. (You do not need to modify the list to do this.) Use recursion.List filter:
- Write a function called
filterInitial()
that takes a string list and a character and returns a new string list containing only the strings that start with the give letter. Use recursion.- [For IntLists] Write a function called
filterPositive()
that takes an integer list and returns a new integer list whose elements are the positive elements of the input list. That is, the output list is a copy of the input list with negative and zero elements removed. Use recursion.List generator:
- [For IntLists] Write a function called
range()
that takes two integers,low
andhigh
and returns a list of the integers in the interval [low
…high
) (the list includeslow
but nothigh
). Iflow
≥high
, the resulting list should be empty. Use recursion.StringListUtils.cpp
.
Act these out. Some people represent list nodes, others represent activation records. Activation records track their variables and where they are in the function at any given time. List nodes track their data value(s) and next pointers.
A StringList
Implementation
Another List Implementation
An enormous benefit of good data abstractions is that the implementer is free to change the impelmentation as long as they continue to support the published interface. It is therefore possible to have multiple implementations of a data abstraction, each optimized for different situations (such as making certain operations faster at the expense of others for certain applications). Client code can use any faithful implementation without any change whatsoever.
Here is an implementation of integer lists that does not
use NULL
to represent the empty list.
A StringList
Implementation
There is a saying in computing that every interesting program
contains at least one list structure. Lists are very common,
and we've seen one way of implementing them — there are
others, and Comp 15 uses a more traditionally
imperative/object-oriented approach.
Because they are so common, C++ has a list implementation as
part of its Standard Template Library. This implementation
works for any type of data. To get a sense of how this might be
possible, consider the StringList
module shown
below. Can you see how similar it is to
the StringList
module? Could you write a program that
takes the name of a type, say T and outputs a correct
TList.h
and TList.cpp
?
StringList.cpp
: