C++ notes
Tags :: Language
Arrays
An array is declared as int x[10];
, where the variable x
is an array with 10 int entries. In standard C++ the size of the array must be constant and known at compile time.
Operations
operations are typically performed in loops
float x[3];
for (int i = 0; i < 3; ++i) {
x[i] = v[i] - 3.0 * w[i];
}
Higher dimensions
float A[7][9] //a 7 by 9 matrix
int q[2][3][2] // a 2 x 3 x 2 array
The language does not provide linear algebra operations upon arrays. Implementations based on arrays are inelegent and error prone. e.g. vector addition would look like
void vector_add(unsigned size, const double v1[], const double v2[], double s[])
{
for (unsigned i= 0; i < size; ++i)
s[i]= v1[i] + v2[i];
}
Note how the function requires user passed size which is prone to errors and can be hard to debug at run time.
To get the size of an array we would use
sizeof x / sizeof x[0]
Disadvantages
Arrays have the following disadvantages
- Indicies are not checked before accessing an array , and we can find ourselves outside teh array when the program crashes with segmentation fault/violation. False access can also mess up our data as the program keeps running and produces wrong results.
- The size of the array must be known at compile time, which is a serious problem when we file an array with data from a file.
The first problem can only be solved with new array types and the second with dynamic allocation with pointers.
Variable size arrays
int size;
cin >> size;
int* y = new int[size];
Above allocates an array of int
with size based of input, y is then a pointer
Pointers bear the same danger as arrays: accessing the data out of range, which can casue program crashes ot silent data invalidation. When dealing with dynamically allocated arrays, it tis the programmers responsibility to store the array size.
The programmer is also responsible for releasing memory when it is not needed anymore
delete[] v;
Using sizeof
to get the number of entries in pointers will not work, as it will instead give you the byte size of the pointer itself which is independent from the number of entries.
Whereas defining an array of size n reserves space for n entries, defining a pointer only reserves space to hold an address.
Sequential container types
SequenceContainers are used when memory needs to be stored sequentially or when you want to access data sequentially. There are several types
contigous memory
std::array
static contigous array, fast access with fixed number of elementsstd::vector
dynamic contingous array, fast access but costly insertions/deletions
non-contigous memory
std::deque
double ended queue with effecient insertion/sequence at front and backstd::list
andstd::forward_list
linked list structure alowing effecient insertion/deletion into middle of sequence
Container adapters
These are special types of container class. Not a full class, but wrappers arount the base sequence container types. Theses encapsulate the underlying container type and limit the interface accordingly
stack
: adapter providing LIFO structurequeue
: adapter providing FIFO structurepriority_queue
: adapter providing a priority queue, allowing constant-time lookup of the largest element (by default)
Classes
Classes are not only ways of bundling software, but primarily as instruments to establish new abstractions in our software.
The most important tasks in scientific programming are
- Identifying the mathematical abstractions that are important in the domain
- Representing these abstractions comprehensively and efficiently in software
Use the right abstractions if they do not exist, implement them. Focusing on the right representation for domain specific software has evolved into a programming paradigm called Domain-Driven Design.
The elegant way of writing scientific software is to provide the best abstraction. A good implementation reduces the user interface to the essential behavior and omits all unneccessary commitments to the technical details.
The important benefit of classes in C++ is the ability to establish new abstractions and to provide alternative realizations for them.
A class defines a new data type which can contain
- Data: memember variables, also called Data Members or Fields
- Functions: Methods or Member Functions
- Type definitions
- Contained classes
Members
Member Variables
A concise class example is representing complex numbers
class complex
{
public:
double r, i;
};
complex z, c;
z.r= 3.5; z.i= 2;
c.r= 2; c.i= -3.5;
which stores both the real and imaginary parts of a complex number.
Accessibility
C++ provides three specified accessibility options
- Public
- Accessible from everywhere
- Private
- Accessible only within the class
- Protected
- accessible in the class and it’s derived classes
Accessibility is controlled by Access Modifiers.
class rational
{
public:
...
rational operator+(...) {...}
rational operator-(...) {...}
private:
int p;
int q;
};
the modifiers applies to all following members until another one appears. Class members before the first modifier are all private
File IO
C++ provides classes to preform input and output of characters from/to files:
ofstream
: write to filesifstream
: read from filesfstream
: both read and write to/from files
File streams are used in the same fashion as cin
and cout
, but we have to associate them with physical files. e.g.
#include <fstream>
int main()
{
std::ofstrem square_file;
square_file.open("squares.text");
for (int i = 0; i < 10; ++i) {
square_file i << "^2 = " << i * i << '\n';
}
square_file.close();
}
Alternatively we can pass the filename as an argument to the constructor of the stream to open the file implicitly. The file is also implicitly closed when square_file
goes out of scope (in this case at the end of main
).
Shortened version of code
#include <fstream>
int main()
{
std::ofstrem square_file{"square.txt"};
for (int i = 0; i < 10; ++i) {
square_file i << "^2 = " << i * i << '\n';
}
}
Generic Stream
Streams are not limited to screens, keyboards, and files. Every class can be used as a strem when it is derived from istrem
, ostrem
, or iostream
and provides implementations for the functions of those classes.
Output functions can be written to accept every kind of output streama by using a mutable reference to ostream
as an argument.
#include <iostream>
#include <fstream>
#include <sstream>
void write_something(std::ostream& os)
{
os << "3 * 3 = " << 3 * 3 << '\n';
}
int main ()
{
std::ofstream myfile{"example.text"};
std::stringstream mysstrem;
write_something(std::cout);
write_something(my_file);
write_something(mysstream);
std::cout << "mysstream is : " << mystream.str();
}
Likewise generic input can be implemented with istream
and read/write with iostream
.
Handling /O errors
Streams do not throw exceptions by default as they are older than exceptions. To safely read files we need to check error flags after each I/O operation.
int main()
{
std::ifstream infile;
std::string filename{"some_missing_file.xyz"};
bool opened = false;
while (!opened) {
infile.open(filename);
if (infile.good()) {
opened = true;
} else {
std::cout << "the file '" << filename << "' doesent exist"
std::cin >> filename;
}
}
int i;
double d;
infile >> i >> d;
if (infile.good()) {
std::cout << "i is " << i << ", d is " << d << "\n";
} else {
infile.close();
}
}
References
- Discovering Modern C++: An Intensive Course for Scientists, Engineers, and Programmers
- Sequential Containers