C++ code bits
|
What is this? | Code demo | Commentary |
|
|
|
Hello World
The traditional first look at a new language
|
// Hello.cpp
#include <iostream>
int main() {
std::cout << "\n Hello World from C++"
<< "\n\n";
}
|
C++ functions begin with a specific return type or keyword auto that uses type inference to decide the
function's return type. They accept arguments in a parentisized list, encapsulate
an executable code in a set of statements enclosed in braces, and infrequently declare a return type.
auto g(ArgType a) -> ReturnType { ... }
Each executable C++ program starts in a "main" function, which often calls other functions
supplying operational details. There can be only one main in an executable.
|
|
|
|
Hello Objects
Demonstrates how Types are defined and instances created. The type DemoObj is defined above its main function
and instances dob and cln are created in main.
|
// CppObject::CreateObj.cpp
#include <iostream>
#include <string>
class DemoObject {
public:
DemoObject(const std::string& name)
: name_(name) {}
std::string name();
static void about();
private:
std::string name_;
};
std::string DemoObject::name() {
return name_;
}
void DemoObject::about() {
std::cout << "Demos creation and use of objects";
}
int main() {
DemoObject dob("Zing");
std::cout <<
"Created DemoObject instance with name "
<< dob.name();
/* create clone using compiler gen copy ctor */
DemoObject cln = dob;
std::cout <<
"\nCreated DemoObject clone with name "
<< cln.name();
std::cout << "\n";
DemoObject::about();
std::cout <<
"\nThat's all Folks" << "\n\n";
}
|
Types are defined with classes, like DemoObj on the left. Functionality is provided through methods,
e.g., functions associated with the class. Methods may be defined inline within the class declaration,
or separately with declarations that include the class name.
The method DemoObj(const std::string& name) is a constructor method that creates an instance of DemoObj
and endows it with a name, passed as a std::string&. The :name_(name) suffix explicitly constructs the private
DemoObj::name_ field.
Method std::string DemoObject::name() is a method that implicitly accepts a reference to its instance and returns
its name.
Methods that have a "static" prefix do not take an implicit reference to self and so cannot access member data.
They act like ordinary functions
but are accessed with their type followed by colons followed by the function. Non-static functions take an implicit reference
to self, can access member data, and are accessed from an object name followed by a period.
Types and functions declare public accessibility within regions of the class declaration marked public.
Functions declare return types with
the -> SomeType syntax following the parameter list.
auto f(T t) -> U { ... }
The code at the left declares the type and operations for DemoObj.
C:\...\CreateObject\CppObject>
cd build
C:\...\CreateObject\CppObject\build>
cmake .. >NULL
C:\...\CreateObject\CppObject\build>
cmake --build . >NULL
C:\...\CreateObject\CppObject\build>
"./debug/CreateObj.exe"
Created DemoObject instance with name Zing
Created DemoObject clone with name Zing
Demons creation and use of objects
That's all Folks
|
|
|
|
Hello Data
This section demonstrates data binding, copy, and move operations for primitive and one typical
composite type, the std::vector.
For primitive types like int and float, construction and assignment are essentially identical for
C++, Rust, and C#.
Copy, assign, and move operations for composite types like std::vector and std::string implicitly
invoke copy and move constructors and copy and move assignment operators. If not defined by the
class, they are compiler-generated.
References:
CppStory_Classes
|
/* DataOps.cpp - demo data operations */
#include <iostream>
#include <vector>
#include <string>
template <typename Coll>
void show(
const Coll& c, const std::string& msg = ""
) {
std::cout << "\n ";
if(msg.length() > 0) {
std::cout << msg << " ";
}
using ValType = typename Coll::value_type;
for(ValType item : c) {
std::cout << item << " ";
}
}
int main() {
std::cout <<
"\n -- demo data operations --\n";
/*
Primitive data types: int, double, ...
occupy contiguous regions of memory,
so copies simply copy memory.
*/
std::cout << "\n -- integer ops --";
int x = 42;
auto y = x - 2; // copy construction
std::cout << "\n x = "
<< x << ", y = " << y;
x = y; // copy assign
std::cout << "\n after copy assign: x = y";
std::cout << "\n x = " << x
<< ", y = "
<< y << "\n";
/*
Most non-primitive types:
Vec<T>, String, ...
occupy memory on stack and heap,
so they do not satisfy copy trait, so
need copy ctor and copy assign operations.
These often, but not always, can be
correctly built by compiler, as needed.
*/
std::cout << "\n -- Vec ops --";
std::vector<int> v { 1,2,4 };
show(v, "v = ");
auto w = v; // copy assign - a clone
std::cout <<
"\n after copy construction: let w = v:";
show(v, "v = ");
show(w, "w = ");
std::cout << "\n set w[1] = -2";
w[1] = -2;
show(v, "v = ");
show(w, "w = ");
// move construction from temporary
std::vector<int> vm = std::vector<int> { 1,2,3 };
std::cout << "\n move construction: vector<int>: ";
show(vm);
std::cout <<
"\n\n That's all Folks!!\n\n";
}
|
For composite types like std::string and std::vector, construction, copy, and assignment are
very different for the three languages C++, Rust, and C#.
C++ handles these operations with methods defined by the class or are compiler generated. See the
reference cited in the left panel.
-- demo data operations --
-- integer ops --
x = 42, y = 40
after copy assign: x = y
x = 40, y = 40
-- Vec ops --
v = 1 2 4
after copy construction: let w = v:
v = 1 2 4
w = 1 2 4
set w[1] = -2
v = 1 2 4
w = 1 -2 4
move construction: vector:
1 2 3
That's all Folks!!
The compiler chooses which constructor to call, e.g., copy or move, based on the program
context. Constructions are copy unless the source is a temporary. These context dependencies
make C++ more difficult to learn and to apply consistently.
|
|
|
|
Iteration
Iteration is the process of stepping through a collection and, perhaps, doing something
with each item encountered.
This demo iterates over strings, string views, arrays, and array views. C++20 views are
similar to Rust slices.
The demos use std::iterator and range-based for loops.
This code needs to be compiled for C++20. The gcc option is --std=c++20.
|
#include<string>
#include<ranges>
#include<iostream>
#include <locale>
#include <algorithm>
#include <string_view>
#include <typeinfo>
#include <iterator>
#include <vector>
/*-- helper function --*/
void putln(size_t num = 1) {
for(size_t i=0; i<num; ++i)
std::cout << "\n";
}
/*-- basic string iteration demos --*/
void string_iteration() {
std::string test_string = "a test string";
std::cout << "\n ascii characters from "
<< test_string << "\n ";
std::string::iterator iter =
test_string.begin();
while(iter != test_string.end()) {
char ch = *iter;
std::cout << ch << " ";
if(iter == test_string.end())
break;
++iter;
}
std::cout << "\n test_string: "
<< test_string;
putln();
}
/*-- idiomatic string iteration demos --*/
void idomatic_string_iteration() {
std::string test_string =
"another test string";
std::cout
<< "\n idiomatic ascii chars from:\n "
<< test_string << "\n ";
for(auto ch : test_string) {
std::cout << ch << " ";
}
putln();
char ch2 = test_string[1];
std::cout << "\n 2nd character of "
<< test_string
<< " is " << ch2;
std::cout << "\n test_string: "
<< test_string;
putln();
}
/*-----------------------------------------------
demonstrate all_of(...), is_alphabetic, is_...,
ranges, and string_view.
*/
/*-- helper function show collection items --*/
template<typename C>
void put_coll(
C& coll, const std::string& prefix = ""
) {
std::cout << prefix;
for(auto item : coll) {
std::cout << item;
}
}
/*-- helper function, displays test results --*/
void test(
bool pred,
const std::string& src,
const std::string& category
) {
if(pred)
{
std::cout << "\n " << src
<< " is " << category;
}
else {
std::cout << "\n " << src
<< " is not " << category;
}
}
/*-- demonstrate string adapter functions --*/
void string_adapters() {
std::string ls = "abc123";
/*-- are all chars alphbetic --*/
auto is_alpha =
[](char ch) -> bool {
return std::isalpha(ch);
};
test(
std::all_of(ls.begin(), ls.end(), is_alpha),
ls, "alphabetic"
);
/*-- are all chars alphanumeric --*/
auto is_alnum = [](char ch) -> bool
{ return std::isalnum(ch); };
test(
std::all_of(ls.begin(), ls.end(), is_alnum),
ls, "alphanumeric"
);
/*-- are all chars ascii --*/
auto is_ascii = [](char ch) -> bool
{
return 0 <= ch && ch < 128;
};
test(
std::all_of(ls.begin(), ls.end(), is_ascii),
ls, "ascii"
);
/*-- are all chars numeric --*/
auto is_num = [](char ch) -> bool
{ return std::isdigit(ch); };
test(
std::all_of(ls.begin(), ls.end(), is_num),
ls, "numeric"
);
putln();
/*-- using range::view with pipe --*/
auto r = ls | std::views::filter(is_num);
put_coll(r,"\n r is ");
/*--
numeric if numeric range, r, is same size as ls
--*/
test(
std::distance(r.begin(), r.end()) == ls.size(),
ls, "numeric"
);
putln();
/*-- display chars from str slice --*/
ls = "abc123";
/*-- non-owning view --*/
std::string_view slice{ ls };
slice.remove_prefix(2);
slice.remove_suffix(2);
std::cout << "\n third and fourth chars of "
<< ls << " are "
<< slice;
put_coll(slice, "\n slice is ");
std::cout << "\n ls is still " << ls;
putln();
/*---------------------------------------------
Form string from numeric chars in source, ls.
Uses std::range adapter std::view.
*/
auto results = ls | std::views::filter(is_num);
std::cout << "\n numeric chars of "
<< ls << " are ";
for(auto r:results) {
std::cout << r;
}
put_coll(results, "\n ");
/*---------------------------------------------
The results item has a very ugly type.
Uncomment lines below to see it.
That means that std::cout << results;
will fail to compile.
*/
// std::cout << typeid(results).name();
putln();
}
/*-----------------------------------------------
Define and iterate through byte array
*/
using byte = short int;
using Iter = byte*;
void define_and_iterate_byte_array() {
byte ba[] = { 1, 2, 3, 4, 5 };
std::cout << "\n ";
for(
Iter it=std::begin(ba);
it != std::end(ba);
++it
)
std::cout << *it << " ";
putln();
}
void idiomatic_define_and_iterate_byte_array() {
short int ba[] = { 1, 2, 3, 4, 5 };
std::cout << "\n ";
for(auto i : ba) {
std::cout << i << " ";
}
put_coll(ba, "\n ");
putln();
std::cout << "\n [";
auto temp = ba | std::views::take(4);
for(auto i : temp) {
std::cout << i << ", ";
}
auto iter = std::end(ba);
auto last = *(--iter);
std::cout << last << "]";
}
int main() {
std::cout
<< "\n -- demonstrate iteration --\n";
std::cout << "\n -- string iteration --";
string_iteration();
idomatic_string_iteration();
std::cout
<< "\n -- string iteration adapters --";
string_adapters();
std::cout
<< "\n\n -- byte array iteration --";
define_and_iterate_byte_array();
std::cout
<< "\n -- idiomatic byte array iter'n --";
idiomatic_define_and_iterate_byte_array();
std::cout << "\n\n That's all Folks!\n\n";
}
|
Unlike Rust, which uses utf8 characters that range in size from 1 to 4 bytes,
C++ characters are 1 byte for std::char and 2 bytes for std::w_char. Since it has
fixed size characters, iteration over strings use the same processes
as for arrays and vectors.
-- demonstrate iteration --
-- string iteration --
ascii characters from a test string
a t e s t s t r i n g
test_string: a test string
idiomatic ascii chars from:
another test string
a n o t h e r t e s t s t r i n g
2nd character of another test string is n
test_string: another test string
-- string iteration adapters --
abc123 is not alphabetic
abc123 is alphanumeric
abc123 is ascii
abc123 is not numeric
r is 123
abc123 is not numeric
third and fourth chars of abc123 are c1
slice is c1
ls is still abc123
numeric chars of abc123 are 123
123
-- byte array iteration --
1 2 3 4 5
-- idiomatic byte array iter'n --
1 2 3 4 5
12345
[1, 2, 3, 4, 5]
That's all Folks!
|
|
|
|
Data Structures | | |
|
|
|
Functions | | |
|
|
|
Generic functions | | |
|
|
|
Structs | | |
|
|
|
Generic structs | | |
|
|
|
Basic DIP | | |
|
|
|
Generic DIP | | |