Overview
Primarily the imperative features and topics we've already introduced in the Sample Programs lecture
Topics
Declarations
A
declaration is a statement that introduces a name into the program. It specifies a type for the named entity:
- A type defines a set of possible values and a set of operations (for an object).
- An object is some memory that holds a value of some type.
- A value is a set of bits interpreted according to a type.
- A variable is a named object.
auto
- inferring type from initializer using
auto
- avoid having to write long/complicated type names
- avoid having to figure out the exact type when the semantics give you the basic idea
auto i = 17; // int — Doh
Rational r;
…
auto n = r.getNum(); // don't have to go look up the return type of 'getNum'
std::vector<SomeLongClassName> vect;
// an iterator for the above class has type std::vector<SomeLongClassName>::iterator
for (auto iter = vect.begin(); iter != vect.end(); iter++)
…
auto
was already a keyword in C
- Before C++ 1,
auto
was an anachronistic way of declaring a local variable (which automatically
goes in and out of scope lifetime upon entry/exit of the function)
bool
as integer type
char
- usually 8 bit
w_char
and other character types fill in the Unicode gap
- const / constexpr
- variables declared with the
const
modifier can only be assigned to once
constexpr
must be a compile-time value
Pointers
- pointer: memory address
- typed; must declare what type value the pointer is referencing
- too chaotic otherwise, referencing an integer value and then using it as a floating point is a nasty bug to catch
- with typed pointers, the compiler can perform proper conversions or flag an error
- requires explicit dereferencing
int i = 17;
int *ip = &i; // ip is a pointer to integer; & is 'address-of' operator
…
cout << ip; // prints out pointer
cout << *ip; // prints out object pointed to by the pointer; * - 'indirection'/'dereferencing' operator
…
(*ip)++; // increments what the pointer points at
ip++; // increments the pointer
- Note: the effect of incrementing a pointer — or performing other arithmetic operations on a pointer —
depends on the type of data the pointer is referencing; we will discuss this further when we present pointers in more detail.
Employee e; // e is of type Employee (class type)
Employee *ep = &e;
…
cout << ep; // "
cout << *ep; // "
…
//ep.getEmpId(); // illegal … ep is not an class object, it's a pointer
(*ep).getEmpId(); // dereference ep to get the class object, then use member selection (.
) operator
//*ep.getEmpId(); // also illegal … postfix operators have precedence, so this is *(ep.getEmpId())
ep->getEmpId(); // intuitive, convenient notation for member selection through a pointer
- an example of what is (erroneously) termed 'call-by-reference':
void swap(int *ip1, int *ip2) {
int templ = *ip1;
*ip1 = *ip2;
*ip2 = temp;
}
…
swap(&x, &y); // x and y are declared as int
- This is still call-by-value — the values that are being passed are the addresses of the integer objects to be swapped
- and it is those values that are copied when the function is called, and then destroyed when the function exits
- it is the indirection provided by passing pointers to the objects to be swapped — rather
than the objects themselves, that provides us with the desired semantics
- note the explicit presence and use of the operators and machinery (
*
, &
) associated with
pointers and indirection; this will NOT be the case with true call-by-reference (using references)
- the above is sometimes referred to as call-by-pointer … a somewhat misleading term
as it is still nothing more than call-by-value
Arrays
- require constant size
int a[10]; // note bracket location
const int MAX_SIZE = 100;
Employee a[MAX_SIZE];
- Usual subscripting semantics
int a[10] = {…}
for (int i = 0; i < 9; i++) {
cout << a[i];
}
- Can use a pointer to move through an array
int a[10] = {…}
int *ip = &a[0];
for (int i = 0; i < 9; i++) {
cout << *ip;
ip++;
}
- no bounds checking; no
length
field
References
- reference: abstraction of pointer
- pointers with transparent dereferencing; i.e., no explicit dereferencing necessary
- alias of another object
- no 'reseating'; i.e., once a reference is assigned a value, it's permanent
- no way of working with the reference itself
int i = 17;
int &ir = i; // MUST be initialized in this context; rarely used in this manner
…
ir++; // increments i
// This is the primary use for reference
void f(int &x, int &y); // true call-by-reference
…
f(a, b);
- and here is an example of true 'call-by-reference':
void swap(int &ir1, int &ir2) {
int temp = ir1;
ir1 = ir2;
ir2 = temp;
}
…
swap(x, y); // x and y are declared as int
- The compiler passes the addresses of the arguments (as references) to the parameters
- The reference parameters act as aliases to the arguments
- Note the absence of the pointer/indirection operators/machinery
- That's the whole point of the reference type — the compiler provides the referencing/dereferencing machinery behind the scenes
User-defined Types
Structures
- heterogeneous container
- 'elements' are usually called fields
- data only; no functions (behavior)
- pre-cursor to classes
struct Stack {
int arr[MAX_SIZE];
int tos;
}; // Note the ';'
- initially used as a means of declaring variables with a layout of heterogeneous fields
struct {
int id;
char name[100];
double gpa;
} student1, student2;
- then a tag (i.e., name for the layout) was added
struct Student {
int id;
char name[100];
double gpa;
} student1, student2;
- composition (i.e., fields that are themselves structures) is supported
struct Person {
int age;
Name name;
Address address;
};
Classes
class Stack {
public:
Stack(); // (default) constructor
void push(int val); // function headers
int pop();
bool isEmpty() const; // 'const' indicates the receiving object is not modified in function
private:
static int MAX_SIZE = 100; // static primitive type can be initialized 'in place'
int arr[MAX_SIZE]; // data members
int tos;
};
- data members (state) + member functions (behavior)
- encapsulation
- interface (class declaration — what you see above) is usually in a separate file
from implementation (bodies of member functions)
- the term 'interface' is used (informally) in a slightly different sense than the (formal) keyword / construct in Java
Member Initializer List
There is a specific syntax for the initialization of data members in a constructor:
class Counter {
Counter(int limit) : val(0), limit(limit) {…}
…
private:
int count;
int limit;
};
The notation
: val(0), limit(limit)
is a list of data-members followed by a parenthesized initializer:
data-member(
expression), …
- We will see this notation is necessary for proper initialization in the context of composition and inheritance
Enumerations
enum class DayOfWeek {SUN, MON, TUE, WED, THU, FRI, SAT}
…
DayOfWeek dow;
…
dow = DayOfWeek::WED;
Modularity
- interface vs implementation
- declaration vs definition
- function header (declaration) vs function body (declaration)
- separate compilation
- (
.h
vs .cpp
) files
- function headers (interface) goes in
.h
files
- function definitions/bodies (implementation) goes in
.cpp
files
- namespaces
Exception Handling
- any type is suitable to be thrown as an exception
throw "Stack underflow"; // we'll stick with 'throw string("Stack underflow") for the moment
const int PAGE_NOT_FOUND = 404; // in the context of other int error 'codes'
throw PAGE_NOT_FOUND;
throw RationalException("division by 0"); // a exception class (like Java)
- invariant / precondition — basic assumptions about an entity's
semantics
- a precondition to a stack pop is that the stack is not empty
- an invariant about the
Rational
class is that the value is in lowest normal form
- exception-handling allows us to enforce invariants and preconditions
- a consequence of modular programming is that the point at which the error occurs and where it should/can be handled
are not the same
- exception-handling — in particular the semantics of throw/catch — allow us to take advantage of this separation
Advice
- [1] Don't panic! All will become clear in time; §2.1.
- Motherhood and apple pie again.
- [2] You don't have to know every detail of C++ to write good programs; §1.3.1.
- C programmers don't know C++-specific features, but were still able to write 'just fine' programs (e.g. Unix)
- Java doesn't have all the features of C++, but they can also write 'just fine' programs (and vice versa).
- [3] Focus on programming techniques, not on language features; §2.1.