Disclaimer. This is a short (and informal) review of topics I originally compiled while enrolled in CS3157 Advanced Programming at Columbia University in Spring 2015. It is by no means meant to be a comprehensive review of any of the above listed topics, though I hope that it might be useful to some as a quick reference or refresher on material relevant to user-level UNIX programming in C. Please email me with any corrections or comments.
## Input/Output
### Standard IO
The C standard library automatically provides every running program with 3 IO channels: stdin (incoming character stream, usually from keyboard), stdout (outgoing character stream, newline or filled buffer, usually to terminal screen), stderr (outgoing stream, unbuffered, normally to terminal screen)
<stdio.h>
contains prototypes for standard IO functions such as printf()
scanf()
and many more
### Redirection with the UNIX terminal
To redirect stdin from a file, use a <
symbol as in: ./isort < file_with_a_number
To have stdout go to a file, use a >
symbol as in: ./isort > sorting_result
Use 2>
to redirect stderr, as in ./isort 2> myerrors
Use >>
to append to an existing file
Use 2>&1
to redirect to the same place as stdout
Note that > file
must already be explicitly defined in this case
### Pipelines
A pipe connects stdout of one program to stdin of another.
prog1 | prog2 | prog3
You can throw redirections in there too:
prog 1 < input_file | prog2 | prog3 > output_file
, or
cat input_file | prog1 2>&1 | prog2 | prog3 > output_file
### Formatted IO
inf scanf(formatting, &value)
where formatting=%d
, value=&i
or something. Most of these skip leading whitespaces.
int printf(formatting, values)
where formatting=Hi %s
, value=name
, for example. Obvious... %u
=unsigned int, %ld
=long, %lu
=unsigned long, %f
=double, %p
=address
int sscanf(const char* input, const char* format, ...)
reads from input string instead of stdin
int sprintf(char* output_buffer, const char* format, ...)
prints to output buffer instead of stdout
snprintf takes a size argument
### File IO
FILE* fopen(const char* filename, char* mode)
opens a file, returning a FILE*
that can be passed to other file-related functions. Use mode="rb"
, "wb"
, "ab"
appending a "+"
for dual operations.
char* fgets(char *buffer, int size, FILE \*file)
reads at most size-1 characters into buffer, stopping if newline is read (the newline is included in the characters read) and terminating the buffer with '\0'
. Returns NULL
or EOF
on error, use ferror()
afterwards.
int fputs(const char *str, FILE *file)
writes str to FILE, returns EOF on error
int fscanf(FILE *file, const char *format, ...)
int fprintf(FILE *file, const char *format, ...)
Are the file IO analogues of scanf
& printf
MAKE SURE TO USE fclose(FILE* file)
to close a file. Note that stdin, stdout, and stderr are all FILE*
objects that have been pre-opened.
fflush(FILE\*)
manually flushes the buffer for a file, setbuf(fp, NULL)
turns off buffering for fp.
int fseek(FILE\* file, long offset, int whence)
sets the file position for the next read or write--the new position, measured in bytes, is obtained by adding offset bytes to the position specified by whence: SEEK_SET
, SEEK_CUR
, or SEEK_END
. Returns non-zero value on error.
size_t fread(void *p, size_t size, size_t n, FILE *file)
reads n
objecst, each size
bytes long, from a file into the memory location pointed to by p. Returns the number of objects successfully read. If written != n, then use ferror()
.
size_t fwrite(const void *p, size_t size, size_t n, FILE *file)
writes instead.
## Introduction to UNIX
### Intro to OS
Software organization:
applications (emacs, gcc, firefox, etc.) build on
library functions (printf, strcpy, malloc, fopen, fread) build on
system calls (open, read, fork, signal) build on
OS kernel built on hardware (processor, memory, disk, video card, keyboard, etc.)
### UNIX Overview
All users are identified by a username--all users are equal except for root (uid 0). The file system is comprised of a single root directory with relative paths corresponding true absolute paths. Everything in UNIX is a file, even directories and hardware devices.
Every user has permissions that are denoted by an integer number corresponding to: permissions = read,write,exec in binary. To change the permissions for a file, chmod is used, as in chmod 644, which sets the owner's permissions to 6 (110 = read and write), the group permissions to 4 (100 = read only), and other's permissions to 4 (100 = read only).
Unix IO works using file descriptors (small integers) which are used for files and sockets, and stdin/stdout/stderr.
### Processes
A program is a packaged set of instructions, whereas a process is an instance of a program. A program can have many processes associated with it.
A process (id = getpid()
) is created by fork and exececuted by an existing process. For example, inside a process, fork() can be called, which "returns twice" (less than 0 if error), giving pid=fork()==0
for the child process and the child process pid for the parent process. execl() can be used to call a program from a child process after forking. The kernel starts the "init" process, which in turn starts various login managers, etc.
waitpid(...)
can be used to wait for a child process specified by a certain pid to finish so that the parent process can cleanup resources. With the flag WNOHANG
, this can be used to check if a child process has exited.
Note that the process ID of the child process is NOT equal to 0. Also note that the order of execution of the child and parent processes relative to each other is unpredictable.
### Signals
Signals are ways of the OS to tell processes immediately that something happened. Sometimes, then signal(SIG_IDENTIFIER, handler_function)
can be used to catch the signal and so your own thing, such as for SIGINT
when the user presses Ctrl-C.
## Introduction to TCP/IP Networking
A pipe is the simplest form of one-way (unidirectional) interprocess communication (with a common ancestor), created by the pipe()
system call. Usually a pipe is called by a process before it forks in order to setup a communication channel between child processes.
### TCP/IP Networking
Sockets are similar to pipes, but they are two way (bidirectional) and connect to remote processes. A socket is a type of file used for network communication between processes.
There are 5 protocol layers of TCP/IP, which are the protocols of the Internet. The sockets API sits between layer 4 and 5. We will take layers 1-4 as black-box. Sockets provide an interface to TCP/IP.
An IP address is a 32-bit (4 byte) integer, usually written in dotted-quad notation such as 128.59.0.5
. The Domain Name Server (DNS) translates hostnames, such as tokyo.clic.cs.columbia.edu
into an IP address.
Port numbers are needed to distinguish between many network applications in a host. They are unsigned 2-byte integers: 1-65535 (0 is reserved, we can generally use only 1024 and above). A fixed number exists for many well-known apps (ex: 80 for web servers, 25 for email).
The client-server model stipulates that a server "listens" on a known port, waiting for a client to "connect" to it. From that point on, after the server accepts the connection, there exists two-way communication between the client and the server/host.
### Netcat
Netcat (nc
) is the TCP/IP swiss army knife! Study the man pages for this--"man nc". Netcat connects stdin/stdout with a socket.
Server mode: nc -l <port>
, listens on a specified port for connections
Client mode: nc <hostname or IP> <port>
, connects to a host specified by IP on a selected port
"Named pipes" can be created using mkfifo, which create FIFO (first in first out) named files that essentially represent pipes. These pipes can be written to and read from, allowing for them to function like sockets between processes.
Nb: the tee
Tee takes input from stdin, and writes it to both stdout and a file whose name is specified as an argument. As well, when using a pipe to feed the output of one program to multiple processes, the second process must be running in the background because opening a FIFO for reading blocks until a program opens it for writing and vice versa.
Netcat connects the stdin of the client to the stdout of the server and the stdin of the server to the stdout of the client.
## Sockets and HTTP
### Sockets API
Make sure to do reading from "Beej's Guide to Network Programming" and Baylor's lecture slides and code examples.
Sending and receiving bytes through a socket connection can be accomplished by means of using the following functions:
send(int socket, cosnt void \*buffer, size_t length, int flags);
Normally, send()
blocks until it sends all bytes requested and returns the number of bytes sent or -1 in the case of an error. When flag==0
, send()
is equivalent to write()
as in file operations.
recv(int socket, void \*buffer, size_t length, int flags);
Normally, recv() blocks until it has received at least 1 byte and returns the number of bytes received, 0 if the connection is closed, or -1 in the case of an error. When flag==0
, recv()
is equivalent to read()
as in file operations.
### HTTP - Hyper Text Transfer Protocol
HTTP is the network protocol used to deliver virtually all files and other data, collectively called resources, on the world wide web, whether they are HTML files, image files, query results, or anything else. Usually, HTTP takes place through TCP/IP sockets. A browser is an HTTP client because its sends requests to an HTTP server (web server), which then sends responses back to the client through the default port 80.
HTTP is a protocol to exchange data in the case of web uses (HTML, etc.). It is a series of POST
and GET
requests following a certain format, which the client and server use to coordinate the exchange of the desired information.
The format of the request and response messages are similar, and English-oriented. Both kinds of messages consist of: an initial line, zero or more header lines, a blank line, and an optional message body (e.g. a file, query data, or query output).
<initial line, different for request vs. response>
Header1: value1
Header2: value2
Header3: value3
<optional message body goes here>
A request line has three parts, separated by spaces, a method name, the local path of the requested resource, and the version of HTTP being used.
GET /path/to/file/index.html HTTP/1.0
GET
is the most common HTTP method; it says "give me this resource".
The initial response line is the HTTP version, the response status code, and an English phrase describing the status code.
HTTP/1.0 200 OK
or
HTTP/1.0 404 Not Found
Header values can identify more information about the request being made or the response being given. For example, a browser can identify itself through the User-agent: Mozilla/3.0Gold
header. If the HTTP message includes a body, there are usually header lines in the message that describe the body, such as Content-Type: text/html
or Content-Length: x
giving the type and size in bytes of the content respectively.
The HEAD
method can be used just like a GET
request, but it asks the server to return the headers only, not the body or value of the response. It is useful to check the characteristics of a resource without actually downloading it, thereby saving bandwidth and time.
The POST
method is used to send data to the server to be processed in some way. It differs from a GET
request in that it contains data as a message body, has extra headers to describe the message body (like Content-Type
and Content-Length
), and the request URI is not a resource, but is a program to process the data that you are sending.
Generally speaking, HTTP 1.1 is a superset of HTTP 1.0 with improvements including faster responses, by allowing multiple transactions to take place over a single persistent connection, faster response by bandwidth savings and by cache support, faster response for dynamically-generated pages, by supporting chunked encoding, which allows a response to be sent before its total length is known, and efficient use of IP addresses, by allowing multiple domains to be served from a single IP address. HTTP 1.1 requires a few extra things from both clients and servers...
An HTTP 1.1 header must specify which host name (and possibly port) the request is intended for, using Host: www.host1.com:80
. For chunked transfer-encoding, the type of transfer-encoding is specified in the header, followed by a blank line and the data in possibly many chunks before another blank line.
The 100 Continue
response must be handled by all HTTP 1.1 clients in order to allow for keeping slow connections open.
Dynamic web sites manage sessions by having the server "feed" the client a "cookie" of data at the start of the session, telling the client information about the protocol, the status, the format of the data that will be transferred (text/html, for example), and where to store a named local cookie containing data for next time.
### Sockets Usage
mySock = socket(family, type, protocol);
Usually family=PF_INET
, type=SOCK_STREAM
, protocol=IPPROTO_TCP
, returning a file (socket) descriptor for the socket in UNIX.
For IP, struct sockaddr_in
is used to specify the address of a socket's connection.
The workflow for establishing a socket connection for the server is
1. Creating a TCP socket:
if((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
die("socket() failed");
2. Assigning a port to the socket
echoServAddr.sin_family = AF_INET // internet address family
echoServAddr.sin_addr.s_adr = htonl(INADDR_ANY) // any incoming interface
echoServAddr.sin_port = htons(echoServPort); // local port number
if(bind(servSock, (struct sockaddr\*) &echoServAddr, sizeof(echoServAddr)) < 0)
die("bind() failed");
3. Setting the socket to listen:
if(listen(servSock, MAXPENDING) < 0)
die("listen() failed");
4. Repeatedly:
a. Accepting a new connection
b. Communicating
c. Closing the connection and returning to start of loop
for(;;) {
clntLen = sizeof(echoSlntAddr);
if((cSock = accept(servSock, (struct sockaddr\*) &echoClntAddr, &clntLen)) < 0)
die("accept() failed");
}
The server is now blocked waiting for a connection from a client. Note that communication logic happens after accepting the client socket and it is necessary to close this socket before returning to the top of the loop.
The workflow for establishing a socket connection for the client is
1. Creating a TCP socket:
if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
die("socket() failed");
2. Establishing a connection based upon address information:
echoServAddr.sin_family = AF_INET; // internet address family
echoServAddr.sin_addr.s_addr = inet_addr(servIP); // server IP address
echoServAddr.sin_port = htons(echoServPort); // server port
if(connect(sock, (struct sockaddr*) *echoServAddr, sizeof(echoServAddr)) < 0)
die("connect() failed");
3. Communicate: Client->: sends string by "writing" to socket
echoStringLen = strlen(echoString); // determine input length
if(send(sock, echoString, echoStringLen, 0) != echoStringLen)
die("send() sent a different number of bytes than expected");
->Server: reads from socket into buffer
if((recvMsgSize = recv(cSocket, echoBuffer, RCVBUFSIZE, 0)) < 0)
die("recv() failed");
Server-> and Client<- can be done in the same way, working with the corresponding sockets.
4. Close the connection: For client and also server, the connection can be closed by calling close(socket)
.
### Notes about socket usage
A client must know the server's address and port, whereas the server only needs to know its own port. There is no correlation between which can use send()
and recv()
, they can both be used by the client and server. close()
is analogous to EOF
as far as delimiting communication. For more complex communication, the TCP/IP protocol can be used to transport bytes that are ordered in an agreed upon way by both the sender and receiver--encoded using primitive types (strings, integers, etc.) and composed types (messages with fields). Strings are encoded as ASCII, Unicode, UTF and are delimited by length or termination character. Integers can be sent using their native representations but the byte-ordering needs to be paid attention to, using the htons (host to network short) and ntohs (network to host short) functions to convert to the network's Big-Endian byte ordering. Message ordering with fields can have fixed-length fields, as in primitive integers and shorts forming a message, or can use variable-length fields that are somehow delimited.
### Sockets as Files
One can also read and write from a socket using the same conventions that normally apply to files, by opening a connected (accepted) socket as a file:
FILE* sockin = fdopen(clntSock, "r");
FILE* sockout = fdopen(dup(clntSock), "w");
These files, sockin and sockout, can now be manipulated as usual. Note that if the file sockin or sockout is closed, the underlying socket is closed as well (not necessary to close manually). Further, the duplication is necessary in the case that read and write operations are attempting to happen at the same time to the same file. It is safer to just specify the write as a duplicate of the socket used for reading.
## Introduction to C++
### Why C++?
Features: object-oriented programming facilities, user-defined types (classes), polymorphism (inheritance), generic programming (templates), exceptions, full-blown standard library (containers, algorithms), and a STRING class. We will focus on how the fundamental language facilities work, cornerstones for writing correct and safe C++ code, RAII: resource acquisition is initialization, standard library essentials (with templates), practical usage with class design and C++ syntax.
### Strings in C (and their limitations)
A string is allocated on the stack (by creating an array of chars) or on the heap, by using malloc(sizeof(char) * strlen(str))
giving a char*
. A String struct can be defined in C with a char *data
element and an int len member with functions to allocate, deallocate, append, etc. but this is annoyingly stupid...
### Review of Important Concepts
Declaration tells the compiler the name and type of an object which is defined somewhere else, such as extern int x; int f(int x); struct MyList; class MyClass;
Definitions allocate memory, or define functions with code body or objects with members.
In C, the distinction between stack and heap allocation is based upon normal declaration or the explicit reservation of heap memory using malloc and free.
In C++, stack allocation corresponds to a normal declaration, such as
struct Pt p3(0,0)
which, due to RAII, is destroyed at the end of its scope (its destructor is called, can clean up sockets and files and more)
To allocate on the heap in C++, the new
keyword is used with delete
for the pointer to the object, as in:
struct Pt \*p4 = new Pt(0,0);
delete p4;
Passing by value (copy) and by reference
f(struct Pt p)
passes by copy of value
f(struct Pt \*p)
passes by copy of the pointer value
f(struct Pt &p)
passes by reference to the object (can modify like pointer but cannot ressaign using pointer arithmetic, and operates like a normal object)
Nb: heap-allocated array of objects are deleted by delete [] object_pointer_array
### C++ Basics
The Constructor decides the arguments to the creation of an object (usually a default constructor is provided in most cases), and properly initializes all data members and base classes.
The Destructor properly deallocates all data members.
The Copy Constructor is called when initializing an object, passing as an argument to a function, or returning a value from a function.
A Copy Assignment is called in assignment expressions.
The compiler generates default versions of these when they aren't explicitly provided, but these might not be what you want.
In order to specify if you want the compiler to generate a copy constructor and assignment operator, either set the prototype = delete or = default as in:
DuperUpper(const DuperUpper& du) = delete;
tells the compiler not to generate copy constructor
DuperUpper& operator=(const DuperUpper& du) = default;
tells the compiler to use the default generated assignment operator
### C++ Essentials
C++ 11 newly added move constructors and move assignments as a performance enhancement.
A reference can be thought of as a copy of a dereferenced pointer. That is, it functions essentially as an alias for an object that changes with the object as well as can be assigned to change the value of the original object.
Copy constructor: MyObject(const MyObject& o)
Copy assignment: MyObject& operator=(const MyObject& o)
See code for string example class!
The subtle difference between a class and a struct is that by default (before specification), members in a class are private while members in a struct are public. There are four elements of a C++ class that you should always consider:
1. Constructor: creates the object
2. Destructor: destroys the object
3. Copy Constructor: specifies how to construct a class type variable using an argument of that class type, i.e. Mystring s1("hi"); Mystring s2(s1);
or when passing by value to a function. Sometimes this is necessary to explicitly specify in order to avoid memory errors resulting in having pointers to the same heap memory get freed independently.
As a rule of thumb, if your class necessitates explicit definition of a destructor, as MyString
does, chances are that your class necessitates explicit definition of a copy constructor.
4. Operator=()
: If your class necessitates the definition of a copy constructor, your class necessitates the definition of an **assignment operator** for the same reasons. Returns a reference so that calls can be chained (as well as no copy).
Nb: this
is a pointer to the current object that you are working inside.
### Fun Stuff with C++
Implicit type conversions can exist in C++, even with class type variables. For example, using +=
to add to MyString
object automatically uses the MyString
defined constructor for a char*
and then calls the defined +=
operator. Smart.
C++ allows the overloading of operators in order to define useful behaviors for user data types, allowing for shorter notation and more readable and readily understandable code.
You may find yourself running into the question of member versus nonmember implementation of your operators. Symmetric operators, operators that should allow implicit conversion of either operand, should be nonmember functions. Two examples of these are the +
and -
operators. Operators whose left-hand operand isn't of the class type shouldn't be members of the class, for example, the <<
and >
operators of our MyString
class. Operators that change the state of their object _should_ be members. The assignment (=
), subscript ([]
), call (()
), and member access arrow (->
) operators must be members.
In order for a function that is not a member to have access to our non-public data members, we must declare that functions signature as a friend in the class. Remember this joke: "Only you and your friends can touch your private parts."
A const member function is a function that promises not to modify the object on which the function is being called. This is declared after the end of the function signature, as in
const char\* operator[](int i) const;
## A Tour of C++ notes
### Tour of C++ chapter 1
It is better to use the universal form based on curly-brace-delimited initializer lists to keep from losing data in assignments (say, if the below were of integer type).
double d1 = 2.3;
double d2{2.3};
When defining a variable, you don't actually need to state its type explicitly when it can be deduced form the initializer, in the case of functions that return only one value type in an instance, or in something like the following cases:
auto b = true; // a bool
auto ch = 'x'; // a char
auto i = 123; // an int
auto d = 1.2; // a double
auto z = sqrt(y); // z has the type of whatever sqrt(y) returns
It is, however, advisable to specify the type of a variable when one wants to be explicit about a variable's range or precision, or the definition is in a large scope where one wants to make the type clearly visible to readers of one's code.
const
roughly means "I promise not to change this value" and is used mainly when specifying interfaces. The compiler enforces the promise made by const
.
constexpr
roughly means "to be evaluated at compile time" and is used primarily to specify constants, to allow placement of data in read-only memory (where it is unlikely to be corrupted) and for performance. For a function to be usable in a constant expression that will be evaluated by the compiler, it must be defined as a constexpr
. The ranged for statement is used for loops that traverse a sequence in the simplest way:
int v[] = {0,1,2,3,4,5,6,7,8,9};
for(auto x : v)
cout << x << '\n';
for(auto x : {10,21,32,43,54,65})
cout << x << '\n';
The ranged-for statement can be read as "for every element of v, from the first to the last, place a copy in x". The range-for statement can be used for any sequence of elements. If we wanted x to not be a copy but rather to refer to an element, we would write:
for(auto& x : v)
++x;
In a declaration, the unary suffix & means "reference to", which is similar to a pointer except that you don't need to use a prefix _ to access the value referred to by the reference. Also, a reference cannot be made to refer to a different object after its initialization, which means they are particularly useful for specifying function arguments.
When a pointer does not point to an object (that is, when dereferencing it would be invalid), or if we need to represent the notion of "no object available", we give a pointer the value nullptr
, as in:
double_ pd = nullptr;
Link<Record>\* lst = nullptr;
It is often wise to check that a pointer argument that is supposed to point to something, actually points to something--duh.
### Tour of C++ chapter 4
The default meaning of copy is memberwise copy: copy each member. Copy initialization and copy assignment are distinct in the cases below:
complex z1;
complex z2{z1}; // copy initialization
complex z3;
z3 = z2; // copy assignment
The fact that a class has a destructor is a strong hint that the default (memberwise) copy semantics are wrong and the compiler should at least warn against this example. A user-defined copy constructor and copy assignment can be defined as follows:
class Vector {
public:
Vector(const Vector& a); // copy constructor
Vector& operator=(const Vector& a); // copy assignment
}
By defining constructors, copy operations, move operations, and a destructor, a programmer can provide complete control of the lifetime of a contained resource (such as the elements of a container). In order to suppress an operation, that is, to have an attempt to copy an object, for example, be caught by the compiler, you simply do as follows to remove the default definition:
}Shape(const Shape&)=delete; // no copy operations
}Shape& operator=(const Shape&)=delete;
The explicit keyword bars the compiler from performing implicit conversions. For example, if a function takes an object Foo
type, but is instead passed an int
, yet there exists a constructor for a Foo
from an int
, it would allow this behavior. Adding explicit to the constructor prevents the compiler from using that constructor for implicit conversions.
### Tour of C++ chapter 5
The using keyword allows the data type of a templated object to be stored, as in:
template<typename T>
class Vector
{
public:
using value_type = T;
// ...
}
Which makes Vector::value_type
a typename
object that can later be used. An example of when this would be useful is:
template<typename C>
using Element_type = typename C::value_type; // the type of C's elements
template<typename Container>
void algo(Container& c)
{
Vector<Element_type<Container>> vec; // keep the results here
// ...
}
### Tour of C++ chapter 9
A class with the main purpose of holding objects is commonly called a container. The most useful standard-library container is vector. A vector is a sequence of elements of a given type. The elements are stored contiguously in memory. A typical implementation of vector will consist of a handle holding pointers to the first elements, one-past-the-last element, and one-past-the-last allocated space. It also holds an allocator from which the vector can acquire memory for its elements, with the default using new and delete.
vector.push_back(e)
adds a new element at the end of a vector, increasing its size by one.
When a new element is inserted into a vector, its value is copied into the container. IMPORTANT!! The element is not a reference or a pointer to some object outside of the container (unless you are copying a pointer and want to be pedantic about my phrasing).
The standard-library vector does not guarantee range checking, for example, int i = book[book.size()].number
is out of range and is more likely to place some random value in i
rather than giving an error.
The list container implements a linked list that is useful when we want to insert elements and delete elements without moving other elements. Writing code in a list to lookup corresponding pairs of data is tedious, so the map exists.
The map is an associative array or dictionary implemented as a balanced binary tree that allows values to be indexed by keys. The cost of a map lookup is O(log(n)) and if an element is not found with array notation, it is added with the given key and the default value of the data type.
### Tour of C++ chapter 10
The standard library provides common operation on containers, such as sorting and traversing data structures with iterators. In order to sort:
sort(vec.begin(), vec.end()); // by default uses the < operator for order
Note that the <
operator must be defined in this case for the objects being compared--the data type of vec.
For operations such as unique_copy that take an iterator which can be used to add elements to the output container, list.begin()
can commonly be used, but then the sizes must match. In order to add elements to the end of the container and extends the container to make room for them, back_inserter(list)
could be used instead.
The standard algorithm find looks for a value in a sequence and returns an iterator to the element found--returning the end()
iterator in the case that nothing could be found, which is common for all standard-library search algorithms.
There exist many different types of iterators for the different containers: for example, an iterator for a vector could simply be a pointer to the element, whereas an iterator for a list might be a pointer to a link. For all iterators the following hold...
1. applying ++
to any iterator yields an iterator that refers to the the next element
2. applying *
yields the element to which the iterator refers
For example, list=<Entry>::iterator
is the general iterator type for list<Entry>
, but this could be ignored by using auto data types.
## Generic Programming: templates and STL
### Template functions and classes
When defined with a template, the compiler uses the template definition to generate the "typed instances" of the template function as needed.
When implementing a templated class, the template declaration is needed once above the class declaration and above all templated functions that are defined in another file.
### Vector standard library class
vector<T>
contains objects of type T
"by value", that is, when you push_back(x)
, a copy of x
is constructed (by calling the copy constructor of T
) and held in the vector. The elements are destroyed along with the vector when the vector gets destroyed.
Deque
is short of double-ended queue, similar to vector
, but unlike vector, deque
supports efficient addition and removal of elements from the beginning of the deque
, yet in general is slower and/or wastes more memory than vector and elements are NOT guaranteed to be stored contiguously in memory.
### Iterators
When using a ranged-based for loop for const iterators, you can use the form:
for(const auto& element : v)
cout << element << endl;
Dereferencing an iterator gives you a T&, and dereferencing a const_iterator gives you a const T&
. v.end()
gives an iterator that points to one past the last element and is used as a marker for the end, so it CANNOT be dereferenced.
## Advanced C++: smart pointer
### Smart pointer
What is it? A C++ class that behaves like a pointer. Why? We want automatic life-time management of heap-allocated objects, value semantics without having to copy large objects, similar to a Java reference.
The SmartPtr
class is a light-weight handle for heap-allocated objects which manages the object life-time using reference counting and provides value semantics, thus allowing it to be put into standard containers.
### shared_ptr
The C++11 standard includes a smart pointer template class called shared_ptr
, which works exactly the same way as our SmartPtr
, but is more powerful. It has atomic reference counting for thread safety, it can attach weak_ptr
, which does not participate in reference counting, and has a customizable delete operation.
## Citation
Cited as:
Schuermann, Lucas V. (May 2016). C/C++ Essentials Review. Writing. https://lucasschuermann.com/writing/c-essentials-review
Or
@misc{schuermann2016cpp,
title = {C/C++ Essentials Review},
author = {Schuermann, Lucas V.},
year = {2016},
month = {May},
url = "https://lucasschuermann.com/writing/c-essentials-review"
}