2.1-pointers-linkedlists

advertisement
CS204 – Advanced Programming
Pointers and Linked Lists
Part 1: Basics
Representation of Data
All data in a the computer’s memory is represented as a sequence of bits:
Bit : unit of storage, represents the level of an electrical charge. Can be either 0 or 1.
0
1
Byte (a.k.a Octet): 8 bits make up a byte, and a character is one byte
A byte can represent 256 (0…255) different symbols:
27 26 25 24 23 22 21 20
0
0
0
0
0
0
0 0
 0
0
0
0
0
0
0
0 1
 1
0
0
0
0
0
0
1 0
 2
0
0
0
0
0
0
1 1
 3
1
1
1
1
1 1
 255
//binary representation and corresponding integer values
...
1
1
Word: typical data size used in computer
– 32 bits on some old computers (maybe in use)
– 64 bits on most current modern computers
Hex Numbers
Hexadecimal numbers:
- Each `digit` can take values: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
corresponding to values in: 0 to 15 (0-9, A = 10, B=11, ... F=15)
- 4 binary digits are one hex digit
Sometimes written as 0x2435af00
e.g. 2435af00

0 0 1 0 | 0 1 0 0 | _ _ _ _ | _ _ _ _| _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _
Hex Numbers: ctd.
Representation and rules of arithmetic is similar to decimal,
but do not forget that the number base is 16.
0x2435AF00 + 1 =
0x2435AF01
0x2435AEFF + 1 =
0x2435AF00
Memory
What happens when we define variables:
char c = 'A';
int n = 5;
c
0x2435af00
Code of 'A'
0x2435af00
0x2435af01
01000001
c
00000000
00000000
00000000
00000101
n
...
Memory
n
0x2435af01
.
.
.
Symbol table
5 in binary
Pointers
A pointer is a variable to store an “address in computer memory”
Thus it points to a memory location
01000001
c
00000101
n
...
0x2435af00
Purpose:
p
Memory
– Basis for implementing linked data structures linked lists, trees, graphs...
– Provide dynamic memory allocation, variable size arrays
Overview
There are a couple of things you can do with pointers:
– Declare one:
int * ptr;
char * pchar;
– Store a value in it: ptr = &counter;
– Dereference one:
cout << *ptr;
(Access the value stored where the
pointer points to)
cout << (*ptr).Day();
cout << ptr->Day();
Now we will see them in detail.
7
Pointers: definition
A pointer is defined as: type * pointer_variable;
e.g.
char * ptr;
ptr_variable does not store a type value, but stores the address of a memory location that
stores a type value
c
char c = 'A';
char *p;
char *q = NULL
what happens in memory
A
p
?
q
You can never know where an unititialized pointer points (i.e. the address of the
memory location that it keeps)
“Null” is defined in standard header files to mean “nowhere” or ”nothing”. It is the
initial value for a variable that does not point to anywhere. Normally this value is 0.
Pointers: “address of” operator (&)
You can store the address of another variable in a pointer
Actually stores
variable:
the code of 'A'
char c = 'A';
char * p; what happens in memory
p = &c;
c
A
.
.
.
p
0x2435af00
Means address of c
Assigning an existing variable’s address is not the main use
of pointers; but, this is useful in explaining the concept of
address storage.
Pointers: dereferencing
*pointer-variable
derefence (or indirection) operator. It gives the content of
memory location pointed by the pointer-variable
char c = 'A';
char * p;
p = &c;
c
A
.
.
what happens in memory
cout << *p;
.
p
0x2435af00
prints the value of memory location pointed by p. This is
and this is the same of the value of regular variable c.
just like “cout << c;” prints the value of the char variable
A
Pointers: assignment
A pointer can be assigned to another pointer of the same type.
Assume we have done as before:
double n;
double * p;
p = &n;
*p = 17.5;
// memory location pointed by p contains 17.5
17.5
p
n
Now you can assign p to another pointer variable:
double *q;
q = p;
// q and p points to the same location in memory
q
p
17.5
n
Pointers: definition
(what happens behind the scenes)
char c = 'A';
char *p;
0x2435af00
0x2435af01
c
p
A
?
.
.
.
Memory
c
p
0x2435af00
0x2435af01
.
.
.
Symbol table
//a pointer uses 4 bytes of memory
when you have 32-bit adresses
Pointers: address of a variable
(what happens behind the scenes)
c
p
char c = 'A';
char *p;
p = &c;
0x2435af00
0x2435af01
0x2435af00
0x2435af01
.
.
.
// p now points to c.
Symbol table
A
0x2435af00
Alternative and more meaningful view:
.
.
.
Memory
c
p
A
Pointers: dereferencing
(what happens behind the scenes)
char c = 'A';
char *p;
p = &c;
*p = 'B';
// p now points to c.
c
p
0x2435af00
0x2435af01
//unchanged
.
.
.
0x2435af00
0x2435af01
A B
0x2435af00
.
.
.
Symbol table
Alternative and more meaningful view:
c
p
Memory
A B
Some Remarks
What happens if you try to assign a string/int/double
expression to a pointer variable?
– e.g.
double *q;
q = 123.45;
:syntax error
What happens if you try to access a memory location pointed
by a pointer that is not initialized?
– e.g.
double *q;
cout << *q << endl;
:a run-time (application) error occurs
What happens if you display the value of a pointer?
– e.g.
cout << q << endl;
:it displays the value of q, which is the address where it
points to.
Dynamic memory allocation with new
Dynamic memory allocation using new statement
new type
– allocates enough memory from heap (a special part of memory reserved
for dynamic allocation - will discuss later) to store a type value
– also returns the address of this memory location
– need to assign this address to a pointer variable for processing
Example
double *p; //a pointer for double type, but currently points nowhere
p
p = new double;
?
// memory is allocated to store a double value, but
//currently not initialized. p now points to that location
p
?
Pointers with user-defined types/classes
You can have pointers for any type:
– built-in or user-defined types, classes and structs
– E.g. int, double, char, string, robot, dice, date, …
You can dynamically allocate memory for any type; note that
the class constructor is called automatically to construct the
object:
myClass *
classPtr;
classPtr = new
myClass;
Pointer to Class Members
Date *p_date;
//preferred naming - starts with p
Date *p1 = new Date;
//p1
Date *p2 = p1;
Date tomorrow = *p1 + 1;
//p2
//tomorrow
int month = (*p1).Month();
//month
int day = p1->Day();
//day
16/2/2015
17/2/2015
2
16
ptr-> is a shorthand for (*ptr). if ptr is a pointer to a struct or a class
Dynamic memory allocation with new –
Allocating Multiple Variables
int *p1, *p2;
new keyword dynamically allocates enough memory for a single int, and
returns its address, which is stored in p1
p1 = new int;
In general, the new keyword dynamically allocates enough memory for the
following type and count. Pointer p2 points to the first element of the list.
This is how we generate DYNAMIC ARRAYs.
p2 = new int[4];
type
count
p2
Allocation of multiple variables with new
Assume that we want to hold a dynamic array to hold
vacation dates of varying numbers:
cin >> num_vacations;
Date * vacations = new Date[num_vacations];
The allocated memory is a contiguous memory than can hold
num_vacations dates. Notice that you can access it like an
array (it does not change or advance vacations as a pointer;
just computes an offset from where vacations points to):
Date today;
vacations[0]= today;
Vacations[1]= today + 3;
Allocation of multiple variables with new
Similarly, we can have a pointer to a list of 100 integers:
int * primenumbers = new int[100];
//This offset operation does not mess up the pointer
primenumbers[0]=2;
primenumbers[1]=3;
cout << "First prime is " << primenumbers[0];
21
Manipulation using pointers
You can also manipulate such an array using pointer syntax.
int * pa = new int[100];
pa[0]
and
*pa
are the same things
What about pa[1]?
It is the same as *(pa+1)
In general, ptr+x means, the xth memory location (of the type of
ptr) in the memory after ptr.
What about pa[500]?
Try and see! Also try other values larger than 99!
22
Pointers for Implementing
Linked Data Structures
Linked Lists
Built-in Arrays:
- too much or too little memory may be allocated
+ ease of use
- direct access
- inserting an element into a sorted array may require shifting
all the elements
- deleting an element requires shifting
Vectors :
+/- may be resized but inefficient
- still some space wasted
- inserting an element into a sorted array may require shifting
all the elements (also for deletion)
Note: In CS201 of previous years, Tapestry's Tvector class was being used instead of standart
Vector class. Basically they are the same. You can use whatever you want.
Linked Lists
head
Linked lists:
+ dynamic memory allocation
+ insertion/deletion is cheap (no shifting)
- more cumbersome to program with
- one extra pointer is needed per element
Introduction to linked lists: definition
Consider the following struct definition
struct node
{
string word;
int
num;
node
*next;
};
node *
//node is a user given name
//
pointer for the next node
p = new node;
p
?
?
num
word
?
next
Reminder: structs: as data aggregates
see Tapestry Chp. 7 pp. 330-
If you need multiple arrays with tied elements (e.g. grades[MAXCLASS_SIZE],
names[MAXCLASS_SIZE]...) or in general, any “tied” data, you should consider
using a data structure called struct:
struct point
{
double x_coord;
double y_coord;
};
point p1, p2;
p1.x_coord = 10.0;
p1.y_coord = 0.0;
//access members using the dot notation
...
Very similar to classes - but no member functions. You should use structs rather than classes
only when you want to use them as data aggregates, without any member function (you may
have a constructor though, see the next slide).
27
Structs with constructors
If you define one or more constructors:
struct point
{
double x;
double y;
};
Instead of:
point curve[100];
//default constructor
point::point()
{
x = 0;
y = 0;
}
curve[0].x = 0;
curve[0].y = 0;
curve[1].x = 7;
curve[1].y = 12;
...
//constructor
point::point(int x_coord, int y_coord)
: x (x_coord),
y (y_coord)
{
//nothing more to initialize
}
You can use:
point curve[100];
curve[0] = point (0,0);
curve[1] = point (7, 12);
General Remarks:
 If no constructor is used, it is OK.
 If you want to have a constructor with parameter, also write a default constructor
 If there is constructor, creating a struct (dynamic or normal) variable automatically
