C++ Page #3: Special Topics


There's strictly no warranty for the correctness of this text. You use any of the information provided here at your own risk.


Contents:

  1. Processing Command-Line Options
  2. Using Regular Expressions
  3. Reading and Writing Text-Files
  4. "filesystem"-operations: getcwd; listdir; exists
  5. Creating Dynamic Libraries on Linux (.so-Files)


1. Processing Command-Line Options

The program's "main()"-function can also have the parameters "int argc" and "char *argv[]".
"argv" is an array of "pointers to char". So it's an array of C-strings.
The first of these strings is the name of the script.
"argc" is the number of elements of "argv". It is at least 1. Neither the programmer, nor the user passes the number "argc" (or "argv") explicitely to the "main()"-function. Instead, the user passes options on the command-line, and "argc" and "argv" are automatically set accordingly.
It is relatively easy to convert the C-strings in "argv" to C++-strings (which are of type "std::string"). Here's an example:

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char *argv[]) {
    cout << argc << endl;
    vector<string> v;
    string s;
    for (int i = 0; i < argc; i++) {
        s = argv[i];
        // "v.push_back(argv[i]);" would also be possible:
        v.push_back(s);
    }
    for (string i : v) {
        cout << i << endl;
    }
    return 0;
}


2. Using Regular Expressions

For simpler search- and replace-operations in C++strings, the methods

string.find()

and

string.replace()

can be used. Since C++-20, there are also

string.starts_with()

and

string.ends_with()
Since C++11, it is also possible to use more complicated regular expressions.
The function "regex.search()" returns "true", if a pattern in a string is matched. Arguments are a C++-string and a "std::regex"-object, created from a regular expression. Here's an example:
#include <iostream>
#include <regex>

using namespace std;

int main() {
    if (regex_search("Hello", regex("He"))) {
        cout << "Found." << endl;
    }
}

This searches for characters that aren't numbers:

#include <iostream>
#include <regex>

using namespace std;

int main() {
    if (regex_search("123a", regex(R"(\D)"))) {
        cout << "Found." << endl;
    }
}

The "R" somehow indicates a raw string. Otherwise you'd have to write "\\D" all the time.

As can be expected, there's also "regex_replace()".


3. Reading and Writing Text-Files

Here we're writing some lines of text to a file called "schiller.txt", then we read in the contents of that file into a vector and print it:

#include <iostream>
#include <vector>
#include <fstream>

using namespace std;

void printStringVector(vector<string> &vecref) {
    int vecsize = vecref.size();
    cout << "[";
    for (int i=0; i < vecsize; i++) {
        cout << "\"";
        cout << vecref[i];
        cout << "\"";
        if (i < vecsize - 1) {
            cout << ", ";
        }
    }
    cout << "]" << endl;
}

vector<string> readfile(string filename) {
    vector<string> a;
    string buf;
    ifstream fh(filename);
    if (fh.good() == false) {
        cout << "Warning: Couldn't read from file '" << filename << "'." << endl;
        fh.close();
        return a;
    }
    while (getline(fh, buf)) {
        a.push_back(buf);
    }
    fh.close();
    return a;
}

void writefile(const string &filename, vector<string> &text) {
    ofstream fh;
    fh.open(filename);
    if (fh.good() == false) {
        cout << "Warning: Cannot write to file '" << filename << "'. Nothing done." << endl;
        fh.close();
        return;
    }
    for (string i: text) {
        fh << i << endl;
    }
    fh.close();
}

int main() {
    string filename = "schiller.txt";
    vector<string> schiller = {"Und blicket sie lange verwundert an.", 
                               "Drauf spricht er: 'Es ist euch gelungen,",
                               "Ihr habt das Herz mir bezwungen,",
                               "Und die Treue, sie ist doch kein leerer Wahn.'"};
    // Writing to file:
    // Commented out for security reasons. If you want to write the file
    // "schiller.txt", delete the leading "//" of the next line:
    // writefile(filename, schiller);

    // Reading from file:
    vector<string> a = readfile(filename);
    printStringVector(a);
    return 0;
}

Notice, that you have to use "getline(fh, [string]);" to read a line from a file, just like you have to use getline(), when reading a line from "cin". If you just did "fh2 >> buf", it would read the file word by word.


4. "filesystem"-operations: getcwd; listdir; exists

The language update "C++17" introduced "std::filesystem", which made certain important file-operations in C++ a lot easier. (Before C++17, these functions could be found in the C++-library "Booth"; some people also kept solving the problem in C-style.)
Here's an example program. As "C++17" is required, it has to be compiled using the compiler-executable "g++-14" or such. The example program then shows, how the following operations can be done:

