mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-21 06:45:39 -06:00
New Core Tutorial
git-svn-id: svn://ultimatepp.org/upp/trunk@10538 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
1f9e585869
commit
2fa5bd86a1
37 changed files with 3125 additions and 0 deletions
26
tutorial/CoreTutorial/Any.cpp
Normal file
26
tutorial/CoreTutorial/Any.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void AnyTutorial()
|
||||||
|
{
|
||||||
|
/// .`Any`
|
||||||
|
|
||||||
|
/// `Any` is a container that can contain none or one element of %any% type. `Any::Is`
|
||||||
|
/// method matches exact type ignoring class hierarchies (unlike `One::Is`). You can use
|
||||||
|
/// `Get` to retrieve a reference to the instance stored:
|
||||||
|
|
||||||
|
for(int pass = 0; pass < 2; pass++) {
|
||||||
|
Any x;
|
||||||
|
if(pass)
|
||||||
|
x.Create<String>() = "Hello!";
|
||||||
|
else
|
||||||
|
x.Create<Color>() = Blue();
|
||||||
|
|
||||||
|
if(x.Is<String>())
|
||||||
|
LOG("Any is now String: " << x.Get<String>());
|
||||||
|
|
||||||
|
if(x.Is<Color>())
|
||||||
|
LOG("Any is now Color: " << x.Get<Color>());
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
25
tutorial/CoreTutorial/Array.cpp
Normal file
25
tutorial/CoreTutorial/Array.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void ArrayTutorial()
|
||||||
|
{
|
||||||
|
/// .Array flavor
|
||||||
|
|
||||||
|
/// If elements are not `Moveable` and therefore cannot be stored in `Vector` flavor, they
|
||||||
|
/// can still be stored in `Array` flavor. Another reason for using Array is the need for
|
||||||
|
/// referencing elements - Array flavor never invalidates references or pointers to them.
|
||||||
|
/// Finally, if sizeof(T) is large (say more than 100-200 bytes), using Array might be better
|
||||||
|
/// from performance perspective.
|
||||||
|
|
||||||
|
/// Example of elements that cannot be stored in Vector flavor are standard library objects like
|
||||||
|
/// `std::string` (because obviously, standard library knows nothing about U++ Moveable
|
||||||
|
/// concept):
|
||||||
|
|
||||||
|
Array<std::string> as;
|
||||||
|
for(int i = 0; i < 4; i++)
|
||||||
|
as.Add("Test");
|
||||||
|
|
||||||
|
for(auto s : as)
|
||||||
|
DUMP(s.c_str());
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
82
tutorial/CoreTutorial/AsString.cpp
Normal file
82
tutorial/CoreTutorial/AsString.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void AsStringTutorial()
|
||||||
|
{
|
||||||
|
/// .`AsString`, `ToString` and `operator<<`
|
||||||
|
|
||||||
|
/// U++ Core provides simple yet effective standard schema for converting values to default
|
||||||
|
/// textual form. System is based on the combination of template functions (following code
|
||||||
|
/// is part of U++ library):
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
namespace Upp {
|
||||||
|
template <class T>
|
||||||
|
inline String AsString(const T& x)
|
||||||
|
{
|
||||||
|
return x.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline Stream& operator<<(Stream& s, const T& x)
|
||||||
|
{
|
||||||
|
s << AsString(x);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline String& operator<<(String& s, const T& x)
|
||||||
|
{
|
||||||
|
s.Cat(AsString(x));
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Client types have to either define `String ToString` method or specialize `AsString`
|
||||||
|
/// template in `Upp` namespace. Such types can be appended to Streams or Strings using
|
||||||
|
/// `operator<<`. Of course, U++ value types and primitive types have required items
|
||||||
|
/// predefined by U++:
|
||||||
|
|
||||||
|
FileOut fout(ConfigFile("test.txt"));
|
||||||
|
String sout;
|
||||||
|
|
||||||
|
fout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime();
|
||||||
|
sout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime();
|
||||||
|
|
||||||
|
fout.Close();
|
||||||
|
|
||||||
|
DUMP(LoadFile(ConfigFile("test.txt")));
|
||||||
|
DUMP(sout);
|
||||||
|
|
||||||
|
/// Getting client types involved into this schema is not too difficult, all you need to do
|
||||||
|
/// is to add `ToString` method:
|
||||||
|
|
||||||
|
struct BinFoo {
|
||||||
|
int x;
|
||||||
|
|
||||||
|
String ToString() const { return FormatIntBase(x, 2); }
|
||||||
|
};
|
||||||
|
|
||||||
|
BinFoo bf;
|
||||||
|
bf.x = 30;
|
||||||
|
|
||||||
|
sout.Clear();
|
||||||
|
sout << bf;
|
||||||
|
DUMP(sout);
|
||||||
|
|
||||||
|
/// If you cannot add `ToString`, you can still specialize template in Upp namespace:
|
||||||
|
|
||||||
|
struct RomanFoo {
|
||||||
|
int x;
|
||||||
|
|
||||||
|
RomanFoo(int x) : x(x) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
namespace Upp {
|
||||||
|
template <> String Upp::AsString(const RomanFoo& a) { return FormatIntRoman(a.x); }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
54
tutorial/CoreTutorial/Bidirectional.cpp
Normal file
54
tutorial/CoreTutorial/Bidirectional.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Bidirectional()
|
||||||
|
{
|
||||||
|
/// .Bidirectional containers
|
||||||
|
|
||||||
|
/// `Vector` and `Array` containers allow fast adding and removing elements at the end of
|
||||||
|
/// sequence. Sometimes, same is needed at begin of sequence too (usually to support FIFO
|
||||||
|
/// queues). `BiVector` and `BiArray` are optimal for this scenario:
|
||||||
|
|
||||||
|
BiVector<int> n;
|
||||||
|
n.AddHead(1);
|
||||||
|
n.AddTail(2);
|
||||||
|
n.AddHead(3);
|
||||||
|
n.AddTail(4);
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
n.DropHead();
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
n.DropTail();
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
struct Val {
|
||||||
|
virtual String ToString() const = 0;
|
||||||
|
virtual ~Val() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Number : Val {
|
||||||
|
int n;
|
||||||
|
virtual String ToString() const { return AsString(n); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Text : Val {
|
||||||
|
String s;
|
||||||
|
virtual String ToString() const { return s; }
|
||||||
|
};
|
||||||
|
|
||||||
|
BiArray<Val> num;
|
||||||
|
num.CreateHead<Number>().n = 3;
|
||||||
|
num.CreateTail<Text>().s = "Hello";
|
||||||
|
num.CreateHead<Text>().s = "World";
|
||||||
|
num.CreateTail<Number>().n = 2;
|
||||||
|
|
||||||
|
DUMP(num);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
59
tutorial/CoreTutorial/ClientTypes.cpp
Normal file
59
tutorial/CoreTutorial/ClientTypes.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void ContainerClientTypes()
|
||||||
|
{
|
||||||
|
/// .Client types in U++ containers
|
||||||
|
|
||||||
|
/// So far we were using int as type of elements. In order to store client defined types
|
||||||
|
/// into the `Vector` (and the Vector ^topic://Core/src/Overview$en-us:flavor^) the type
|
||||||
|
/// must satisfy ^topic://Core/src/Moveable$en-us:moveable^ requirement - in short, it must
|
||||||
|
/// not contain back-pointers nor virtual methods. Type must be marked as %moveable% in
|
||||||
|
/// order to define interface contract using `Moveable`
|
||||||
|
/// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^:
|
||||||
|
|
||||||
|
struct Distribution : Moveable<Distribution> {
|
||||||
|
String text;
|
||||||
|
Vector<int> data;
|
||||||
|
|
||||||
|
String ToString() const { return text + ": " + AsString(data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Now to add `Distribution` elements you cannot use `Vector::Add(const T&)`, because it requires
|
||||||
|
/// elements to have default deep-copy constructor - and `Distribution does not have one, as
|
||||||
|
/// `Vector<int>` has default pick-constructor, so Distribution itself has pick-constructor.
|
||||||
|
/// It would no be a good idea either, because deep-copy would involve expensive copying of
|
||||||
|
/// inner Vector.
|
||||||
|
|
||||||
|
/// Instead, Add without parameters has to be used - it default constructs (that is cheap)
|
||||||
|
/// element in Vector and returns reference to it:
|
||||||
|
|
||||||
|
Vector<Distribution> dist;
|
||||||
|
for(int n = 5; n <= 10; n++) {
|
||||||
|
Distribution& d = dist.Add();
|
||||||
|
d.text << "Test " << n;
|
||||||
|
for(int i = 0; i < 10000; i++)
|
||||||
|
d.data.At(Random(n), 0)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DUMPC(dist);
|
||||||
|
|
||||||
|
/// Another possibility is to use `Vector::Add(T&&)` method, which uses pick-constructor
|
||||||
|
/// instead of deep-copy constructor. E.g. `Distribution` elements might be generated by
|
||||||
|
/// some function:
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
Distribution CreateDist(int n);
|
||||||
|
|
||||||
|
/// and code for adding such elements to Vector then looks like:
|
||||||
|
|
||||||
|
for(n = 5; n <= 10; n++)
|
||||||
|
dist.Add(CreateDist(n));
|
||||||
|
|
||||||
|
/// alternatively, you can use default-constructed variant too
|
||||||
|
|
||||||
|
dist.Add() = CreateDist();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
35
tutorial/CoreTutorial/CombineHash.cpp
Normal file
35
tutorial/CoreTutorial/CombineHash.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void CombineHashTutorial()
|
||||||
|
{
|
||||||
|
/// .CombineHash
|
||||||
|
|
||||||
|
/// To simplify providing of high quality hash codes for composite types, U++ provides
|
||||||
|
/// `CombineHash` utility class. This class uses `GetHashValue` function to gather hash
|
||||||
|
/// codes of all values and combines them to provide final hash value for composite type:
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
String a;
|
||||||
|
int b;
|
||||||
|
|
||||||
|
unsigned GetHashValue() const { return CombineHash(a, b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Note that `GetHashValue` is defined as function template that calls `GetHashValue`
|
||||||
|
/// method of its argument, therefore defining `GetHashValue` method defines `GetHashValue`
|
||||||
|
/// function too:
|
||||||
|
|
||||||
|
Foo x;
|
||||||
|
x.a = "world";
|
||||||
|
x.b = 22;
|
||||||
|
|
||||||
|
DUMP(GetHashValue(x));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
x.a << '!';
|
||||||
|
|
||||||
|
DUMP(GetHashValue(x));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
134
tutorial/CoreTutorial/Compare.cpp
Normal file
134
tutorial/CoreTutorial/Compare.cpp
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void ComparableTutorial()
|
||||||
|
{
|
||||||
|
/// .SgnCompare and CombineCompare
|
||||||
|
|
||||||
|
/// Traditional approach of C language of representing comparison results was 3-state:
|
||||||
|
/// comparing a and b results in negative value (if a < b), zero (if a == b) or positive
|
||||||
|
/// value (a > b). In C++ standard library, comparisons are usually represented with `bool`
|
||||||
|
/// predicates.
|
||||||
|
|
||||||
|
/// However, with `bool` predicate it becomes somewhat more difficult to provide
|
||||||
|
/// comparisons for composite types:
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
String a;
|
||||||
|
int b;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
// we want to order Foo instances by a first, then b, then c
|
||||||
|
|
||||||
|
bool operator<(const Foo& x) const {
|
||||||
|
return a < x.a ? true
|
||||||
|
: a == x.a ? b < x.b ? true
|
||||||
|
: b == x.b ? false
|
||||||
|
: c < x.c
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// U++ provides standard function `SgnCompare`, which returns negative value/zero/positive
|
||||||
|
/// in "C style":
|
||||||
|
|
||||||
|
int a = 1;
|
||||||
|
int b = 2;
|
||||||
|
|
||||||
|
DUMP(SgnCompare(a, b));
|
||||||
|
DUMP(SgnCompare(b, a));
|
||||||
|
DUMP(SgnCompare(a, a));
|
||||||
|
|
||||||
|
/// Default implementation of `SgnCompare` calls `Compare` method of value:
|
||||||
|
|
||||||
|
struct MyClass {
|
||||||
|
int val;
|
||||||
|
|
||||||
|
int Compare(const MyClass& x) const { return SgnCompare(val, x.val); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `SgnCompare` is now defined for `MyClass`:
|
||||||
|
|
||||||
|
MyClass u, v;
|
||||||
|
u.val = 1;
|
||||||
|
v.val = 2;
|
||||||
|
|
||||||
|
DUMP(SgnCompare(u, v));
|
||||||
|
DUMP(SgnCompare(v, u));
|
||||||
|
DUMP(SgnCompare(v, v));
|
||||||
|
|
||||||
|
/// Now getting back to `Foo`, with `SgnCompare` `operator<` becomes much less difficult:
|
||||||
|
|
||||||
|
struct Foo2 {
|
||||||
|
String a;
|
||||||
|
int b;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
bool operator<(const Foo2& x) const {
|
||||||
|
int q = SgnCompare(a, x.a);
|
||||||
|
if(q) return q < 0;
|
||||||
|
q = SgnCompare(b, x.b);
|
||||||
|
if(q) return q < 0;
|
||||||
|
q = SgnCompare(c, x.c);
|
||||||
|
return q < 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Alternatively, it is possible to define just `Compare` method and use `Comparable`
|
||||||
|
/// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^ to
|
||||||
|
/// define all relation operators:
|
||||||
|
|
||||||
|
struct Foo3 : Comparable<Foo3> {
|
||||||
|
String a;
|
||||||
|
int b;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
int Compare(const Foo3& x) const {
|
||||||
|
int q = SgnCompare(a, x.a);
|
||||||
|
if(q) return q;
|
||||||
|
q = SgnCompare(b, x.b);
|
||||||
|
if(q) return q;
|
||||||
|
return SgnCompare(c, x.c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Foo3 m, n;
|
||||||
|
m.a = "A";
|
||||||
|
m.b = 1;
|
||||||
|
m.c = 2;
|
||||||
|
n.a = "A";
|
||||||
|
n.b = 1;
|
||||||
|
n.c = 3;
|
||||||
|
|
||||||
|
DUMP(m < n);
|
||||||
|
DUMP(m == n);
|
||||||
|
DUMP(m != n);
|
||||||
|
DUMP(SgnCompare(m, n));
|
||||||
|
|
||||||
|
/// While the content of `Compare` method is trivial, it can be further simplified using
|
||||||
|
/// `CombineCompare` helper class:
|
||||||
|
|
||||||
|
struct Foo4 : Comparable<Foo4> {
|
||||||
|
String a;
|
||||||
|
int b;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
int Compare(const Foo4& x) const {
|
||||||
|
return CombineCompare(a, x.a)(b, x.b)(c, x.c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Foo4 o, p;
|
||||||
|
o.a = "A";
|
||||||
|
o.b = 1;
|
||||||
|
o.c = 2;
|
||||||
|
p.a = "A";
|
||||||
|
p.b = 1;
|
||||||
|
p.c = 3;
|
||||||
|
|
||||||
|
DUMP(o < p);
|
||||||
|
DUMP(o == p);
|
||||||
|
DUMP(o != p);
|
||||||
|
DUMP(SgnCompare(o, p));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
45
tutorial/CoreTutorial/CoreTutorial.upp
Normal file
45
tutorial/CoreTutorial/CoreTutorial.upp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
uses
|
||||||
|
Core,
|
||||||
|
RichEdit;
|
||||||
|
|
||||||
|
file
|
||||||
|
Tutorial.h,
|
||||||
|
Tutorial.cpp,
|
||||||
|
Core readonly separator,
|
||||||
|
ToDo.cpp,
|
||||||
|
Range.cpp,
|
||||||
|
Logging.cpp,
|
||||||
|
String.cpp,
|
||||||
|
StringBuffer.cpp,
|
||||||
|
WString.cpp,
|
||||||
|
DateTime.cpp,
|
||||||
|
AsString.cpp,
|
||||||
|
Value.cpp,
|
||||||
|
Null.cpp,
|
||||||
|
Value2.cpp,
|
||||||
|
CombineHash.cpp,
|
||||||
|
Compare.cpp,
|
||||||
|
Vector.cpp,
|
||||||
|
Vector2.cpp,
|
||||||
|
Transfer.cpp,
|
||||||
|
ClientTypes.cpp,
|
||||||
|
Array.cpp,
|
||||||
|
PolyArray.cpp,
|
||||||
|
Bidirectional.cpp,
|
||||||
|
Index.cpp,
|
||||||
|
IndexClient.cpp,
|
||||||
|
Map.cpp,
|
||||||
|
One.cpp,
|
||||||
|
Any.cpp,
|
||||||
|
InVector.cpp,
|
||||||
|
SortedMap.cpp,
|
||||||
|
Tuple.cpp,
|
||||||
|
Ranges.cpp,
|
||||||
|
Sort.cpp,
|
||||||
|
Function.cpp,
|
||||||
|
tutorial2.cpp,
|
||||||
|
help.qtf;
|
||||||
|
|
||||||
|
mainconfig
|
||||||
|
"" = "GUI";
|
||||||
|
|
||||||
119
tutorial/CoreTutorial/DateTime.cpp
Normal file
119
tutorial/CoreTutorial/DateTime.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void DateTime()
|
||||||
|
{
|
||||||
|
/// .Date and Time
|
||||||
|
|
||||||
|
/// To represent date and time, U++ provides `Date` and `Time` concrete types.
|
||||||
|
|
||||||
|
Date date = GetSysDate();
|
||||||
|
|
||||||
|
DUMP(date);
|
||||||
|
|
||||||
|
/// All data members of `Date` structure are public:
|
||||||
|
|
||||||
|
DUMP((int)date.year); // we need to cast to int because some date members
|
||||||
|
DUMP((int)date.month); // are of unsigned character type which would log
|
||||||
|
DUMP((int)date.day); // as characters
|
||||||
|
|
||||||
|
/// Dates can be compared:
|
||||||
|
|
||||||
|
DUMP(date > Date(2000, 1, 1));
|
||||||
|
|
||||||
|
/// Adding a number to `Date` adds a number of days to it, incrementing/decrementing goes to
|
||||||
|
/// the next/previous day:
|
||||||
|
|
||||||
|
DUMP(date + 1);
|
||||||
|
DUMP(--date);
|
||||||
|
DUMP(++date);
|
||||||
|
|
||||||
|
/// Subtraction of dates yields a number of days between them:
|
||||||
|
|
||||||
|
DUMP(date - Date(2000, 1, 1));
|
||||||
|
|
||||||
|
/// There are several `Date` and calendar related functions:
|
||||||
|
|
||||||
|
DUMP(IsLeapYear(2012));
|
||||||
|
DUMP(IsLeapYear(2014));
|
||||||
|
DUMP(IsLeapYear(2015));
|
||||||
|
DUMP(IsLeapYear(2016));
|
||||||
|
DUMP(IsLeapYear(2017));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(GetDaysOfMonth(2, 2015));
|
||||||
|
DUMP(GetDaysOfMonth(2, 2016));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(DayOfWeek(date)); // 0 is Sunday
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(LastDayOfMonth(date));
|
||||||
|
DUMP(FirstDayOfMonth(date));
|
||||||
|
DUMP(LastDayOfYear(date));
|
||||||
|
DUMP(FirstDayOfYear(date));
|
||||||
|
DUMP(DayOfYear(date)); // number of days since Jan-1 + 1
|
||||||
|
DUMP(DayOfYear(Date(2016, 1, 1)));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(AddMonths(date, 20));
|
||||||
|
DUMP(GetMonths(date, date + 100)); // number of 'whole months' between two dates
|
||||||
|
DUMP(GetMonthsP(date, date + 100)); // number of 'whole or partial months' between two dates
|
||||||
|
DUMP(AddYears(date, 2));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(GetWeekDate(2015, 1));
|
||||||
|
int year;
|
||||||
|
DUMP(GetWeek(Date(2016, 1, 1), year)); // first day of year can belong to previous year
|
||||||
|
DUMP(year);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(EasterDay(2015));
|
||||||
|
DUMP(EasterDay(2016));
|
||||||
|
|
||||||
|
/// U++ defines the beginning and the end of era, most algorithms can safely assume that as
|
||||||
|
/// minimal and maximal values `Date` can represent:
|
||||||
|
|
||||||
|
DUMP(Date::Low());
|
||||||
|
DUMP(Date::High());
|
||||||
|
|
||||||
|
/// Time is derived from `Date`, adding members to represent time:
|
||||||
|
|
||||||
|
Time time = GetSysTime();
|
||||||
|
DUMP(time);
|
||||||
|
DUMP((Date)time);
|
||||||
|
DUMP((int)time.hour);
|
||||||
|
DUMP((int)time.minute);
|
||||||
|
DUMP((int)time.second);
|
||||||
|
|
||||||
|
/// Times can be compared:
|
||||||
|
|
||||||
|
DUMP(time > Time(1970, 0, 0));
|
||||||
|
|
||||||
|
/// Warning: As `Time` is derived from the `Date`, most operations automatically convert
|
||||||
|
/// `Time` back to `Date`. You have to use `ToTime` conversion function to convert `Date`
|
||||||
|
/// to `Time`:
|
||||||
|
|
||||||
|
DUMP(time > date); // time gets converted to Date...
|
||||||
|
DUMP(time > ToTime(date));
|
||||||
|
|
||||||
|
/// Like `Date`, `Time` supports add and subtract operations, but numbers represent seconds
|
||||||
|
/// (using `int64` datatype):
|
||||||
|
|
||||||
|
DUMP(time + 1);
|
||||||
|
DUMP(time + 24 * 3600);
|
||||||
|
DUMP(time - date); // time converts to Date, so the result is in days
|
||||||
|
DUMP(time - ToTime(date)); // Time - Time is in seconds
|
||||||
|
|
||||||
|
/// `Time` defines era limits too:
|
||||||
|
|
||||||
|
DUMP(Time::Low());
|
||||||
|
DUMP(Time::High());
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
90
tutorial/CoreTutorial/Function.cpp
Normal file
90
tutorial/CoreTutorial/Function.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void FunctionTutorial()
|
||||||
|
{
|
||||||
|
/// .Function
|
||||||
|
|
||||||
|
/// U++ `Function` is quite similar to `std::function` - it is a function wrapper that can
|
||||||
|
/// store/copy/invoke any callable target. There are two important differences.
|
||||||
|
/// First, invoking empty `Function` is NOP, if `Function` has return type `T`, it returns
|
||||||
|
/// `T()`. Second, `Function` allows effective chaining of callable targets using
|
||||||
|
/// `operator<<`, if `Function` has return type, the return type of last callable appended
|
||||||
|
/// is used.
|
||||||
|
|
||||||
|
/// Usually, the callable target is C++11 lambda:
|
||||||
|
|
||||||
|
Function<int (int)> fn = [](int n) { LOG("Called A"); return 3 * n; };
|
||||||
|
|
||||||
|
LOG("About to call function");
|
||||||
|
int n = fn(7);
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
/// If you chain another lambda into `Function`, all are called, but the last one's return
|
||||||
|
/// value is used:
|
||||||
|
|
||||||
|
fn << [](int n) { LOG("Called B"); return n * n; };
|
||||||
|
LOG("About to call combined function");
|
||||||
|
n = fn(7);
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
/// Invoking empty lambda does nothing and returns default constructed return value. This
|
||||||
|
/// is quite useful for GUI classes, which have a lot of output events represented by
|
||||||
|
/// `Function` which are often unassigned to any action.
|
||||||
|
|
||||||
|
fn.Clear();
|
||||||
|
LOG("About to call empty function");
|
||||||
|
n = fn(7);
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
/// While using `Function` with lambda expression is the most common, you can use any
|
||||||
|
/// target that has corresponding `operator()` defined:
|
||||||
|
|
||||||
|
struct Functor {
|
||||||
|
int operator()(int x) { LOG("Called Foo"); return x % 2; }
|
||||||
|
};
|
||||||
|
|
||||||
|
fn = Functor();
|
||||||
|
LOG("About to call Functor");
|
||||||
|
n = fn(7);
|
||||||
|
DUMP(n);
|
||||||
|
|
||||||
|
/// As `Function` with `void` and `bool` return types are the most frequently used, U++
|
||||||
|
/// defines template aliases `Event`:
|
||||||
|
|
||||||
|
Event<> ev = [] { LOG("Event invoked"); };
|
||||||
|
|
||||||
|
ev();
|
||||||
|
|
||||||
|
/// and `Gate`:
|
||||||
|
|
||||||
|
Gate<int> gt = [](int x) { LOG("Gate invoked with " << x); return x < 10; };
|
||||||
|
|
||||||
|
bool b = gt(9);
|
||||||
|
DUMP(b);
|
||||||
|
b = gt(10);
|
||||||
|
DUMP(b);
|
||||||
|
|
||||||
|
/// Using lambda to define calls to methods with more parameters can be verbose and
|
||||||
|
/// error-prone. The issue can be simplified by using `THISFN` macro:
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
void Test(int a, const String& b) { LOG("Foo::Test " << a << ", " << b); }
|
||||||
|
|
||||||
|
typedef Foo CLASSNAME; // required for THISFN
|
||||||
|
|
||||||
|
void Do() {
|
||||||
|
Event<int, const String&> fn;
|
||||||
|
|
||||||
|
fn = [=](int a, const String& b) { Test(a, b); };
|
||||||
|
fn(1, "using lambda");
|
||||||
|
|
||||||
|
fn = THISFN(Test); // this is functionally equivalent, but less verbose
|
||||||
|
fn(2, "using THISFN");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Foo f;
|
||||||
|
f.Do();
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
28
tutorial/CoreTutorial/InVector.cpp
Normal file
28
tutorial/CoreTutorial/InVector.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void InVectorTutorial()
|
||||||
|
{
|
||||||
|
/// .`InVector`, `InArray`
|
||||||
|
|
||||||
|
/// `InVector` and `InArray` are container types quite similar to `Vector`/`Array`, but
|
||||||
|
/// they trade the speed of `operator[]` with the ability to insert or remove elements at
|
||||||
|
/// any position quickly. You can expect `operator[]` to be about 10 times slower than in
|
||||||
|
/// Vector (but that is still quite fast), while `Insert` at any position scales well up to
|
||||||
|
/// hundreds of megabytes of data (e.g. `InVector` containing 100M of String elements is
|
||||||
|
/// handled without problems).
|
||||||
|
|
||||||
|
InVector<int> v;
|
||||||
|
for(int i = 0; i < 1000000; i++)
|
||||||
|
v.Add(i);
|
||||||
|
v.Insert(0, -1); // This is fast
|
||||||
|
|
||||||
|
/// While the interface of `InVector`/`InArray` is almost identical to `Vector`/`Array`,
|
||||||
|
/// `InVector`/`InArray` in addition implements `FindLowerBound`/`FindUpperBound` methods -
|
||||||
|
/// while normal generic range algorithms work, it is possible to provide
|
||||||
|
/// `InVector`/`InArray` specific optimizations that basically match the performace of
|
||||||
|
/// `Find*Bound` on simple `Vector`.
|
||||||
|
|
||||||
|
DUMP(v.FindLowerBound(55));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
115
tutorial/CoreTutorial/Index.cpp
Normal file
115
tutorial/CoreTutorial/Index.cpp
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void IndexTutorial() {
|
||||||
|
/// .`Index`
|
||||||
|
|
||||||
|
/// `Index` is the the foundation of all U++ associative operations and is one of defining
|
||||||
|
/// features of U++.
|
||||||
|
|
||||||
|
/// `Index` is a container very similar to the plain `Vector` (it is random access array of
|
||||||
|
/// elements with fast addition at the end) with one additional feature - it is able to fast
|
||||||
|
/// retrieve position of element with required value using `Find` method:
|
||||||
|
|
||||||
|
Index<String> ndx;
|
||||||
|
ndx.Add("alfa");
|
||||||
|
ndx.Add("beta");
|
||||||
|
ndx.Add("gamma");
|
||||||
|
ndx.Add("delta");
|
||||||
|
ndx.Add("kappa");
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
DUMP(ndx.Find("beta"));
|
||||||
|
|
||||||
|
/// If element is not present in `Index`, `Find` returns a negative value:
|
||||||
|
|
||||||
|
DUMP(ndx.Find("something"));
|
||||||
|
|
||||||
|
/// Any element can be replaced using `Set` method:
|
||||||
|
|
||||||
|
ndx.Set(1, "alfa");
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
/// If there are more elements with the same value, they can be iterated using `FindNext`
|
||||||
|
/// method:
|
||||||
|
|
||||||
|
int fi = ndx.Find("alfa");
|
||||||
|
while(fi >= 0) {
|
||||||
|
DUMP(fi);
|
||||||
|
fi = ndx.FindNext(fi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `FindAdd` method retrieves position of element like `Find`, but if element is not
|
||||||
|
/// present in `Index`, it is added:
|
||||||
|
|
||||||
|
DUMP(ndx.FindAdd("one"));
|
||||||
|
DUMP(ndx.FindAdd("two"));
|
||||||
|
DUMP(ndx.FindAdd("three"));
|
||||||
|
DUMP(ndx.FindAdd("two"));
|
||||||
|
DUMP(ndx.FindAdd("three"));
|
||||||
|
DUMP(ndx.FindAdd("one"));
|
||||||
|
|
||||||
|
/// Removing elements from random access sequence tends to be expensive, that is why rather
|
||||||
|
/// than remove, `Index` supports `Unlink` and `UnlinkKey` operations, which retain the
|
||||||
|
/// element in `Index` but make it invisible for `Find` operation:
|
||||||
|
|
||||||
|
ndx.Unlink(2);
|
||||||
|
ndx.UnlinkKey("kappa");
|
||||||
|
|
||||||
|
DUMP(ndx.Find(ndx[2]));
|
||||||
|
DUMP(ndx.Find("kappa"));
|
||||||
|
|
||||||
|
/// You can test whether element at given position is unlinked using `IsUnlinked` method
|
||||||
|
|
||||||
|
DUMP(ndx.IsUnlinked(1));
|
||||||
|
DUMP(ndx.IsUnlinked(2));
|
||||||
|
|
||||||
|
/// Unlinked positions can be reused by `Put` method:
|
||||||
|
|
||||||
|
ndx.Put("foo");
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
DUMP(ndx.Find("foo"));
|
||||||
|
|
||||||
|
/// You can also remove all unlinked elements from `Index` using `Sweep` method:
|
||||||
|
|
||||||
|
ndx.Sweep();
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
/// Operations directly removing or inserting elements of Index are expensive, but
|
||||||
|
/// available too:
|
||||||
|
|
||||||
|
ndx.Remove(1);
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
ndx.RemoveKey("two");
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
ndx.Insert(0, "insert");
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
/// PickKeys operation allows you to obtain Vector of elements of Index in low
|
||||||
|
/// constant time operation (while destroying source Index)
|
||||||
|
|
||||||
|
Vector<String> d = ndx.PickKeys();
|
||||||
|
|
||||||
|
DUMP(d);
|
||||||
|
|
||||||
|
/// Pick-assigning `Vector` to `Index` is supported as well:
|
||||||
|
|
||||||
|
d[0] = "test";
|
||||||
|
|
||||||
|
ndx = pick(d);
|
||||||
|
|
||||||
|
DUMP(ndx);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
31
tutorial/CoreTutorial/IndexClient.cpp
Normal file
31
tutorial/CoreTutorial/IndexClient.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void IndexClient()
|
||||||
|
{
|
||||||
|
/// .Index and client types
|
||||||
|
|
||||||
|
/// In order to store elements to `Index`, they type must be `Moveable`, have deep copy and
|
||||||
|
/// defined the `operator==` and a `GetHashValue` function or method to compute the hash
|
||||||
|
/// code. It is recommended to use `CombineHash` to combine hash values of types that
|
||||||
|
/// already provide `GetHashValue`:
|
||||||
|
|
||||||
|
struct Person : Moveable<Person> {
|
||||||
|
String name;
|
||||||
|
String surname;
|
||||||
|
|
||||||
|
unsigned GetHashValue() const { return CombineHash(name, surname); }
|
||||||
|
bool operator==(const Person& b) const { return name == b.name && surname == b.surname; }
|
||||||
|
|
||||||
|
Person(String name, String surname) : name(name), surname(surname) {}
|
||||||
|
Person() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Index<Person> p;
|
||||||
|
p.Add(Person("John", "Smith"));
|
||||||
|
p.Add(Person("Paul", "Carpenter"));
|
||||||
|
p.Add(Person("Carl", "Engles"));
|
||||||
|
|
||||||
|
DUMP(p.Find(Person("Paul", "Carpenter")));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
85
tutorial/CoreTutorial/Logging.cpp
Normal file
85
tutorial/CoreTutorial/Logging.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Logging()
|
||||||
|
{
|
||||||
|
///. Logging
|
||||||
|
|
||||||
|
/// Logging is a useful technique to trace the flow of the code and examine results. In
|
||||||
|
/// this tutorial we will be using logging extensively, so let us start tutorial with the
|
||||||
|
/// explanation of logging.
|
||||||
|
|
||||||
|
/// In debug mode and with default settings, macro `LOG` puts string into output log file.
|
||||||
|
/// Log file is placed into 'config-directory', which by default is .exe directory in Win32
|
||||||
|
/// and ~/.upp/appname in POSIX.
|
||||||
|
|
||||||
|
/// In TheIDE, you can access the log using 'Debug'/'View the log file Alt+L'.
|
||||||
|
|
||||||
|
LOG("Hello world");
|
||||||
|
|
||||||
|
/// You can log values of various types, as long as they have `AsString` function defined
|
||||||
|
/// You can chain values in single `LOG` using `operator<<`:
|
||||||
|
|
||||||
|
int x = 123;
|
||||||
|
LOG("Value of x is " << x);
|
||||||
|
|
||||||
|
/// As it is very common to log a value of single variable, `DUMP` macro provides a useful
|
||||||
|
/// shortcut, creating a log line with the variable name and value:
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// To get the value in hexadecimal code, you can use `LOGHEX` / `DUMPHEX`
|
||||||
|
|
||||||
|
DUMPHEX(x);
|
||||||
|
String h = "foo";
|
||||||
|
DUMPHEX(h);
|
||||||
|
|
||||||
|
/// To log the value of a container (or generic Range), you can either use normal
|
||||||
|
/// `LOG` / `DUMP`:
|
||||||
|
|
||||||
|
Vector<int> v = { 1, 2, 3 };
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// or you can use DUMPC for multi-line output:
|
||||||
|
|
||||||
|
DUMPC(v);
|
||||||
|
|
||||||
|
/// For maps, use DUMPM:
|
||||||
|
|
||||||
|
VectorMap<int, String> map = { { 1, "one" }, { 2, "two" } };
|
||||||
|
|
||||||
|
DUMP(map);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMPM(map);
|
||||||
|
|
||||||
|
/// All normal `LOG`s are removed in release mode. If you need to log things in release mode,
|
||||||
|
/// you need to use `LOG`/`DUMP` variant with '`R`' prefix (`RLOG`, `RDUMP`, `RDUMPHEX`...):
|
||||||
|
|
||||||
|
RLOG("This will be logged in release mode too!");
|
||||||
|
|
||||||
|
/// Sort of opposite situation is when adding temporary `LOG`s to the code for debugging. In
|
||||||
|
/// that case, '`D`' prefixed variants (`DLOG`, `DDUMP`, `DDUMPHEX`...) are handy - these cause
|
||||||
|
/// compile error in release mode, so will not get forgotten in the code past the release:
|
||||||
|
|
||||||
|
DLOG("This would not compile in release mode.");
|
||||||
|
|
||||||
|
/// The last flavor of `LOG` you can encounter while reading U++ sources is the one prefixed
|
||||||
|
/// with '`L`'. This one is not actually defined in U++ library and is just a convention. On
|
||||||
|
/// the start of file, there is usually something like:
|
||||||
|
|
||||||
|
#define LLOG(x) // DLOG(x)
|
||||||
|
|
||||||
|
/// and by uncommenting the body part, you can activate the logging in that particular file.
|
||||||
|
|
||||||
|
/// While logging to .log file is default, there are various ways how to affect logging,
|
||||||
|
/// for example following line adjusts logging to output the log both to the console and
|
||||||
|
/// .log file:
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
StdLogSetup(LOG_COUT|LOG_FILE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
138
tutorial/CoreTutorial/Map.cpp
Normal file
138
tutorial/CoreTutorial/Map.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Map()
|
||||||
|
{
|
||||||
|
/// .`VectorMap`, `ArrayMap`
|
||||||
|
|
||||||
|
/// `VectorMap` is nothing else than a simple composition of `Index` of keys and `Vector`
|
||||||
|
/// of values. You can use `Add` methods to put elements into the `VectorMap`:
|
||||||
|
|
||||||
|
struct Person : Moveable<Person> {
|
||||||
|
String name;
|
||||||
|
String surname;
|
||||||
|
|
||||||
|
String ToString() const { return String() << name << ' ' << surname; }
|
||||||
|
|
||||||
|
Person(String name, String surname) : name(name), surname(surname) {}
|
||||||
|
Person() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
VectorMap<String, Person> m;
|
||||||
|
|
||||||
|
m.Add("1", Person("John", "Smith"));
|
||||||
|
m.Add("2", Person("Carl", "Engles"));
|
||||||
|
|
||||||
|
Person& p = m.Add("3");
|
||||||
|
p.name = "Paul";
|
||||||
|
p.surname = "Carpenter";
|
||||||
|
|
||||||
|
DUMP(m);
|
||||||
|
|
||||||
|
/// `VectorMap` provides read-only access to its `Index` of keys and read-write access to its
|
||||||
|
/// `Vector` of values:
|
||||||
|
|
||||||
|
DUMP(m.GetKeys());
|
||||||
|
DUMP(m.GetValues());
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
m.GetValues()[2].name = "Peter";
|
||||||
|
|
||||||
|
DUMP(m);
|
||||||
|
|
||||||
|
/// You can use indices to iterate `VectorMap` contents:
|
||||||
|
|
||||||
|
for(int i = 0; i < m.GetCount(); i++)
|
||||||
|
LOG(m.GetKey(i) << ": " << m[i]);
|
||||||
|
|
||||||
|
/// Standard `begin` / `end` pair for `VectorMap` is the range of just values (internal Vector)
|
||||||
|
/// - it corresponds with `operator[]` returning values:
|
||||||
|
|
||||||
|
for(const auto& p : m)
|
||||||
|
DUMP(p);
|
||||||
|
|
||||||
|
/// To iterate through keys, you can use `begin`/`end` of internal `Index`:
|
||||||
|
|
||||||
|
for(const auto& k : m.GetKeys())
|
||||||
|
DUMP(p);
|
||||||
|
|
||||||
|
/// Alternatively, it is possible to create 'projection range' of VectorMap that provides
|
||||||
|
/// convenient key/value iteration, using `operator~` (note that is also removes 'unliked'
|
||||||
|
/// items, see later):
|
||||||
|
|
||||||
|
for(const auto& e : ~m) {
|
||||||
|
DUMP(e.key);
|
||||||
|
DUMP(e.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// You can use Find method to retrieve position of element with required key:
|
||||||
|
|
||||||
|
DUMP(m.Find("2"));
|
||||||
|
|
||||||
|
/// or Get method to retrieve corresponding value:
|
||||||
|
|
||||||
|
DUMP(m.Get("2"));
|
||||||
|
|
||||||
|
/// Passing key not present in `VectorMap` as `Get` parameter is undefined behavior (ASSERT
|
||||||
|
/// fails in debug mode), but there exists two parameter version of `Get` that returns second
|
||||||
|
/// parameter if the key is not found in VectorMap:
|
||||||
|
|
||||||
|
DUMP(m.Get("33", Person("unknown", "person")));
|
||||||
|
|
||||||
|
/// As with `Index`, you can use `Unlink` to make elements invisible for Find operations:
|
||||||
|
|
||||||
|
m.Unlink(1);
|
||||||
|
DUMP(m.Find("2"));
|
||||||
|
|
||||||
|
/// `SetKey` changes the key of the element:
|
||||||
|
|
||||||
|
m.SetKey(1, "33");
|
||||||
|
DUMP(m.Get("33", Person("unknown", "person")));
|
||||||
|
|
||||||
|
/// If there are more elements with the same key in `VectorMap`, you can iterate them using
|
||||||
|
/// `FindNext` method:
|
||||||
|
|
||||||
|
m.Add("33", Person("Peter", "Pan"));
|
||||||
|
|
||||||
|
int q = m.Find("33");
|
||||||
|
while(q >= 0) {
|
||||||
|
DUMP(m[q]);
|
||||||
|
q = m.FindNext(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlinked positions can be 'reused' using Put method:
|
||||||
|
|
||||||
|
m.UnlinkKey("33");
|
||||||
|
m.Put("22", Person("Ali", "Baba"));
|
||||||
|
m.Put("44", Person("Ivan", "Wilks"));
|
||||||
|
|
||||||
|
DUMP(m);
|
||||||
|
|
||||||
|
/// `PickValues` / `PickIndex` / `PickKeys` / pick internal `Vector` / `Index` / `Vector`
|
||||||
|
/// of `Index`:
|
||||||
|
|
||||||
|
Vector<Person> ps = m.PickValues();
|
||||||
|
Vector<String> ks = m.PickKeys();
|
||||||
|
|
||||||
|
DUMP(ps);
|
||||||
|
DUMP(ks);
|
||||||
|
DUMP(m);
|
||||||
|
|
||||||
|
/// `VectorMap` pick constructor to create map by picking:
|
||||||
|
|
||||||
|
ks[0] = "Changed key";
|
||||||
|
|
||||||
|
m = VectorMap<String, Person>(pick(ks), pick(ps));
|
||||||
|
|
||||||
|
DUMP(m);
|
||||||
|
|
||||||
|
/// `ArrayMap` is composition of Index and Array, for cases where Array is better fit for
|
||||||
|
/// value type (e.g. they are polymorphic):
|
||||||
|
|
||||||
|
ArrayMap<String, Person> am;
|
||||||
|
am.Create<Person>("key", "new", "person");
|
||||||
|
|
||||||
|
DUMP(am);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
44
tutorial/CoreTutorial/Null.cpp
Normal file
44
tutorial/CoreTutorial/Null.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void NullTutorial()
|
||||||
|
{
|
||||||
|
/// .`Null`
|
||||||
|
|
||||||
|
/// U++ defines a special `Null` constant to represent an empty value. This constant is
|
||||||
|
/// convertible to many value types including primitive types `double`, `int` and `int64`
|
||||||
|
/// (defined as lowest number the type can represent). If type supports ordering (<, >),
|
||||||
|
/// all values of the type are greater than Null value. To test whether a value is empty,
|
||||||
|
/// use `IsNull` function.
|
||||||
|
|
||||||
|
int x = Null;
|
||||||
|
int y = 120;
|
||||||
|
Date d = Null;
|
||||||
|
Date e = GetSysDate();
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
DUMP(y);
|
||||||
|
DUMP(d);
|
||||||
|
DUMP(e > d);
|
||||||
|
|
||||||
|
/// Null is the only instance of `Nuller` type. Assigning `Null` to
|
||||||
|
/// primitive types is achieved by cast operators of `Nuller`, other types can do it using
|
||||||
|
/// constructor from `Nuller`.
|
||||||
|
|
||||||
|
/// As a special case, if Value contains Null, it is convertible to any value type that can contain Null:
|
||||||
|
|
||||||
|
Value v = x; // x is int
|
||||||
|
e = v; // e is Date, but v is Null, so Null is assigned to e
|
||||||
|
|
||||||
|
DUMP(IsNull(e));
|
||||||
|
|
||||||
|
/// Function `Nvl` is U++ analog of well known SQL function coalesce (ifnull, Nvl), which
|
||||||
|
/// returns the first non-null argument (or `Null` if all are `Null`).
|
||||||
|
|
||||||
|
int a = Null;
|
||||||
|
int b = 123;
|
||||||
|
int c = 1;
|
||||||
|
|
||||||
|
DUMP(Nvl(a, b, c));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
52
tutorial/CoreTutorial/One.cpp
Normal file
52
tutorial/CoreTutorial/One.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void OneTutorial()
|
||||||
|
{
|
||||||
|
/// .`One`
|
||||||
|
|
||||||
|
/// `One` is a container that can store none or one element of T or derived from T. It is
|
||||||
|
/// functionally quite similar to `std::unique_ptr`, but has some convenient features.
|
||||||
|
|
||||||
|
struct Base {
|
||||||
|
virtual String Get() = 0;
|
||||||
|
virtual ~Base() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived1 : Base {
|
||||||
|
virtual String Get() { return "Derived1"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived2 : Base {
|
||||||
|
virtual String Get() { return "Derived2"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
One<Base> s;
|
||||||
|
|
||||||
|
/// `operator bool` of one returns true if it contains an element:
|
||||||
|
|
||||||
|
DUMP((bool)s);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
s.Create<Derived1>();
|
||||||
|
DUMP((bool)s);
|
||||||
|
DUMP(s->Get());
|
||||||
|
|
||||||
|
/// You can use `Is` to check if certain type is currently stored in `One`:
|
||||||
|
|
||||||
|
DUMP(s.Is<Derived1>());
|
||||||
|
DUMP(s.Is<Base>());
|
||||||
|
DUMP(s.Is<Derived2>());
|
||||||
|
|
||||||
|
/// To get a pointer to the contained instance, use `operator~`:
|
||||||
|
|
||||||
|
Base *b = ~s;
|
||||||
|
DUMP(b->Get());
|
||||||
|
|
||||||
|
/// Clear method removes the element from One:
|
||||||
|
|
||||||
|
s.Clear();
|
||||||
|
DUMP((bool)s);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
55
tutorial/CoreTutorial/PolyArray.cpp
Normal file
55
tutorial/CoreTutorial/PolyArray.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void PolyArray()
|
||||||
|
{
|
||||||
|
/// .Polymorphic `Array`
|
||||||
|
|
||||||
|
/// `Array` can even be used for storing polymorphic elements:
|
||||||
|
|
||||||
|
struct Number {
|
||||||
|
virtual double Get() const = 0;
|
||||||
|
String ToString() const { return AsString(Get()); }
|
||||||
|
virtual ~Number() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Integer : public Number {
|
||||||
|
int n;
|
||||||
|
virtual double Get() const { return n; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Double : public Number {
|
||||||
|
double n;
|
||||||
|
virtual double Get() const { return n; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// To add such derived types to `Array`, you can best use in-place creation with `Create`
|
||||||
|
/// method:
|
||||||
|
|
||||||
|
Array<Number> num;
|
||||||
|
num.Create<Double>().n = 15.5;
|
||||||
|
num.Create<Integer>().n = 3;
|
||||||
|
|
||||||
|
DUMP(num);
|
||||||
|
|
||||||
|
/// Alternatively, you can use `Add(T *)` method and provide a pointer to the newly created
|
||||||
|
/// instance on the heap (`Add` returns a reference to the instance):
|
||||||
|
|
||||||
|
Double *nd = new Double;
|
||||||
|
nd->n = 1.1;
|
||||||
|
num.Add(nd);
|
||||||
|
|
||||||
|
DUMP(num);
|
||||||
|
|
||||||
|
/// Array takes ownership of heap object and deletes it as appropriate. We recommend to use
|
||||||
|
/// this variant only if in-place creation with `Create` is not possible.
|
||||||
|
|
||||||
|
/// It is OK do directly apply U++ algorithms on `Array` (the most stringent requirement of
|
||||||
|
/// any of basic algorithms is that there is `IterSwap` provided for container iterators
|
||||||
|
/// and that is specialized for `Array` iterators):
|
||||||
|
|
||||||
|
Sort(num, [](const Number& a, const Number& b) { return a.Get() < b.Get(); });
|
||||||
|
|
||||||
|
DUMP(num);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
6
tutorial/CoreTutorial/Range.cpp
Normal file
6
tutorial/CoreTutorial/Range.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Ranges()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
69
tutorial/CoreTutorial/Ranges.cpp
Normal file
69
tutorial/CoreTutorial/Ranges.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Range()
|
||||||
|
{
|
||||||
|
/// .Range
|
||||||
|
|
||||||
|
/// Unlike STL, which interface algorithms with data using `begin` / `end` pair, U++ algorithms
|
||||||
|
/// usually work on %Ranges%. Range is an object that has `begin` / `end` methods providing
|
||||||
|
/// random access to elements (all U++ containers are random access), `operator[]` and
|
||||||
|
/// `GetCount` method.
|
||||||
|
|
||||||
|
/// Obviously, U++ containers are ranges:
|
||||||
|
|
||||||
|
Vector<int> x = { 1, 2, 3, 4, 5, 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
DUMP(FindIndex(x, 2)); // FindIndex is a trivial algorithm that does linear search
|
||||||
|
|
||||||
|
/// If you want the algorithm to run on part of container only, you can use `SubRange`
|
||||||
|
/// instance:
|
||||||
|
|
||||||
|
DUMP(SubRange(x, 3, 6));
|
||||||
|
DUMP(FindIndex(SubRange(x, 3, 6), 4));
|
||||||
|
|
||||||
|
/// As a side-job, SubRange can also be created from 'begin' / 'end' pair, thus e.g.
|
||||||
|
/// allowing algorithms to work on C arrays:
|
||||||
|
|
||||||
|
int a[] = { 1, 22, 4, 2, 8 };
|
||||||
|
|
||||||
|
auto ar = SubRange(std::begin(a), std::end(a));
|
||||||
|
|
||||||
|
DUMP(ar);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
Sort(ar);
|
||||||
|
DUMP(ar);
|
||||||
|
|
||||||
|
/// There are some macro aliases that make type management of ranges easier:
|
||||||
|
|
||||||
|
DUMP(typeid(ValueTypeOf<decltype(x)>).name());
|
||||||
|
DUMP(typeid(ValueTypeOf<decltype(SubRange(x, 1, 1))>).name());
|
||||||
|
DUMP(typeid(IteratorOf<decltype(x)>).name());
|
||||||
|
DUMP(typeid(ConstIteratorOf<decltype(SubRange(x, 1, 1))>).name());
|
||||||
|
DUMP(typeid(SubRangeOf<Vector<int>>).name());
|
||||||
|
|
||||||
|
/// While containers themselves and SubRange are the two most common range types, U++ has two
|
||||||
|
/// special ranges. `ConstRange` simply provides the range of single value:
|
||||||
|
|
||||||
|
DUMP(ConstRange(1, 10));
|
||||||
|
|
||||||
|
/// `ViewRange` picks a source range and `Vector` of integer indices a provides a view of
|
||||||
|
/// source range through this `Vector`:
|
||||||
|
|
||||||
|
Vector<int> h{ 2, 4, 0 };
|
||||||
|
|
||||||
|
DUMP(ViewRange(x, clone(h)));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
Sort(ViewRange(x, clone(h)));
|
||||||
|
DUMP(ViewRange(x, clone(h)));
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// Finally `FilterRange` creates a subrange of elements satisfying certain condition:
|
||||||
|
|
||||||
|
DUMP(FilterRange(x, [](int x) { return x > 3; }));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
61
tutorial/CoreTutorial/Sort.cpp
Normal file
61
tutorial/CoreTutorial/Sort.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Sorting()
|
||||||
|
{
|
||||||
|
/// .Sorting
|
||||||
|
|
||||||
|
/// Unsurprisingly, `Sort` function sorts a range. You can specify sorting predicate,
|
||||||
|
/// default is `operator<`:
|
||||||
|
|
||||||
|
Vector<String> x { "1", "2", "10" };
|
||||||
|
|
||||||
|
Sort(x);
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
Sort(x, [](const String& a, const String& b) { return atoi(a) < atoi(b); });
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
|
||||||
|
/// `IndexSort` is sort variant that is able to sort two ranges (like `Vector` or `Array`)
|
||||||
|
/// of the same size, based on values in the first range:
|
||||||
|
|
||||||
|
Vector<int> a { 5, 10, 2, 9, 7, 3 };
|
||||||
|
Vector<String> b { "five", "ten", "two", "nine", "seven", "three" };
|
||||||
|
|
||||||
|
IndexSort(a, b);
|
||||||
|
|
||||||
|
DUMP(a);
|
||||||
|
DUMP(b);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
IndexSort(b, a);
|
||||||
|
|
||||||
|
DUMP(a);
|
||||||
|
DUMP(b);
|
||||||
|
|
||||||
|
/// There are also `IndexSort2` and `IndexSort3` variants that sort 2 or 3 dependent ranges.
|
||||||
|
|
||||||
|
/// Sometimes, instead of sorting items in the range, it is useful to know the order of
|
||||||
|
/// items as sorted, using `GetSortOrder`:
|
||||||
|
|
||||||
|
Vector<int> o = GetSortOrder(a);
|
||||||
|
|
||||||
|
DUMP(o);
|
||||||
|
|
||||||
|
/// Normal `Sort` is not stable - equal items can appear in sorted range in random order.
|
||||||
|
/// If maintaining original order of equal items is important, use `StableSort` variant
|
||||||
|
/// (with performance penalty):
|
||||||
|
|
||||||
|
Vector<Point> t { Point(10, 10), Point(7, 1), Point(7, 2), Point(7, 3), Point(1, 0) };
|
||||||
|
StableSort(t, [](const Point& a, const Point& b) { return a.x < b.x; });
|
||||||
|
|
||||||
|
DUMP(t);
|
||||||
|
|
||||||
|
/// All sorting algorithms have they 'Stable' variant, so there is `StableIndexSort`,
|
||||||
|
/// `GetStableSortOrder` etc...
|
||||||
|
}
|
||||||
37
tutorial/CoreTutorial/SortedMap.cpp
Normal file
37
tutorial/CoreTutorial/SortedMap.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void SortedMap()
|
||||||
|
{
|
||||||
|
/// .`SortedIndex`, `SortedVectorMap`, `SortedArrayMap`
|
||||||
|
|
||||||
|
/// `SortedIndex` is similar to regular `Index`, but keeps its elements in sorted order
|
||||||
|
/// (sorting predicate is a template parameter, defaults to `StdLess`). Implementation is
|
||||||
|
/// using `InVector`, so it works fine even with very large number of elements (performance
|
||||||
|
/// is similar to tree based `std::set`). Unlike `Index`, `SortedIndex` provides
|
||||||
|
/// lower/upper bounds searches, so it allows range search.
|
||||||
|
|
||||||
|
SortedIndex<int> x;
|
||||||
|
x.Add(5);
|
||||||
|
x.Add(3);
|
||||||
|
x.Add(7);
|
||||||
|
x.Add(1);
|
||||||
|
|
||||||
|
DUMPC(x);
|
||||||
|
DUMP(x.Find(3));
|
||||||
|
DUMP(x.Find(3));
|
||||||
|
DUMP(x.FindLowerBound(3));
|
||||||
|
DUMP(x.FindUpperBound(6));
|
||||||
|
|
||||||
|
/// `SortedVectorMap` and `SortedArrayMap` are then `SortedIndex` based equivalents to
|
||||||
|
/// `VectorMap`/`ArrayMap`:
|
||||||
|
|
||||||
|
SortedVectorMap<String, int> m;
|
||||||
|
m.Add("zulu", 11);
|
||||||
|
m.Add("frank", 12);
|
||||||
|
m.Add("alfa", 13);
|
||||||
|
|
||||||
|
DUMPM(m);
|
||||||
|
DUMP(m.Get("zulu"));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
133
tutorial/CoreTutorial/String.cpp
Normal file
133
tutorial/CoreTutorial/String.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void StringTutorial()
|
||||||
|
{
|
||||||
|
|
||||||
|
/// .String
|
||||||
|
|
||||||
|
/// String is a value type useful for storing text or binary data.
|
||||||
|
|
||||||
|
String a = "Hello";
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// You camn concatenate with another String or literal:
|
||||||
|
|
||||||
|
a = a + " world";
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// Or single character or specified number of characters from another `String` or literal:
|
||||||
|
|
||||||
|
a.Cat('!');
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
a.Cat("ABCDEFGHIJKLM", 3);
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// `Clear` method empties the String:
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// You can use `operator<<` to append to existing `String`. Non-string values are
|
||||||
|
/// converted to appropriate `String` representation (using standard function `AsString`,
|
||||||
|
/// whose default template definition calls `ToString` method for value):
|
||||||
|
|
||||||
|
for(int i = 0; i < 10; i++)
|
||||||
|
a << i << ", ";
|
||||||
|
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// Sometimes is is useful to use `operator<<` to produce a temporary `String` value (e.g. as
|
||||||
|
/// real argument to function call):
|
||||||
|
|
||||||
|
String b = String() << "Number is " << 123 << ".";
|
||||||
|
|
||||||
|
DUMP(b);
|
||||||
|
|
||||||
|
|
||||||
|
/// String provides many various methods for obtaining character count, inserting
|
||||||
|
/// characters into `String` or removing them:
|
||||||
|
|
||||||
|
a = "0123456789";
|
||||||
|
|
||||||
|
DUMP(a.GetCount());
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(a.GetLength()); // GetLength is a synonym of GetCount
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
a.Insert(6, "<inserted>");
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
a.Remove(2, 2);
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// as well as searching and comparing methods:
|
||||||
|
|
||||||
|
DUMP(a.Find('e'));
|
||||||
|
DUMP(a.ReverseFind('e'));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(a.Find("ins"));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
DUMP(a.StartsWith("ABC"));
|
||||||
|
DUMP(a.StartsWith("01"));
|
||||||
|
DUMP(a.EndsWith("89"));
|
||||||
|
|
||||||
|
/// You can get slice of String using Mid method; with single parameter it provides slice
|
||||||
|
/// to the end of String:
|
||||||
|
|
||||||
|
DUMP(a.Mid(3, 3));
|
||||||
|
DUMP(a.Mid(3));
|
||||||
|
|
||||||
|
/// You can also trim the length of String using Trim (this is faster than using any other
|
||||||
|
/// method):
|
||||||
|
|
||||||
|
a.Trim(4);
|
||||||
|
DUMP(a);
|
||||||
|
|
||||||
|
/// You can obtain integer values of individual characters using operator[]:
|
||||||
|
|
||||||
|
DUMP(a[0]);
|
||||||
|
|
||||||
|
/// or the value of first character using operator* (note that if `GetCount() == 0`, this
|
||||||
|
/// will return zero terminator):
|
||||||
|
|
||||||
|
DUMP(*a);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
|
||||||
|
DUMP(*a);
|
||||||
|
|
||||||
|
/// `String` has implicit cast to zero terminated `const char *ptr` (only valid as long as
|
||||||
|
/// `String` does not mutate:
|
||||||
|
|
||||||
|
a = "1234";
|
||||||
|
const char *s = a;
|
||||||
|
while(*s)
|
||||||
|
LOG(*s++);
|
||||||
|
|
||||||
|
/// `String` also has standard `begin` `end` methods, which e.g. allows for C++11 `for`:
|
||||||
|
|
||||||
|
for(char ch : a)
|
||||||
|
LOG(ch);
|
||||||
|
|
||||||
|
/// It is absolutely OK and common to use String for storing binary data, including zeroes:
|
||||||
|
|
||||||
|
a.Cat(0);
|
||||||
|
|
||||||
|
DUMPHEX(a);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
61
tutorial/CoreTutorial/StringBuffer.cpp
Normal file
61
tutorial/CoreTutorial/StringBuffer.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void CApiFunction(char *c)
|
||||||
|
{
|
||||||
|
strcpy(c, "Hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
void StringBufferTutorial()
|
||||||
|
{
|
||||||
|
|
||||||
|
/// .StringBuffer
|
||||||
|
|
||||||
|
/// If you need a direct write access to `String`'s C-string character buffer, you can use
|
||||||
|
/// complementary `StringBuffer` class. One of reasons to do so is when you have to deal
|
||||||
|
/// with some C-API functions that expects to write directly to `char *` and you would like
|
||||||
|
/// that result converted to the `String`:
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void CApiFunction(char *c)
|
||||||
|
{
|
||||||
|
strcpy(c, "Hello");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
StringBuffer b;
|
||||||
|
b.SetLength(200);
|
||||||
|
CApiFunction(b);
|
||||||
|
b.Strlen();
|
||||||
|
String x = b;
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// In this case, `SetLength` creates a C array of 200 characters. You can then call C-API
|
||||||
|
/// function. Later you set the real length using `Strlen` - this function performs strlen
|
||||||
|
/// of buffer and sets the length accordingly. Later you simply assign the `StringBuffer`
|
||||||
|
/// to `String`. Note that for performance reasons, this operation clears the
|
||||||
|
/// `StringBuffer` content (operation is fast and does not depend on the number of
|
||||||
|
/// characters).
|
||||||
|
|
||||||
|
/// Another usage scenario of StringBuffer is altering existing String:
|
||||||
|
|
||||||
|
b = x;
|
||||||
|
b[1] = 'a';
|
||||||
|
x = b;
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// Similar to assigning StringBuffer to String, assigning String to StringBuffer clears
|
||||||
|
/// the source String.
|
||||||
|
|
||||||
|
/// StringBuffer also provides appending operations:
|
||||||
|
|
||||||
|
b = x;
|
||||||
|
b.Cat('!');
|
||||||
|
x = b;
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// Note that sometimes when creating some String from a lot of single characters, using
|
||||||
|
/// StringBuffer for the operation is slightly faster then using String directly.
|
||||||
|
}
|
||||||
25
tutorial/CoreTutorial/ToDo.cpp
Normal file
25
tutorial/CoreTutorial/ToDo.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
return CombineCompare(a.NDX, b.NDX)(a.V_TEXT, b.V_TEXT)(a.V_NUMBER)(b.V_NUMBER);
|
||||||
|
|
||||||
|
Vector<int> id = clone(_id);
|
||||||
|
|
||||||
|
SplitTo(ln.Mid(4), '@', path, ln);
|
||||||
|
|
||||||
|
|
||||||
|
CONSOLE_APP_MAIN
|
||||||
|
{
|
||||||
|
Event<const Vector<int>&> h = [](const Vector<int>& x) { DUMP(x); };
|
||||||
|
|
||||||
|
Event<> ev;
|
||||||
|
|
||||||
|
{
|
||||||
|
Vector<int> x = { 1, 2 };
|
||||||
|
ev = [=, x = pick(x)] { h(x); };
|
||||||
|
}
|
||||||
|
|
||||||
|
ev();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
65
tutorial/CoreTutorial/Transfer.cpp
Normal file
65
tutorial/CoreTutorial/Transfer.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void Transfer()
|
||||||
|
{
|
||||||
|
/// .Transfer issues
|
||||||
|
|
||||||
|
/// Often you need to pass content of one container to another of the same type. U++
|
||||||
|
/// containers always support ^topic://Core/srcdoc/pick_$en-us:pick semantics^ (synonym of
|
||||||
|
/// std::move), and, depending on type stored, also might support
|
||||||
|
/// ^topic://Core/srcdoc/pick_$en-us:clone semantics^. When transferring the value, you
|
||||||
|
/// have to explicitly specify which one to use:
|
||||||
|
|
||||||
|
Vector<int> v{ 1, 2 };
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
Vector<int> v1 = pick(v);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
DUMP(v1);
|
||||||
|
|
||||||
|
/// now source `Vector` `v` is empty, as elements were 'picked' to `v1`.
|
||||||
|
|
||||||
|
/// If you really need to preserve value of source (and elements support deep copy
|
||||||
|
/// operation), you can use `clone`:
|
||||||
|
|
||||||
|
v = clone(v1);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
DUMP(v1);
|
||||||
|
|
||||||
|
/// The requirement of explicit `clone` has the advantage of avoiding unexpected deep
|
||||||
|
/// copies. For example:
|
||||||
|
|
||||||
|
Vector<Vector<int>> x;
|
||||||
|
x.Add() << 1 << 2 << 3;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
for(auto i : x) { LOG(i); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// results in run-time error, whereas the equivalent code with `std::vector` compiles but
|
||||||
|
/// silently performs deep copy for each iteration:
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
std::vector<std::vector<int>> sv;
|
||||||
|
sv.push_back({1, 2, 3});
|
||||||
|
for(auto i : sv) // invokes std::vector<int> copy constructor
|
||||||
|
for(auto j : i)
|
||||||
|
DUMP(j);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// That said, in certain cases it is simpler to have default copy instead of explicit
|
||||||
|
/// `clone`. You can easily achieve that using `WithDeepCopy` template:
|
||||||
|
|
||||||
|
WithDeepCopy<Vector<int>> v2;
|
||||||
|
|
||||||
|
v2 = v;
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
DUMP(v2);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
110
tutorial/CoreTutorial/Tuple.cpp
Normal file
110
tutorial/CoreTutorial/Tuple.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void TupleTutorial()
|
||||||
|
{
|
||||||
|
/// . Tuples
|
||||||
|
|
||||||
|
/// Template class `Tuple` allows combining 2-4 values with
|
||||||
|
/// different types. These are principally similar to `std::tuple`, with some advantages.
|
||||||
|
/// Unlike `std::tuple`, individual elements are directly accessible as member variables
|
||||||
|
/// `a`..`d`, `Tuple` supports persistent storage patterns (`Serialize`, `Jsonize`, `Xmlize`), hash
|
||||||
|
/// code (`GetHashValue`), conversion to `String` and Value conversions.
|
||||||
|
|
||||||
|
/// To create a `Tuple` value, you can use the `MakeTuple` function.
|
||||||
|
|
||||||
|
Tuple<int, String, String> x = MakeTuple(12, "hello", "world");
|
||||||
|
|
||||||
|
/// Individual values are accessible as members `a` .. `d`:
|
||||||
|
|
||||||
|
DUMP(x.a);
|
||||||
|
DUMP(x.b);
|
||||||
|
DUMP(x.c);
|
||||||
|
|
||||||
|
/// Or using `Get`:
|
||||||
|
|
||||||
|
DUMP(x.Get<1>());
|
||||||
|
DUMP(x.Get<int>());
|
||||||
|
|
||||||
|
/// As long as all individual types have conversion to `String` (`AsString`), the tuple also
|
||||||
|
/// has such conversion and thus can e.g. be easily logged:
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// As long as individual types have defined `GetHashValue`, so does `Tuple`:
|
||||||
|
|
||||||
|
DUMP(GetHashValue(x));
|
||||||
|
|
||||||
|
/// As long as individual types have defined `operator==`, `Tuple` has defined `operator==`
|
||||||
|
/// and `operator!=`:
|
||||||
|
|
||||||
|
Tuple<int, String, String> y = x;
|
||||||
|
DUMP(x == y);
|
||||||
|
DUMP(x != y);
|
||||||
|
y.a++;
|
||||||
|
DUMP(x == y);
|
||||||
|
DUMP(x != y);
|
||||||
|
|
||||||
|
/// As long as all individual types have defined `SgnCompare`,
|
||||||
|
/// Tuple has SgnCompare, Compare method and operators <, <=, >, >=:
|
||||||
|
|
||||||
|
DUMP(x.Compare(y));
|
||||||
|
DUMP(SgnCompare(x, y));
|
||||||
|
DUMP(x < y);
|
||||||
|
|
||||||
|
/// GetCount returns the width of `Tuple`:
|
||||||
|
|
||||||
|
DUMP(x.GetCount());
|
||||||
|
|
||||||
|
/// Elements that are directly convertible with `Value` can be 'Get'/'Set':
|
||||||
|
|
||||||
|
for(int i = 0; i < x.GetCount(); i++)
|
||||||
|
DUMP(x.Get(i));
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
x.Set(1, "Hi");
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// As long as all individual types are convertible with `Value`, you can convert Tuple to
|
||||||
|
/// `ValueArray` and back:
|
||||||
|
|
||||||
|
ValueArray va = x.GetArray();
|
||||||
|
DUMP(va);
|
||||||
|
|
||||||
|
va.Set(2, "Joe");
|
||||||
|
x.SetArray(va);
|
||||||
|
|
||||||
|
/// It is OK to assign `Tuple` to `Tuple` with different individual types, as long as types
|
||||||
|
/// are directly convertible:
|
||||||
|
|
||||||
|
Tuple<double, String, String> d = x;
|
||||||
|
DUMP(d);
|
||||||
|
|
||||||
|
/// Tie can be used to assign tuple to l-values:
|
||||||
|
|
||||||
|
int i;
|
||||||
|
String s1, s2;
|
||||||
|
|
||||||
|
Tie(i, s1, s2) = x;
|
||||||
|
|
||||||
|
DUMP(i);
|
||||||
|
DUMP(s1);
|
||||||
|
DUMP(s2);
|
||||||
|
|
||||||
|
/// U++ Tuples are carefully designed as POD type, which allows POD arrays to be intialized
|
||||||
|
/// with classic C style:
|
||||||
|
|
||||||
|
static Tuple2<int, const char *> map[] = {
|
||||||
|
{ 1, "one" },
|
||||||
|
{ 2, "one" },
|
||||||
|
{ 3, "one" },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Simple FindTuple template function is provided to search for tuple based on the first
|
||||||
|
/// value (`a`) (linear O(n) search):
|
||||||
|
|
||||||
|
DUMP(FindTuple(map, __countof(map), 3)->b);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
193
tutorial/CoreTutorial/Tutorial.cpp
Normal file
193
tutorial/CoreTutorial/Tutorial.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
#include "RichEdit/RichEdit.h"
|
||||||
|
|
||||||
|
using namespace Upp;
|
||||||
|
|
||||||
|
String out;
|
||||||
|
|
||||||
|
int major = 0;
|
||||||
|
int minor = 0;
|
||||||
|
String qtf =
|
||||||
|
"[ $$0,0#00000000000000000000000000000000:Default]"
|
||||||
|
"[a83;*R6 $$1,3#31310162474203024125188417583966:caption]"
|
||||||
|
"[H4;b83;*4 $$2,3#07864147445237544204411237157677:title]"
|
||||||
|
"[b42;a42 $$3,3#45413000475342174754091244180557:text]"
|
||||||
|
"[l100;C@5*;1 $$4,4#20902679421464641399138805415013:code]"
|
||||||
|
"[l100;*C$7;2 $$5,5#07531550463529505371228428965313:log]"
|
||||||
|
"[b73;*3 $$2,3#07864147445237544204111237153677:subtitle]"
|
||||||
|
;
|
||||||
|
|
||||||
|
#define OUT(x) out << x << '\n';
|
||||||
|
|
||||||
|
void FlushDoc(String& docblock)
|
||||||
|
{
|
||||||
|
docblock = TrimBoth(docblock);
|
||||||
|
if(docblock.GetCount() == 0)
|
||||||
|
return;
|
||||||
|
OUT("============= DOC");
|
||||||
|
OUT(docblock);
|
||||||
|
|
||||||
|
bool title = false;
|
||||||
|
|
||||||
|
String style = "[s3;";
|
||||||
|
if(docblock.StartsWith("..")) {
|
||||||
|
docblock = AsString(major) + "." + AsString(++minor) + ' ' + TrimBoth(docblock.Mid(2));
|
||||||
|
style = "[s7;";
|
||||||
|
title = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(docblock.StartsWith(".")) {
|
||||||
|
docblock = AsString(++major) + "." + ' ' + TrimBoth(docblock.Mid(1));
|
||||||
|
minor = 0;
|
||||||
|
style = "[s2;";
|
||||||
|
title = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
qtf << style << " ";
|
||||||
|
|
||||||
|
const char *s = docblock;
|
||||||
|
|
||||||
|
while(*s)
|
||||||
|
if((s == ~docblock || findarg(s[-1], '(', ' ', '\'', '\"') >= 0) && findarg(s[0], '*', '%', '_', '`', '^') >= 0 && s[1] && s[1] != ' ') {
|
||||||
|
int c = *s++;
|
||||||
|
const char *b = s;
|
||||||
|
const char *dc = NULL;
|
||||||
|
while(*s && *s != c) {
|
||||||
|
if(*s == ':')
|
||||||
|
dc = s;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
if(c == '^') {
|
||||||
|
qtf << "[^";
|
||||||
|
if(dc) {
|
||||||
|
qtf.Cat(b, dc);
|
||||||
|
b = dc + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qtf.Cat(b, s);
|
||||||
|
qtf << "^ ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qtf << decode(c, '*', "[* ", '%', "[/ ", '_', "[_ ", '`', title ? "[C@5 " : "[C@5* ", "");
|
||||||
|
while(b < s)
|
||||||
|
qtf << '`' << *b++;
|
||||||
|
qtf << "]";
|
||||||
|
if(*s) s++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qtf << '`' << *s++;
|
||||||
|
|
||||||
|
qtf << "&]";
|
||||||
|
docblock.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushLog(Vector<String>& log)
|
||||||
|
{
|
||||||
|
if(log.GetCount() == 0)
|
||||||
|
return;
|
||||||
|
OUT("============ LOG");
|
||||||
|
OUT(Join(log, "\r\n"));
|
||||||
|
|
||||||
|
qtf << "[s5; \1" << Join(log, "\n") << "\1&]&";
|
||||||
|
log.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushCode(Vector<String>& code)
|
||||||
|
{
|
||||||
|
while(code.GetCount() && TrimBoth(code[0]).GetCount() == 0)
|
||||||
|
code.Remove(0);
|
||||||
|
while(code.GetCount() && TrimBoth(code.Top()).GetCount() == 0)
|
||||||
|
code.Drop();
|
||||||
|
|
||||||
|
bool tabs = true;
|
||||||
|
for(auto l : code)
|
||||||
|
if(l.GetCount() && *l != '\t') {
|
||||||
|
tabs = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(tabs)
|
||||||
|
for(auto& l : code)
|
||||||
|
if(l.GetCount())
|
||||||
|
l.Remove(0);
|
||||||
|
|
||||||
|
if(code.GetCount() == 0)
|
||||||
|
return;
|
||||||
|
OUT("============= CODE");
|
||||||
|
OUT(Join(code, "\r\n"));
|
||||||
|
|
||||||
|
qtf << "&[s4; \1" << Join(code, "\n") << "\1&]&";
|
||||||
|
code.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MakeTutorial()
|
||||||
|
{
|
||||||
|
String log = LoadFile(GetStdLogPath());
|
||||||
|
StringStream ss(log);
|
||||||
|
|
||||||
|
VectorMap<String, Vector<Tuple<int, String>>> logline;
|
||||||
|
|
||||||
|
String path;
|
||||||
|
int line;
|
||||||
|
|
||||||
|
while(!ss.IsEof()) {
|
||||||
|
String ln = ss.GetLine();
|
||||||
|
if(ln.StartsWith("-=>")) {
|
||||||
|
SplitTo(ln.Mid(4), '@', path, ln);
|
||||||
|
line = atoi(ln) - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(path.GetCount())
|
||||||
|
logline.GetAdd(path).Add(MakeTuple(line, ln));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto&& f : ~logline) {
|
||||||
|
Vector<String> src = Split(Filter(LoadFile(f.key), [] (int c) { return c == '\r' ? 0 : c; }), '\n', false);
|
||||||
|
int i = 0;
|
||||||
|
int logi = 0;
|
||||||
|
bool wasdoc = false;
|
||||||
|
Vector<String> code;
|
||||||
|
while(i < src.GetCount()) {
|
||||||
|
String block;
|
||||||
|
while(i < src.GetCount() && TrimLeft(src[i]).StartsWith("///")) {
|
||||||
|
FlushCode(code);
|
||||||
|
Vector<String> logblock;
|
||||||
|
while(logi < f.value.GetCount() && f.value[logi].a <= i)
|
||||||
|
logblock.Add(f.value[logi++].b);
|
||||||
|
FlushLog(logblock);
|
||||||
|
|
||||||
|
String line = src[i++];
|
||||||
|
int q = line.FindAfter("///");
|
||||||
|
if(TrimRight(line).GetCount() > q) {
|
||||||
|
if(block.GetCount())
|
||||||
|
block << ' ';
|
||||||
|
block << TrimBoth(line.Mid(q));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
FlushDoc(block);
|
||||||
|
wasdoc = true;
|
||||||
|
}
|
||||||
|
FlushDoc(block);
|
||||||
|
while(i < src.GetCount() && !TrimLeft(src[i]).StartsWith("///")) {
|
||||||
|
String tl = TrimLeft(src[i]);
|
||||||
|
if(!tl.StartsWith("#if") && !tl.StartsWith("#end"))
|
||||||
|
code.Add(src[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
DUMPC(code);
|
||||||
|
if(!wasdoc)
|
||||||
|
code.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("--------------------------------------------");
|
||||||
|
LOG(out);
|
||||||
|
|
||||||
|
LOG("--------------------------------------------");
|
||||||
|
LOG(qtf);
|
||||||
|
|
||||||
|
RichEditWithToolBar edit;
|
||||||
|
edit.SetReadOnly();
|
||||||
|
edit <<= qtf;
|
||||||
|
TopWindow win;
|
||||||
|
win.Add(edit.SizePos());
|
||||||
|
win.Run();
|
||||||
|
}
|
||||||
51
tutorial/CoreTutorial/Tutorial.h
Normal file
51
tutorial/CoreTutorial/Tutorial.h
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include <RichEdit/RichEdit.h>
|
||||||
|
|
||||||
|
using namespace Upp;
|
||||||
|
|
||||||
|
#define LOGPOS UPP::VppLog() << "-=> " << __FILE__ << '@' << __LINE__ << '\n',
|
||||||
|
|
||||||
|
#undef LOG
|
||||||
|
#undef DUMP
|
||||||
|
#undef DUMPC
|
||||||
|
#undef DUMPM
|
||||||
|
#undef LOGHEX
|
||||||
|
#undef DUMPHEX
|
||||||
|
|
||||||
|
#define LOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
|
||||||
|
#define DUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
|
||||||
|
#define DUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define DUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define LOGHEX(x) LOGPOS UPP::LogHex(x)
|
||||||
|
#define DUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
|
||||||
|
|
||||||
|
#undef DLOG
|
||||||
|
#undef DDUMP
|
||||||
|
#undef DDUMPC
|
||||||
|
#undef DDUMPM
|
||||||
|
#undef DLOGHEX
|
||||||
|
#undef DDUMPHEX
|
||||||
|
|
||||||
|
#define DLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
|
||||||
|
#define DDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
|
||||||
|
#define DDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define DDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define DLOGHEX(x) LOGPOS UPP::LogHex(x)
|
||||||
|
#define DDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
|
||||||
|
|
||||||
|
#undef RLOG
|
||||||
|
#undef RDUMP
|
||||||
|
#undef RDUMPC
|
||||||
|
#undef RDUMPM
|
||||||
|
#undef RLOGHEX
|
||||||
|
#undef RDUMPHEX
|
||||||
|
|
||||||
|
#define RLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
|
||||||
|
#define RDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
|
||||||
|
#define RDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define RDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
|
||||||
|
#define RLOGHEX(x) LOGPOS UPP::LogHex(x)
|
||||||
|
#define RDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
|
||||||
|
|
||||||
|
#define DO(x) void x(); x();
|
||||||
|
|
||||||
|
void MakeTutorial();
|
||||||
77
tutorial/CoreTutorial/Value.cpp
Normal file
77
tutorial/CoreTutorial/Value.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void ValueTutorial()
|
||||||
|
{
|
||||||
|
/// .Value
|
||||||
|
|
||||||
|
/// Value is sort of equivalent of polymorphic data types from scripting languages like Python
|
||||||
|
/// or JavaSript. `Value` can represent values of concrete types, some types also have
|
||||||
|
/// extended interoperability with `Value` and it is then possible to e.g. compare `Value`s
|
||||||
|
/// containing such types against each other or serialize them for persistent storage.
|
||||||
|
|
||||||
|
/// Usually, Value compatible types define typecast operator to `Value` and constructor
|
||||||
|
/// from `Value`, so that interaction is for the most part seamless:
|
||||||
|
|
||||||
|
Value a = 1;
|
||||||
|
Value b = 2.34;
|
||||||
|
Value c = GetSysDate();
|
||||||
|
Value d = "hello";
|
||||||
|
|
||||||
|
DUMP(a);
|
||||||
|
DUMP(b);
|
||||||
|
DUMP(c);
|
||||||
|
DUMP(d);
|
||||||
|
|
||||||
|
int x = a;
|
||||||
|
double y = b;
|
||||||
|
Date z = c;
|
||||||
|
String s = d;
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
DUMP(y);
|
||||||
|
DUMP(z);
|
||||||
|
DUMP(s);
|
||||||
|
|
||||||
|
/// As for primitive types, Value seamlessly works with `int`, `int64`, `bool` and `double`.
|
||||||
|
/// Casting `Value` to a type that it does not contain throws an exception:
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = a;
|
||||||
|
DUMP(s); // we never get here....
|
||||||
|
}
|
||||||
|
catch(ValueTypeError) {
|
||||||
|
LOG("Failed Value conversion");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// However, conversion between related types is possible (as long as it is supported by
|
||||||
|
/// these types):
|
||||||
|
|
||||||
|
double i = a;
|
||||||
|
int j = b;
|
||||||
|
Time k = c;
|
||||||
|
WString t = d;
|
||||||
|
|
||||||
|
DUMP(i);
|
||||||
|
DUMP(j);
|
||||||
|
DUMP(k);
|
||||||
|
DUMP(t);
|
||||||
|
|
||||||
|
/// To determine type of value stored in `Value`, you can use `Is` method:
|
||||||
|
|
||||||
|
DUMP(a.Is<int>());
|
||||||
|
DUMP(a.Is<double>());
|
||||||
|
DUMP(b.Is<double>());
|
||||||
|
DUMP(c.Is<int>());
|
||||||
|
DUMP(c.Is<Date>());
|
||||||
|
DUMP(d.Is<String>());
|
||||||
|
|
||||||
|
/// Note that Is tests for absolute type match, not for compatible types. For that reason,
|
||||||
|
/// for widely used compatible types helper functions are defined:
|
||||||
|
|
||||||
|
DUMP(IsNumber(a));
|
||||||
|
DUMP(IsNumber(b));
|
||||||
|
DUMP(IsDateTime(c));
|
||||||
|
DUMP(IsString(d));
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
90
tutorial/CoreTutorial/Value2.cpp
Normal file
90
tutorial/CoreTutorial/Value2.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Value2Tutorial()
|
||||||
|
{
|
||||||
|
/// .Client types and `Value`, `RawValue`, `RichValue`
|
||||||
|
|
||||||
|
/// There are two Value compatibility levels. The simple one, `RawValue`, has little
|
||||||
|
/// requirements for the type used - only copy constructor and assignment operator are
|
||||||
|
/// required (and there are even forms of `RawValue` that work for types missing these):
|
||||||
|
|
||||||
|
struct RawFoo {
|
||||||
|
String x;
|
||||||
|
// default copy constructor and assignment operator are provided by compiler
|
||||||
|
};
|
||||||
|
|
||||||
|
/// To convert such type to `Value`, use `RawToValue`:
|
||||||
|
|
||||||
|
RawFoo h;
|
||||||
|
h.x = "hello";
|
||||||
|
Value q = RawToValue(h);
|
||||||
|
|
||||||
|
DUMP(q.Is<RawFoo>());
|
||||||
|
|
||||||
|
/// To convert it back, us 'To' templated member function of `Value`, it returns a constant
|
||||||
|
/// reference to the value:
|
||||||
|
|
||||||
|
DUMP(q.To<RawFoo>().x);
|
||||||
|
|
||||||
|
/// `RichValue` level `Value`s provide more operations for `Value` - equality test,
|
||||||
|
/// `IsNull` test, hashing, conversion to text, serialization (possibly to XML and Json),
|
||||||
|
/// comparison. In order to make serialization work, type must also have assigned an
|
||||||
|
/// integer id (client types should use ids in range 10000..20000). Type can provide the
|
||||||
|
/// support for these operations via template function specializations or (perhaps more
|
||||||
|
/// convenient) using defined methods and inheriting from `ValueType` base class template:
|
||||||
|
|
||||||
|
struct Foo : ValueType<Foo, 10010> {
|
||||||
|
int x;
|
||||||
|
|
||||||
|
Foo(const Nuller&) { x = Null; }
|
||||||
|
Foo(int x) : x(x) {}
|
||||||
|
Foo() {}
|
||||||
|
|
||||||
|
// We provide these methods to allow automatic conversion of Foo to/from Value
|
||||||
|
operator Value() const { return RichToValue(*this); }
|
||||||
|
Foo(const Value& v) { *this = v.Get<Foo>(); }
|
||||||
|
|
||||||
|
String ToString() const { return AsString(x); }
|
||||||
|
unsigned GetHashValue() const { return x; }
|
||||||
|
void Serialize(Stream& s) { s % x; }
|
||||||
|
bool operator==(const Foo& b) const { return x == b.x; }
|
||||||
|
bool IsNullInstance() const { return IsNull(x); }
|
||||||
|
int Compare(const Foo& b) const { return SgnCompare(x, b.x); }
|
||||||
|
// This type does not define XML nor Json serialization
|
||||||
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
INITBLOCK { // This has to be at file level scope
|
||||||
|
#endif
|
||||||
|
Value::Register<Foo>(); // need to register value type integer id to allow serialization
|
||||||
|
#if 0
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Value a = Foo(54321); // uses Foo::operator Value
|
||||||
|
Value b = Foo(54321);
|
||||||
|
Value c = Foo(600);
|
||||||
|
|
||||||
|
DUMP(a); // uses Foo::ToString
|
||||||
|
DUMP(a == b); // uses Foo::operator==
|
||||||
|
DUMP(a == c);
|
||||||
|
DUMP(c < a); // uses Foo::Compare
|
||||||
|
|
||||||
|
DUMP(IsNull(a)); // uses Foo::IsNullInstance
|
||||||
|
|
||||||
|
Foo foo = c; // Uses Foo::Foo(const Value&)
|
||||||
|
DUMP(foo);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
String s = StoreAsString(a); // Uses Foo::Serialize
|
||||||
|
|
||||||
|
Value loaded;
|
||||||
|
// Using registered (Value::Registered) integer id creates the correct type, then uses
|
||||||
|
// Foo::Serialize to load the data from the stream
|
||||||
|
LoadFromString(loaded, s);
|
||||||
|
|
||||||
|
DUMP(loaded);
|
||||||
|
|
||||||
|
///
|
||||||
|
};
|
||||||
58
tutorial/CoreTutorial/Vector.cpp
Normal file
58
tutorial/CoreTutorial/Vector.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Vector1()
|
||||||
|
{
|
||||||
|
/// .`Vector` basics
|
||||||
|
|
||||||
|
/// `Vector` is the basic container of U++. It is the random access container similar to
|
||||||
|
/// `std::vector` with one important performance related difference: There are rules for
|
||||||
|
/// elements of `Vector` that allow its implementation to move elements in memory using
|
||||||
|
/// plain `memcpy`/`memmove` ("Moveable" concept).
|
||||||
|
|
||||||
|
/// Anyway, for now let us start with simple `Vector` of `int`s:
|
||||||
|
|
||||||
|
Vector<int> v;
|
||||||
|
|
||||||
|
/// You can add elements to the Vector as parameters of the Add method
|
||||||
|
|
||||||
|
v.Add(1);
|
||||||
|
v.Add(2);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// Alternative and very important possibility for U++ containers is 'in-place creation'.
|
||||||
|
/// In this case, parameter-less Add returns a reference to a new element in `Vector`:
|
||||||
|
|
||||||
|
v.Add() = 3;
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// You can also use `operator<<`
|
||||||
|
|
||||||
|
v << 4 << 5;
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// `Vector` also supports initializer lists:
|
||||||
|
|
||||||
|
v.Append({ 6, 7 });
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// To iterate `Vector` you can use indices:
|
||||||
|
|
||||||
|
for(int i = 0; i < v.GetCount(); i++)
|
||||||
|
LOG(v[i]);
|
||||||
|
|
||||||
|
/// begin/end interface:
|
||||||
|
|
||||||
|
for(auto q = v.begin(), e = v.end(); q != e; q++)
|
||||||
|
LOG(*q);
|
||||||
|
|
||||||
|
/// C++11 range-for syntax:
|
||||||
|
|
||||||
|
for(const auto& q : v)
|
||||||
|
LOG(q);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
46
tutorial/CoreTutorial/Vector2.cpp
Normal file
46
tutorial/CoreTutorial/Vector2.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void Vector2()
|
||||||
|
{
|
||||||
|
/// .`Vector` operations
|
||||||
|
|
||||||
|
/// You can `Insert` or `Remove` elements at random positions of Vector (O(n) complexity):
|
||||||
|
|
||||||
|
Vector<int> v;
|
||||||
|
v.Add(1);
|
||||||
|
v.Add(2);
|
||||||
|
|
||||||
|
v.Insert(1, 10);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
v.Insert(0, { 7, 6, 5 });
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
v.Remove(0);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// At method returns element at specified position ensuring that such a position exists.
|
||||||
|
/// If there is not enough elements in `Vector`, required number of elements is added. If
|
||||||
|
/// second parameter of `At` is present, newly added elements are initialized to this value.
|
||||||
|
|
||||||
|
v.Clear();
|
||||||
|
for(int i = 0; i < 10000; i++)
|
||||||
|
v.At(Random(10), 0)++;
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
/// You can apply algorithms on containers, e.g. Sort
|
||||||
|
|
||||||
|
Sort(v);
|
||||||
|
|
||||||
|
DUMP(v);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
33
tutorial/CoreTutorial/WString.cpp
Normal file
33
tutorial/CoreTutorial/WString.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
void WStringTutorial()
|
||||||
|
{
|
||||||
|
/// .WString
|
||||||
|
|
||||||
|
/// String works with 8 bit characters. For 16-bit character encoding use `WString`. Both
|
||||||
|
/// classes are closely related and share most of interface methods. U++ also provides
|
||||||
|
/// conversions between `String` and `WString` and you can also use 8 bit string literals with
|
||||||
|
/// `WString`. Conversion is ruled by current default character set. Default value of default
|
||||||
|
/// character set is `CHARSET_UTF8`. This conversion is also used in `WString::ToString`,
|
||||||
|
/// e.g. when putting `WString` to log:
|
||||||
|
|
||||||
|
WString x = "characters 280-300: "; // you can assign 8-bit character literal to WString
|
||||||
|
for(int i = 280; i < 300; i++)
|
||||||
|
x.Cat(i);
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
/// `ToString` converts `WString` to `String`:
|
||||||
|
|
||||||
|
String y = x.ToString();
|
||||||
|
DUMP(y);
|
||||||
|
|
||||||
|
/// `ToWString` converts `String` to `WString`:
|
||||||
|
|
||||||
|
y.Cat(" (appended)"); // you can use 8-bit character literals in most WString operations
|
||||||
|
x = y.ToWString();
|
||||||
|
|
||||||
|
DUMP(x);
|
||||||
|
|
||||||
|
///
|
||||||
|
}
|
||||||
747
tutorial/CoreTutorial/help.qtf
Normal file
747
tutorial/CoreTutorial/help.qtf
Normal file
|
|
@ -0,0 +1,747 @@
|
||||||
|
[ $$0,0#00000000000000000000000000000000:Default]
|
||||||
|
[a83;*R6 $$1,3#31310162474203024125188417583966:caption]
|
||||||
|
[H4;b83;*4 $$2,3#07864147445237544204411237157677:title]
|
||||||
|
[b42;a42;ph2 $$3,3#45413000475342174754091244180557:text]
|
||||||
|
[l321;C@5;1 $$4,4#20902679421464641399138805415013:code]
|
||||||
|
[l321;b83;a83;*C$7;2 $$5,5#07531550463529505371228428965313:result`-line]
|
||||||
|
[l321;*C$7;2 $$6,6#03451589433145915344929335295360:result]
|
||||||
|
[{_}%EN-US
|
||||||
|
[s1; U`+`+ Containers Tutorial&]
|
||||||
|
[s2; 1. Vector basics&]
|
||||||
|
[s3; Let us start with a simple [* Vector] of ints&]
|
||||||
|
[s4; -|[* Vector<int>] v;&]
|
||||||
|
[s3; You can add elements to the Vector as parameters of the [* Add]
|
||||||
|
method&]
|
||||||
|
[s4; -|v.[* Add](1);&]
|
||||||
|
[s4; -|v.Add(2);&]
|
||||||
|
[s4; -|v.Add(3);&]
|
||||||
|
[s3; To iterate Vector you can use indices&]
|
||||||
|
[s4; -|for(int i `= 0; i < v.[* GetCount](); i`+`+)&]
|
||||||
|
[s4; -|-|LOG(v[* `[]i[* `]]);&]
|
||||||
|
[s3; Alternative, U`+`+ also provides standard C`+`+ begin/end interface&]
|
||||||
|
[s4; -|for(auto q `= v.[* begin](), e `= v.[* end](); q !`= e; q`+`+)&]
|
||||||
|
[s4; -|-|LOG([* `*]q);&]
|
||||||
|
[s3; or with C`+`+11 range`-for syntax:&]
|
||||||
|
[s4; -|for(const auto`& q : v)&]
|
||||||
|
[s4; -|-|LOG(q);&]
|
||||||
|
[s3;/ &]
|
||||||
|
[s3; [/ Note: LOG is diagnostics macro that outputs its argument to
|
||||||
|
the .log file in debug mode. Another similar macros we will use
|
||||||
|
in this tutorial are DUMP (similar to the LOG, but dumps the
|
||||||
|
source expression too) and DUMPC (dumps the content of the container).]&]
|
||||||
|
[s3;/ &]
|
||||||
|
[s2; 2. Vector operations&]
|
||||||
|
[s3; You can [* Insert] or [* Remove] elements at random positions of
|
||||||
|
Vector&]
|
||||||
|
[s4; -|Vector<int> v;&]
|
||||||
|
[s4; -|v.Add(1);&]
|
||||||
|
[s4; -|v.Add(2);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|v.[* Insert](1, 10);&]
|
||||||
|
[s5; v `= `{ 1, 10, 2 `}&]
|
||||||
|
[s4; -|v.[* Remove](0);&]
|
||||||
|
[s5; v `= `{ 10, 2 `}&]
|
||||||
|
[s3; [* At] method returns element at specified position ensuring that
|
||||||
|
such a position exists. If there is not enough elements in Vector,
|
||||||
|
required number of elements is added. If second parameter of
|
||||||
|
At is present, newly added elements are initialized to this value.
|
||||||
|
As an example, we will create distribution of RandomValue with
|
||||||
|
unknown maximal value&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|v.Clear();&]
|
||||||
|
[s4; -|for(int i `= 0; i < 10000; i`+`+)&]
|
||||||
|
[s4; -|-|v.[* At](RandomValue(), 0)`+`+;&]
|
||||||
|
[s5; v `= `{ 958, 998, 983, 1012, 1013, 1050, 989, 998, 1007, 992
|
||||||
|
`}&]
|
||||||
|
[s3; You can apply algorithms on containers, e.g. [* Sort]&]
|
||||||
|
[s4; -|[* Sort](v);&]
|
||||||
|
[s5; v `= `{ 958, 983, 989, 992, 998, 998, 1007, 1012, 1013, 1050
|
||||||
|
`}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 3. Transfer issues&]
|
||||||
|
[s3; Often you need to pass content of one container to another of
|
||||||
|
the same type. NTL containers always support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ p
|
||||||
|
ick semantics] (equivalent of std`::move), and, depending on type
|
||||||
|
stored, also might support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ clone
|
||||||
|
sematics]. When transfering the value, you have to explicitly
|
||||||
|
specify which one to use:&]
|
||||||
|
[s4; -|Vector<int> v;&]
|
||||||
|
[s4; -|v.Add(1);&]
|
||||||
|
[s4; -|v.Add(2);&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Vector<int> v1 [* `=] pick(v);&]
|
||||||
|
[s3; now source Vector [C v] is destroyed `- picked `- and you can
|
||||||
|
no longer access its content. This behaviour has many advantages.
|
||||||
|
First, it is consistently fast and in most cases, transfer of
|
||||||
|
value instead of full copy is exactly what you need. Second,
|
||||||
|
NTL containers can store elements that lack copy operation `-
|
||||||
|
in that case, pick transfer is the only option anyway.&]
|
||||||
|
[s3; If you really need to preserve value of source (and elements
|
||||||
|
support deep copy operation), you can use [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ c
|
||||||
|
lone]&]
|
||||||
|
[s4; -|v [* `=] clone(v1);&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 4. Client types&]
|
||||||
|
[s3; So far we were using int as type of elements. In order to store
|
||||||
|
client defined types into the Vector (and the Vector [^topic`:`/`/Core`/src`/Overview`$en`-us^ f
|
||||||
|
lavor]) the type must satisfy [^topic`:`/`/Core`/src`/Moveable`$en`-us^ moveable]
|
||||||
|
requirement `- in short, it must not contain back`-pointers nor
|
||||||
|
virtual methods. Type must be marked as moveable in order to
|
||||||
|
define interface contract using&]
|
||||||
|
[s4; -|struct Distribution : [* Moveable]<Distribution> `{&]
|
||||||
|
[s4; -|-|String text;&]
|
||||||
|
[s4; -|-|Vector<int> data;&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|-|rval`_default(Distribution);&]
|
||||||
|
[s4; -|-|Distribution() `{`}&]
|
||||||
|
[s4; -|`};&]
|
||||||
|
[s3; Note that rval`_default macro is helper to restore default deleted
|
||||||
|
r`-value constructor in C`+`+11 and default constructor is also
|
||||||
|
default deleted in C`+`+11.&]
|
||||||
|
[s3; Now to add Distribution elements you cannot use Add with parameter,
|
||||||
|
because it requires elements to have default deep`-copy constructor
|
||||||
|
`- and Distribution does not have one, as Vector<int> has default
|
||||||
|
pick`-constructor, so Distribution itself has pick`-constructor.
|
||||||
|
It would no be a good idea either, because deep`-copy would involve
|
||||||
|
expensive copying of inner Vector.&]
|
||||||
|
[s3; Instead, [* Add] without parameters has to be used `- it default
|
||||||
|
constructs (that is cheap) element in Vector and returns reference
|
||||||
|
to it&]
|
||||||
|
[s4; -|Vector<Distribution> dist;&]
|
||||||
|
[s4; -|for(int n `= 5; n <`= 10; n`+`+) `{&]
|
||||||
|
[s4; -|-|Distribution`& d `= dist.[* Add]();&]
|
||||||
|
[s4; -|-|d.text << `"Test `" << n;&]
|
||||||
|
[s4; -|-|for(int i `= 0; i < 10000; i`+`+)&]
|
||||||
|
[s4; -|-|-|d.data.At(rand() % n, 0)`+`+;&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; Test 5: `{ 2018, 1992, 2025, 1988, 1977 `}&]
|
||||||
|
[s6; Test 6: `{ 1670, 1682, 1668, 1658, 1646, 1676 `}&]
|
||||||
|
[s6; Test 7: `{ 1444, 1406, 1419, 1493, 1370, 1418, 1450 `}&]
|
||||||
|
[s6; Test 8: `{ 1236, 1199, 1245, 1273, 1279, 1302, 1250, 1216 `}&]
|
||||||
|
[s6; Test 9: `{ 1115, 1111, 1100, 1122, 1192, 1102, 1089, 1064, 1105
|
||||||
|
`}&]
|
||||||
|
[s6; Test 10: `{ 969, 956, 1002, 1023, 1006, 994, 1066, 1022, 929,
|
||||||
|
1033 `}&]
|
||||||
|
[s3; Another possibility is to use AddPick method, which uses pick`-constructor
|
||||||
|
instead of deep`-copy constructor. E.g. Distribution elements
|
||||||
|
might be generated by some function &]
|
||||||
|
[s4; -|Distribution CreateDist(int n);&]
|
||||||
|
[s3; and code for adding such elements to Vector then looks like&]
|
||||||
|
[s4; -|for(n `= 5; n <`= 10; n`+`+)&]
|
||||||
|
[s4; -|-|dist.[* AddPick](CreateDist(n));&]
|
||||||
|
[s3; alternatively, you can use default`-constructed variant too&]
|
||||||
|
[s4; -|-|dist.Add() `= CreateDist(); // alternative&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 5. Array flavor&]
|
||||||
|
[s3; If elements do not satisfy requirements for Vector flavor, they
|
||||||
|
can still be stored in Array flavor. Another reason for using
|
||||||
|
Array is need for referencing elements `- Array flavor never
|
||||||
|
invalidates references or pointers to them.&]
|
||||||
|
[s3; Example of elements that cannot be stored in Vector flavor are
|
||||||
|
STL containers (STL does not specify the NTL flavor for obvious
|
||||||
|
reasons):&]
|
||||||
|
[s4; -|[* Array]< std`::list<int> > al;&]
|
||||||
|
[s4; -|for(int i `= 0; i < 4; i`+`+) `{&]
|
||||||
|
[s4; -|-|std`::list<int>`& l `= al.Add();&]
|
||||||
|
[s4; -|-|for(int q `= 0; q < i; q`+`+)&]
|
||||||
|
[s4; -|-|-|l.push`_back(q);&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 6. Polymorphic Array&]
|
||||||
|
[s3; Array can even be used for storing polymorphic elements &]
|
||||||
|
[s4; struct Number `{&]
|
||||||
|
[s4; -|virtual double Get() const `= 0;&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|String ToString() const `{ return AsString(Get()); `}&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|virtual `~Number() `{`}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; struct Integer : public Number `{&]
|
||||||
|
[s4; -|int n;&]
|
||||||
|
[s4; -|virtual double Get() const `{ return n; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Integer() `{`}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; struct Double : public Number `{&]
|
||||||
|
[s4; -|double n;&]
|
||||||
|
[s4; -|virtual double Get() const `{ return n; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Double() `{`}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; bool operator<(const Number`& a, const Number`& b)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|return a.Get() < b.Get();&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; In this case, elements are added using Add with pointer to base
|
||||||
|
element type parameter. Do not be confused by new and pointer,
|
||||||
|
Array takes ownership of passed object and behaves like container
|
||||||
|
of base type elements&]
|
||||||
|
[s4; -|Array<Number> num;&]
|
||||||
|
[s4; -|num.Create<Double>().n `= 15.5;&]
|
||||||
|
[s4; -|num.Create<Integer>().n `= 3;&]
|
||||||
|
[s5; num `= `{ 15.5, 3 `}&]
|
||||||
|
[s3; Thanks to well defined algorithm requirements, you can e.g.
|
||||||
|
directly apply Sort on such Array&]
|
||||||
|
[s4; -|bool operator<(const Number`& a, const Number`& b)&]
|
||||||
|
[s4; -|`{&]
|
||||||
|
[s4; -|-|return a.Get() < b.Get();&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; .......&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Sort(num);&]
|
||||||
|
[s5; num `= `{ 3, 15.5 `}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 7. Bidirectional containers&]
|
||||||
|
[s3; Vector and Array containers allow fast adding and removing elements
|
||||||
|
at the end of sequence. Sometimes, same is needed at begin of
|
||||||
|
sequence too (usually to support FIFO like operations). In such
|
||||||
|
case, BiVector and BiArray should be used&]
|
||||||
|
[s4; -|BiVector<int> n;&]
|
||||||
|
[s4; -|n.[* AddHead](1);&]
|
||||||
|
[s4; -|n.[* AddTail](2);&]
|
||||||
|
[s4; -|n.AddHead(3);&]
|
||||||
|
[s4; -|n.AddTail(4);&]
|
||||||
|
[s5; n `= `{ 3, 1, 2, 4 `}&]
|
||||||
|
[s4; -|n.[* DropHead]();&]
|
||||||
|
[s5; n `= `{ 1, 2, 4 `}&]
|
||||||
|
[s4; -|n.[* DropTail]();&]
|
||||||
|
[s5; n `= `{ 1, 2 `}&]
|
||||||
|
[s4; -|BiArray<Number> num;&]
|
||||||
|
[s4; -|num.CreateHead<Integer>().n `= 3;&]
|
||||||
|
[s4; -|num.CreateTail<Double>().n `= 15.5;&]
|
||||||
|
[s4; -|num.CreateHead<Double>().n `= 2.23;&]
|
||||||
|
[s4; -|num.CreateTail<Integer>().n `= 2;&]
|
||||||
|
[s5; num `= `{ 2.23, 3, 15.5, 2 `}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 8. Index&]
|
||||||
|
[s3; Index is a container very similar to the plain Vector (it is
|
||||||
|
random access array of elements with fast addition at the end)
|
||||||
|
with one unique feature `- it is able to fast retrieve position
|
||||||
|
of element with required value using [* Find] method&]
|
||||||
|
[s4; -|[* Index]<String> ndx;&]
|
||||||
|
[s4; -|ndx.[* Add](`"alfa`");&]
|
||||||
|
[s4; -|ndx.Add(`"beta`");&]
|
||||||
|
[s4; -|ndx.Add(`"gamma`");&]
|
||||||
|
[s4; -|ndx.Add(`"delta`");&]
|
||||||
|
[s4; -|ndx.Add(`"kappa`");&]
|
||||||
|
[s5; ndx `= `{ alfa, beta, gamma, delta, kappa `}&]
|
||||||
|
[s4; -|DUMP(ndx.[* Find](`"beta`"));&]
|
||||||
|
[s5; ndx.Find(`"beta`") `= 1&]
|
||||||
|
[s3; If element is not present in Index, Find returns a negative
|
||||||
|
value&]
|
||||||
|
[s4; -|DUMP(ndx.Find(`"something`"));&]
|
||||||
|
[s5; ndx.Find(`"something`") `= `-1&]
|
||||||
|
[s3; Any element can be replaced using [* Set] method&]
|
||||||
|
[s4; -|ndx.[* Set](0, `"delta`");&]
|
||||||
|
[s5; ndx `= `{ delta, beta, gamma, delta, kappa `}&]
|
||||||
|
[s3; If there are more elements with the same value, they can be
|
||||||
|
iterated using [* FindNext] method&]
|
||||||
|
[s4; -|int fi `= ndx.Find(`"delta`");&]
|
||||||
|
[s4; -|while(fi >`= 0) `{&]
|
||||||
|
[s4; -|-|DUMP(fi);&]
|
||||||
|
[s4; -|-|fi `= ndx.[* FindNext](fi);&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; -|cout << `'n`';&]
|
||||||
|
[s5; 0 3-|&]
|
||||||
|
[s3; [* FindAdd] method retrieves position of element like Find, but
|
||||||
|
if element is not present in Index, it is added&]
|
||||||
|
[s4; -|DUMP(ndx.[* FindAdd](`"one`"));&]
|
||||||
|
[s4; -|DUMP(ndx.FindAdd(`"two`"));&]
|
||||||
|
[s4; -|DUMP(ndx.FindAdd(`"three`"));&]
|
||||||
|
[s4; -|DUMP(ndx.FindAdd(`"two`"));&]
|
||||||
|
[s4; -|DUMP(ndx.FindAdd(`"three`"));&]
|
||||||
|
[s4; -|DUMP(ndx.FindAdd(`"one`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; ndx.FindAdd(`"one`") `= 5&]
|
||||||
|
[s6; ndx.FindAdd(`"two`") `= 6&]
|
||||||
|
[s6; ndx.FindAdd(`"three`") `= 7&]
|
||||||
|
[s6; ndx.FindAdd(`"two`") `= 6&]
|
||||||
|
[s6; ndx.FindAdd(`"three`") `= 7&]
|
||||||
|
[s6; ndx.FindAdd(`"one`") `= 5&]
|
||||||
|
[s3; Removing elements from random access sequence is always expensive,
|
||||||
|
that is why rather than remove, Index supports [* Unlink] and [* UnlinkKey
|
||||||
|
]operations, which leave element in Index but make it invisible
|
||||||
|
for Find operation&]
|
||||||
|
[s4; -|ndx.[* Unlink](2);&]
|
||||||
|
[s4; -|ndx.[* UnlinkKey](`"kappa`");&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(ndx.Find(ndx`[2`]));&]
|
||||||
|
[s4; -|DUMP(ndx.Find(`"kappa`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; ndx.Find(ndx`[2`]) `= `-1&]
|
||||||
|
[s6; ndx.Find(`"kappa`") `= `-1&]
|
||||||
|
[s3; You can test whether element at given position is unlinked using
|
||||||
|
[* IsUnlinked] method&]
|
||||||
|
[s4; -|DUMP(ndx.[* IsUnlinked](1));&]
|
||||||
|
[s4; -|DUMP(ndx.IsUnlinked(2));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; ndx.IsUnlinked(1) `= 0&]
|
||||||
|
[s6; ndx.IsUnlinked(2) `= 1&]
|
||||||
|
[s3; Unlinked positions can be reused by [* Put] method&]
|
||||||
|
[s4; -|ndx.[* Put](`"foo`");&]
|
||||||
|
[s5; ndx `= `{ delta, beta, foo, delta, kappa, one, two, three `}&]
|
||||||
|
[s4; -|DUMP(ndx.Find(`"foo`"));&]
|
||||||
|
[s5; ndx.Find(`"foo`") `= 2&]
|
||||||
|
[s3; You can also remove all unlinked elements from Index using [* Sweep]
|
||||||
|
method&]
|
||||||
|
[s4; -|ndx.Sweep();&]
|
||||||
|
[s5; ndx `= `{ delta, beta, foo, delta, one, two, three `}&]
|
||||||
|
[s3; As we said, operations directly removing or inserting elements
|
||||||
|
of Index are very expensive, but sometimes this might not matter,
|
||||||
|
so they are available too&]
|
||||||
|
[s4; -|ndx.[* Remove](1);&]
|
||||||
|
[s5; ndx `= `{ delta, foo, delta, one, two, three `}&]
|
||||||
|
[s4; -|ndx.[* RemoveKey](`"two`");&]
|
||||||
|
[s5; ndx `= `{ delta, foo, delta, one, three `}&]
|
||||||
|
[s4; -|ndx.[* Insert](0, `"insert`");&]
|
||||||
|
[s5; ndx `= `{ insert, delta, foo, delta, one, three `}&]
|
||||||
|
[s3; Finally, [* PickKeys] operation allows you to obtain Vector of
|
||||||
|
elements of Index in low constant time operation (while destroying
|
||||||
|
source Index)&]
|
||||||
|
[s4; -|Vector<String> d `= ndx.[* PickKeys]();&]
|
||||||
|
[s4; -|Sort(d);&]
|
||||||
|
[s5; d `= `{ delta, delta, foo, insert, one, three `}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 9. Index and client types&]
|
||||||
|
[s3; In order to store elements to Index, they must be moveable (you
|
||||||
|
can use [* ArrayIndex] for types that are not) and they must have
|
||||||
|
defined the operator`=`= and a function to compute hash value.
|
||||||
|
Notice usage THE of [* CombineHash] to combine hash values of types
|
||||||
|
already known to U`+`+ into final result&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; struct Person : Moveable<Person> `{&]
|
||||||
|
[s4; -|String name;&]
|
||||||
|
[s4; -|String surname;&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|unsigned [* GetHashValue]() const `{ return [* CombineHash](name,
|
||||||
|
surname); `}&]
|
||||||
|
[s4; -|bool [* operator`=`=](const Person`& b) const `{ return name
|
||||||
|
`=`= b.name `&`& surname `=`= b.surname; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Person(String name, String surname) : name(name), surname(surname)
|
||||||
|
`{`}&]
|
||||||
|
[s4; -|Person() `{`}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; .......&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Index<Person> p;&]
|
||||||
|
[s4; -|p.Add(Person(`"John`", `"Smith`"));&]
|
||||||
|
[s4; -|p.Add(Person(`"Paul`", `"Carpenter`"));&]
|
||||||
|
[s4; -|p.Add(Person(`"Carl`", `"Engles`"));&]
|
||||||
|
[s4; -| &]
|
||||||
|
[s4; -|DUMP(p.Find(Person(`"Paul`", `"Carpenter`")));&]
|
||||||
|
[s5; p.Find(Person(`"Paul`", `"Carpenter`")) `= 1&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 10. VectorMap, ArrayMap&]
|
||||||
|
[s3; VectorMap is nothing more than a simple composition of Index
|
||||||
|
and Vector. You can use [* Add] methods to put elements into the
|
||||||
|
VectorMap&]
|
||||||
|
[s4; -|[* VectorMap]<String, Person> m;&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|m.[* Add](`"1`", Person(`"John`", `"Smith`"));&]
|
||||||
|
[s4; -|m.Add(`"2`", Person(`"Carl`", `"Engles`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Person`& p `= m.[* Add](`"3`");&]
|
||||||
|
[s4; -|p.name `= `"Paul`";&]
|
||||||
|
[s4; -|p.surname `= `"Carpenter`";&]
|
||||||
|
[s3; VectorMap provides constant access to its underlying Index and
|
||||||
|
Vector&]
|
||||||
|
[s4; -|DUMP(m.[* GetKeys]());&]
|
||||||
|
[s4; -|DUMP(m.[* GetValues]());&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; m.GetKeys() `= `{ 1, 2, 3 `}&]
|
||||||
|
[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter
|
||||||
|
`}&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; You can use indices to iterate map contents&]
|
||||||
|
[s4; -|for(int i `= 0; i < m.GetCount(); i`+`+)&]
|
||||||
|
[s4; -|-|LOG(m.[* GetKey](i) << `": `" << m[* `[]i[* `]]);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; 1: John Smith&]
|
||||||
|
[s6; 2: Carl Engles&]
|
||||||
|
[s6; 3: Paul Carpenter&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; You can use [* Find] method to retrieve position of element with
|
||||||
|
required key&]
|
||||||
|
[s4; -|DUMP(m.[* Find](`"2`"));&]
|
||||||
|
[s5; m.Find(`"2`") `= 1&]
|
||||||
|
[s3; or [* Get] method to retrieve corresponding value&]
|
||||||
|
[s4; -|DUMP(m.[* Get](`"2`"));&]
|
||||||
|
[s5; m.Get(`"2`") `= Carl Engles&]
|
||||||
|
[s3; Passing key not present in VectorMap as Get parameter is a logic
|
||||||
|
error, but there exists two parameter version that returns second
|
||||||
|
parameter if key is not in VectorMap&]
|
||||||
|
[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&]
|
||||||
|
[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= unknown person&]
|
||||||
|
[s3; As with Index, you can use [* Unlink] to make elements invisible
|
||||||
|
for Find operations&]
|
||||||
|
[s4; -|m.Unlink(1);&]
|
||||||
|
[s4; -|DUMP(m.Find(`"2`"));&]
|
||||||
|
[s5; m.Find(`"2`") `= `-1&]
|
||||||
|
[s3; You can use [* SetKey] method to change the key of the element&]
|
||||||
|
[s4; -|m.[* SetKey](1, `"33`");&]
|
||||||
|
[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&]
|
||||||
|
[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= Carl Engles&]
|
||||||
|
[s3; If there are more elements with the same key in VectorMap, you
|
||||||
|
can iterate them using [* FindNext] method:&]
|
||||||
|
[s4; -|m.Add(`"33`", Person(`"Peter`", `"Pan`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; m.GetKeys() `= `{ 1, 33, 3, 33 `}&]
|
||||||
|
[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter,
|
||||||
|
Peter Pan `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|int q `= m.Find(`"33`");&]
|
||||||
|
[s4; -|while(q >`= 0) `{&]
|
||||||
|
[s4; -|-|cout << m`[q`] << `'n`';&]
|
||||||
|
[s4; -|-|q `= m.[* FindNext](q);&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s6; Carl Engles&]
|
||||||
|
[s6; Peter Pan&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; You can reuse unlinked positions using [* Put] method:&]
|
||||||
|
[s4; -|m.[* UnlinkKey](`"33`");&]
|
||||||
|
[s4; -|m.[* Put](`"22`", Person(`"Ali`", `"Baba`"));&]
|
||||||
|
[s4; -|m.Put(`"44`", Person(`"Ivan`", `"Wilks`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; m.GetKeys() `= `{ 1, 22, 3, 44 `}&]
|
||||||
|
[s6; m.GetValues() `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan
|
||||||
|
Wilks `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; [* GetSortOrder] algorithm returns order of elements as Vector<int>
|
||||||
|
container. You can use it to order content of VectorMap without
|
||||||
|
actually moving its elements&]
|
||||||
|
[s4; -|bool operator<(const Person`& a, const Person`& b)&]
|
||||||
|
[s4; -|`{&]
|
||||||
|
[s4; -|-|return a.surname `=`= b.surname ? a.name < b.name&]
|
||||||
|
[s4; -| : a.surname < b.surname;&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; .......&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Vector<int> order `= [* GetSortOrder](m.GetValues());&]
|
||||||
|
[s5; order `= `{ 1, 2, 0, 3 `}&]
|
||||||
|
[s4; -|for(int i `= 0; i < order.GetCount(); i`+`+)&]
|
||||||
|
[s4; -|-|cout << m.GetKey(order`[i`]) << `": `" << m`[order`[i`]`] <<
|
||||||
|
`'n`';&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; 22: Ali Baba&]
|
||||||
|
[s6; 3: Paul Carpenter&]
|
||||||
|
[s6; 1: John Smith&]
|
||||||
|
[s6; 44: Ivan Wilks&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; You can get Vector of values or keys using [* PickValues] resp.
|
||||||
|
[* PickKeys] methods in low constant time, while destroying content
|
||||||
|
of source VectorMap&]
|
||||||
|
[s4; -|Vector<Person> ps `= m.[* PickValues]();&]
|
||||||
|
[s5; ps `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan Wilks `}&]
|
||||||
|
[s3; If type of values does not satisfy requirements for Vector elements
|
||||||
|
or if references to elements are needed, you can use [* ArrayMap]
|
||||||
|
instead&]
|
||||||
|
[s4; -|[* ArrayMap]<String, Number> am;&]
|
||||||
|
[s4; -|am.Create<Integer>(`"A`").n `= 11;&]
|
||||||
|
[s4; -|am.Create<Double>(`"B`").n `= 2.1;&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; am.GetKeys() `= `{ A, B `}&]
|
||||||
|
[s6; am.GetValues() `= `{ 11, 2.1 `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(am.Get(`"A`"));&]
|
||||||
|
[s4; -|DUMP(am.Find(`"B`"));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; am.Get(`"A`") `= 11&]
|
||||||
|
[s6; am.Find(`"B`") `= 1&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 11. One&]
|
||||||
|
[s3; One is a container that can store none or one element of T or
|
||||||
|
derived from T. It functionally quite similiar to std`::unique`_ptr,
|
||||||
|
but has some more convenient features (like Create method).&]
|
||||||
|
[s4; struct Base `{&]
|
||||||
|
[s4; -|virtual String Get() `= 0;&]
|
||||||
|
[s4; -|virtual `~Base() `{`}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; struct Derived1 : Base `{&]
|
||||||
|
[s4; -|virtual String Get() `{ return `"Derived1`"; `}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; struct Derived2 : Base `{&]
|
||||||
|
[s4; -|virtual String Get() `{ return `"Derived2`"; `}&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; void MakeDerived1(One<Base>`& t)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|t.Create<Derived1>();&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; void MakeDerived2(One<Base>`& t)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|t.Create<Derived2>();&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; .......&]
|
||||||
|
[s4; -|[* One]<Base> s;&]
|
||||||
|
[s3; Operator bool of one returns true if it contains an element:&]
|
||||||
|
[s4; -|DUMP([* (bool)]s);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; (bool)s `= false&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|s.[* Create]<Derived1>();&]
|
||||||
|
[s4; -|DUMP((bool)s);&]
|
||||||
|
[s4; -|DUMP(s`->Get());&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; (bool)s `= true&]
|
||||||
|
[s6; s`->Get() `= Derived1&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; Clear method removes the element from One:&]
|
||||||
|
[s4; -|s.[* Clear]();&]
|
||||||
|
[s4; -|DUMP((bool)s);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; (bool)s `= false&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; &]
|
||||||
|
[s3; One represents a convenient and recommended method how to deal
|
||||||
|
with class factories in U`+`+: Define them as a function (or method)
|
||||||
|
with reference to One parameter, e.g.:&]
|
||||||
|
[s4; void MakeDerived1(One<Base>`& t)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|t.Create<Derived1>();&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; void MakeDerived2(One<Base>`& t)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|t.Create<Derived2>();&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; VectorMap<int, void (`*)(One<Base>`&)> factories;&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; INITBLOCK `{&]
|
||||||
|
[s4; -|factories.Add(0, MakeDerived1);&]
|
||||||
|
[s4; -|factories.Add(1, MakeDerived2);&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; void Create(One<Base>`& t, int what)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|(`*factories.Get(what))(t);&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 12. Any&]
|
||||||
|
[s3; Any is a container that can contain none or one element of [*/ any]
|
||||||
|
type, the only requirement is that the type has default constructor.
|
||||||
|
Important thing to remember is that [* Is] method matches [/ exact]
|
||||||
|
type ignoring class hierarchies (FileIn is derived from Stream,
|
||||||
|
but if Any contains FileIn, Is<Stream>() returns false).&]
|
||||||
|
[s4; void Do([* Any]`& x)&]
|
||||||
|
[s4; `{&]
|
||||||
|
[s4; -|if(x.[* Is]<String>())&]
|
||||||
|
[s4; -|-|LOG(`"String: `" << x.[* Get]<String>());&]
|
||||||
|
[s4; -|if(x.[* Is]<FileIn>()) `{&]
|
||||||
|
[s4; -|-|LOG(`"`-`-`- File: `");&]
|
||||||
|
[s4; -|-|LOG(LoadStream(x.[* Get]<FileIn>()));&]
|
||||||
|
[s4; -|-|LOG(`"`-`-`-`-`-`-`-`-`-`-`");&]
|
||||||
|
[s4; -|`}&]
|
||||||
|
[s4; -|if(x.[* IsEmpty]())&]
|
||||||
|
[s4; -|-|LOG(`"empty`");&]
|
||||||
|
[s4; `}&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; .....&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Any x;&]
|
||||||
|
[s4; -|x.[* Create]<String>() `= `"Hello!`";&]
|
||||||
|
[s4; -|Do(x);&]
|
||||||
|
[s4; -|x.[* Create]<FileIn>().Open(GetDataFile(`"Ntl12.cpp`"));&]
|
||||||
|
[s4; -|Do(x);&]
|
||||||
|
[s4; -|x.[* Clear]();&]
|
||||||
|
[s4; -|Do(x);&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 13. InVector, InArray&]
|
||||||
|
[s3; InVector and InArray are vector types quite similar to Vector/Array,
|
||||||
|
but they trade the speed of operator`[`] with the ability to
|
||||||
|
insert or remove elements at any position quickly. You can expect
|
||||||
|
operator`[`] to be about 10 times slower than in Vector (but
|
||||||
|
that is still very fast), while Insert at any position scales
|
||||||
|
well up to hundreds of megabytes of data (e.g. InVector containing
|
||||||
|
100M of String elements is handled without problems).&]
|
||||||
|
[s4; -|[* InVector]<int> v;&]
|
||||||
|
[s4; -|for(int i `= 0; i < 1000000; i`+`+)&]
|
||||||
|
[s4; -|-|v.Add(i);&]
|
||||||
|
[s4; -|v.[* Insert](0, `-1); // This is fast&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; While the interface of InVector/InArray is almost identical
|
||||||
|
to Vector/Array, InVector/InArray in addition implements FindLowerBound/FindUpper
|
||||||
|
Bound functions `- while normal random access algorithms work,
|
||||||
|
it is possible to provide InVector specific optimization that
|
||||||
|
basically matches the performace of Find`*Bound on sample Vector.&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(v.[* FindLowerBound](55));&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 14. SortedIndex, SortedVectorMap, SortedArrayMap&]
|
||||||
|
[s3; SortedIndex is similar to regular Index, but keeps its elements
|
||||||
|
in sorted order (sorting predicate is a template parameter, defaults
|
||||||
|
to StdLess). Implementation is using InVector, so it works fine
|
||||||
|
even with very large number of elements (performance is similar
|
||||||
|
to tree based std`::set). Unlike Index, SortedIndex provides lower/upper
|
||||||
|
bounds searches, so it allow range search.&]
|
||||||
|
[s4; -|[* SortedIndex]<int> x;&]
|
||||||
|
[s4; -|x.Add(5);&]
|
||||||
|
[s4; -|x.Add(3);&]
|
||||||
|
[s4; -|x.Add(7);&]
|
||||||
|
[s4; -|x.Add(1);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|DUMPC(x);&]
|
||||||
|
[s4; -|DUMP(x.[* Find](3));&]
|
||||||
|
[s4; -|DUMP(x.[* Find](3));&]
|
||||||
|
[s4; -|DUMP(x.[* FindLowerBound](3));&]
|
||||||
|
[s4; -|DUMP(x.[* FindUpperBound](6));&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; SortedVectorMap and SortedArrayMap are then SortedIndex based
|
||||||
|
equivalents to VectorMap/ArrayMap `- maps that keep keys sorted:&]
|
||||||
|
[s4; -|[* SortedVectorMap]<String, int> m;&]
|
||||||
|
[s4; -|m.Add(`"zulu`", 11);&]
|
||||||
|
[s4; -|m.Add(`"frank`", 12);&]
|
||||||
|
[s4; -|m.Add(`"alfa`", 13);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|DUMPM(m);&]
|
||||||
|
[s4; -|DUMP(m.[* Get](`"zulu`"));&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 15. Tuples&]
|
||||||
|
[s3; U`+`+ has template classes Tuple2, Tuple3 and Tuple4 for combining
|
||||||
|
2`-4 values with different types. These are quite similiar to
|
||||||
|
std`::tuple class, with some advantages.&]
|
||||||
|
[s3; To create a Tuple value, you can use the Tuple function. If
|
||||||
|
correct types canot be deduced from parameters, you can specify
|
||||||
|
them explicitly:&]
|
||||||
|
[s4; -|Tuple3<int, String, String> x `= [* Tuple]<int, String, String>(12,
|
||||||
|
`"hello`", `"world`");&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; Individual values are accessible as members a .. d:&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(x.a);&]
|
||||||
|
[s4; -|DUMP(x.b);&]
|
||||||
|
[s4; -|DUMP(x.c);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s3; As long as all individual types have conversion to String (AsString),
|
||||||
|
the tuple also has such conversion and thus can e.g. be easily
|
||||||
|
logged:&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(x);&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; Also, as long as individual types have defined GetHashValue,
|
||||||
|
so does Tuple:&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(GetHashValue(x));&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; As long as individual types have defined operator`=`=, Tuple
|
||||||
|
has defined operator`=`= and operator!`=&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|Tuple3<int, String, String> y `= x;&]
|
||||||
|
[s4; -|DUMP(x !`= y);&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; Finally, as long as all individual types have defined SgnCompare
|
||||||
|
(most U`+`+ types have), Tuple has SgnCompare, Compare method
|
||||||
|
and operators <, <`=, >, >`=:&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|DUMP(x.Compare(y));&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|y.b `= `"a`";&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|DUMP(SgnCompare(x, y));&]
|
||||||
|
[s4; -|DUMP(x < y);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; &]
|
||||||
|
[s3; U`+`+ Tuples are strictly designed as POD type, which allows
|
||||||
|
POD arrays to be intialized with classic C style:&]
|
||||||
|
[s4; -|static Tuple2<int, const char `*> map`[`] `= `{&]
|
||||||
|
[s4; -|-|`{ 1, `"one`" `},&]
|
||||||
|
[s4; -|-|`{ 2, `"one`" `},&]
|
||||||
|
[s4; -|-|`{ 3, `"one`" `},&]
|
||||||
|
[s4; -|`};&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s3; &]
|
||||||
|
[s3; Simple FindTuple template function is provided to search for
|
||||||
|
tuple based on the first value:&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; 15. Sorting&]
|
||||||
|
[s3; IndexSort is sort variant that is able to sort two random access
|
||||||
|
container (like Vector or Array) of the same size, based on values
|
||||||
|
in on of containers:&]
|
||||||
|
[s4; -|Vector<int> a;&]
|
||||||
|
[s4; -|Vector<String> b;-|&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|a << 5 << 10 << 2 << 9 << 7 << 3;&]
|
||||||
|
[s4; -|b << `"five`" << `"ten`" << `"two`" << `"nine`" << `"seven`"
|
||||||
|
<< `"three`";&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|[* IndexSort](a, b);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; a `= `[2, 3, 5, 7, 9, 10`]&]
|
||||||
|
[s6; b `= `[two, three, five, seven, nine, ten`]&]
|
||||||
|
[s4; &]
|
||||||
|
[s4; -|[* IndexSort](b, a);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; a `= `[5, 9, 7, 10, 3, 2`]&]
|
||||||
|
[s6; b `= `[five, nine, seven, ten, three, two`]&]
|
||||||
|
[s3; Order of sorted items is defined by sorting predicate. By default,
|
||||||
|
operator< comparing items of container is used (this predicate
|
||||||
|
can be provided by StdLess template), but it is possible to specify
|
||||||
|
different sorting order, e.g. by using predefined StdGreater
|
||||||
|
predicate:&]
|
||||||
|
[s4; -|Sort(a, [* StdGreater]<int>());&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; a `= `[10, 9, 7, 5, 3, 2`]&]
|
||||||
|
[s3; Sometimes, instead of sorting items in the container, it is
|
||||||
|
useful to know the order of items as sorted, using GetSortOrder:&]
|
||||||
|
[s4; -|Vector<int> o `= [* GetSortOrder](a);&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; o `= `[5, 4, 3, 2, 1, 0`]&]
|
||||||
|
[s3; FieldRelation predefined predicate can be used to sort container
|
||||||
|
of structures by specific field:&]
|
||||||
|
[s4; -|Vector<Point> p;&]
|
||||||
|
[s4; -|p << Point(5, 10) << Point(7, 2) << Point(4, 8) << Point(1,
|
||||||
|
0);&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|Sort(p, [* FieldRelation](`&Point`::x, StdLess<int>()));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; p `= `[`[1, 0`], `[4, 8`], `[5, 10`], `[7, 2`]`]&]
|
||||||
|
[s3; MethodRelation is good for sorting of structures based on constant
|
||||||
|
method of structure:-|&]
|
||||||
|
[s4; struct Foo `{&]
|
||||||
|
[s4; -|String a;&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|int [* Get]() const `{ return atoi(a); `}&]
|
||||||
|
[s4; -|....&]
|
||||||
|
[s4; `};&]
|
||||||
|
[s4; ....&]
|
||||||
|
[s4; -|Array<Foo> f;&]
|
||||||
|
[s4; -|f << `"12`" << `"1`" << `"10`" << `"7`" << `"5`";&]
|
||||||
|
[s4; -|&]
|
||||||
|
[s4; -|Sort(f, [* MethodRelation](`&Foo`::[* Get], StdLess<int>()));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; f `= `[1, 5, 7, 10, 12`]&]
|
||||||
|
[s3; Normal Sort is not stable `- equal items can appear in sorted
|
||||||
|
sequence in random order. If maintaining original order of equal
|
||||||
|
items is important, use StableSort variant (with slight performance
|
||||||
|
penalty):&]
|
||||||
|
[s4; -|Vector<Point> t;&]
|
||||||
|
[s4; -|t << Point(10, 10) << Point(7, 1) << Point(7, 2) << Point(7,
|
||||||
|
3) << Point(1, 0);&]
|
||||||
|
[s4; -|[* StableSort](t, FieldRelation(`&Point`::x, StdLess<int>()));&]
|
||||||
|
[s4; &]
|
||||||
|
[s6; t `= `[`[1, 0`], `[7, 1`], `[7, 2`], `[7, 3`], `[10, 10`]`]&]
|
||||||
|
[s3; &]
|
||||||
|
[s2; Recommended tutorials:&]
|
||||||
|
[s3; If you want to learn more, we have several tutorials that you
|
||||||
|
can find useful:&]
|
||||||
|
[s3;l160;i150;O0; [^topic`:`/`/Core`/srcdoc`/CoreTutorial`$en`-us^ U`+`+
|
||||||
|
Core value types] `- if you miss this one`- it it a good occasion
|
||||||
|
to catch up. Here you will learn about Core basics.&]
|
||||||
|
[s0; [^topic`:`/`/Sql`/srcdoc`/tutorial`$en`-us^ SQL] `- learn how
|
||||||
|
to use databases with U`+`+ framework.&]
|
||||||
|
[s0; ]]
|
||||||
5
tutorial/CoreTutorial/init
Normal file
5
tutorial/CoreTutorial/init
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#ifndef _tutorial2_icpp_init_stub
|
||||||
|
#define _tutorial2_icpp_init_stub
|
||||||
|
#include "Core/init"
|
||||||
|
#include "RichEdit/init"
|
||||||
|
#endif
|
||||||
41
tutorial/CoreTutorial/tutorial2.cpp
Normal file
41
tutorial/CoreTutorial/tutorial2.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include "Tutorial.h"
|
||||||
|
|
||||||
|
GUI_APP_MAIN
|
||||||
|
{
|
||||||
|
DO(Logging);
|
||||||
|
DO(StringTutorial);
|
||||||
|
DO(StringBufferTutorial);
|
||||||
|
DO(WStringTutorial);
|
||||||
|
DO(DateTime);
|
||||||
|
DO(AsStringTutorial);
|
||||||
|
DO(ValueTutorial);
|
||||||
|
DO(NullTutorial);
|
||||||
|
DO(Value2Tutorial);
|
||||||
|
DO(CombineHashTutorial);
|
||||||
|
DO(ComparableTutorial);
|
||||||
|
|
||||||
|
DO(Vector1);
|
||||||
|
DO(Vector2);
|
||||||
|
DO(Transfer);
|
||||||
|
DO(ContainerClientTypes);
|
||||||
|
DO(ArrayTutorial);
|
||||||
|
DO(PolyArray);
|
||||||
|
|
||||||
|
DO(Bidirectional);
|
||||||
|
|
||||||
|
DO(IndexTutorial);
|
||||||
|
DO(IndexClient);
|
||||||
|
DO(Map);
|
||||||
|
DO(OneTutorial);
|
||||||
|
DO(AnyTutorial);
|
||||||
|
DO(InVectorTutorial);
|
||||||
|
DO(SortedMap);
|
||||||
|
DO(TupleTutorial);
|
||||||
|
|
||||||
|
DO(Range);
|
||||||
|
DO(Sorting);
|
||||||
|
|
||||||
|
DO(FunctionTutorial);
|
||||||
|
|
||||||
|
MakeTutorial();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue