U++ Core value types tutorial
1. String
String is a value type useful for storing text or binary data. Content of String can be also obtained in form zero terminated const char *ptr (valid till the next mutating operation only.
You can assign a C string literal to String
String a;
a = "Hello";
a = Hello
concatenate with another String or literal
a = a + " world";
a = Hello world
or single character or specified number of characters from another String or literal
a.Cat('!');
a = Hello world!
a.Cat("ABCDEFGHIJKLM", 3);
a = Hello world!ABC
Clear method empties the String
a.Clear();
You can use operator<< to append to existing String. Note that this is more efficient form than operator+ as String is not required to be assigned a temporary. Also, U++ provides extensible framework for converting values to String
for(int i = 0; i < 10; i++)
a << i << ", ";
a = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
Sometimes is is useful to use operator<< to produce a temporary String value (e.g. as real argument to function call).
String().Cat() << "Number is " << 123 << "."
Note: This strange special Cat method is needed because C++ does not allow non-cont references to temporary objects.
Number is 123.
String provides methods for obtaining character count, inserting characters into String or removing them
a = "0123456789";
a.GetLength() = 10
a.Insert(6, "<inserted>");
a = 012345<inserted>6789
a.Remove(2, 2);
a = 0145<inserted>6789
as well as a couple of searching and comparing methods
a.Find('e')
a.Find('e') = 8
a.ReverseFind('e')
a.ReverseFind('e') = 11
a.StartsWith("ABC")
a.StartsWith("ABC") = false
a.EndsWith("KLM")
a.EndsWith("KLM") = false
a.Find("ted")
a.Find("ted") = 10
You can get slice of String using Mid method; with single parameter it provides slice to the end of String
a.Mid(3, 3)
a.Mid(3, 3) = 5<i
a.Mid(3)
a.Mid(3) = 5<inserted>6789
You can also decrease the length of String using Trim
a.Trim(4);
a = 0145
You can obtain int values of individual characters using operator[]
a[0]
a[0] = 48
or the value of first character using operator* (note that if GetLengt() == 0, this will return zero terminator)
DUMP(*a);
*a = 48
2. 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
void CApiFunction(char *c)
{
strcpy(c, "Hello");
}
..........
StringBuffer b;
b.SetLength(200);
CApiFunction(b);
b.Strlen();
String x = b;
x = Hello
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 about altering existing String
b = x;
b[1] = 'a';
x = b;
x = Hallo
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;
x = Hallo!
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.
3. 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.
WString x = "characters 280-300: ";
for(int i = 280; i < 300; i++)
x.Cat(i);
x = characters 280-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī
String y = x.ToString();
DUMP(y);
y = characters 280-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī
y.Cat(" (appended)");
x = y.ToWString();
x = characters 280-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī (appended)
(Note: y content is displayed as result of conversion from utf-8 encoded data)
4. Date and Time
To represent date and time, U++ provides Date and Time concrete types.
Date date = GetSysDate();
date = 01/21/2007
All data members of Date structure are public:
date.year = 2007
date.month = 1
date.day = 21
Dates can be compared:
date > Date(1970, 1, 1) = true
Adding a number to Date adds a number of days to it:
date + 1 = 01/22/2007
Subtraction of dates yields a number of days between them:
date - Date(1970, 1, 1) = 13534
U++ defines the beginning and the end of era, most algorithms can safely assume that as minimal and maximal values Date can represent:
Date::Low() = 01/01/-4000
Date::High() = 01/01/4000
Time is derived from Date, adding members to represent time:
Time time = GetSysTime();
time = 01/21/2007 23:28:59
(Date&)time = 01/21/2007
(int)time.hour = 23
(int)time.minute = 28
(int)time.second = 59
Times can be compared:
time > Time(1970, 0, 0) = true
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:
time > date = false
time > ToTime(date) = true
Like Date, Time supports add and subtract operations, but numbers represent seconds (using int64 datatype):
time + 1 = 01/21/2007 23:29:00
time + 24 * 3600 = 01/22/2007 23:28:59
time - date = 0
time - ToTime(date) = 84539
Time also defines era limits:
Time::Low() = 01/01/-4000 00:00:00
Time::High() = 01/01/4000 00:00:00
5. 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++ headers):
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;
}
Client types have to either define String ToString method (usually more convenient) or specialize AsString template. 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();
sout = 1.23 01/22/2007 01/22/2007 17:58:58
Getting client types involved into this schema is not too difficult (example shows both methods):
struct BinFoo {
int x;
String ToString() const { return FormatIntBase(x, 2); }
BinFoo(int x) : x(x) {}
};
struct RomanFoo {
int x;
RomanFoo(int x) : x(x) {}
};
namespace Upp {
template <>
String Upp::AsString(const RomanFoo& a) { return FormatIntRoman(a.x); }
};
.......
sout << BinFoo(30) << ' ' << RomanFoo(30);
sout = 11110 xxx
6. Value
U++ provides one special value type, Value, that can be used to store and retrieve other values.
Value a = 1;
Value b = 2.34;
Value c = GetSysDate();
Value d = "hello";
Usually, value types define typecast operator to Value and constructor from Value, so that interaction is for the most part seamless:
int x = a;
double y = b;
Date z = c;
String s = d;
x = 1
y = 2.34
z = 01/24/2007
s = hello
As for primitive types, Value seamlessly works with int, int64, bool and double.
Casting Value to a type that it does not contain causes runtime error. On the other hand, conversion between related types is possible:
double i = a;
int j = b;
Time k = c;
WString t = d;
i = 1
j = 2
k = 01/24/2007
t = hello
To determine type of value stored in Value, you can use Is method:
a.Is<int>() = true
a.Is<double>() = false
b.Is<double>() = true
c.Is<int>() = false
c.Is<Date>() = true
d.Is<String>() = true
Note that Is tests for absolute type match, not for compatible types. For that reason, for widely used compatible types utility functions are defined:
IsNumber(a) = true
IsNumber(b) = true
IsDateTime(c) = true
IsString(d) = true
7. 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();
IsNull(x) = true
IsNull(y) = false
IsNull(d) = true
e > d = true
C++ language note: 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;
e = v;
IsNull(e) = true
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;
Nvl(a, b, c) = 123
8. 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:
struct RawFoo {
String x;
};
(int this case, default copy constructor and assignment operator are provided by compiler).
RawFoo h;
h.x = "hello";
Value q = RawToValue(h);
q.Is<Foo>() = true
q.To<Foo>().x = "hello"
RichValue level Values provide more operations for Value - equality test, IsNull test, hashing, conversion to text and serialization. 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() {}
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); }
};
INITBLOCK {
Value::Register<Foo>();
}
.......
Value a = RichToValue(Foo(54321));
Value b = RichToValue(Foo(54321));
(a == b) = true
IsNull(a) = false
v.Get<Foo>() = 54321
String s = StoreAsString(a);
LoadFromString(v, s);
v.Get<Foo>() = 54321
To avoid RichToValue and ValueTo calls, the client type can also provide constructor from Value and cast operator to Value:
struct Foo : ValueType<Foo, 10010> {
int x;
......
operator Value() { return RichToValue(*this); }
Foo(const Value& v) { *this = v.Get<Foo>(); }
};
......
Value c = Foo(321);
Foo x = c;
x = 123
9. 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;
GetHashValue(x) = 4272824901
x.a << '!';
GetHashValue(x) = 3378606405
Recommended tutorials:
If you want to learn more, we have several tutorials that you can find useful:
Containers Tutorial - everything you should know about NTL (Not standard Template Library) containers. This is the natural continuation of issues raised in this article.
GUI tutorial - learn about creating GUI application with U++.
|