Participate in the topic discussion at the end of the article and give asynchronous books every day

— Asynchronous small editor

As software developers, when faced with new tasks and challenges, we often break them down into solutions and snippets of code that we are familiar with, and develop the fastest solution based on customer needs and deadlines. However, doing so is simply fulfilling the requirements of the job, and sometimes not as helpful as one might think in learning more development skills and ideas to become a better, more effective developer.

Why learn data structures? In the early days of computer development, the main purpose of using computers was to deal with numerical problems. Using a computer to solve a specific problem generally needs to go through the following steps: first abstracts the appropriate mathematical model from the specific problem, and then designs or selects the algorithm to solve this mathematical model, and then writes the program and carries on debugging, testing, until finally gets the solution.

Because the operations initially involved were simple integer, real, or Boolean data, the programmer concentrated on the mechanics of the program rather than on the data structure. With the expansion of computer applications and the development of hardware and software, non-numerical computing problems become more and more important. According to statistics, more than 90% of machine time is now spent dealing with non-numerical problems. Such problems involve more complex data structures, and the relationships between data elements generally cannot be described by mathematical equations. Therefore, the key to solving such problems is no longer mathematical analysis and calculation methods, but to design appropriate data structures.

Professor N.Wirth, a famous Swiss computer scientist, once put forward:

Algorithm + data structure = program

The essence of programming is to design/choose good data structures and good algorithms for real problems, and good algorithms depend largely on the data structures that describe real problems.

Data types: Basic data structures

It may be a misnomer to call data types basic data structures, but developers tend to use them to build their own classes and data sets, so from their point of view, it’s no mistake. So, before we go any further with data structures, it’s a good idea to quickly review data types.

1.1 Numerical data types

A book could be written on all numeric data types in C#, Java, Objective-C, and Swift. Here, we review only the numeric data type identifiers most commonly used in each language. The easiest way to evaluate these data types is to give examples for each language based on their actual data size and analyze them within the same framework.

It looks the same, but it’s different!

When developing applications on multiple mobile platforms, be aware that different languages may share the same/set of data type identifiers or keywords, but these identifiers are not necessarily equivalent from a low-level perspective. Similarly, the same data type may have different identifiers in different languages. Take the 16-bit unsigned integer (16-bit unsigned INTEGER), which is called an unsigned short type in Objective-C. However, in C# or Swift, it is represented by ushort type and UInt16 type respectively. Java specifies that 16-bit unsigned integers can only be used as char, although this type is not usually used for numeric data. Each of these data types represents a 16-bit unsigned integer with a different name. This may not seem like a big deal, but when you’re developing for multiple devices using the native languages of each platform separately, it’s important to be aware of this difference for consistency. Otherwise, there is a risk of specific bugs/bugs on different platforms that can be very difficult to detect and judge.

1.1.1 integer

An integer data type is defined as representing an integer that is signed (negative, zero, or positive) or unsigned (zero or positive). For integers, each language has its own specific identifiers and keywords, so it’s easiest to think in terms of storage length. For our purposes, we will discuss only integers representing 8, 16, 32, and 64 bit storage objects.

The 8-bit data type, or collectively called byte, is the smallest data type we have explored. If you’ve reviewed binary math, you know that an 8-bit memory block can represent either 28 or 256 values. The value of the signed byte ranges from −128 to 127 or from −27 to 27−1. The value of an unsigned byte ranges from 0 to 255 or from 0 to 28−1.

With some exceptions, 16-bit data types are usually called short. This data type can represent 216 values. The value of the signed short integer ranges from −215 to 215−1. The value of an unsigned short integer ranges from 0 to 216−1.

32-bit data types are generally thought of as integers and sometimes as long integers. The integer can represent 232 values. The value of a signed integer ranges from −231 to 231−1. The value of an unsigned integer ranges from 0 to 232−1.