calls default constructor (e.g. curve has 100 elements, all are (0,0) when created)
28
Introduction to linked lists: inserting a node
Without a constructor:
With a constructor:
node *p;
p = new node;
node * p;
p=new node(5,"Ali",NULL);
p->num = 5;
p->word = "Ali";
p->next = NULL
p
5
Ali
num
word
next
Updated node struct with constructor
In order to use the struct as in the previous slide, we need to add a
constructor to it:
struct node //node is a user given name
{
int
num;
string
word;
node
*next; // pointer for the next node
//default constructor; actually does not initialize
//anything but should exist
node::node()
{
}
//constructor with 3 parameters
node::node(int n, string w, node * p)
: num(n),word(w),next(p)
{};
};
30
Introduction to linked lists: adding a new node
How can you add another node that is pointed by p->link?
node *p;
p = new node(5,"Ali",NULL);
node *q;
q
p
5
Ali
num
word
?
next
Introduction to linked lists
node *p;
p = new node;
p = new node(5,"Ali",NULL);
node *q;
q = new node;
q
p
5
Ali
num
word
? ?
next
num
?
?
word
next
Introduction to linked lists
node *p, *q;
p = new node;
p = new node(5,"Ali",NULL);
q = new node;
q->num=8;
//I can still access fields one by one
q->word = "Veli";
q
p
5
Ali
num
word
? 8
next
num
?
Veli
word
next
Introduction to linked lists
node *p, *q;
p = new node;
p->num = 5;
p->word = "Ali";
p->next = NULL;
q = new node;
q->num=8;
q->word = "Veli";
q->next = NULL;
p->next = q;
p
5
Ali
num
word
q
? 8
next
num
Veli
word
next
Linked Lists: Typical Functions
head
Printing an existing list pointed by head:
struct node {
int info;
node *next;
};
. . . //constructors
node *head, *ptr;
//list is filled here...
//head points to first node
ptr = head;
while (ptr != NULL)
{
cout << ptr ->info << endl;
ptr = ptr->next;
}
End of a linked list
should point to NULL
Linked Lists: Typical Functions
head
Adding a node to the end of the list:
tail
void Add2End(node * tail, int id)
{
node *nn = new node(id,NULL);
tail->next = nn;
}
//This added the new id to the end of the list,
//but now tail also needs updating – how?
//we could return the new tail from the function: node * Add2End(node * tail, int id)
//and let the caller do the update
//or we could make tail a reference parameter and update it here…
//but we left it as it is right now
Linked Lists: building
//Modified from strlink.cpp in Tapestry code
//TASK: Put contents of storage into a linked list,
struct node {
int info;
node *next;
};
int storage[] = {1,2,3,4};
node *head = NULL;
node *temp = NULL;
for (int k=0; k < 4; k++)
{
temp = new node();
temp->info = storage[k];
temp->next = head;
head = temp;
}
What happens as a result of this
code’s execution? Let's trace on the board
Linked Lists: building
// Modified from strlink.cpp in Tapestry code
struct node {
int info;
node *next;
node::node () {}
node (const int & s, node * link)
: info(s), next (link)
{}
};
int storage[] = {1,2,3,4};
node *head = NULL, *temp = NULL;
for (int k=0; k < 4; k++)
{
temp = new node (storage[k], head);
temp = new node();
temp->info = storage[k];
temp->next = head;
head = temp;
}
The codes in the last 3 slides are
in ptrfunc.cpp, but this file also
contains some features that we
have not seen as of now. Please
do not get confused.
You better use a constructor to
insert data more compactly and in a
less error-prone fashion – like this.
Stack and Heap
Scope and Lifetime
Extern and Static Variables
Freeing Memory Allocated by New
39
Static vs. Dynamic Memory Allocation
Automatic (ordinary) variables: Normal declaration of variables within a function
(including main): e.g. ints, chars that you define by “int n;” , “char c;” etc..
– C++ allocates memory on the stack (a pool of memory cells) for automatic
variables when a function begins, releases the space when the function
completes.
– The lifetime of an automatic variable is defined by its scope (the compound block
in which the variable is defined). After the block finishes, the variable’s location is
returned to memory.
• A block is any code fragment enclosed in an left curly brace, {, and a right
curly brace, }.
Dynamic variables: Allocated by the new operator, on the heap (a storage pool of
available memory cells for dynamic allocation). E.g. p = new int[100];
– new returns the address of the first element allocated (this is generally assigned
to a pointer variable)
– System will return NULL if there is no more space in heap to be allocated.
– Programmer should use delete to release space when it is no longer needed.
– The lifetime of a dynamic variable is until they are explicitly deleted
40
Local Variables vs Global Variables
• A block is any code fragment enclosed in an left curly brace,
{, and a right curly brace, }.
• Variables are categorized as either local or global solely
based on there they are declared: inside a block or outside
of all blocks.
– Local variables are declared in a block.
– Global variables are declared outside of all blocks.
• The scope of a local variable is the block in which the
variable is declared.
• A global variable is not limited in scope. This type of variable
is visible to every module in the project.
– A commonly complained shortcoming of C++ is the exposure created
by using global variables.
– This situation is usually avoided by prohibiting the use of global
variables and instead passing information between modules in the
form of function/method parameters.
41
Extern variables
• We said that a global variable is visible to every module in the
project
– But this is not automatic; you cannot simply use a global variable defined
in say student.cpp in another cpp (say classes.cpp) of the same project
•
There is a mechanism to reach a global variable declared in
another cpp file; using extern variable.
• The actual definition of the global variable remains as is
– This is where the memory is allocated for that variable
• In the cpp file that you will reach the global variable defined
outside of this cpp
– You have to redefine the same global variable with preceeding extern
keyword.
– In this way, you do not allocate a new memory location, but inform the
compiler that the actual definition of the variable is outside of that cpp
file.
• See externdemo1.cpp and externdemo2.cpp
42
Static variables
Static Local Variables
• A variant of the 'normal' local variable is the static local. When the
keyword static preceeds to the variable declaration, the lifetime
of the variable becomes the entire execution of the program.
– The compiler to preserve the value of the variable even when it goes out
of scope.
– When program execution reenters the block in which the variable is
declared, the variable still has the value it had when execution last left
that block.
Static Global Variables
• A variant of the 'normal' global variable is the static global. Static
global variables are visible to all methods/functions in the module
(i.e. .cpp or .h files) where the variable is declared, but not visible
to any other modules in the project. This strategy greatly reduces
the opportunities for logic errors in larger programs. By doing this
sharing information within a module is still possible. As long as
modules are kept small and manageable, this strategy is useful.
43
Heap/Stack: usage overview
Stack (also called as runtime stack)
• All automatic (ordinary) variables use a special part of memory called the runtime
stack
• The stack is useful for storing context during function calls, which is performed
automatically and transparent to the programmer.
– When a function, say dothis, is called in another function, say dothat, the function
dothat simply pushes all its local variables (its context) onto the stack before passing
the control to dothis. When the function dothis is completed, control returns to dothat
but beforehand, dothat pops the context off the stack.
Heap
• The heap is basically rest of memory that the program has. In that sense, it is
often the largest segment in a program.
• Dynamic variables are allocated on the heap and memory allocated for them
stays until explicitly freed (deleted).
•
The memory area used for static and global variable allocation is basically part of
heap, but mostly the area used for dynamically allocated variables is separated
from static/global
– This is compiler dependant
See stackheapaddress.cpp
44
Heap/Stack
Where are pointer variables stored, stack or heap?
Pointer variables (themselves), just like the normal built-in
variables and objects (int, double, string, vector, etc.) also
use up memory, but not from the heap
– they use the run-time stack
45
Pointers: Delete
The statement to de-allocate a memory location and return to
the heap is:
delete PointerVariable;
– the memory location pointed by PointerVariable is now returned back
to the heap;
– this area now may be reallocated with a new statement
– careful: PointerVariable still points to the same location, but that
location is no longer used. This may cause confusion, so it is a good
idea to reset the pointer to NULL (zero) after deleting.
e.g.
p = NULL;
46
Freeing allocated memory with delete
Date *p1 = new Date();
Date *p2 = p1;
p1
p2
25/02/2013
...
delete p1; //We need to delete memory allocated with new
delete p2;
//Deleting (freeing) previously freed memory possibly
//causes a crash or a corrupt program depending on the
// compiler and platform
47
Pointers to variables on the stack
Can we have a pointer to point a variable that is not
dynamically allocated?
- using the & (address of) operator
- such variables are not allocated from heap!
• that is why their addresses are not close to dynamically
allocated variables
int num;
int *ptr;
num=5;
ptr = #
//ptr contains the address of num;
cout << *ptr << endl;
What is output?
48
Question
int n;
int * p_temp = &n;
Do we need to delete p_temp?
No. It points to a stack variable.
What happens if we delete?
Most likely a crash or corrupt program, depending on the
compiler.
49
Memory allocation with the new operator
Points to be careful for
Warning message: address of local variable returned
What is the problem here?
Dice * MakeDie(int n)
//return pointer to n sided object
{
Dice nSided(n);
return &nSided;
}
Dice * cube = MakeDie (4);
Dice * tetra = MakeDie (6);
cout << cube->NumSides();
50
Memory allocation with the new operator
What is the problem?
Dice * MakeDie(int n)
Returning a pointer to a variable on the
stack (with the & operator),
is wrong!
//return pointer to n sided object
{
Dice nSided(n);
nsided
return &nSided;
no longer exists
after the
function returns
}
Dice * cube = MakeDie (4);
cube
Dice * tetra = MakeDie (6);
cout << cube->NumSides();
51
Memory allocation with the new operator
Solution:
Dice * MakeDie(int n)
//return pointer to n sided object
{
Dice * dice_ptr = new Dice (n);
dice_ptr
return dice_ptr;
}
Dice * cube = MakeDie (4);
cube
Dice * tetra = MakeDie (6);
cout << cube->NumSides();
//Is there any problem left?
52
Download