What made the core developers choose C++ as the main programming language for implementing bitcoin? Was this because of their personal preference? Or another reason?
The most important reason is that the original Satoshi codebase was written in C++. A system like Bitcoin has extremely strong requirements about consistency in behaviour: all nodes on the network must accept and reject the exact same blocks, or we risk a fork. The first choice to avoid bugs that risk introducing inconsistency is to not rewrite the code from scratch.
Although all programming languages have flaws, and C++ is no exception, there are various reasons that make it an appropriate choice. An easily-overlooked one is that it provides very tight control over memory usage. Many modern languages (especially those that rely on garbage collection) make it hard if not impossible to reason about tight bounds of memory usage. Since Bitcoin Core is a security-critical application that is directly exposed to the Internet, it is nontrivial to guarantee that there is no way to trigger it to allocate large amounts of memory, thrashing the machine's swap space, or worse, killing the process.
This is in addition to a good balance between high-level where desired but also many avenues for performance optimization. Performance may be more important than is immediately obvious, as the convergence of the network relies on quick validation and propagation of blocks. If blocks take too long to validate compared to their creation frequency, economic factors start to unfairly advantage larger/faster miners (see the Selfish Mining attack, for example). Under extreme conditions, if blocks would take ~minutes to validate, the network may fail to converge at all, as nodes simply don't hear about blocks in time anymore.
I gave a keynote address at cppcon 2016 about almost this exact issue. There are a variety of reasons why C++ is an excellent language choice for blockchain applications like Bitcoin.
Blockchain applications have a large attack surface. They're meant to interoperate with a large number of untrusted endpoints while still providing reliable service to local clients. This requires keeping tight control over resources like memory and CPU usage.
Modern CPUs have lots of cores. Some parts of blockchain applications parallelize perfectly (like checking digital signatures) while some parts don't parallelize at all (like executing transactions in order). Modern C++ has a very good mix of effective inter-thread communication and optimization of single-thread performance.
C++ doesn't have a run time that stops the world or manages memory, giving your application consistent control over this behavior. Move semantics allow you to get objects that behave like values (
a=b; a+=1; does not change
b) but perform like references (deep copies are avoided except where needed).
Smart pointers allow you to avoid the costs of GC without the tedium of manual memory management. Design patterns like RAII makes code easier to maintain. Template metaprogramming facilitates independence between APIs and implementations.
C++'s inheritance scheme makes the design of complex type hierarchies easier.
The language is both mature and maintained. The compiler technology is extremely solid, yet new features are still being added. And the new features are aimed at solving real issues. Debuggers and analytical tools of all kinds are available for everything from performance profiling to automatic detection of issues of all kinds.