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

  1. 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.
  2. 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 elements
  • std::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 back
  • std::list and std::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 structure
  • queue: adapter providing FIFO structure
  • priority_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

  1. Identifying the mathematical abstractions that are important in the domain
  2. 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 files
  • ifstream: read from files
  • fstream: 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

Links to this note