Finally, 64-bit data types are generally thought of as long integers, whereas in Objective-C they are defined as long longs. Long integers can represent 264 values. The value of a signed long integer ranges from −263 to −1. The value of an unsigned long integer ranges from 0 to 264−1.

Note that the values mentioned above are consistent in the four languages we use, but may vary slightly in other languages. It is always a good idea to familiarize yourself with the nuts and bolts of numerical identifiers in your language, especially if you need to specify extreme values using identifiers.

C

C# uses integers to represent integer types. It provides both BYTE and Sbyte mechanisms for generating 8-bit integers. Both integers can represent 256 values, ranging from 0 to 255 unsigned bytes. Signed bytes support negative values. Therefore, the value ranges from −128 to 127.

Interestingly, C# has changed its naming pattern for longer identifiers. It USES
uAs an unsigned prefix instead of using
sbyteIn the
sAs a prefix for signed. So C# is used separately
short.
ushort;
int.
uint;
long.
Ulong asThe code for 16 -, 32 -, and 64-bit integer identifiers is as follows.



Java

Java uses integral types as part of its raw data types. Java provides only one way to create 8-bit types, called byte. This is a signed data type and can represent the value from −127 to 128. Java also provides a wrapper class called Byte, which not only wraps raw values, but also provides additional constructor support for parsed strings or text like “42” that can be converted to numeric values. This pattern is repeated in 16-bit, 32-bit, and 64-bit types.

Java and C# share identifiers for all integer types, which means Java also provides byte, short, int, and long identifiers for 8-bit, 16-bit, 32-bit, and 64-bit types. The only exception in Java is the char identifier for a 16-bit unsigned data type. Note that the char type is usually only used to assign ASCII characters, not actual integer values.



In the above code, be careful
intThe type and
IntegerClass. Unlike other raw wrapper classes,
IntegerDoes not share names with its supported identifiers.

Also, note the long type and its assigned value. In each case, the values have the suffix L. This is a Java requirement for long because the compiler translates all numeric literals to 32-bit integers by default. When you want to specify that a literal value is longer than 32 bits, you must add the suffix L to it. Otherwise, the compiler may report an error. However, there is no such restriction when passing a string value to the Long constructor:

Objective-C

For 8-bit data, Objective-C provides char types in both signed and unsigned formats. The value of the signed data type ranges from −127 to 128, whereas the value of the unsigned data type ranges from 0 to 255. Developers can also choose to use objective-C’s fixed-width integer int8_t and uint8_t. This pattern is repeated in 16-bit, 32-bit, and 64-bit types. Finally, Objective-C also provides an object-oriented wrapper class for each type of integer in the form of an NSNumber class.

Char or any other integer is very different from its fixed-width integer. With the exception of the char type, which is always 1 byte long, the length of integers in Objective-C varies depending on the implementation and the underlying architecture. This is because Objective-C is based on C, which is designed to work efficiently on different kinds of underlying architectures. Although you can determine the exact length of an integer data structure at run and compile time, in general you can only determine short <= int <= long <= long long.

This is where fixed-width integers come in handy. The (u)int

_t integer allows you to define exactly 8 -, 16 -, 32 -, or 64 – bit integers in cases where strict control is required over the length of bytes.

As you can see from the above example, when using the char type in code, you must specify the identifier unsigned, such as unsigned char. Signed is the default identifier for char and can be omitted, which also means that char is equivalent to signed char. Other integral types in Objective-C follow this pattern.

Larger integers in Objective-C include short for 16 bits, int for 32 bits, and long long for 64 bits. Each of the above integers has a fixed-width type according to the pattern (u)int

_t. NSNumber provides support methods for each type of integer.



Swift

The Swift language, like other languages, provides separate identifiers for both signed and unsigned integers, such as Int8 and UInt8. Identifying the applicable data type by identifier name applies to each of Swift’s integer types, making Swift perhaps the simplest language.

