CSE332S Object-Oriented Programming in C++ (Lecture 3)
C++ basic data types
- int, long, short, char (signed, integer arithmetic)
- char is only 1 byte for all platforms
- other types are platform dependent
- can determine the size of the type by using
sizeof(),<climits> INT_MAX
- float, double (floating point arithmetic)
- more expensive in space and time
- useful when you need to describe continuous quantities
- bool (boolean logic)
User-defined types
- (unscoped or scoped) enum
- maps a sequence of integer values to named constants
- function and operators
- function is a named sequence of statements, for example
int main()
- function is a named sequence of statements, for example
- struct and class
- similar to abstractions in cpp, extend C struct
struct and class
- struct is public by default
- class is private by default
- both can have
- member variables
- member functions
- constructors
- destructors
- common practice:
- use struct for simple data structures
- use class for more complex data structures with non-trivial functionality
struct My_Data{
My_Data(int x, int y): x_(x), y_(y) {}
int x_;
int y_;
};class My_Data{
public:
My_Object(int x, int y): x_(x), y_(y) {}
~My_Object(){}
private:
int x_;
int y_;
};More about native and user-defined types
- Pointer
- raw memory address of an object
- its type constrains what types it can point to
- can take on a value of 0 (null pointer)
- Reference
- alias for an existing object
- its type constrains what types it can refer to
- cannot take on a value of 0 (always refer to a valid object)
- Mutable (default) vs. const types (read right to left)
const int x;is a read-only variableint jis a read-write declaration
Scopes
Each variable is associated with a scope, which is a region of the program where the variable is valid
- the entire program is a global scope
- a namespace is a scope
- member of a class is a scope
- a function is a scope
- a block is a scope
int g_x; // global scope
namespace my_namespace{
int n_x; // namespace scope
}
class My_Class{
int c_x; // class scope
int my_function(){
int f_x; // function scope
{
int b_x; // block scope
}
return 0;
}
}A symbol is only visible within its scope
- helps hide unneeded details (abstraction)
- helps avoid name collisions (encapsulation)
Motivation for pointer and reference
We often need to refer to an object, but don’t want to copy it
There are two common ways to do this:
- Indirectly, via a pointer
- This gives the address of the object
- Requires the code to do extra work. eg, dereferencing
- Like going to the address of the object
- Directly, via a reference
- Acts as an alias for the object
- Code interacts with reference as if it were the object itself
Pointer and reference syntax
Pointer
A pointer is a variable that holds the address of an object
can be untyped. eg, void *p;
usually typed. eg, int *p; so that it can be checked by the compiler
If typed, the type constrains what it can point to, a int pointer can only point to an int. int *p;
A pointer can be null, eg, int *p = nullptr;
We can change to what it points to, eg, p = &x;
Reference
A reference is an alias for an existing object, also holds the address of the object, but is only created on compile time.
Usually with nicer interface than pointers.
Must be typed, and its type constrains what types it can refer to. int &r;
Always refers to a valid object, so cannot be null. int &r = nullptr; is invalid.
Note: reference cannot be reassigned to refer to a different object.
| symbol | used in declaration | used in definition |
|---|---|---|
unary & | reference, eg, int &r; | address-of, eg, int &r = &x; |
unary * | pointer, eg, int *p; | dereference, eg, int *p = *q; |
-> | member access, eg, p->x; | member access via pointer, eg, p->second; |
. | member access, eg, p.x; | member access via reference, eg, p.second; |
Aliasing via pointers and references
Aliasing via reference
Example:
int main(int argc, char *argv[]){
int i=0;
int j=1;
int &r = i;
int &s = i;
r = 8; // do not need to dereference r, just use it as an alias for i
cout << "i: " << i << ", j: " << j << ", r: " << r << ", s: " << s << endl;
// should print: i: 8, j: 1, r: 8, s: 8
return 0;
}Aliasing via pointer
Example:
int main(int argc, char *argv[]){
int i=0;
int j=1;
int *p = &i;
int *q = &i;
*q = 6; // need to dereference q to access the value of j
cout << "i: " << i << ", j: " << j << ", p: " << *p << ", q: " << *q << endl;
// should print: i: 6, j: 1, p: 6, q: 6
return 0;
}Reference to Pointer
Example:
int main(int argc, char *argv[]){
int j = 1;
int &r = j; // r is a **reference** to j
int *p = &r; // p is a **pointer** to the address of r, here & is the address-of operator, which returns the address of the object
int * &t = p; // t is a **reference** to pointer p, here & is the reference operator, which returns the reference of the object.
cout << "j: " << j << ", r: " << r << ", p: " << *p << ", t: " << *t << endl;
// should print: j: 1, r: 1, p: 1, t: [address of p]
return 0;
}Notice that we cannot have a pointer to a reference. But we can have a reference to a pointer.
Reference to Constant
Example:
int main(int argc, char *argv[]){
const int i = 0;
int j = 1;
int &r = j; // r cannot refer to i, because i is a constant (if true, alter i through r should be valid)
const int &s=i; // s can refer to i, because s is a constant reference (we don't reassign s)
const int &t=j; // t can refer to j, because t is a constant reference (we don't reassign t)
cout << "i: " << i << ", j: " << j << ", r: " << r << ", s: " << s << ", t: " << t << endl;
// should print: i: 0, j: 1, r: 1, s: 0
return 0;
}Notice that we cannot have a non-constant reference to a constant object. But we can have a constant reference to a non-constant object.
Pointer to Constant
Example:
int main(int argc, char *argv[]){
const int i = 0;
int j = 1;
int k = 2;
// pointer to int
int *w = &j;
// const pointer to int
int *const x = &j;
// pointer to const int
const int *y = &i;
// const pointer to const int, notice that we cannot change the value of the int that z is pointing to, in this case j **via pointer z**, nor the address that z is pointing to. But we can change the value of j via pointer w or j itself.
const int *const z = &j;
}- Read declaration from right to left, eg,
int *w = &j;meanswis a pointer to anintthat is the address ofj. - Make promises via the
constkeyword, two options:const int *p;meanspis a pointer to a constantint, so we cannot change the value of theintthatpis pointing to, but we can change the address thatpis pointing to.int *const p;meanspis a constant pointer to anint, so we cannot change the address thatpis pointing to, but we can change the value of theintthatpis pointing to.
- A pointer to non-constant cannot point to a const variable.
- neither
w = &i;norx = &i;is valid. - any of the pointer can points to
j.
- neither
Pass by value, pass by reference, and type inference
Example:
int main(int argc, char *argv[]){
int h = -1;
int i = 0;
int j = 1;
int k = 2;
return func(h, i, j, &k);
}
int func(int a, const int &b, int &c, int *d){
++a; // [int] pass by value, a is a copy of h, so a is not the same as h
c = b; // [int &] pass by reference, c is an alias for j, the value of c is the same as the value of b (or i), but we cannot change the value of b (or i) through c (const int &b)
*d = a; // [int *] pass by value, d is a pointer to k, so *d is the value of k, a is assigned to value of k.
++d; // d is a pointer to k, but pass by value, so ++d doesn't change the value of k.
return 0;
}More type declaration keywords
typedef keyword introduces a “type alias” for a type.
typedef Foo * Foo_ptr; // Foo_ptr is a type alias for Foo *
// the following two variables are of the same type
Foo_ptr p1 = 0;
Foo *p2 = 0;auto keyword allows the compiler to deduce the type of a variable from the initializer.
int x = 0; // x is of type int
float y = 1.0; // y is of type float
auto z = x + y; // z is of type float, with initialized value 1.0decltype keyword allows the compiler to deduce the type of a variable from the type of an expression.
int x = 0;
double y = 0.0;
float z = 0.0f;
decltype(x) a; // a is of type int, value is not initialized
decltype(y) b; // b is of type double, value is not initialized
decltype(z) c; // c is of type float, value is not initialized