2014-09-11 02:04:18 +04:00
|
|
|
---
|
2022-08-03 05:23:36 +03:00
|
|
|
language: C++
|
2014-09-11 02:04:18 +04:00
|
|
|
filename: learncpp.cpp
|
|
|
|
contributors:
|
2021-01-31 23:55:38 +03:00
|
|
|
- ["Steven Basart", "https://github.com/xksteven"]
|
2014-10-10 09:57:03 +04:00
|
|
|
- ["Matt Kline", "https://github.com/mrkline"]
|
2015-08-30 23:20:18 +03:00
|
|
|
- ["Geoff Liu", "http://geoffliu.me"]
|
2021-01-31 23:55:38 +03:00
|
|
|
- ["Connor Waters", "https://github.com/connorwaters"]
|
|
|
|
- ["Ankush Goyal", "https://github.com/ankushg07"]
|
2016-06-27 16:30:07 +03:00
|
|
|
- ["Jatin Dhankhar", "https://github.com/jatindhankhar"]
|
2014-09-11 02:04:18 +04:00
|
|
|
---
|
|
|
|
|
2014-10-18 05:42:30 +04:00
|
|
|
C++ is a systems programming language that,
|
2021-01-31 23:55:38 +03:00
|
|
|
[according to its inventor Bjarne Stroustrup](https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote),
|
2014-10-18 05:42:30 +04:00
|
|
|
was designed to
|
2014-10-10 10:06:05 +04:00
|
|
|
|
2014-10-18 05:42:30 +04:00
|
|
|
- be a "better C"
|
|
|
|
- support data abstraction
|
|
|
|
- support object-oriented programming
|
|
|
|
- support generic programming
|
2014-10-10 10:06:05 +04:00
|
|
|
|
|
|
|
Though its syntax can be more difficult or complex than newer languages,
|
|
|
|
it is widely used because it compiles to native instructions that can be
|
|
|
|
directly run by the processor and offers tight control over hardware (like C)
|
|
|
|
while offering high-level features such as generics, exceptions, and classes.
|
|
|
|
This combination of speed and functionality makes C++
|
|
|
|
one of the most widely-used programming languages.
|
2014-09-11 02:04:18 +04:00
|
|
|
|
|
|
|
```c++
|
2014-10-10 10:06:05 +04:00
|
|
|
//////////////////
|
|
|
|
// Comparison to C
|
|
|
|
//////////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// C++ is _almost_ a superset of C and shares its basic syntax for
|
|
|
|
// variable declarations, primitive types, and functions.
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2015-03-13 18:40:45 +03:00
|
|
|
// Just like in C, your program's entry point is a function called
|
2015-04-26 17:33:29 +03:00
|
|
|
// main with an integer return type.
|
2014-10-18 05:42:30 +04:00
|
|
|
// This value serves as the program's exit status.
|
2021-01-31 23:55:38 +03:00
|
|
|
// See https://en.wikipedia.org/wiki/Exit_status for more information.
|
2014-10-18 05:42:30 +04:00
|
|
|
int main(int argc, char** argv)
|
2014-10-10 10:06:05 +04:00
|
|
|
{
|
2014-10-18 05:42:30 +04:00
|
|
|
// Command line arguments are passed in by argc and argv in the same way
|
|
|
|
// they are in C.
|
|
|
|
// argc indicates the number of arguments,
|
|
|
|
// and argv is an array of C-style strings (char*)
|
|
|
|
// representing the arguments.
|
|
|
|
// The first argument is the name by which the program was called.
|
|
|
|
// argc and argv can be omitted if you do not care about arguments,
|
|
|
|
// giving the function signature of int main()
|
|
|
|
|
|
|
|
// An exit status of 0 indicates success.
|
|
|
|
return 0;
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2015-03-13 18:40:45 +03:00
|
|
|
// However, C++ varies in some of the following ways:
|
|
|
|
|
2015-10-02 18:19:38 +03:00
|
|
|
// In C++, character literals are chars
|
|
|
|
sizeof('c') == sizeof(char) == 1
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2015-10-02 18:19:38 +03:00
|
|
|
// In C, character literals are ints
|
|
|
|
sizeof('c') == sizeof(int)
|
2014-09-11 02:04:18 +04:00
|
|
|
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// C++ has strict prototyping
|
|
|
|
void func(); // function which accepts no arguments
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// In C
|
|
|
|
void func(); // function which may accept any number of arguments
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Use nullptr instead of NULL in C++
|
|
|
|
int* ip = nullptr;
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2018-10-18 19:26:26 +03:00
|
|
|
// C standard headers are available in C++.
|
2019-08-03 21:18:39 +03:00
|
|
|
// C headers end in .h, while
|
2018-10-18 19:26:26 +03:00
|
|
|
// C++ headers are prefixed with "c" and have no ".h" suffix.
|
|
|
|
|
|
|
|
// The C++ standard version:
|
2014-10-10 10:06:05 +04:00
|
|
|
#include <cstdio>
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2018-12-28 18:50:16 +03:00
|
|
|
// The C standard version:
|
2018-10-18 19:26:26 +03:00
|
|
|
#include <stdio.h>
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
int main()
|
|
|
|
{
|
|
|
|
printf("Hello, world!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
///////////////////////
|
|
|
|
// Function overloading
|
|
|
|
///////////////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// C++ supports function overloading
|
|
|
|
// provided each function takes different parameters.
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void print(char const* myString)
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
2014-10-10 10:06:05 +04:00
|
|
|
printf("String %s\n", myString);
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void print(int myInt)
|
|
|
|
{
|
|
|
|
printf("My int is %d", myInt);
|
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 09:57:52 +04:00
|
|
|
int main()
|
|
|
|
{
|
2014-10-18 05:42:30 +04:00
|
|
|
print("Hello"); // Resolves to void print(const char*)
|
|
|
|
print(15); // Resolves to void print(int)
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
/////////////////////////////
|
|
|
|
// Default function arguments
|
|
|
|
/////////////////////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-18 05:42:30 +04:00
|
|
|
// You can provide default arguments for a function
|
|
|
|
// if they are not provided by the caller.
|
|
|
|
|
|
|
|
void doSomethingWithInts(int a = 1, int b = 4)
|
|
|
|
{
|
|
|
|
// Do something with the ints here
|
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 09:57:52 +04:00
|
|
|
int main()
|
|
|
|
{
|
2014-10-18 05:42:30 +04:00
|
|
|
doSomethingWithInts(); // a = 1, b = 4
|
|
|
|
doSomethingWithInts(20); // a = 20, b = 4
|
|
|
|
doSomethingWithInts(20, 5); // a = 20, b = 5
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default arguments must be at the end of the arguments list.
|
|
|
|
|
|
|
|
void invalidDeclaration(int a = 1, int b) // Error!
|
|
|
|
{
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
/////////////
|
|
|
|
// Namespaces
|
|
|
|
/////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Namespaces provide separate scopes for variable, function,
|
|
|
|
// and other declarations.
|
2014-10-18 05:42:30 +04:00
|
|
|
// Namespaces can be nested.
|
2014-10-10 10:06:05 +04:00
|
|
|
|
|
|
|
namespace First {
|
|
|
|
namespace Nested {
|
|
|
|
void foo()
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
2014-10-10 10:06:05 +04:00
|
|
|
printf("This is First::Nested::foo\n");
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-10-10 10:06:05 +04:00
|
|
|
} // end namespace Nested
|
|
|
|
} // end namespace First
|
|
|
|
|
|
|
|
namespace Second {
|
|
|
|
void foo()
|
|
|
|
{
|
2015-12-13 01:51:23 +03:00
|
|
|
printf("This is Second::foo\n");
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2022-08-29 08:13:34 +03:00
|
|
|
void bar()
|
|
|
|
{
|
|
|
|
printf("This is Second::bar\n");
|
|
|
|
}
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void foo()
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
2014-10-10 10:06:05 +04:00
|
|
|
printf("This is global foo\n");
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 09:57:52 +04:00
|
|
|
int main()
|
|
|
|
{
|
2015-10-02 19:00:13 +03:00
|
|
|
// Includes all symbols from namespace Second into the current scope. Note
|
2022-08-29 08:13:34 +03:00
|
|
|
// that while bar() works, simply using foo() no longer works, since it is
|
|
|
|
// now ambiguous whether we're calling the foo in namespace Second or the
|
|
|
|
// top level.
|
2014-10-10 10:06:05 +04:00
|
|
|
using namespace Second;
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2022-08-29 08:13:34 +03:00
|
|
|
bar(); // prints "This is Second::bar"
|
2015-08-28 20:48:38 +03:00
|
|
|
Second::foo(); // prints "This is Second::foo"
|
2014-10-10 10:06:05 +04:00
|
|
|
First::Nested::foo(); // prints "This is First::Nested::foo"
|
|
|
|
::foo(); // prints "This is global foo"
|
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
///////////////
|
|
|
|
// Input/Output
|
|
|
|
///////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// C++ input and output uses streams
|
|
|
|
// cin, cout, and cerr represent stdin, stdout, and stderr.
|
|
|
|
// << is the insertion operator and >> is the extraction operator.
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
#include <iostream> // Include for I/O streams
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-13 10:35:49 +04:00
|
|
|
using namespace std; // Streams are in the std namespace (standard library)
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 09:57:52 +04:00
|
|
|
int main()
|
|
|
|
{
|
|
|
|
int myInt;
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Prints to stdout (or terminal/screen)
|
2014-10-17 11:03:33 +04:00
|
|
|
cout << "Enter your favorite number:\n";
|
2014-10-10 10:06:05 +04:00
|
|
|
// Takes in input
|
2014-10-10 09:57:52 +04:00
|
|
|
cin >> myInt;
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// cout can also be formatted
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << "Your favorite number is " << myInt << '\n';
|
2014-10-17 11:03:33 +04:00
|
|
|
// prints "Your favorite number is <myInt>"
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2020-05-23 09:42:00 +03:00
|
|
|
cerr << "Used for error messages";
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
//////////
|
|
|
|
// Strings
|
|
|
|
//////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Strings in C++ are objects and have many member functions
|
|
|
|
#include <string>
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-13 10:35:49 +04:00
|
|
|
using namespace std; // Strings are also in the namespace std (standard library)
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
string myString = "Hello";
|
|
|
|
string myOtherString = " World";
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// + is used for concatenation.
|
|
|
|
cout << myString + myOtherString; // "Hello World"
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
cout << myString + " You"; // "Hello You"
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2017-05-18 13:48:46 +03:00
|
|
|
// C++ strings are mutable.
|
2014-10-10 10:06:05 +04:00
|
|
|
myString.append(" Dog");
|
|
|
|
cout << myString; // "Hello Dog"
|
2014-09-11 02:04:18 +04:00
|
|
|
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
/////////////
|
|
|
|
// References
|
|
|
|
/////////////
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// In addition to pointers like the ones in C,
|
|
|
|
// C++ has _references_.
|
|
|
|
// These are pointer types that cannot be reassigned once set
|
|
|
|
// and cannot be null.
|
|
|
|
// They also have the same syntax as the variable itself:
|
|
|
|
// No * is needed for dereferencing and
|
|
|
|
// & (address of) is not used for assignment.
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
string foo = "I am foo";
|
|
|
|
string bar = "I am bar";
|
|
|
|
|
|
|
|
|
|
|
|
string& fooRef = foo; // This creates a reference to foo.
|
|
|
|
fooRef += ". Hi!"; // Modifies foo through the reference
|
2014-10-13 10:35:49 +04:00
|
|
|
cout << fooRef; // Prints "I am foo. Hi!"
|
2014-10-10 10:06:05 +04:00
|
|
|
|
2014-12-02 07:39:49 +03:00
|
|
|
// Doesn't reassign "fooRef". This is the same as "foo = bar", and
|
|
|
|
// foo == "I am bar"
|
|
|
|
// after this line.
|
2015-10-04 22:02:34 +03:00
|
|
|
cout << &fooRef << endl; //Prints the address of foo
|
2014-12-02 07:39:49 +03:00
|
|
|
fooRef = bar;
|
2015-10-04 22:02:34 +03:00
|
|
|
cout << &fooRef << endl; //Still prints the address of foo
|
2015-10-04 07:42:55 +03:00
|
|
|
cout << fooRef; // Prints "I am bar"
|
|
|
|
|
2018-12-28 18:50:16 +03:00
|
|
|
// The address of fooRef remains the same, i.e. it is still referring to foo.
|
2015-10-04 07:42:55 +03:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
|
|
|
|
const string& barRef = bar; // Create a const reference to bar.
|
|
|
|
// Like C, const values (and pointers and references) cannot be modified.
|
|
|
|
barRef += ". Hi!"; // Error, const references cannot be modified.
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2015-08-30 23:20:18 +03:00
|
|
|
// Sidetrack: Before we talk more about references, we must introduce a concept
|
|
|
|
// called a temporary object. Suppose we have the following code:
|
|
|
|
string tempObjectFun() { ... }
|
|
|
|
string retVal = tempObjectFun();
|
|
|
|
|
|
|
|
// What happens in the second line is actually:
|
2015-10-02 19:00:13 +03:00
|
|
|
// - a string object is returned from tempObjectFun
|
2015-10-07 00:27:12 +03:00
|
|
|
// - a new string is constructed with the returned object as argument to the
|
2015-08-30 23:20:18 +03:00
|
|
|
// constructor
|
|
|
|
// - the returned object is destroyed
|
|
|
|
// The returned object is called a temporary object. Temporary objects are
|
|
|
|
// created whenever a function returns an object, and they are destroyed at the
|
2015-09-02 09:46:30 +03:00
|
|
|
// end of the evaluation of the enclosing expression (Well, this is what the
|
|
|
|
// standard says, but compilers are allowed to change this behavior. Look up
|
|
|
|
// "return value optimization" if you're into this kind of details). So in this
|
|
|
|
// code:
|
2015-08-30 23:20:18 +03:00
|
|
|
foo(bar(tempObjectFun()))
|
|
|
|
|
2015-10-02 19:00:13 +03:00
|
|
|
// assuming foo and bar exist, the object returned from tempObjectFun is
|
|
|
|
// passed to bar, and it is destroyed before foo is called.
|
2015-08-30 23:20:18 +03:00
|
|
|
|
|
|
|
// Now back to references. The exception to the "at the end of the enclosing
|
|
|
|
// expression" rule is if a temporary object is bound to a const reference, in
|
|
|
|
// which case its life gets extended to the current scope:
|
|
|
|
|
|
|
|
void constReferenceTempObjectFun() {
|
2015-10-02 19:00:13 +03:00
|
|
|
// constRef gets the temporary object, and it is valid until the end of this
|
2015-08-30 23:20:18 +03:00
|
|
|
// function.
|
|
|
|
const string& constRef = tempObjectFun();
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another kind of reference introduced in C++11 is specifically for temporary
|
|
|
|
// objects. You cannot have a variable of its type, but it takes precedence in
|
|
|
|
// overload resolution:
|
|
|
|
|
|
|
|
void someFun(string& s) { ... } // Regular reference
|
|
|
|
void someFun(string&& s) { ... } // Reference to temporary object
|
|
|
|
|
|
|
|
string foo;
|
|
|
|
someFun(foo); // Calls the version with regular reference
|
|
|
|
someFun(tempObjectFun()); // Calls the version with temporary reference
|
|
|
|
|
|
|
|
// For example, you will see these two versions of constructors for
|
|
|
|
// std::basic_string:
|
|
|
|
basic_string(const basic_string& other);
|
|
|
|
basic_string(basic_string&& other);
|
|
|
|
|
|
|
|
// Idea being if we are constructing a new string from a temporary object (which
|
|
|
|
// is going to be destroyed soon anyway), we can have a more efficient
|
|
|
|
// constructor that "salvages" parts of that temporary string. You will see this
|
2015-10-02 19:00:13 +03:00
|
|
|
// concept referred to as "move semantics".
|
2015-08-30 23:20:18 +03:00
|
|
|
|
2015-10-17 13:05:54 +03:00
|
|
|
/////////////////////
|
|
|
|
// Enums
|
|
|
|
/////////////////////
|
|
|
|
|
|
|
|
// Enums are a way to assign a value to a constant most commonly used for
|
|
|
|
// easier visualization and reading of code
|
|
|
|
enum ECarTypes
|
|
|
|
{
|
|
|
|
Sedan,
|
|
|
|
Hatchback,
|
|
|
|
SUV,
|
|
|
|
Wagon
|
|
|
|
};
|
|
|
|
|
|
|
|
ECarTypes GetPreferredCarType()
|
|
|
|
{
|
|
|
|
return ECarTypes::Hatchback;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As of C++11 there is an easy way to assign a type to the enum which can be
|
2016-02-15 22:33:23 +03:00
|
|
|
// useful in serialization of data and converting enums back-and-forth between
|
2015-10-17 13:05:54 +03:00
|
|
|
// the desired type and their respective constants
|
|
|
|
enum ECarTypes : uint8_t
|
|
|
|
{
|
|
|
|
Sedan, // 0
|
|
|
|
Hatchback, // 1
|
|
|
|
SUV = 254, // 254
|
|
|
|
Hybrid // 255
|
|
|
|
};
|
|
|
|
|
|
|
|
void WriteByteToFile(uint8_t InputValue)
|
|
|
|
{
|
|
|
|
// Serialize the InputValue to a file
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritePreferredCarTypeToFile(ECarTypes InputCarType)
|
|
|
|
{
|
|
|
|
// The enum is implicitly converted to a uint8_t due to its declared enum type
|
|
|
|
WriteByteToFile(InputCarType);
|
|
|
|
}
|
|
|
|
|
|
|
|
// On the other hand you may not want enums to be accidentally cast to an integer
|
2016-02-15 22:33:23 +03:00
|
|
|
// type or to other enums so it is instead possible to create an enum class which
|
2015-10-17 13:05:54 +03:00
|
|
|
// won't be implicitly converted
|
|
|
|
enum class ECarTypes : uint8_t
|
|
|
|
{
|
|
|
|
Sedan, // 0
|
|
|
|
Hatchback, // 1
|
|
|
|
SUV = 254, // 254
|
|
|
|
Hybrid // 255
|
|
|
|
};
|
|
|
|
|
|
|
|
void WriteByteToFile(uint8_t InputValue)
|
|
|
|
{
|
|
|
|
// Serialize the InputValue to a file
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritePreferredCarTypeToFile(ECarTypes InputCarType)
|
|
|
|
{
|
|
|
|
// Won't compile even though ECarTypes is a uint8_t due to the enum
|
|
|
|
// being declared as an "enum class"!
|
|
|
|
WriteByteToFile(InputCarType);
|
|
|
|
}
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
//////////////////////////////////////////
|
|
|
|
// Classes and object-oriented programming
|
|
|
|
//////////////////////////////////////////
|
|
|
|
|
|
|
|
// First example of classes
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
// Declare a class.
|
|
|
|
// Classes are usually declared in header (.h or .hpp) files.
|
|
|
|
class Dog {
|
|
|
|
// Member variables and functions are private by default.
|
|
|
|
std::string name;
|
|
|
|
int weight;
|
|
|
|
|
|
|
|
// All members following this are public
|
|
|
|
// until "private:" or "protected:" is found.
|
|
|
|
public:
|
|
|
|
|
|
|
|
// Default constructor
|
|
|
|
Dog();
|
|
|
|
|
|
|
|
// Member function declarations (implementations to follow)
|
|
|
|
// Note that we use std::string here instead of placing
|
|
|
|
// using namespace std;
|
|
|
|
// above.
|
|
|
|
// Never put a "using namespace" statement in a header.
|
|
|
|
void setName(const std::string& dogsName);
|
|
|
|
|
|
|
|
void setWeight(int dogsWeight);
|
|
|
|
|
|
|
|
// Functions that do not modify the state of the object
|
|
|
|
// should be marked as const.
|
|
|
|
// This allows you to call them if given a const reference to the object.
|
|
|
|
// Also note the functions must be explicitly declared as _virtual_
|
|
|
|
// in order to be overridden in derived classes.
|
|
|
|
// Functions are not virtual by default for performance reasons.
|
|
|
|
virtual void print() const;
|
|
|
|
|
|
|
|
// Functions can also be defined inside the class body.
|
|
|
|
// Functions defined as such are automatically inlined.
|
2015-06-01 04:42:03 +03:00
|
|
|
void bark() const { std::cout << name << " barks!\n"; }
|
2014-10-10 10:06:05 +04:00
|
|
|
|
|
|
|
// Along with constructors, C++ provides destructors.
|
|
|
|
// These are called when an object is deleted or falls out of scope.
|
|
|
|
// This enables powerful paradigms such as RAII
|
2014-10-17 11:57:32 +04:00
|
|
|
// (see below)
|
2015-10-02 19:00:13 +03:00
|
|
|
// The destructor should be virtual if a class is to be derived from;
|
|
|
|
// if it is not virtual, then the derived class' destructor will
|
|
|
|
// not be called if the object is destroyed through a base-class reference
|
|
|
|
// or pointer.
|
2014-10-10 10:06:05 +04:00
|
|
|
virtual ~Dog();
|
|
|
|
|
|
|
|
}; // A semicolon must follow the class definition.
|
|
|
|
|
|
|
|
// Class member functions are usually implemented in .cpp files.
|
2015-06-01 04:38:03 +03:00
|
|
|
Dog::Dog()
|
2014-10-10 10:06:05 +04:00
|
|
|
{
|
|
|
|
std::cout << "A dog has been constructed\n";
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Objects (such as strings) should be passed by reference
|
|
|
|
// if you are modifying them or const reference if you are not.
|
|
|
|
void Dog::setName(const std::string& dogsName)
|
|
|
|
{
|
2014-10-27 06:36:43 +03:00
|
|
|
name = dogsName;
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void Dog::setWeight(int dogsWeight)
|
|
|
|
{
|
|
|
|
weight = dogsWeight;
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Notice that "virtual" is only needed in the declaration, not the definition.
|
|
|
|
void Dog::print() const
|
|
|
|
{
|
|
|
|
std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2015-06-01 04:38:03 +03:00
|
|
|
Dog::~Dog()
|
2014-10-10 10:06:05 +04:00
|
|
|
{
|
2020-09-13 07:06:26 +03:00
|
|
|
std::cout << "Goodbye " << name << '\n';
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
int main() {
|
|
|
|
Dog myDog; // prints "A dog has been constructed"
|
|
|
|
myDog.setName("Barkley");
|
|
|
|
myDog.setWeight(10);
|
2015-06-01 04:38:03 +03:00
|
|
|
myDog.print(); // prints "Dog is Barkley and weighs 10 kg"
|
2014-10-10 10:06:05 +04:00
|
|
|
return 0;
|
|
|
|
} // prints "Goodbye Barkley"
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Inheritance:
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// This class inherits everything public and protected from the Dog class
|
2016-02-15 22:33:23 +03:00
|
|
|
// as well as private but may not directly access private members/methods
|
2015-10-11 10:41:20 +03:00
|
|
|
// without a public or protected method for doing so
|
2014-10-10 10:06:05 +04:00
|
|
|
class OwnedDog : public Dog {
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2017-08-30 02:46:49 +03:00
|
|
|
public:
|
2015-06-01 04:42:03 +03:00
|
|
|
void setOwner(const std::string& dogsOwner);
|
2014-09-11 02:04:18 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Override the behavior of the print function for all OwnedDogs. See
|
2021-01-31 23:55:38 +03:00
|
|
|
// https://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Subtyping
|
2014-10-10 10:06:05 +04:00
|
|
|
// for a more general introduction if you are unfamiliar with
|
|
|
|
// subtype polymorphism.
|
|
|
|
// The override keyword is optional but makes sure you are actually
|
|
|
|
// overriding the method in a base class.
|
|
|
|
void print() const override;
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
private:
|
|
|
|
std::string owner;
|
2014-10-10 09:57:52 +04:00
|
|
|
};
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Meanwhile, in the corresponding .cpp file:
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void OwnedDog::setOwner(const std::string& dogsOwner)
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
2014-10-10 10:06:05 +04:00
|
|
|
owner = dogsOwner;
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
void OwnedDog::print() const
|
|
|
|
{
|
|
|
|
Dog::print(); // Call the print function in the base Dog class
|
2020-09-13 07:06:26 +03:00
|
|
|
std::cout << "Dog is owned by " << owner << '\n';
|
2014-10-10 10:06:05 +04:00
|
|
|
// Prints "Dog is <name> and weights <weight>"
|
|
|
|
// "Dog is owned by <owner>"
|
|
|
|
}
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
//////////////////////////////////////////
|
|
|
|
// Initialization and Operator Overloading
|
|
|
|
//////////////////////////////////////////
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// In C++ you can overload the behavior of operators such as +, -, *, /, etc.
|
|
|
|
// This is done by defining a function which is called
|
|
|
|
// whenever the operator is used.
|
2014-10-10 09:57:52 +04:00
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
class Point {
|
|
|
|
public:
|
|
|
|
// Member variables can be given default values in this manner.
|
|
|
|
double x = 0;
|
|
|
|
double y = 0;
|
|
|
|
|
|
|
|
// Define a default constructor which does nothing
|
|
|
|
// but initialize the Point to the default value (0, 0)
|
|
|
|
Point() { };
|
|
|
|
|
|
|
|
// The following syntax is known as an initialization list
|
2014-10-18 05:42:30 +04:00
|
|
|
// and is the proper way to initialize class member values
|
2014-10-10 10:06:05 +04:00
|
|
|
Point (double a, double b) :
|
|
|
|
x(a),
|
|
|
|
y(b)
|
|
|
|
{ /* Do nothing except initialize the values */ }
|
|
|
|
|
|
|
|
// Overload the + operator.
|
|
|
|
Point operator+(const Point& rhs) const;
|
|
|
|
|
|
|
|
// Overload the += operator
|
|
|
|
Point& operator+=(const Point& rhs);
|
2014-10-13 10:35:49 +04:00
|
|
|
|
|
|
|
// It would also make sense to add the - and -= operators,
|
|
|
|
// but we will skip those for brevity.
|
2014-10-10 09:57:52 +04:00
|
|
|
};
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
Point Point::operator+(const Point& rhs) const
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
2014-10-10 10:06:05 +04:00
|
|
|
// Create a new point that is the sum of this one and rhs.
|
|
|
|
return Point(x + rhs.x, y + rhs.y);
|
2014-10-10 09:57:52 +04:00
|
|
|
}
|
|
|
|
|
2019-06-30 13:40:47 +03:00
|
|
|
// It's good practice to return a reference to the leftmost variable of
|
|
|
|
// an assignment. `(a += b) == c` will work this way.
|
2014-10-10 10:06:05 +04:00
|
|
|
Point& Point::operator+=(const Point& rhs)
|
2014-10-10 09:57:52 +04:00
|
|
|
{
|
|
|
|
x += rhs.x;
|
|
|
|
y += rhs.y;
|
2019-06-30 13:40:47 +03:00
|
|
|
|
|
|
|
// `this` is a pointer to the object, on which a method is called.
|
2014-10-10 09:57:52 +04:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main () {
|
2014-10-10 10:06:05 +04:00
|
|
|
Point up (0,1);
|
|
|
|
Point right (1,0);
|
|
|
|
// This calls the Point + operator
|
2015-05-20 20:38:11 +03:00
|
|
|
// Point up calls the + (function) with right as its parameter
|
2014-10-10 10:06:05 +04:00
|
|
|
Point result = up + right;
|
|
|
|
// Prints "Result is upright (1,1)"
|
2014-10-10 09:57:52 +04:00
|
|
|
cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
|
|
|
|
return 0;
|
2014-09-11 02:04:18 +04:00
|
|
|
}
|
|
|
|
|
2015-05-04 11:40:04 +03:00
|
|
|
/////////////////////
|
|
|
|
// Templates
|
|
|
|
/////////////////////
|
|
|
|
|
|
|
|
// Templates in C++ are mostly used for generic programming, though they are
|
2015-10-02 19:00:13 +03:00
|
|
|
// much more powerful than generic constructs in other languages. They also
|
|
|
|
// support explicit and partial specialization and functional-style type
|
|
|
|
// classes; in fact, they are a Turing-complete functional language embedded
|
|
|
|
// in C++!
|
2015-05-04 11:40:04 +03:00
|
|
|
|
|
|
|
// We start with the kind of generic programming you might be familiar with. To
|
|
|
|
// define a class or function that takes a type parameter:
|
|
|
|
template<class T>
|
|
|
|
class Box {
|
2015-06-02 05:00:52 +03:00
|
|
|
public:
|
2015-05-04 11:40:04 +03:00
|
|
|
// In this class, T can be used as any other type.
|
|
|
|
void insert(const T&) { ... }
|
|
|
|
};
|
|
|
|
|
|
|
|
// During compilation, the compiler actually generates copies of each template
|
2015-10-02 19:00:13 +03:00
|
|
|
// with parameters substituted, so the full definition of the class must be
|
2015-05-04 11:40:04 +03:00
|
|
|
// present at each invocation. This is why you will see template classes defined
|
|
|
|
// entirely in header files.
|
|
|
|
|
|
|
|
// To instantiate a template class on the stack:
|
|
|
|
Box<int> intBox;
|
|
|
|
|
|
|
|
// and you can use it as you would expect:
|
|
|
|
intBox.insert(123);
|
|
|
|
|
|
|
|
// You can, of course, nest templates:
|
|
|
|
Box<Box<int> > boxOfBox;
|
|
|
|
boxOfBox.insert(intBox);
|
|
|
|
|
2015-10-02 19:00:13 +03:00
|
|
|
// Until C++11, you had to place a space between the two '>'s, otherwise '>>'
|
|
|
|
// would be parsed as the right shift operator.
|
2015-05-04 11:40:04 +03:00
|
|
|
|
|
|
|
// You will sometimes see
|
|
|
|
// template<typename T>
|
2015-10-02 19:00:13 +03:00
|
|
|
// instead. The 'class' keyword and 'typename' keywords are _mostly_
|
|
|
|
// interchangeable in this case. For the full explanation, see
|
2021-01-31 23:55:38 +03:00
|
|
|
// https://en.wikipedia.org/wiki/Typename
|
2015-05-04 11:40:04 +03:00
|
|
|
// (yes, that keyword has its own Wikipedia page).
|
|
|
|
|
|
|
|
// Similarly, a template function:
|
|
|
|
template<class T>
|
|
|
|
void barkThreeTimes(const T& input)
|
|
|
|
{
|
|
|
|
input.bark();
|
|
|
|
input.bark();
|
|
|
|
input.bark();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notice that nothing is specified about the type parameters here. The compiler
|
|
|
|
// will generate and then type-check every invocation of the template, so the
|
|
|
|
// above function works with any type 'T' that has a const 'bark' method!
|
|
|
|
|
|
|
|
Dog fluffy;
|
|
|
|
fluffy.setName("Fluffy")
|
2015-05-04 11:45:31 +03:00
|
|
|
barkThreeTimes(fluffy); // Prints "Fluffy barks" three times.
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2015-05-04 11:45:31 +03:00
|
|
|
// Template parameters don't have to be classes:
|
2015-05-04 11:40:04 +03:00
|
|
|
template<int Y>
|
|
|
|
void printMessage() {
|
|
|
|
cout << "Learn C++ in " << Y << " minutes!" << endl;
|
|
|
|
}
|
|
|
|
|
2015-05-05 09:45:14 +03:00
|
|
|
// And you can explicitly specialize templates for more efficient code. Of
|
|
|
|
// course, most real-world uses of specialization are not as trivial as this.
|
|
|
|
// Note that you still need to declare the function (or class) as a template
|
|
|
|
// even if you explicitly specified all parameters.
|
2015-05-04 11:40:04 +03:00
|
|
|
template<>
|
|
|
|
void printMessage<10>() {
|
|
|
|
cout << "Learn C++ faster in only 10 minutes!" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
printMessage<20>(); // Prints "Learn C++ in 20 minutes!"
|
|
|
|
printMessage<10>(); // Prints "Learn C++ faster in only 10 minutes!"
|
|
|
|
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
/////////////////////
|
|
|
|
// Exception Handling
|
|
|
|
/////////////////////
|
|
|
|
|
|
|
|
// The standard library provides a few exception types
|
2021-01-31 23:55:38 +03:00
|
|
|
// (see https://en.cppreference.com/w/cpp/error/exception)
|
2019-03-11 00:56:47 +03:00
|
|
|
// but any type can be thrown as an exception
|
2014-10-10 10:06:05 +04:00
|
|
|
#include <exception>
|
2015-06-02 23:30:35 +03:00
|
|
|
#include <stdexcept>
|
2014-10-10 10:06:05 +04:00
|
|
|
|
|
|
|
// All exceptions thrown inside the _try_ block can be caught by subsequent
|
|
|
|
// _catch_ handlers.
|
|
|
|
try {
|
|
|
|
// Do not allocate exceptions on the heap using _new_.
|
2015-06-02 23:30:35 +03:00
|
|
|
throw std::runtime_error("A problem occurred");
|
2014-10-10 10:06:05 +04:00
|
|
|
}
|
2015-10-02 19:00:13 +03:00
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Catch exceptions by const reference if they are objects
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
2015-10-02 19:00:13 +03:00
|
|
|
std::cout << ex.what();
|
|
|
|
}
|
|
|
|
|
2014-10-10 10:06:05 +04:00
|
|
|
// Catches any exception not caught by previous _catch_ blocks
|
2015-10-02 19:00:13 +03:00
|
|
|
catch (...)
|
2014-10-10 10:06:05 +04:00
|
|
|
{
|
|
|
|
std::cout << "Unknown exception caught";
|
|
|
|
throw; // Re-throws the exception
|
|
|
|
}
|
2014-10-17 11:57:32 +04:00
|
|
|
|
|
|
|
///////
|
|
|
|
// RAII
|
|
|
|
///////
|
|
|
|
|
2015-10-02 19:00:13 +03:00
|
|
|
// RAII stands for "Resource Acquisition Is Initialization".
|
|
|
|
// It is often considered the most powerful paradigm in C++
|
2014-10-17 11:57:32 +04:00
|
|
|
// and is the simple concept that a constructor for an object
|
|
|
|
// acquires that object's resources and the destructor releases them.
|
|
|
|
|
|
|
|
// To understand how this is useful,
|
|
|
|
// consider a function that uses a C file handle:
|
|
|
|
void doSomethingWithAFile(const char* filename)
|
|
|
|
{
|
|
|
|
// To begin with, assume nothing can fail.
|
|
|
|
|
|
|
|
FILE* fh = fopen(filename, "r"); // Open the file in read mode.
|
|
|
|
|
|
|
|
doSomethingWithTheFile(fh);
|
|
|
|
doSomethingElseWithIt(fh);
|
|
|
|
|
|
|
|
fclose(fh); // Close the file handle.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unfortunately, things are quickly complicated by error handling.
|
|
|
|
// Suppose fopen can fail, and that doSomethingWithTheFile and
|
|
|
|
// doSomethingElseWithIt return error codes if they fail.
|
2015-10-02 19:00:13 +03:00
|
|
|
// (Exceptions are the preferred way of handling failure,
|
|
|
|
// but some programmers, especially those with a C background,
|
|
|
|
// disagree on the utility of exceptions).
|
2014-10-17 11:57:32 +04:00
|
|
|
// We now have to check each call for failure and close the file handle
|
|
|
|
// if a problem occurred.
|
|
|
|
bool doSomethingWithAFile(const char* filename)
|
|
|
|
{
|
|
|
|
FILE* fh = fopen(filename, "r"); // Open the file in read mode
|
|
|
|
if (fh == nullptr) // The returned pointer is null on failure.
|
2015-02-25 20:10:57 +03:00
|
|
|
return false; // Report that failure to the caller.
|
2014-10-17 11:57:32 +04:00
|
|
|
|
|
|
|
// Assume each function returns false if it failed
|
|
|
|
if (!doSomethingWithTheFile(fh)) {
|
|
|
|
fclose(fh); // Close the file handle so it doesn't leak.
|
|
|
|
return false; // Propagate the error.
|
|
|
|
}
|
|
|
|
if (!doSomethingElseWithIt(fh)) {
|
|
|
|
fclose(fh); // Close the file handle so it doesn't leak.
|
|
|
|
return false; // Propagate the error.
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fh); // Close the file handle so it doesn't leak.
|
|
|
|
return true; // Indicate success
|
|
|
|
}
|
|
|
|
|
|
|
|
// C programmers often clean this up a little bit using goto:
|
|
|
|
bool doSomethingWithAFile(const char* filename)
|
|
|
|
{
|
|
|
|
FILE* fh = fopen(filename, "r");
|
|
|
|
if (fh == nullptr)
|
2015-02-25 20:10:57 +03:00
|
|
|
return false;
|
2014-10-17 11:57:32 +04:00
|
|
|
|
|
|
|
if (!doSomethingWithTheFile(fh))
|
|
|
|
goto failure;
|
|
|
|
|
|
|
|
if (!doSomethingElseWithIt(fh))
|
|
|
|
goto failure;
|
|
|
|
|
|
|
|
fclose(fh); // Close the file
|
|
|
|
return true; // Indicate success
|
|
|
|
|
|
|
|
failure:
|
|
|
|
fclose(fh);
|
|
|
|
return false; // Propagate the error
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the functions indicate errors using exceptions,
|
|
|
|
// things are a little cleaner, but still sub-optimal.
|
|
|
|
void doSomethingWithAFile(const char* filename)
|
|
|
|
{
|
2019-08-03 21:18:39 +03:00
|
|
|
FILE* fh = fopen(filename, "r"); // Open the file in shared_ptrread mode
|
2014-10-17 11:57:32 +04:00
|
|
|
if (fh == nullptr)
|
2015-06-03 02:14:52 +03:00
|
|
|
throw std::runtime_error("Could not open the file.");
|
2014-10-17 11:57:32 +04:00
|
|
|
|
|
|
|
try {
|
|
|
|
doSomethingWithTheFile(fh);
|
|
|
|
doSomethingElseWithIt(fh);
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
fclose(fh); // Be sure to close the file if an error occurs.
|
|
|
|
throw; // Then re-throw the exception.
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fh); // Close the file
|
|
|
|
// Everything succeeded
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare this to the use of C++'s file stream class (fstream)
|
|
|
|
// fstream uses its destructor to close the file.
|
|
|
|
// Recall from above that destructors are automatically called
|
2015-04-11 07:52:23 +03:00
|
|
|
// whenever an object falls out of scope.
|
2014-10-17 11:57:32 +04:00
|
|
|
void doSomethingWithAFile(const std::string& filename)
|
|
|
|
{
|
|
|
|
// ifstream is short for input file stream
|
|
|
|
std::ifstream fh(filename); // Open the file
|
|
|
|
|
|
|
|
// Do things with the file
|
|
|
|
doSomethingWithTheFile(fh);
|
|
|
|
doSomethingElseWithIt(fh);
|
|
|
|
|
|
|
|
} // The file is automatically closed here by the destructor
|
|
|
|
|
|
|
|
// This has _massive_ advantages:
|
|
|
|
// 1. No matter what happens,
|
|
|
|
// the resource (in this case the file handle) will be cleaned up.
|
|
|
|
// Once you write the destructor correctly,
|
|
|
|
// It is _impossible_ to forget to close the handle and leak the resource.
|
|
|
|
// 2. Note that the code is much cleaner.
|
|
|
|
// The destructor handles closing the file behind the scenes
|
|
|
|
// without you having to worry about it.
|
|
|
|
// 3. The code is exception safe.
|
|
|
|
// An exception can be thrown anywhere in the function and cleanup
|
|
|
|
// will still occur.
|
|
|
|
|
|
|
|
// All idiomatic C++ code uses RAII extensively for all resources.
|
|
|
|
// Additional examples include
|
|
|
|
// - Memory using unique_ptr and shared_ptr
|
|
|
|
// - Containers - the standard library linked list,
|
|
|
|
// vector (i.e. self-resizing array), hash maps, and so on
|
|
|
|
// all automatically destroy their contents when they fall out of scope.
|
|
|
|
// - Mutexes using lock_guard and unique_lock
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2019-01-04 15:23:05 +03:00
|
|
|
|
2019-08-03 21:18:39 +03:00
|
|
|
/////////////////////
|
|
|
|
// Smart Pointer
|
|
|
|
/////////////////////
|
|
|
|
|
2019-08-31 18:11:27 +03:00
|
|
|
// Generally a smart pointer is a class which wraps a "raw pointer" (usage of "new"
|
2019-08-03 21:18:39 +03:00
|
|
|
// respectively malloc/calloc in C). The goal is to be able to
|
2019-08-31 18:11:27 +03:00
|
|
|
// manage the lifetime of the object being pointed to without ever needing to explicitly delete
|
2019-08-03 21:18:39 +03:00
|
|
|
// the object. The term itself simply describes a set of pointers with the
|
|
|
|
// mentioned abstraction.
|
2019-08-31 18:11:27 +03:00
|
|
|
// Smart pointers should preferred over raw pointers, to prevent
|
|
|
|
// risky memory leaks, which happen if you forget to delete an object.
|
2019-08-03 21:18:39 +03:00
|
|
|
|
|
|
|
// Usage of a raw pointer:
|
|
|
|
Dog* ptr = new Dog();
|
|
|
|
ptr->bark();
|
|
|
|
delete ptr;
|
|
|
|
|
2019-08-31 18:11:27 +03:00
|
|
|
// By using a smart pointer, you don't have to worry about the deletion
|
|
|
|
// of the object anymore.
|
|
|
|
// A smart pointer describes a policy, to count the references to the
|
|
|
|
// pointer. The object gets destroyed when the last
|
|
|
|
// reference to the object gets destroyed.
|
2019-08-03 21:18:39 +03:00
|
|
|
|
|
|
|
// Usage of "std::shared_ptr":
|
|
|
|
void foo()
|
|
|
|
{
|
2019-08-31 18:11:27 +03:00
|
|
|
// It's no longer necessary to delete the Dog.
|
2019-08-03 21:18:39 +03:00
|
|
|
std::shared_ptr<Dog> doggo(new Dog());
|
|
|
|
doggo->bark();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Beware of possible circular references!!!
|
|
|
|
// There will be always a reference, so it will be never destroyed!
|
2019-08-31 18:11:27 +03:00
|
|
|
std::shared_ptr<Dog> doggo_one(new Dog());
|
|
|
|
std::shared_ptr<Dog> doggo_two(new Dog());
|
2019-08-03 21:18:39 +03:00
|
|
|
doggo_one = doggo_two; // p1 references p2
|
|
|
|
doggo_two = doggo_one; // p2 references p1
|
|
|
|
|
2019-08-31 18:11:27 +03:00
|
|
|
// There are several kinds of smart pointers.
|
|
|
|
// The way you have to use them is always the same.
|
|
|
|
// This leads us to the question: when should we use each kind of smart pointer?
|
|
|
|
// std::unique_ptr - use it when you just want to hold one reference to
|
|
|
|
// the object.
|
|
|
|
// std::shared_ptr - use it when you want to hold multiple references to the
|
|
|
|
// same object and want to make sure that it's deallocated
|
|
|
|
// when all references are gone.
|
|
|
|
// std::weak_ptr - use it when you want to access
|
|
|
|
// the underlying object of a std::shared_ptr without causing that object to stay allocated.
|
|
|
|
// Weak pointers are used to prevent circular referencing.
|
2019-08-03 21:18:39 +03:00
|
|
|
|
|
|
|
|
2019-01-04 15:23:05 +03:00
|
|
|
/////////////////////
|
|
|
|
// Containers
|
|
|
|
/////////////////////
|
|
|
|
|
|
|
|
// Containers or the Standard Template Library are some predefined templates.
|
|
|
|
// They manage the storage space for its elements and provide
|
|
|
|
// member functions to access and manipulate them.
|
|
|
|
|
|
|
|
// Few containers are as follows:
|
|
|
|
|
|
|
|
// Vector (Dynamic array)
|
|
|
|
// Allow us to Define the Array or list of objects at run time
|
|
|
|
#include <vector>
|
|
|
|
string val;
|
|
|
|
vector<string> my_vector; // initialize the vector
|
|
|
|
cin >> val;
|
|
|
|
my_vector.push_back(val); // will push the value of 'val' into vector ("array") my_vector
|
|
|
|
my_vector.push_back(val); // will push the value into the vector again (now having two elements)
|
|
|
|
|
|
|
|
// To iterate through a vector we have 2 choices:
|
|
|
|
// Either classic looping (iterating through the vector from index 0 to its last index):
|
|
|
|
for (int i = 0; i < my_vector.size(); i++) {
|
|
|
|
cout << my_vector[i] << endl; // for accessing a vector's element we can use the operator []
|
|
|
|
}
|
|
|
|
|
|
|
|
// or using an iterator:
|
|
|
|
vector<string>::iterator it; // initialize the iterator for vector
|
|
|
|
for (it = my_vector.begin(); it != my_vector.end(); ++it) {
|
|
|
|
cout << *it << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set
|
|
|
|
// Sets are containers that store unique elements following a specific order.
|
|
|
|
// Set is a very useful container to store unique values in sorted order
|
|
|
|
// without any other functions or code.
|
|
|
|
|
|
|
|
#include<set>
|
|
|
|
set<int> ST; // Will initialize the set of int data type
|
|
|
|
ST.insert(30); // Will insert the value 30 in set ST
|
|
|
|
ST.insert(10); // Will insert the value 10 in set ST
|
|
|
|
ST.insert(20); // Will insert the value 20 in set ST
|
|
|
|
ST.insert(30); // Will insert the value 30 in set ST
|
|
|
|
// Now elements of sets are as follows
|
|
|
|
// 10 20 30
|
|
|
|
|
|
|
|
// To erase an element
|
|
|
|
ST.erase(20); // Will erase element with value 20
|
|
|
|
// Set ST: 10 30
|
|
|
|
// To iterate through Set we use iterators
|
|
|
|
set<int>::iterator it;
|
2020-09-29 15:50:47 +03:00
|
|
|
for(it=ST.begin();it!=ST.end();it++) {
|
2019-01-04 15:23:05 +03:00
|
|
|
cout << *it << endl;
|
|
|
|
}
|
|
|
|
// Output:
|
|
|
|
// 10
|
|
|
|
// 30
|
|
|
|
|
|
|
|
// To clear the complete container we use Container_name.clear()
|
|
|
|
ST.clear();
|
|
|
|
cout << ST.size(); // will print the size of set ST
|
|
|
|
// Output: 0
|
|
|
|
|
|
|
|
// NOTE: for duplicate elements we can use multiset
|
2019-08-03 21:18:39 +03:00
|
|
|
// NOTE: For hash sets, use unordered_set. They are more efficient but
|
2019-01-04 15:23:05 +03:00
|
|
|
// do not preserve order. unordered_set is available since C++11
|
|
|
|
|
|
|
|
// Map
|
|
|
|
// Maps store elements formed by a combination of a key value
|
|
|
|
// and a mapped value, following a specific order.
|
|
|
|
|
|
|
|
#include<map>
|
|
|
|
map<char, int> mymap; // Will initialize the map with key as char and value as int
|
|
|
|
|
|
|
|
mymap.insert(pair<char,int>('A',1));
|
|
|
|
// Will insert value 1 for key A
|
|
|
|
mymap.insert(pair<char,int>('Z',26));
|
|
|
|
// Will insert value 26 for key Z
|
|
|
|
|
|
|
|
// To iterate
|
|
|
|
map<char,int>::iterator it;
|
|
|
|
for (it=mymap.begin(); it!=mymap.end(); ++it)
|
2022-01-03 19:36:58 +03:00
|
|
|
std::cout << it->first << "->" << it->second << std::endl;
|
2019-01-04 15:23:05 +03:00
|
|
|
// Output:
|
|
|
|
// A->1
|
|
|
|
// Z->26
|
|
|
|
|
|
|
|
// To find the value corresponding to a key
|
|
|
|
it = mymap.find('Z');
|
|
|
|
cout << it->second;
|
|
|
|
|
|
|
|
// Output: 26
|
|
|
|
|
2019-08-03 21:18:39 +03:00
|
|
|
// NOTE: For hash maps, use unordered_map. They are more efficient but do
|
2019-01-04 15:23:05 +03:00
|
|
|
// not preserve order. unordered_map is available since C++11.
|
|
|
|
|
|
|
|
// Containers with object keys of non-primitive values (custom classes) require
|
2015-11-01 02:17:58 +03:00
|
|
|
// compare function in the object itself or as a function pointer. Primitives
|
|
|
|
// have default comparators, but you can override it.
|
|
|
|
class Foo {
|
|
|
|
public:
|
2016-08-02 16:32:38 +03:00
|
|
|
int j;
|
|
|
|
Foo(int a) : j(a) {}
|
2015-11-01 02:17:58 +03:00
|
|
|
};
|
|
|
|
struct compareFunction {
|
|
|
|
bool operator()(const Foo& a, const Foo& b) const {
|
|
|
|
return a.j < b.j;
|
|
|
|
}
|
|
|
|
};
|
2018-12-28 18:50:16 +03:00
|
|
|
// this isn't allowed (although it can vary depending on compiler)
|
|
|
|
// std::map<Foo, int> fooMap;
|
2015-11-01 02:17:58 +03:00
|
|
|
std::map<Foo, int, compareFunction> fooMap;
|
|
|
|
fooMap[Foo(1)] = 1;
|
|
|
|
fooMap.find(Foo(1)); //true
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2019-01-04 15:23:05 +03:00
|
|
|
|
2016-01-05 11:06:23 +03:00
|
|
|
///////////////////////////////////////
|
|
|
|
// Lambda Expressions (C++11 and above)
|
|
|
|
///////////////////////////////////////
|
|
|
|
|
|
|
|
// lambdas are a convenient way of defining an anonymous function
|
2016-02-15 22:33:23 +03:00
|
|
|
// object right at the location where it is invoked or passed as
|
2016-01-05 11:06:23 +03:00
|
|
|
// an argument to a function.
|
|
|
|
|
2016-02-15 22:33:23 +03:00
|
|
|
// For example, consider sorting a vector of pairs using the second
|
2016-01-05 11:06:23 +03:00
|
|
|
// value of the pair
|
|
|
|
|
|
|
|
vector<pair<int, int> > tester;
|
|
|
|
tester.push_back(make_pair(3, 6));
|
|
|
|
tester.push_back(make_pair(1, 9));
|
|
|
|
tester.push_back(make_pair(5, 0));
|
|
|
|
|
|
|
|
// Pass a lambda expression as third argument to the sort function
|
|
|
|
// sort is from the <algorithm> header
|
|
|
|
|
|
|
|
sort(tester.begin(), tester.end(), [](const pair<int, int>& lhs, const pair<int, int>& rhs) {
|
2016-01-07 22:51:38 +03:00
|
|
|
return lhs.second < rhs.second;
|
|
|
|
});
|
2016-01-05 11:06:23 +03:00
|
|
|
|
|
|
|
// Notice the syntax of the lambda expression,
|
2016-01-07 22:51:38 +03:00
|
|
|
// [] in the lambda is used to "capture" variables
|
|
|
|
// The "Capture List" defines what from the outside of the lambda should be available inside the function body and how.
|
|
|
|
// It can be either:
|
|
|
|
// 1. a value : [x]
|
|
|
|
// 2. a reference : [&x]
|
|
|
|
// 3. any variable currently in scope by reference [&]
|
|
|
|
// 4. same as 3, but by value [=]
|
|
|
|
// Example:
|
2016-01-05 11:06:23 +03:00
|
|
|
|
|
|
|
vector<int> dog_ids;
|
|
|
|
// number_of_dogs = 3;
|
2016-01-07 22:51:38 +03:00
|
|
|
for(int i = 0; i < 3; i++) {
|
2016-02-15 22:33:23 +03:00
|
|
|
dog_ids.push_back(i);
|
2016-01-05 11:06:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int weight[3] = {30, 50, 10};
|
|
|
|
|
|
|
|
// Say you want to sort dog_ids according to the dogs' weights
|
|
|
|
// So dog_ids should in the end become: [2, 0, 1]
|
|
|
|
|
|
|
|
// Here's where lambda expressions come in handy
|
|
|
|
|
|
|
|
sort(dog_ids.begin(), dog_ids.end(), [&weight](const int &lhs, const int &rhs) {
|
2016-01-07 22:51:38 +03:00
|
|
|
return weight[lhs] < weight[rhs];
|
|
|
|
});
|
2016-01-05 11:06:23 +03:00
|
|
|
// Note we captured "weight" by reference in the above example.
|
2021-01-31 23:55:38 +03:00
|
|
|
// More on Lambdas in C++ : https://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11
|
2016-01-05 11:06:23 +03:00
|
|
|
|
|
|
|
///////////////////////////////
|
|
|
|
// Range For (C++11 and above)
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
// You can use a range for loop to iterate over a container
|
|
|
|
int arr[] = {1, 10, 3};
|
|
|
|
|
|
|
|
for(int elem: arr){
|
|
|
|
cout << elem << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// You can use "auto" and not worry about the type of the elements of the container
|
|
|
|
// For example:
|
|
|
|
|
|
|
|
for(auto elem: arr) {
|
|
|
|
// Do something with each element of arr
|
|
|
|
}
|
|
|
|
|
2015-05-04 11:40:04 +03:00
|
|
|
/////////////////////
|
|
|
|
// Fun stuff
|
|
|
|
/////////////////////
|
|
|
|
|
2015-05-05 09:45:14 +03:00
|
|
|
// Aspects of C++ that may be surprising to newcomers (and even some veterans).
|
|
|
|
// This section is, unfortunately, wildly incomplete; C++ is one of the easiest
|
|
|
|
// languages with which to shoot yourself in the foot.
|
2015-05-04 11:40:04 +03:00
|
|
|
|
|
|
|
// You can override private methods!
|
|
|
|
class Foo {
|
|
|
|
virtual void bar();
|
|
|
|
};
|
|
|
|
class FooSub : public Foo {
|
2015-10-01 17:55:28 +03:00
|
|
|
virtual void bar(); // Overrides Foo::bar!
|
2015-05-04 11:40:04 +03:00
|
|
|
};
|
|
|
|
|
2015-05-05 09:45:14 +03:00
|
|
|
|
|
|
|
// 0 == false == NULL (most of the time)!
|
2015-05-04 11:40:04 +03:00
|
|
|
bool* pt = new bool;
|
2015-10-02 19:00:13 +03:00
|
|
|
*pt = 0; // Sets the value points by 'pt' to false.
|
2015-05-05 09:45:14 +03:00
|
|
|
pt = 0; // Sets 'pt' to the null pointer. Both lines compile without warnings.
|
|
|
|
|
|
|
|
// nullptr is supposed to fix some of that issue:
|
|
|
|
int* pt2 = new int;
|
2015-10-02 19:00:13 +03:00
|
|
|
*pt2 = nullptr; // Doesn't compile
|
2015-05-05 09:45:14 +03:00
|
|
|
pt2 = nullptr; // Sets pt2 to null.
|
|
|
|
|
2015-10-02 19:00:13 +03:00
|
|
|
// There is an exception made for bools.
|
|
|
|
// This is to allow you to test for null pointers with if(!ptr),
|
|
|
|
// but as a consequence you can assign nullptr to a bool directly!
|
2015-05-05 09:45:14 +03:00
|
|
|
*pt = nullptr; // This still compiles, even though '*pt' is a bool!
|
|
|
|
|
|
|
|
|
|
|
|
// '=' != '=' != '='!
|
2015-08-30 23:46:46 +03:00
|
|
|
// Calls Foo::Foo(const Foo&) or some variant (see move semantics) copy
|
|
|
|
// constructor.
|
2015-05-05 09:45:14 +03:00
|
|
|
Foo f2;
|
|
|
|
Foo f1 = f2;
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2015-05-05 09:45:14 +03:00
|
|
|
// Calls Foo::Foo(const Foo&) or variant, but only copies the 'Foo' part of
|
2015-05-07 00:25:28 +03:00
|
|
|
// 'fooSub'. Any extra members of 'fooSub' are discarded. This sometimes
|
|
|
|
// horrifying behavior is called "object slicing."
|
2015-05-05 09:45:14 +03:00
|
|
|
FooSub fooSub;
|
|
|
|
Foo f1 = fooSub;
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2015-05-05 09:45:14 +03:00
|
|
|
// Calls Foo::operator=(Foo&) or variant.
|
2015-05-04 11:40:04 +03:00
|
|
|
Foo f1;
|
2015-05-05 09:45:14 +03:00
|
|
|
f1 = f2;
|
2015-05-04 11:40:04 +03:00
|
|
|
|
2015-08-30 23:41:02 +03:00
|
|
|
|
2016-01-28 08:08:51 +03:00
|
|
|
///////////////////////////////////////
|
|
|
|
// Tuples (C++11 and above)
|
|
|
|
///////////////////////////////////////
|
|
|
|
|
|
|
|
#include<tuple>
|
|
|
|
|
2021-01-31 23:55:38 +03:00
|
|
|
// Conceptually, Tuples are similar to old data structures (C-like structs)
|
|
|
|
// but instead of having named data members,
|
2016-01-28 08:08:51 +03:00
|
|
|
// its elements are accessed by their order in the tuple.
|
|
|
|
|
|
|
|
// We start with constructing a tuple.
|
|
|
|
// Packing values into tuple
|
2016-01-28 16:04:41 +03:00
|
|
|
auto first = make_tuple(10, 'A');
|
2016-01-28 08:08:51 +03:00
|
|
|
const int maxN = 1e9;
|
2016-01-28 12:56:35 +03:00
|
|
|
const int maxL = 15;
|
2016-01-28 16:04:41 +03:00
|
|
|
auto second = make_tuple(maxN, maxL);
|
2016-01-28 08:08:51 +03:00
|
|
|
|
2016-08-02 16:32:38 +03:00
|
|
|
// Printing elements of 'first' tuple
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << get<0>(first) << " " << get<1>(first) << '\n'; //prints : 10 A
|
2016-01-28 08:08:51 +03:00
|
|
|
|
2016-08-02 16:32:38 +03:00
|
|
|
// Printing elements of 'second' tuple
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << get<0>(second) << " " << get<1>(second) << '\n'; // prints: 1000000000 15
|
2016-01-28 08:08:51 +03:00
|
|
|
|
|
|
|
// Unpacking tuple into variables
|
|
|
|
|
|
|
|
int first_int;
|
|
|
|
char first_char;
|
2016-01-28 16:04:41 +03:00
|
|
|
tie(first_int, first_char) = first;
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << first_int << " " << first_char << '\n'; // prints : 10 A
|
2016-01-28 08:08:51 +03:00
|
|
|
|
|
|
|
// We can also create tuple like this.
|
|
|
|
|
2016-01-28 16:04:41 +03:00
|
|
|
tuple<int, char, double> third(11, 'A', 3.14141);
|
2016-01-28 08:08:51 +03:00
|
|
|
// tuple_size returns number of elements in a tuple (as a constexpr)
|
|
|
|
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << tuple_size<decltype(third)>::value << '\n'; // prints: 3
|
2016-01-28 08:08:51 +03:00
|
|
|
|
|
|
|
// tuple_cat concatenates the elements of all the tuples in the same order.
|
|
|
|
|
2016-01-28 16:04:41 +03:00
|
|
|
auto concatenated_tuple = tuple_cat(first, second, third);
|
c++/en typo, formatting fixes (#2531)
Space should be removed.
before:
// concatenated_tuple becomes = (10, 'A', 1e9, 15, 11, 'A' ,3.14141)
after:
// concatenated_tuple becomes = (10, 'A', 1e9, 15, 11, 'A', 3.14141)
Typo initialize is correct form.
before:
map<char, int> mymap; // Will initalize the map with key as char and value as int
after:
map<char, int> mymap; // Will initialize the map with key as char and value as int
Typo corresponding is correct form.
before:
// To find the value correponsing to a key
after:
// To find the value corresponding to a key
2016-10-30 12:58:59 +03:00
|
|
|
// concatenated_tuple becomes = (10, 'A', 1e9, 15, 11, 'A', 3.14141)
|
2016-01-28 08:08:51 +03:00
|
|
|
|
2020-09-13 07:06:26 +03:00
|
|
|
cout << get<0>(concatenated_tuple) << '\n'; // prints: 10
|
|
|
|
cout << get<3>(concatenated_tuple) << '\n'; // prints: 15
|
|
|
|
cout << get<5>(concatenated_tuple) << '\n'; // prints: 'A'
|
2016-01-28 08:08:51 +03:00
|
|
|
|
2016-06-26 23:49:51 +03:00
|
|
|
|
2016-06-27 16:30:07 +03:00
|
|
|
///////////////////////////////////
|
|
|
|
// Logical and Bitwise operators
|
|
|
|
//////////////////////////////////
|
|
|
|
|
|
|
|
// Most of the operators in C++ are same as in other languages
|
|
|
|
|
|
|
|
// Logical operators
|
|
|
|
|
2016-08-02 16:32:38 +03:00
|
|
|
// C++ uses Short-circuit evaluation for boolean expressions, i.e, the second argument is executed or
|
2016-06-27 16:30:07 +03:00
|
|
|
// evaluated only if the first argument does not suffice to determine the value of the expression
|
|
|
|
|
|
|
|
true && false // Performs **logical and** to yield false
|
|
|
|
true || false // Performs **logical or** to yield true
|
2016-08-02 16:32:38 +03:00
|
|
|
! true // Performs **logical not** to yield false
|
2016-06-27 16:30:07 +03:00
|
|
|
|
|
|
|
// Instead of using symbols equivalent keywords can be used
|
|
|
|
true and false // Performs **logical and** to yield false
|
2016-08-02 16:32:38 +03:00
|
|
|
true or false // Performs **logical or** to yield true
|
|
|
|
not true // Performs **logical not** to yield false
|
2016-06-27 16:30:07 +03:00
|
|
|
|
|
|
|
// Bitwise operators
|
|
|
|
|
|
|
|
// **<<** Left Shift Operator
|
|
|
|
// << shifts bits to the left
|
|
|
|
4 << 1 // Shifts bits of 4 to left by 1 to give 8
|
|
|
|
// x << n can be thought as x * 2^n
|
|
|
|
|
|
|
|
|
|
|
|
// **>>** Right Shift Operator
|
2016-08-02 16:32:38 +03:00
|
|
|
// >> shifts bits to the right
|
2016-06-27 16:30:07 +03:00
|
|
|
4 >> 1 // Shifts bits of 4 to right by 1 to give 2
|
2016-08-02 16:32:38 +03:00
|
|
|
// x >> n can be thought as x / 2^n
|
2016-06-27 16:30:07 +03:00
|
|
|
|
2016-08-02 16:32:38 +03:00
|
|
|
~4 // Performs a bitwise not
|
2016-06-27 16:30:07 +03:00
|
|
|
4 | 3 // Performs bitwise or
|
|
|
|
4 & 3 // Performs bitwise and
|
|
|
|
4 ^ 3 // Performs bitwise xor
|
|
|
|
|
|
|
|
// Equivalent keywords are
|
2016-08-02 16:32:38 +03:00
|
|
|
compl 4 // Performs a bitwise not
|
|
|
|
4 bitor 3 // Performs bitwise or
|
2016-06-27 16:30:07 +03:00
|
|
|
4 bitand 3 // Performs bitwise and
|
2016-08-02 16:32:38 +03:00
|
|
|
4 xor 3 // Performs bitwise xor
|
2016-06-27 16:30:07 +03:00
|
|
|
|
2016-06-26 23:49:51 +03:00
|
|
|
|
2014-09-11 02:04:18 +04:00
|
|
|
```
|
2015-05-20 20:38:11 +03:00
|
|
|
Further Reading:
|
2014-10-10 09:57:52 +04:00
|
|
|
|
2018-10-14 02:56:21 +03:00
|
|
|
* An up-to-date language reference can be found at [CPP Reference](http://cppreference.com/w/cpp).
|
2021-11-02 00:24:35 +03:00
|
|
|
* A tutorial for beginners or experts, covering many modern features and good practices: [LearnCpp.com](https://www.learncpp.com/)
|
2018-10-14 02:56:21 +03:00
|
|
|
* A tutorial covering basics of language and setting up coding environment is available at [TheChernoProject - C++](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb).
|
2021-11-02 00:24:35 +03:00
|
|
|
* Additional resources may be found at [CPlusPlus](http://cplusplus.com).
|