#include <iostream>
#include <vector>
#include <filesystem>

// Compile with g++-14 (C++17 and above)

using namespace std;

void printStringVector(vector<string> &vecref) {
    int vecsize = vecref.size();
    cout << "[";
    for (int i=0; i < vecsize; i++) {
        cout << "\"";
        cout << vecref[i];
        cout << "\"";
        if (i < vecsize - 1) {
            cout << ", ";
        }
    }
    cout << "]" << endl;
}

vector<string> listdir(string &path, bool wantfullpath = false) {
    vector<string> v;
    std::filesystem::path p;
    // Code from: https://stackoverflow.com/questions/612097/how-can-i-get-the-list-of-files-in-a-directory-using-c-or-c
    for (const auto & entry : std::filesystem::directory_iterator(path)) {
        p = entry.path();
        if (wantfullpath) {
            v.push_back(p);
        } else {
            v.push_back(p.filename());
        }
    }
    return v;
}

int main() {

    // Getting the current working directory:
    string cwd = std::filesystem::current_path();
    cout << endl << "The current working directory is '";
    cout << cwd << "'." << endl;
    cout << endl;

    // Printing the contents of the current working directory:
    vector<string> v = listdir(cwd);
    cout << "The content of the current working directory is:" << endl;
    printStringVector(v);
    cout << endl;

    // Check, if a file exists:
    string filename;
    if (v.size() > 0) {
        filename = cwd + "/" + v[0];
        if (std::filesystem::exists(filename)) {
            cout << "The file '" << filename << "' exists." << endl;
        } else {
            cout << "The file '" << filename << "' doesn't exist." << endl;
        }
    }
    cout << endl;
}


5. Creating Dynamic Libraries on Linux (.so-Files)

Creating a library from a ".cpp"-source-file isn't as difficult as one might think. This tutorial explained, how it's done.

To create and use a dynamic C++-library on Linux, two files are needed: One is the library-file, which is called something like "libsomelibrary.so", the other one is a header-file with an extension ".h" (or as we are in C++ with no extension at all).
The ".so"-file is copied to the directory "/usr/lib64", the header-file to a directory under "/usr/include".
Then, the header file is included in a new source-file with "#include <somelibrary>".
And finally, the compiler has to be told to use the library: "g++ -lsomelibrary sourcefile.cpp".

Ok, let's create the two mentioned files first: If you have a source-file "somelibrary.cpp" that contains all the functions of the library, you just need to run these commands:

g++ -fPIC -c somelibrary.cpp -o somelibrary.o
g++ -shared -o libsomelibrary.so somelibrary.o

This creates a file "libsomelibrary.so". Here's a little Perl-script around the two commands above:

#!/usr/bin/perl

use warnings;
use strict;

my $LIBNAME = "somelibrary";

my $e;
$e = "g++ -fPIC -c ";
$e .= $LIBNAME;
$e .= ".cpp -o ";
$e .= $LIBNAME;
$e .= ".o";
print "$e\n";
# system($e);

$e = "g++ -shared -o lib";
$e .= $LIBNAME;
$e .= ".so ";
$e .= $LIBNAME;
$e .= ".o";
print "$e\n";
# system($e);

Then you need a file "somelibrary.h". This file basically exposes the available functions to a developer, who wants to use the library. The ".h"-file shows what's there, while the ".cpp"-file defines, how it works.
In general, definitions say, how a thing works, while declarations just say that the thing exists.

The file "somelibrary.h" could for example look like this:

// somelibrary.h
// This is a C++ Library header.

#ifndef SOME_LIBRARY
#define SOME_LIBRARY

using namespace std;

// Functions:
void someFunction(string &parameter);

#endif

When you have "libsomelibrary.so" and "somelibrary.h", these files should be copied to directories in the path of the compiler. ".so"-files are typically in

/usr/lib64

while header files (".h") are in or below

/usr/include

On my system there is for example a directory

/usr/include/c++/7

that contains files like "iostream", which should sound familiar.
The ".h"-suffix of the header file can also be left out, so you can for example do as root:

#!/bin/bash

if [[ $EUID -ne 0 ]]; then
   echo 
   echo "Error: This script must be run as root." 
   echo 
   exit 1
fi

cp -v libsomelibrary.so /usr/lib64
cp -v somelibrary.h /usr/include/c++/7/somelibrary

After that, the library is ready to use.
In the ".cpp"-file that uses it, the headerfile can be included with

#include <somelibrary>

And when a new program is compiled, to use the library, the command-line option "-lsomelibrary" has to be added to the compilation-line like this:

g++ -lsomelibrary newprogram.cpp



Back to the computing-page


Author: hlubenow2 {at-symbol} gmx.net