COMP6771 - Advanced C++ Programming
Weekly Exercise - Week 2
With my own solution (might be incorrect)
Answer the following questions
cppint get_age(); // (*)
int main() {
}
cppint get_age();
int age = get_age(); // (*)
cppint main() {
auto age = 20; // (*)
}
cppint main() {
auto age = 20;
std::cout << age << std::endl; // (*)
}
cppint get_age();
int get_age() { // (*)
return 6771;
}
Answer the following questions
cpp/* 1 */ auto put(char) -> void;
/* 2 */ auto put(int) -> void;
/* 3 */ auto put(const char) -> void;
/* 4 */ auto put(char &) -> void;
put('a');
Which overload of put
would be selected and why?
put
was called with a char
and overload 3 is just a redeclaration of overload 1.char
is implicitly promotable to int
and so overload 2 is the best match.put
was called with a temporary const char
.put
was called with a temporary char
and temporaries preferentially bind to references.cpp/* 1 */ auto put(char) -> void;
/* 2 */ auto put(char &) -> void;
/* 3 */ auto put(const char &) -> void;
char c = 'a';
put(c);
Which overload of put
would be selected and why?
put
was called with a char
.put
was called with a mutable char
and and references have higher priority.put
was called with a const char
and const references have higher priority.cpp/* 1 */ auto memcpy(char *dst, const char *src, int n = 0) -> void *;
/* 2 */ auto memcpy(const char *dst, char * const src) -> char *;
char *dst = /* appropriate initialisation... */;
const char *src = /* appropriate initialisation... */;
void *ptr = memcpy(dst, src);
Which overload of memcpy
would be selected and why?
ptr
's type bettersrc
argument has higher priority than the corresponding bottom-level const src
in overload 1.cpp/* 1 */ auto min(int(&arr)[2]) -> int;
/* 2 */ auto min(int *arr) -> int;
/* 3 */ auto min(int(&arr)[]) -> int;
auto fn(int buf[3]) -> void {
min(buf);
}
Which overload of min
would be selected and why?
min
was called with an array of length 3, 3 is close to 2, so this is the best match.buf
argument decays to int *
and so overload 2 is the best match.int(&)[2]
nor int *
match int(&)[3]
perfectly but a reference to an array of unknown length does, so this is the best match.cpp/* 1 */ auto sink(int i, ...);
/* 2 */ auto sink(int i, short s);
/* 3 */ auto sink(...);
auto L = std::numeric_limits<long>::max();
sink(1, L);
Which overload of sink
would be selected and why?
long
to short
Answer the following questions
cpp// api.h
int rand();
// me.cpp
#include "api.h"
int rand() {
return 42;
}
// you.cpp
int rand() {
return 6771;
}
// client.cpp
#include "api.h"
int i = rand();
you.cpp
did not include api.h
rand()
.rand()
.cppnamespace constants {
#define N 6771
}
int N = constants::N;
int main() {
int ints[N] = {1, 2, 3};
}
constants
is a bad name for a namespaceint N
is changed to int 6771
.N
is not const and so cannot be used in ints[N]
.cpp#include <vector>
int main() {
std::vector<int> v;
unsigned i;
while (i-- > 0) {
v.push_back(i);
}
}
i
is just a variable declaration and the real i
hasn't been defined yet.i
is uninitialised and so its use is illegal.v
is not used after the for-loop.cppint main() {
int *ptr = new int{42};
*ptr = 6771;
return *ptr;
}
main()
.new
can fail allocation and throws an exception if that happensint{42}
is invalid syntax.ptr
was nullptr
or not before dereferencing.C++ has a poignant emphasis on strong type-safety. To that end it offers a type-safe version of the C-style cast called static_cast
which only allows conversion between compatible types e.g. int
to float
, void *
to int *
etc., and will not allow unrelated casts e.g. void *
to int
.
In this exercise we will use static_cast
to safely scale a vector of integers by a double
value and return a new vector of doubles.
In src/2.4/scale.h
there is documentation for scale()
which does this conversion.
Implement this function in src/2.4/scale.cpp
.
You will also need to write at least two tests for it in src/2.4/scale.test.cpp
.
To improve ease of use, also add a default value of 0.5 for the scaling factor. This will allow users to not have to pass a scale factor when commonly scaling a vector in half.
C++// scale.h
auto scale(std::vector<int>& ivec, double factor=0.5) -> std::vector<double>;
// scale.cpp
auto scale(std::vector<int>& ivec, double factor) -> std::vector<double> {
std::vector<double> dvec(ivec.size());
for (size_t i = 0; i < ivec.size(); i++) {
dvec[i] = ivec[i] * factor;
}
return dvec;
}
The Standard Template Library (aka the STL, now part of the C++ standard proper) has three fundamental concepts: containers, iterators, and algorithms. Iterators are the glue that sits between containers of data and the algorithms that operate on them. By using these three concepts together, code reuse is maximised and composition of existing code becomes very easy.
In this exercise you will be using std::vector<int>::iterator
to implement a less general version of std::mismatch, one of the many algorithms provided by the standard library.
There is documentation for our version of mismatch()
in src/2.5/mismatch.cpp
. Complete this function and write at least three more tests to verify your code is correct. Two have already been provided for you.
C++auto mismatch(std::vector<int>& v1, std::vector<int>& v2) -> std::pair<iter, iter> {
auto v1_iter = v1.begin();
auto v2_iter = v2.begin();
while(v1_iter != v1.end() and v2_iter != v2.end()) {
if (*v1_iter != *v2_iter) {
return std::make_pair(v1_iter, v2_iter);
}
++v1_iter;
++v2_iter;
}
return std::make_pair(v1_iter, v2_iter);
}
The purpose of this exercise is to get experience using different parts of the C++ Standard Library's helpful types and data structures. In src/permutation.h, there is documentation for a function that, given two strings, determines if one string is a permutation of the other. In this instance, a string s1 is a permuation of another string s2 if:
You need to implement this function in src/2.6/permutation.cpp
and write at least two tests in src/2.6/permutation.test.cpp
.
C++auto is_permutation(const std::string& x, const std::string& y) -> bool
{
if (x.empty() and y.empty()) {
return true;
}
if (x.size() != y.size()) {
return false;
}
std::map<char, int> letter_map_x;
std::map<char, int> letter_map_y;
for (const auto& c : x) {
++letter_map_x[c];
}
for (const auto& c : y) {
++letter_map_y[c];
}
for (const auto& el : letter_map_x) {
if (el.second != letter_map_y[el.first]) {
return false;
}
}
return true;
}
The C++ Standard Library provides many algorithms, one very widely-used one being std::sort. std::sort
accepts two iterators denoting a range and performs an optimised sort on that range.
In this exercise, we shall explore what requirements std::sort
expects this pair of iterators to satisfy and how the sequence containers std::vector
, std::list
, and std::array
satisfy these requirements.
In src/2.7/assortment.cpp
there are 3 overloads for a function sort()
which accepts a vector, array, and list of integers. There are also three test cases in src/2.7/assortment.test.cpp
for sanity checking.
Try implementing each sort()
function using std::sort
.
You may notice that the program will not compile.
Consider these questions:
std::vector
is always handy...)Modify your implementation such that now the tests pass.
C++#include "assortment.h"
#include <algorithm>
auto sort(std::vector<int>& ivec) -> void
{
std::sort(ivec.begin(), ivec.end());
}
auto sort(std::array<int, 4>& iarr) -> void
{
std::sort(iarr.begin(), iarr.end());
}
auto sort(std::list<int>& ilist) -> void
{
ilist.sort();
}
C++ builds upon many constructs found in C. For example:
std::function
(and more) over raw function pointersenum class
over enum
std::optional
over "magic" invalid values such as nullptr
for an absent T*
In this exercise we shall implement a paint-mixing algorithm to gain familiarity with these new constructs. Namely:
enum class
called paint
.sizeof(<any paint>) == 1
.std::function
.std::optional
.In src/2.8/mixing_paint.h
, there is documentation for a function mix
that accepts a mixing strategy and vector of paints and mixes them according to the given strategy.
Mixing follows the left-fold algorithm, for example, if we were summing integers...:
cppauto add(int x, int y) -> int { return x + y; }
auto sum(const std::vector<int> nums, int init) -> int {
for (auto i : nums) {
init = add(init, i);
}
return init;
}
Here, the accumulator is on the left of the add()
function and the next element in the list is on the right.
There is also documentation for a default mixing strategy wacky_colour
which you will also need to implement.
Complete these functions in src/2.8/mixer.cpp
and write at least two tests for mixer
and two tests for wacky_colour
in src/2.8/mixer.test.cpp
.
C++auto wacky_colour(paint p1, paint p2) -> std::optional<paint>
{
if (p1 == red and p2 == green) {
return yellow;
}
if (p1 == red and p2 == blue) {
return magenta;
}
if (p1 == green and p2 == blue) {
return cyan;
}
if (p1 == yellow and p2 == magenta) {
return brown;
}
if (p1 == cyan and p2 == magenta) {
return brown;
}
if (p1 == brown and p2 == brown) {
return brown;
}
return std::nullopt;
}
auto mix(const std::vector<paint>& paints, std::function<std::optional<paint>(paint, paint)> fn) -> std::optional<paint>
{
std::optional<paint> result = paints[0];
for (size_t i = 1; i < paints.size(); i++) {
result = fn(result.value(), paints[i]);
if (not result) {
return std::nullopt;
}
}
return result;
}
本文作者:Jeff Wu
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!