This document is Copyright (c) Virgil Mager, |
struct structTag {..};
struct MyType {..};
MyType myObject;
struct MyType { int i; float f;} s1, s2;
MyType myFunction(MyType s) { return s; }; // this function actually receives a strcuture as argument,
// and returns a structure as its return value
s1={1, 2.3}; // initialize a structure
s2 = myFunction(s1); // this line exemplifies the full range of structure passing and assignment
// (function argument passing, return value, and structure assignment - the "s2=..")
// C++ expression:
a + b * -c;
// functional representation:
SUM(a, PROD(b, NEGATE(c)));
a + b + c; // this expression translates into 'SUM( SUM(a,b), c)', which is evaluated as follows:
// - the "top-level" function is the first 'SUM' function
// - this expression has only one "leaf" function, which is the second 'SUM' function
// first the "leaf" 'SUM(a,b)' function is evaluated
// - 'a' and 'b' are transferred to the "leaf" 'SUM' function
// - the "leaf" 'SUM' value is calculated
// - the result of the 'SUM' is returned to the expression evaluator
// then the "top-level" 'SUM' function is evaluated
// - 'c' and the result returned by the "leaf" 'SUM' are transferred to the "top-level" 'SUM'
// - the "top-level" 'SUM' value is calculated
// - the result of the "top-level" 'SUM' is returned to the expression evaluator
// finally, result returned by the "top-level" 'SUM' is also the result of the entire expression
void byValue(int i);
void byReference(int &i);
// function definitionIn order to evaluate the 'byValue()' function in this example, the following steps occur:
void byValue (int a) {
printf("%d", a);
}
// invoking the function
float f=12.34;
byValue(f);
// function definitionIn order to evaluate 'byReference(i)', the function evaluator passes *by reference* the integer data 'i' to the function 'byReference()', and thus *the function will access the variable 'i' directly* by means of its aliasing argument 'a'. The result of executing 'byReference(i)' is that *the variable 'i' will be incremented*, even though this variable is *not part the function*
// NOTE: the argument delaration 'int &a' specifies that:
// - 'a' is an integer argument
// - *and* that the argument initialization is 'by reference'
void byReference(int &a) {
a++;
};
// invoking the function
float f =2.3;
int i = 4;
byReference(f); // this will NOT compile: the integer argument 'a' cannot be *an alias* for a float
byReference(i); // OK: the integer 'i' can be *aliased* by the integer argument 'a' of 'byReference()'
enum MyColors {red, green, blue};
void myFunction(MyColors color) {
if (color == red) {..}
};
[some code]
{ // this is the start of a new (un-named) namespace
int a; // variable 'a' belongs to the namespace and cannot be accessed from outside the namespace
[some code]
{ // this is the start of a new (nested) namespace
int a; // the variable 'a' in this namespace hides the variable 'a' from the containing namespace
int b; // variable 'b' belongs to the namespace and cannot be accessed from outside the namespace
[some code]
};
};
[some code]
enum MyAlternatives {all, nothing};
MyAlternatives useTheEnumValues(bool b) {
if b return all; // the 'all' and 'nothing' values can be accessed from within the function,
else return nothing; // i.e. they are not hidden inside a namespace
};
namespace MyNamespace {
int myInt=5; // being declared at level 0, this is an "ordinary" static variable,
// but it cannot be accessed directly by simply using its name
enum MyFlags {run, stop}; // the enum values are declared directly inside MyNamespace
};
int myFunction1(MyNamespace::MyFlags flags) {
if (flags == MyNamespace::run) {..};
return MyNamespace::myInt;
};
namespace OuterNamespace {
namespace InnerNamespace {
static int myInt;
};
};
int f() {
return OuterNamespace::InnerNamespace::myInt;
};
namespace MyNamespace {
identifier_1; // this declares 'identifier_1' as belonging to 'MyNamespace'
};
[some code]
namespace MyNamespace {
identifier_2; // this "merges" 'identifier_2' with 'identifier_1' inside 'MyNamespace';
// thus, 'MyNamespace' now contains both 'identifier_1' and 'identifier_2'
};
int myFunction2(MyNamespace::MyFlags flags) {
using MyNamespace::run; // this makes visible MyNamespace::run inside this namespace (i.e. in the function body)
// note: more than one identifier can be specified by separating them with commas
if (flags == run) {..}; // no need to specify MyNamespace::run because 'run' has already been identified
return MyNamespace::myInt; // here it is required to specify the namespace of 'myInt'
};
using namespace MyNamespace; // this makes visible all the MyNamespace idetifiers
// in the current namespace, i.e. at the top level of the program
int myFunction3(MyFlags flags) {
if (flags == run) {..};
return myInt;
};
struct MyStruct {
char c; // data field, like in C
float myFunction(float f); // method inside structure, specific to C++
};
struct MyStruct {
char c;
inline float myFunction(float f) {return f*f;};
};
// non-inline definition of a method
float MyStruct::myFunction(float f) {
return f*f;
};
float staticFloatVariable=1.2; // a "level 0" static variable
void f(int * ip) {
staticFloatVariable=3.4; // alter a static variable outside the function body
*ip=5; // modify the location of an integer in memory as pointed by 'ip'
};
// declare an object with an internal function (i.e. an object method)
struct MyType {
int value;
void changeValue(int i);
};
// define vhat the internal function does (it can alter the object's components)
void MyType::changeValue(int i) { value=i; };
{ // somewhere in the program...
MyType myObject; // now myObject effectively exists in memory
myObject.changeValue(2); // the 'changeValue' function is strictly "attached" to myObject,
// and it changes the 'value' field of myObject
};
// re-write the 'changeValue()' method to use the 'this' pointernote: "uncovering" the 'value' member in the line 'this->value = value' could have also been done via explicit name qualification instead of using the run-time 'this' pointer: 'MyType::value = value;'
void MyType::changeValue(int value) {
// here the 'MyType' structure member 'value' is hidden
// by the 'value' argument; however, the 'value' member
// can be directly accessed through the 'this' pointer
this->value = value;
};
{ // somewhere in the code
MyType myObject;
myObject.changeValue(2); // the 'this' pointer inside the 'changeValue()' method
// references (at run-time) the 'myObject' instance
};
// declaring a structure with a static property and a static method
struct MyType {
static int si;
int i;
void myMethod(int j); // "normal method"
static void myStaticMethod(int j); // static method: can access 'si' but not 'i';
};
MyType::myMethod(int j) {si=i=j;}; // can access both static and non-static properties
MyType::myStaticMethod(int j) {si=j;}; // can *only* access the static properties
{ // program flow using the static method described above.
MyType::myStaticMethod(10); // this will set the 'si' static property to 10
MyType myObject; // declare a new object of type MyType; it will have si==10
myObject.myMethod(2); // will set 'i' to 2, and also change 'si' to 2
}
struct MyType {The special syntax '::*' is introduced for declaring a global variable (or a local variable inside a function/method) which is a pointer to the method myMethod of the type MyType above:
int* myMethod(char* s, float f);
};
int* (MyType::*myMethod_ptr)(char* s, float f); // use the qualified name notation for the method pointerIn order to initialize the method pointer (i.e. to specify to which method of a given object type it should point), the following syntax will be used:
// (note how *myMethod_ptr can now "stand for" myMethod)
myMethod_ptr=&MyType::myMethod; // the method poiner is initialized with the qualified name of myMethodIn order to invoke 'myMethod()' *of a given object myObject* (of type MyType) via the method pointer 'myMethod_ptr', the special syntax '.*' and '->*' is introduced:
(myObject.*myMethod_ptr)(s, f); // myObject is an object instance (e.g. an automatic variable)Thus, a valid code snippet for using a method pointer may look as follows:
(myObject->*myMethod_ptr)(s, f); // myObject is a pointer to an object
struct MyType {
void myMethod(int i);
};
int main(int argc, char **argv) {
MyType myObject;
void (MyType::*myMethod_ptr)(int)=&MyType::myMethod; // declare and initialize the method pointer
(myObject.*myMethod_ptr)(argc); // invoke 'myMethod' by using the method pointer
}
struct MyType {
int* (MyType::*myMethod_ptr)(int i); // note that the fully qualified name has to be used,
// even though 'myMethod_ptr' is part of the object
int* myMethod(int i);
};
int main (int argc, char **argv) {
MyType myObject;
myObject.myMethod_ptr=&MyType::myMethod; // initialize the method pointer
(myObject.*(myObject.myMethod_ptr))(argc); // invoke myMethod via the method pointer
// note that myMethod_ptr is a property of myObject
}
struct MyType {
int* (MyType::*myMethod_ptr)(int i);
int* myMethod(int i);
void callerMethod(char c);
};
void MyType::callerMethod(char c) {
myMethod_ptr=&MyType::myMethod; // initialize the method pointer (which is a property of MyType)
(this->*myMethod_ptr)((int)c); // use 'this->*myMethod_ptr' to specify the target object
// note: the "full" notation would be (this->*(this->myMethod))(...)
}
struct MyType {
int* (MyType::*myMethod_ptr)(int i);
int* myMethod(int i);
};
struct CallerObjectType {
MyType *obj; // CallerObjectType contains a pointer to an object of type MyType
void callerObjectMethod(char c);
};
void CallerObjectType::callerObjectMethod(char c) {
(obj->*(obj->myMethod_ptr))((int)c); // invoke myMethod of object 'obj' via object 'obj's myMethod_ptr
// note: it is assumed that both the object pointer 'obj',
// and object 'obj's myMethod_ptr are already initialized
}
int myFunction (const int i) {..}; // 'i' receives the parameter value
// but cannot be modified inside the function
char* const s = (char*)calloc(N, sizeof(char)); // allocates memory for a string of chars;
// *the pointer 's' is declared as 'const'*
// (cannot be changed) and initialized with
// the string's start address
strcpy(s,"12345"); // legal: the allocated memory area itself is *not* const,
// only the pointer variable 's' itself is a const
free(s); // legal (same as above)
for (i=0; i< N; i++) s++; // illegal! the 's' pointer is declared as 'const'
const char* s = (char*)calloc(N, sizeof(char)); // allocates memory for a string of chars;
// *the characters to which 's' points
// are declared 'const', and not 's'*
strcpy(s,"12345"); // illegal: the allocated memory area itself *is* const and cannot be changed
// only the pointer variable 's' itself is a not const
free(s); // illegal: cannot 'free' the 'const' memory area
for (i=0; i< N; i++) s++; // legal: the 's' pointer itself is *not* declared as 'const'
int returnValue() { // this is equivalent with declaring: 'const int returnValue()'
return 10;
};
const int& returnReference() { // here 'const' must be specified if one wants to have a 'const'
static int si; // returned value; if it is not specified then the return value
return si; // is *not* a 'const' because the function *does not return
}; // a temporary value*, but rather the static variable 'si' itself
// function declarations
void f(const int& i) {..};
void g(int& i) {..};
{ // code invoking the functions
int i=2;
const int c=3;
f(i); // legal: 'i' is non-const and it can get aliased as 'const int'
g(i); // legal: 'i' is non-const and gets aliased as 'int'
f(c); // legal: the 'c' variable is a 'const' and gets aliased as 'const int'
g(c); // illegal: the 'c' variable is a 'const' and cannot get aliased as non-const 'int'
f(i+2); // legal: the result of 'i+2' is a 'const' that gets aliased as 'const int'
g(i+2); // illegal: the result of 'i+2' is a 'const' and cannot get aliased as non-const 'int'
}
struct A {
int i;
A& set(A&);
};
A& A::set(A& a) {
i = a.i;
return *this;
};
{ // code using A
A a, b, c;
a.set( b.set(c) ); // first 'b.set()' returns 'b' as non-const reference,
// which is accepted by 'c.set()' as a non-const input
}
struct A {
int i;
const A& set(const A&);
};
const A& A::set(const A& a) {
i = a.i;
return *this;
};
{ // code using A
A a, b, c;
const A d = {1};
a.set( b.set(c) ); // first 'b.set()' takes a non-const argument and uses it internally
// via a 'const' alias, and then it returns 'b' as a 'const' reference;
// finally, 'a.set()' takes the 'const' value returned by 'b.set()' as
// argument via a 'const' alias, and then returns 'a' as a 'const' reference
a.set(d); // 'a.set()' takes a 'const 'argument and aliases it to a 'const'
}
struct A {
int i;
const A& set(A&);
};
const A& A::set(A& a) {
i = a.i;
return *this;
};
{ // code using A
A a, b, c;
const A d = {1};
a.set( b.set(c) ); // here a.set() will fail:
// first 'b.set()' takes a non-const argument and uses it internally
// via a non-const alias, and then it returns 'b' as a 'const' reference;
// finally, 'a.set()' tries to take the 'const' value returned by
// 'b.set()' as argument via a non-const alias, which will fail to compile
a.set(d); // this fails to compile when 'a.set()' tries to take the 'const' argument
// 'd' and alias it to a non-const
}
// declaring two functions that may return a const reference, a non-const referecne,
// or an (always const) temporary object, *but take a const reference as argument*
f(MyType const &argument) {..};
[const-or-not] MyType [&-or-not] g(MyType const &argument) {..};
{ // code using the functions declared above
[const-or-not] MyType x;
g(x); // g(x) is valid no matter if 'x' is declared as 'const' or non-const
f(g(x)); // this expression is valid no matter if g() returns a 'const' or non-const
}
{
MyType const myObject = {..};
int i = myObject.anIntMember; // legal: can read the members of a 'const' object
myObject.anIntMember = 10; // *illegal*: cannot modify tyhe members of a 'const' object
}
struct MyType {
int i;
static int si;
void set(int i);
int myFunction(void) const; // the declaration specifies that this function
// cannot modify any non-static members (in our case 'i');
// still, it can modify static members (in our case 'si')
};
/* *illegal* implementation
int MyType::myFunction(void) const { //
i++; // illegal! function was declared 'const', so it cannot modify structure members
return i;
}; */
// legal implementation
int MyType::myFunction(void) const {
int j=i;
j++; // legal: local variables inside the function can be modified (obviously)
si++; // legal: static members *can* be changed by 'const' methods
return j;
};
int myFunction (int i) {..};
char* myFunction (char* s) {..};
[xxx] myFunction (char* s) {..}; // illegal! the C++ compiler will report
// 'multiple definitions for function myFunction(char*)
void aCallerFunction(void) {
int i=10;
char* s="123";
i=myFunction(i); // the 'int myFunction (int i)' will be invoked
s=myFunction(s); // the 'char* myFunction (char* s)' will be invoked
};
// declare and define a ComplexNumber data type
struct ComplexNumber {float r; float i;};
// declare and define the overloaded '-' operator for complex numbers (as defined above):
// for binary operator, the first argument of the overloading function is the operator's left argument,
// and the second argument of the overloading function is the operator's right argument;
// for unary operators, the argument of the overloading function is the operator's argument
// no matter if the argument is on the left or on the right of the operator;
// this also means there can be only one function for overloading a unary operator
// that can have both a left or right argument ('++' and '--')
ComplexNumber operator- (ComplexNumber const& cx1, ComplexNumber const& cx2) {
ComplexNumber cxResult;
cxResult.r=cx1.r - cx2.r;
cxResult.i=cx1.i - cx2.i;
return cxResult; // uses the C++ facility that allows returning a structure
};
// declare and define a ComplexNumber data type
// this object contains an internal definition for ComplexNumber + ComplexNumber
// and for ComplexNumber + float
struct ComplexNumber {
float r;
float i;
ComplexNumber operator+ (ComplexNumber const& cx);
void operator+= (float f);
};
ComplexNumber ComplexNumber::operator+ (ComplexNumber const& cx) {
ComplexNumber cxResult=cx;;
cxResult.r += r;
cxResult.i += i;
return cxResult; // uses the C++ facility that allows returning a structure:
// a copy of cxResult is returned
};
void ComplexNumber::operator+= (float f) {
r += f;
i += f;
};
// use the '+' and '+=' operators defined above
ComplexNumber myFunction (ComplexNumber const& cx1, ComplexNumber const& cx2) {
cx1+=3.14; // cx1+=3.14 is equivalent to cx1.operator+=(3.14)
return (cx1 + cx2); // cx1 + cx2 is equivalent to cx1.operator+(cx2)
};
struct MyType {
float f;
operator int();
operator ComplexNumber();
};
MyType::int() { // convert MyType to integer
return (int)f;
};
MyType::ComplexNumber() { // convert MyType to ComplexNumber
ComplexNumber cx;
cx.i = cx.r = f;
return cx;
};
ComplexNumber f(ComplexNumber const& cx, MyType const& m) {
return cx + m; // 'm' will be automatically converted to ComplexNumber such that
// the 'ComplexNumber::operator+' argument types can be marched
};
struct MyType {
int i;
bool was_used;
void operator=(MyType const& s) { // this overloads the structure assignment 'operator='
i=s.i; // by copying only the 'int' value from the source structure
was_used=false; // to the destination, while the destination's 'bool' element
}; // will be set to 'false'
} s1, s2;
s1={1, true}; // initialize a structure
s2 = s1; // the overloaded operator= is used for assignment,
// and as a result s2.i will be 1 and s2.was_used will be 'false'
struct MySquareType {
int size;
void resize(int new_size);
};
void MySquareType::resize(int new_size) {size=new_size;};
int main() {
MySquareType myFirstSquare; // 'size' is as yet undefined
myFirstSquare.resize(10); // now the square size is set to 10
printf("the size of a square side: %i\n", myFirstSquare.size);
};
struct MyType {
MyType(); // the default constructor is a function having no return type (not even 'void'),
// and having the name of the structure and taking no arguments
[properties and other methods]
};
MyType::MyType() { // definition of the default constructor
[initialize (some of) the object's properties each time a new object is constructed]
};
struct MyType {
MyType(); // the default constructor (as described above)
~MyType(); // destructor: syntax uses the structure name preceded by a '~'
// the destructor has no parameters and no return type (not even 'void')
[properties and other methods]
};
MyType::~MyType() { // definition of the destructor
[specify various clean-up operations to be performed right before deallocating the object]
};
struct MyType {
MyType(); // "default constructor": constructor notation uses the object name
~MyType(); // "default destructor": destructor notation uses the object name preceded by '~'
char* string;
void set_string(char* s);
};
MyType::MyType() {
string=NULL; // set an initial value (NULL is just as an example, could have been somthing else)
};
void MyType::set_string(char * s) {
string=calloc(strlen(s)+1, sizeof(char));
strcpy(string, s);
};
MyType::~MyType() {
if (string) free(string);
};
void myFunction(void) {
MyType* myFirstObject = new MyType; // this will declare and allocate
// an object via 'new'
MyType mySecondObject; // this will declare and allocate
// an object via simple variable declartion
myFirstObject->set_string("first"); // set the string value of 'string'
// inside 'myFirstObject'
mySecondObject.set_string("second"); // set the string value of 'string'
// inside 'mySecondObject'
[..]
delete myFirstObject; // 'myFirstObject' must be explicitly deleted from memory
// (becuase it was declared via the 'new' operator)
// 'mySecondObject' is automatically deleted when the
// program flow reaches the end of its "namespace scope"
// (i.e. the namespace where it was declared, which in this
// case is the end of the body of 'myFunction()')
};
struct MyType {
MyType(); // default constructor
MyType(int); // constructor taking an integer argument
int i; // an integer structure member
}
MyType::MyType() {i=0;};
MyType::MyType(int i) {this->i=i;};
void myFunction() {
MyType myObject_1; // automatic variable, default constructor
MyType myObject_2(12); // automatic variable, constructor with argument
MyType* myObject_3 = new MyType; // object instantiated with 'new', default constructor
MyType* myObject_4 = new MyType(5); // object instantiated with 'new', constructor with argument
[..]
delete myObject_3; // objects declared with 'new' can be deleted
delete myObject_4; // (no matter what type of constructor was used)
};
struct MyType {
int flag;
MyType (int i) {flag=i;}; // default constructor
MyType (MyType &X) {flag=-1;}; // the copy constructor
void operator <<(MyType b) {flag=b.flag;}; // arg by value, copy constructor invoked
void operator =(MyType& b) {flag=b.flag;}; // arg by reference, copy constructor not invoked
};
{ // code using MyType
MyType a(0), b(1); // a.flag==0, b.flag==1
a << b; // b is passed by value, and thus 'b's copy that is passed
// to 'operator<<' is built using the copy constructor;
// thus, a.flag will become '-1'!
a = b; // now 'b' is passed by reference, and thus no copy of it
// is made when it's passed to 'operator=';
// thus, a.flag will become '1' from the value of 'b.flag'
}
MyType myFunction(void) {
return * new MyType(0);
};
{ // code showing some effects of having the copy constructor return a 'const'
MyType x(0);
x = myFunction(); // this line *will not compile* because myFunction returns a
// 'const' object that is being received by a non-const reference.
// HOWEVER, if the declaration of 'MyType::operator=(MyType&)'
// changes to MyType::operator=(const MyType&) then
// this line of code compiles OK
x << myFunction(); // because the result of myFunction is passed by value
// to the 'MyType::operator<<(MyType)' this line might
// seem that it should compile OK, but it DOESN'T!
// The result of myFunction is indeed a 'const MyType' value,
// but now this value needs to be used for initializing the
// argument of 'MyType::operator<<(MyType)' which is of type
// MyType. Since a copy constructor is defined for MyType,
// the initialization *has to go thrwough the copy constructor*,
// but the copy constructor is defined as taking a non-const
// reference as argument, so a match cannot be made with the
// 'const' value returned by myFunction.
// HOWEVER, changing the copy constructor to take a const argument
// 'MyType::MyType(const MyType&)' allows this line to compile OK
};
struct A { // a simple structure that will be a property of a container structure
int i;
A()
A(int i);
};
struct C {
A a; // structure C contains as member an object 'a' of type A
C();
C(int i);
};
// Constructor syntax used to invoke constructors of member objects
C::C(): a() {..}; // the C() constructor will *first* invoke the constructor for 'a'
// and then proceed with executing its own code
C::C(int i): a(i) {..}; // similar with the above, but this time it's not the default constructor
// example if structure C above would contain two members 'a' and 'b' of type A:
C::C(int i): a(i), b(2*i) {..};
// given the example above, the following redefinitions of C::C(int) are equivalent:
C::C(int i): a() {..}; // explicit invocation of the default constructor a() *is redundant*
C::C(int i) {..}; // implicit invocation of the default constructor a()
// considering the above example, let us add destructor functions for both A and C:
A::~A() {cout << "[A destructor] ";};
C::~C() {cout << "[C destructor] ";};
void f() {
C* c = new C;
delete c; // this code line will print on cout: [A destructor] [C destructor]
cout << "\n";
};
void myFunction() {
MyType myMatrix[10][30]; // array constructed via object declaration
MyType* myArray = new MyType[20]; // array constructed via the 'new' operator
[..]
};
void myFunction () {
MyType* myArray = new MyType[10];
[..]
delete[] myArray; // the size of the array to delete must not be specified
};
B
/ \
D D1
| |
E E1
// base type
struct MyVehicle {
Color color; // the color of the vehicle
MyVehicle();
MyVehicle(Color color);
~MyVehicle();
void paint(Color color); // the function that paints the vehicle
bool legal_to_be_driven(void);
};
MyVehicle::MyVehicle() {color=none; };
MyVehicle::MyVehicle(Color color) {this->color = color; };
MyVehicle::~MyVehicle() {};
void MyVehicle::paint(Color color) {this->color = color; };
bool MyVehicle::legal_to_be_driven(void) {if (color==none) return false; else return true; };
// derived type
struct MyRegVehicle: MyVehicle { // 'my registered vehicle' is derived from 'my vehicle'
RegistrationNumber registration_number;
MyRegVehicle();
MyRegVehicle(Color color, RegistrationNumber registration_number);
~MyRegVehicle();
bool legal_to_be_driven(void); // base function redeclared in the derived structure: details below
};
MyRegVehicle::myRegVehicle() {RegistrationNumber = unregistered; };
MyRegVehicle::~myRegVehicle() {};
myRegVehicle::myRegVehicle(Color color, RegistrationNumber registration_number)
: MyVehicle(Color color) {
this->registration_number = registration_number;
};
myRegVehicle::myRegVehicle(Color color, RegistrationNumber registration_number) {
paint(color);
this->registration_number = registration_number;
};
struct A { // a simple structure that will be a property of a container structure
int i;
A()
A(int i);
};
struct B { // structure B will be the base type for the container structure C
B();
};
struct C: B { // a structure derived from B that contains two objects of type A
A a1, a2;
C()
C(int i);
};
C::C(): B(), a1(), a2() {..}; // default constructor: specifies that the base constructor needs
// to be invoked, and also the constructors for the a1 and a2 properties
// note: explicitly invoking B(), a1(), and a2() here is redundant:
// 'C::C() {..};' would have exactly the same effect
C::C(int i): B(), a2(i) {..}; // similar to the as above, but for the non-default constructor
// note1: the A() default constructor is implicitly invoked for the a1 object
// note2: explicitly invoking the B() default constructor is redundant
// example of accessing a hidden base function from within the derived type's namespace
bool MyRegVehicle::legal_to_be_driven(void) {
if (MyVehicle::legal_to_be_driven()==false || registration_number==unregistered) return false;
else return true;
};
B {
void print() {printf("B\n"); };
};
D: B {
void print() {printf("D\n"); };
};
{ // code suing these data types
B b;
D d;
b.print(); // will print "B"
b = d;
b.print(); // will still print "B"
}
B {
void print() {printf("B\n"); };
};
D: B {
void print() {printf("D\n"); };
};
// a function that takes an argument of base type 'B' by reference
void f(B& b) {b.print();};
{ // code using the function f() in conjuntion with base and derived type objects
B b;
D d;
f(b); // prints "B"
f(d); // prints "D";
};
struct B {..} b;
struct D: B {..; int i;} d;
main() {
b=d; // allowed: any extra elements (e.g. 'i' above, etc) of 'd' are lost during the structure copy
};
struct B {
int i;
};
struct C {
int i;
operator B();
};
struct D: B {
int j;
operator B();
};
// define the derived type's converison operator to the base type
D::operator B() {printf ("explicit derived-to-base conversion\n"); return *(new B);};
// define the conversion operator from the unrelated C type to B
C::operator B() {printf ("explicit type conversion C-to-B\n"); return *(new B);};
// define a function that takes a base type argument; it will be passed a different argument
void f(B b) {
printf("in function 'f'\n");
}
{ // program code
B b;
C c;
D d;
b = d; // this will *NOT* print the "explicit derived-to-base conversion" message
// the default "lossy" structure-copy will be used for assignment
f(d); // again, this will *NOT* print the "explicit derived-to-base conversion" message
// the default "lossy" structure-copy will be used for passing the derived argument
b = c; // this *will* print "explicit type conversion C-to-B"
f(c); // this *will* print "explicit type conversion C-to-B"
}
struct D; // just declare 'D' because it needs to be referenced in 'B'
struct B {
int i;
B& operator=(D&);
};
struct D: B {
int j;
};
// define the base class' overloaded derived-to-base assignment operator
B& B::operator=(D& d) {printf ("explicit derived-to-base assignment\n"); return *this;};
{ // program code
B b;
D d;
b = d; // this will print "explicit derived-to-base assignment"
}
main () {
MyVehicle v, *vp=&v;
myRegVehicle r, *rp=&r;
v.legal_to_be_driven(); // invokes the base structure's function
vp->legal_to_be_driven(); // invokes the base structure's function
r.legal_to_be_driven(); // invokes the derived structure's function
rp->legal_to_be_driven(); // invokes the derived structure's function
rp->MyVehicle::legal_to_be_driven(); // invokes the base structure's function
// (because the fully qualified function name was used)
vp=rp; // now the "generic vehicle" pointer vp actually points to a specific
// (i.e. derived) type of vehicle (the registered vehicle)
vp->legal_to_be_driven(); // but 'vp->' still invokes the base structure's function:
// because the vp is a pointer of type MyVehicle*,
// *it doens't matter that it has been assigned a 'registered vehicle' value*,
// (or indeed any other value whatsoever)
(MyRegVehicle*)vp->legal_to_be_driven(); // invokes the derived structure's function
// because the '(MyRegVehicle*)vp'
// is a pointer of type myRegVehicle*
(MyRegVehicle*)vp->MyVehicle::legal_to_be_driven(); // invokes the base structure's function
// because although the '(MyRegVehicle*)vp'
// is actually a pointer to MyRegVehicle type,
// the qualified function name explicitly
// specifies the base function
};
bool legal_to_be_driven(void) --changes-to--> virtual bool legal_to_be_driven(void)then:
main () {
MyVehicle v, *vp=&v;
myRegVehicle r, *rp=&r;
v.legal_to_be_driven(); // same as with trivial redefinition: invokes the base function
vp->legal_to_be_driven(); // same as with trivial redefinition: invokes the base function
r.legal_to_be_driven(); // same as with trivial redefinition: invokes the derived function
rp->legal_to_be_driven(); // same as with trivial redefinition: invokes the derived function
rp->MyVehicle::legal_to_be_driven(); // same as with trivial redefinition: invokes the base function
// (because the qualified function name specifies the base function)
vp=rp; // now the "generic vehicle" pointer vp actually points to a specific
// (i.e. derived) type of vehicle (the registered vehicle)
vp->legal_to_be_driven(); // HERE THE VIRTUAL FUNCTIONS MECHANISM COMES INTO EFFECT: in this case
// when the legal_to_be_driven() has been declared 'virtual' in the base type,
// *IT DOES MATTER* that vp has been assigned with (i.e. it references)
// a derived type: the DERIVED class' function is invoked.
// In other words:
// if a *base-type pointer gets to point to a derived type*, then
// access to a virtual function member via this pointer will be "redirected"
// to the derived type's implementation of that virtual function
(myRegVehicle*)vp->legal_to_be_driven(); // same as with trivial redefinition:
// invokes the derived type's function
(myRegVehicle*)vp->MyVehicle::legal_to_be_driven(); // same as with trivial redefinition:
// invokes the base type's function
};
struct Polygon {
virtual float get_area(void) {return -1};
};
struct Triangle : Polygon {
virtual float get_area(void) { [compute triangle's area] };
};
float computePolygonArea(Polygon* p) { // 'p' is a generic polygon, i.e. the function can be called
// with 'p' actually pointing to a Polygon, a Triangle, or a Square
return p->get_area(); // the correct get_area() function is automatically called, no matter
// what type of object 'p' actually points to
};
main() {
Polygon myP, *p;
Triangle myT;
p = &myP; computePolygonArea(p); // the Polygon area calculation function is used (returns -1)
p = &myT; computePolygonArea(p); // the Triangle area calculation function is used
// if the get_area(void) wouldn't have been declared 'virtual'
// then the Polygon function would have been used
p->get_area(); // the Triangle area calculation function is used
// if the get_area(void) wouldn't have been declared 'virtual' then
// the Polygon function would have been used
}
struct B {
virtual void print() {printf("B\n");};
};
struct D: B {
int i; // derived type that does not redeclare the virtual function
};
struct DD: D {
virtual void print() {printf("DD\n");};
};
struct DDD: DD {
int j; // derived from DD that does not redeclare the virtual function;
// the first definition of the virtual function up in the hierarchy
// will be used, i.e. the 'DD:print()' implementation
};
main() {
B b;
D d;
DD dd;
DDD ddd;
B* bp;
bp=&b; bp->print(); // will print "B"
bp=&d; bp->print(); // will print "B"
bp=ⅆ bp->print(); // will print "DD"
bp=&ddd; bp->print(); // will print "DD"
}
main() {
myP = new Polygon;
myT = new Triangle;
Polygon* p;
p=myP; delete(p); // this will delete 'myP' by invoking its destructor
p=myT; delete(p); // if myP has a virtual destructor, then 'delete(p)' will
// invoke it and thus the Triangle destructor will be called;
// however, if the Polygon destructor has not been declared as 'virtual',
// then the Polygon destructor will be called (because 'p' was declared Polygon*)
}
virtual [funtion-declaration] =0;
virtual float get_area(void) {return -1;} --changes-to--> virtual float get_area(void) = 0
B1 ... BnThe syntax for deriving a type from several base types is very similar to the one used with "simple" inheritance:
| |
\ /
\ /
\ /
D
struct B1 {..};
struct Bn {..};
struct D: B1, Bn {..};
D::D(int a, int b, int c): // a constructor for a multiple inheritance type:
B1(a), B2(b), B2_member(c) // it explicitly invokes the constructors of
{ // two base classes, and the constructor for a
// member object 'B2_member' contained in class B2
[..]
};
// example of managing homonymous virtual functions residing in two base casses
DerivedType::virtualFunction(..) {
virtualFunctionResultForBaseType1=BaseType1::virtualFunction(..);
virtualFunctionResultForBaseType2=BaseType2::virtualFunction(..);
manageTheAggregateResult(virtualFunctionResultForBaseType1,
virtualFunctionResultForBaseType2);
};
Btype // a common base typeGiven the above situation, 'virtual derivation' is introduced to force the multipl inheritance derived type MDtype to *hold only one copy* of the elements of Btype. The syntax for virtual derivation requires that *both Dtype1 and Dtype2* are declared as 'virtually derived' from Btype:
/ \
/ \
/ \
| |
Dtype1 ... Dtype2 // two types derived from Btype
| |
\ /
\ /
\ /
MDtype // MDtype multiply derived from Dtype1 and Dtype2
struct Btype {..}; // a common base type
struct Dtype1: virtual Btype {..}; // two types virtually derived from Btype
struct Dtype2: virtual Btype {..};
struct MDtype: Dtype1, Dtype2 {..}; // MDtype multiply derived from Dtype1 and Dtype2
class MyType {
int privateVariable;
void privateFunction(void);
protected:
bool flagUsedOnlyByDerivedClasses;
public:
MyType();
MyType(MyType& X);
~MyType();
inline int getThePrivateVariable (void) const {return privateVariable; };
inline void setThePrivateVariable (int i) {privateVariable=i; };
};
class A {..};The difference between the derivation types consist in the protection level of the memebers of the derived class as they are inherited from the base class: given a member of a base class that has a given protection level, its protection level in the derived class is the most restrictive level between the original protection level of the member in the base class and the derivation type (of course, no matter what derivation type is used, the private members of the base class are not accessible by methods of the derived class).
class B: public A {..};
class C: protected A {..};
class D: private A {..};
class A {
int Private;
protected:
int Protected;
public:
int Public;
};
class B: public A {..}; // all the members of A have the same protection level in B
class C: protected A {..}; // both the 'Protected' and 'Public' members of A become protected in B
class D: private A {..}; // both the 'Protected' and 'Public' members of A become private in B
// note: in all of the above examples, the 'Private' member of A
// cannot be accessed from methods of B
calss A {..};
class B: A {..}; // this is equivalent to 'class B: private A {..};'
class MyType {
void * vp;
MyType();
~MyType();
MyType(MyType& X);
MyType& operator=(MyType& X);
};
MyType::MyType() {vp=NULL; };
MyType::MyType(MyType& X) {assert(0); };
MyType& MyType::operator=(MyType& X) {assert(0); };
class DoubleComplex {
double r, i;
friend std::ostream& operator<< (std::ostream &co, const DoubleComplex &dcx);
public:
DoubleComplex(double rr, double ii) {r=rr; i=ii;};
};
// the friend function:
// 1) it does not belong to the class (its name is not qualified with the class name)
// and its position in the class definition (under private, public, etc) is not relevant
// 2) it has unrestricted access to all members of the class (private, protected, and public),
// 3) there is no other way to overload the '<<' operator as defined inside the
// standard C++ library class 'ostream' except friend functions because: the "alternative"
// of modifying the class definition (and adding yet another overloaded operator
// for each new type of right-hand argument it is to accept) is obviously not acceptable
std::ostream& operator<< (std::ostream &co, const DoubleComplex &dcx) {
std::cout << "[" << dcx.r << ":" << dcx.i << "]";
return co; // return the 'co' argument such that the '<<' operators can be chained
};
void main() {
DoubleComplex dcx(1,2);
std::cout << dcx << "\n"; // will print [1:2] on the 'stdout'
}
Shape* pShapeNULL=NULL; // a Shape* initialized with NULL
Shape* pShape=&myShape; // a Shape* pointing to a Shape instance
Shape* pShapeIsTr=&myTriangle; // a Shape* pointing to a Triangle instance
Shape* pShapeIsCi=&myCircle; // a Shape* pointing to a Circle instance
dynamic_cast< Circle*>(pShapeNULL); // NULL pointer converts to NULL: cast returns NULL
dynamic_cast< Circle*>(pShape); // cannot convert up in the hierarchy: cast returns NULL
dynamic_cast< Circle*>(pShapeIsTr); // cannot convert on different branch: cast returns NULL
dynamic_cast< Circle*>(pShapeIsCi); // the Shape* pShapeIsCi points to a circle: cast OK
int main() { // 'main' codeblock starts here
[..]
{ // an internal codeblock inside 'main'
[..]
}
[..]
{ // another internal codeblock contained in the 'main' codeblock
[..]
myFunction(); // a function call will switch execution to the codeblock of 'myFunction',
// which itself may contain nested codeblocks, other function calls, etc
}
};
try { // start of the 'try' codeblock:
// the statements in the codeblock can include nested codeblocks
[..] // (including other 'try/catch' codeblocks), nested function calls
// (which can themselves contain 'try/catch' codeblocks), etc.
// a 'throw' statement can reside at any "level inside" these nested codeblocks
}
catch (ObjectType_1 variableType_1) { // start of the first 'catch' codeblock
[..] // 'varibaleType_1' si the "catcher variable"
}
catch (ObjectType_n variableType_n) { // start of the n-th 'catch' codeblock
[..] // 'varibaleType_n' si the "catcher variable"
}
catch (...) { // 'catch(...)' is the syntax for "catch all";
[..] // in this case there is no "catcher variable"
}
{ // a non-try/catch codeblockIn the example above, when 'myFunction' throws the object instance 'myObject' which is of type 'ObjectType2' the executition of the function is interrupted, and a check is made to see if the surrounding codeblock of the 'throw' statement is a 'try' codeblock. As this is not the case (because the surounding codeblock of the 'throw' statement is the function body's codeblock) the execution is transferred to the end of the one-level-up enclosing codeblock, which is the 'try' codeblock of the code that invoked 'myFunction'. At this point, because the codeblock is indeed a 'try' codeblock, the successive 'catch' statements are sequentially tested if they can "catch" 'myObject'. Sice the example above assumes that ObjectType2 cannot be converted to ObjectType1, it follows that:
[..]
try { // a try/catch codeblock
[..]
myFunction(); -------------------> myFunction() { // codeblock of 'myFunction'
ObjectType2 myObject;
[..]
throw myObject; // throw an instance
// of 'ObjectType2'
};
// because this is not a try/catch codeblock,
// a thrown object will be again thrown "one level up"
[..]
}
catch (ObjectType1 myVariable) { // assuming ObjectType2 can *not* convert to ObjectType1:
[..] // this can *not* catch an object of type ObjectType2
}
catch (ObjectType2 myVariable) { // this *can* catch an object of type ObjectType2
[..]
}
catch (...) { // 'catch (...)' is the syntax for "catch all",
[..] // it *could* (but will not) catch an object of type ObjectType2
}
[..] // code to be executed after the 'try/catch' codeblock
}
// declaration: in this example MyType must provide the '=' and '+=' operators
template < typename MyType>
MyType myFunction(MyType a[], int N) { // calculate the sum of the elements of an array
MyType sum=0;
for (int i=0; i< N; i++) sum+=a[i];
return sum;
}
// the above declaration does *not* declare a function 'myFunction(..)'
// but rather a function that must *always* be parameterized when invoked,
// i.e. 'myFunction(..)' can never be used in the program after the above definition
// because the syntax 'myFunction< ATypeName>(..)' will *always* be required:
// function call
{
[..]
int arraySum = myFunction < int>(myIntArray, 10); // invokes the function parameterized with
[..] // the data type being 'int'
}
// declaration
template< typename T, int N>
struct A {
T a[N]; // N must be a compile-time constant, so this syntax is valid
T get_min();
A* get_this();
};
// the above declaration does *not* define a data type 'A' but rather
// a "type of types" that must *always* be parameterized when used anywhere
// in a program after the ending '}' of the template object definition,
// i.e. 'A' can never be used as-such in the program after the above definition
// and the syntax 'A< ATypeName, anIntValue>(..)' will *always* be required:
// definition of a member function of the template-based class
template< typename T, int N>
T A< T, N>::get_min() {
T m = a[0];
for (int i=0; i< N; i++)
m = m< a[i] ? m : a[i];
return m;
};
// remark: in the above definition of the 'get_min(..)' member function
// the class name prefix to 'get_min(..)' is *not* 'A::' but rather the fully
// parameterized version 'A< T, N>::' because a non-parameterized syntax 'A'
// simply does not designate a valid data type; only a fully parameterized
// 'A< T, N>' actually refers to a "real" data type.
template< typename T, int N>
A< T, N>* A< T, N>::get_this() {
return this;
};
// same remarks w/r to the syntax of the template function definition as above
// using template-based classes
{
[..]
A< int, 100> a1; // an automatic variable
A< long, 10>* a2p; // a heap variable accessible via pointer
[..]
int min_a1 = a1.get_min();
long min_a2 = a2p->get_min();
[..]
};
// general template class declaration
template< typename T>
class Pair {
public:
T a, b;
};
// specialization: the specialized version of a templates-based class definition
// does *not* have to be identical with the general form of the class; actually,
// a completly different data type *may* be defined when specializing a class template
template< > // this syntax specifies that a template specialization follows
class Pair< char> { // when specializing templates one need to explicitly parameterize the declaration
public:
char a, b;
char get_max(char& a, char& b) {return a > b ? a : b;};
};
// remark: this class definition that explicitly specifies the template parameter
// does *not* declare a data type 'Pair', but rather it defines the specific data type
// 'Pair< char>' which is a "real" data type that can be used further in the program
// declaring a template-based function with default value
template< typename T=int>
class MyType {
T t;
};
// using a template-based function with default value
{
[..]
MyType< > myObject; // this declares 'myObject' as being of type 'MyType< int>'
[..]
}