C++#
Basics#
Statements, Variables, I/O, Operators, Literals, Type Safety, typedef, Breaking Up Code
Switch, Loops, Functions, Errors, Exceptions, Debugging, Program Development
Program Evaluation, Namespaces, Struct, Enumerations, Overloading, Lambda Expressions
process of developing a program: analysis, design, programming, testing
implementation: programming + testing, programming is part practical, part theoretical
when approaching software, do it in a principled and serious manner, need to be part of the solution, not add to the problems
since
main()
is called by the system, the return value won’t be used, but on systems like Unix/Linux, it can be used to check program exit statussource code or program text: what we read an write
executable, object code or machine code: what the computer executes, object code files have suffix .obj (on Windows) or .o (Unix), and not portable among systems
linker: links the compiled object code files of separate parts, translation units, to form an executable
compile-time errors: found by the compiler
link-time errors: found by the liker
run-time or logic errors: not found until the program is run
keep_window_open()
: needed on some Windows machines to prevent closing the window instantlyobject: region of memory with a type that specifies what kind of information can be placed
declaration/definition distinction allows to separate program into many parts that can be compiled separately
a program usually contains some data structures or states
arguments: inputs to a part of a program
results: outputs from a part of a program
ugly code is hard to read and hard to correct as it often hides logical errors
always test programs with bad input
don’t demonstrate cleverness by writing the most complex program, demonstrate competence by writing simplest code that does the job
Statements#
- Declaration
statement that gives a name to an object or introduces a name into a scope
can declare many times as long as consistent
double sqrt(double); double sqrt(double); double sqrt(double); int sqrt(double); // error: inconsistentfunctions and definitions consumes memory whereas declarations don’t
declaring before using simplifies reading for both humans and compiler
- Definition
statement that introduces a new name and sets aside memory for a variable, every definition is also a declaration
const
requires initializingit is good to always initialize variables, uninitialized variables cause obscure bugs
preference for the
{}
initializer syntax
string
andvector
are initialized with default when not supplied explicitlyglobal variable is default initialized to 0, but should minimize use of globals
local and class members are uninitialized
- Header
collection of declarations, usually in another file
to use headers,
#include
in (preprocess) source files, both in files that use its declarations and provide definitions
.h
suffix is most common for headers,.cpp
for source filessome compilers and most development environments insist on having suffixes although they can be omitted
should only contain declarations that can be duplicated in several files
Variables#
named objects
- types
define a set of possible values and a set of operations
bool, char, int, float, double, void, wchar_t (wide char type)
definition without an initializer are initialized with NULL
extern
tells the compiler that the variable is defined in another source, outside of current scopeomitting type with modifiers (signed, unsigned, long, short) auto implies int
int i, j, k; char c; float f = 1.5, e = 2.2; extern q; unsigned x; // x is int
- type qualifiers
const
: cannot be changed during execution
volatile
: value may be changed in ways not specified by the program
restrict
: qualified pointer is initially the only means by which the object it points to can be accessedvalue: a set of bits in memory interpreted according to a type
- lvalue (variables)
expressions that refer to memory location
may appear in left or right side of assignment
“the object named by x”
- rvalue (numeric literals)
data value stored in memory
cannot have a value assigned, only appear on the right
“values of object named by x”
- scopes
global: defined outside of all functions, usually on top of the program, and can be accessed by any function
local: declared inside a function or block, can be used only by statements that are inside the function
can have same name for local and global but value of local will take preference
storage class: defines the scope and life-time of variables and functions
auto
: default class for all local variables, can only be used in functions/locals
register
for local variables to be stored in a register instead of RAM
variable max size is equal to register size usually one word
cannot have ‘&’ operator applied to it as it does not have mem location
should only be used for quick access such as counters
not guaranteed to be stored in register depending on hardware restrictions
static
keep local variable instead of creating and destroying
maintain values between function calls
applying to global causes it’s scope to be restricted to the declared file
using on class data member causes only one copy of that member to be shared by all objects of its class
extern
to give reference of global
variable cannot be initialized as it only points the variable name at location that has been defined
commonly used when tow or more files share the same globals
mutable
applies only to class objects
allows a member of object to override const member function
logically, assignment and initialization are different
constexpr
: symbolic constant and must be given a value at compile time
- Scope
region of program text
global scope: area of text outside any other scope
namespace scope: named scope nested in global scope or in another namespace
class scope: within class
local scope: between {…} of a block or in a function argument list
statement scope: as in a for-statement
to keep names local as not to interfere with names declared elsewhere
clash: two incompatible declarations in the same scope
keep names as local as possible
larger the scope of a name is, the longer and more descriptive its name should be
functions within classes: member functions
classes within classes: member classes
classes within functions: local classes (avoid)
functions within functions: local/nested functions (not legal in C++)
blocks within functions and other blocks: nested blocks
I/O#
occurs in streams, sequences of bytes
reading of strings is terminated by whitespace (space, newline, tab)
characters that are not ordinary: Ctrl+Z (Windows), Ctrl+D (Unix) terminates an input stream
separate how the program reads and writes from actual input and output devices
directly addressing each kind of device will need to change the program for a new screen or disk every time or limit users to certain screens and disks
most modern OS separate the detailed handling of I/O devices into device drivers
- Different Kinds of I/O
streams of data items (files, network, display devices)
user interacting with keyboard or through GUI
<iostream>
cin (standard input): instance of istream class, used with stream extraction operator, get from, ‘>>’
cout (standard output): instance of ostream class, used with stream insertion operator ‘<<’
cerr (un-buffered standard error stream): instance of ostream class< each stream insertion causes output to appear immediately, more resilient to errors as it is not optimized
clog (buffered standard error stream): instance of ostream class, each stream insertion is held in a buffer till filled or flushed
<iomanip>
services useful for formatted I/O with parameterized stream manipulators
setw, setprecision
<fstream>
services for user-controlled file processing
reading a name consisting of two words
string first, second; cin >> first >> second;reading character array using
cin.get()
, which reads a string with whitespacechar ch[100]; cin.get(ch, 50);have a balance between program complexity and accommodation of users’ personal tastes
- hexadecimal
a digit exactly represents 4-bit value
popular for outputting hardware-related information
- Integer Output Manipulators
oct
,dec
,hex
,showbase
,noshowbase
<< hex
and<< oct
informs any further integer outputs to be hex or octare called manipulators and are sticky until output format is changed
can ask the
ostream
to show the base of each integerdecimals have no prefix, hexas have 0x and octals have 0 as prefix
cout << 123 << '\t' << hex << 1234 << '\t' << oct << 1234; // output: 123 4d2 2322 cout << 123 << '\t' << hex << 1234 << "\thello\t" << 1234; // output: 123 4d2 hello 4d2 // changing output back to decimal cout << hex << 1234 << '\t'<< dec << "hello\t" << 1234; // output: 4d2 hello 1234 cout << showbase << 1234 << '\t' << hex << 1234 << '\t' << oct << 1234; // 1234 0x4d2 02322 // showbase manipulator persists cout << '\t' << 1234 << '\t' << noshowbase << 1234; // 02322 2322
- Stream Insertion Operator
by default,
>>
assumes numbers use decimal notationcan tell it to read various formats and input manipulators also stick
can tell
>>
to accept prefixesstream member function
unsetf()
clears the flagscin >> a >> hex >> b >> oct >> c >> d; // 1234 4d22 2322 2322 cout << a << '\t' << b << '\t' << c << '\t' << d; // 1234 1234 1234 1234 cin.unsetf(ios::dec); cin.unsetf(ios::oct); cin.unsetf(ios::hex); cin >> a >> b >> c >> d; // 1234 0x4d2 02322 02322 cout << a << '\t' << b << '\t' << c << '\t' << d; // 1234 1234 1234 1234
- Floating Point Manipulators
fixed
,scientific
,defaultfloat
,setprecision()
they also stick
by default, float values are printed using six total digits with defaultfloat
number is rounded for best approximation to be printed with six digits
floating-point format only applies to floating-point numbers
can set the precision with
setprecision()
cout << 1234.56789 << '\t' << fixed << 1234.56789 << '\t' << scientific << 1234.56789 << '\t' << 1234.56789; // 1234.57 1234.567890 1.2345678e+03 1.2345678e+03 cout << scientific << 1234.56789 << '\t' << 123456789 << '\t' << 1234567.0; // 1.2345678e+03 123456789 1.234567e+06 // 1234567.0 prints in scientific because fix format cannot be used to be accurate cout << defaultfloat << 1234.56789 << 1234567.0; // 1234.57 1.23457e+06 // defaultfloat chooses between scientific and fixed to present the most accurate #include <iomanip> cout << 1234.56789 << '\t' << setprecision(8) << 1234.56789; // 1234.57 1234.5679
- setw()
fields for integers
using scientific and fixed formats, how much space a value takes up on output by floating-point numbers can be controlled, which is useful for printing table
same thing can be done for integers with fields
can specify exactly how many character positions an integer value or string value will occupy
field sizes don’t stick
bad formatting is almost always same as bad output data
overflows are noticeable and can be corrected
fields can also be used for floating-point and strings
cout << 12345 << '|' << setw(4) << 12345 << '|' << setw(8) << 12345; // 12345|12345| 12345 // normal|doesn't fit in 4 field| three spaces in front // numbers will not be truncated to fit cout << 12345 << '|' << setw(8) << 12345 << '|' << "asdfg"; // 12345| 12345.6| asdfg
- Buffer
data structure that ostream uses internally to store data given by user to OS
delay between ostream and characters appearing is usually because they are still in buffer
buffering is important for performance
istream uses buffer to communicate with the OS
with istream, buffering can be quite visible to the user
Operators#
- Arithmetic
+, -, ;, /, %, ++, –
modulo operator (%) cannot operate on floats, so use
fmod()
from<cmath>
- Relational
==, !=, >, <, >=, <=
- Logical
&&, ||, !
- Bitwise
perform bit-by-bit operation
&, |, ^ (XOR), ~ (complement), <<, >>
- Assignment
=, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=
- Misc
sizeof, conditional, comma, member (. & ->), cast, address (&), indirection (*)
sizeof()
can be used on type name or expression to get number of bytes
sizeof()
for type gives size of an object, and gives size of the type for expressionsize of a type can be different on various implementation of C++
- Precedence
left to right: postfix, multiplicative, additive, shift, relational, equality, bitwise (AND, OR, XOR), logical (AND, OR), comma
right to left: unary, conditional, assignment
*=
&/=
are referred to as scaling in many application domainsif an operator has an operand type of
double
, floating-point arithmetic is used
Literals#
- Integer
prefix specifies the base or radix
0x or 0X for hexa
0 for octal
nothing for decimal
can have suffix combination of U/u and L/l for unsigned and long
85 //decimal 0213 // octal 0x4b // hexa 30 // int 30u // unsigned int 30l // long 30UL // unsigned long
- Float
has int part, decimal point, fractional part and exponent part
can represent in decimal or exponential form
3.141 3141E-5LBoolean: true & false, should not consider as 1 or 0
- Character
enclosed in single quote
wide character begins with L and should be stored in
wchar_t
range of char values is [-128:127], but only [0:127]l can be used portably as different computers have different ranges, [0:255]
a alert or bell
b backspace
f form feed
r carriage return
t horizontal tab
v vertical tab
ooo octal number of one to three digits
xhh… hexa number of one or more digits
- String
enclosed in double quotes
representation of a string is a bit more complicated than that of an int as a string keeps track of the number of characters it holds
functions:
strcpy()
,strcat()
,strlen()
,strcmp()
,strchr()
,strstr()
// C-style char myStr[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; char myStr[] = "Hello" // string class string myStr = "Hello"; // can break long lines "hello, \ world" // adjacent string literals are concatenated by the compiler "line 1 " "still line 1"
- defining constants
using #define preprocessor
using const keyword
#define LINE 10 const int LINE = 10;
Type Safety#
when objects are used only according to the rules for their type
using a variable before it has been initialized is no type-safe
C++ compiler cannot guarantee complete type safety
- Type Conversions
are safe when no information is lost
bool to char
bool to int
bool to double
char to int
char to double
int to double
for really large int, some computers suffer loss of precision when converting to double
unsafe/narrowing conversions can implicitly turn a value to another type that does not equal the original value
- Accepted by Compiler Even Though Unsafe
double to int
double to char
double to bool
int to char
int to bool
char to bool
universal and uniform initialization: {}-list-based notation to avoid accidents
double x{2.7}; int y{x}; // error: double->int might narrowuse explicit conversion if possible
void g(double x) { int x1 = x; int x2 = int(x); int x3 = static_cast<int>(x); }
Breaking Up Code#
big computation should be broken into smaller ones
- Abstraction
programming and design technique that relies on separation of interface and implementation
use access labels to define abstract interface to classes (public, private)
class internals are protected from user-level errors, which might corrupt the state of object
class implementation may evolve without requiring change in user-level code
interface must be kept independent of the implementation
- Divide & Conquer
divide larger problem into several little ones
each of the resulting problems is significantly smaller than the original
typedef#
create new name for existing type
typedef int hello; hello distance; // hello is of type int
Switch#
values must be of an int, char or enumeration, cannot switch string
values in case labels must be constant expression, cannot use variable
cannot use same value for two
case
labelscan use several
case
labels for a single casealways end each
case
withbreak
, compiler will not warn if forgotten
Loops#
- while
the first program ever to run on a stored-program computer (the EDSAC), written and run by David Wheeler, was simple iteration of square numbers
loop variable must be defined and initialized outside (before) the statement
int i{0}; while (i<100) { ++i; }
- for
never modify the loop variable inside the body of statement
conditional expression is assumed to be true if absent
for (int i=0; i<100;++i){} for(;;;)range-for
for(int x:v) {} // for each int x in v
Functions#
parameter list: list of arguments required by the function
parameter names are not important in declaration, supplying information separate from the complete function definition
declaration is required to be called from another file
int myFunc(int, int);give
void
as return type to return nothingvoid noReturnFunction() {}
- falling through the end of function
not returning a value as declared
only
main()
is special case as falling through is equal toreturn 0;
acceptable to drop through bottom of
void
function which is same asreturn;
programs are easier to write and understand if each function performs a single logical action
standard library provides
swap()
for every type that can be copied
- Call/Pass by Value
default, copy the actual value of argument into formal parameter
argument is not affected, but there is cost of copying the value
- Call/Pass by Pointer
copy the address of argument into formal parameter
argument is affected
int myfunc(int *a, int *b);
- Call/Pass by Reference
copy the reference/address of argument into formal parameter
argument is affected
references make both reading and writing of same element easy without repetition
int myfunc(int &a, int &b); vector<vector<double>>v; double& vector = v[f(x)][g(y)]; var = var/2+sqrt(var);
- Call/Pass by Const Reference
const
stops the argument being modifiedconst reference doesn’t need an lvalue
int myfunc(const int &a); // since cr is const, literal can be passed // compiler sets aside an int for cr to refer to, temporary: compiler-generated object void g(int a, int& r, const int& cr); g(x, y, z) g(1, 2, 3); // error, int& r needs variable g(1, y, 3); // OKpointer can be reassigned, reference must be bound at initialization and cannot be rebound
- By-Value vs By-Reference
use non-const reference to change
by-value gives a copy
by-const-reference prevents from changing the value of the object
by-value for small objects, by-const-reference for non-modify large object, by-reference only when needed
return a result rather than modifying through a reference argument
non-const-reference are essential for manipulating containers, other large objects and for functions that change several objects as functions can have only one return value
best to avoid functions that modify several objects
recursive: function that directly or indirectly calls itself
- Function Activation Record
data structure containing a copy of called function’s parameters and local variables
each function has its record
from implementation’s point of view, a parameter is just another local variable
run-time cost of making function activation record doesn’t depend on how big it is
record stack grows by one each time the function is called
the record is no longer used when the function returns
- constexpr Functions
to avoid doing same calculation many times
evaluated by the compiler if given constant expressions as arguments
must be simple for compiler to evaluate, otherwise error
must have a body of single return
may not change the value of variables outside its body
const int x = 1; constexpr test(int i) { return x + i; }do not return a pointer to a local variable
Errors#
- Compile-time Errors
found by compiler, which is the first line of defense against errors
Syntax errors: not always easy to be reported in understandable way by the compiler
Type errors: mismatches between the declared types, every function call must provide the expected number of arguments
- Link-time Errors
found by the linker when trying to combine object files into executable
every function must be declared with the same type in every translation unit (compiled parts)
every function must be defined exactly once
functions with the same name but different types will not match and will be ignored
misspelled function name doesn’t usually give a linker error, but compiler does
compile-time errors are found earlier than link-time and easier to fix
exactly one definition of an entity but can be many declarations
- Run-time Errors
found by checks in a running program
detected by the computer, a library or user code
called function, the callee, must check its own arguments as checking can be in one place
but checking in function isn’t always done when cannot modify the function definition (using from library), called function doesn’t know what to do in case of error (library writer can detect errors, but only user know what to do with them), called function doesn’t know where it was called from, performance cost of a check can be more than the cost of calculating the result
letting the called function send errors and the caller handling them can have problems as both called function and caller must do tests, caller can forget to test, and many functions do not have an extra return value to indicated an error
- Logic Errors
found by the programmer
usually the most difficult to find and fix
check, estimate, that the result is plausible as it is not easy to know what is reasonable
- Sources of Errors
poor or incomplete program specifications
unexpected arguments, input or state (data)
logical errors
Exceptions#
std::exception
(provideswhat()
method)
std::bad_alloc
(can be thrown by new)
std::bad_cast
(can be thrown by dynamic_cast)
std::bad_typeid
(can be thrown by typeid)
std::bad_exception
(useful to handle unexpected exceptions)
std::logic_error
std::domain_error
(mathematically invalid domain used)
std::invalid_argument
std::length_error
std::out_of_range
(off-by-one error, bounds error): subscript operation of vector knows its size and will check, if the check fails, the subscript operation throws ‘out_of_range’ exception
std::runtime_error
std::overflow_error
std::underflow_error
std::range_error
holds a string that can be used by an error handler
simply catch it to deal with, catching in
main()
is ideal for simple programscontainers: collections of data
try
: followed by one or more catch blocks
catch
: catch exception with an exception handler
throw
: throws an exception when problem shows up
throw MyClass{}
: make an object of type ‘MyClass’ with default values and throw itprotected code: code within try/catch block
try { //protected code } catch (ExceptionName e) { //code to handle exception } catch (...) { // catch any type of exception }
narrow_cast<int>(2.9)
throws runtime_error exception as the type changes
‘cast’ means ‘type conversion’
‘cast’ doesn’t change its operand, but produces new value specified in ‘<…>’
Debugging#
need to know if the program actually worked correctly
complicated code is where bugs can most easily hide
add invariants, conditions that should always hold, in code sections suspected of bugs
assertion/assert: statement that states an invariant
pre-condition: requirement of a function upon its argument, always consider if a quick check of pre-conditions can be written
post-condition: what to do if pre-condition is violated, both provide sanity checks
Program Development#
analysis: figure out a set of requirements or specification
design: create overall structure
implementation: write, debug and test the code
testing: a run with a given set of inputs
only a combination of analysis and experimentation (design & implement) gives the solid understanding to write a good program
prototype: limited initial version aimed at experimentation
grow a program from working parts rather than writing all at once
- Token
sequence of characters that represents a unit
representing each token as a (kind, value) pair is conventional
parsing: reading a stream of tokens according to a grammar, which is done by parser or syntax analyzer
for programs that accept user input, write a grammar defining the syntax of input and write a program that implements the rules of that grammar
- Writing Simple Grammar
distinguish a rule from token
put one rule after another (sequencing)
express alternative patterns (alternation)
express a repeating pattern (repetition)
recognize the grammar rule to start with
some call tokens terminals and rules non-terminals or productions
it is not ideal to throw away input without determining what it is
order of declaration is important, cannot use a name before it has been declared
- Symbol Table
mechanism to keep track of variables
can use
map
from standard library
isalpha()
: check character for alphabetical letter (isalpha('1')
is false)
isdigit()
: check character for digitinterpreters: programs that immediately executes the expressions it has analyzed
Program Evaluation#
variable is constructed when the execution reaches the definition, and destroyed when it goes out of scope
static
local variable is initialized only the first time its function is calledglobal variables are constructed in the order in which they are defined and destroyed in reverse order in single translation unit
do not use global variables in everything
in different translation units, order of initialization of global variables may be different
compilers only allocate and deallocate memory necessary amount
- never access the value of variable in an expression twice
v[i] = ++i
not all compilers warn about the bad code and different compilers result different values
left or right hand side of the assignment may be evaluated first
Namespaces#
used as additional info to differentiate similar functions, classes, variables
defines a scope
fully qualified name: has namespace/class name and member name
namespace first_space { int x = 1; } namespace second_space { int x = 2; } int main() { first_space::x; // fully qualified name second_space::x; }
using
directive
avoid prepending of namespaces, tells the compiler that subsequent code is making use of names specified in namespace
no declaration for a name in the scope means it is likely to be in
std
using namespace first_space; int main() { x; // use x from the first_space }
can be used to refer to particular item within a namespace
using std::cout; int main() { cout << "hello" << std::endl; }
names introduced obey normal scope rules, entities with the same name defined in outer scope are hidden
good idea to avoid
using
for namespaces except for well known ones such asstd
may lose track to which names come from where
putting
using
in header file is bad habitseparate parts of a namespace can be spread over multiple files
- namespaces can be nested
access members of nested by using resolution operators
namespace first_space { int x = 1; namespace second_space { int x = 2; } } using namespace first_space::second_space; int main() { x; // x from second_space }
Struct#
two kinds of user-defined types: classes & enumerations
class where members are public by default
primarily used for data structures where members can take any value
struct Books { char title[50]; char author[50]; } book;
access with member access operator (.)
struct Books Book1; strcpy(Book1.title, "Hello World");can be passed as a function argument
pointers to structures
struct Books *struct_pointer; struct_pointer = &Book1; struct_pointer->title;can use with typedef
typedef struct { char title[50]; char author[50]; } Books; Books Book1, Book2;
Enumerations#
simple user-defined type
declares an optional type name and a set of zero or more identifiers
each enumerator is a constant whose type is enumeration
by default, the value of first is 0, second is 1 and so on but can give a name value by adding an initializer
body of enumeration is a list of enumerators
class
inenum class
means that the enumerators are in the scope of enumeration and their values do not implicitly convert to other typesenum class color { red, green, blue}; Color c = Color::blue; int num = c; // error
enum class
should be preferredcannot define constructor for an enumeration to check initializer values
useful when a set of related named integer constants is needed
in plain
enums
, enumerators are in the same scope as the enum and their values implicitly convert to integers and other typesplain enums are less strict than
enum classes
, but can pollute the scope in which their enumerator is definedenum Color { red , green = 3, blue}; // blue will have value 4 as each will be one greater Color c = Color::blue; // c is of type color int num = c; // OK
Overloading#
specifying more than one definition for function name or operator in the same scope
both declarations have different arguments and definition
overload resolution: compiler determines the most appropriate definition to use by comparing argument types used to call the function with parameter types specified in the definitions
- Operator Overloading
functions starting with
operator
can define as ordinary non-member functions or as class member functions
overloaded operator must have at least one user-defined type as operand
define operators only with their conventional meaning
most interesting operators to overload (=, ==, !=, <, [] subscript, () call)
MyClass operator+(const MyClass& c) { MyClass d; d.x = this->x + c.x; return d; }
cannot overload (::, .* , ., ?:, sizeof, typeid)
need to make I/O operator overloading a friend of the class as it would be called without creating an object
friend ostream &operator<<(osstream& output, const MyClass& c) { output << c.x; return output; } friend istream &operator>>(istream& input, MyClass& c) { input >> c.x; return input }
overloading function call operator () is not creating new way to call a function, it’s creating operator function that can be passed an arbitrary number of parameters
MyClass operator()(int a, int b, int c) { MyClass C; C.x = a + b + c; return C; } MyClass Class1; MyClass Class2; Class2 = Class1(2, 2, 2,); // will call overloaded operator
in overloading class member operator (->), operator -> must be a member function and return type must be a pointer or an object of a class to which it can be applied
- Function Overloading
can have multiple definitions for same function name in the same scope
definition of functions must differ by the types and/or number of arguments
cannot differ only return type
Lambda Expressions#
unnamed function defined as an argument
lambda introducer (
[]
), followed by argument list and function bodyreturn type can be deduced from function body or specified explicitly
introducer can be used to gain access to local variables
lambda expressions should be kept simple, and only use for functions that fit on a line or two
auto myLambda = [](int x) {return 2 * x}; // specify return type auto myLambda = [](int x) -> int {return 2 * x}; // access local variable int y = 2; auto myLambda = [y](int x) {return y * x};
Files#
#include <fstream>
a file is sequence of bytes numbered from 0 upward
file format has same role for files on disk as types for objects in main memory
ostream converts objects in main memory into streams of bytes and writes them to disk
istream takes a stream of bytes from disk and composes objects from them
ofstream
: data type for output file stream, ostream for writing to a file
ifstream
: data type for input file stream, istream for reading from a file
fstream
: type for general file stream, both ofstream and ifstreamfile must be opened before read or write
ifstream ist {filename}; // open file for reading ofstream ost {filename}; // open file for writingwhen a file stream goes out of scope, associated file is closed and buffer if flushed
best to open files early in program before any computation
relying on scope minimizes the chances of file stream being used before attach
- explicit open and close
all members are in
std::ios_base
ios::app
: append, useful for writing logs
ios::ate
: open file for output and move rw control to the end of the file
ios::binary
: binary mode, can have system-specific behavior
ios::in
: open for reading
ios::out
: open for writing
ios::trunc
: truncate contents before open if file already existsifstream ifs; ifs.open(filename, ios_base::in); void open(const char* filename, ios::openmode mode); ofstream ofs {filename, ios::app};can combine multiple modes
ofstream outfile; // open in write mode and truncate if already exists outfile.open("file.dat", ios::out | ios::trunc); // open for read and write outfile.open("file.dat", ios::out | ios::in);when using character representation, some character must be used to represent end of number in memory
distinction between storing fixed-size binary representation and variable-size character string representation also occurs in files
it is possible to request
istream
andostream
to copy bytes to and from files withios::binary
template<class T> char* as_bytes(T& i) { void* addr = &i; return static_cast<char*>(addr); } int main() { ifstream ifs{"inputFile", ios::binary}; ofstream ofs{"outputFile", ios::binary}; vector<int> v; for(int x; ifs.read(as_bytes(x), sizeof(int));) v.push_back(x); for(int x: v) ofs.write(as_bytes(x), sizeof(int)); return 0; }when moving from character-oriented I/O to binary I/O,
>>
and<<
must be given up as they turn values into character sequences
as_bytes()
is needed to get the address of the first byte of an object’s representationdefault character I/O is portable, human readable and supported by type system
don’t mess with binary I/O unless really needed
cannot open a file stream second time without closing first
program auto flushes all streams, release all allocated memory and close all files, but good practice to close all opened files before termination
// member of fstream, ifstream, ofstream void close();most common reason for failure to open a file for reading is that it doesn’t exist
OS will create new file if nonexistent file is opened for output, will not for input
stick to reading from files opened as
istreams
and writing to files opened asostreams
- istream States
good()
,eof()
,fail()
,bad()
difference between fail and bad is not precisely defined
a stream that is
bad()
is alsofail()
when a stream fails, it can be recovered by taking it out of the
fail()
state withclear()
stream state can be set back to
fail()
withist.clear(ios_base::failbit)
, which setsiostream
state to flags mentioned but clear flags that are notcharacter can be put back into
ist
usingunget()
getting more data from
bad()
state is unlikely, but to throw an exception withist.exceptions(ist.exceptions()|ios_base::badbit)
iostream
can handle different character sets, implement different buffering strategies, and contain facilities for formatting monetary amounts in various languagesusually errors are much rarer for output than for input, only test each output operation of ostream if output devices have more chance of being unavailable or broken
use insertion operator (<<) to write to file, ofstream or fstream object instead of cout
use extraction operator (>>) to read file, ifstream or fstream object instead of cin
- Problems when Reading
user typing out-of-range value
getting no value (eof)
user typing wrong type
terminators are useful when reading files with nested constructs
every file opened for reading has a read/get position and files for writing has write/put position
- File Position Pointers
integer value that specifies the location in the file as a number of bytes from start
seekg
: seek get for istream
seekp
: seek put for ostreamboth have argument of long int
seek directions:
ios::beg
(default),ios::cur
,ios::end
fileObject.seekg(n, ios::end);there is next to no run-time error checking when positioning is used
undefined when seeking beyond end of a file and operating systems differ what happen
istringstream
&ostringstream
string can be used as the source of
istream
orostream
istringstream
is useful for extracting numeric values from a stringwill go into
eof()
state if read beyond the end ofistringstream
string
ostringstream
can be useful for formatting output for a system that requires simple string argument such as GUI systemistringstream is {s}; ostringstream os; os.str().c_str();
str()
member function ofostringstream
returns the string composed by output operations to anostringstream
c_str()
is member ofstring
that returns C-style stringstringstreams are used to separate actual I/O from processing, usually to filter characters out of input
can use
ostringstream
to concatenate strings
istream
library provides to read individual characters and whole linesstring name; getline(cin, name);
usually parse the line after entered
reading individual characters gives full control
get()
does not skip whitespace and returns a reference toistream
like>>
- standard library functions for character classification
isspace()
,isalpha()
,isdigit()
,isxdigit()
,isupper()
,islower()
isalnum()
,iscntrl()
,ispunct()
,isprint()
,isgraph()
toupper()
,tolower(c)
can be combined using ||
isalnum()
:isalpha()||isdigit()
standard library
iostream
rely on concept calledstreambuf
Date & Time#
inherits date/time functions from C <ctime>
- Time-Related Types
clock_t
,time_t
,size_t
: represent as integer
tm
: in the form of C structure, tm_sec/min/hour/mday/mon/year/wday/yday/isdsttime_t time(time_t *time); // time in seconds since Jan 1, 1970 char *ctime(const time_t *time); // pointer to string of form 'day month year hr:min:s' struct tm *localtime(const time_t *time); // pointer to tm structure clock_t clock(void); // approx of running program time char *asctime(const struct tm *time); /* pointer to string with info stored in structure pointed to by time converted in the form 'day month date hr:min:s year\n\0' */ struct tm *gmtime(const time_t *time); // pointer to time in tm structure time_t mktime(struct tm *time); /* calendar-time equivalent of the time found in the structure pointed by time */ double difftime(time_t time2, time_t time1); // calculates difference in seconds size_t strftime(); // use to format date and time
Built-in functions#
<cmath>#
cos, sin, tan, log, pow, hypot, sqrt, abs, fabs, floor
<cstdlib>#
srand, rand
Arrays#
consist of contiguous memory locations
double myArr[100]; int test[] = {1, 2, 3}; // will have initialized size int x[5] = {0}; // all elements will be 0 int x[5] = {1}; // only first element will be 1 and others 0 // initialize all elements to 0 #include <cstring> int x[5]; memset(x, 0, sizeof(x)); // access by index x[2]; // third element // sometimes declared with extra memory for out of bounds error protection int x[n + 5]; // int x[ROW][COLUMN], two dimensional array int x[3][4]; // 3 rows, 4 columns int x[3][4] = {{0}}; // initialized all elements to 0
cannot use more than size limit of 10^8
- cannot return entire array from function, but can return a pointer to an array
have to define local variable as
static
to return address of local to outside function
int * returnArray() { static int x[3]; return x; }
Pointers#
Free Store, NULL Pointer, Void Pointer, Explicit Type Conversion
an object which holds address value of another
‘address of’ operator, unary
&
, is used to get the address of an object‘contents of/dereference’ operator, unary
*
, is used to get the value of the object pointed tomost pointer values/addresses use hexadecimal notation
can do arithmetic operations and comparisons
dereference operator can also be used for left-hand assignment
int x = 10; int* p_x= &x; std::cout << *p_x; // get value pointed to by p_x, value of x *p_x = 9; // assign new value to the value pointed to by p_x, 'x' becomes 9
- pointer is not integer
pointer type provides operations for addresses
int provides operations for integers, arithmetic and logical
pointers and integers implicitly do not mix
int* pi = &x; int i = pi; // error pi = 2; // error
pointer to char,
char*
, is not same as pointer to int,int*
int* pi = &i; char* pc = pi; // error pi = pc; // error
if assigning different types of pointers is allowed, memory locations could be changed since each type has different memory sizes
pointer pointing to the start of array can access it by pointer arithmetic or array-style indexing
- memory set aside by compiler
code/text storage: for the code
static storage: for global variables
stack/automatic storage: for functions, arguments and local variables
- a pointer doesn’t know how many elements it points to
out-of-range access, transient bugs, can affect unrelated parts of the program and are hard to find
problems in some C-style programs are caused by access through uninitialized pointers and out-of-range access
optimizer, compiling on different machine or turning off debug features can cause a program with uninitialized variables to run differently
pointer to a pointer
int *ptr; int **pptr; ptr = &x; pptr = &ptr;
Free Store#
memory not touched by the compiler, also called the heap
can allocate in the heap with
new
new
operator returns a pointer to the object it createscan allocate individual elements or arrays
int* p = new int[4]; // allocate 4 ints on the free store int x = *p; // first object pointed by p int y = ptr[1]; // second object pointed by p int z = *&p[2]; // third object pointed by p int* p1 = new int[4] {1, 2, 3, 4}; // allocate array on the free store and initialized it int* p1 = new int[] {1, 2, 3, 4}; // can omit number of elements when initialized
- always return memory to free store after using
essential for long-running programs such as operating systems, embedded systems and libraries
delete[] p; // free array of objects delete p; // free individual object
- careful not to delete an object twice
deleting null pointer is harmless
delete p; // first time deletion delete p; // error: p points to memory owned by free-store manager int* np = nullptr; delete np; // ok delete np; // ok
- automatic garbage collection
recycle/free memory not needed without human intervention
can be costly and not ideal for all applications
programs under operating systems auto return memory to the system at the end
allow memory leak only when program will not use memory more than available and memory consumption estimate for a program should be correct
NULL Pointer#
memory at address 0 is reserved by the OS
signals that the pointer is not intended to point to an accessible memory
use when no pointer to use for when initializing a pointer
can avoid accidental misuse of uninitialized pointer
int* p0 = nullptr; // value zero is called when assigned to a pointer // before C++11 int* p0 = 0; int* p0 = NULL;
Void Pointer#
pointer to memory that the compiler doesn’t know the type of
use to transmit address between code that don’t know each other’s types, e.g address arguments of a callback function
can assign to pointer to any object type
static_cast
can be used to convert between related pointer types, but use only when necessaryvoid* pv1= new int; // ok void* pv2= new double[10]; // ok pv2 = pv1; // ok double* pd = pv1; // error pv[2] = 9; // error *pv1 = 2; // error: cannot dereference a void* int* pi = static_cast<int*>(pv1); // ok, also called explicit conversion
Explicit Type Conversion#
use only if really necessary
static_cast
to convert between related pointer types
reinterpret_cast
to convert one pointer type to another
not easily portable
const_cast
cast away const
prefer
static_cast
if neededRegister* in = reinterpret_cast<Register*> (0xff); // necessary when writing device drivers void f(const Buffer* p) { Buffer* b = const_cast<Buffer*> (p); // strip const from const Buffer* }
assignment to pointer changes the pointer’s value, not the pointed-to
need to use
new
or&
to get a pointerneed to use
*
or[]
to access an object pointed to- assignment of pointers doesn’t do deep copy, unlike references
assigns to the pointer object itself
reference and pointer are both implemented by using a memory address
using a pointer argument alerts the programmer something might be changed
- when to use
pass-by-value for tiny objects
use pointer parameter for functions where
nullptr
is a valid argumentuse a reference in other cases
pointer fiddling is tedious and error-prone, should be hidden in well-written and tested functions
References#
an alias, cannot have NULL, cannot be changed to refer to another object, must be initialized when created
int i = 1;
int& r = i;
usually used for function argument lists and function return values
- return by reference
returns an implicit pointer to return value
function can be used on the left side of an assignment
int x[] = {1, 2, 3}; int& setValue(int i) { return x[i] } // function on the left side of assignment setValue(1) = 10; // x = {1, 10, 3}
cannot return a reference to local var
assignment to a reference changes the value of the object referred to
cannot make a reference refer to a different object after initialization
- assignment of references does deep copy, unlike pointers
assigns to the referred-to object
reference and pointer are both implemented by using a memory address
- when to use
pass-by-value for tiny objects
use pointer parameter for functions where
nullptr
is a valid argumentuse a reference in other cases
Classes#
Const Functions, Friend Functions, Inline Functions, this pointer, Static Members
used to specify the form of an object
data and functions, parts used to define the class, are members of the class
has zero or more members
class definition states what the class can do
access members using the
object.member
notation orobject->member
if given a pointer to the object
class MyClass {
public:
int x;
string s;
};
MyClass Class1;
Class1.x = 1;
Class1.s = "hello";
- keep classes complete and minimal
provide constructors
support copying or prohibit it
use types to provide good argument checking
identify non-modifying member functions
free all resources in the destructor
implementation: part of the class that users access only indirectly
private and protected members cannot be accessed directly with direct member access operator
Access Specifiers#
default is private
a class can have multiple labeled sections
- public
can set and get public variables without member functions
can be used by all functions
- private
cannot be accessed from outside the class
only the class and friend functions can access members
- protected
can be accessed in child classes
Member Functions#
has definition or prototype within the class definition
can be defined within the class or separately using scope resolution operator (::)
function definitions are implementations that specify how things are done, and usually specified outside the class so that they don’t distract
within member function, a member name refers to the member of that name in the object for which the member function was called
// defined within class class MyClass { public: int myFunc(void) { return 1 * 2; } }; // defined outside class MyClass { public: void setX(int y); }; void MyClass::setX(int y) { x = y; }
Helper Functions#
also called convenience functions, auxiliary functions
a design concept, not programming language concept
often take arguments of the classes that they are helpers of
usually put public first as it is what most people are interested in
the rule that a name must be declared before it is used is relaxed within the limited scope of a class
the more public member functions are, the harder it is to find bugs
- effects of writing definition of member function within the class definition
compiler will try to generate code for function at each point of call, inline functions
all uses of the class will have to be recompiled when body of the function is changed
class definition gets larger
never put member function bodies in class declaration unless there is performance boost from inlining tiny functions
create new types if it’ll make the code clearer
Constructor#
special member function that is executed whenever new objects are created
have same name as the class and does not return any, not even void
useful for setting initial values
default constructor does not have parameters
class MyClass { public: MyClass(int y); private: int x, a, b; }; MyClass::MyClass(int y) { x = y; } // using Initialization, same as above syntax MyClass::MyClass(int y): x{y} {} int main() { MyClass c1 {99}; // common style of initialization with constructor arguments MyClass c1(99); // C++98 style MyClass c1 = {99}; // OK MyClass c1 = MyClass{99}; // OK }
can use default arguments to provide several overloaded functions, but can only define default arguments for trailing parameters
// 'a' & 'b' are default arguments, with values to be used if not supplied MyClass::MyClass(int y, int a = 2, int b = 3): x{y} {} // default 'b' MyClass::MyClass(int y, int a = 2): x{y} {} // default 'a' or 'b' MyClass::MyClass(int y): x{y} {} // error, all parameters must have default argument starting from 'a' MyClass::MyClass(int y, int a = 2, int b, int c): x{y} {}
for type T, T{} is the notation for the default value, as defined by the default constructor
string s1 = string{}; // empty string "" vector<string> v1 = vector<string>{}; // empty vector, no elements int i = int{}; // 0 double d = double{}; // 0.0 // same as above string s1; vector<string> v1; int i; double d;
without constructor, an invariant cannot be established
if a class doesn’t have good invariant, use a
struct
as the data being dealt might be plain datain-class initializer: an initializer for a class member specified as part of the member declaration
Destructor#
executed whenever an object goes out of scope or delete expression is applied to a pointer to the object
exact same as the class prefixed with a tilde (~), does not return any and can’t take parameters
useful for releasing resources like closing files or releasing memory
class MyClass { public: ~MyClass(); }; MyClass::~MyClass(void) { cout << "Object deleted"; }
destructors of the members are called when the object containing the member is destroyed
struct MyStruct { string x; vector<string> y; } void f1() { MyStruct s1; } // destructors of x and y are called when s1 goes out of scope
- class with a virtual function needs a virtual destructor
as the class is likely to be used as base class
derived classes are likely to be allocated using
new
and deleted through pointer to its base
Copy Constructor#
creates an object by initializing with an object of the same class
used to initialize one object from another of same type, copy an object to pass it as argument to function, copy an object to return it from a function
compiler defines one if a class does not have it
a must to have if the class has pointer variables and dynamic memory allocations
class MyClass { public: MyClass(const MyClass &obj); }; MyClass::MyClass(const MyClass &obj) { ptr = new int; *ptr = *obj.ptr; } int main() { MyClass class1(10); // calls copy constructor MyClass class2 = class1; // also calls copy constructor }
Const Functions#
reading of member variables is allowed inside the function, but not writing
// ok int getX() const { return x; } // compile time error int getX() const { x++; return x; }
Friend Functions#
defined outside the class’ scope but can access all private and protected members
prototypes appear in class definition but are not member functions
class MyClass { public: friend void printX(MyClass c); friend class MyClass2; // declare all member functions of MyClass2 as friends }; void printX(MyClass c) { cout << c.x; }
Inline Functions#
commonly used with classes
compiler places a copy at each point where the function is called
changes to inline function require all clients of function to be recompiled
compiler can ignore the inline qualifier if defined function has more than a line
function definition in a class definition is an inline function definition
inline int myFunc(int x) { return x; }
pointer to class, access with ->
MyClass Class1; MyClass *ptrClass; ptrClass = &Class1; cout << ptrClass->x;
this Pointer#
every object has access to its own address through
this
pointerpoints to the object for which a member function is called
implicit parameter to all member functions
friend functions do not have it
class MyClass { int compare(MyClass c) { return this->x > c.x; return x > c.x; // no need to mention `this` to access a member } int x; };
cannot mutate
this
in a member functionclass MyClass { void compare(MyClass* c) { this = c; // error } };
Static Members#
only one copy no matter how many objects are created
shared by all objects
all static data is initialized to zero when first object is created
cannot put in class definition
class MyClass { public: static int y; }; int MyClass::y = 9; int main() { cout << MyClass::y; }
static function member is independent of objects and can be called even if no objects exist
accessed using only class name and scope resolution operator
can only access static data member, other static member functions and other outside functions
have a class scope and do not have access to the
this
pointercan be used to determine if objects are created or not
class MyClass { public: static int y; static int getY() { return y; } }; int MyClass::y = 9; int main() { cout << MyClass::getY(); }
Object Oriented Programming#
Inheritance, Data Encapsulation, Interfaces, Polymorphism, Designing Classes
usage of inheritance, polymorphism and encapsulation is the main definition of OOP
programming by difference: program only the difference of derived class to the base class
Inheritance#
allow to reuse the code functionality and fast implementation time
a class can be derived from more than one base classes
// base class class MyClass { public: void setX(int a) { x = a; } protected: int x; }; class TheClass { protected: int h; }; // derived class class HisClass: public MyClass, public TheClass { public: int y; }; int main() { HisClass HC; HC.setX(1); }
derived class can access all non-private members of base
cannot inherit constructors, destructors and copy constructors, overloaded operators and friend functions of the base class
rarely use protected and private inheritance
- Public Inheritance
public and protected members of base become public and protected of derived
private members are only accessible through calls to public and protected members of base
- Protected Inheritance
public and protected members of base become protected members of derived
public and protected member names can be used by members of the class and derived classes
- Private Inheritance
public and protected member names can only be used by members of the class
- Virtual Table
also called vtbl and its address is called virtual pointer, vptr
when inheritance is used, the data members of derived class are simply added after those of a base
the table tells which function is actually invoked when a function is called, with only two memory accesses for finding the right function
only one vtbl for each class with a virtual function, not each object
- Overriding
explicit use of
override
is useful in large, complicated class hierarchiesstruct A { virtual void f() {} void g() {} } struc B:A { void f() override {} void g() override {} // error, no virtual A::g to override }
- Interface Inheritance
using interface provided by a base class
without having to know about the derived classes
doesn’t need to recompile base class every time the derived classes change
- Implementation Inheritance
simplify the implementation of derived classes by using what the base offers
any change to the interface of the base require recompilation of all derived classes and their users
Data Encapsulation#
OOP concept that binds the data and functions and keeps both safe from interference
let to data hiding, important concept of OOP
encapsulation: bundling the data and functions that use them
abstraction: exposing only the interfaces and hiding implementation details from users
supports through creation of
classes
keep as many of the details of each class hidden from all other classes as possible
Interfaces#
describe the behavior or capabilities of a class without committing to particular implementation
implemented using abstract classes by declaring at least one of its functions as pure virtual function
abstract class (ABC) provides appropriate base class from which others can inherit
instantiating an object of ABC causes compilation error
subclass of ABC needs to implement each of the virtual functions
/* abstract class defines interface and two other classes implemented same function but with different algorithm */ class Shape { public: virtual int area() = 0; // pure virtual function }; // Rectangle & Triangle must implement area() class Rectangle : public Shape { public: int area() { return (width * height); } }; class Triangle : public Shape { public: int area() { return (width * height / 2); } };
Polymorphism#
occurs when there’s a hierarchy of classes and they are related by inheritance
a call to member function will cause different function to be executed depending on the type of object that invokes the function
class Shape { public: int area() { cout << "Parent class"; return 0; } }; class Rectangle: public Shape { public: int area() { cout << "Rectangle class"; return (width * height); } }; class Triangle: public Shape { public: int area() { cout << "Triangle class"; return (width * height / 2); } }; int main() { Shape* shape; Rectangle rec; Triangle tri; shape = &rec; shape->area(); // will call parent Shape area() shape = &tri; shape->area(); // will call parent Shape area() /*call of the function area() being set once by the compiler as the version defined in the base class */ return 0; }
- above output is caused by static resolution of the function call or static linkage
the function call being fixed before the program is executed
sometimes called early binding because the area() function is set during compilation
class Shape { public: virtual int area() { cout << "Parent class"; return 0; } }; int main() { // will call respective area() shape = &rec; shape->area(); shape = &tri; shape->area(); }
- compiler now looks at the contents of the pointer instead of it’s type
since addresses of objects of tri and rec classes are stored in shape, respective area() function is called
- dynamic linkage or late binding
function in the base class declared with
virtual
and signals the compiler not to have static linkage for the functiononly call functions based on the kind of object for which it is called
- pure virtual function
must be overridden
cannot create objects of classes with pure virtual functions
if all pure virtual functions are not overridden, derived class is still abstract
classes with pure virtual function are pure interfaces, have no data members and constructors
class Shape { public: virtual int area() = 0; // tells the compiler that the function has no body };
Designing Classes#
try not to be too clever and keep coherent concept
trying to solve everything can lead to a failure
even a library only models its application domain from particular perspective
single class providing everything can make a user confuse
make even small details consistent for ease of use and to avoid run-time errors
always ensure that modification to the state of an object is done only by its own class
- Abstract Class
can only be used as as base class
usually define interfaces to groups of related classes
state one ore more virtual functions need to be overridden in some derived class
- Concrete Class
can be used to create objects
overriding: defining a function in a derived class to be used through interfaces provided by a base
prevent the default copy operations for a type if they can cause trouble
do not mix default copying and class hierarchies and pass by reference
when designing a base class, prevent copy constructor and copy assignment by using
=delete
write explicit functions to copy objects of types where default copy operations are disabled
- Derivation
building one class from another and using the new class instead of the original
inheritance will allow the use of members from the base
- Virtual Functions
defining the same function in derived class so that it is called when a user calls the base class function
must have exactly same name and type as the base class
also called as run-time polymorphism, dynamic dispatch, run-time dispatch as the function called is determined at run time based on the type of the object
must be declared
virtual
in the class declaration, but not when defining outside
- Private/Protected members
keeping implementation details private to protect direct use
also called encapsulation
Dynamic Memory#
stack: store all variables declared inside the function
heap: unused memory and can be used to allocate memory dynamically when program runs
new
: allocate memory at run time, returns the address of the space allocateddelete
: de-allocates memory used by ‘new’ operatorany built-in or user defined data type can allocate memory dynamically
double* pvalue = NULL; // initialize pointer
pvlaue = new double; // request memory
memory may not be allocated if the free store had been used up
// good practice to check if new operator is returning NULL pointer
if (!(pvalue = new double)) {
cout << "Error: out of memory" << '\n';
exit(1);
}
delete pvalue; // release memory
malloc()
from C still exists but not recommended to usenew
doesn’t just allocate memory, it also constructs objects unlikemalloc()
Array Allocation#
syntax to release memory for arrays are same
char* pvalue = NULL; pvalue = new char[20]; delete [] pvalue; // multi-dimensional array double** pvalue = NULL; pvalue = new char[3][4]; // allocate memory for 3x4 array delete [] pvalue;
Object Allocation#
MyClass* myClassArray = new MyClass[4]; // allocate array of four MyClass objects delete[] myClassArray; // constructor and destructor, while deleting objects, will be called four times
Templates#
blueprint to create generic class or function
library containers like iterators & algorithms have been developed using template concept
function template#
template<class type> ret-type func-name(parameter list) {}
template<typename T> inline T const& Max(T const& a, T const& b) { return a < b ? b : a; } int main() { cout << Max(1, 2); }
class template#
template<class type> class class-name {};
can define more than one generic data type with comma-separated list
Preprocessor#
directives that give instructions to the compiler to preprocess the information before actual compilation starts
begin with #
not C++ statements and do not end in a semicolon
#define
creates symbolic constants, a macro
#define PI 3.14159265 #define MIN(a,b) (((a) < (b)) ? a : b)
- conditional compilation
compiling selective portions of source code
#define DEBUG int main() { #ifdef DEBUG cerr << "Only compiled if DEBUG has been defined before #ifdef DEBUG" #endif #if 0 code prevented from compiling #endif }
#
preprocessor operatorconvert replacement-text token to string surrounded by quotes
#define CONVERT(x) #x int main() { cout << CONVERT(hello world) << '\n'; // cout << "hello world" << '\n'; }
##
preprocessor operatorconcatenate two tokens
#define CONCAT(x, y) x ## y int main() { int xy = 100; cout << CONCAT(x, y); // cout << xy; }
__LINE__
: current line number of program when it is being compiled__FILE__
: current file name of program when it is being compiled__DATE__
: string date of translation of source file into object code in month/day/year__TIME__
: time at which the program was compiled in hour:minute:second
Signal Handling#
- signals that program can catch
SIGABRT: abnormal termination of program (e.g call to abort)
SIGFPE: arithmetic operation error (e.g divide by zero or overflow)
SIGILL: illegal instruction
SIGINT: external interrupt, usually by user
SIGSEGV: invalid access to storage
SIGTERM: termination request
signal()
to trap unexpected events
two arguments: int for signal number, pointer to the signal-handling function
always used to register signal to catch
#include <csignal> void signalHandler(int signum) { cout << signum << '\n'; exit(signum); } int main() { signal(SIGINT, signalHandler); }
raise()
to generate signals, int signal number as an argument
if (i == 3) { raise(SIGINT); // will call signalHandler }
Multithreading#
specialized form of multitasking
- process-based multitasking
handles the concurrent execution of programs
- thread-based multitasking
deals with concurrent execution of pieces of the same program
- thread
each part of multithreaded program that can run concurrently
each thread defines separate path of execution
before C++ 11, no built-in support for multithreading, instead rely on the OS for the feature
creating POSIX thread
#include <pthread.h> pthread_create (thread, attr, start_routine, arg) pthread_exit (status)
the routine can be called any number of times from any part of the code
thread: unique ID for new thread returned by the subroutine
attr: attribute object that may be used to set thread attributes, NULL for default values
start_routine: routine that the thread will execute once it is created
arg: single argument that may be passed to start_routine, must be passed by reference as pointer cast of type void, NULL may be used is no argument to be passed
pthread_exit()
is called after a thread has completed its work
max number of thread may be created is implementation dependent
threads are peer and may create other threads
no implied hierarchy or dependency between threads
if
main()
finishes before the threads and exits withpthread_exit()
, other threads will continue to executeotherwise, they will be terminated when
main()
finishespthread_join(threadid, status)
,pthread_detach(threadid)
blocks the calling thread until the specified threadid terminates
one of thread attributes defines whether it is joinable or detached
if a thread is created as detached, it can never be joined
Web Programming#
CGI (common gateway interface)#
set of standards that define how info is exchanged between web server and custom script
currently maintained by NCSA
CGI programs are written in Python, PERL, Shell, C or C++ etc.
var/www/cgi-bin#
CGI files have extension as .cgi
by default, Apache server is configured to run CGI programs
C++ CGI programs can interact with other external system, such as RDBMS, to exchange info
Graphics#
subject that touch good software design and programming language facilities
can embed color and 2D positions idea in a 1D stream of characters, like HTML and XML
can use GUI toolkits, such as FLTK, and implement classes using them
GUI#
separates the main logic of application from I/O, which allows to change program presentation
GUI programs have control inversion, the order of execution is determined by actions of the user, which is different from conventional programs
control inversion complicates both program organization and debugging
Conventional Programs:
application -> input function -> user responds
GUI Programs:
application <- system <- user action
keep GUI of a program simple and build it incrementally by testing at each stage
- Debugging a GUI Program
check each program parts
simplify the code and check carefully
check linker settings
compare to a working code if possible
exceptions do not work well when using GUI library
STL#
Pair, Vector, List, Stack, Queue, Deque, Priority Queue, Bounds, Set, Multi Set, Unordered Set
Map, Multi Map, Unordered Map, Sort, Reverse, PopCount, Permutation, Min/Max Element
Standard Template Library, compilation of predefined algorithms, containers, functions and iterators
Pair#
store two objects as single unit
// pair<TYPE, TYPE>; pair<int, int> p; p = make_pair(1, 2); // OR p = {1, 2}; p.first; // 1 p.second; // 2
Vector#
contiguous dynamic array
similar to an array in other languages, but doesn’t need to specify the size of vector in advance
doesn’t just store elements, also stores size
element type comes after
vector
in angle brackets (< >)will only accept elements of declared type
can also define a vector of given size without specifying elements
#include <vector> // vector<Type> v; vector<int> v(6); // vector of 6 int initialized to 0/garbage value, depend on compiler vector<int> v(6, 3); // vector of 6 int initialized to 3 vector<string> s(4); // vector of 4 strings initialized to ""
- range of vector v: [0:v.size())
notion of half-open sequences is used throughout C++ and the C++ standard library
v.push_back(); // add new element at the end, copy objects v.emplace_back(); // dynamically increase the size and add element at the end // faster than push_back(), no copying objects v.reserve(3); // increase and set vector capacity without creating objects // no initializing values vector<pair<int, int>> v; v.push_back({1, 2}); v.emplace_back(1, 2); // auto assume input is a pair vector<int> v1; vector<int> v2(v1); // copy vector v[1]; v.at(1); // accessing vector
- getting input and adding to vector, using input operation as the condition for a for-loop
use character ‘|’ to terminate the input
using for-loop limit the scope of input variable, x, to the loop, rather than ‘while’
vector<double> vs; for(double x; cin>>x) vs.push_back(x);iterator: points to the memory address
vector<int>::iterator it = v.begin(); // vector iterator *(it) // same as v[0] *(it + 2) // v[2] v.end(); // points to memory location after the last element v.rend(); // reverse the vector and points to memory location after the last element v.rbegin(); // reverse the vector and points to memory location of the first element v.back(); // value of last element // print values using iterator for(vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *(it) << endl; } for(auto it = v.begin(); it != v.end(); it++) { cout << *(it) << endl; } // type of "it" = int for(auto it : v) { cout << it << endl; } // Delete // v.erase(ITERATOR) OR v.erase(START, END), END is exclusive v.erase(v.begin()); v.erase(v.begin(), v.begin() + 3); // delete from begin to begin + 2 v.pop_back(); // remove the last element v.clear(); // delete entire vector // Insert // v.insert(ITERATOR, OPTIONAL_COUNT, VALUE) v.insert(v.begin(), 2); // insert 2 at the start v.insert(v.begin() + 1, 3, 2); // insert three 2s at begin + 1 v1.insert(v1.begin(), v2.begin(), v2.end()); // add v2 at the start of v1 // Swap v1.swap(v2); v.empty(); // return 0 or 1 v.size(); // number of elements v.capacity(); // storage space allocated, can be greater than the size v.shrink_to_fit(); // release unused storage space, capacity equal size after shrink // 2D vector vector<vector <int>> v; v.push_back(v1); v.push_back(v2); v.push_back(v3); // will have 3 rows, columns differ on sizes of v1, v2 and v3 // vector<vector <int>> v(ROW, vector<int> (COLUMN)); vector<vector <int>> v(3, vector<int> (4, 0)); // initialize elements to zero
List#
non-contiguous doubly linked list
each link holds information and pointers to other links
operations are cheaper than in vector
list<int> ls; ls.push_back(); // add new element at the end ls.emplace_back(); // dynamically increase the size and add element at the end // faster than push_back() ls.emplace_front(); // dynamically increase the size and add element at the start ls.push_front(); // add element at the begin ls.end(); // points to memory location after the last element ls.rend(); // reverse the vector and points to memory location after the last element ls.rbegin(); // reverse the vector and points to memory location of the first element ls.back(); // value of last element // Delete // ls.erase(ITERATOR) OR ls.erase(START, END), END is exclusive ls.erase(ls.begin()); ls.erase(ls.begin(), ls.begin() + 3); // delete from begin to begin + 2 ls.pop_back(); // remove the last element ls.pop_front(); // remove the first element ls.clear(); // delete entire vector // Insert // ls.insert(ITERATOR, OPTIONAL_COUNT, VALUE) ls.insert(ls.begin(), 2); // insert 2 at the start ls1.insert(ls1.begin(), ls2.begin(), ls2.end()); // add ls2 at the begin of ls1 // Swap ls1.swap(ls2); ls.empty(); // return 0 or 1 ls.size(); // number of elements
Stack#
Last In, First Out
cannot access by index, no iterator
all operations are O(1)
stack<int> st; st.push(1); // add existing element at the start of the stack st.emplace(2); // create new element and add at the start of the stack // st = {1, 2} st.top(); // last added element, 2 st.pop(); // remove last added element st.empty(); // return 0 or 1 st.size(); // number of elements // Swap st1.swap(st2); while(!st.empty()) { cout << st.top() << "\n"; st.pop(); }
Queue#
First In, First Out
cannot access by index, all operations are O(1)
queue<int> q; q.push(1); // add existing element at the end of the queue q.emplace(2); // create new element and add at the end of the queue // q = {1, 2} q.front(); // first element q.back(); // last element q.pop(); // remove first element q.empty(); // return 0 or 1 q.size(); // number of elements // Swap q1.swap(q2); while(!q.empty()) { cout << q.front() << "\n"; q.pop(); }
Deque#
dynamic double-ended queue, contiguous memory is not guaranteed
can insert and pop from both sides
deque<int> dq; dq.push_back(); // add new element at the end dq.emplace_back(); // dynamically increase the size and add element at the end // faster than push_back() dq.emplace_front(); // dynamically increase the size and add element at the start dq.push_front(); // add element at the begin dq.front(); // first element dq.back(); // last element dq.end(); // points to memory location after the last element dq.rend(); // reverse the vector and points to memory location after the last element dq.rbegin(); // reverse the vector and points to memory location of the first element // Delete // dq.erase(ITERATOR) OR dq.erase(START, END), END is exclusive dq.erase(dq.begin()); dq.erase(dq.begin(), dq.begin() + 3); // delete from begin to begin + 2 dq.pop_back(); // remove the last element dq.pop_front(); // remove the first element dq.clear(); // delete entire vector // Insert // ls.insert(ITERATOR, OPTIONAL_COUNT, VALUE) dq.insert(dq.begin(), 2); // insert 2 at the start dq1.insert(dq1.begin(), dq2.begin(), dq2.end()); // add ls2 at the begin of ls1 // Swap dq1.swap(dq2); dq.empty(); // return 0 or 1 dq.size(); // number of elements
Priority Queue#
First In, First Out, max heap, largest value stay at the front, non-contiguous
cannot access by index
push: O(log n), top: O(1), pop: O(log n)
priority_queue<int> pq; pq.push(4); // add existing element to the queue pq.emplace(3); // create new element and add to the queue pq.push(9); // pq = {9, 4, 3} pq.top(); // first element pq.pop(); // remove first element pq.empty(); // return 0 or 1 pq.size(); // number of elements // Swap pq1.swap(pq2); // min heap, smallest value at the front priority_queue<int, vector<int>, greater<int>> pq;
Bounds#
usually used for binary search
elements should be in sorted order
- Upper
greater than
vector<int> v = {1, 3, 9}; // address of element greater than to 3 auto it = upper_bound(v.begin(), v.end(), 3); // *it = 9 auto it = lower_bound(v.begin(), v.end(), 4); // *it = 9 auto it = upper_bound(v.begin(), v.end(), 3) - v.begin(); // it = 2, index of element
- Lower
greater than or equal to
vector<int> v = {1, 3, 9}; // address of element greater than or equal to 3 auto it = lower_bound(v.begin(), v.end(), 3); // *it = 3 auto it = lower_bound(v.begin(), v.end(), 4); // *it = 9 auto it = lower_bound(v.begin(), v.end(), 3) - v.begin(); // it = 1, index of element
Set#
store elements in unique sorted order, default ascending
will not store existing element if added again
operations are in O(log n)
set<int> s; s.insert(4); // add existing element s.emplace(3); // create new element and add // s = {3, 4} s.find(); // find element and return iterator // finding non-existing element will return iterator after last element s.erase(); // find element/iterator and delete, maintain sorted order, erase(START, END) s.count(); // check element exist, return 0 or 1 s.empty(); // return 0 or 1 s.lower_bound(); s.upper_bound(); for(auto it = s.begin(); it != s.end(); it++) { cout << *(it) << endl; } // sorted in descending order set<int, greater<int>> s;
Unordered Set#
elements have to be unique, stored in random order
operations are in O(1), rarely can be O(n)
no lower_bound and upper_bound functions
unordered_set<int> us; us.insert(); // add existing element us.emplace(); // create new element and add us.find(); // find element and return iterator // finding non-existing element will return iterator after last element us.erase(); // find element/iterator and delete us.erase(us.find()); // only first occurrence of the element will be deleted us.erase(us.find(), us.find()+2); // erase(START, END), END is exclusive us.count(); // return count
Multi Set#
store elements in sorted order, default ascending
elements not need to be unique
multiset<int> ms; ms.insert(4); // add existing element ms.emplace(3); // create new element and add ms.emplace(4); // create new element and add // ms = {3, 4, 4} ms.find(); // find element and return iterator // finding non-existing element will return iterator after last element ms.erase(); // find element/iterator and delete, maintain sorted order // will delete all occurrence of the element ms.erase(ms.find()); // only first occurrence of the element will be deleted ms.erase(ms.find(), ms.find()+2); // erase(START, END), END is exclusive ms.count(); // return count // sorted in descending order multiset<int, greater<int>> ms;
Map#
store key-value pairs in sorted order by key
keys must be unique and can be any data type
can have large values, unlike array
operations are in O(log n)
map<int, int> mp; // map<pair<int, int>, int> mp; mp[KEY] = VALUE; mp.insert({KEY, VALUE}); mp.emplace({KEY, VALUE}); for(auto it : mp){ // fist=key, second=value cout << it.first << " " << it.second << endl; } mp.find(); // return iterator
Multi Map#
store key-value pairs in sorted order by key
accept duplicate keys
multimap<int, int> mp; mp[KEY] = VALUE; mp.insert({KEY, VALUE}); mp.emplace({KEY, VALUE}); for(auto it : mp){ // fist=key, second=value cout << it.first << " " << it.second << endl; } mp.find(); // return iterator
Unordered Map#
store key-value pairs, not sorted order
keys must be unique
operations are in O(1), rarely can be O(n)
unordered_map<int, int> mp; mp[KEY] = VALUE; mp.insert({KEY, VALUE}); mp.emplace({KEY, VALUE}); for(auto it = mp.begin(); it != mp.end(), it++){ cout << it->first << " " << it->second << endl; } mp.find(); // return iterator
Sort#
// sort(START_ITERATOR, END_ITERATOR) END_ITERATOR is exclusive, ascending by default sort(a, a + 4); sort(a + 2, a + 4); // sort only portion of array // sort(START, END, BOOL_COMP_FUNC) sort(a, a + 4, greater<int>()); // descending order // Example using comparator function vector<pair<int, int>, pair<int, int>> v; bool comp(pair<int, int>& a, pair<int, int>& b){ return a.first < b.first; // if false, a and b will be swapped to sort } sort(v.begin(), v.end(), comp);
Reverse#
// reverse(START_ITERATOR, END_ITERATOR) END_ITERATOR is exclusive reverse(a, a + 4); reverse(a + 2, a + 4); // reverse only portion of array
PopCount#
return number of 1 bits
__builtin_popcount(x); // x is int __builtin_popcountll(x); // x is long long
Permutation#
- Next Permutation
return 0 or 1
string need to be sorted
// next_permutation(s.begin(), s.end()); string s = "123"; // will have 3! permutations string x = "231"; // only 3 permutations do { cout << s << endl; } while(next_permutation(s.begin(), s.end()));
Min/Max Element#
return memory of min/max element in array
int max = *min_element(START_ITERATOR, END_ITERATOR); int max = *max_element(START_ITERATOR, END_ITERATOR);
STL Custom Implementation#
Vector Custom#
- need data members to hold the size and elements
functions such as
push_back()
cannot be implemented with fixed number of elementsneed data member that points to different sets of elements if more space is required , such as a pointer to the first element
class vector { public: vector(int); ~vector(); int size() const { return sz; }; double get(int); void set(int, double); int operator[](int); private: int sz; double* elements; };
List Custom#
Sample Project Structure#
- top of the project should be entry point for development
enable all development features such as dependency management, tests, docs etc.
- have src as separate cmake project
so consumers can use it as entry point
does not affect development environment
each sub folder is a module, can enable/disable easily
every sub folder’s structure looks similar to the parent’s
- use a package manager
to have reproducible build for consumers
e.g Conan, vcpkg
- at least do these for CI
build on windows, linux, mac with gcc, clang, msvc
build debug and release versions
run cppcheck, clang-tidy
- use tools that makes source files uniform
formatters such as clang-format, cmake-format
linters such as clang-tidy, clazy, cppcheck, include-what-you-use
commercial linters such as pvs-studio, sonar are also available
pre-commit tools
example structure
PROJECT |--->cmake |--->src | |--->app1 | | |--->src | | |----CMakeLists.txt | |--->cmake | |--->module | | |--->include | | |--->src | | |----CMakeLists.txt | |----CMakeLists.txt | |--->tests |----CMakeLists.txt |----CMakePresets.json