Comp151
C++: Data Abstraction & Classes
Outline • Data abstraction • Data members and member functions • Information hiding
[comp151]
1
A Brief History of C++
[comp151]
2
• Bjarne Stroustrup of AT&T Bell Labs extends C with Simula-like classes in the early 1980s, and the new language was called ”C with Classes”. • C++, the successor of ”C with Classes”, was designed by Stroustrup in 1986, and version 2.0 was introduced in 1989. The design of C++ was guided by the following three principles: 1. The use of classes would not result in programs executing any more slowly than programs not using classes. 2. C programs should run as a subset of C++ programs. 3. No run-time inefficiency should be added to the language.
Data Abstraction
[comp151]
3
• What is a “chair”? • What is a “stack”?
A data abstraction is a simplified view of an object that includes only features one is interested in while hides away the unnecessary details. In programming languages, a data abstraction becomes an abstract data type or a user-defined type. In OOP, it is implemented as a class.
Example: Implement a Stack with an Array
1
data
2 size
3 top
[comp151]
4
Example: Implement a Stack with a Linked List[comp151] top -999
top (latest item)
-999
1
2
5
Information Hiding
[comp151]
6
• An abstract specification tells us the behavior of an object independent of its implementation. i.e. It tells us what an object does independent of how it works. • Information hiding is also known as data encapsulation, or representation independence. • The principle of information hiding: Design a program so that the implementation of an object can be changed without affecting the rest of the program. e.g. Changing the implementation of a stack from an array to a linked list has no effect on users’ programs.
[comp151]
Example: stack ar.h class Stack {
access control
data memebers
private: int size; Object* top; Object* data;
// max. size of storage // point to the next available space // data storage
public: Stack( int N); ~Stack();
// a constructor // destructor
// basic operations void push(const Object& x ); Object pop( ); // status operations int num_elements( ) const; bool empty( ) const; bool full( ) const; };
// add another datum // get the most recent datum
member functions (public interface)
7
Structure vs. Class
[comp151]
8
In C++, structures are special classes and they can have member functions: struct Stack { int size; int∗ top; int∗ data; Stack(int N); void push(int x); int pop(); };
By default, struct { . . . }; = class { public: . . . }; class { . . . }; = struct { private: . . . };
// a stack of integers
Class Name: Name Equivalence
[comp151]
9
A class definition introduces a new abstract data type. C++ relies on name equivalence (and NOT structure equivalence) for class types. class class class class
X { int a; }; Y { int a; }; W { int a; }; W { int a; };
// error, double definition
X x; Y y; x = y;
// error: type mismatch
Class Data Members
[comp151]
10
Data members can be any basic type, or any user-defined types if they are already “seen” except: • A class name can be used in its own definition for its pointers: class Cell { int info; Cell *next; . . . }; • A forward declaration for class pointers: class Cell; class Stack { int size; Cell∗ top; Cell x; };
// forward declaration
// points to an object with forward declaration // Error: Cell not defined!
Class Data Members ..
[comp151]
11
But, they can NOT be initialized inside the class definition. class Stack { ... int∗ top = 0; };
// Error: class data member cannot be initialized here
Initialization should be done with appropriate constructors, or member functions.
Class Member Functions
[comp151]
12
These are the functions declared inside the body of a class; but they can be defined in two ways: • within the class body, in which case, they are inline functions. class Stack { ... void push(int x) { ∗top = x; ++top; } int pop() { --top; return (∗top); } };
Class Member Functions ..
[comp151]
13
• outside the class body (any benefits of doing this?) class Stack { ... void push(int x); int pop(); }; void Stack::push(int x) { ∗top = x; ++top; } int Stack::pop() { --top; return (∗top); }
Question: Can we add data and function declarations to a class after the end of the class definition?
Inline Functions
[comp151]
14
• Function calls are expensive because when a function is called, the operating system has to do a lot of things behind the scene to make that happens. int f(int x) { return 4∗x∗x + 9∗x + 1; } int main() { int y = f(5); }
• For small functions that are called frequently, it is actually more efficient to unfold the function codes at the expense of program size (both source file and executable). int main() { int y = 4∗5∗5 + 9∗5 + 1; }
Inline Functions ..
[comp151]
15
• But functions has the benefit of easy reading, easy maintenace, and type checking by the compiler. • In C++, you have the benefits of both by declaring the function inline. inline int f(int x) { return 4∗x∗x + 9∗x + 1; } int main() { int y = f(5); }
• In C++, when you defined a member function inside the class, it is treated as an inline function. • However, C++ compilers may NOT honor your inline declaration. i.e. The inline declaration is just a hint to the compiler which still has the freedom to choose whether to inline your function or not, especially when it is large!
Inline Class Member Functions
[comp151]
16
While member functions can be defined inside the class body and are automatically treated as inline functions, to enhance readability, one may also define them outside a class definition but usually in the same include file as follows: // stack1.h
// stack2.h
class Stack { ... inline void push(int x) { ∗top = x; ++top; } };
class Stack { ... inline void push(int x); }; inline void Stack::push(int x) { ∗top = x; ++top; }
Member Access Control
[comp151]
17
A member of a class can be: public : accessible to anybody private : accessible only to member functions and friends of the class. ⇒ Enforce information hiding protected : accessible to member functions and friends of the class, as well as to member functions and friends of its derived classes (subclasses).
Example: Member Access Control
[comp151]
18
class Stack { private: Cell∗ top; public: int size; ... }; int main() { Stack x; cout x.size; x.push(2); cout x.top→info; }
// OK: size is public // OK: push() is public // ERROR: cannot access top
[comp151]
How Are Objects Implemented?
• Each class object gets its own copy of the class data members. • All objects of the same class share one copy of the member functions.
Stack x:
size top data
Stack y:
size top data
int main() { Stack x(2), y(3); push()
x.push(1); y.push(2); y.pop(); }
pop()
19
This Pointer
[comp151]
20
• Each class member function implicitly contains a pointer of its class type named “this”. • When an object calls the function, this pointer is set to point to the object. • For example, the C++ compiler developed by AT & T Bell Labs will translate Stack::push(int x) function in the Stack class to a unique global function by adding a new argument: void Stack::push(Stack∗ this, int x) { ∗this→top = x; ++this→top; }
• a.push(x) becomes push(&a, x).
Example: Return an Object by (*this)
[comp151]
21
class Complex { private: float real; float imag; public: Complex add(const Complex& x) { real += x.real; imag += x.imag; return ∗this; } };
// Addition of complex numbers
Class Scope and Scope Operator ::
[comp151]
22
C++ uses lexical (static) scope rules: the binding of name occurrences to declarations are done statically at compile time. int height; class Weird { short height; Weird() { height = 0; } };
Quiz #1: Which “height” is used in Weird::Weird()? Quiz #2: Can we access the global one in a class body?
Class Object Initialization
[comp151]
23
If ALL data members of the class are public, they can be initialized when they are created as follows: class Word { public: int frequency; char∗ str; }; int main() { Word movie = {1, "Titantic"}; }
Class Object Initialization ..
[comp151]
What happens if some of data members are private? class Word { char∗ str; public: int frequency; }; int main() { Word movie = {1, "Titantic"}; }
Error: a.cc:8: ‘movie’ must be initialized by constructor, not by ‘{...}’
24
What you should take from this lecture
[comp151]
• Data abstraction: class • Information hiding – class vs struct – Access control specifiers: private, protected, public – scope • the use of inline functions • this pointer
25
Comp151
Separate Compilation
Motivation: “Divided We Win”
[comp151]
26
We have a program “program.cpp” that uses a class called Picture to manipulate “character pictures”: you may frame them, and horizontally or vertically glue them together. It is useful to keep the implementation of the Picture class in a separate file “picture.cpp”, because: • We can easily reuse it in another (application) program • Two programmers can work easily together: one implements Picture and the other writes the main program, “program.cpp”. • When the program is changed, only “program.cpp” needs to be compiled again, so the compilation is faster. In large software projects this makes a huge difference! Remark: By convention, C++ program files usually have the suffix: “.cpp”, “.cc”, “.C”, or “.cxx”.
Class Header File: “.h”
[comp151]
27
• Since we don’t want the user who writes “program.cpp” to know the details of the class Picture (which can be commercial secrets), we need to separate the class interfaces from the class implementation. • On the other hand, the main program “program.cpp” also needs to know about the definition of class Picture and its methods before it can be compiled. • The solution is to describe the class Picture in 2 files: – class header file, “picture.h” — containing the interface – class implementation file, “picture.cpp” — containing the implementation (of constructors and all methods)
[comp151]
Class Header File: “.h” ..
/∗ picture.h ∗/ class Picture { // ... Picture∗ frame(const Picture&); }
/∗ picture.cpp ∗/ #include ”picture.h”
/∗ program.cpp ∗/ #include ”picture.h”
Picture∗ frame(const Picture& x) { // codes to frame a picture ... }
int main() { // manipulate pictures... }
28
[comp151]
Class Header File: “.h” ...
pix_main*.cpp
pix_main3.cpp pix_main2.cpp
picture.h
picture.cpp
pix_main1.cpp Picture class
29
Separate Compilation
[comp151]
30
In Unix, we may compile the program with the GNU C++ compiler as follows: g++ -c program.cpp g++ -c picture.cpp g++ -o program program.o picture.o • “g++” has many options; “man g++” for details. • The first two lines with “-c” option create the object files “program.o” and “picture.o”. They can’t run on their own. • The last line creates the executable program called “program” (with the “-o” option) by linking the object files together. • Linker: is a program that binds together separately compiled codes.
[comp151]
Linking Object Files
compile
file1.cpp
file1.o link
file2.cpp
file2.o
file3.cpp
file3.o
fileN.cpp
fileN.o
a.out
31
Separate Compilation ..
[comp151]
32
• If “program.cpp” is changed but “picture.cpp” is not, then the second line is not necessary and you just need: g++ -c program.cpp g++ -o program program.o picture.o • The separate compilation process can be simplified using “gmake”on a “Makefile”. • If you don’t want the “.o” files, you may compile as follows: g++ -o program program.cpp picture.cpp But then you don’t get the object files, “program.o” and “picture.o”, but only the executable “program”.
[comp151]
Libraries
33
To produce a working executable, the linker needs to include the codes for functions that are declared in the standard C++ header files (iostream.h, string.h, etc.). The corresponding codes can be found in the standard C++ libraries. • A library is a collection of object files. • The linker selects object codes from the libraries that contain the definitions for functions used in the program files, and includes them in the executable. • Some libraries are used automatically by the C++ linker, such as the standard C++ library. Other libraries have to be specified during the linking process with the “-l” option. e.g. To link with the standard math library “libm.a”, g++ -o myprog myprog.o -lm
Preprocessor Directives: #include
[comp151]
34
Besides statements allowed in a programming language, some useful features are added via directives which are handled by a program called preprocessor before the source code is compiled. • In C++, preprocessor directives begin with the # sign in the very first column. • The #include directive reads in the contents of the named file. #include <standard_file.h> #include "my_file.h" • Angle brackets (< >) are used to include standard header files which are searched at the standard library directories. • Quotes (" ") are used to include user-defined header files which are searched first at the current directory. • "g++ -I" may be used to change the search path.
#ifndef, #define, #endif /* program.h */ #include "b.h" #include "c.h" ...
/* b.h */ #include "a.h" #include "d.h" ...
[comp151]
35
/* c.h */ #include "a.h" #include "e.h" ...
• Since #include directives may be nested, the same header file may be included twice! – multiple processing ⇒ waste of time – re-definition of #define constants/macros • Thus, the need of conditional directives #ifndef PICTURE_H #define PICTURE_H // object declarations, class definitions, functions #endif // PICTURE_H
What you should take from this lecture
[comp151]
• Header file – separate interface (.h) and implementation (.cpp) – re-usability and sharing • Separate compilation – compiles (to an object code .o) – link (object codes and library to produce an executable file) • Uses of preprocessor directives
36
Comp151
Definition & Declaration
[comp151]
Example: Definition
37
/∗ progam1.cpp ∗/ #include #include <string.h> int global var = 23;
// global variable definition
void reverse print(const char∗ s) { for (int j = strlen(s) - 1; j ≥ 0; --j) cout s[j]; cout endl; }
// function definition
[comp151]
Example: Declaration
38
/∗ progam2.cpp ∗/ #include // external variable declaration extern int global var; extern void reverse print(const char∗ s); // external function declaration void main(int argc, const char∗ argv[]) { float local var; local var = 987.654;
// local variable definition
cout "global var = " global var endl; cout "local var = " local var endl; cout "input str (backwards) = "; reverse print(argv[1]); }
Definition
[comp151]
39
• A definition introduces a variable’s or a function’s name and type. • A variable definition reserves a number of bytes of memory for the variable. • A function definition generates codes for the function. • In both cases, definitions causes memory to be allocated to store the variable or function. • An object must be defined exactly once in a program.
Declaration
[comp151]
40
• The declaration of a variable announces that the variable exists and is defined somewhere else (in the same file, or in a different file). The connection is made when the object files are linked. • A declaration consists of the variable’s name and its type preceded by the keyword extern. • A declaration does not generate codes, and does not reserve memory. • There can be many declarations for an object name in a program. • If a declaration is used in a file different from that with the definition of the object, the linker will insert the real memory address of the object instead of the symbolic name. • In C++, a variable must be defined or declared to the program before it is used.
Advantages of Header Files
[comp151]
41
In general, a header file provides a centralized location for • external object declarations • function declarations • class definitions • inline function definitions The advantages are: 1. By including the header files, all files of the same piece of software are guaranteed to contain the same declaration for a global object or function. 2. Should a declaration require updating, only one change to the header file needs be made.
[comp151]
Variables
42
A variable is a symbolic name assigned to some memory storage. • The size of this storage depends on the type of the variable. e.g. char is 1-byte long and int is 4byte long. • The difference between a variable and a literal constant is that a variable is addressable.
2000 2004
354
:x
76
:y
[comp151]
lvalue & rvalue
43
[ interpretation of " x = x + 1 " ]
x:
x + 1
A variable has dual roles, depending on where it appears in the program, it can represent • lvalue: the location of the memory storage • rvalue: the value in the storage They are so called because a variable represents an lvalue (a rvalue) if it is written to the left (right) of an assignment statement. Thus, the following are invalid statements in C++: 4 = 1; grade + 10 = new_grade;
Comp151
Pointers
[comp151]
Pointers: Introduction
int x = 361; int* y = &x;
0x1a2b
44
0x1ffa
:y
361
:x
• Related to the concept of “objects” (c.f. objects in OOP and “links” in HTML documents/webpages). • Two main uses: 1. indirect addressing (as in assembly language programming) 2. dynamic memory allocation
0x1ffa
Use of Pointers: Dynamic Memory Allocation [comp151] #ifndef IQ1 H #define IQ1 H
#include #include ”iq1.h”
#include
int main() { IQ∗ x = new IQ("Newton", 200); IQ∗ y = new IQ("Einstein", 250);
class IQ { private: char name[20]; int score; public: IQ(const char∗ s, int k) { strcpy(name, s); score = k; } void smarter(int k) { score += k; } void print() const { cout "( " name " , " score " )" endl; } }; #endif // IQ1 H
x→print(); y→print(); return 0; }
x
Newton 200
y
Einstein 250
45
[comp151]
Use of Pointers: Indirect Addressing #include #include ”iq1.h”
#include #include ”iq1.h”
int main() { int choice; IQ x("Newton", 200); IQ y("Einstein", 250);
int main() { int choice; IQ∗ iq ptr; IQ x("Newton", 200); IQ y("Einstein", 250);
cin choice; if (choice == 1) { x.smarter(50); x.print(); } else { y.smarter(50); y.print(); }
cin choice; if (choice == 1) iq ptr = &x; else iq ptr = &y; iq ptr→smarter(50); iq ptr→print();
}
Newton 200
y:
Einstein 250
return 0; }
return 0;
x:
46
[comp151]
Pointers: Operations
47
Two basic unary operations: • Referencing, &: &x ⇒ address of variable x. • Dereferencing/Indirection, ∗: ∗x ⇒ value of the memory location stored in variable x. Other operations: +, −, ++, −−, new, delete int x = 1, y = 2, ∗w; w = &x; y = ∗w; ∗w = 3;
// w now points to x // c.f. y = x // c.f. x = 3
IQ∗ x = new IQ("Newton", 200); x→print(); // record fields accessed thru pointers (∗x).print(); // record fields accessed thru non-pointers
Pointers and Records: Linked List Example
[comp151]
48
#ifndef IQ2 H #define IQ2 H #include class IQ { private: char name[20]; int score; IQ∗ next;
a
Ada 100
Bob 50
Christy 60
public: IQ(const char∗ s, int k) { strcpy(name, s); score = k; next = NULL; } IQ∗ get next() { return next; } void add(IQ∗ p) { next = p; } void smarter(int k) { score += k; } void print() const { cout "( " name " , " score " )" endl; } }; #endif
// IQ2 H
Pointers and Records: Dynamic Data
[comp151]
49
Dynamic structures (e.g. linked list, binary tree) that grow/shrink during execution can be implemented using records and pointers. • new : to create a new record • delete : to destroy a previously allocated record e.g. To add Angela between Ada and Bob:
IQ* p = new IQ(‘‘Angela’’, 70); // allocation of *p p->add(a->get_next()); // p->next = a->next a->add(p); // a->next = p; e.g. To “fire” Bob: IQ* b = a->get_next(); a->add(b->get_next()); delete b;
// b = a->next; // a->next = b->next; // deallocation of *b
Pointers: Common Bugs! (1)
[comp151]
50
e.g. To add Angela between Ada and Bob: IQ* p = new IQ(‘‘Angela’’, 70); // allocation of *p a->add(p); // a->next = p; BUG! Bob is lost! p->add(a->get_next()); // p->next = a->next;
Memory Leak: when there is garbage — allocated storage/structure which becomes inaccessible.
[comp151]
Pointers: Common Bugs! (2)
51
e.g. To “fire” Bob: IQ* b = a->get_next(); a->add(b->get_next()); delete b; cout << b->name;
// // // //
b = a->next; a->next = b->next; deallocation of *b BUG! What is b?
Dangling Pointer: points to storage that is being used for another purpose; typically, the storage has been freed.
Pointers and Arrays In C++, the name of an array is also treated as a pointer to its first element.
[comp151]
52
a
a[0]
a+1
a[1]
a+2
a[2]
&a[j] = a+j
a[j]
• a = &a[0]. • a + j is the same as &a[j]. • ∗(a + j) is the same as a[j] • Thus, any operation that can be achieved by array subscripting can also be done with pointers. • The meaning of (a + j) is: new address k base address (a) + sizeof(object) ∗j.
a+(N-1)
a[N-1]
Pointers and Arrays: Examples char∗ strcpy(char∗ destination, const char∗ source) { char∗ d = destination; while (∗source != ’\0’) { ∗destination = ∗source; ++destination, ++source; } ∗destination = ’\0’; return d; } char∗ strcpy2(char∗ destination, const char∗ source) { char∗ d = destination; while (∗source != ’\0’) ∗destination++ = ∗source++; ∗destination = ’\0’; return d; }
[comp151]
53
Dynamic Allocation of Multi-Dimensional Arrays[comp151]
To dynamically allocate (T**) a a 2-D (N + 1) × (M + 1) a+1 array of IQ during runtime:
(T*) a[0] a[1]
54
a[0][0] a[0][1]
a[0][M]
a[1][0] a[1][1]
a[1][M]
a[N][0] a[N][1]
a[N][M]
IQ∗∗ a = new (IQ∗) [N+1]; for(int i = 0; i < N+1; i++) a[i] = new IQ [M+1]; a+N
a[N]
Summary • Uses of pointer – Dynamic memory allocation – indirect addressing • Pointer operations: +, −, ++, −−, new, delete • Problems of memory leak and dangling pointer • Dynamic (multi-dimensional) array
[comp151]
55
Comp151
Reference Variables
[comp151]
Creating a Reference Variable
56
A reference is an alternative name (alias) for an object. int j = 1; int& r = j; int x = r; r = 2;
// Now r = j = 1 // Now x = 1 // Now r = j = 2
• The notation X& means reference to X. • A reference must always be bound to an object. Therefore, it must be initialized when it is created: int j = 1; int& r1 = j; int& r2;
// Ok // Error
Initialization & Assignment
[comp151]
57
Distinguish between initializing a reference and making an assignment to it: int j = 10; int& r = j; ++r; j += 8; r = 7;
// both r and j become 11 // both r and j become 19 // both r and j become 7
cout j " , " &j endl; cout r " , " &r endl;
• The object which a reference refers to cannot be changed after initialization. It always refers to the object it was initialized to. • Assignment only changes the “value” of its referenced object.
Call-By-Reference and Reference Arguments
[comp151]
58
Reference arguments are a special case of references: int f(int& i) { ++i; return i; } int main() { int j = 7; cout f(j) endl; cout j endl; }
Quiz: Rewrite the above program using C pointers so that both programs produce the same effect.
Call-By-Reference and Call-By-Value
[comp151]
59
In C++, an argument to a function may be passed by 2 methods: • call-by-reference (CBR) • call-by-value (CBV) int f(int i) { ++i; return i; } int main() { int j = 7; cout f(j) endl; cout j endl; }
• In the call f(j), i is created similarly to the construction: CBR CBV int& i = j; int i = j; • Note that in CBV, any change to i will not result in any change to j.
Two Reasons for Call-By-Reference
[comp151]
60
1. When changes in the value of arguments are to be modified by the function caller. 2. If you pass a function argument by value, the function gets a local copy of the argument. So there is no inadventent modification. For large objects, copying is expensive. Passing an object by reference does not require copying. But it may modify function arguments. How to solve this problem? class Large Obj { public: // Lots of data members int height; }; void print height(const Large Obj& LO){ cout LO.height(); } int main() { Large Obj dinosaur(50); print height(dinosaur); }
Pointer vs. Reference
[comp151]
61
Reference can be thought as a special kind of pointer, but there are 3 big differences to remember! • A pointer can point to nothing (NULL), but a reference is always bound to an object. • A pointer can point to different objects at different times (through assignments). A reference is always bound to the same object. Assignments to a reference does NOT change the object it refers to but only the value of the referenced object. • The name of a pointer refers to the pointer object. The * or -> operators have to be used to access the object. The name of a reference always refers to the object. There are no special operators.
Example: Pointer vs. Reference IQ w("Maxwell", 180); IQ x("Newton", 200); IQ∗ a = 0; IQ& y = x; IQ& z;
[comp151]
62
// Ok: ’a’ bound to nothing // Ok: ’y’ is an alias of ’x’ // Error: uninitialized ref var!
a = new IQ("Einstein", 250); // Ok: ’a’ points to ”Einstein” a = new IQ("Galileo", 190); // Ok: ’a’ now points to ”Galileo” y = w; // ’y’ is STILL an alias of ’x’ NOT ’w’; // the value of ’w’ is copied to ’y’ (’x’) a→smarter(10); (∗a).print(); y.smarter(20); y.print();
Comp151
const-ness
const
[comp151]
63
• const: used in C++ to express a user-defined constant — a value that can’t be changed. const float PI = 3.1416; • Constant variables are usually written in capital letters. • In the old days, constants are defined by the #define preprocessor directive: #define PI 3.1416 Any shortcomings? • The const keyword can be regarded as a safety net for programmers. If an object should not change, make it a const object; the compiler will issue an error message if you try to change a const object.
Example: Constants of Basic Types
[comp151]
#include <stdlib.h> const int NUM STUDENTS = 117; const int MAX GRADE = 100; void assign grade randomly() { srand(time(0)); int grade[NUM STUDENTS]; for (int i = 0; i < NUM STUDENTS; ++i) grade[i] = (double(rand())/RAND MAX) ∗ (MAX GRADE + 1) }
64
Example: Constant Objects class Date { int year, month, day; Date(int, int, int); int difference(const Date&); void add month() { month += 1; }; };
[comp151]
65
// day, month, year
int main() { const Date job start(1,4,1998); Date x(25,2,2002); // How long have I worked in UST in days? cout "Today I have worked " x.difference(job start) " days.\n"; // What about next month? job start.add month(); // Error, but caught by compiler cout "In a month I’ll have worked " x.difference(job start) " days.\n"; }
const and Pointers
[comp151]
66
When using a pointer, two objects are involved: the pointer itself, and the object pointed to. • The syntax for pointers to constants and constant pointers can be confusing. The rule is that any const to the left of the ∗ in a declaration refers to the object pointed to; any const to the right of the ∗ refers to the pointer itself. • It can be helpful to read these declarations from right to left. char c = ’Y’; char ∗const cpc = &c; char const∗ pcc; const char∗ pcc2; const char ∗const cpcc = &c; char const ∗const cpcc2 = &c;
Example: const and Pointers void example(char∗ p) { char s[] = "COMP171"; char p[] = "MATH101";
67
// Same as char ∗const s = ”COMP171” // Same as char ∗const p = ”MATH101”
const char∗ pcc = s; pcc[5] = ’5’; pcc = p;
// Pointer to constant char // Error! // OK, but what does that mean?
char ∗const cpc = s; cpc[5] = ’5’; cpc = p;
// Constant pointer // OK // Error!
const char ∗const cpcc = s; cpcc[5] = ’5’; cpcc = p; }
[comp151]
// Constant pointer to constant char // Error! // Error!
const and Pointers . . .
[comp151]
68
Having a pointer-to-const pointing to a non-const object doesn’t make that object a constant! int i = 151; i += 20;
// OK
int∗ pi = &i; ∗pi += 20;
// OK
const int∗ pic = &i; ∗pic += 20; pic = pi; ∗pic += 20;
// Error! Can’t change i through pic // OK // Error! Can’t change ∗pi thru pic
pi = pic; // Warning: assignment to ‘int ∗’ from ‘const int ∗’ discards const
const: References as Function Arguments
[comp151]
69
While there are 2 good reasons (what are they?) to pass an argument as a reference, you can (and should!) express your intention to leave a reference argument of your function unchanged by making it const. This has 2 advantages: 1. If you accidentally try to modify the argument in your function, the compiler will catch the error: void cbr(Larg Obj& LO) { LO.height += 10; } void cbcr(const Larg Obj& LO) { LO.height += 10; }
// Fine
// Error!
const: References as Function Arguments . . .
[comp151]
70
2. You can call a function that has a const reference parameter with both const and non-const arguments. Conversely, a function that has a non-const reference parameter can only be called with non-const arguments. void cbr(Larg Obj& LO) { cout LO.height; } void cbcr(const Larg Obj& LO) { cout LO.height; } int main() { Large Obj dinosaur(50); const Large Obj rocket(100); cbr(dinosaur); cbcr(dinosaur); cbr(rocket); cbcr(rocket); }
// Error
const: Member Functions
[comp151]
71
To indicate that a class member function does not modify the class object, one can (and should!) place the const keyword after the argument list. class Date { int year, month, day; public: int get day() const { return day; } int get month() const { return month; } // Non-const function void add year(int y); };
Summary
[comp151]
It is good practice to make: • objects that you don’t intend to change const. const double PI = 3.1415927; const Date HandOver(1,7,1997);
• function arguments that you don’t intend to change const. void print height(const Large Obj& LO){ cout LO.height(): }
• class member functions that do not change the object const. int Date::get day() const { return day; }
72