1 / 41

Chapter 10: Pointers

Chapter 10: Pointers. Chapter Goals To learn how to declare, initialize and use pointers To become familiar with dynamic memory allocation and deal location To use pointers in common programming situations that involve optional and shared objects

laddie
Download Presentation

Chapter 10: Pointers

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chapter 10: Pointers Chapter Goals • To learn how to declare, initialize and use pointers • To become familiar with dynamic memory allocation and deal location • To use pointers in common programming situations that involve optional and shared objects • To understand the relationship between arrays and pointers • To be able to convert between string objects and character pointers

  2. Pointers and Memory Allocation • The C++ run-time system can create new objects for us. • When we create a new object, a memory allocator finds a storage location for the object. • The memory allocator keeps a large storage area, called the heap, for that purpose. • The heap is a flexible pool of memory that can hold values of any type. • When you allocate a new heap object, the memory allocator tells where the object is located, by giving you the object's memory address. • To manipulate memory addresses, you need to use a pointer.

  3. Pointers and Memory Allocation • Syntax of new operator To allocate new memory space: new type_name new type_name(exp1, exp2, . . . , expn) • Example: new Time; new Employee("Lin, Lisa", 68000) • Purpose: Allocates and construct a value on the heap and returns a pointer to the value. (It really returns the address of the memory area being allocated.)

  4. Pointers and Memory Allocation • Syntax: Pointer Variable Definition type_name* variable_name; type_name* variable_name = expression; • Example: Employee* boss; Product* p = new Product; • Purpose: Define a new pointer variable, and optionally supply an initial value.

  5. Pointers and Memory Allocation • The following three declarations have the same effect: declaring p as of type “pointer to int” int* p; int *p; int * p; • Be aware that the effect of the * only applies to the variable immediately following it. • The following declaration: int *p, q, *x; causes the following: Variabledeclared Type

  6. Pointers and Memory Allocation • Pointers are declared by placing an * before the variable name. Example: Time *deadline; • Pointers hold the location of an object, not the actual object itself. • To create a new object, you use the new operator. Example: deadline = new Time; • You can also call the new command in conjunction with a constructor to initialize the object. Example: Employee* boss = new Employee("Lin, Lisa", 68000);

  7. Pointers and Memory Allocation • To access the object that a pointer points to, the object must be dereferenced: • Use * to access the actual object. • Use -> to access methods of the object. Example: raise_salary(*boss, 10); string name = boss->get_name(); • The dot operator has a higher precedence than the * operator: Example: string name = *boss.get_name(); // Error string name = (*boss).get_name(); // OK string name = boss->get_name(); // OK

  8. Pointer Dereferencing • Syntax: Pointer deferencing Let p represent a pointer expression *p - object being pointed to by p • If *p is a complex object (e.g. class) then to apply a particular method: say m() Two alternatives: • p->m() • (*p).m() • Example: Employee *boss=new Employee(…); boss->set_salary(70000); • Purpose: Access the object to which a pointer points.

  9. Example Assume the following: class Rational { private: int num, den; public: Rational(int n, int d) { num=n; den=d; } … };

  10. Consider the following two statements in a C++ program Rational *p; p = new Rational(1,3); num den p This memory area is dynamically Allocated with the execution of new Rational(1,3) If it is created on memory address is x, the x is the value assigned to p. This memory area is allocated with declaration of p. The area can hold the address of an object of type Rational.

  11. We usually draw an arrow from the pointer variable area to the object area that it points to (whose address it contains). num den p Also, remember (*p).num is the same as p->num which is value 1…

  12. Pointers and Memory Allocation • The special value NULL indicates that a pointer doesn't point anywhere. • Instead of leaving pointers uninitialized, you should always set them to NULL when you define them. Example: Employee* boss = NULL; // will set later … if (boss != NULL) name = boss->get_name(); // OK • You cannot dereference a NULL pointer. Example: Employee* boss = NULL; string name = boss->get_name(); // NO!! Program will crash

  13. Deallocating Dynamic Memory • When you make a variable of a certain type, the object is allocated on the run-time stack. This object automatically goes away when the program leaves the block in which the variable is allocated: Example: void f() { Employee harry; // memory for employee allocated on the stack . . . } // memory for employee object automatically // reclaimed

  14. Deallocating Dynamic Memory • Value that are allocated on the heap do not follow this automatic allocation mechanism. • You must reclaim the memory using the delete operator. • Example: void g() { Employee* boss;   boss = new Employee(…);   // memory for employee object allocated on the heap    … delete boss; // memory for employee object manually reclaimed } // memory for boss is automatically reclaimed

  15. Syntax for delete Expression • Syntax for delete expression: delete pointer_expression … deallocates the area being pointed to by the pointer expression. • Example: delete boss;// deallocates area pointed to by boss • Purpose: To deallocate a memory area that has been dynamically allocated on the heap and allow the memory to be reallocated.

  16. Deallocating Dynamic Memory • Reclaiming a pointer variable does not automatically reclaim the object to which it points. • (Common Error 10.4) A memory block that is not deallocated is called a memory leak. • Leaked memory can cause the heap to run out of memory. • Program crashes. • Computer freezes up. • When a pointer is defined it points to a random address; using this random address causes and error. Example: Employee* boss; string name = boss->get_name(); // NO!! boss contains a random address

  17. Deallocating Dynamic Memory • After you delete the value attached to a pointer, you can no longer use that address. Example: delete boss; string name = boss->get_name(); // NO!! boss points to a deleted element • (Common Error) Such pointers are called dangling because they do not point to a valid object.

  18. Common Uses for Pointers (Optional Attributes) • One use for pointers is optional attributes. • If the attribute is needed, the pointer is set to the address of the object; otherwise the pointer will be set to NULL. Example: class Department { … private: string name; Employee* receptionist; }; • This is better than allocating space for an object that might not be used. Example: class Department { … private: string name; bool has_receptionist; Employee receptionist; // uses more space… };

  19. Common Uses for Pointers (Sharing) • Another common use of pointers is sharing. • Rather than duplicating objects, we can use pointers to share the object. • Pointers are useful to model a "n:1" relationship, in which a number of different variables share the same object. • Example: Many departments may have a receptionist and a secretary. In some departments, this it the same person. class Department { … private: string name; Employee* receptionist; Employee* secretary; };

  20. Common Uses for Pointers (Sharing) • Sharing is particularly important when changes to the object need to be observed by all users of the object. Example: Employee* tina = new Employee("Tester, Tina", 50000); Department qc("Quality Control"); qc.set_receptionist(tina); qc.set_secretary(tina); tina->set_salary(55000); • Without using pointers, changing Tina's salary would not update the information in the receptionist or secretary attribute.

  21. Arrays and Pointers • There is an intimate connection between arrays and pointers in C++. • The name of an array is a pointer to the starting element. Example: int a[10]; int* p = a; (p=&a[0]) // now p points to a[0]; *a = 12; // same as a[0] = 12; • You can add an integer to offset to the pointer to point to another array location (pointer arithmetic). • *(a + 3)means the same asa[3] • This relationship is called the array/pointer duality law. • For any integer n, *(a + n)means the same asa[n]

  22. Arrays and Pointers • When an array is passed into a function, it is actually a pointer to the starting element of the array. Example: double maximum(const double a[], int a_size) {    if (a_size == 0) return 0;    double highest = a[0];    int i;    for (i = 0; i < a_size; i++)       if (a[i] > highest)          highest = a[i];    return highest;}

  23. Arrays and Pointers (cont.) • The declaration of function maximum gives the illusion that an entire array is passed to the function, but in fact the function receives only the starting address of the array. double maximum(const double* a, int a_size) { // identical code as above yields same results … } • It is essential that the function also knows where the array ends.

  24. Pointers to Character Strings • C++ inherits a more primitive level of string handling from the C language, in which strings are represented as arrays of char values. • We don't recommend that you use character pointers or arrays in your programs to represent strings, but you occasionally need to interface with functions that receive or return char* values. • The declaration and initialization char s[] = "Harry"; creates • Literal strings are stored inside char arrays. S[0] s[1] s[2] s[3] s[4] s[5]

  25. Pointers to Character Strings • The string class has a constructor string(char *) that you can use to convert any character pointer or array to a safe an convenient string object. Example: char* p = "Harry"; string name(p); • Use the c_str member function of the string class to obtain a char* pointer that points to the first character in the string object. • The tempnam function in the standard library yields the name of a temporary file, and expects a char* parameter for the directory name. Example: string dir = …; char* p = tempnam(dir.c_str(), NULL); string file_name(p);

  26. Dynamic Arrays • In C++, arrays can be dynamically created, expanded, or shrunken Example: int *x; x = new int[5]; x points to a contiguous memory area of 5 objects of type int X[0] x[1] x[2] x[3] x[4] x *x *(x+1) *(x+2) *(x+3) *(x+4)

  27. Dynamic Arrays • In the previous case, we say that x points to a dynamic array of int, or that x is a dynamic array • To deallocate a dynamic array being pointed to by a pointer variable, we need to use: delete[] pointer_variable Example: delete[] x;

  28. VectorInt Data Structure • Let us discuss an example that illustrates the idea of how the Vector data structure can be implemented. • Since we have not studied templates in C++ yet, we will focus only on a type of vector whose elements are of type int. • In this case, we will focus on a vector for integer elements: VectorInt

  29. // VectorInt data structure …. class VectorInt { private: int *element; // to hold the elements in the current VectorInt instance int capacity; // the maximum capacity of the current instance public: VectorInt(int cap=0); // Default constructor and constructor to specify a capacity VectorInt(const VectorInt& other); // Copy constructor … we need this because of the pointer attribute // Creates a new instance (this) as a replication of other. VectorInt operator=(const VectorInt& other); // Assignment operator for the VectorInt data structure. Changes the // value of the current object to make it a replica of other. void push_back(const int& newElement); // Expands the current instance, appending a new position with value // equal to newElement.

  30. // …. continuation of specification of data structure VectorInt… void pop_back(); // Erases last element of current instance, reducing its capacity by 1. void setElementAt(cons int pos, const int& e); // Assuming that pos is a valid position in current instance, is places e there int getElementAt(cons int pos) const; // Assuming that pos is a valid position in current instance, it returns // a copy of the element there. int size() const; // Returns the size of current instance (its capacity)/ ~VectorInt(); // Destructor. Releases all the memory being used by the current instance. // Automatically executed when the particular (nonstatic) variable // representing this instance gets out of scope… };

  31. The following illustrates the idea of how this data structures work. Consider the declarations: VectorInt p(5), w(3), z; Then we have: element p 0 1 2 3 4 capacity element z element capacity w 0 1 2 capacity

  32. Let us implement some of the methods for class VectorInt, as they would appear in file VectorInt.cpp VectorInt::VectorInt(int cap=0) { assert (cap>=0); capacity = cap; if (capacity > 0) element = new int[capacity]; else element = NULL; } VectorInt::VectorInt(const VectorInt& other) { // Copy Constructor. The current instance is // being created as a copy of “other”. capacity = other.capacity; if (capacity > 0) { element = new int[capacity]; for (int i=0; i<capacity; i++) element[i] = other.element[i]; } else element = NULL; } This type of constructor is called “copy constructor”. It is automatically used when: 1. passing parameters by value of type VectorInt 2. returning an object of type VectorInt from a function 3. when declaring a variable of this type as in: VectorInt x(y); where y is also of type VecorInt and previously created.

  33. Let’s consider a case in which a copy constructor as the previous is used, and why we need it. Consider the function: void f(VectorInt w) { … whatever } Assume that p has been declared as VectorInt(5). And initialized as: for (int i=0; i<p.size(); i++) p.setElementAt(i, 2*i); Then, again, we have p as follows: element p 0 1 2 3 4 capacity

  34. If the function f is invoked as in: f(p); p is passed by value. So, we expect that during the execution of f that is initiated with this call, that the parameter w is a copy of p. This is true because we have defined the copy constructor as before. If such copy constructor in not defined, then what we would have in the previous case is illustrated next. In the activation record for the current execution of f the parameter w will be as follows: …. element element activation record of current execution of f p w 0 1 2 3 4 capacity capacity ….. This is p as previously illustrated. process stack

  35. Notice then that p and w share the same dynamic array. But this is not correct. This is not what passing parameter by value should be. The following illustrates what should be the correct situation: …. element element activation record of current execution of f p w 0 1 2 3 4 capacity capacity ….. process stack 0 1 2 3 4

  36. Just for the sake of completeness, suppose that we have the following (not correct) implementation of the copy constructor: VectorInt::VectorInt(const VectorInt& other) { // Copy Constructor. The current instance is // being created as a copy of “other”. capacity = other.capacity; if (capacity > 0) { element = new int[capacity]; for (int i=0; i < capacity; i++) element[i] = other.element[capacity – 1 - i]; } else element = NULL; } … notice that now the new element array is initialized as a reversed copy of other.element. Then, when the function f is called, the following happens:

  37. This is what happens with the incorrect copy constructor… …. element element activation record of current execution of f p w 0 1 2 3 4 capacity capacity ….. process stack 0 1 2 3 4 …this is in the heap…

  38. The operator=(…) function implements the assignment operation for the VectorInt data type. After this declaration, the = operator can by used to copy or assign objects of type VectorInt to variables of type VectorInt. For example: VectorInt p, q, r; …. initialize p … // the following creates in q a separated copy of p q = p; … this works because the operator= to be implemented shortly … also, the following statement will be valid: r=q=p; it creates in r and in q two separated copies of p… … if we do not have this method, then it will happen something similar to what was illustrated with the parameter of function f seen before.

  39. Implementation of operator=(…) for the class VectorInt: VectorInt VectorInt::operator=(const VectorInt& other) { // if other and *this are the same, // then do nothing if (this != &other) { if (capacity > 0) delete[] element; capacity = other.capacity; if (capacity > 0) { element = new int[capacity]; for (int i=0; i<capacity; i++) element[i] = other.element[i]; } else element = NULL; } return *this; } One parameter only. This will take the value of the object to be copied to the implicit object … This last statement, which returns a copy of the implicit object (the one pointed to by the pointer this), allows for multiple assignments such as: r=q=p;

  40. Destructor • A destructor is a particular method which is automatically executed when a variable space is recovered. • when explicitly applying delete operator • when the variable gets out of scope • For a class named T, its destructor is ~T() • Whenever a class has pointer attributes, a destructor needs to be implemented. • Example: Destructor for class VectorInt VectorInt::~VectorInt() { if (element != NULL) delete[] element; }

  41. Functions with name beginning with keyword operator… • Remember, operator=(..) is the name of the function. • If p and q are of type VectorInt, then the execution of • p.operator=(q) will copy the value of q to p. • Since this function has name using the operator keyword, • the compiler will allow then the use of the operator = • when invoking the function. This provides standard code. • So, in a case like the previous, the following two statements • are equivalent: • p = q and p.operator=(q) • A statement such as r=q=p is valid, and is equivalent to: • r.operator=(q.operator=(p)) • There are many other operators that can be used…

More Related