In this class, you should use C++ strings for everything, but be aware that you will have to deal with C-style strings in a few situations:
/* This program demonstrates all of the ways to call the find() method of strings. */ #include <iostream> #include <string> #include <cstdlib> #include <cstdio> using namespace std; int main() { string a, b; size_t i; /* Set two strings to use as examples. */ a = "Lighting Strikes. Lightning Strikes Again."; b = "Light"; /* Print out the strings with digits over the top, so that it's easier to see the digits. */ cout << " "; for (i = 0; i < a.size(); i++) cout << i%10; cout << endl; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << endl; /* We call a.find() in a variety of ways. Ignore the printf statements, because they are a little confusing. Just look at the calls on the right. */ printf("a.find(b) = %ld\n", a.find(b)); printf("a.find(b, 1) = %ld\n", a.find(b, 1)); printf("a.find(b, 20) = %ld\n", a.find(b, 20)); printf("a.find('g') = %ld\n", a.find('g')); printf("a.find('g', 20) = %ld\n", a.find('g', 20)); printf("a.find(\"Strike\") = %ld\n", a.find("Strike")); printf("a.find(\"Strike\", 20) = %ld\n", a.find("Strike", 20)); printf("a.find(\"Aging\", 0, 2) = %ld\n", a.find("Aging", 0, 2)); printf("\n"); printf("string::npos = %ld\n", string::npos); return 0; } |
The first three find() calls illustrate finding a C++ string within a string. It returns the index of the first occurrence of the substring. If you call find() with a second argument, it says to start looking at that index. The first occurrence of "Light" starting from character 1 is at character 19. If find() fails, it returns string::npos, which is in reality -1. However, you should use string::npos rather than -1 to make your programs more portable.
The next two find()'s show finding a character, and the next two show finding a C style substring. The last one shows that if you give it a C style substring, a starting index and a third argument -- count-- it will only look for count characters of the substring. Thus, even though "Aging" doesn't appear in the string, we're only looking for the first two characters -- "Ag" -- which occur at index 37.
UNIX> bin/string-find 0123456789012345678901234567890123456789012 a = Lighting Strikes. Lightning Strikes Again. b = Light a.find(b) = 0 a.find(b, 1) = 19 a.find(b, 20) = -1 a.find('g') = 2 a.find('g', 20) = 21 a.find("Strike") = 9 a.find("Strike", 20) = 29 a.find("Aging", 0, 2) = 37 string::npos = -1 UNIX>The feature of C++ that lets you define multiple instances of a procedure or method that work on multiple types of arguments is called polymorphism. If you give a combination of arguments that is not supported, then you will get a compilation error. For example, in src/bad-find.cpp we make a seemingly innocuous call of "a.find(b, 1, 3)":
/* This shows how you get a compiler error if you make a find() call with the wrong types of arguments. In this instance, you are trying to call: size_type find(const string &str, size_type pos, size_type count); However, that combination of parameters is not supported. */ #include <iostream> using namespace std; int main() { string a, b; int i; a = "Lighting Strikes. Lightning Strikes Again."; b = "Light"; i = a.find(b, 1, 3); return 0; } |
This doesn't compile, because there is no definition of find(string, int, int). There are the following definitions:
UNIX> g++ src/bad-find.cpp bad-find.cpp:19:9: error: no matching member function for call to 'find' i = a.find(b, 1, 3); ...... UNIX>
Substr() is a method that takes a starting index and an optional count, and returns a substring of a string. The simple example program is src/string-sub.cpp
/* This program demonstrates the substr() method of strings. */ #include <iostream> using namespace std; int main() { string a; size_t i; a = "Lighting Strikes. Lightning Strikes Again."; /* Print out digits, so that it's easier to see the indices of the string. */ cout << " "; for (i = 0; i < a.size(); i++) cout << i%10; cout << endl; /* Now make a few a.substr() calls. */ cout << "a = " << a << endl; cout << "a.substr(19) = " << a.substr(19) << endl; cout << "a.substr(19, 13) = " << a.substr(19, 13) << endl; cout << "a.substr(19, 13).substr(5) = " << a.substr(19, 13).substr(5) << endl; return 0; } |
When only one argument is given to substr(), it returns a substring from the given index to the end of the string. If two arguments are given, it returns the specified number of characters. Since the substring is a string, you can call its methods, such as c_str() and substr().
UNIX> string-sub 0123456789012345678901234567890123456789012 a = Lighting Strikes. Lightning Strikes Again. a.substr(19) = Lightning Strikes Again. a.substr(19, 13) = Lightning Str a.substr(19, 13).substr(5) = ning Str UNIX>
/* This is a program to demonstrate using exceptions to handle errors. We process the command line, and when we get an error, we throw an exception, passing a string to the "throw" call. When you throw the exception, the control goes to a "catch" clause that specifies the type of the thrown exception. */ #include <iostream> #include <cstdio> #include <sstream> using namespace std; int main(int argc, char **argv) { int a, b, c; istringstream ss; /* Process the command line, and when you see something wrong, you throw an exception. Since the quoted strings are actually C-style strings, you need to typecast them to C++ strings when you throw them. */ try { if (argc != 4) throw((string) "usage: bin/ex1 a b c"); ss.clear(); ss.str(argv[1]); if (!(ss >> a)) throw((string) "a is not an integer."); ss.clear(); ss.str(argv[2]); if (!(ss >> b)) throw((string) "b is not an integer."); ss.clear(); ss.str(argv[3]); if (!(ss >> c)) throw((string) "c is not an integer."); /* Here's where you "catch" a thrown exception. */ } catch (string s) { cerr << s << endl; return 1; } /* If the "try" clause was successful, you'll end up here, skipping the "catch" code. */ printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %d\n", c); return 0; } |
When we run this, you can see that whenever we discover an error, the control goes directly to the catch() code. If there's no error, the catch() code is skipped:
UNIX> bin/ex1 usage: bin/ex1 a b c UNIX> bin/ex1 no numbers here a is not an integer. UNIX> bin/ex1 55 fred 77 b is not an integer. UNIX> bin/ex1 55 66 77 a = 55 b = 66 c = 77 UNIX>You'll note that I did a typecast of the quoted string to a C++ string. If you don't do that, you'll need to catch a const char *, or you won't catch the exception. In src/ex2.cpp, I don't do the typecast, and as you see, the exception is not caught:
UNIX> bin/ex2 libc++abi.dylib: terminating with uncaught exception of type char const* Abort UNIX>You can specify a catch that catches anything with:
catch (...) |
You don't get the argument when you do this.
/* This demonstrates catching an exception thrown by a procedure call. */ #include <iostream> #include <cstdio> using namespace std; /* The procedure a() throws an exception that it does not catch. */ void a() { printf("A is called, and is going to throw an exception of type int.\n"); throw(5); printf("This code does not get called.\n"); } /* Instead, main() catches the exception and prints out its argument. */ int main() { try { printf("Calling a()\n"); a(); printf("A did not return, did it?\n"); } catch (int i) { printf("I caught an integer: %d\n", i); } return 0; } |
You should be able to trace through the code here to see the following output:
UNIX> bin/ex3 Calling a() A is called, and is going to throw an exception of type int. I caught an integer: 5 UNIX>If you are nested very deep in a sequence of procedure and method calls, and you throw an exception, it will be caught by the closest caller who catches that type of exception. If no one catches it, then you'll see an error like the one with ex2.cpp above.
There are interesting interactions with exceptions and destructors. Stay tuned to future lectures for that.
The issue of random numbers is a thorny one. C's rand() has undergone a number of changes and is not a very good random number generator. I don't believe that drand48()/lrand48() has changed over time, but it's a pretty bad random number generator. I don't recommend that you use lrand48(), because it is so bad. C++11 implements a variety of random number generators -- go ahead and read src/https://en.cppreference.com/w/cpp/numeric/random if you care. They are a little clunky for my tastes, and I've been burned by underlying RNG implementations changing, so I don't use them. However, that doesn't preclude you from using them, so feel free to.
What I'm going to do in this class is use a header-only RNG library that my research group uses. It's called the "Mother of All" RNG, and the original code is available on Github at src/https://github.com/mfoo/Math-Library-Test/blob/master/src/mother.cpp. It is open source, so use it however you want. When I use it in a set of lecture notes, I'll include the header file in the lecture note directory. In this directory, it is in include/MOA.hpp. Here are the relevant parts of the class definition:
/* These are wrappers around the "Mother of All" random number generator from http://www.agner.org/random/. */ #pragma once #include <stdio.h> #include <stdlib.h> #include <string.h> #include <cstdint> class MOA { public: double Random_Double(); /* Returns a double in the interval [0, 1) */ double Random_DoubleI(); /* Returns a double in the interval [0, 1] */ int Random_Integer(); /* Returns an integer between 0 and 2^31-1 */ uint32_t Random_32(); /* Returns a random 32-bit number */ uint64_t Random_64(); /* Returns a random 64-bit number */ void Random_128(uint64_t *x); /* Returns a random 128-bit number */ uint32_t Random_W(int w, int zero_ok); /* Returns a random w-bit number. (w <= 32)*/ void Fill_Random_Region (void *reg, int size); /* reg should be aligned to 4 bytes, but size can be anything. */ void Seed(uint32_t seed); /* Seed the RNG */ |
To demonstrate its simple use, the program src/gendouble.cpp reads a number from standard input, and then generates that many random doubles, between 0 and 1:
/* This program uses the Mother of All random number generator from MOA.hpp to print a bunch of random doubles between 0 and 1. */ #include "MOA.hpp" #include <iostream> #include <cstdio> using namespace std; int main() { MOA rng; double d; int n, i; /* Read a value, n, from standard input. */ if (!(cin >> n)) return 1; /* Generate n random doubles and print them. */ rng.Seed(0); for (i = 0; i < n; i++) { d = rng.Random_Double(); printf("%7.5lf\n", d); } return 0; } |
It works as expected, and is pretty unexciting. Since I set the seed to zero, you get the same random numbers every time. If you don't set the seed, it sets the seed to the current time (in seconds), so you get a different sequence each time you run the program.
UNIX> echo 2 | bin/gendouble 0.33661 0.58429 UNIX> echo 4 | bin/gendouble 0.33661 0.58429 0.33730 0.79562 UNIX>The MOA generator is pretty simple and fast.