Reference is an alias to an already-existing object or function.
Init is required:
int ival = 1024;
int &refVal = ival; //refval refers to (another name for) ival
int &refVal2; //ERROR: a reference must be initialized
Reference is just an alias:
//int& ref1 =1;//non-const lvalue reference cannot bind to a temporary
const int& ref2 = 2; //OK
int val = 3;
int& ref3 = val; //OK
A pointer is a type pointing to anothe type. A pointer is basically the same as common variables, storing a piece of data. Unlike normal variable which stores a value, e.g. int, double or char, a pointer stores a memory address.
Pointers are simply addresses. Deferencing a pointer involves copyting that pointer into a register, and then using this register in a memory reference.
A special pointer type that can hold the address of any object of any data type. But, the type of the object at that address is unkown.
We cannot use a void*
to operate on the object it addresses.
Generally, we use a void*
pointer to deal with memory as memory, rather than using the pointer to access object stored in that memory.
int value = 1024;
void *voidPtr = &value;
//cout << *voidPtr << endl; //ERROR: canot dereference
int *intPtr = static_cast<int*>(voidPtr);
cout << *intPtr << endl; //now, dereference as normal
A reference is not an object. Hence, we cannot have a pointer to a reference. However, we can define a reference to a pointer.
int i =42;
int *p; //p is a pointer to int
/* read from right to left:
* (a). &r means r is a reference
* (b). * means r refers to a pointer
* (c). int means pointer points to an int
* together: r is a reference to a pointer to an int
*/
int *&r = p; //r is a reference to the pointer p
r = &i; //r refers to a pointer; assigning &i to r makes p point to i
*r = 0; //dereferencing r yields i, pointed by p; changes i to 0
Array can hold objects of most any type, including pointers.
Array is an object, and thus we can define both pointers and references to arrays.
int *ptrs[10]; //ptrs is an arr of ten ptrs to int
int &refs[10] = /* ? */; //ERROR: no arr of refs
int (*Parray)[10] = &arr; //Parray points to an arr of ten ints
int (&arrRef)[10] = arr; //arrRef refers to an arr of ten ints
int *(&arry)[10] = ptrs; //arry is a reference to an arr of ten points to int
Operations on arrays are often really operations on ptrs.
when we use an array as an initializer for a var defined using auto, the deduced type is a ptr, not an arr:
int ia[] = {0,1,2,3,4,5,6,7,8,9}; ia is an arr of ten ints
auto ia2(ia); //ia2 is an int* that points to 1st ele in ia
ia2 = 42; //ERROR: ia2 is ptr, can't assign int to ptr
The compiler does the underlying conversion as: auto ia2(&ia[0])
. The conversion does not happen for decltype:
//ia3 is an array of ten ints
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; //ERROR: can't assign int* to array
ia3[4] = i; //OK
Pointers are iterators
int *p = &ia[2]; //p points to the ele indexed by 2
int j = p[1]; //p[1] i.e. *(p+1), ia[3]
int k = p[-2]; //p[-2] is the same ele as ia[0]
MultiD array is actually array of arrays.
int ia[3][4]; //array if size 3; each ele is an arr of ints of size 4
int (*p)[4] = ia; //p points to an arr of 4 ints
p = &ia[2]; //p now points to the last ele in ia
A function ptr is just that - a ptr that denotes a func rather than an obj.
Like any other ptr, a func ptr points to a particular type.
A func's type is determined by its return type and the types of its paras.
The func's name is not part of its scope.
//type: bool(const string&, const string&)
bool lengthCompare(const string &, const string &);
-----------
//form: return_type (*func)(parameter list)
bool (*pf)(const string &, const string &); //uninit
When we use the name of a func as a value, the func is automatically converted to a pointer.
pf = lengthCompare; //pf points to the func
pf = &lengthCompare; //'&' is optional
We can use a ptr to a func to call the func to which the ptr points.
bool b1 = pf("hello", "goodbye"); //calls lengthCompare
bool b2 = (*pf)("hello", "goodbye"); //ditto
bool b3 = lengthCompare("hello", "goodbye"); //ditto
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; //pf1 points to ff(unsigned)
The compiler uses the type of the ptr to decide which overloaded func to use.
//3rd para is a func type and is auto treated as a ptr to func
void useBigger(const string &s1, const string &s2,
bool pf(const string &, const string &));
//equival decl: explicitly define the para as a ptr to func
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string &, const string &));
--------------
//auto converts the func to a ptr to func
useBigger(s1, s2, lengthCompare);
Type aliases, along with decltype enable simpler code to use func ptrs:
//Func and Func2 have func types
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2; //same type
//FuncP and FuncP2 have ptr to func type
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2; //same type
using F = int(int*, int); //F is a func type, not a ptr
using PF = int(*)(int*, int); //PF is a ptr type
Unlike what happens to paras that have func type, the return type is not auto converted to a ptr type.
Thus, we must explicitly specify that the return type is a ptr type:
PF f1(int); //OK: pf is a ptr to func; f1 returns a ptr to func
F f1(int); //ERROR: F is a func type; f1 can't return a func
F *f1(int); //OK: explicitly specify that the return type is a ptr to func
------------------
//we can directly declare f1
int (*f1(int))(int*, int);
//we can also simplify the decl using a trailing return
auto f1(int) -> int (*)(int*, int)
Const is to make a variable unchangeable.
a reference that refers to a const type. A reference to const cannot be used to change the bound object.
const int ci = 1024;
const int &r1 = ci; //OK: both ref and obj are const
r1 = 42; //ERROR: r1 is a reference to const
int &r2 = ci; //ERROR: nonconst ref to const obj
A reference to const may refer to an object that is NOT const
a reference to const resticts ONLY what we can do through that reference, but says nothing about whether the underlying object itself is const or not.
int i = 42;
int &r1 = i; //r1 bound to i
const int &r2 = i; //r2 also bound to i; but cannot change i
r1 = 0; //OK
r2 = 0; //ERROR: r2 is a reference to const
const double pi = 3.14; //pi is const, val cannot be changed
double *ptr = π //ERROR: ptr is a plain pointer
const double *cptr = π //OK: cptr points to a double that is const
*cptr = 42; //ERROR: cannot assign to *cptr
double dval = 3.14; //a plain double
cptr = &dval; //OK: but cannot change dval via cptr
int errNumb = 0;
int *const curErr = &errNumb; //curErr always points to errNumb
const double pi = 3.14;
const double *const pip = π //pip is a const ptr to a const obj
In OOP, a 'method' of an object has access to the member variables.
class Class1{
void Method1();
int MemberVariable;
}
//method1() has no explicit paras but it can still alter 'MemberVariable'
void Class1::Method1(){
MemberVariable1 += 1;
}
To avoid this, we can put const
after the para list:
class Class2{
void Method1() const;
int MemberVariable;
}
const int* const Method3(const int* const&) const;
const-1: returns a pointer to const
const-2: the returned pointer itself is const
const-3 and -4: parameter is a const pointer to a pointer, which points to a const int
const-5: the method cannot alter member fields.
A pointer is an object that can point to a different object. As a result, we can talk independently about whether a pointer is const and whether the objects to which it can point are const.
int i = 0;
int *const p1 = &i; //top-level, cannot change p1
const int ci = 42; //top-level, cannot change ci
const int *p2 = &ci; //low-level, cannot change *p2
const int *const p3 = p2; //left-low, right-top
const int &r = i; //low-level, cannot change i via r
top-level const is ignored when copying objs
i = ci; //OK: top-level const in ci is ignored
p2 = p3; //OK: top-level const in p3 is ignored
low-level const is never ignored
-- objs must have the same low-level const or there must be available conversion
-- in general, we can convert a nonconst to const
int *p = p2; //ERROR: p2 has low-level const but p doesn't
p2 = p3; //OK: same low-level const
p2 = &i; //OK: can convert int* to const int*
int &r = ci; //ERROR: cannot bind int& to a const int obj
const int &r2 = i; //OK: can bind const int& to plain int
Conceptually, constexpr indicates a value that's not only constant, it's known during compilation.
const int *p = nullptr; //p is a ptr to a const int
constexpr int *q = nullptr; //q is a const ptr to int
constexpr functions
constexpr
int pow(int base, int exp) noexcept{
...
}
//called in compilation
constexpr auto numConds = 5;
std::array<int, pow(3, numConds)> results;
//called in runtime
auto base = readFromDB("base");
auto exp = readFromDB("exponent");
auto baseToExp = pow(base, exp);