To clearly illustrate the declaration process, the above example explicitly declares the data type using the :Int8 and :UInt8 identifiers. In Swift, you can also omit these identifiers and allow Swift to infer data types dynamically at run time.




Why do I need to know this? Why, you may ask, do I need to know such intricate details about data types? Can’t I just declare an int object or something like that and write some interesting code? Modern computers and even mobile devices offer almost unlimited resources, so it’s no big deal, right?

That’s not the case. In your daily programming experience, it’s probably ok to use either data type most of the time. A list of West Virginia DMV license plates issued on any given day, for example, could range from dozens to hundreds. You can use a short or a double long variable to control the for loop iteration. Either way, the impact of this loop on your system’s performance is negligible.

Suppose you are working with a set of data in which every discrete result matches a 16-bit type, and you habitually work with 32-bit types. What happens? The result is to double the amount of memory required to process this data set. This is probably fine when there are only 100 or 100,000 discrete results. But when you’re dealing with a large data set, a million or more discrete results, it can have a significant impact on system performance.

1.1.2 Single-precision floating point type

Single precision Floating point types are often called floats. 32-bit floating-point containers can store values with higher precision than integers, usually with 6 to 7 significant digits. Many languages use the float keyword/identifier to mark single-precision floating-point values, as do the four languages discussed in this book.

Note that since floating point values do not accurately represent numbers based on 10, their accuracy is limited by the zeroing error. Floating-point numerical algorithms are complex, and the details are irrelevant to most developers at any given time. However, learning floating point types can deepen your understanding of the underlying technology and the details of each language’s implementation.

. 8% \ skills. Tif {}

Since I’m not an expert in this area, I’m just going to take a quick look at the science behind these types, not the math. I have listed some of the work of experts in this field in the additional resources at the end of this chapter, which I strongly encourage you to follow.

C

C# uses the float keyword to identify 32-bit floating point values. The precision of float in C# is six significant digits, and the approximate value ranges from −3.4 x 1038 to +3.4 x 1038:

As you can see from the code above, the use of float suffixes f in assignment. This is because C#, like other C-based languages, defaults to a double (discussed later) when dealing with a decimal number to the right of an assignment. If you assign a double-precision floating-point value directly to a single-precision floating-point type without f or the suffix f, you will get a compilation error.

Also, notice the zeroing error of the last bit. We assign PI of the 30 significant digits to piFloat. But since float can hold only six significant digits, all subsequent digits are cancelled out. If we kept the 6 significant digits of PI directly, we would get 3.141592, but because of the zeroing error, the actual floating-point value is 3.141593.

Java

Like C#, Java uses a float identifier to determine floating point values. In Java, the precision of float is 6 or 7 significant digits. The approximate value ranges from −3.4 x 1038 to +3.4 x 1038.

As you can see from the code above, floating-point assignment operations have an F suffix. This is because Java, like other C-based languages, defaults to a double when dealing with a decimal number to the right of an assignment statement. If you assign a double-precision floating-point value directly to a single-precision floating-point type without an f or f suffix, you will get a compilation error.

Objective-C

Objective-c uses float identifiers to determine floating point values. In Objective-C, the precision of the float type is six significant digits, and the approximate value ranges from −3.4 x 1038 to +3.4 x 1038:

As you can see from the code above, floating-point assignment operations have an F suffix. This is because Objective-C, like other C-based languages, defaults to a double when dealing with decimal numbers to the right of assignment statements. If you assign a double-precision floating-point value directly to a single-precision floating-point type without an f or f suffix, you will get a compilation error.

Also, notice the zeroing error of the last bit. We assign PI of the 30 significant digits to piFloat. But since float can hold only six significant digits, all subsequent digits are cancelled out. If we kept the 6 significant digits of PI directly, we would get 3.141592, but because of the zeroing error, the actual floating-point value is 3.141593.

Swift

Swift uses a float identifier to determine floating point values. In Swift, the precision of float type is six significant digits, and the approximate value ranges from −3.4 x 1038 to +3.4 x 1038:



As can be seen from the above code, the floating-point assignment operation has
fThe suffix. This is because Swift, like other C-based languages, defaults to a double when dealing with real numbers to the right of assignment statements. If it is not added in the assignment
for
FSuffix, while assigning a double-precision floating-point value directly to a single-precision floating-point type produces a compilation error.

Also, notice the zeroing error of the last bit. We assign the PI of the 30 significant digits to floatValue. But since float can hold only six significant digits, all subsequent digits are cancelled out. If we kept the 6 significant digits of PI directly, we would get 3.141 592, but because of the zeroing error, the actual floating-point value is 3.141 593.

1.1.3 Double-precision floating point type

The double precision floating point type is often called a double. 64-bit floating-point containers can store values with higher precision than integers, which typically have 15 significant digits. Multiple languages use the double keyword/identifier to mark double floating-point values, as do the four languages we discussed.

In most cases, it doesn’t matter whether you choose a float or a double, unless memory is tight, and float should be chosen whenever possible. Many people think that float is more efficient than double in most cases, and in general, it is. But in some cases, a double can be more efficient than a float. In fact, each type of efficiency varies from case to case because there are too many criteria to detail here. Therefore, if you want to achieve maximum efficiency in a particular application, you need to carefully study the various influences to choose the most appropriate type. If productivity isn’t that important, pick an appropriate type and get back to work.

C

C# uses the double keyword to identify 64-bit floating point values. In C#, the accuracy of double is 14 or 15 significant digits, ranging from ±5.0×10−324 to ±1.7×10308:

As you can see from the above code, the assignment operation of the wholeDouble variable is suffixed with d. This is because C#, like other C-based languages, defaults to integer numbers on the right side of assignment statements. If you try to assign an integer value directly to a double without the d or d suffix, you get a compilation error.

Also, notice the zeroing error of the last bit. We assign the value of PI to piDouble with 30 significant digits. But a double can only hold 14 significant digits, after which all digits are cancelled out. We get 3.141 592 653 589 793 if we keep 15 significant digits for the PI value directly, but because of the zeroing error, the actual floating-point value is 3.141 592 653 589 79.

Java

Java uses the double keyword to identify 64-bit floating point values. In Java, the precision of double is 15 or 16 significant digits, and the approximate value ranges from ±4.9×10−324 to ±1.8×10308:

Look at the code above and notice the zeroing error in the last bit. We assign the value of PI to piDouble with 30 significant digits. But a double can only hold 15 significant digits, after which all digits are cancelled out. We get 3.141 592 653 589 793 2 if we keep 15 significant digits directly for the value of PI, but because of the zeroing error, the actual floating-point value is 3.141 592 653 589 793.

Objective-C

Objective-c also uses the double identifier to determine 64-bit floating point values. In Objective-C, the precision of the double type is 15 significant digits, ranging from 2.3 x 10−308 to 1.7 x 10308. To further improve accuracy, Objective-C also provides a higher-precision version of the double type, called the long double. The long double type can store 80-bit floating point values with accuracy of 19-bit significant digits. The approximate values range from 3.4×10−4932 to 1.1×104932:

Look at the code above and notice the zeroing error in the last bit. We assign the value of PI to piDouble with 30 significant digits. But a double can only hold 15 significant digits, after which all digits are cancelled out. We get 3.141 592 653 589 793 2 if we keep 15 significant digits directly for the value of PI, but because of the zeroing error, the actual floating-point value is 3.141 592 653 589 793.

Swift

Swift uses the double identifier to determine 64-bit floating point values. In Swift, the precision of the double type is 15 significant digits, and the approximate value ranges from 2.3×10−308 to 1.7×10308. Note that, according to Apple’s Swift documentation, double is recommended when both float and double satisfy the requirements:

Look at the code above and note the zeroing error in the last bit. We assign PI of 30 significant digits to doubleValue. But since a double can hold only 15 significant digits, all subsequent digits are canceled out. We get 3.141 592 653 589 793 if we keep 15 significant digits for the PI value directly, but because of the zeroing error, the actual floating-point value is 3.141 592 653 589 79.

1.1.4 Currency Type

Because floating-point arithmetic is actually based on binary mathematics and is inherently inaccurate, the single-precision and double-precision floating-point types cannot accurately represent the decimal currency we use. At first glance, it may seem like a good idea to represent money as a single – or double-precision floating-point type, since it can eliminate small errors in the calculation process. However, when these inexact results are added to a large number of complex calculations, errors can accumulate, resulting in serious deviations and hard-to-track errors. This makes the single-precision and double-precision floating-point types impossible to use for decimal currencies that require near-perfect precision. Fortunately, the four languages we’ve discussed all provide mechanisms for money and other mathematical problems that require high-precision decimal operations.

C

C# uses the decimal keyword to identify exact floating-point values. In C#, decimal has a precision of 28 or 29 significant digits and ranges from ±1.0×10−28 to ±7.9×1028:



In the code above, we assign the value of PI to 30 significant digits
decimal Value, but it retains only 28 significant digits.

Java

Java provides an object-oriented solution to the money class problem in the form of BigDecimal classes:



In the code above, we initialize a decimal value as a text parameter to the constructor
BigDecimalClass. The results show that the program runs
BigDecimalClass returns 30 significant digits with no loss of precision.

Objective-C

Objective-c provides an object-oriented solution to the money class problem in the form of the NSDecimalNumber class:



Swift

Swift provides an object-oriented solution to the money class problem with the class NSDecimalNumber of the same name as objective-C. This class initializes somewhat differently in Swift and Objective-C, but does the same thing.

Note that the output of both objective-C and Swift cases has 30 significant digits, indicating that the NSDecimal Number class is suitable for dealing with money and other decimal values.

Full disclosure, there is a simpler and arguably more elegant alternative to these custom types. We can use int or long for currency calculations, counting minutes instead of dollars:

1.1.5 Type Conversion

In computer science, type conversion or typecasting is the conversion of objects or data from one type to another. For example, you call a method that returns an integer, and you need to pass that value as an argument to another method, but the second method requires that the argument be a long integer. Since integer values are by definition within the range of values allowed by long integers, int values can be redefined as long.

Typically, type conversions can be made through implicit conversion or explicit conversion (also called coercion, explicit conversion). In addition, we need to understand the difference between static and dynamic languages to fully appreciate the meaning of type conversions.

1. Statically typed and dynamically typed languages

Statically typed languages perform Type checking at compile time. This means that when you try to generate a solution, the compiler checks and enforces constraints on all data types in your program. If the check fails, the build is stopped and an error is reported. C#, Java, and Swift are statically typed languages.

Dynamically typed languages, on the other hand, do most or all of their type checking at execution time. This means that if a developer is careless with the programming, the program might be fine during the build phase, but it could go wrong during execution. Objective-c is a dynamically typed language that mixes statically and dynamically typed objects. The pure C objects used to store numeric data discussed earlier in this chapter are statically typed, while the NSNumber and NSDecimalNumber classes in Objective-C are dynamically typed. Consider the following objective-C code example:

The compiler gives an error on the first line, which reads “Initializing ‘double’ with an expression of incompatible type ‘NSString *'”. This is because double is a static type of pure C. The compiler knows what to do with this static type even before it is generated, so the code fails the check.

For the second line, however, the compiler only issues a warning saying “Incompatible pointer types initializing ‘NSNumber *’ with an expression of type ‘NSString *'”. This is because objective-C’s NSNumber class is a dynamic type. The compiler is smart enough to detect errors, but will still allow the build to proceed (unless you instruct the compiler in your build Settings to treat warnings as errors).

