mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 06:05:58 -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
dd88feaf2e
commit
6c22e727de
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