C++: General Concepts
A compiled, Object Oriented, statically typed programming language that predates most others. Some quirks:
- C++ programs will execute even if they don't compile!
- C++ supports Pointers and Dereferencing.
- C++ divides files into Class Definitions (suffixed
.hfiles) and their implementations (suffixed.cppfiles). - C++ often requires more explicit use of Garbage Collecting and memory management (especially w.r.t. the Heap).
Runtime Environment
Use gcc through Xcode (if on a Mac) and cmake for compilation and executation of C++ code:
$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.16)
Target: x86_64-apple-darwin19.4.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ cmake --version
cmake version 3.16.6
CMake suite maintained and supported by Kitware (kitware.com/cmake).
Console Out
cout << “hello world”;
Note that the
<<operator is the concatenation operator (.or+in other languages) it's outputs data to the console.>>receives data (from saycin).
Resources and Links
Code samples:
C++: Files and OOD
There are no Interfaces or Abstract Classes in C++. However:
filename.ha Class Definition that’s pre-compiled (since it's not likely to change).filename.cppthe implementation of that Class Definition (with full method definitions/implementations).::is the scope resolution operator and defines the implemented methods of the class definition.:is the subclassing operator (equivalent of theextendskeyword in Java).
Constructors
C++ Constructors come in a few flavors:
- Default Constructors - C++ supported default constructors that are automatically supplied for each Class. It's convenient to think of these as akin to the
@NoArgs,@AllArgsannotations in Spring. - Customized Constructors - developer-supplied and defined constructors.
- Copy Constructors - used internally or explicitly invoked to initialize a new Object with the values of another member of the same Class.
Refer to: https://en.cppreference.com/w/cpp/language/default_constructor, https://en.cppreference.com/w/cpp/language/copy_constructor
For example, given ExampleClass.h, ExampleClass.cpp, and main.cpp:
ExampleClass.h:
// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_H // if not already defined, add - otherwise it will be ignored
namespace ExampleNamespace {
class ExampleClass {
public:
// even explicitly specifying constructor here defines a custom
ExampleClass();
ExampleClass(int _a, int _b);
int a, b;
void exampleMethod();
};
}
#endif
ExampleClass.cpp:
// example_class implementation
#include <iostream> // header in standard library
#include "ExampleClass.h" // header in custom library
using namespace ExampleNamespace; // now you don't have to prepend the namespace when calling methods, etc.
using namespace std;
// implement class method without class syntax here
void ExampleNamespace::ExampleClass::exampleMethod() {
a = 5;
b = 100;
std::cout << "I'm a console out message from ExampleClass exampleMethod()" << endl;
}
// custom default constructor
ExampleNamespace::ExampleClass::ExampleClass() {
a = 100;
b = 100;
}
// custom constructor
ExampleNamespace::ExampleClass::ExampleClass(int _a, int _b) {
a = _a;
b = _b;
}
main.cpp:
#include <iostream>
#include "ExampleClass.h"
using namespace ExampleNamespace;
using namespace std;
int main() {
try {
ExampleClass ec; // custom default constructor
std::cout << &ec << " with " << ec.a << " " << ec.b << std::endl;
ExampleClass ecc(1,1); // custom constructor
std::cout << &ecc << " with " << ecc.a << " " << ecc.b << std::endl;
ExampleClass * eccc = new ExampleClass; // pointer with custom default constructor
std::cout << eccc << " with " << (*eccc).a << " " << (*eccc).b << std::endl;
delete eccc;
ExampleClass * ecccc = new ExampleClass(111,111); // pointer with custom constructor
std::cout << ecccc << " with " << (*ecccc).a << " " << (*ecccc).b << std::endl;
delete ecccc;
// copies - keeps different addresses
ExampleClass copyExample;
copyExample.a = 1000;
copyExample.b = 1000;
ExampleClass otherCopyExample;
otherCopyExample.a = 1111;
otherCopyExample.b = 1111;
std::cout << copyExample.a << " " << copyExample.b << " at: " << ©Example << " " << otherCopyExample.a << " " << otherCopyExample.b << " at: " << &otherCopyExample << std::endl;
copyExample = otherCopyExample;
std::cout << copyExample.a << " " << copyExample.b << " at: " << ©Example << " " << otherCopyExample.a << " " << otherCopyExample.b << " at: " << &otherCopyExample << std::endl;
// memory assignment - note how the memory addresses for the variables remain distinct despite assigning their pointers to each other.
ExampleClass x;
x.a = 111;
x.b = 111;
ExampleClass * xx = &x;
ExampleClass y;
y.a = 1000;
y.b = 1000;
ExampleClass * yy = &y;
std::cout << x.a << " " << x.b << " at: " << &x << " " << xx << " " << y.a << " " << y.b << " at: " << &y << " " << yy << std::endl;
xx = yy;
std::cout << x.a << " " << x.b << " at: " << &x << " " << xx << " " << y.a << " " << y.b << " at: " << &y << " " << yy << std::endl;
std::cout << (*xx).a << " " << (*xx).b << " at: " << xx << " " << (*yy).a << " " << (*yy).b << " at: " << yy << std::endl;
// references
ExampleClass refCopyExample;
refCopyExample.a = 1000;
refCopyExample.b = 1000;
ExampleClass & otherRefCopyExample = refCopyExample;
std::cout << refCopyExample.a << " " << refCopyExample.b << " at: " << &refCopyExample << " " << otherRefCopyExample.a << " " << otherRefCopyExample.b << " at: " << &otherRefCopyExample << std::endl;
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Class Object Assignments
Remember that objects created in the Stack do not automatically persist in the Heap. One illuminating topic is how objects in two distinct functions can be assigned.
ExampleClass methodOne() {
ExampleClass ec;
return ec;
}
int main() {
ExampleClass ecc;
ecc = methodOne();
}
Will the above throw an error? No, the following process is performed:
- Default Constructor called in both
methodOne()(forec) andmain()(forecc). - Copy Constructor is called when returning
methodOne()(this link two discrete events on the Stack). - Assignment Operator is called to copy the "innards" from
ectoecc(values are copied fromectoecc).
Access modifiers
Can be declared in classes (for encapsulated class fields) using the following:
// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_One_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_One_H // if not already defined, add - otherwise it will be ignored
namespace ExampleNamespace {
class ExampleClassOne {
public:
int num;
void exampleMethodOne();
};
}
#endif
Class Methods
- Class methods don't have to be explicitly declared within the body of a Class Definition. (It's convenient to think of this as akin to a Function declaration that's place above any
classkeyword in ES6+.) - Separate Class implementation files can therefore be omitted.
- Similarly, the Class Definition file needn't contain a
classkeyword at all.
Example One
ExampleClassOne.h:
// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_One_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_One_H // if not already defined, add - otherwise it will be ignored
namespace ExampleNamespace {
class ExampleClassOne {
public:
int num;
void exampleMethodOne();
};
}
#endif
main.cpp:
// example_class implementation
#include <iostream> // header in standard library
#include "ExampleClassOne.h" // header in custom library
using namespace ExampleNamespace; // now you don't have to prepend the namespace when calling methods, etc.
using namespace std;
// implement class method without class syntax here
void ExampleClassOne::exampleMethodOne() {
cout << "I'm a console out message from ExampleClassOne exampleMethodOne()" << endl;
}
// executable code (must be wrapped in main method)
int main() {
try {
ExampleClassOne exampleOne;
cout << "Review the random number assigned here: " << exampleOne.num << endl;
exampleOne.num = 2;
exampleOne.exampleMethodOne();
cout << exampleOne.num << endl;
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/6%20-%20class
Example Two
add.h:
#ifndef Example_Class_H
#define Example_Class_H
int add(int x, int y)
{
return x + y;
}
#endif
main.cpp:
#include <iostream>
#include "add.h"
int main() {
try {
std::cout << "The sum of 3 and 4 is " << add(3, 4) << '\n';
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/3%20-%20dependency
Inheritance
Given a pair of Class Definitions and their implementations: BaseClass and SuperClass.
SuperClass.h:
#include <iostream>
#include "SuperClass.h"
using namespace ExampleNamespace;
using namespace std;
void SuperClass::superClassMethod() {
num = 500;
std::cout << "superClassMethod() called in SuperClass " << num << std::endl;
}
SuperClass.cpp:
#ifndef SUPER_CLASS_H
#define SUPER_CLASS_H
namespace ExampleNamespace {
class SuperClass {
public:
int num;
virtual void superClassMethod();
};
}
#endif
BaseClass.h:
#ifndef BASE_CLASS_H
#define BASE_CLASS_H
#include "SuperClass.h"
// Specify an associated Namespace - this is akin to a package in Java
namespace ExampleNamespace {
class BaseClass: virtual public SuperClass {
public:
int num;
void baseClassMethod();
// Note that this may throw a warning - it can be ignored
// warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
void superClassMethod() override;
void superEquivalentMethod();
};
}
#endif
BaseClass.cpp:
#include <iostream>
#include "BaseClass.h"
using namespace ExampleNamespace;
using namespace std;
void BaseClass::baseClassMethod() {
num = 3;
std::cout << "baseClassMethod() " << num << std::endl;
}
void BaseClass::superClassMethod() {
num = 7;
std::cout << "superClassMethod() override " << num << std::endl;
}
void BaseClass::superEquivalentMethod() {
SuperClass::superClassMethod();
std::cout << "superEquivalentMethod() called in BaseClass " << num << std::endl;
}
main.cpp:
// simple classless executable with function.
#include <iostream>
#include "BaseClass.h"
using namespace ExampleNamespace;
int main() {
try {
ExampleNamespace::BaseClass bc;
bc.baseClassMethod();
bc.superClassMethod();
bc.superEquivalentMethod();
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/8%20-%20inheritance
Resources and Links
- https://en.cppreference.com/w/cpp/language/default_constructor
- https://en.cppreference.com/w/cpp/language/copy_constructor
Code samples:
- https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/7%20-%20constructors
- https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/6%20-%20class
- https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/3%20-%20dependency
- https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/8%20-%20inheritance
C++: Memory
Generally, C++ requires and offers more precise control over memory use in terms of Garbage Collection, variable declaration, and memory addressing.
Garbage Collection
- No automatic Garbage Collection (like in Java) - variables are not automatically removed from memory.
- Unlike Java, C++ distinguishes between Heap and Stack memory (for Garbage Collection). In Java, application memory is allocated and automatically Garbage Collection in the Heap (which exists as an environment resource). Java's Stack is a derivative object implemented as an LIFO Dequeue and created within the application.
- C++ will automatically clear Stack memory and variables (e.g. - variables within the scope of a completed function will be removed from the Stack).
- For Heap memory (memory that persists longer than the lifespan of a specific function), using
delete,free(),malloc(), etc. is required. - Use
newto allocate memory to Heap and delete to remove (recommended). Usenewif a variable must be passed between functions.
Pointers and References
Consider:
int num = 0;
- Variables hold values.
- Reference operator gets the address from a variable holding a value.
&num //address of value of num
Pointer variables hold addresses.
Note: The
&symbol can also be used in variable declaration to essentially create another name or alias for a variable already declared.
int* pointer_num = # //address of value of num
- Dereferencing returns the value of the address stored in a pointer variable.
*pointer_num; //0
Tip: think of
**as being the same as no*(the two cancel out) - akin to double-negation elimination.
Example
#include <iostream>
int main()
{
try {
// ------------------- variable with value -------------------
int num = 100;
// note using std::cout explicitly here instead of the using keyword at top of file
std::cout << "num " << num << std::endl;
// ------------------- pointer variable with a reference to the address of the variable above -------------------
int *numAddress = #
std::cout << "numAddress " << numAddress << std::endl;
// ------------------- dereference the address to get the value back -------------------
int derefNum = *numAddress;
std::cout << "derefNum " << derefNum << std::endl;
*numAddress = 42;
std::cout << "numAddress " << numAddress << std::endl;
std::cout << "*numAddress " << *numAddress << std::endl;
// ------------------- Reference variables -------------------
int & refVar = derefNum;
std::cout << "refVar to derefNum " << refVar << std::endl;
// ------------------- heap example #1 -------------------
// declare a pointer variable using new keyword - which automatically (always) assigns memory to the heap
int * exampleA = new int;
std::cout << "Initialized to last value on heap: " << exampleA << " " << *exampleA << std::endl;
delete exampleA;
// ------------------- heap example #2 -------------------
// declare a pointer variable and allocate a memory address in heap
int * heapVariable = (int*) malloc(1);
// assign a value to the pointer variable that doesn't exceed the specified size
heapVariable[0] = 45;
std::cout << "Heap assigned value " << heapVariable[0] << std::endl;
std::cout << "Heap pointer variable / address " << heapVariable << std::endl;
// return the allocated memory block back to the heap
free(heapVariable);
// ------------------- heap example #3 -------------------
// declare a pointer variable using new keyword - which automatically assigns memory to the heap
int * newVar = new int;
std::cout << "Note the value initialized to is " << newVar << " " << *newVar << std::endl;
*newVar = 1000;
std::cout << "newVar " << *newVar << " at " << newVar << std::endl;
// declare a pointer variable assigning NULL
int * nullVar = NULL;
// use delete keyword only for variables declared with new keyword or NULL
delete newVar;
delete nullVar;
// ------------------- null pointer versus NULL -------------------
// NULL is a value that can be assigned to a variable
// null pointer keyword specifies a null address - technically, 0x0
int * pointerVar = nullptr;
// Cannot access nor delete - will throw error or exit code 11 if you attempt either
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
// main() must always return an exit code
return 0;
}
C++: Template Functions
C++ supports Template Functions which are akin to using Java Generics in Method definitions.
Example
ExampleClass.h:
#include <iostream>
#ifndef EXAMPLE_CLASS_H
#define EXAMPLE_CLASS_H
using namespace std;
namespace ExampleNamespace {
// only one type need be flexibly declared
template<typename T> class ExampleClass {
public:
int num;
T flexibleVar;
// best to define these in the same class with template<typename T> declaration
void exampleMethodOne() {
cout << "exampleMethodOne() " << flexibleVar << " " << typeid(flexibleVar).name() << endl;
}
T flexibleMethodOne(T a) {
cout << "flexibleMethodOne() " << a << " " << typeid(a).name() << endl;
return a;
}
T flexibleMethodTwo(T a, T b) {
T result = a + b;
cout << "flexibleMethodTwo() " << result << " " << typeid(result).name() << endl;
return a + b;
}
};
}
#endif
main.cpp:
#include <iostream>
#include "ExampleClass.h"
using namespace std;
using namespace ExampleNamespace;
// Within the definition of a Function
template<typename V>
V standaloneExampleMethod(V x) {
return x;
}
int main() {
try {
ExampleClass<string> ec;
string random = "I am a random string";
ec.flexibleVar = random;
ec.num = 0;
ec.exampleMethodOne();
ec.flexibleMethodOne("text");
ec.flexibleMethodTwo("hello","world");
std::cout << "My values are " << ec.flexibleVar << " " << ec.num << '\n';
ExampleClass<int> ecc;
ecc.flexibleVar = 5;
ecc.num = 100;
ecc.flexibleMethodTwo(1,2);
std::cout << "My values are " << ecc.flexibleVar << " " << ecc.num << '\n';
string a = standaloneExampleMethod("hello");
int b = standaloneExampleMethod(5);
std::cout << "Flexible template values " << a << " " << b << '\n';
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}