Obviously, the previous example will run with an error, but in some cases your application will work even if there is a warning. However, no matter what language you’re using, it’s always a good idea to remove existing warnings and continue programming. This helps to keep the code clean and avoid runtime errors that are difficult to diagnose.

Sometimes warnings may not be handled in a timely manner, but you should clearly document the code and explain the source of the warning so that other developers can understand the context. If you have to, you can use macros and preprocessor (precompiler) commands to ignore warnings one by one.

2. Implicit conversion and explicit conversion

Type casting that does not require any special syntax in the source code is implicit casting. Implicit conversion is more convenient. Consider the following C# code example:

In the above example, due to
aIt can be defined as
intType, can also be defined as
doubleType, and both types are artificially defined, so you can place
aconvert
doubleType. However, because implicit conversions do not require an artificial type definition, the compiler may not be able to fully determine the constraints to which the conversions apply, so the compiler cannot perform a conversion check until the program runs. This makes implicit conversions risky. Consider the following C# code example:

The above example does not tell the compiler how to handle string values, so this is an implicit conversion. For application generation in this case, the compiler will report an error on this line of code saying”
Cannot implicitly convert type 'string' to 'double'“. Now, consider an explicit conversion of the same example:

Assuming that the string is parsed, the above conversion is explicit and therefore type-safe.

3. Indent and extend transformations

When the two types of data are converted, it is critical that the conversion result is within the range allowed by the target data structure. If the source data type has more bytes than the target data type, the type is converted to a narrowing conversion.

Limiting conversions are not always possible, and information is likely to be lost during conversions. For example, converting a floating-point type to an integer results in data loss (loss of precision), and the result is approximated to the nearest integer to the original value. In most statically typed languages, an indent conversion cannot be performed implicitly. Using the single and double types of C# code seen earlier in this chapter, convert the double indentation to single precision:

In this example, the compiler will report an error saying”
Cannot implicitly convert type 'double' to 'float'. And explicit conversion exists (Are you missing a cast?)“. The compiler notices this narrowing conversion and treats the loss of precision as an error. The error message suggests that we use an explicit conversion to resolve the problem.

We will now use an explicit conversion to
doubleThe type of
piDoubleConversion to single precision, the compiler will no longer report errors due to precision loss.

If the source data type is less than the number of bytes supported by the target data type, this type conversion is for extended conversion. The extended conversion preserves the value of the source type, but may change the way the value is represented. Most statically typed languages allow implicitly extended conversions. As an example of the previous C# code:

In this case, the implicit conversion does not cause an error from the compiler, and the application generates normally. To take this example a step further:

The above explicit conversion improves the reliability of the code, but does not change the nature of the statement in any way. Even though this is redundant, it does not cause the compiler to fail. In addition to improving reliability, the explicit extension transformation has no additional impact on the program. Therefore, you can choose to extend the transformation implicitly or explicitly according to your preference.

We looked at the basic data types provided by the four most common mobile development languages. You learned the characteristics and operations of numeric and floating point data types from the perspective of the underlying architecture and language specifications. We also learned how to convert objects from one type to another, and how to extend and limit conversions depending on the size of the source and target types in the conversions.

This article is excerpted from Programmer learning Data Structures



By John Z. Sonmez

Click on the link to buy the paper book


A practical guide to mastering data structures in objective-C, C#, Java and Swift

Today’s interactive

Do you think data structures are fundamental to programming? Deadline: 17:00 on June 30th, leave a message + forward this activity to the moments of friends, small editor will be lucky to select one reader to give a paper book and two e-reading version of the 20 yuan asynchronous community voucher, (the most like message will automatically get one).


Recommended reading

May 2018 Book List (bonus at the end)

A list of new books for April 2018

Asynchronous books the most complete Python book list

A list of essential algorithms books for programmers

The first Python neural network programming book






Long press the QR code, you can follow us yo

I share IT articles with you every day.


If you reply “follow” in the background of “Asynchronous books”, you can get 2000 online video courses for free

Programmers learn Data Structures

Read the original