Function Practice#
Here are some practice problems to help us understand functions.
Write a function called
swapthat takes two numbersaandband swaps their values. There is no return value.solution
#include <iostream> #include <format> void swap(double& a, double& b) { double tmp = a; a = b; b = tmp; } void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } int main() { double x{1.0}; double y{2.0}; std::cout << std::format("x = {}, y = {}\n", x, y); swap(x, y); std::cout << std::format("x = {}, y = {}\n", x, y); int a{-100}; int b{100}; std::cout << std::format("a = {}, b = {}\n", a, b); swap(a, b); std::cout << std::format("a = {}, b = {}\n", a, b); }
Here we write 2 different versions of
swap()—one that takesdoubleand one that takesint. We can do this, even though the function name is the same, because C++ can tell them apart by the datatypes they take. This is a concept called function overloading.The functions are
voidsince they don’t explicitly return anything. Instead the values passed in as arguments are modified directly (hence, using references,&, in the function declaration).Tip
Swapping objects is a key part of a sorting algorithm, so C++ provides std::swap.
Write a function
sumthat accepts astd::vectorof numbers and returns the sum.solution
#include <iostream> #include <vector> double sum(const std::vector<double>& vec) { double s{}; for (auto e : vec) { s += e; } return s; } int main() { std::vector<double> v{1.0, 2.0, 3.0, 4.0, 5.0}; auto s = sum(v); std::cout << "the sum is " << s << std::endl; }
This is pretty straightforward, with one implementation note. We accept the vector argument
vecas aconstreference. This is to prevent C++ from making a potentially-expensive copy (since the default is pass-by-value). By marking itconstwe are ensuring that the functionsumcannot modify the contents ofvec.Functions can be called recursively (i.e. the function calls itself on a smaller problem).
Let’s use recursion to write a function that computes the factorial of an integer.
solution
#include <iostream> #include <format> long fac(long N) { if (N < 0) { return 0; } if (N == 0) { return 1; } else { return N * fac(N-1); } } int main() { int A{5}; std::cout << std::format("{}! = {}\n", A, fac(A)); }
We use a
longdatatype here to help deal with integer overflow (we will still overflow for a moderate sized integer though, even withlong).We also add a check to ensure that the input integer,
Nis positive. Otherwise, we will create infinite recursion and the program will crash. Later we’ll see how we can handle this with an assertion.Let’s rewrite our Prime numbers example to use a function called
is_prime()that takes anintand returns aboolindicated whether the input number is prime.solution
#include <iostream> #include <cmath> bool is_prime(int N) { bool prime{true}; int max_factor = static_cast<int>(std::sqrt(N)); for (int q = 2; q <= max_factor; ++q) { if (N % q == 0) { prime = false; break; } } return prime; } int main() { const int n_max = 100; for (int n = 2; n <= n_max; ++n) { if (is_prime(n)) { std::cout << n << " is prime!" << std::endl; } } }
This rewrite shows how using functions can make the code easier to understand. Now the loop in
mainis much more readable, because we can see it is just checking if the loop variable is prime, using theis_prime()function. And we can understandis_prime()on its own. So by splitting things up, we it allows us to understand the code in logical pieces.Let’s rewrite our Homework #4 problem of creating a vector of
Pointand finding the distance from the origin to use a function that finds the distance for a singlePoint.solution
#include <cmath> #include <format> #include <iostream> #include <vector> #include <cmath> struct Point { double x; double y; }; double dist(const Point p) { //double d = std::sqrt(std::pow(p.x, 2.0) + std::pow(p.y, 2.0)); double d = std::hypot(p.x, p.y); return d; } int main() { std::vector<Point> points{Point{.x=1.5, .y=2.3}, Point{.x=3.2, .y=1.6}, Point{.x=5.3, .y=0.1}, Point{.x=8.6, .y=2.5}}; for (auto p : points) { std::cout << std::format("Point ({:5.2f}, {:5.2f}) is {:6.3f} from the origin.\n", p.x, p.y, dist(p)); } }
This is a straightforward change to the code. The main utility here is that we can now easily use our
dist()function elsewhere in our code as needed.Write a function called
linspacethat takes a minimum and maximum coordinate, \(x_\mathrm{min}\) and \(x_\mathrm{max}\), and a number of points, \(N\) and returns a vector with \(N\) points equally spaced between \(x_\mathrm{min}\) and \(x_\mathrm{max}\) (with those endpoints included).E.g.,
linspace(0, 1, 11)would return{0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}.solution
#include <iostream> #include <vector> std::vector<double> linspace(double xmin, double xmax, int N) { // compute spacing between points double dx = (xmax - xmin) / static_cast<double>(N - 1); // create an empty vector std::vector<double> xvec{}; // loop over point, compute x, add to the vector for (int i = 0; i < N; ++i) { double x = xmin + i * dx; xvec.push_back(x); } // return new vector return xvec; } int main() { auto v = linspace(0.0, 1.0, 11); for (auto e : v) { std::cout << e << " "; } std::cout << std::endl; }
This is a good example of a function that returns a
std::vector.